/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/osutils.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-08 23:30:31 UTC
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170608233031-3qavls2o7a1pqllj
Update imports.

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
25
27
from .lazy_import import lazy_import
26
28
lazy_import(globals(), """
27
29
from datetime import datetime
 
30
from datetime import timedelta
28
31
import getpass
29
32
import locale
30
33
import ntpath
44
47
 
45
48
from breezy import (
46
49
    config,
 
50
    errors,
47
51
    trace,
48
52
    win32utils,
49
53
    )
50
54
from breezy.i18n import gettext
51
55
""")
52
56
 
 
57
from .sixish import (
 
58
    PY3,
 
59
    text_type,
 
60
    )
 
61
 
53
62
from hashlib import (
54
63
    md5,
55
64
    sha1 as sha,
57
66
 
58
67
 
59
68
import breezy
60
 
from . import (
61
 
    _fs_enc,
62
 
    errors,
63
 
    )
64
 
 
 
69
from . import _fs_enc
 
70
 
 
71
 
 
72
# Cross platform wall-clock time functionality with decent resolution.
 
73
# On Linux ``time.clock`` returns only CPU time. On Windows, ``time.time()``
 
74
# only has a resolution of ~15ms. Note that ``time.clock()`` is not
 
75
# synchronized with ``time.time()``, this is only meant to be used to find
 
76
# delta times by subtracting from another call to this function.
 
77
timer_func = time.time
 
78
if sys.platform == 'win32':
 
79
    timer_func = time.clock
65
80
 
66
81
# On win32, O_BINARY is used to indicate the file should
67
82
# be opened in binary mode, rather than text mode.
74
89
O_NOINHERIT = getattr(os, 'O_NOINHERIT', 0)
75
90
 
76
91
 
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
 
92
def get_unicode_argv():
 
93
    if PY3:
 
94
        return sys.argv[1:]
 
95
    try:
 
96
        user_encoding = get_user_encoding()
 
97
        return [a.decode(user_encoding) for a in sys.argv[1:]]
 
98
    except UnicodeDecodeError:
 
99
        raise errors.BzrError(gettext("Parameter {0!r} encoding is unsupported by {1} "
 
100
            "application locale.").format(a, user_encoding))
84
101
 
85
102
 
86
103
def make_readonly(filename):
128
145
        return set(paths)
129
146
 
130
147
    def sort_key(path):
131
 
        if isinstance(path, bytes):
132
 
            return path.split(b'/')
133
 
        else:
134
 
            return path.split('/')
 
148
        return path.split('/')
135
149
    sorted_paths = sorted(list(paths), key=sort_key)
136
150
 
137
151
    search_paths = [sorted_paths[0]]
164
178
 
165
179
_directory_kind = 'directory'
166
180
 
167
 
 
168
181
def get_umask():
169
182
    """Return the current umask"""
170
183
    # Assume that people aren't messing with the umask while running
201
214
            return True
202
215
        except OSError as e:
203
216
            if e.errno == errno.ENOENT:
204
 
                return False
 
217
                return False;
205
218
            else:
206
 
                raise errors.BzrError(
207
 
                    gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
 
219
                raise errors.BzrError(gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
208
220
 
209
221
 
210
222
def fancy_rename(old, new, rename_func, unlink_func):
234
246
    file_existed = False
235
247
    try:
236
248
        rename_func(new, tmp_name)
237
 
    except (errors.NoSuchFile,):
 
249
    except (errors.NoSuchFile,) as e:
238
250
        pass
239
251
    except IOError as e:
240
252
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
244
256
            raise
245
257
    except Exception as e:
246
258
        if (getattr(e, 'errno', None) is None
247
 
                or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
 
259
            or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
248
260
            raise
249
261
    else:
250
262
        file_existed = True
260
272
        # case-insensitive filesystem), so we may have accidentally renamed
261
273
        # source by when we tried to rename target
262
274
        if (file_existed and e.errno in (None, errno.ENOENT)
263
 
                and old.lower() == new.lower()):
 
275
            and old.lower() == new.lower()):
264
276
            # source and target are the same file on a case-insensitive
265
277
            # filesystem, so we don't generate an exception
266
278
            pass
301
313
    # as a special case here by simply removing the first slash, as we consider
302
314
    # that breaking POSIX compatibility for this obscure feature is acceptable.
303
315
    # 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 "/".
 
316
    # the repo is hosted at the root of the filesystem, i.e. in "/".    
305
317
    if path.startswith('//'):
306
318
        path = path[1:]
307
319
    return path
308
320
 
309
321
 
 
322
def _posix_path_from_environ(key):
 
323
    """Get unicode path from `key` in environment or None if not present
 
324
 
 
325
    Note that posix systems use arbitrary byte strings for filesystem objects,
 
326
    so a path that raises BadFilenameEncoding here may still be accessible.
 
327
    """
 
328
    val = os.environ.get(key, None)
 
329
    if PY3 or val is None:
 
330
        return val
 
331
    try:
 
332
        return val.decode(_fs_enc)
 
333
    except UnicodeDecodeError:
 
334
        # GZ 2011-12-12:Ideally want to include `key` in the exception message
 
335
        raise errors.BadFilenameEncoding(val, _fs_enc)
 
336
 
 
337
 
310
338
def _posix_get_home_dir():
311
339
    """Get the home directory of the current user as a unicode path"""
312
340
    path = posixpath.expanduser("~")
320
348
 
321
349
def _posix_getuser_unicode():
322
350
    """Get username from environment or password database as unicode"""
323
 
    return getpass.getuser()
 
351
    name = getpass.getuser()
 
352
    user_encoding = get_user_encoding()
 
353
    try:
 
354
        return name.decode(user_encoding)
 
355
    except UnicodeDecodeError:
 
356
        raise errors.BzrError("Encoding of username %r is unsupported by %s "
 
357
            "application locale." % (name, user_encoding))
324
358
 
325
359
 
326
360
def _win32_fixdrive(path):
338
372
 
339
373
def _win32_abspath(path):
340
374
    # Real ntpath.abspath doesn't have a problem with a unicode cwd
341
 
    return _win32_fixdrive(ntpath.abspath(path).replace('\\', '/'))
 
375
    return _win32_fixdrive(ntpath.abspath(unicode(path)).replace('\\', '/'))
342
376
 
343
377
 
344
378
def _win32_realpath(path):
345
379
    # Real ntpath.realpath doesn't have a problem with a unicode cwd
346
 
    return _win32_fixdrive(ntpath.realpath(path).replace('\\', '/'))
 
380
    return _win32_fixdrive(ntpath.realpath(unicode(path)).replace('\\', '/'))
347
381
 
348
382
 
349
383
def _win32_pathjoin(*args):
351
385
 
352
386
 
353
387
def _win32_normpath(path):
354
 
    return _win32_fixdrive(ntpath.normpath(path).replace('\\', '/'))
 
388
    return _win32_fixdrive(ntpath.normpath(unicode(path)).replace('\\', '/'))
355
389
 
356
390
 
357
391
def _win32_getcwd():
396
430
            rename_func(old, new)
397
431
        except OSError as e:
398
432
            detailed_error = OSError(e.errno, e.strerror +
399
 
                                     " [occurred when renaming '%s' to '%s']" %
400
 
                                     (old, new))
 
433
                                " [occurred when renaming '%s' to '%s']" %
 
434
                                (old, new))
401
435
            detailed_error.old_filename = old
402
436
            detailed_error.new_filename = new
403
437
            raise detailed_error
405
439
    return _rename_wrapper
406
440
 
407
441
 
408
 
_getcwd = os.getcwd
 
442
if sys.version_info > (3,):
 
443
    _getcwd = os.getcwd
 
444
else:
 
445
    _getcwd = os.getcwdu
409
446
 
410
447
 
411
448
# Default rename wraps os.rename()
417
454
realpath = _posix_realpath
418
455
pathjoin = os.path.join
419
456
normpath = _posix_normpath
 
457
path_from_environ = _posix_path_from_environ
420
458
_get_home_dir = _posix_get_home_dir
421
459
getuser_unicode = _posix_getuser_unicode
422
460
getcwd = _getcwd
430
468
lstat = os.lstat
431
469
fstat = os.fstat
432
470
 
433
 
 
434
471
def wrap_stat(st):
435
472
    return st
436
473
 
463
500
        """
464
501
        exception = excinfo[1]
465
502
        if function in (os.remove, os.rmdir) \
466
 
                and isinstance(exception, OSError) \
467
 
                and exception.errno == errno.EACCES:
 
503
            and isinstance(exception, OSError) \
 
504
            and exception.errno == errno.EACCES:
468
505
            make_writable(path)
469
506
            function(path)
470
507
        else:
474
511
        """Replacer for shutil.rmtree: could remove readonly dirs/files"""
475
512
        return shutil.rmtree(path, ignore_errors, onerror)
476
513
 
 
514
    f = win32utils.get_unicode_argv     # special function or None
 
515
    if f is not None:
 
516
        get_unicode_argv = f
 
517
    path_from_environ = win32utils.get_environ_unicode
477
518
    _get_home_dir = win32utils.get_home_location
478
519
    getuser_unicode = win32utils.get_user_name
479
520
 
504
545
            output_encoding = get_user_encoding()
505
546
            if trace:
506
547
                mutter('encoding stdout as osutils.get_user_encoding() %r',
507
 
                       output_encoding)
 
548
                   output_encoding)
508
549
        else:
509
550
            output_encoding = input_encoding
510
551
            if trace:
511
552
                mutter('encoding stdout as sys.stdin encoding %r',
512
 
                       output_encoding)
 
553
                    output_encoding)
513
554
    else:
514
555
        if trace:
515
556
            mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
518
559
        output_encoding = get_user_encoding()
519
560
        if trace:
520
561
            mutter('cp0 is invalid encoding.'
521
 
                   ' encoding stdout as osutils.get_user_encoding() %r',
522
 
                   output_encoding)
 
562
               ' encoding stdout as osutils.get_user_encoding() %r',
 
563
               output_encoding)
523
564
    # check encoding
524
565
    try:
525
566
        codecs.lookup(output_encoding)
528
569
                         ' unknown terminal encoding %s.\n'
529
570
                         '  Using encoding %s instead.\n'
530
571
                         % (output_encoding, get_user_encoding())
531
 
                         )
 
572
                        )
532
573
        output_encoding = get_user_encoding()
533
574
 
534
575
    return output_encoding
539
580
        F = realpath
540
581
    else:
541
582
        F = abspath
542
 
    [p, e] = os.path.split(f)
 
583
    [p,e] = os.path.split(f)
543
584
    if e == "" or e == "." or e == "..":
544
585
        return F(f)
545
586
    else:
561
602
    except OSError:
562
603
        return False
563
604
 
564
 
 
565
605
def islink(f):
566
606
    """True if f is a symlink."""
567
607
    try:
569
609
    except OSError:
570
610
        return False
571
611
 
572
 
 
573
612
def is_inside(dir, fname):
574
613
    """True if fname is inside dir.
575
614
 
585
624
    if dir == fname:
586
625
        return True
587
626
 
588
 
    if dir in ('', b''):
 
627
    if dir == '':
589
628
        return True
590
629
 
591
 
    if isinstance(dir, bytes):
592
 
        if not dir.endswith(b'/'):
593
 
            dir += b'/'
594
 
    else:
595
 
        if not dir.endswith('/'):
596
 
            dir += '/'
 
630
    if dir[-1] != '/':
 
631
        dir += '/'
597
632
 
598
633
    return fname.startswith(dir)
599
634
 
672
707
    # writes fail on some platforms (e.g. Windows with SMB  mounted
673
708
    # drives).
674
709
    if not segment_size:
675
 
        segment_size = 5242880  # 5MB
 
710
        segment_size = 5242880 # 5MB
676
711
    offsets = range(0, len(bytes), segment_size)
677
712
    view = memoryview(bytes)
678
713
    write = file_handle.write
679
714
    for offset in offsets:
680
 
        write(view[offset:offset + segment_size])
 
715
        write(view[offset:offset+segment_size])
681
716
 
682
717
 
683
718
def file_iterator(input_file, readsize=32768):
688
723
        yield b
689
724
 
690
725
 
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
726
def sha_file(f):
698
727
    """Calculate the hexdigest of an open file.
699
728
 
700
729
    The file cursor should be already at the start.
701
730
    """
702
731
    s = sha()
703
 
    BUFSIZE = 128 << 10
 
732
    BUFSIZE = 128<<10
704
733
    while True:
705
734
        b = f.read(BUFSIZE)
706
735
        if not b:
707
736
            break
708
737
        s.update(b)
709
 
    return _hexdigest(s)
 
738
    return s.hexdigest()
710
739
 
711
740
 
712
741
def size_sha_file(f):
717
746
    """
718
747
    size = 0
719
748
    s = sha()
720
 
    BUFSIZE = 128 << 10
 
749
    BUFSIZE = 128<<10
721
750
    while True:
722
751
        b = f.read(BUFSIZE)
723
752
        if not b:
724
753
            break
725
754
        size += len(b)
726
755
        s.update(b)
727
 
    return size, _hexdigest(s)
 
756
    return size, s.hexdigest()
728
757
 
729
758
 
730
759
def sha_file_by_name(fname):
733
762
    f = os.open(fname, os.O_RDONLY | O_BINARY | O_NOINHERIT)
734
763
    try:
735
764
        while True:
736
 
            b = os.read(f, 1 << 16)
 
765
            b = os.read(f, 1<<16)
737
766
            if not b:
738
 
                return _hexdigest(s)
 
767
                return s.hexdigest()
739
768
            s.update(b)
740
769
    finally:
741
770
        os.close(f)
746
775
    s = _factory()
747
776
    for string in strings:
748
777
        s.update(string)
749
 
    return _hexdigest(s)
 
778
    return s.hexdigest()
750
779
 
751
780
 
752
781
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))
 
782
    return _factory(f).hexdigest()
755
783
 
756
784
 
757
785
def fingerprint_file(f):
758
786
    b = f.read()
759
787
    return {'size': len(b),
760
 
            'sha1': _hexdigest(sha(b))}
 
788
            'sha1': sha(b).hexdigest()}
761
789
 
762
790
 
763
791
def compare_files(a, b):
768
796
        bi = b.read(BUFSIZE)
769
797
        if ai != bi:
770
798
            return False
771
 
        if not ai:
 
799
        if ai == '':
772
800
            return True
773
801
 
774
802
 
 
803
def gmtime(seconds=None):
 
804
    """Convert seconds since the Epoch to a time tuple expressing UTC (a.k.a.
 
805
    GMT). When 'seconds' is not passed in, convert the current time instead.
 
806
    Handy replacement for time.gmtime() buggy on Windows and 32-bit platforms.
 
807
    """
 
808
    if seconds is None:
 
809
        seconds = time.time()
 
810
    return (datetime(1970, 1, 1) + timedelta(seconds=seconds)).timetuple()
 
811
 
 
812
 
775
813
def local_time_offset(t=None):
776
814
    """Return offset of local zone from GMT, either at present or at time t."""
777
815
    if t is None:
779
817
    offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
780
818
    return offset.days * 86400 + offset.seconds
781
819
 
782
 
 
783
820
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
784
821
_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays]
785
822
 
797
834
    :param show_offset: Whether to append the timezone.
798
835
    """
799
836
    (date_fmt, tt, offset_str) = \
800
 
        _format_date(t, offset, timezone, date_fmt, show_offset)
 
837
               _format_date(t, offset, timezone, date_fmt, show_offset)
801
838
    date_fmt = date_fmt.replace('%a', weekdays[tt[6]])
802
839
    date_str = time.strftime(date_fmt, tt)
803
840
    return date_str + offset_str
808
845
 
809
846
 
810
847
def format_date_with_offset_in_original_timezone(t, offset=0,
811
 
                                                 _cache=_offset_cache):
 
848
    _cache=_offset_cache):
812
849
    """Return a formatted date string in the original timezone.
813
850
 
814
851
    This routine may be faster then format_date.
818
855
    """
819
856
    if offset is None:
820
857
        offset = 0
821
 
    tt = time.gmtime(t + offset)
 
858
    tt = gmtime(t + offset)
822
859
    date_fmt = _default_format_by_weekday_num[tt[6]]
823
860
    date_str = time.strftime(date_fmt, tt)
824
861
    offset_str = _cache.get(offset, None)
841
878
    :param show_offset: Whether to append the timezone.
842
879
    """
843
880
    (date_fmt, tt, offset_str) = \
844
 
        _format_date(t, offset, timezone, date_fmt, show_offset)
 
881
               _format_date(t, offset, timezone, date_fmt, show_offset)
845
882
    date_str = time.strftime(date_fmt, tt)
846
 
    if not isinstance(date_str, str):
 
883
    if not isinstance(date_str, text_type):
847
884
        date_str = date_str.decode(get_user_encoding(), 'replace')
848
885
    return date_str + offset_str
849
886
 
850
887
 
851
888
def _format_date(t, offset, timezone, date_fmt, show_offset):
852
889
    if timezone == 'utc':
853
 
        tt = time.gmtime(t)
 
890
        tt = gmtime(t)
854
891
        offset = 0
855
892
    elif timezone == 'original':
856
893
        if offset is None:
857
894
            offset = 0
858
 
        tt = time.gmtime(t + offset)
 
895
        tt = gmtime(t + offset)
859
896
    elif timezone == 'local':
860
897
        tt = time.localtime(t)
861
898
        offset = local_time_offset(t)
862
899
    else:
863
 
        raise UnsupportedTimezoneFormat(timezone)
 
900
        raise errors.UnsupportedTimezoneFormat(timezone)
864
901
    if date_fmt is None:
865
902
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
866
903
    if show_offset:
871
908
 
872
909
 
873
910
def compact_date(when):
874
 
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
 
911
    return time.strftime('%Y%m%d%H%M%S', gmtime(when))
875
912
 
876
913
 
877
914
def format_delta(delta):
890
927
        delta = -delta
891
928
 
892
929
    seconds = delta
893
 
    if seconds < 90:  # print seconds up to 90 seconds
 
930
    if seconds < 90: # print seconds up to 90 seconds
894
931
        if seconds == 1:
895
932
            return '%d second %s' % (seconds, direction,)
896
933
        else:
902
939
        plural_seconds = ''
903
940
    else:
904
941
        plural_seconds = 's'
905
 
    if minutes < 90:  # print minutes, seconds up to 90 minutes
 
942
    if minutes < 90: # print minutes, seconds up to 90 minutes
906
943
        if minutes == 1:
907
944
            return '%d minute, %d second%s %s' % (
908
 
                minutes, seconds, plural_seconds, direction)
 
945
                    minutes, seconds, plural_seconds, direction)
909
946
        else:
910
947
            return '%d minutes, %d second%s %s' % (
911
 
                minutes, seconds, plural_seconds, direction)
 
948
                    minutes, seconds, plural_seconds, direction)
912
949
 
913
950
    hours = int(minutes / 60)
914
951
    minutes -= 60 * hours
923
960
    return '%d hours, %d minute%s %s' % (hours, minutes,
924
961
                                         plural_minutes, direction)
925
962
 
926
 
 
927
963
def filesize(f):
928
964
    """Return size of given open file."""
929
965
    return os.fstat(f.fileno())[stat.ST_SIZE]
930
966
 
931
967
 
932
 
# Alias os.urandom to support platforms (which?) without /dev/urandom and
 
968
# Alias os.urandom to support platforms (which?) without /dev/urandom and 
933
969
# override if it doesn't work. Avoid checking on windows where there is
934
970
# significant initialisation cost that can be avoided for some bzr calls.
935
971
 
950
986
 
951
987
 
952
988
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
953
 
 
954
 
 
955
989
def rand_chars(num):
956
990
    """Return a random string of num alphanumeric characters
957
991
 
960
994
    """
961
995
    s = ''
962
996
    for raw_byte in rand_bytes(num):
963
 
        s += ALNUM[raw_byte % 36]
 
997
        s += ALNUM[ord(raw_byte) % 36]
964
998
    return s
965
999
 
966
1000
 
967
 
# TODO: We could later have path objects that remember their list
968
 
# decomposition (might be too tricksy though.)
 
1001
## TODO: We could later have path objects that remember their list
 
1002
## decomposition (might be too tricksy though.)
969
1003
 
970
1004
def splitpath(p):
971
1005
    """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 = ('.', '')
 
1006
    # split on either delimiter because people might use either on
 
1007
    # Windows
 
1008
    ps = re.split(r'[\\/]', p)
992
1009
 
993
1010
    rps = []
994
1011
    for f in ps:
995
 
        if f == parent_dir:
 
1012
        if f == '..':
996
1013
            raise errors.BzrError(gettext("sorry, %r not allowed in path") % f)
997
 
        elif f in current_empty_dir:
 
1014
        elif (f == '.') or (f == ''):
998
1015
            pass
999
1016
        else:
1000
1017
            rps.append(f)
1054
1071
def report_extension_load_failures():
1055
1072
    if not _extension_load_failures:
1056
1073
        return
1057
 
    if config.GlobalConfig().suppress_warning('missing_extensions'):
 
1074
    if config.GlobalStack().get('ignore_missing_extensions'):
1058
1075
        return
1059
1076
    # the warnings framework should by default show this only once
1060
1077
    from .trace import warning
1061
1078
    warning(
1062
1079
        "brz: warning: some compiled extensions could not be loaded; "
1063
 
        "see ``brz help missing-extensions``")
 
1080
        "see <https://answers.launchpad.net/bzr/+faq/703>")
