~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-10-25 08:29:08 UTC
  • mfrom: (2940.1.2 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20071025082908-abn3kunrb2ivdvth
renaming of experimental pack formats to include knitpack in their name (Ian Clatworthy)

Show diffs side-by-side

added added

removed removed

Lines of Context:
53
53
    )
54
54
""")
55
55
 
56
 
 
57
56
import bzrlib
58
57
from bzrlib import symbol_versioning
59
58
from bzrlib.symbol_versioning import (
69
68
# OR with 0 on those platforms
70
69
O_BINARY = getattr(os, 'O_BINARY', 0)
71
70
 
 
71
# On posix, use lstat instead of stat so that we can
 
72
# operate on broken symlinks. On Windows revert to stat.
 
73
lstat = getattr(os, 'lstat', os.stat)
72
74
 
73
75
def make_readonly(filename):
74
76
    """Make a filename read-only."""
75
 
    mod = os.lstat(filename).st_mode
 
77
    mod = lstat(filename).st_mode
76
78
    if not stat.S_ISLNK(mod):
77
79
        mod = mod & 0777555
78
80
        os.chmod(filename, mod)
79
81
 
80
82
 
81
83
def make_writable(filename):
82
 
    mod = os.lstat(filename).st_mode
 
84
    mod = lstat(filename).st_mode
83
85
    if not stat.S_ISLNK(mod):
84
86
        mod = mod | 0200
85
87
        os.chmod(filename, mod)
149
151
    try:
150
152
        return _mapper(_lstat(f).st_mode)
151
153
    except OSError, e:
152
 
        if getattr(e, 'errno', None) in (errno.ENOENT, errno.ENOTDIR):
 
154
        if getattr(e, 'errno', None) == errno.ENOENT:
153
155
            raise errors.NoSuchFile(f)
154
156
        raise
155
157
 
234
236
 
235
237
    success = False
236
238
    try:
237
 
        try:
238
 
            # This may throw an exception, in which case success will
239
 
            # not be set.
240
 
            rename_func(old, new)
241
 
            success = True
242
 
        except (IOError, OSError), e:
243
 
            # source and target may be aliases of each other (e.g. on a
244
 
            # case-insensitive filesystem), so we may have accidentally renamed
245
 
            # source by when we tried to rename target
246
 
            if not (file_existed and e.errno in (None, errno.ENOENT)):
247
 
                raise
 
239
        # This may throw an exception, in which case success will
 
240
        # not be set.
 
241
        rename_func(old, new)
 
242
        success = True
248
243
    finally:
249
244
        if file_existed:
250
245
            # If the file used to exist, rename it back into place
360
355
 
361
356
 
362
357
def _mac_getcwd():
363
 
    return unicodedata.normalize('NFC', os.getcwdu())
 
358
    return unicodedata.normalize('NFKC', os.getcwdu())
364
359
 
365
360
 
366
361
# Default is to just use the python builtins, but these can be rebound on
468
463
        return pathjoin(F(p), e)
469
464
 
470
465
 
 
466
def backup_file(fn):
 
467
    """Copy a file to a backup.
 
468
 
 
469
    Backups are named in GNU-style, with a ~ suffix.
 
470
 
 
471
    If the file is already a backup, it's not copied.
 
472
    """
 
473
    if fn[-1] == '~':
 
474
        return
 
475
    bfn = fn + '~'
 
476
 
 
477
    if has_symlinks() and os.path.islink(fn):
 
478
        target = os.readlink(fn)
 
479
        os.symlink(target, bfn)
 
480
        return
 
481
    inf = file(fn, 'rb')
 
482
    try:
 
483
        content = inf.read()
 
484
    finally:
 
485
        inf.close()
 
486
    
 
487
    outf = file(bfn, 'wb')
 
488
    try:
 
489
        outf.write(content)
 
490
    finally:
 
491
        outf.close()
 
492
 
 
493
 
471
494
def isdir(f):
472
495
    """True if f is an accessible directory."""
473
496
    try:
530
553
    return False
531
554
 
532
555
 
533
 
def pumpfile(from_file, to_file, read_length=-1, buff_size=32768):
 
556
def pumpfile(fromfile, tofile):
534
557
    """Copy contents of one file to another.
