/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: Breezy landing bot
  • Author(s): Colin Watson
  • Date: 2020-11-16 21:47:08 UTC
  • mfrom: (7521.1.1 remove-lp-workaround)
  • Revision ID: breezy.the.bot@gmail.com-20201116214708-jos209mgxi41oy15
Remove breezy.git workaround for bazaar.launchpad.net.

Merged from https://code.launchpad.net/~cjwatson/brz/remove-lp-workaround/+merge/393710

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
24
22
import time
25
23
import codecs
26
24
 
27
 
from bzrlib.lazy_import import lazy_import
 
25
from .lazy_import import lazy_import
28
26
lazy_import(globals(), """
29
27
from datetime import datetime
30
28
import getpass
44
42
from tempfile import mkdtemp
45
43
import unicodedata
46
44
 
47
 
from bzrlib import (
48
 
    cache_utf8,
 
45
from breezy import (
49
46
    config,
50
 
    errors,
51
47
    trace,
52
48
    win32utils,
53
49
    )
54
 
from bzrlib.i18n import gettext
 
50
from breezy.i18n import gettext
55
51
""")
56
52
 
57
 
from bzrlib.symbol_versioning import (
58
 
    DEPRECATED_PARAMETER,
59
 
    deprecated_function,
60
 
    deprecated_in,
61
 
    deprecated_passed,
62
 
    warn as warn_deprecated,
63
 
    )
64
 
 
65
53
from hashlib import (
66
54
    md5,
67
55
    sha1 as sha,
68
56
    )
69
57
 
70
58
 
71
 
import bzrlib
72
 
from bzrlib import symbol_versioning, _fs_enc
73
 
 
74
 
 
75
 
# Cross platform wall-clock time functionality with decent resolution.
76
 
# On Linux ``time.clock`` returns only CPU time. On Windows, ``time.time()``
77
 
# only has a resolution of ~15ms. Note that ``time.clock()`` is not
78
 
# synchronized with ``time.time()``, this is only meant to be used to find
79
 
# delta times by subtracting from another call to this function.
80
 
timer_func = time.time
81
 
if sys.platform == 'win32':
82
 
    timer_func = time.clock
 
59
import breezy
 
60
from . import (
 
61
    _fs_enc,
 
62
    errors,
 
63
    )
 
64
 
83
65
 
84
66
# On win32, O_BINARY is used to indicate the file should
85
67
# be opened in binary mode, rather than text mode.
92
74
O_NOINHERIT = getattr(os, 'O_NOINHERIT', 0)
93
75
 
94
76
 
95
 
def get_unicode_argv():
96
 
    try:
97
 
        user_encoding = get_user_encoding()
98
 
        return [a.decode(user_encoding) for a in sys.argv[1:]]
99
 
    except UnicodeDecodeError:
100
 
        raise errors.BzrError(gettext("Parameter {0!r} encoding is unsupported by {1} "
101
 
            "application locale.").format(a, user_encoding))
 
77
class UnsupportedTimezoneFormat(errors.BzrError):
 
78
 
 
79
    _fmt = ('Unsupported timezone format "%(timezone)s", '
 
80
            'options are "utc", "original", "local".')
 
81
 
 
82
    def __init__(self, timezone):
 
83
        self.timezone = timezone
102
84
 
103
85
 
104
86
def make_readonly(filename):
105
87
    """Make a filename read-only."""
106
88
    mod = os.lstat(filename).st_mode
107
89
    if not stat.S_ISLNK(mod):
108
 
        mod = mod & 0777555
 
90
        mod = mod & 0o777555
109
91
        chmod_if_possible(filename, mod)
110
92
 
111
93
 
112
94
def make_writable(filename):
113
95
    mod = os.lstat(filename).st_mode
114
96
    if not stat.S_ISLNK(mod):
115
 
        mod = mod | 0200
 
97
        mod = mod | 0o200
116
98
        chmod_if_possible(filename, mod)
117
99
 
118
100
 
124
106
        # It is probably faster to just do the chmod, rather than
125
107
        # doing a stat, and then trying to compare
126
108
        os.chmod(filename, mode)
127
 
    except (IOError, OSError),e:
 
109
    except (IOError, OSError) as e:
128
110
        # Permission/access denied seems to commonly happen on smbfs; there's
129
111
        # probably no point warning about it.
130
112
        # <https://bugs.launchpad.net/bzr/+bug/606537>
146
128
        return set(paths)
147
129
 
148
130
    def sort_key(path):
149
 
        return path.split('/')
 
131
        if isinstance(path, bytes):
 
132
            return path.split(b'/')
 
133
        else:
 
134
            return path.split('/')
150
135
    sorted_paths = sorted(list(paths), key=sort_key)
151
136
 
152
137
    search_paths = [sorted_paths[0]]
179
164
 
180
165
_directory_kind = 'directory'
181
166
 
 
167
 
182
168
def get_umask():
183
169
    """Return the current umask"""
184
170
    # Assume that people aren't messing with the umask while running
213
199
            stat = getattr(os, 'lstat', os.stat)
214
200
            stat(f)
215
201
            return True
216
 
        except OSError, e:
 
202
        except OSError as e:
217
203
            if e.errno == errno.ENOENT:
218
 
                return False;
 
204
                return False
219
205
            else:
220
 
                raise errors.BzrError(gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
 
206
                raise errors.BzrError(
 
207
                    gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
221
208
 
222
209
 
223
210
def fancy_rename(old, new, rename_func, unlink_func):
247
234
    file_existed = False
248
235
    try:
249
236
        rename_func(new, tmp_name)
250
 
    except (errors.NoSuchFile,), e:
 
237
    except (errors.NoSuchFile,):
251
238
        pass
252
 
    except IOError, e:
 
239
    except IOError as e:
253
240
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
254
241
        # function raises an IOError with errno is None when a rename fails.
255
242
        # This then gets caught here.
256
243
        if e.errno not in (None, errno.ENOENT, errno.ENOTDIR):
257
244
            raise
258
 
    except Exception, e:
 
245
    except Exception as e:
259
246
        if (getattr(e, 'errno', None) is None
260
 
            or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
 
247
                or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
261
248
            raise
262
249
    else:
263
250
        file_existed = True
264
251
 
265
 
    failure_exc = None
266
252
    success = False
267
253
    try:
268
 
        try:
269
 
            # This may throw an exception, in which case success will
270
 
            # not be set.
271
 
            rename_func(old, new)
272
 
            success = True
273
 
        except (IOError, OSError), e:
274
 
            # source and target may be aliases of each other (e.g. on a
275
 
            # case-insensitive filesystem), so we may have accidentally renamed
276
 
            # source by when we tried to rename target
277
 
            failure_exc = sys.exc_info()
278
 
            if (file_existed and e.errno in (None, errno.ENOENT)
 
254
        # This may throw an exception, in which case success will
 
255
        # not be set.
 
256
        rename_func(old, new)
 
257
        success = True
 
258
    except (IOError, OSError) as e:
 
259
        # source and target may be aliases of each other (e.g. on a
 
260
        # case-insensitive filesystem), so we may have accidentally renamed
 
261
        # source by when we tried to rename target
 
262
        if (file_existed and e.errno in (None, errno.ENOENT)
279
263
                and old.lower() == new.lower()):
280
 
                # source and target are the same file on a case-insensitive
281
 
                # filesystem, so we don't generate an exception
282
 
                failure_exc = None
 
264
            # source and target are the same file on a case-insensitive
 
265
            # filesystem, so we don't generate an exception
 
266
            pass
 
267
        else:
 
268
            raise
283
269
    finally:
284
270
        if file_existed:
285
271
            # If the file used to exist, rename it back into place
288
274
                unlink_func(tmp_name)
289
275
            else:
290
276
                rename_func(tmp_name, new)
291
 
    if failure_exc is not None:
292
 
        try:
293
 
            raise failure_exc[0], failure_exc[1], failure_exc[2]
294
 
        finally:
295
 
            del failure_exc
296
277
 
297
278
 
298
279
# In Python 2.4.2 and older, os.path.abspath and os.path.realpath
320
301
    # as a special case here by simply removing the first slash, as we consider
321
302
    # that breaking POSIX compatibility for this obscure feature is acceptable.
322
303
    # This is not a paranoid precaution, as we notably get paths like this when
323
 
    # the repo is hosted at the root of the filesystem, i.e. in "/".    
 
304
    # the repo is hosted at the root of the filesystem, i.e. in "/".
324
305
    if path.startswith('//'):
325
306
        path = path[1:]
326
307
    return path
327
308
 
328
309
 
329
 
def _posix_path_from_environ(key):
330
 
    """Get unicode path from `key` in environment or None if not present
331
 
 
332
 
    Note that posix systems use arbitrary byte strings for filesystem objects,
333
 
    so a path that raises BadFilenameEncoding here may still be accessible.
334
 
    """
335
 
    val = os.environ.get(key, None)
336
 
    if val is None:
337
 
        return val
338
 
    try:
339
 
        return val.decode(_fs_enc)
340
 
    except UnicodeDecodeError:
341
 
        # GZ 2011-12-12:Ideally want to include `key` in the exception message
342
 
        raise errors.BadFilenameEncoding(val, _fs_enc)
343
 
 
344
 
 
345
310
def _posix_get_home_dir():
346
311
    """Get the home directory of the current user as a unicode path"""
347
312
    path = posixpath.expanduser("~")
348
313
    try:
349
314
        return path.decode(_fs_enc)
 
315
    except AttributeError:
 
316
        return path
350
317
    except UnicodeDecodeError:
351
318
        raise errors.BadFilenameEncoding(path, _fs_enc)
352
319
 
353
320
 
354
321
def _posix_getuser_unicode():
355
322
    """Get username from environment or password database as unicode"""
356
 
    name = getpass.getuser()
357
 
    user_encoding = get_user_encoding()
358
 
    try:
359
 
        return name.decode(user_encoding)
360
 
    except UnicodeDecodeError:
361
 
        raise errors.BzrError("Encoding of username %r is unsupported by %s "
362
 
            "application locale." % (name, user_encoding))
 
323
    return getpass.getuser()
363
324
 
364
325
 
365
326
def _win32_fixdrive(path):
377
338
 
378
339
def _win32_abspath(path):
379
340
    # Real ntpath.abspath doesn't have a problem with a unicode cwd
380
 
    return _win32_fixdrive(ntpath.abspath(unicode(path)).replace('\\', '/'))
381
 
 
382
 
 
383
 
def _win98_abspath(path):
384
 
    """Return the absolute version of a path.
385
 
    Windows 98 safe implementation (python reimplementation
386
 
    of Win32 API function GetFullPathNameW)
387
 
    """
388
 
    # Corner cases:
389
 
    #   C:\path     => C:/path
390
 
    #   C:/path     => C:/path
391
 
    #   \\HOST\path => //HOST/path
392
 
    #   //HOST/path => //HOST/path
393
 
    #   path        => C:/cwd/path
394
 
    #   /path       => C:/path
395
 
    path = unicode(path)
396
 
    # check for absolute path
397
 
    drive = ntpath.splitdrive(path)[0]
398
 
    if drive == '' and path[:2] not in('//','\\\\'):
399
 
        cwd = os.getcwdu()
400
 
        # we cannot simply os.path.join cwd and path
401
 
        # because os.path.join('C:','/path') produce '/path'
402
 
        # and this is incorrect
403
 
        if path[:1] in ('/','\\'):
404
 
            cwd = ntpath.splitdrive(cwd)[0]
405
 
            path = path[1:]
406
 
        path = cwd + '\\' + path
407
 
    return _win32_fixdrive(ntpath.normpath(path).replace('\\', '/'))
 
341
    return _win32_fixdrive(ntpath.abspath(path).replace('\\', '/'))
408
342
 
409
343
 
410
344
def _win32_realpath(path):
411
345
    # Real ntpath.realpath doesn't have a problem with a unicode cwd
412
 
    return _win32_fixdrive(ntpath.realpath(unicode(path)).replace('\\', '/'))
 
346
    return _win32_fixdrive(ntpath.realpath(path).replace('\\', '/'))
413
347
 
414
348
 
415
349
def _win32_pathjoin(*args):
417
351
 
418
352
 
419
353
def _win32_normpath(path):
420
 
    return _win32_fixdrive(ntpath.normpath(unicode(path)).replace('\\', '/'))
 
354
    return _win32_fixdrive(ntpath.normpath(path).replace('\\', '/'))
421
355
 
422
356
 
423
357
def _win32_getcwd():
424
 
    return _win32_fixdrive(os.getcwdu().replace('\\', '/'))
 
358
    return _win32_fixdrive(_getcwd().replace('\\', '/'))
425
359
 
426
360
 
427
361
def _win32_mkdtemp(*args, **kwargs):
436
370
    """
437
371
    try:
438
372
        fancy_rename(old, new, rename_func=os.rename, unlink_func=os.unlink)
439
 
    except OSError, e:
 
373
    except OSError as e:
440
374
        if e.errno in (errno.EPERM, errno.EACCES, errno.EBUSY, errno.EINVAL):
441
375
            # If we try to rename a non-existant file onto cwd, we get
442
376
            # EPERM or EACCES instead of ENOENT, this will raise ENOENT
447
381
 
448
382
 
449
383
def _mac_getcwd():
450
 
    return unicodedata.normalize('NFC', os.getcwdu())
 
384
    return unicodedata.normalize('NFC', _getcwd())
451
385
 
452
386
 
453
387
def _rename_wrap_exception(rename_func):
460
394
    def _rename_wrapper(old, new):
461
395
        try:
462
396
            rename_func(old, new)
463
 
        except OSError, e:
 
397
        except OSError as e:
464
398
            detailed_error = OSError(e.errno, e.strerror +
465
 
                                " [occurred when renaming '%s' to '%s']" %
466
 
                                (old, new))
 
399
                                     " [occurred when renaming '%s' to '%s']" %
 
400
                                     (old, new))
467
401
            detailed_error.old_filename = old
468
402
            detailed_error.new_filename = new
469
403
            raise detailed_error
470
404
 
471
405
    return _rename_wrapper
472
406
 
 
407
 
 
408
_getcwd = os.getcwd
 
409
 
 
410
 
473
411
# Default rename wraps os.rename()
474
412
rename = _rename_wrap_exception(os.rename)
475
413
 
479
417
realpath = _posix_realpath
480
418
pathjoin = os.path.join
481
419
normpath = _posix_normpath
482
 
path_from_environ = _posix_path_from_environ
483
420
_get_home_dir = _posix_get_home_dir
484
421
getuser_unicode = _posix_getuser_unicode
485
 
getcwd = os.getcwdu
 
422
getcwd = _getcwd
486
423
dirname = os.path.dirname
487
424
basename = os.path.basename
488
425
split = os.path.split
493
430
lstat = os.lstat
494
431
fstat = os.fstat
495
432
 
 
433
 
496
434
def wrap_stat(st):
497
435
    return st
498
436
 
501
439
 
502
440
 
503
441
if sys.platform == 'win32':
504
 
    if win32utils.winver == 'Windows 98':
505
 
        abspath = _win98_abspath
506
 
    else:
507
 
        abspath = _win32_abspath
 
442
    abspath = _win32_abspath
508
443
    realpath = _win32_realpath
509
444
    pathjoin = _win32_pathjoin
510
445
    normpath = _win32_normpath
512
447
    mkdtemp = _win32_mkdtemp
513
448
    rename = _rename_wrap_exception(_win32_rename)
514
449
    try:
515
 
        from bzrlib import _walkdirs_win32
 
450
        from . import _walkdirs_win32
516
451
    except ImportError:
517
452
        pass
518
453
    else:
528
463
        """
529
464
        exception = excinfo[1]
530
465
        if function in (os.remove, os.rmdir) \
531
 
            and isinstance(exception, OSError) \
532
 
            and exception.errno == errno.EACCES:
 
466
                and isinstance(exception, OSError) \
 
467
                and exception.errno == errno.EACCES:
533
468
            make_writable(path)
534
469
            function(path)
535
470
        else:
539
474
        """Replacer for shutil.rmtree: could remove readonly dirs/files"""
540
475
        return shutil.rmtree(path, ignore_errors, onerror)
541
476
 
542
 
    f = win32utils.get_unicode_argv     # special function or None
543
 
    if f is not None:
544
 
        get_unicode_argv = f
545
 
    path_from_environ = win32utils.get_environ_unicode
546
477
    _get_home_dir = win32utils.get_home_location
547
478
    getuser_unicode = win32utils.get_user_name
548
479
 
565
496
 
566
497
    :param trace: If True trace the selected encoding via mutter().
567
498
    """
568
 
    from bzrlib.trace import mutter
 
499
    from .trace import mutter
569
500
    output_encoding = getattr(sys.stdout, 'encoding', None)
570
501
    if not output_encoding:
571
502
        input_encoding = getattr(sys.stdin, 'encoding', None)
573
504
            output_encoding = get_user_encoding()
574
505
            if trace:
575
506
                mutter('encoding stdout as osutils.get_user_encoding() %r',
576
 
                   output_encoding)
 
507
                       output_encoding)
577
508
        else:
578
509
            output_encoding = input_encoding
579
510
            if trace:
580
511
                mutter('encoding stdout as sys.stdin encoding %r',
581
 
                    output_encoding)
 
512
                       output_encoding)
582
513
    else:
583
514
        if trace:
584
515
            mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
587
518
        output_encoding = get_user_encoding()
588
519
        if trace:
589
520
            mutter('cp0 is invalid encoding.'
590
 
               ' encoding stdout as osutils.get_user_encoding() %r',
591
 
               output_encoding)
 
521
                   ' encoding stdout as osutils.get_user_encoding() %r',
 
522
                   output_encoding)
592
523
    # check encoding
593
524
    try:
594
525
        codecs.lookup(output_encoding)
595
526
    except LookupError:
596
 
        sys.stderr.write('bzr: warning:'
 
527
        sys.stderr.write('brz: warning:'
597
528
                         ' unknown terminal encoding %s.\n'
598
529
                         '  Using encoding %s instead.\n'
599
530
                         % (output_encoding, get_user_encoding())
600
 
                        )
 
531
                         )
601
532
        output_encoding = get_user_encoding()
602
533
 
603
534
    return output_encoding
608
539
        F = realpath
609
540
    else:
610
541
        F = abspath
611
 
    [p,e] = os.path.split(f)
 
542
    [p, e] = os.path.split(f)
612
543
    if e == "" or e == "." or e == "..":
613
544
        return F(f)
614
545
    else:
630
561
    except OSError:
631
562
        return False
632
563
 
 
564
 
633
565
def islink(f):
634
566
    """True if f is a symlink."""
635
567
    try:
637
569
    except OSError:
638
570
        return False
639
571
 
 
572
 
640
573
def is_inside(dir, fname):
641
574
    """True if fname is inside dir.
642
575
 
652
585
    if dir == fname:
653
586
        return True
654
587
 
655
 
    if dir == '':
 
588
    if dir in ('', b''):
656
589
        return True
657
590
 
658
 
    if dir[-1] != '/':
659
 
        dir += '/'
 
591
    if isinstance(dir, bytes):
 
592
        if not dir.endswith(b'/'):
 
593
            dir += b'/'
 
594
    else:
 
595
        if not dir.endswith('/'):
 
596
            dir += '/'
660
597
 
661
598
    return fname.startswith(dir)
662
599
 
735
672
    # writes fail on some platforms (e.g. Windows with SMB  mounted
736
673
    # drives).
737
674
    if not segment_size:
738
 
        segment_size = 5242880 # 5MB
739
 
    segments = range(len(bytes) / segment_size + 1)
 
675
        segment_size = 5242880  # 5MB
 
676
    offsets = range(0, len(bytes), segment_size)
 
677
    view = memoryview(bytes)
740
678
    write = file_handle.write
741
 
    for segment_index in segments:
742
 
        segment = buffer(bytes, segment_index * segment_size, segment_size)
743
 
        write(segment)
 
679
    for offset in offsets:
 
680
        write(view[offset:offset + segment_size])
744
681
 
745
682
 
746
683
def file_iterator(input_file, readsize=32768):
751
688
        yield b
752
689
 
753
690
 
 
691
# GZ 2017-09-16: Makes sense in general for hexdigest() result to be text, but
 
692
# used as bytes through most interfaces so encode with this wrapper.
 
693
def _hexdigest(hashobj):
 
694
    return hashobj.hexdigest().encode()
 
695
 
 
696
 
754
697
def sha_file(f):
755
698
    """Calculate the hexdigest of an open file.
756
699
 
757
700
    The file cursor should be already at the start.
758
701
    """
759
702
    s = sha()
760
 
    BUFSIZE = 128<<10
 
703
    BUFSIZE = 128 << 10
761
704
    while True:
762
705
        b = f.read(BUFSIZE)
763
706
        if not b:
764
707
            break
765
708
        s.update(b)
766
 
    return s.hexdigest()
 
709
    return _hexdigest(s)
767
710
 
768
711
 
769
712
def size_sha_file(f):
774
717
    """
775
718
    size = 0
776
719
    s = sha()
777
 
    BUFSIZE = 128<<10
 
720
    BUFSIZE = 128 << 10
778
721
    while True:
779
722
        b = f.read(BUFSIZE)
780
723
        if not b:
781
724
            break
782
725
        size += len(b)
783
726
        s.update(b)
784
 
    return size, s.hexdigest()
 
727
    return size, _hexdigest(s)
785
728
 
786
729
 
787
730
def sha_file_by_name(fname):
790
733
    f = os.open(fname, os.O_RDONLY | O_BINARY | O_NOINHERIT)
791
734
    try:
792
735
        while True:
793
 
            b = os.read(f, 1<<16)
 
736
            b = os.read(f, 1 << 16)
794
737
            if not b:
795
 
                return s.hexdigest()
 
738
                return _hexdigest(s)
796
739
            s.update(b)
797
740
    finally:
798
741
        os.close(f)
801
744
def sha_strings(strings, _factory=sha):
802
745
    """Return the sha-1 of concatenation of strings"""
803
746
    s = _factory()
804
 
    map(s.update, strings)
805
 
    return s.hexdigest()
 
747
    for string in strings:
 
748
        s.update(string)
 
749
    return _hexdigest(s)
806
750
 
807
751
 
808
752
def sha_string(f, _factory=sha):
809
 
    return _factory(f).hexdigest()
 
753
    # GZ 2017-09-16: Dodgy if factory is ever not sha, probably shouldn't be.
 
754
    return _hexdigest(_factory(f))
810
755
 
811
756
 
812
757
def fingerprint_file(f):
813
758
    b = f.read()
814
759
    return {'size': len(b),
815
 
            'sha1': sha(b).hexdigest()}
 
760
            'sha1': _hexdigest(sha(b))}
816
761
 
817
762
 
818
763
def compare_files(a, b):
823
768
        bi = b.read(BUFSIZE)
824
769
        if ai != bi:
825
770
            return False
826
 
        if ai == '':
 
771
        if not ai:
827
772
            return True
828
773
 
829
774
 
834
779
    offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
835
780
    return offset.days * 86400 + offset.seconds
836
781
 
 
782
 
837
783
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
838
784
_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays]
839
785
 
851
797
    :param show_offset: Whether to append the timezone.
852
798
    """
853
799
    (date_fmt, tt, offset_str) = \
854
 
               _format_date(t, offset, timezone, date_fmt, show_offset)
 
800
        _format_date(t, offset, timezone, date_fmt, show_offset)
855
801
    date_fmt = date_fmt.replace('%a', weekdays[tt[6]])
856
802
    date_str = time.strftime(date_fmt, tt)
857
803
    return date_str + offset_str
862
808
 
863
809
 
864
810
def format_date_with_offset_in_original_timezone(t, offset=0,
865
 
    _cache=_offset_cache):
 
811
                                                 _cache=_offset_cache):
866
812
    """Return a formatted date string in the original timezone.
867
813
 
868
814
    This routine may be faster then format_date.
895
841
    :param show_offset: Whether to append the timezone.
896
842
    """
897
843
    (date_fmt, tt, offset_str) = \
898
 
               _format_date(t, offset, timezone, date_fmt, show_offset)
 
844
        _format_date(t, offset, timezone, date_fmt, show_offset)
899
845
    date_str = time.strftime(date_fmt, tt)
900
 
    if not isinstance(date_str, unicode):
 
846
    if not isinstance(date_str, str):
901
847
        date_str = date_str.decode(get_user_encoding(), 'replace')
902
848
    return date_str + offset_str
903
849
 
914
860
        tt = time.localtime(t)
915
861
        offset = local_time_offset(t)
916
862
    else:
917
 
        raise errors.UnsupportedTimezoneFormat(timezone)
 
863
        raise UnsupportedTimezoneFormat(timezone)
918
864
    if date_fmt is None:
919
865
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
920
866
    if show_offset:
944
890
        delta = -delta
945
891
 
946
892
    seconds = delta
947
 
    if seconds < 90: # print seconds up to 90 seconds
 
893
    if seconds < 90:  # print seconds up to 90 seconds
948
894
        if seconds == 1:
949
895
            return '%d second %s' % (seconds, direction,)
950
896
        else:
956
902
        plural_seconds = ''
957
903
    else:
958
904
        plural_seconds = 's'
959
 
    if minutes < 90: # print minutes, seconds up to 90 minutes
 
905
    if minutes < 90:  # print minutes, seconds up to 90 minutes
960
906
        if minutes == 1:
961
907
            return '%d minute, %d second%s %s' % (
962
 
                    minutes, seconds, plural_seconds, direction)
 
908
                minutes, seconds, plural_seconds, direction)
963
909
        else:
964
910
            return '%d minutes, %d second%s %s' % (
965
 
                    minutes, seconds, plural_seconds, direction)
 
911
                minutes, seconds, plural_seconds, direction)
966
912
 
967
913
    hours = int(minutes / 60)
968
914
    minutes -= 60 * hours
977
923
    return '%d hours, %d minute%s %s' % (hours, minutes,
978
924
                                         plural_minutes, direction)
979
925
 
 
926
 
980
927
def filesize(f):
981
928
    """Return size of given open file."""
982
929
    return os.fstat(f.fileno())[stat.ST_SIZE]
983
930
 
984
931
 
985
 
# Alias os.urandom to support platforms (which?) without /dev/urandom and 
 
932
# Alias os.urandom to support platforms (which?) without /dev/urandom and
986
933
# override if it doesn't work. Avoid checking on windows where there is
987
934
# significant initialisation cost that can be avoided for some bzr calls.
988
935
 
1003
950
 
1004
951
 
1005
952
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
 
953
 
 
954
 
1006
955
def rand_chars(num):
1007
956
    """Return a random string of num alphanumeric characters
1008
957
 
1011
960
    """
1012
961
    s = ''
1013
962
    for raw_byte in rand_bytes(num):
1014
 
        s += ALNUM[ord(raw_byte) % 36]
 
963
        s += ALNUM[raw_byte % 36]
1015
964
    return s
1016
965
 
1017
966
 
1018
 
## TODO: We could later have path objects that remember their list
1019
 
## decomposition (might be too tricksy though.)
 
967
# TODO: We could later have path objects that remember their list
 
968
# decomposition (might be too tricksy though.)
1020
969
 
1021
970
def splitpath(p):
1022
971
    """Turn string into list of parts."""
1023
 
    # split on either delimiter because people might use either on
1024
 
    # Windows
1025
 
    ps = re.split(r'[\\/]', p)
 
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 = ('.', '')
1026
992
 
1027
993
    rps = []
1028
994
    for f in ps:
1029
 
        if f == '..':
 
995
        if f == parent_dir:
1030
996
            raise errors.BzrError(gettext("sorry, %r not allowed in path") % f)
1031
 
        elif (f == '.') or (f == ''):
 
997
        elif f in current_empty_dir:
1032
998
            pass
1033
999
        else:
1034
1000
            rps.append(f)
1066
1032
    implementation should be loaded instead::
1067
1033
 
1068
1034
    >>> try:
1069
 
    >>>     import bzrlib._fictional_extension_pyx
 
1035
    >>>     import breezy._fictional_extension_pyx
1070
1036
    >>> except ImportError, e:
1071
 
    >>>     bzrlib.osutils.failed_to_load_extension(e)
1072
 
    >>>     import bzrlib._fictional_extension_py
 
1037
    >>>     breezy.osutils.failed_to_load_extension(e)
 
1038
    >>>     import breezy._fictional_extension_py
1073
1039
    """
1074
1040
    # NB: This docstring is just an example, not a doctest, because doctest
1075
1041
    # currently can't cope with the use of lazy imports in this namespace --
1088
1054
def report_extension_load_failures():
1089
1055
    if not _extension_load_failures:
1090
1056
        return
1091
 
    if config.GlobalStack().get('ignore_missing_extensions'):
 
1057
    if config.GlobalConfig().suppress_warning('missing_extensions'):
1092
1058
        return
1093
1059
    # the warnings framework should by default show this only once
1094
 
    from bzrlib.trace import warning
 
1060
    from .trace import warning
1095
1061
    warning(
1096
 
        "bzr: warning: some compiled extensions could not be loaded; "
1097
 
        "see <https://answers.launchpad.net/bzr/+faq/703>")
 
1062
        "brz: warning: some compiled extensions could not be loaded; "
 
1063
        "see ``brz help missing-extensions``")
1098
1064
    # we no longer show the specific missing extensions here, because it makes
1099
1065
    # the message too long and scary - see
1100
1066
    # https://bugs.launchpad.net/bzr/+bug/430529
1101
1067
 
1102
1068
 
1103
1069
try:
1104
 
    from bzrlib._chunks_to_lines_pyx import chunks_to_lines
1105
 
except ImportError, e:
 
1070
    from ._chunks_to_lines_pyx import chunks_to_lines
 
1071
except ImportError as e:
1106
1072
    failed_to_load_extension(e)
1107
 
    from bzrlib._chunks_to_lines_py import chunks_to_lines
 
1073
    from ._chunks_to_lines_py import chunks_to_lines
1108
1074
 
1109
1075
 
1110
1076
def split_lines(s):
1111
1077
    """Split s into lines, but without removing the newline characters."""
1112
1078
    # Trivially convert a fulltext into a 'chunked' representation, and let
1113
1079
    # chunks_to_lines do the heavy lifting.
1114
 
    if isinstance(s, str):
 
1080
    if isinstance(s, bytes):
1115
1081
        # chunks_to_lines only supports 8-bit strings
1116
1082
        return chunks_to_lines([s])
1117
1083
    else:
1123
1089
 
1124
1090
    This supports Unicode or plain string objects.
1125
1091
    """
1126
 
    lines = s.split('\n')
1127
 
    result = [line + '\n' for line in lines[:-1]]
 
1092
    nl = b'\n' if isinstance(s, bytes) else u'\n'
 
1093
    lines = s.split(nl)
 
1094
    result = [line + nl for line in lines[:-1]]
1128
1095
    if lines[-1]:
1129
1096
        result.append(lines[-1])
1130
1097
    return result
1141
1108
        return
1142
1109
    try:
1143
1110
        os.link(src, dest)
1144
 
    except (OSError, IOError), e:
 
1111
    except (OSError, IOError) as e:
1145
1112
        if e.errno != errno.EXDEV:
1146
1113
            raise
1147
1114
        shutil.copyfile(src, dest)
1153
1120
    Will delete even if readonly.
1154
1121
    """
1155
1122
    try:
1156
 
       _delete_file_or_dir(path)
1157
 
    except (OSError, IOError), e:
 
1123
        _delete_file_or_dir(path)
 
1124
    except (OSError, IOError) as e:
1158
1125
        if e.errno in (errno.EPERM, errno.EACCES):
1159
1126
            # make writable and try again
1160
1127
            try:
1172
1139
    # - root can damage a solaris file system by using unlink,
1173
1140
    # - unlink raises different exceptions on different OSes (linux: EISDIR, win32:
1174
1141
    #   EACCES, OSX: EPERM) when invoked on a directory.
1175
 
    if isdir(path): # Takes care of symlinks
 
1142
    if isdir(path):  # Takes care of symlinks
1176
1143
        os.rmdir(path)
1177
1144
    else:
1178
1145
        os.unlink(path)
1220
1187
    #    separators
1221
1188
    # 3) '\xa0' isn't unicode safe since it is >128.
1222
1189
 
1223
 
    # This should *not* be a unicode set of characters in case the source
1224
 
    # string is not a Unicode string. We can auto-up-cast the characters since
1225
 
    # they are ascii, but we don't want to auto-up-cast the string in case it
1226
 
    # is utf-8
1227
 
    for ch in ' \t\n\r\v\f':
 
1190
    if isinstance(s, str):
 
1191
        ws = ' \t\n\r\v\f'
 
1192
    else:
 
1193
        ws = (b' ', b'\t', b'\n', b'\r', b'\v', b'\f')
 
1194
    for ch in ws:
1228
1195
        if ch in s:
1229
1196
            return True
1230
1197
    else:
1257
1224
    if len(base) < MIN_ABS_PATHLENGTH:
1258
1225
        # must have space for e.g. a drive letter
1259
1226
        raise ValueError(gettext('%r is too short to calculate a relative path')
1260
 
            % (base,))
 
1227
                         % (base,))
1261
1228
 
1262
1229
    rp = abspath(path)
1263
1230
 
1300
1267
 
1301
1268
    abs_base = abspath(base)
1302
1269
    current = abs_base
1303
 
    _listdir = os.listdir
1304
1270
 
1305
1271
    # use an explicit iterator so we can easily consume the rest on early exit.
1306
1272
    bit_iter = iter(rel.split('/'))
1307
1273
    for bit in bit_iter:
1308
1274
        lbit = bit.lower()
1309
1275
        try:
1310
 
            next_entries = _listdir(current)
1311
 
        except OSError: # enoent, eperm, etc
 
1276
            next_entries = scandir(current)
 
1277
        except OSError:  # enoent, eperm, etc
1312
1278
            # We can't find this in the filesystem, so just append the
1313
1279
            # remaining bits.
1314
1280
            current = pathjoin(current, bit, *list(bit_iter))
1315
1281
            break
1316
 
        for look in next_entries:
1317
 
            if lbit == look.lower():
1318
 
                current = pathjoin(current, look)
 
1282
        for entry in next_entries:
 
1283
            if lbit == entry.name.lower():
 
1284
                current = entry.path
1319
1285
                break
1320
1286
        else:
1321
1287
            # got to the end, nothing matched, so we just return the
1325
1291
            break
1326
1292
    return current[len(abs_base):].lstrip('/')
1327
1293
 
 
1294
 
1328
1295
# XXX - TODO - we need better detection/integration of case-insensitive
1329
1296
# file-systems; Linux often sees FAT32 devices (or NFS-mounted OSX
1330
1297
# filesystems), for example, so could probably benefit from the same basic
1335
1302
else:
1336
1303
    canonical_relpath = relpath
1337
1304
 
 
1305
 
1338
1306
def canonical_relpaths(base, paths):
1339
1307
    """Create an iterable to canonicalize a sequence of relative paths.
1340
1308
 
1352
1320
    Otherwise it is decoded from the the filesystem's encoding. If decoding
1353
1321
    fails, a errors.BadFilenameEncoding exception is raised.
1354
1322
    """
1355
 
    if type(filename) is unicode:
 
1323
    if isinstance(filename, str):
1356
1324
        return filename
1357
1325
    try:
1358
1326
        return filename.decode(_fs_enc)
1367
1335
    Otherwise it is decoded from utf-8. If decoding fails, the exception is
1368
1336
    wrapped in a BzrBadParameterNotUnicode exception.
1369
1337
    """
1370
 
    if isinstance(unicode_or_utf8_string, unicode):
 
1338
    if isinstance(unicode_or_utf8_string, str):
1371
1339
        return unicode_or_utf8_string
1372
1340
    try:
1373
1341
        return unicode_or_utf8_string.decode('utf8')
1381
1349
    If it is a str, it is returned.
1382
1350
    If it is Unicode, it is encoded into a utf-8 string.
1383
1351
    """
1384
 
    if isinstance(unicode_or_utf8_string, str):
 
1352
    if isinstance(unicode_or_utf8_string, bytes):
1385
1353
        # TODO: jam 20070209 This is overkill, and probably has an impact on
1386
1354
        #       performance if we are dealing with lots of apis that want a
1387
1355
        #       utf-8 revision id
1394
1362
    return unicode_or_utf8_string.encode('utf-8')
1395
1363
 
1396
1364
 
1397
 
_revision_id_warning = ('Unicode revision ids were deprecated in bzr 0.15.'
1398
 
                        ' Revision id generators should be creating utf8'
1399
 
                        ' revision ids.')
1400
 
 
1401
 
 
1402
 
def safe_revision_id(unicode_or_utf8_string, warn=True):
1403
 
    """Revision ids should now be utf8, but at one point they were unicode.
1404
 
 
1405
 
    :param unicode_or_utf8_string: A possibly Unicode revision_id. (can also be
1406
 
        utf8 or None).
1407
 
    :param warn: Functions that are sanitizing user data can set warn=False
1408
 
    :return: None or a utf8 revision id.
1409
 
    """
1410
 
    if (unicode_or_utf8_string is None
1411
 
        or unicode_or_utf8_string.__class__ == str):
1412
 
        return unicode_or_utf8_string
1413
 
    if warn:
1414
 
        symbol_versioning.warn(_revision_id_warning, DeprecationWarning,
1415
 
                               stacklevel=2)
1416
 
    return cache_utf8.encode(unicode_or_utf8_string)
1417
 
 
1418
 
 
1419
 
_file_id_warning = ('Unicode file ids were deprecated in bzr 0.15. File id'
1420
 
                    ' generators should be creating utf8 file ids.')
1421
 
 
1422
 
 
1423
 
def safe_file_id(unicode_or_utf8_string, warn=True):
1424
 
    """File ids should now be utf8, but at one point they were unicode.
1425
 
 
1426
 
    This is the same as safe_utf8, except it uses the cached encode functions
1427
 
    to save a little bit of performance.
1428
 
 
1429
 
    :param unicode_or_utf8_string: A possibly Unicode file_id. (can also be
1430
 
        utf8 or None).
1431
 
    :param warn: Functions that are sanitizing user data can set warn=False
1432
 
    :return: None or a utf8 file id.
1433
 
    """
1434
 
    if (unicode_or_utf8_string is None
1435
 
        or unicode_or_utf8_string.__class__ == str):
1436
 
        return unicode_or_utf8_string
1437
 
    if warn:
1438
 
        symbol_versioning.warn(_file_id_warning, DeprecationWarning,
1439
 
                               stacklevel=2)
1440
 
    return cache_utf8.encode(unicode_or_utf8_string)
1441
 
 
1442
 
 
1443
1365
_platform_normalizes_filenames = False
1444
1366
if sys.platform == 'darwin':
1445
1367
    _platform_normalizes_filenames = True
1468
1390
    can be accessed by that path.
1469
1391
    """
1470
1392
 
1471
 
    return unicodedata.normalize('NFC', unicode(path)), True
 
1393
    if isinstance(path, bytes):
 
1394
        path = path.decode(sys.getfilesystemencoding())
 
1395
    return unicodedata.normalize('NFC', path), True
1472
1396
 
1473
1397
 
1474
1398
def _inaccessible_normalized_filename(path):
1475
1399
    __doc__ = _accessible_normalized_filename.__doc__
1476
1400
 
1477
 
    normalized = unicodedata.normalize('NFC', unicode(path))
 
1401
    if isinstance(path, bytes):
 
1402
        path = path.decode(sys.getfilesystemencoding())
 
1403
    normalized = unicodedata.normalize('NFC', path)
1478
1404
    return normalized, normalized == path
1479
1405
 
1480
1406
 
1503
1429
    except AttributeError:
1504
1430
        # siginterrupt doesn't exist on this platform, or for this version
1505
1431
        # of Python.
1506
 
        siginterrupt = lambda signum, flag: None
 
1432
        def siginterrupt(signum, flag): return None
1507
1433
    if restart_syscall:
1508
1434
        def sig_handler(*args):
1509
1435
            # Python resets the siginterrupt flag when a signal is
1534
1460
_terminal_size_state = 'no_data'
1535
1461
_first_terminal_size = None
1536
1462
 
 
1463
 
1537
1464
def terminal_width():
1538
1465
    """Return terminal width.
1539
1466
 
1540
1467
    None is returned if the width can't established precisely.
1541
1468
 
1542
1469
    The rules are:
1543
 
    - if BZR_COLUMNS is set, returns its value
 
1470
    - if BRZ_COLUMNS is set, returns its value
1544
1471
    - if there is no controlling terminal, returns None
1545
1472
    - query the OS, if the queried size has changed since the last query,
1546
1473
      return its value,
1561
1488
    # Note to implementors: if changing the rules for determining the width,
1562
1489
    # make sure you've considered the behaviour in these cases:
1563
1490
    #  - M-x shell in emacs, where $COLUMNS is set and TIOCGWINSZ returns 0,0.
1564
 
    #  - bzr log | less, in bash, where $COLUMNS not set and TIOCGWINSZ returns
 
1491
    #  - brz log | less, in bash, where $COLUMNS not set and TIOCGWINSZ returns
1565
1492
    #    0,0.
1566
1493
    #  - (add more interesting cases here, if you find any)
1567
1494
    # Some programs implement "Use $COLUMNS (if set) until SIGWINCH occurs",
1571
1498
    # time so we can notice if the reported size has changed, which should have
1572
1499
    # a similar effect.
1573
1500
 
1574
 
    # If BZR_COLUMNS is set, take it, user is always right
 
1501
    # If BRZ_COLUMNS is set, take it, user is always right
1575
1502
    # Except if they specified 0 in which case, impose no limit here
1576
1503
    try:
1577
 
        width = int(os.environ['BZR_COLUMNS'])
 
1504
        width = int(os.environ['BRZ_COLUMNS'])
1578
1505
    except (KeyError, ValueError):
1579
1506
        width = None
1580
1507
    if width is not None:
1585
1512
 
1586
1513
    isatty = getattr(sys.stdout, 'isatty', None)
1587
1514
    if isatty is None or not isatty():
1588
 
        # Don't guess, setting BZR_COLUMNS is the recommended way to override.
 
1515
        # Don't guess, setting BRZ_COLUMNS is the recommended way to override.
1589
1516
        return None
1590
1517
 
1591
1518
    # Query the OS
1620
1547
 
1621
1548
 
1622
1549
def _win32_terminal_size(width, height):
1623
 
    width, height = win32utils.get_console_size(defaultx=width, defaulty=height)
 
1550
    width, height = win32utils.get_console_size(
 
1551
        defaultx=width, defaulty=height)
1624
1552
    return width, height
1625
1553
 
1626
1554
 
1627
1555
def _ioctl_terminal_size(width, height):
1628
1556
    try:
1629
 
        import struct, fcntl, termios
 
1557
        import struct
 
1558
        import fcntl
 
1559
        import termios
1630
1560
        s = struct.pack('HHHH', 0, 0, 0, 0)
1631
1561
        x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
1632
1562
        height, width = struct.unpack('HHHH', x)[0:2]
1634
1564
        pass
1635
1565
    return width, height
1636
1566
 
 
1567
 
1637
1568
_terminal_size = None
1638
1569
"""Returns the terminal size as (width, height).
1639
1570
 
1649
1580
    _terminal_size = _ioctl_terminal_size
1650
1581
 
1651
1582
 
1652
 
def supports_executable():
1653
 
    return sys.platform != "win32"
 
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
1654
1617
 
1655
1618
 
1656
1619
def supports_posix_readonly():
1679
1642
        if orig_val is not None:
1680
1643
            del os.environ[env_variable]
1681
1644
    else:
1682
 
        if isinstance(value, unicode):
1683
 
            value = value.encode(get_user_encoding())
1684
1645
        os.environ[env_variable] = value
1685
1646
    return orig_val
1686
1647
 
1699
1660
        raise errors.IllegalPath(path)
1700
1661
 
1701
1662
 
1702
 
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
 
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
 
1703
1673
 
1704
1674
def _is_error_enotdir(e):
1705
1675
    """Check if this exception represents ENOTDIR.
1717
1687
    :return: True if this represents an ENOTDIR error. False otherwise.
1718
1688
    """
1719
1689
    en = getattr(e, 'errno', None)
1720
 
    if (en == errno.ENOTDIR
1721
 
        or (sys.platform == 'win32'
1722
 
            and (en == _WIN32_ERROR_DIRECTORY
1723
 
                 or (en == errno.EINVAL
1724
 
                     and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
1725
 
        ))):
 
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
             ))):