1064
1081
    # we no longer show the specific missing extensions here, because it makes
1065
1082
    # the message too long and scary - see
1066
1083
    # https://bugs.launchpad.net/bzr/+bug/430529
1077
1094
    """Split s into lines, but without removing the newline characters."""
1078
1095
    # Trivially convert a fulltext into a 'chunked' representation, and let
1079
1096
    # chunks_to_lines do the heavy lifting.
1080
 
    if isinstance(s, bytes):
 
1097
    if isinstance(s, str):
1081
1098
        # chunks_to_lines only supports 8-bit strings
1082
1099
        return chunks_to_lines([s])
1083
1100
    else:
1089
1106
 
1090
1107
    This supports Unicode or plain string objects.
1091
1108
    """
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]]
 
1109
    lines = s.split('\n')
 
1110
    result = [line + '\n' for line in lines[:-1]]
1095
1111
    if lines[-1]:
1096
1112
        result.append(lines[-1])
1097
1113
    return result
1120
1136
    Will delete even if readonly.
1121
1137
    """
1122
1138
    try:
1123
 
        _delete_file_or_dir(path)
 
1139
       _delete_file_or_dir(path)
1124
1140
    except (OSError, IOError) as e:
1125
1141
        if e.errno in (errno.EPERM, errno.EACCES):