535
 
 
536
 
    The read_length can either be -1 to read to end-of-file (EOF) or
537
 
    it can specify the maximum number of bytes to read.
538
 
 
539
 
    The buff_size represents the maximum size for each read operation
540
 
    performed on from_file.
541
 
 
 
558
    
542
559
    :return: The number of bytes copied.
543
560
    """
 
561
    BUFSIZE = 32768
544
562
    length = 0
545
 
    if read_length >= 0:
546
 
        # read specified number of bytes
547
 
 
548
 
        while read_length > 0:
549
 
            num_bytes_to_read = min(read_length, buff_size)
550
 
 
551
 
            block = from_file.read(num_bytes_to_read)
552
 
            if not block:
553
 
                # EOF reached
554
 
                break
555
 
            to_file.write(block)
556
 
 
557
 
            actual_bytes_read = len(block)
558
 
            read_length -= actual_bytes_read
559
 
            length += actual_bytes_read
560
 
    else:
561
 
        # read to EOF
562
 
        while True:
563
 
            block = from_file.read(buff_size)
564
 
            if not block:
565
 
                # EOF reached
566
 
                break
567
 
            to_file.write(block)
568
 
            length += len(block)
 
563
    while True:
 
564
        b = fromfile.read(BUFSIZE)
 
565
        if not b:
 
566
            break
 
567
        tofile.write(b)
 
568
        length += len(b)
569
569
    return length
570
570
 
571
571
 
572
 
def pump_string_file(bytes, file_handle, segment_size=None):
573
 
    """Write bytes to file_handle in many smaller writes.
574
 
 
575
 
    :param bytes: The string to write.
576
 
    :param file_handle: The file to write to.
577
 
    """
578
 
    # Write data in chunks rather than all at once, because very large
579
 
    # writes fail on some platforms (e.g. Windows with SMB  mounted
580
 
    # drives).
581
 
    if not segment_size:
582
 
        segment_size = 5242880 # 5MB
583
 
    segments = range(len(bytes) / segment_size + 1)
584
 
    write = file_handle.write
585
 
    for segment_index in segments:
586
 
        segment = buffer(bytes, segment_index * segment_size, segment_size)
587
 
        write(segment)
588
 
 
589
 
 
590
572
def file_iterator(input_file, readsize=32768):
591
573
    while True:
592
574
        b = input_file.read(readsize)
596
578
 
597
579
 
598
580
def sha_file(f):
599
 
    """Calculate the hexdigest of an open file.
600
 
 
601
 
    The file cursor should be already at the start.
602
 
    """
 
581
    if getattr(f, 'tell', None) is not None:
 
582
        assert f.tell() == 0
603
583
    s = sha.new()
604
584
    BUFSIZE = 128<<10
605
585
    while True:
660
640
    offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
661
641
    return offset.days * 86400 + offset.seconds
662
642
 
663
 
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
664
643
    
665
644
def format_date(t, offset=0, timezone='original', date_fmt=None,
666
645
                show_offset=True):
685
664
        tt = time.localtime(t)
686
665
        offset = local_time_offset(t)
687
666
    else:
688
 
        raise errors.UnsupportedTimezoneFormat(timezone)
 
667
        raise errors.BzrError("unsupported timezone format %r" % timezone,
 
668
                              ['options are "utc", "original", "local"'])
689
669
    if date_fmt is None:
690
670
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
691
671
    if show_offset:
692
672
        offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
693
673
    else:
694
674
        offset_str = ''
695
 
    # day of week depends on locale, so we do this ourself
696
 
    date_fmt = date_fmt.replace('%a', weekdays[tt[6]])
697
675
    return (time.strftime(date_fmt, tt) +  offset_str)
698
676
 
699
677
 
796
774
 
797
775
def splitpath(p):
798
776
    """Turn string into list of parts."""
 
777
    assert isinstance(p, basestring)
 
778
 
799
779
    # split on either delimiter because people might use either on
800
780
    # Windows
801
781
    ps = re.split(r'[\\/]', p)
811
791
    return rps
812
792
 
813
793
def joinpath(p):
 
794
    assert isinstance(p, (list, tuple))
814
795
    for f in p:
815
796
        if (f == '..') or (f is None) or (f == ''):
816
797
            raise errors.BzrError("sorry, %r not allowed in path" % f)
863
844
        return False
864
845
 
865
846
 
866
 
def has_hardlinks():
867
 
    if getattr(os, 'link', None) is not None:
868
 
        return True
869
 
    else:
870
 
        return False
871
 
 
872
 
 
873
 
def host_os_dereferences_symlinks():
874
 
    return (has_symlinks()
875
 
            and sys.platform not in ('cygwin', 'win32'))
876
 
 
877
 
 
878
847
def contains_whitespace(s):
879
848
    """True if there are any whitespace characters in s."""
880
849
    # string.whitespace can include '\xa0' in certain locales, because it is
915
884
    avoids that problem.
916
885
    """