1726
1696
        return True
1727
1697
    return False
1728
1698
 
1755
1725
        rooted higher up.
1756
1726
    :return: an iterator over the dirs.
1757
1727
    """
1758
 
    #TODO there is a bit of a smell where the results of the directory-
 
1728
    # TODO there is a bit of a smell where the results of the directory-
1759
1729
    # summary in this, and the path from the root, may not agree
1760
1730
    # depending on top and prefix - i.e. ./foo and foo as a pair leads to
1761
1731
    # potentially confusing output. We should make this more robust - but
1762
1732
    # not at a speed cost. RBC 20060731
1763
 
    _lstat = os.lstat
1764
1733
    _directory = _directory_kind
1765
 
    _listdir = os.listdir
1766
 
    _kind_from_mode = file_kind_from_stat_mode
1767
1734
    pending = [(safe_unicode(prefix), "", _directory, None, safe_unicode(top))]
1768
1735
    while pending:
1769
1736
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1775
1742
        top_slash = top + u'/'
1776
1743
 
1777
1744
        dirblock = []
1778
 
        append = dirblock.append
1779
1745
        try:
1780
 
            names = sorted(map(decode_filename, _listdir(top)))
1781
 
        except OSError, e:
 
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))
 
1751
        except OSError as e:
1782
1752
            if not _is_error_enotdir(e):
1783
1753
                raise
1784
 
        else:
1785
 
            for name in names:
1786
 
                abspath = top_slash + name
1787
 
                statvalue = _lstat(abspath)
1788
 
                kind = _kind_from_mode(statvalue.st_mode)
1789
 
                append((relprefix + name, name, kind, statvalue, abspath))
 
1754
        except UnicodeDecodeError as e:
 
1755
            raise errors.BadFilenameEncoding(e.object, _fs_enc)
 
1756
        dirblock.sort()
1790
1757
        yield (relroot, top), dirblock
1791
1758
 
1792
1759
        # push the user specified dirs from dirblock
1837
1804
    """