1126
1142
            # make writable and try again
1139
1155
    # - root can damage a solaris file system by using unlink,
1140
1156
    # - unlink raises different exceptions on different OSes (linux: EISDIR, win32:
1141
1157
    #   EACCES, OSX: EPERM) when invoked on a directory.
1142
 
    if isdir(path):  # Takes care of symlinks
 
1158
    if isdir(path): # Takes care of symlinks
1143
1159
        os.rmdir(path)
1144
1160
    else:
1145
1161
        os.unlink(path)
1187
1203
    #    separators
1188
1204
    # 3) '\xa0' isn't unicode safe since it is >128.
1189
1205
 
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:
 
1206
    # This should *not* be a unicode set of characters in case the source
 
1207
    # string is not a Unicode string. We can auto-up-cast the characters since
 
1208
    # they are ascii, but we don't want to auto-up-cast the string in case it
 
1209
    # is utf-8
 
1210
    for ch in ' \t\n\r\v\f':
1195
1211
        if ch in s:
1196
1212
            return True
1197
1213
    else:
1224
1240
    if len(base) < MIN_ABS_PATHLENGTH:
1225
1241
        # must have space for e.g. a drive letter
1226
1242
        raise ValueError(gettext('%r is too short to calculate a relative path')
1227
 
                         % (base,))
 