917
886
 
918
 
    if len(base) < MIN_ABS_PATHLENGTH:
919
 
        # must have space for e.g. a drive letter
920
 
        raise ValueError('%r is too short to calculate a relative path'
921
 
            % (base,))
 
887
    assert len(base) >= MIN_ABS_PATHLENGTH, ('Length of base must be equal or'
 
888
        ' exceed the platform minimum length (which is %d)' % 
 
889
        MIN_ABS_PATHLENGTH)
922
890
 
923
891
    rp = abspath(path)
924
892
 
1041
1009
    On platforms where the system does not normalize filenames 
1042
1010
    (Windows, Linux), you have to access a file by its exact path.
1043
1011
 
1044
 
    Internally, bzr only supports NFC normalization, since that is 
 
1012
    Internally, bzr only supports NFC/NFKC normalization, since that is 
1045
1013
    the standard for XML documents.
1046
1014
 
1047
1015
    So return the normalized path, and a flag indicating if the file
1048
1016
    can be accessed by that path.
1049
1017
    """
1050
1018
 
1051
 
    return unicodedata.normalize('NFC', unicode(path)), True
 
1019
    return unicodedata.normalize('NFKC', unicode(path)), True
1052
1020
 
1053
1021
 
1054
1022
def _inaccessible_normalized_filename(path):
1055
1023
    __doc__ = _accessible_normalized_filename.__doc__
1056
1024
 
1057
 
    normalized = unicodedata.normalize('NFC', unicode(path))
 
1025
    normalized = unicodedata.normalize('NFKC', unicode(path))
1058
1026
    return normalized, normalized == path
1059
1027
 
1060
1028
 
1137
1105
        raise errors.IllegalPath(path)
1138
1106
 
1139
1107
 
1140
 
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
1141
 
 
1142
 
def _is_error_enotdir(e):
1143
 
    """Check if this exception represents ENOTDIR.
1144
 
 
1145
 
    Unfortunately, python is very inconsistent about the exception
1146
 
    here. The cases are:
1147
 
      1) Linux, Mac OSX all versions seem to set errno == ENOTDIR
1148
 
      2) Windows, Python2.4, uses errno == ERROR_DIRECTORY (267)
1149
 
         which is the windows error code.
1150
 
      3) Windows, Python2.5 uses errno == EINVAL and
1151
 
         winerror == ERROR_DIRECTORY
1152
 
 
1153
 
    :param e: An Exception object (expected to be OSError with an errno
1154
 
        attribute, but we should be able to cope with anything)
1155
 
    :return: True if this represents an ENOTDIR error. False otherwise.
1156
 
    """
1157
 
    en = getattr(e, 'errno', None)
1158
 
    if (en == errno.ENOTDIR
1159
 
        or (sys.platform == 'win32'
1160
 
            and (en == _WIN32_ERROR_DIRECTORY
1161
 
                 or (en == errno.EINVAL
1162
 
                     and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
1163
 
        ))):
1164
 
        return True
1165
 
    return False
1166
 
 
1167
 
 
1168
1108
def walkdirs(top, prefix=""):
1169
1109
    """Yield data about all the directories in a tree.