1838
1805
    global _selected_dir_reader
1839
1806
    if _selected_dir_reader is None:
1840
 
        if sys.platform == "win32" and win32utils.winver == 'Windows NT':
1841
 
            # Win98 doesn't have unicode apis like FindFirstFileW
1842
 
            # TODO: We possibly could support Win98 by falling back to the
1843
 
            #       original FindFirstFile, and using TCHAR instead of WCHAR,
1844
 
            #       but that gets a bit tricky, and requires custom compiling
1845
 
            #       for win98 anyway.
 
1807
        if sys.platform == "win32":
1846
1808
            try:
1847
 
                from bzrlib._walkdirs_win32 import Win32ReadDir
 
1809
                from ._walkdirs_win32 import Win32ReadDir
1848
1810
                _selected_dir_reader = Win32ReadDir()
1849
1811
            except ImportError:
1850
1812
                pass
1851
1813
        elif _fs_enc in ('utf-8', 'ascii'):
1852
1814
            try:
1853
 
                from bzrlib._readdir_pyx import UTF8DirReader
 
1815
                from ._readdir_pyx import UTF8DirReader
1854
1816
                _selected_dir_reader = UTF8DirReader()
1855
 
            except ImportError, e:
 
1817
            except ImportError as e:
1856
1818
                failed_to_load_extension(e)