1243
            % (base,))
1228
1244
 
1229
1245
    rp = abspath(path)
1230
1246
 
1267
1283
 
1268
1284
    abs_base = abspath(base)
1269
1285
    current = abs_base
 
1286
    _listdir = os.listdir
1270
1287
 
1271
1288
    # use an explicit iterator so we can easily consume the rest on early exit.
1272
1289
    bit_iter = iter(rel.split('/'))
1273
1290
    for bit in bit_iter:
1274
1291
        lbit = bit.lower()
1275
1292
        try:
1276
 
            next_entries = scandir(current)
1277
 
        except OSError:  # enoent, eperm, etc
 
1293
            next_entries = _listdir(current)
 
1294
        except OSError: # enoent, eperm, etc
1278
1295
            # We can't find this in the filesystem, so just append the
1279
1296
            # remaining bits.
1280
1297
            current = pathjoin(current, bit, *list(bit_iter))
1281
1298
            break
1282
 
        for entry in next_entries:
1283
 
            if lbit == entry.name.lower():
1284
 
                current = entry.path
 
1299
        for look in next_entries:
 
1300
            if lbit == look.lower():
 
1301
                current = pathjoin(current, look)
1285
1302
                break
1286
1303
        else:
1287
1304
            # got to the end, nothing matched, so we just return the
