~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Kent Gibson
  • Date: 2007-03-07 14:49:00 UTC
  • mfrom: (2324 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2350.
  • Revision ID: warthog618@gmail.com-20070307144900-6bt7twg47zul3w0w
merged bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
54
54
""")
55
55
 
56
56
import bzrlib
 
57
from bzrlib import symbol_versioning
57
58
from bzrlib.symbol_versioning import (
58
59
    deprecated_function,
59
60
    zero_nine,
748
749
    return rps
749
750
 
750
751
def joinpath(p):
751
 
    assert isinstance(p, list)
 
752
    assert isinstance(p, (list, tuple))
752
753
    for f in p:
753
754
        if (f == '..') or (f is None) or (f == ''):
754
755
            raise errors.BzrError("sorry, %r not allowed in path" % f)
904
905
    return unicode_or_utf8_string.encode('utf-8')
905
906
 
906
907
 
907
 
def safe_revision_id(unicode_or_utf8_string):
 
908
_revision_id_warning = ('Unicode revision ids were deprecated in bzr 0.15.'
 
909
                        ' Revision id generators should be creating utf8'
 
910
                        ' revision ids.')
 
911
 
 
912
 
 
913
def safe_revision_id(unicode_or_utf8_string, warn=True):
908
914
    """Revision ids should now be utf8, but at one point they were unicode.
909
915
 
 
916
    :param unicode_or_utf8_string: A possibly Unicode revision_id. (can also be
 
917
        utf8 or None).
 
918
    :param warn: Functions that are sanitizing user data can set warn=False
 
919
    :return: None or a utf8 revision id.
 
920
    """
 
921
    if (unicode_or_utf8_string is None
 
922
        or unicode_or_utf8_string.__class__ == str):
 
923
        return unicode_or_utf8_string
 
924
    if warn:
 
925
        symbol_versioning.warn(_revision_id_warning, DeprecationWarning,
 
926
                               stacklevel=2)
 
927
    return cache_utf8.encode(unicode_or_utf8_string)
 
928
 
 
929
 
 
930
_file_id_warning = ('Unicode file ids were deprecated in bzr 0.15. File id'
 
931
                    ' generators should be creating utf8 file ids.')
 
932
 
 
933
 
 
934
def safe_file_id(unicode_or_utf8_string, warn=True):
 
935
    """File ids should now be utf8, but at one point they were unicode.
 
936
 
910
937
    This is the same as safe_utf8, except it uses the cached encode functions
911
938
    to save a little bit of performance.
 
939
 
 
940
    :param unicode_or_utf8_string: A possibly Unicode file_id. (can also be
 
941
        utf8 or None).
 
942
    :param warn: Functions that are sanitizing user data can set warn=False
 
943
    :return: None or a utf8 file id.
912
944
    """
913
 
    if unicode_or_utf8_string is None:
914
 
        return None
915
 
    if isinstance(unicode_or_utf8_string, str):
916
 
        # TODO: jam 20070209 Eventually just remove this check.
917
 
        try:
918
 
            utf8_str = cache_utf8.get_cached_utf8(unicode_or_utf8_string)
919
 
        except UnicodeDecodeError:
920
 
            raise errors.BzrBadParameterNotUnicode(unicode_or_utf8_string)
921
 
        return utf8_str
 
945
    if (unicode_or_utf8_string is None
 
946
        or unicode_or_utf8_string.__class__ == str):
 
947
        return unicode_or_utf8_string
 
948
    if warn:
 
949
        symbol_versioning.warn(_file_id_warning, DeprecationWarning,
 
950
                               stacklevel=2)
922
951
    return cache_utf8.encode(unicode_or_utf8_string)
923
952
 
924
953
 
925
 
# TODO: jam 20070217 We start by just re-using safe_revision_id, but ultimately
926
 
#       we want to use a different dictionary cache, because trapping file ids
927
 
#       and revision ids in the same dict seemed to have a noticable effect on
928
 
#       performance.
929
 
safe_file_id = safe_revision_id
930
 
 
931
 
 
932
954
_platform_normalizes_filenames = False
933
955
if sys.platform == 'darwin':
934
956
    _platform_normalizes_filenames = True
1055
1077
    
1056
1078
    The data yielded is of the form:
1057
1079
    ((directory-relpath, directory-path-from-top),
1058
 
    [(relpath, basename, kind, lstat), ...]),
 
1080
    [(directory-relpath, basename, kind, lstat, path-from-top), ...]),
1059
1081
     - directory-relpath is the relative path of the directory being returned
1060
1082
       with respect to top. prefix is prepended to this.
1061
1083
     - directory-path-from-root is the path including top for this directory. 
1079
1101
    # depending on top and prefix - i.e. ./foo and foo as a pair leads to
1080
1102
    # potentially confusing output. We should make this more robust - but
1081
1103
    # not at a speed cost. RBC 20060731
1082
 
    lstat = os.lstat
1083
 
    pending = []
 
1104
    _lstat = os.lstat
1084
1105
    _directory = _directory_kind
1085
1106
    _listdir = os.listdir
1086
 
    pending = [(prefix, "", _directory, None, top)]
 
1107
    _kind_from_mode = _formats.get
 
1108
    pending = [(safe_unicode(prefix), "", _directory, None, safe_unicode(top))]
1087
1109
    while pending:
1088
 
        dirblock = []
1089
 
        currentdir = pending.pop()
1090
1110
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1091
 
        top = currentdir[4]
1092
 
        if currentdir[0]:
1093
 
            relroot = currentdir[0] + '/'
1094
 
        else:
1095
 
            relroot = ""
1096
 
        for name in sorted(_listdir(top)):
1097
 
            abspath = top + '/' + name
1098
 
            statvalue = lstat(abspath)
1099
 
            dirblock.append((relroot + name, name,
1100
 
                file_kind_from_stat_mode(statvalue.st_mode),
1101
 
                statvalue, abspath))
1102
 
        yield (currentdir[0], top), dirblock
1103
 
        # push the user specified dirs from dirblock
1104
 
        for dir in reversed(dirblock):
1105
 
            if dir[2] == _directory:
1106
 
                pending.append(dir)
 
1111
        relroot, _, _, _, top = pending.pop()
 
1112
        if relroot:
 
1113
            relprefix = relroot + u'/'
 
1114
        else:
 
1115
            relprefix = ''
 
1116
        top_slash = top + u'/'
 
1117
 
 
1118
        dirblock = []
 
1119
        append = dirblock.append
 
1120
        for name in sorted(_listdir(top)):
 
1121
            abspath = top_slash + name
 
1122
            statvalue = _lstat(abspath)
 
1123
            kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
 
1124
            append((relprefix + name, name, kind, statvalue, abspath))
 
1125
        yield (relroot, top), dirblock
 
1126
 
 
1127
        # push the user specified dirs from dirblock
 
1128
        pending.extend(d for d in reversed(dirblock) if d[2] == _directory)
 
1129
 
 
1130
 
 
1131
def _walkdirs_utf8(top, prefix=""):
 
1132
    """Yield data about all the directories in a tree.
 
1133
 
 
1134
    This yields the same information as walkdirs() only each entry is yielded
 
1135
    in utf-8. On platforms which have a filesystem encoding of utf8 the paths
 
1136
    are returned as exact byte-strings.
 
1137
 
 
1138
    :return: yields a tuple of (dir_info, [file_info])
 
1139
        dir_info is (utf8_relpath, path-from-top)
 
1140
        file_info is (utf8_relpath, utf8_name, kind, lstat, path-from-top)
 
1141
        if top is an absolute path, path-from-top is also an absolute path.
 
1142
        path-from-top might be unicode or utf8, but it is the correct path to
 
1143
        pass to os functions to affect the file in question. (such as os.lstat)
 
1144
    """
 
1145
    fs_encoding = sys.getfilesystemencoding()
 
1146
    if (sys.platform == 'win32' or
 
1147
        fs_encoding not in ('UTF-8', 'US-ASCII', 'ANSI_X3.4-1968')): # ascii
 
1148
        return _walkdirs_unicode_to_utf8(top, prefix=prefix)
 
1149
    else:
 
1150
        return _walkdirs_fs_utf8(top, prefix=prefix)
 
1151
 
 
1152
 
 
1153
def _walkdirs_fs_utf8(top, prefix=""):
 
1154
    """See _walkdirs_utf8.
 
1155
 
 
1156
    This sub-function is called when we know the filesystem is already in utf8
 
1157
    encoding. So we don't need to transcode filenames.
 
1158
    """
 
1159
    _lstat = os.lstat
 
1160
    _directory = _directory_kind
 
1161
    _listdir = os.listdir
 
1162
    _kind_from_mode = _formats.get
 
1163
 
 
1164
    # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
 
1165
    # But we don't actually uses 1-3 in pending, so set them to None
 
1166
    pending = [(safe_utf8(prefix), None, None, None, safe_utf8(top))]
 
1167
    while pending:
 
1168
        relroot, _, _, _, top = pending.pop()
 
1169
        if relroot:
 
1170
            relprefix = relroot + '/'
 
1171
        else:
 
1172
            relprefix = ''
 
1173
        top_slash = top + '/'
 
1174
 
 
1175
        dirblock = []
 
1176
        append = dirblock.append
 
1177
        for name in sorted(_listdir(top)):
 
1178
            abspath = top_slash + name
 
1179
            statvalue = _lstat(abspath)
 
1180
            kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
 
1181
            append((relprefix + name, name, kind, statvalue, abspath))
 
1182
        yield (relroot, top), dirblock
 
1183
 
 
1184
        # push the user specified dirs from dirblock
 
1185
        pending.extend(d for d in reversed(dirblock) if d[2] == _directory)
 
1186
 
 
1187
 
 
1188
def _walkdirs_unicode_to_utf8(top, prefix=""):
 
1189
    """See _walkdirs_utf8
 
1190
 
 
1191
    Because Win32 has a Unicode api, all of the 'path-from-top' entries will be
 
1192
    Unicode paths.
 
1193
    This is currently the fallback code path when the filesystem encoding is
 
1194
    not UTF-8. It may be better to implement an alternative so that we can
 
1195
    safely handle paths that are not properly decodable in the current
 
1196
    encoding.
 
1197
    """
 
1198
    _utf8_encode = codecs.getencoder('utf8')
 
1199
    _lstat = os.lstat
 
1200
    _directory = _directory_kind
 
1201
    _listdir = os.listdir
 
1202
    _kind_from_mode = _formats.get
 
1203
 
 
1204
    pending = [(safe_utf8(prefix), None, None, None, safe_unicode(top))]
 
1205
    while pending:
 
1206
        relroot, _, _, _, top = pending.pop()
 
1207
        if relroot:
 
1208
            relprefix = relroot + '/'
 
1209
        else:
 
1210
            relprefix = ''
 
1211
        top_slash = top + u'/'
 
1212
 
 
1213
        dirblock = []
 
1214
        append = dirblock.append
 
1215
        for name in sorted(_listdir(top)):
 
1216
            name_utf8 = _utf8_encode(name)[0]
 
1217
            abspath = top_slash + name
 
1218
            statvalue = _lstat(abspath)
 
1219
            kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
 
1220
            append((relprefix + name_utf8, name_utf8, kind, statvalue, abspath))
 
1221
        yield (relroot, top), dirblock
 
1222
 
 
1223
        # push the user specified dirs from dirblock
 
1224
        pending.extend(d for d in reversed(dirblock) if d[2] == _directory)
1107
1225
 
1108
1226
 
1109
1227
def copy_tree(from_path, to_path, handlers={}):