1857
1819
                pass
1858
1820
 
1903
1865
        See DirReader.read_dir for details.
1904
1866
        """
1905
1867
        _utf8_encode = self._utf8_encode
1906
 
        _lstat = os.lstat
1907
 
        _listdir = os.listdir
1908
 
        _kind_from_mode = file_kind_from_stat_mode
 
1868
 
 
1869
        def _fs_decode(s): return s.decode(_fs_enc)
 
1870
 
 
1871
        def _fs_encode(s): return s.encode(_fs_enc)
1909
1872
 
1910
1873
        if prefix:
1911
 
            relprefix = prefix + '/'
 
1874
            relprefix = prefix + b'/'
1912
1875
        else:
1913
 
            relprefix = ''
1914
 
        top_slash = top + u'/'
 
1876
            relprefix = b''
 
1877
        top_slash = top + '/'
1915
1878
 
1916
1879
        dirblock = []
1917
1880
        append = dirblock.append
1918
 
        for name in sorted(_listdir(top)):
 
1881
        for entry in scandir(safe_utf8(top)):
1919
1882
            try:
1920
 
                name_utf8 = _utf8_encode(name)[0]
 
1883
                name = _fs_decode(entry.name)
1921
1884
            except UnicodeDecodeError:
1922
1885
                raise errors.BadFilenameEncoding(
1923
 
                    _utf8_encode(relprefix)[0] + name, _fs_enc)
 
1886
                    relprefix + entry.name, _fs_enc)
1924
1887
            abspath = top_slash + name
1925
 
            statvalue = _lstat(abspath)
1926
 
            kind = _kind_from_mode(statvalue.st_mode)
 
1888
            name_utf8 = _utf8_encode(name)[0]
 
1889
            statvalue = entry.stat(follow_symlinks=False)
 
1890
            kind = file_kind_from_stat_mode(statvalue.st_mode)
1927
1891
            append((relprefix + name_utf8, name_utf8, kind, statvalue, abspath))
1928
 
        return dirblock
 
1892
        return sorted(dirblock)
1929
1893
 
1930
1894
 
1931
1895
def copy_tree(from_path, to_path, handlers={}):
1956
1920
        link_to = os.readlink(source)
1957
1921
        os.symlink(link_to, dest)
1958
1922
 
1959
 
    real_handlers = {'file':shutil.copy2,
1960
 
                     'symlink':copy_link,
1961
 
                     'directory':copy_dir,
1962
 
                    }
 
1923
    real_handlers = {'file': shutil.copy2,
 
1924
                     'symlink': copy_link,
 
1925
                     'directory': copy_dir,
 
1926
                     }
1963
1927
    real_handlers.update(handlers)
1964
1928
 
1965
1929
    if not os.path.exists(to_path):
1980
1944
    if chown is None:
1981
1945
        return
1982
1946
 
1983
 
    if src == None:
 
1947
    if src is None:
1984
1948
        src = os.path.dirname(dst)
1985
1949
        if src == '':
1986
1950
            src = '.'
1988
1952
    try:
1989
1953
        s = os.stat(src)
1990
1954
        chown(dst, s.st_uid, s.st_gid)
1991
 
    except OSError, e:
 
1955
    except OSError:
1992
1956
        trace.warning(
1993
1957
            'Unable to copy ownership from "%s" to "%s". '
1994
1958
            'You may want to set it manually.', src, dst)
2000
1964
 
2001
1965
    This can be used to sort paths in the same way that walkdirs does.
2002
1966
    """