1291
1308
            break
1292
1309
    return current[len(abs_base):].lstrip('/')
1293
1310
 
1294
 
 
1295
1311
# XXX - TODO - we need better detection/integration of case-insensitive
1296
1312
# file-systems; Linux often sees FAT32 devices (or NFS-mounted OSX
1297
1313
# filesystems), for example, so could probably benefit from the same basic
1302
1318
else:
1303
1319
    canonical_relpath = relpath
1304
1320
 
1305
 
 
1306
1321
def canonical_relpaths(base, paths):
1307
1322
    """Create an iterable to canonicalize a sequence of relative paths.
1308
1323
 
1320
1335
    Otherwise it is decoded from the the filesystem's encoding. If decoding
1321
1336
    fails, a errors.BadFilenameEncoding exception is raised.
1322
1337
    """
1323
 
    if isinstance(filename, str):
 
1338
    if isinstance(filename, text_type):
1324
1339
        return filename
1325
1340
    try:
1326
1341
        return filename.decode(_fs_enc)
1335
1350
    Otherwise it is decoded from utf-8. If decoding fails, the exception is
1336
1351
    wrapped in a BzrBadParameterNotUnicode exception.
1337
1352
    """
1338
 
    if isinstance(unicode_or_utf8_string, str):
 
1353
    if isinstance(unicode_or_utf8_string, text_type):
1339
1354
        return unicode_or_utf8_string
1340
1355
    try:
1341
1356
        return unicode_or_utf8_string.decode('utf8')
1349
1364
    If it is a str, it is returned.
1350
1365
    If it is Unicode, it is encoded into a utf-8 string.
1351
1366
    """
1352
 
    if isinstance(unicode_or_utf8_string, bytes):
 
1367
    if isinstance(unicode_or_utf8_string, str):
1353
1368
        # TODO: jam 20070209 This is overkill, and probably has an impact on
1354
1369
        #       performance if we are dealing with lots of apis that want a
1355
1370
        #       utf-8 revision id
1362
1377
    return unicode_or_utf8_string.encode('utf-8')
1363
1378
 
1364
1379
 
 
1380
def safe_revision_id(unicode_or_utf8_string):
 
1381
    """Revision ids should now be utf8, but at one point they were unicode.
 
1382
 
 
1383
    :param unicode_or_utf8_string: A possibly Unicode revision_id. (can also be
 
1384
        utf8 or None).
 
1385
    :return: None or a utf8 revision id.
 
1386
    """
 
1387
    if (unicode_or_utf8_string is None
 
1388
        or unicode_or_utf8_string.__class__ == str):
 
1389
        return unicode_or_utf8_string
 
1390
    raise TypeError('Unicode revision ids are no longer supported. '
 
1391
                    'Revision id generators should be creating utf8 revision '
 
1392
                    'ids.')
 
1393
 
 
1394
 
 
1395
def safe_file_id(unicode_or_utf8_string):
 
1396
    """File ids should now be utf8, but at one point they were unicode.
 
1397
 
 
1398
    This is the same as safe_utf8, except it uses the cached encode functions
 
1399
    to save a little bit of performance.
 
1400
 
 
1401
    :param unicode_or_utf8_string: A possibly Unicode file_id. (can also be
 
1402
        utf8 or None).
 
1403
    :return: None or a utf8 file id.
 
1404
    """
 
1405
    if (unicode_or_utf8_string is None
 
1406
        or unicode_or_utf8_string.__class__ == str):
 
1407
        return unicode_or_utf8_string
 
1408
    raise TypeError('Unicode file ids are no longer supported. '
 
1409
                    'File id generators should be creating utf8 file ids.')
 
1410
 
 
1411
 
1365
1412
_platform_normalizes_filenames = False
1366
1413
if sys.platform == 'darwin':
1367
1414
    _platform_normalizes_filenames = True
1390
1437
    can be accessed by that path.
1391
1438
    """
1392
1439
 
1393
 
    if isinstance(path, bytes):
1394
 
        path = path.decode(sys.getfilesystemencoding())
1395
 
    return unicodedata.normalize('NFC', path), True
 
1440
    return unicodedata.normalize('NFC', unicode(path)), True
1396
1441
 
1397
1442
 
1398
1443
def _inaccessible_normalized_filename(path):
1399
1444
    __doc__ = _accessible_normalized_filename.__doc__
1400
1445
 
1401
 
    if isinstance(path, bytes):
1402
 
        path = path.decode(sys.getfilesystemencoding())
1403
 
    normalized = unicodedata.normalize('NFC', path)
 
1446
    normalized = unicodedata.normalize('NFC', unicode(path))
1404
1447
    return normalized, normalized == path
1405
1448
 
1406
1449
 
1429
1472
    except AttributeError:
1430
1473
        # siginterrupt doesn't exist on this platform, or for this version
1431
1474
        # of Python.
1432
 
        def siginterrupt(signum, flag): return None
 
1475
        siginterrupt = lambda signum, flag: None
1433
1476
    if restart_syscall:
1434
1477
        def sig_handler(*args):
1435
1478
            # Python resets the siginterrupt flag when a signal is
1460
1503
_terminal_size_state = 'no_data'
1461
1504
_first_terminal_size = None
1462
1505
 
1463
 
 
1464
1506
def terminal_width():
1465
1507
    """Return terminal width.
