/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 bzrlib/osutils.py

  • Committer: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

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