2003
 
    return (dirname(path) , path)
 
1967
    return (dirname(path), path)
2004
1968
 
2005
1969
 
2006
1970
def compare_paths_prefix_order(path_a, path_b):
2007
1971
    """Compare path_a and path_b to generate the same order walkdirs uses."""
2008
1972
    key_a = path_prefix_key(path_a)
2009
1973
    key_b = path_prefix_key(path_b)
2010
 
    return cmp(key_a, key_b)
 
1974
    return (key_a > key_b) - (key_a < key_b)
2011
1975
 
2012
1976
 
2013
1977
_cached_user_encoding = None
2014
1978
 
2015
1979
 
2016
 
def get_user_encoding(use_cache=DEPRECATED_PARAMETER):
 
1980
def get_user_encoding():
2017
1981
    """Find out what the preferred user encoding is.
2018
1982
 
2019
1983
    This is generally the encoding that is used for command line parameters
2023
1987
    :return: A string defining the preferred user encoding
2024
1988
    """
2025
1989
    global _cached_user_encoding
2026
 
    if deprecated_passed(use_cache):
2027
 
        warn_deprecated("use_cache should only have been used for tests",
2028
 
            DeprecationWarning, stacklevel=2) 
2029
1990
    if _cached_user_encoding is not None:
2030
1991
        return _cached_user_encoding