1466
1508
 
1547
1589
 
1548
1590
 
1549
1591
def _win32_terminal_size(width, height):
1550
 
    width, height = win32utils.get_console_size(
1551
 
        defaultx=width, defaulty=height)
 
1592
    width, height = win32utils.get_console_size(defaultx=width, defaulty=height)
1552
1593
    return width, height
1553
1594
 
1554
1595
 
1555
1596
def _ioctl_terminal_size(width, height):
1556
1597
    try:
1557
 
        import struct
1558
 
        import fcntl
1559
 
        import termios
 
1598
        import struct, fcntl, termios
1560
1599
        s = struct.pack('HHHH', 0, 0, 0, 0)
1561
1600
        x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
1562
1601
        height, width = struct.unpack('HHHH', x)[0:2]
1564
1603
        pass
1565
1604
    return width, height
1566
1605
 
1567
 
 
1568
1606
_terminal_size = None
1569
1607
"""Returns the terminal size as (width, height).
1570
1608
 
1580
1618
    _terminal_size = _ioctl_terminal_size
1581
1619
 
1582
1620
 
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
 
1621
def supports_executable():
 
1622
    return sys.platform != "win32"
1617
1623
 
1618
1624
 
1619
1625
def supports_posix_readonly():
1642
1648
        if orig_val is not None:
1643
1649
            del os.environ[env_variable]
1644
1650
    else:
 
1651
        if not PY3 and isinstance(value, text_type):
 
1652
            value = value.encode(get_user_encoding())
1645
1653
        os.environ[env_variable] = value
1646
1654
    return orig_val
1647
1655
 
1660
1668
        raise errors.IllegalPath(path)
1661
1669
 
1662
1670
 
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
 
 
 
1671
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
1673
1672
 
1674
1673
def _is_error_enotdir(e):
1675
1674
    """Check if this exception represents ENOTDIR.
1687
1686
    :return: True if this represents an ENOTDIR error. False otherwise.
1688
1687
    """
1689
1688
    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
 
             ))):
 
1689
    if (en == errno.ENOTDIR
 
1690
        or (sys.platform == 'win32'
 
1691
            and (en == _WIN32_ERROR_DIRECTORY
 
1692
                 or (en == errno.EINVAL
 
1693
                     and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
 
1694
        ))):
1696
1695
        return True
1697
1696
    return False
1698
1697
 
1725
1724
        rooted higher up.
1726
1725
    :return: an iterator over the dirs.
1727
1726
    """
1728
 
    # TODO there is a bit of a smell where the results of the directory-
 
1727
    #TODO there is a bit of a smell where the results of the directory-
1729
1728
    # summary in this, and the path from the root, may not agree
1730
1729
    # depending on top and prefix - i.e. ./foo and foo as a pair leads to
1731
1730
    # potentially confusing output. We should make this more robust - but
1732
1731
    # not at a speed cost. RBC 20060731
 
1732
    _lstat = os.lstat
1733
1733
    _directory = _directory_kind
 
1734
    _listdir = os.listdir
 
1735
    _kind_from_mode = file_kind_from_stat_mode
1734
1736
    pending = [(safe_unicode(prefix), "", _directory, None, safe_unicode(top))]
1735
1737
    while pending:
1736
1738
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1742
1744
        top_slash = top + u'/'
1743
1745
 
1744
1746
        dirblock = []
 
1747
        append = dirblock.append
1745
1748
        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))
 
1749
            names = sorted(map(decode_filename, _listdir(top)))
1751
1750
        except OSError as e:
1752
1751
            if not _is_error_enotdir(e):
1753
1752
                raise
1754
 
        except UnicodeDecodeError as e:
1755
 
            raise errors.BadFilenameEncoding(e.object, _fs_enc)
1756
 
        dirblock.sort()
 
1753
        else:
 
1754
            for name in names:
 
1755
                abspath = top_slash + name
 
1756
                statvalue = _lstat(abspath)
 
1757
                kind = _kind_from_mode(statvalue.st_mode)
 
1758
                append((relprefix + name, name, kind, statvalue, abspath))
1757
1759
        yield (relroot, top), dirblock
1758
1760
 
1759
1761
        # push the user specified dirs from dirblock
1865
1867
        See DirReader.read_dir for details.