1170
1110
    
1214
1154
 
1215
1155
        dirblock = []
1216
1156
        append = dirblock.append
1217
 
        try:
1218
 
            names = sorted(_listdir(top))
1219
 
        except OSError, e:
1220
 
            if not _is_error_enotdir(e):
1221
 
                raise
1222
 
        else:
1223
 
            for name in names:
1224
 
                abspath = top_slash + name
1225
 
                statvalue = _lstat(abspath)
1226
 
                kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
1227
 
                append((relprefix + name, name, kind, statvalue, abspath))
 
1157
        for name in sorted(_listdir(top)):
 
1158
            abspath = top_slash + name
 
1159
            statvalue = _lstat(abspath)
 
1160
            kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
 
1161
            append((relprefix + name, name, kind, statvalue, abspath))
1228
1162
        yield (relroot, top), dirblock
1229
1163
 
1230
1164
        # push the user specified dirs from dirblock
1231
1165
        pending.extend(d for d in reversed(dirblock) if d[2] == _directory)
1232
1166
 
1233
1167
 
1234
 
_real_walkdirs_utf8 = None
1235
 
 
1236
1168
def _walkdirs_utf8(top, prefix=""):
1237
1169
    """Yield data about all the directories in a tree.
1238
1170
 
1247
1179
        path-from-top might be unicode or utf8, but it is the correct path to
1248
1180
        pass to os functions to affect the file in question. (such as os.lstat)
1249
1181
    """
1250
 
    global _real_walkdirs_utf8
1251
 
    if _real_walkdirs_utf8 is None:
1252
 
        fs_encoding = _fs_enc.upper()
1253
 
        if win32utils.winver == 'Windows NT':
1254
 
            # Win98 doesn't have unicode apis like FindFirstFileW
1255
 
            # TODO: We possibly could support Win98 by falling back to the
1256
 
            #       original FindFirstFile, and using TCHAR instead of WCHAR,
1257
 
            #       but that gets a bit tricky, and requires custom compiling
1258
 
            #       for win98 anyway.
1259
 
            try:
1260
 
                from bzrlib._walkdirs_win32 import _walkdirs_utf8_win32_find_file
1261
 
            except ImportError:
1262
 
                _real_walkdirs_utf8 = _walkdirs_unicode_to_utf8
1263
 
            else:
1264
 
                _real_walkdirs_utf8 = _walkdirs_utf8_win32_find_file
1265
 
        elif fs_encoding not in ('UTF-8', 'US-ASCII', 'ANSI_X3.4-1968'):
1266
 
            # ANSI_X3.4-1968 is a form of ASCII
1267
 
            _real_walkdirs_utf8 = _walkdirs_unicode_to_utf8
1268
 
        else:
1269
 
            _real_walkdirs_utf8 = _walkdirs_fs_utf8
1270
 
    return _real_walkdirs_utf8(top, prefix=prefix)
 
1182
    fs_encoding = _fs_enc.upper()
 
1183
    if (sys.platform == 'win32' or
 
1184
        fs_encoding not in ('UTF-8', 'US-ASCII', 'ANSI_X3.4-1968')): # ascii
 
1185
        return _walkdirs_unicode_to_utf8(top, prefix=prefix)
 
1186
    else:
 
1187
        return _walkdirs_fs_utf8(top, prefix=prefix)
1271
1188
 
1272
1189
 