2031
1992
 
2043
2004
        user_encoding = codecs.lookup(user_encoding).name
2044
2005
    except LookupError:
2045
2006
        if user_encoding not in ("", "cp0"):
2046
 
            sys.stderr.write('bzr: warning:'
 
2007
            sys.stderr.write('brz: warning:'
2047
2008
                             ' unknown encoding %s.'
2048
2009
                             ' Continuing with ascii encoding.\n'
2049
2010
                             % user_encoding
2050
 
                            )
 
2011
                             )
2051
2012
        user_encoding = 'ascii'
2052
2013
    else:
2053
2014
        # Get 'ascii' when setlocale has not been called or LANG=C or unset.
2077
2038
        return win32utils.get_host_name()
2078
2039
    else:
2079
2040
        import socket
2080
 
        return socket.gethostname().decode(get_user_encoding())
 
2041
        return socket.gethostname()
2081
2042
 
2082
2043
 
2083
2044
# We must not read/write any more than 64k at a time from/to a socket so we
2095
2056
 
2096
2057
 
2097
2058
def read_bytes_from_socket(sock, report_activity=None,
2098
 
        max_read_size=MAX_SOCKET_CHUNK):
 
2059
                           max_read_size=MAX_SOCKET_CHUNK):
2099
2060
    """Read up to max_read_size of bytes from sock and notify of progress.
2100
2061
 
2101
2062
    Translates "Connection reset by peer" into file-like EOF (return an
2102
2063
    empty string rather than raise an error), and repeats the recv if
2103
2064
    interrupted by a signal.
2104
2065
    """
2105
 
    while 1:
 
2066
    while True:
2106
2067
        try:
2107
 
            bytes = sock.recv(max_read_size)
2108
 
        except socket.error, e:
 
2068
            data = sock.recv(max_read_size)
 
2069
        except socket.error as e:
2109
2070
            eno = e.args[0]
2110
2071
            if eno in _end_of_stream_errors:
2111
2072
                # The connection was closed by the other side.  Callers expect
2112
2073
                # an empty string to signal end-of-stream.
2113
 
                return ""
 
2074
                return b""
2114
2075
            elif eno == errno.EINTR:
2115
2076
                # Retry the interrupted recv.
2116
2077
                continue
2117
2078
            raise
2118
2079
        else:
2119
2080
            if report_activity is not None:
2120
 
                report_activity(len(bytes), 'read')
2121
 
            return bytes
 
2081
                report_activity(len(data), 'read')
 
2082
            return data
2122
2083
 
2123
2084
 
2124
2085
def recv_all(socket, count):
2131
2092
 
2132
2093
    This isn't optimized and is intended mostly for use in testing.
2133
2094
    """
2134
 
    b = ''
 
2095
    b = b''
2135
2096
    while len(b) < count:
2136
2097
        new = read_bytes_from_socket(socket, None, count - len(b))
2137
 
        if new == '':
2138
 
            break # eof
 
2098
        if new == b'':
 
2099
            break  # eof
2139
2100
        b += new
2140
2101
    return b
2141
2102
 
2155
2116
    """
2156
2117
    sent_total = 0
2157
2118
    byte_count = len(bytes)
 
2119
    view = memoryview(bytes)
2158
2120
    while sent_total < byte_count:
2159
2121
        try:
2160
 
            sent = sock.send(buffer(bytes, sent_total, MAX_SOCKET_CHUNK))
2161
 
        except (socket.error, IOError), e:
 
2122
            sent = sock.send(view[sent_total:sent_total + MAX_SOCKET_CHUNK])
 
2123
        except (socket.error, IOError) as e:
2162
2124
            if e.args[0] in _end_of_stream_errors:
2163
2125
                raise errors.ConnectionReset(
2164
2126
                    "Error trying to write to socket", e)
2189
2151
            sock.connect(sa)
2190
2152
            return sock
2191
2153
 
2192
 
        except socket.error, err:
 
2154
        except socket.error as e:
 
2155
            err = e
2193
2156
            # 'err' is now the most recent error
2194
2157
            if sock is not None:
2195
2158
                sock.close()
2218
2181
def resource_string(package, resource_name):
2219
2182
    """Load a resource from a package and return it as a string.
2220
2183
 
2221
 
    Note: Only packages that start with bzrlib are currently supported.
 
2184
    Note: Only packages that start with breezy are currently supported.
2222
2185
 
2223
2186
    This is designed to be a lightweight implementation of resource
2224
2187
    loading in a way which is API compatible with the same API from
2227
2190
    If and when pkg_resources becomes a standard library, this routine
2228
2191
    can delegate to it.
2229
2192
    """
2230
 
    # Check package name is within bzrlib
2231
 
    if package == "bzrlib":
 
2193
    # Check package name is within breezy
 
2194
    if package == "breezy":
2232
2195
        resource_relpath = resource_name
2233
 
    elif package.startswith("bzrlib."):
2234
 
        package = package[len("bzrlib."):].replace('.', os.sep)
 
2196
    elif package.startswith("breezy."):
 
2197
        package = package[len("breezy."):].replace('.', os.sep)
2235
2198
        resource_relpath = pathjoin(package, resource_name)
2236
2199
    else:
2237
 
        raise errors.BzrError('resource package %s not in bzrlib' % package)
 
2200
        raise errors.BzrError('resource package %s not in breezy' % package)
2238
2201
 
2239
2202
    # Map the resource to a file and read its contents
2240
 
    base = dirname(bzrlib.__file__)
 
2203
    base = dirname(breezy.__file__)
2241
2204
    if getattr(sys, 'frozen', None):    # bzr.exe
2242
2205
        base = abspath(pathjoin(base, '..', '..'))
2243
 
    f = file(pathjoin(base, resource_relpath), "rU")
2244
 
    try:
 
2206
    with open(pathjoin(base, resource_relpath), "rt") as f:
2245
2207
        return f.read()
2246
 
    finally:
2247
 
        f.close()
 
2208
 
2248
2209
 
2249
2210
def file_kind_from_stat_mode_thunk(mode):
2250
2211
    global file_kind_from_stat_mode
2251
2212
    if file_kind_from_stat_mode is file_kind_from_stat_mode_thunk:
2252
2213
        try:
2253
 
            from bzrlib._readdir_pyx import UTF8DirReader
 
2214
            from ._readdir_pyx import UTF8DirReader
2254
2215
            file_kind_from_stat_mode = UTF8DirReader().kind_from_mode
2255
 
        except ImportError, e:
 
2216
        except ImportError:
2256
2217
            # This is one time where we won't warn that an extension failed to
2257
2218
            # load. The extension is never available on Windows anyway.