1866
1868
        """
1867
1869
        _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)
 
1870
        _lstat = os.lstat
 
1871
        _listdir = os.listdir
 
1872
        _kind_from_mode = file_kind_from_stat_mode
1872
1873
 
1873
1874
        if prefix:
1874
 
            relprefix = prefix + b'/'
 
1875
            relprefix = prefix + '/'
1875
1876
        else:
1876
 
            relprefix = b''
1877
 
        top_slash = top + '/'
 
1877
            relprefix = ''
 
1878
        top_slash = top + u'/'
1878
1879
 
1879
1880
        dirblock = []
1880
1881
        append = dirblock.append
1881
 
        for entry in scandir(safe_utf8(top)):
 
1882
        for name in sorted(_listdir(top)):
1882
1883
            try:
1883
 
                name = _fs_decode(entry.name)
 
1884
                name_utf8 = _utf8_encode(name)[0]
1884
1885
            except UnicodeDecodeError:
1885
1886
                raise errors.BadFilenameEncoding(
1886
 
                    relprefix + entry.name, _fs_enc)
 
1887
                    _utf8_encode(relprefix)[0] + name, _fs_enc)
1887
1888
            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)
 
1889
            statvalue = _lstat(abspath)
 
1890
            kind = _kind_from_mode(statvalue.st_mode)
1891
1891
            append((relprefix + name_utf8, name_utf8, kind, statvalue, abspath))
1892
 
        return sorted(dirblock)
 
1892
        return dirblock
1893
1893
 
1894
1894
 
1895
1895
def copy_tree(from_path, to_path, handlers={}):
1920
1920
        link_to = os.readlink(source)
1921
1921
        os.symlink(link_to, dest)
1922
1922
 
1923
 
    real_handlers = {'file': shutil.copy2,
1924
 
                     'symlink': copy_link,
1925
 
                     'directory': copy_dir,
1926
 
                     }
 
1923
    real_handlers = {'file':shutil.copy2,
 
1924
                     'symlink':copy_link,
 
1925
                     'directory':copy_dir,
 
1926
                    }
1927
1927
    real_handlers.update(handlers)
1928
1928
 
1929
1929
    if not os.path.exists(to_path):
1944
1944
    if chown is None:
1945
1945
        return
1946
1946
 
1947
 
    if src is None:
 
1947
    if src == None:
1948
1948
        src = os.path.dirname(dst)
1949
1949
        if src == '':
1950
1950
            src = '.'
1952
1952
    try:
1953
1953
        s = os.stat(src)
1954
1954
        chown(dst, s.st_uid, s.st_gid)
1955
 
    except OSError:
 
1955
    except OSError as e:
1956
1956
        trace.warning(
1957
1957
            'Unable to copy ownership from "%s" to "%s". '
1958
1958
            'You may want to set it manually.', src, dst)
1964
1964
 
1965
1965
    This can be used to sort paths in the same way that walkdirs does.
1966
1966
    """
1967
 
    return (dirname(path), path)
 
1967
    return (dirname(path) , path)
1968
1968
 
1969
1969
 
1970
1970
def compare_paths_prefix_order(path_a, path_b):
1971
1971
    """Compare path_a and path_b to generate the same order walkdirs uses."""
1972
1972
    key_a = path_prefix_key(path_a)
1973
1973
    key_b = path_prefix_key(path_b)
1974
 
    return (key_a > key_b) - (key_a < key_b)
 
1974
    return cmp(key_a, key_b)
1975
1975
 
1976
1976
 
1977
1977
_cached_user_encoding = None
2008
2008
                             ' unknown encoding %s.'
2009
2009
                             ' Continuing with ascii encoding.\n'
2010
2010
                             % user_encoding
2011
 
                             )
 
2011
                            )
2012
2012
        user_encoding = 'ascii'
2013
2013
    else:
2014
2014
        # Get 'ascii' when setlocale has not been called or LANG=C or unset.
2038
2038
        return win32utils.get_host_name()
2039
2039
    else:
2040
2040
        import socket
2041
 
        return socket.gethostname()
 
2041
        return socket.gethostname().decode(get_user_encoding())
2042
2042
 
2043
2043
 
2044
2044
# We must not read/write any more than 64k at a time from/to a socket so we
2056
2056
 
2057
2057
 
2058
2058
def read_bytes_from_socket(sock, report_activity=None,
2059
 
                           max_read_size=MAX_SOCKET_CHUNK):
 
2059
        max_read_size=MAX_SOCKET_CHUNK):
2060
2060
    """Read up to max_read_size of bytes from sock and notify of progress.
2061
2061
 
2062
2062
    Translates "Connection reset by peer" into file-like EOF (return an
2065
2065
    """
2066
2066
    while True:
2067
2067
        try:
2068
 
            data = sock.recv(max_read_size)
 
2068
            bytes = sock.recv(max_read_size)
2069
2069
        except socket.error as e:
2070
2070
            eno = e.args[0]
2071
2071
            if eno in _end_of_stream_errors:
2072
2072
                # The connection was closed by the other side.  Callers expect
2073
2073
                # an empty string to signal end-of-stream.
2074
 
                return b""
 
2074
                return ""
2075
2075
            elif eno == errno.EINTR:
2076
2076
                # Retry the interrupted recv.
2077
2077
                continue
2078
2078
            raise
2079
2079
        else:
2080
2080
            if report_activity is not None:
2081
 
                report_activity(len(data), 'read')
2082
 
            return data
 
2081
                report_activity(len(bytes), 'read')
 
2082
            return bytes
2083
2083
 
2084
2084
 
2085
2085
def recv_all(socket, count):
2092
2092
 
2093
2093
    This isn't optimized and is intended mostly for use in testing.
2094
2094
    """
2095
 
    b = b''
 
2095
    b = ''
2096
2096
    while len(b) < count:
2097
2097
        new = read_bytes_from_socket(socket, None, count - len(b))
2098
 
        if new == b'':
2099
 
            break  # eof
 
2098
        if new == '':
 
2099
            break # eof
2100
2100
        b += new
2101
2101
    return b
2102
2102
 
2119
2119
    view = memoryview(bytes)
2120
2120
    while sent_total < byte_count:
2121
2121
        try:
2122
 
            sent = sock.send(view[sent_total:sent_total + MAX_SOCKET_CHUNK])
 
2122
            sent = sock.send(view[sent_total:sent_total+MAX_SOCKET_CHUNK])
2123
2123
        except (socket.error, IOError) as e:
2124
2124
            if e.args[0] in _end_of_stream_errors:
2125
2125
                raise errors.ConnectionReset(
2151
2151
            sock.connect(sa)
2152
2152
            return sock
2153
2153
 
2154
 
        except socket.error as e:
2155
 
            err = e
 
2154
        except socket.error as err:
2156
2155
            # 'err' is now the most recent error
2157
2156
            if sock is not None:
2158
2157
                sock.close()
2203
2202
    base = dirname(breezy.__file__)
2204
2203
    if getattr(sys, 'frozen', None):    # bzr.exe
2205
2204
        base = abspath(pathjoin(base, '..', '..'))
2206
 
    with open(pathjoin(base, resource_relpath), "rt") as f:
 
2205
    f = file(pathjoin(base, resource_relpath), "rU")
 
2206
    try:
2207
2207
        return f.read()
2208
 
 
 
2208
    finally:
 
2209
        f.close()
2209
2210
 
2210
2211
def file_kind_from_stat_mode_thunk(mode):
2211
2212
    global file_kind_from_stat_mode
2213
2214
        try:
2214
2215
            from ._readdir_pyx import UTF8DirReader
2215
2216
            file_kind_from_stat_mode = UTF8DirReader().kind_from_mode
2216
 
        except ImportError:
 
2217
        except ImportError as e:
2217
2218
            # This is one time where we won't warn that an extension failed to
2218
2219
            # load. The extension is never available on Windows anyway.
2219
2220
            from ._readdir_py import (
2220
2221
                _kind_from_mode as file_kind_from_stat_mode
2221
2222
                )
2222
2223
    return file_kind_from_stat_mode(mode)
2223
 
 
2224
 
 
2225
2224
file_kind_from_stat_mode = file_kind_from_stat_mode_thunk
2226
2225
 
2227
 
 
2228
2226
def file_stat(f, _lstat=os.lstat):
2229
2227
    try:
2230
2228
        # XXX cache?
2234
2232
            raise errors.NoSuchFile(f)
2235
2233
        raise
2236
2234
 
2237
 
 
2238
2235
def file_kind(f, _lstat=os.lstat):
2239
2236
    stat_value = file_stat(f, _lstat)
2240
2237
    return file_kind_from_stat_mode(stat_value.st_mode)
2241
2238
 
2242
 
 
2243
2239
def until_no_eintr(f, *a, **kw):
2244
2240
    """Run f(*a, **kw), retrying if an EINTR error occurs.