1273
1190
def _walkdirs_fs_utf8(top, prefix=""):
1278
1195
    """
1279
1196
    _lstat = os.lstat
1280
1197
    _directory = _directory_kind
1281
 
    # Use C accelerated directory listing.
1282
 
    _listdir = _read_dir
 
1198
    _listdir = os.listdir
1283
1199
    _kind_from_mode = _formats.get
1284
1200
 
1285
1201
    # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1295
1211
 
1296
1212
        dirblock = []
1297
1213
        append = dirblock.append
1298
 
        # read_dir supplies in should-stat order.
1299
 
        for _, name in sorted(_listdir(top)):
 
1214
        for name in sorted(_listdir(top)):
1300
1215
            abspath = top_slash + name
1301
1216
            statvalue = _lstat(abspath)
1302
1217
            kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
1303
1218
            append((relprefix + name, name, kind, statvalue, abspath))
1304
 
        dirblock.sort()
1305
1219
        yield (relroot, top), dirblock
1306
1220
 
1307
1221
        # push the user specified dirs from dirblock
1448
1362
    # Windows returns 'cp0' to indicate there is no code page. So we'll just
1449
1363
    # treat that as ASCII, and not support printing unicode characters to the
1450
1364
    # console.
1451
 
    #
1452
 
    # For python scripts run under vim, we get '', so also treat that as ASCII
1453
 
    if user_encoding in (None, 'cp0', ''):
 
1365
    if user_encoding in (None, 'cp0'):
1454
1366
        user_encoding = 'ascii'
1455
1367
    else:
1456
1368
        # check encoding
1470
1382
    return user_encoding
1471
1383
 
1472
1384
 
1473
 
def get_host_name():
1474
 
    """Return the current unicode host name.
1475
 
 
1476
 
    This is meant to be used in place of socket.gethostname() because that
1477
 
    behaves inconsistently on different platforms.
1478
 
    """
1479
 
    if sys.platform == "win32":
1480
 
        import win32utils
1481
 
        return win32utils.get_host_name()
1482
 
    else:
1483
 
        import socket
1484
 
        return socket.gethostname().decode(get_user_encoding())
1485
 
 
1486
 
 
1487
1385
def recv_all(socket, bytes):
1488
1386
    """Receive an exact number of bytes.
1489
1387
 
1502
1400
        b += new
1503
1401
    return b
1504
1402
 
1505
 
 
1506
 
def send_all(socket, bytes):
1507
 
    """Send all bytes on a socket.
1508
 
 
1509
 
    Regular socket.sendall() can give socket error 10053 on Windows.  This
1510
 
    implementation sends no more than 64k at a time, which avoids this problem.
1511
 
    """
1512
 
    chunk_size = 2**16
1513
 
    for pos in xrange(0, len(bytes), chunk_size):
1514
 
        socket.sendall(bytes[pos:pos+chunk_size])
1515
 
 
1516
 
 
1517
1403
def dereference_path(path):
1518
1404
    """Determine the real path to a file.
1519
1405
 
1531
1417
def supports_mapi():
1532
1418
    """Return True if we can use MAPI to launch a mail client."""
1533
1419
    return sys.platform == "win32"
1534
 
 
1535
 
 
1536
 
def resource_string(package, resource_name):
1537
 
    """Load a resource from a package and return it as a string.
1538
 
 
1539
 
    Note: Only packages that start with bzrlib are currently supported.
1540
 
 
1541
 
    This is designed to be a lightweight implementation of resource
1542
 
    loading in a way which is API compatible with the same API from
1543
 
    pkg_resources. See
1544
 
    http://peak.telecommunity.com/DevCenter/PkgResources#basic-resource-access.
1545
 
    If and when pkg_resources becomes a standard library, this routine
1546
 
    can delegate to it.
1547
 
    """
1548
 
    # Check package name is within bzrlib
1549
 
    if package == "bzrlib":
1550
 
        resource_relpath = resource_name
1551
 
    elif package.startswith("bzrlib."):
1552
 
        package = package[len("bzrlib."):].replace('.', os.sep)
1553
 
        resource_relpath = pathjoin(package, resource_name)
1554
 
    else:
1555
 
        raise errors.BzrError('resource package %s not in bzrlib' % package)
1556
 
 
1557
 
    # Map the resource to a file and read its contents
1558
 
    base = dirname(bzrlib.__file__)
1559
 
    if getattr(sys, 'frozen', None):    # bzr.exe
1560
 
        base = abspath(pathjoin(base, '..', '..'))
1561
 
    filename = pathjoin(base, resource_relpath)
1562
 
    return open(filename, 'rU').read()
1563
 
 
1564
 
 
1565
 
try:
1566
 
    from bzrlib._readdir_pyx import read_dir as _read_dir
1567
 
except ImportError:
1568
 
    from bzrlib._readdir_py import read_dir as _read_dir