2258
 
            from bzrlib._readdir_py import (
 
2219
            from ._readdir_py import (
2259
2220
                _kind_from_mode as file_kind_from_stat_mode
2260
2221
                )
2261
2222
    return file_kind_from_stat_mode(mode)
 
2223
 
 
2224
 
2262
2225
file_kind_from_stat_mode = file_kind_from_stat_mode_thunk
2263
2226
 
 
2227
 
2264
2228
def file_stat(f, _lstat=os.lstat):
2265
2229
    try:
2266
2230
        # XXX cache?
2267
2231
        return _lstat(f)
2268
 
    except OSError, e:
 
2232
    except OSError as e:
2269
2233
        if getattr(e, 'errno', None) in (errno.ENOENT, errno.ENOTDIR):
2270
2234
            raise errors.NoSuchFile(f)
2271
2235
        raise
2272
2236
 
 
2237
 
2273
2238
def file_kind(f, _lstat=os.lstat):
2274
2239
    stat_value = file_stat(f, _lstat)
2275
2240
    return file_kind_from_stat_mode(stat_value.st_mode)
2276
2241
 
 
2242
 
2277
2243
def until_no_eintr(f, *a, **kw):
2278
2244
    """Run f(*a, **kw), retrying if an EINTR error occurs.
2279
2245
 
2284
2250
    Keep in mind that this is not a complete solution to EINTR.  There is
2285
2251
    probably code in the Python standard library and other dependencies that
2286
2252
    may encounter EINTR if a signal arrives (and there is signal handler for
2287
 
    that signal).  So this function can reduce the impact for IO that bzrlib
 
2253
    that signal).  So this function can reduce the impact for IO that breezy
2288
2254
    directly controls, but it is not a complete solution.
2289
2255
    """
2290
2256
    # Borrowed from Twisted's twisted.python.util.untilConcludes function.
2291
2257
    while True:
2292
2258
        try:
2293
2259
            return f(*a, **kw)
2294
 
        except (IOError, OSError), e:
 
2260
        except (IOError, OSError) as e:
2295
2261
            if e.errno == errno.EINTR:
2296
2262
                continue
2297
2263
            raise
2298
2264
 
2299
2265
 
2300
 
@deprecated_function(deprecated_in((2, 2, 0)))
2301
 
def re_compile_checked(re_string, flags=0, where=""):
2302
 
    """Return a compiled re, or raise a sensible error.
2303
 
 
2304
 
    This should only be used when compiling user-supplied REs.
2305
 
 
2306
 
    :param re_string: Text form of regular expression.
2307
 
    :param flags: eg re.IGNORECASE
2308
 
    :param where: Message explaining to the user the context where
2309
 
        it occurred, eg 'log search filter'.
2310
 
    """
2311
 
    # from https://bugs.launchpad.net/bzr/+bug/251352
2312
 
    try:
2313
 
        re_obj = re.compile(re_string, flags)
2314
 
        re_obj.search("")
2315
 
        return re_obj
2316
 
    except errors.InvalidPattern, e:
2317
 
        if where:
2318
 
            where = ' in ' + where
2319
 
        # despite the name 'error' is a type
2320
 
        raise errors.BzrCommandError('Invalid regular expression%s: %s'
2321
 
            % (where, e.msg))
2322
 
 
2323
 
 
2324
2266
if sys.platform == "win32":
2325
2267
    def getchar():
2326
2268
        import msvcrt
2354
2296
                                stdout=subprocess.PIPE).communicate()[0]
2355
2297
elif sys.platform == 'sunos5':
2356
2298
    def _local_concurrency():
2357
 
        return subprocess.Popen(['psrinfo', '-p',],
 
2299
        return subprocess.Popen(['psrinfo', '-p', ],
2358
2300
                                stdout=subprocess.PIPE).communicate()[0]
2359
2301
elif sys.platform == "win32":
2360
2302
    def _local_concurrency():
2368
2310
 
2369
2311
_cached_local_concurrency = None
2370
2312
 
 
2313
 
2371
2314
def local_concurrency(use_cache=True):
2372
2315
    """Return how many processes can be run concurrently.
2373
2316
 
2379
2322
    if _cached_local_concurrency is not None and use_cache:
2380
2323
        return _cached_local_concurrency
2381
2324
 
2382
 
    concurrency = os.environ.get('BZR_CONCURRENCY', None)
 
2325
    concurrency = os.environ.get('BRZ_CONCURRENCY', None)
2383
2326
    if concurrency is None:
 
2327
        import multiprocessing
2384
2328
        try:
2385
 
            import multiprocessing
2386
2329
            concurrency = multiprocessing.cpu_count()
2387
 
        except (ImportError, NotImplementedError):
2388
 
            # multiprocessing is only available on Python >= 2.6
2389
 
            # and multiprocessing.cpu_count() isn't implemented on all
2390
 
            # platforms
 
2330
        except NotImplementedError:
 
2331
            # multiprocessing.cpu_count() isn't implemented on all platforms
2391
2332
            try:
2392
2333
                concurrency = _local_concurrency()
2393
2334
            except (OSError, IOError):
2397
2338
    except (TypeError, ValueError):
2398
2339
        concurrency = 1
2399
2340
    if use_cache:
2400
 
        _cached_concurrency = concurrency
 
2341
        _cached_local_concurrency = concurrency
2401
2342
    return concurrency
2402
2343
 
2403
2344
 
2409
2350
        self.encode = encode
2410
2351
 
2411
2352
    def write(self, object):
2412
 
        if type(object) is str:
 
2353
        if isinstance(object, str):
2413
2354
            self.stream.write(object)
2414
2355
        else:
2415
2356
            data, _ = self.encode(object, self.errors)
2416
2357
            self.stream.write(data)
2417
2358
 
 
2359
 
2418
2360
if sys.platform == 'win32':
2419
2361
    def open_file(filename, mode='r', bufsize=-1):
2420
2362
        """This function is used to override the ``open`` builtin.
2448
2390
            else:
2449
2391
                flags |= os.O_WRONLY
2450
2392
            flags |= os.O_CREAT | os.O_APPEND
2451
 
        else: #reading
 
2393
        else:  # reading
2452
2394
            if updating:
2453
2395
                flags |= os.O_RDWR
2454
2396
            else:
2494
2436
 
2495
2437
def find_executable_on_path(name):
2496
2438
    """Finds an executable on the PATH.
2497
 
    
 
2439
 
2498
2440
    On Windows, this will try to append each extension in the PATHEXT
2499
2441
    environment variable to the name, if it cannot be found with the name
2500
2442
    as given.
2501
 
    
 
2443
 
2502
2444
    :param name: The base name of the executable.
2503
2445
    :return: The path to the executable found or None.
2504
2446
    """
2533
2475
    try:
2534
2476
        # Special meaning of unix kill: just check if it's there.
2535
2477
        os.kill(pid, 0)
2536
 
    except OSError, e:
 
2478
    except OSError as e:
2537
2479
        if e.errno == errno.ESRCH:
2538
2480
            # On this machine, and really not found: as sure as we can be
2539
2481
            # that it's dead.
2542
2484
            # exists, though not ours
2543
2485
            return False
2544
2486
        else:
2545
 
            mutter("os.kill(%d, 0) failed: %s" % (pid, e))
 
2487
            trace.mutter("os.kill(%d, 0) failed: %s" % (pid, e))
2546
2488
            # Don't really know.
2547
2489
            return False
2548
2490
    else:
2549
2491
        # Exists and our process: not dead.
2550
2492
        return False
2551
2493
 
 
2494
 
2552
2495
if sys.platform == "win32":
2553
2496
    is_local_pid_dead = win32utils.is_local_pid_dead
2554
2497
else:
2561
2504
 
2562
2505
def fdatasync(fileno):
2563
2506
    """Flush file contents to disk if possible.
2564
 
    
 
2507
 
2565
2508
    :param fileno: Integer OS file handle.
2566
2509
    :raises TransportNotPossible: If flushing to disk is not possible.
2567
2510
    """
2569
2512
    if fn is not None:
2570
2513
        try:
2571
2514
            fn(fileno)
2572
 
        except IOError, e:
 
2515
        except IOError as e:
2573
2516
            # See bug #1075108, on some platforms fdatasync exists, but can
2574
2517
            # raise ENOTSUP. However, we are calling fdatasync to be helpful
2575
2518
            # and reduce the chance of corruption-on-powerloss situations. It
2581
2524
 
2582
2525
def ensure_empty_directory_exists(path, exception_class):
2583
2526
    """Make sure a local directory exists and is empty.
2584
 
    
 
2527
 
2585
2528
    If it does not exist, it is created.  If it exists and is not empty, an
2586
2529
    instance of exception_class is raised.
2587
2530
    """
2588
2531
    try:
2589
2532
        os.mkdir(path)
2590
 
    except OSError, e:
 
2533
    except OSError as e:
2591
2534
        if e.errno != errno.EEXIST:
2592
2535
            raise
2593
2536
        if os.listdir(path) != []:
2594
2537
            raise exception_class(path)
2595
2538
 
2596
2539
 
2597
 
def is_environment_error(evalue):
2598
 
    """True if exception instance is due to a process environment issue
2599
 
 
2600
 
    This includes OSError and IOError, but also other errors that come from
2601
 
    the operating system or core libraries but are not subclasses of those.
2602
 
    """
2603
 
    if isinstance(evalue, (EnvironmentError, select.error)):
2604
 
        return True
2605
 
    if sys.platform == "win32" and win32utils._is_pywintypes_error(evalue):
2606
 
        return True
2607
 
    return False
 
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