2245
2241
 
2296
2292
                                stdout=subprocess.PIPE).communicate()[0]
2297
2293
elif sys.platform == 'sunos5':
2298
2294
    def _local_concurrency():
2299
 
        return subprocess.Popen(['psrinfo', '-p', ],
 
2295
        return subprocess.Popen(['psrinfo', '-p',],
2300
2296
                                stdout=subprocess.PIPE).communicate()[0]
2301
2297
elif sys.platform == "win32":
2302
2298
    def _local_concurrency():
2310
2306
 
2311
2307
_cached_local_concurrency = None
2312
2308
 
2313
 
 
2314
2309
def local_concurrency(use_cache=True):
2315
2310
    """Return how many processes can be run concurrently.
2316
2311
 
2324
2319
 
2325
2320
    concurrency = os.environ.get('BRZ_CONCURRENCY', None)
2326
2321
    if concurrency is None:
2327
 
        import multiprocessing
2328
2322
        try:
 
2323
            import multiprocessing
2329
2324
            concurrency = multiprocessing.cpu_count()
2330
 
        except NotImplementedError:
2331
 
            # multiprocessing.cpu_count() isn't implemented on all platforms
 
2325
        except (ImportError, NotImplementedError):
 
2326
            # multiprocessing is only available on Python >= 2.6
 
2327
            # and multiprocessing.cpu_count() isn't implemented on all
 
2328
            # platforms
2332
2329
            try:
2333
2330
                concurrency = _local_concurrency()
2334
2331
            except (OSError, IOError):
2338
2335
    except (TypeError, ValueError):
2339
2336
        concurrency = 1
2340
2337
    if use_cache:
2341
 
        _cached_local_concurrency = concurrency
 
2338
        _cached_concurrency = concurrency
2342
2339
    return concurrency
2343
2340
 
2344
2341
 
2356
2353
            data, _ = self.encode(object, self.errors)
2357
2354
            self.stream.write(data)
2358
2355
 
2359
 
 
2360
2356
if sys.platform == 'win32':
2361
2357
    def open_file(filename, mode='r', bufsize=-1):
2362
2358
        """This function is used to override the ``open`` builtin.
2390
2386
            else:
2391
2387
                flags |= os.O_WRONLY
2392
2388
            flags |= os.O_CREAT | os.O_APPEND
2393
 
        else:  # reading
 
2389
        else: #reading
2394
2390
            if updating:
2395
2391
                flags |= os.O_RDWR
2396
2392
            else:
2436
2432
 
2437
2433
def find_executable_on_path(name):
2438
2434
    """Finds an executable on the PATH.
2439
 
 
 
2435
    
2440
2436
    On Windows, this will try to append each extension in the PATHEXT
2441
2437
    environment variable to the name, if it cannot be found with the name
2442
2438
    as given.
2443
 
 
 
2439
    
2444
2440
    :param name: The base name of the executable.
2445
2441
    :return: The path to the executable found or None.
2446
2442
    """
2484
2480
            # exists, though not ours
2485
2481
            return False
2486
2482
        else:
2487
 
            trace.mutter("os.kill(%d, 0) failed: %s" % (pid, e))
 
2483
            mutter("os.kill(%d, 0) failed: %s" % (pid, e))
2488
2484
            # Don't really know.
2489
2485
            return False
2490
2486
    else:
2491
2487
        # Exists and our process: not dead.
2492
2488
        return False
2493
2489
 
2494
 
 
2495
2490
if sys.platform == "win32":
2496
2491
    is_local_pid_dead = win32utils.is_local_pid_dead
2497
2492
else:
2504
2499
 
2505
2500
def fdatasync(fileno):
2506
2501
    """Flush file contents to disk if possible.
2507
 
 
 
2502
    
2508
2503
    :param fileno: Integer OS file handle.
2509
2504
    :raises TransportNotPossible: If flushing to disk is not possible.
2510
2505
    """
2524
2519
 
2525
2520
def ensure_empty_directory_exists(path, exception_class):
2526
2521
    """Make sure a local directory exists and is empty.
2527
 
 
 
2522
    
2528
2523
    If it does not exist, it is created.  If it exists and is not empty, an
2529
2524
    instance of exception_class is raised.
2530
2525
    """
2537
2532
            raise exception_class(path)
2538
2533
 
2539
2534
 
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
 
2535
def is_environment_error(evalue):
 
2536
    """True if exception instance is due to a process environment issue
 
2537
 
 
2538
    This includes OSError and IOError, but also other errors that come from
 
2539
    the operating system or core libraries but are not subclasses of those.
 
2540
    """
 
2541
    if isinstance(evalue, (EnvironmentError, select.error)):
 
2542
        return True
 
2543
    if sys.platform == "win32" and win32utils._is_pywintypes_error(evalue):
 
2544
        return True
 
2545
    return False