~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_dirstate_helpers_c.pyx

  • Committer: Mark Hammond
  • Date: 2009-01-12 01:55:34 UTC
  • mto: (3995.8.2 prepare-1.12)
  • mto: This revision was merged to the branch mainline in revision 4007.
  • Revision ID: mhammond@skippinet.com.au-20090112015534-yfxg50p7mpds9j4v
Include all .html files from the tortoise doc directory.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Helper functions for DirState.
18
18
 
28
28
 
29
29
from bzrlib import cache_utf8, errors, osutils
30
30
from bzrlib.dirstate import DirState
31
 
from bzrlib.osutils import parent_directories, pathjoin, splitpath
 
31
from bzrlib.osutils import pathjoin, splitpath
32
32
 
33
33
 
34
34
# This is the Windows equivalent of ENOTDIR
54
54
cdef extern from *:
55
55
    ctypedef unsigned long size_t
56
56
 
57
 
cdef extern from "_dirstate_helpers_pyx.h":
 
57
cdef extern from "_dirstate_helpers_c.h":
58
58
    ctypedef int intptr_t
59
59
 
60
60
 
119
119
    # void *memrchr(void *s, int c, size_t len)
120
120
 
121
121
 
122
 
cdef void* _my_memrchr(void *s, int c, size_t n): # cannot_raise
 
122
cdef void* _my_memrchr(void *s, int c, size_t n):
123
123
    # memrchr seems to be a GNU extension, so we have to implement it ourselves
124
124
    cdef char *pos
125
125
    cdef char *start
165
165
    return PyString_FromStringAndSize(s, size)
166
166
 
167
167
 
168
 
cdef int _is_aligned(void *ptr): # cannot_raise
 
168
cdef int _is_aligned(void *ptr):
169
169
    """Is this pointer aligned to an integer size offset?
170
170
 
171
171
    :return: 1 if this pointer is aligned, 0 otherwise.
173
173
    return ((<intptr_t>ptr) & ((sizeof(int))-1)) == 0
174
174
 
175
175
 
176
 
cdef int _cmp_by_dirs(char *path1, int size1, char *path2, int size2): # cannot_raise
 
176
cdef int _cmp_by_dirs(char *path1, int size1, char *path2, int size2):
177
177
    cdef unsigned char *cur1
178
178
    cdef unsigned char *cur2
179
179
    cdef unsigned char *end1
237
237
    return 0
238
238
 
239
239
 
240
 
def cmp_by_dirs(path1, path2):
 
240
def cmp_by_dirs_c(path1, path2):
241
241
    """Compare two paths directory by directory.
242
242
 
243
243
    This is equivalent to doing::
266
266
                        PyString_Size(path2))
267
267
 
268
268
 
269
 
def _cmp_path_by_dirblock(path1, path2):
 
269
def _cmp_path_by_dirblock_c(path1, path2):
270
270
    """Compare two paths based on what directory they are in.
271
271
 
272
272
    This generates a sort order, such that all children of a directory are
288
288
    if not PyString_CheckExact(path2):
289
289
        raise TypeError("'path2' must be a plain string, not %s: %r"
290
290
                        % (type(path2), path2))
291
 
    return _cmp_path_by_dirblock_intern(PyString_AsString(path1),
292
 
                                        PyString_Size(path1),
293
 
                                        PyString_AsString(path2),
294
 
                                        PyString_Size(path2))
295
 
 
296
 
 
297
 
cdef int _cmp_path_by_dirblock_intern(char *path1, int path1_len,
298
 
                                      char *path2, int path2_len): # cannot_raise
 
291
    return _cmp_path_by_dirblock(PyString_AsString(path1),
 
292
                                 PyString_Size(path1),
 
293
                                 PyString_AsString(path2),
 
294
                                 PyString_Size(path2))
 
295
 
 
296
 
 
297
cdef int _cmp_path_by_dirblock(char *path1, int path1_len,
 
298
                               char *path2, int path2_len):
299
299
    """Compare two paths by what directory they are in.
300
300
 
301
 
    see ``_cmp_path_by_dirblock`` for details.
 
301
    see ``_cmp_path_by_dirblock_c`` for details.
302
302
    """
303
303
    cdef char *dirname1
304
304
    cdef int dirname1_len
368
368
    return 1
369
369
 
370
370
 
371
 
def _bisect_path_left(paths, path):
 
371
def _bisect_path_left_c(paths, path):
372
372
    """Return the index where to insert path into paths.
373
373
 
374
374
    This uses a path-wise comparison so we get::
413
413
        cur = PyList_GetItem_object_void(paths, _mid)
414
414
        cur_cstr = PyString_AS_STRING_void(cur)
415
415
        cur_size = PyString_GET_SIZE_void(cur)
416
 
        if _cmp_path_by_dirblock_intern(cur_cstr, cur_size,
417
 
                                        path_cstr, path_size) < 0:
 
416
        if _cmp_path_by_dirblock(cur_cstr, cur_size, path_cstr, path_size) < 0:
418
417
            _lo = _mid + 1
419
418
        else:
420
419
            _hi = _mid
421
420
    return _lo
422
421
 
423
422
 
424
 
def _bisect_path_right(paths, path):
 
423
def _bisect_path_right_c(paths, path):
425
424
    """Return the index where to insert path into paths.
426
425
 
427
426
    This uses a path-wise comparison so we get::
466
465
        cur = PyList_GetItem_object_void(paths, _mid)
467
466
        cur_cstr = PyString_AS_STRING_void(cur)
468
467
        cur_size = PyString_GET_SIZE_void(cur)
469
 
        if _cmp_path_by_dirblock_intern(path_cstr, path_size,
470
 
                                        cur_cstr, cur_size) < 0:
 
468
        if _cmp_path_by_dirblock(path_cstr, path_size, cur_cstr, cur_size) < 0:
471
469
            _hi = _mid
472
470
        else:
473
471
            _lo = _mid + 1
474
472
    return _lo
475
473
 
476
474
 
477
 
def bisect_dirblock(dirblocks, dirname, lo=0, hi=None, cache=None):
 
475
def bisect_dirblock_c(dirblocks, dirname, lo=0, hi=None, cache=None):
478
476
    """Return the index where to insert dirname into the dirblocks.
479
477
 
480
478
    The return value idx is such that all directories blocks in dirblock[:idx]
746
744
        self.state._split_root_dirblock_into_contents()
747
745
 
748
746
 
749
 
def _read_dirblocks(state):
 
747
def _read_dirblocks_c(state):
750
748
    """Read in the dirblocks for the given DirState object.
751
749
 
752
750
    This is tightly bound to the DirState internal representation. It should be
768
766
    state._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
769
767
 
770
768
 
771
 
cdef int minikind_from_mode(int mode): # cannot_raise
 
769
cdef int minikind_from_mode(int mode):
772
770
    # in order of frequency:
773
771
    if S_ISREG(mode):
774
772
        return c"f"
843
841
    packed_stat = _pack_stat(stat_value)
844
842
    details = PyList_GetItem_void_void(PyTuple_GetItem_void_void(<void *>entry, 1), 0)
845
843
    saved_minikind = PyString_AsString_obj(<PyObject *>PyTuple_GetItem_void_void(details, 0))[0]
846
 
    if minikind == c'd' and saved_minikind == c't':
847
 
        minikind = c't'
848
844
    saved_link_or_sha1 = PyTuple_GetItem_void_object(details, 1)
849
845
    saved_file_size = PyTuple_GetItem_void_object(details, 2)
850
846
    saved_executable = PyTuple_GetItem_void_object(details, 3)
915
911
    return link_or_sha1
916
912
 
917
913
 
918
 
# TODO: Do we want to worry about exceptions here?
919
 
cdef char _minikind_from_string(object string) except? -1:
 
914
cdef char _minikind_from_string(object string):
920
915
    """Convert a python string to a char."""
921
916
    return PyString_AsString(string)[0]
922
917
 
954
949
    raise KeyError(PyString_FromStringAndSize(_minikind, 1))
955
950
 
956
951
 
957
 
cdef int _versioned_minikind(char minikind): # cannot_raise
 
952
cdef int _versioned_minikind(char minikind):
958
953
    """Return non-zero if minikind is in fltd"""
959
954
    return (minikind == c'f' or
960
955
            minikind == c'd' or
964
959
 
965
960
cdef class ProcessEntryC:
966
961
 
967
 
    cdef int doing_consistency_expansion
968
962
    cdef object old_dirname_to_file_id # dict
969
963
    cdef object new_dirname_to_file_id # dict
 
964
    cdef readonly object uninteresting
970
965
    cdef object last_source_parent
971
966
    cdef object last_target_parent
972
 
    cdef int include_unchanged
973
 
    cdef int partial
 
967
    cdef object include_unchanged
974
968
    cdef object use_filesystem_for_exec
975
969
    cdef object utf8_decode
976
970
    cdef readonly object searched_specific_files
977
 
    cdef readonly object searched_exact_paths
978
971
    cdef object search_specific_files
979
 
    # The parents up to the root of the paths we are searching.
980
 
    # After all normal paths are returned, these specific items are returned.
981
 
    cdef object search_specific_file_parents
982
972
    cdef object state
983
973
    # Current iteration variables:
984
974
    cdef object current_root
996
986
    cdef object current_block_list
997
987
    cdef object current_dir_info
998
988
    cdef object current_dir_list
999
 
    cdef object _pending_consistent_entries # list
1000
989
    cdef int path_index
1001
990
    cdef object root_dir_info
1002
991
    cdef object bisect_left
1003
992
    cdef object pathjoin
1004
993
    cdef object fstat
1005
 
    # A set of the ids we've output when doing partial output.
1006
 
    cdef object seen_ids
1007
994
    cdef object sha_file
1008
995
 
1009
996
    def __init__(self, include_unchanged, use_filesystem_for_exec,
1010
997
        search_specific_files, state, source_index, target_index,
1011
998
        want_unversioned, tree):
1012
 
        self.doing_consistency_expansion = 0
1013
999
        self.old_dirname_to_file_id = {}
1014
1000
        self.new_dirname_to_file_id = {}
1015
 
        # Are we doing a partial iter_changes?
1016
 
        self.partial = set(['']).__ne__(search_specific_files)
 
1001
        # Just a sentry, so that _process_entry can say that this
 
1002
        # record is handled, but isn't interesting to process (unchanged)
 
1003
        self.uninteresting = object()
1017
1004
        # Using a list so that we can access the values and change them in
1018
1005
        # nested scope. Each one is [path, file_id, entry]
1019
1006
        self.last_source_parent = [None, None]
1020
1007
        self.last_target_parent = [None, None]
1021
 
        if include_unchanged is None:
1022
 
            self.include_unchanged = False
1023
 
        else:
1024
 
            self.include_unchanged = int(include_unchanged)
 
1008
        self.include_unchanged = include_unchanged
1025
1009
        self.use_filesystem_for_exec = use_filesystem_for_exec
1026
1010
        self.utf8_decode = cache_utf8._utf8_decode
1027
1011
        # for all search_indexs in each path at or under each element of
1028
 
        # search_specific_files, if the detail is relocated: add the id, and
1029
 
        # add the relocated path as one to search if its not searched already.
1030
 
        # If the detail is not relocated, add the id.
 
1012
        # search_specific_files, if the detail is relocated: add the id, and add the
 
1013
        # relocated path as one to search if its not searched already. If the
 
1014
        # detail is not relocated, add the id.
1031
1015
        self.searched_specific_files = set()
1032
 
        # When we search exact paths without expanding downwards, we record
1033
 
        # that here.
1034
 
        self.searched_exact_paths = set()
1035
1016
        self.search_specific_files = search_specific_files
1036
 
        # The parents up to the root of the paths we are searching.
1037
 
        # After all normal paths are returned, these specific items are returned.
1038
 
        self.search_specific_file_parents = set()
1039
 
        # The ids we've sent out in the delta.
1040
 
        self.seen_ids = set()
1041
1017
        self.state = state
1042
1018
        self.current_root = None
1043
1019
        self.current_root_unicode = None
1059
1035
        self.current_block_pos = -1
1060
1036
        self.current_dir_info = None
1061
1037
        self.current_dir_list = None
1062
 
        self._pending_consistent_entries = []
1063
1038
        self.path_index = 0
1064
1039
        self.root_dir_info = None
1065
1040
        self.bisect_left = bisect.bisect_left
1066
1041
        self.pathjoin = osutils.pathjoin
1067
1042
        self.fstat = os.fstat
1068
1043
        self.sha_file = osutils.sha_file
1069
 
        if target_index != 0:
1070
 
            # A lot of code in here depends on target_index == 0
1071
 
            raise errors.BzrError('unsupported target index')
1072
1044
 
1073
1045
    cdef _process_entry(self, entry, path_info):
1074
1046
        """Compare an entry and real disk to generate delta information.
1075
1047
 
1076
1048
        :param path_info: top_relpath, basename, kind, lstat, abspath for
1077
 
            the path of entry. If None, then the path is considered absent in 
1078
 
            the target (Perhaps we should pass in a concrete entry for this ?)
 
1049
            the path of entry. If None, then the path is considered absent.
 
1050
            (Perhaps we should pass in a concrete entry for this ?)
1079
1051
            Basename is returned as a utf8 string because we expect this
1080
1052
            tuple will be ignored, and don't want to take the time to
1081
1053
            decode.
1082
 
        :return: (iter_changes_result, changed). If the entry has not been
1083
 
            handled then changed is None. Otherwise it is False if no content
1084
 
            or metadata changes have occured, and True if any content or
1085
 
            metadata change has occurred. If self.include_unchanged is True then
1086
 
            if changed is not None, iter_changes_result will always be a result
1087
 
            tuple. Otherwise, iter_changes_result is None unless changed is
1088
 
            True.
 
1054
        :return: None if the these don't match
 
1055
                 A tuple of information about the change, or
 
1056
                 the object 'uninteresting' if these match, but are
 
1057
                 basically identical.
1089
1058
        """
1090
1059
        cdef char target_minikind
1091
1060
        cdef char source_minikind
1127
1096
            else:
1128
1097
                # add the source to the search path to find any children it
1129
1098
                # has.  TODO ? : only add if it is a container ?
1130
 
                if (not self.doing_consistency_expansion and 
1131
 
                    not osutils.is_inside_any(self.searched_specific_files,
1132
 
                                             source_details[1])):
 
1099
                if not osutils.is_inside_any(self.searched_specific_files,
 
1100
                                             source_details[1]):
1133
1101
                    self.search_specific_files.add(source_details[1])
1134
 
                    # expanding from a user requested path, parent expansion
1135
 
                    # for delta consistency happens later.
1136
1102
                # generate the old path; this is needed for stating later
1137
1103
                # as well.
1138
1104
                old_path = source_details[1]
1172
1138
                    if source_minikind != c'f':
1173
1139
                        content_change = 1
1174
1140
                    else:
1175
 
                        # Check the sha. We can't just rely on the size as
1176
 
                        # content filtering may mean differ sizes actually
1177
 
                        # map to the same content
1178
 
                        if link_or_sha1 is None:
1179
 
                            # Stat cache miss:
1180
 
                            statvalue, link_or_sha1 = \
1181
 
                                self.state._sha1_provider.stat_and_sha1(
1182
 
                                path_info[4])
1183
 
                            self.state._observed_sha1(entry, link_or_sha1,
1184
 
                                statvalue)
1185
 
                        content_change = (link_or_sha1 != source_details[1])
 
1141
                        # If the size is the same, check the sha:
 
1142
                        if target_details[2] == source_details[2]:
 
1143
                            if link_or_sha1 is None:
 
1144
                                # Stat cache miss:
 
1145
                                file_obj = file(path_info[4], 'rb')
 
1146
                                try:
 
1147
                                    # XXX: TODO: Use lower level file IO rather
 
1148
                                    # than python objects for sha-misses.
 
1149
                                    statvalue = self.fstat(file_obj.fileno())
 
1150
                                    link_or_sha1 = self.sha_file(file_obj)
 
1151
                                finally:
 
1152
                                    file_obj.close()
 
1153
                                self.state._observed_sha1(entry, link_or_sha1,
 
1154
                                    statvalue)
 
1155
                            content_change = (link_or_sha1 != source_details[1])
 
1156
                        else:
 
1157
                            # Size changed, so must be different
 
1158
                            content_change = 1
1186
1159
                    # Target details is updated at update_entry time
1187
1160
                    if self.use_filesystem_for_exec:
1188
1161
                        # We don't need S_ISREG here, because we are sure
1203
1176
                        content_change = 0
1204
1177
                    target_exec = False
1205
1178
                else:
1206
 
                    if path is None:
1207
 
                        path = self.pathjoin(old_dirname, old_basename)
1208
 
                    raise errors.BadFileKindError(path, path_info[2])
 
1179
                    raise Exception, "unknown kind %s" % path_info[2]
1209
1180
            if source_minikind == c'd':
1210
1181
                if path is None:
1211
1182
                    old_path = path = self.pathjoin(old_dirname, old_basename)
1213
1184
                    file_id = entry[0][2]
1214
1185
                self.old_dirname_to_file_id[old_path] = file_id
1215
1186
            # parent id is the entry for the path in the target tree
1216
 
            if old_basename and old_dirname == self.last_source_parent[0]:
1217
 
                # use a cached hit for non-root source entries.
 
1187
            if old_dirname == self.last_source_parent[0]:
1218
1188
                source_parent_id = self.last_source_parent[1]
1219
1189
            else:
1220
1190
                try:
1230
1200
                    self.last_source_parent[0] = old_dirname
1231
1201
                    self.last_source_parent[1] = source_parent_id
1232
1202
            new_dirname = entry[0][0]
1233
 
            if entry[0][1] and new_dirname == self.last_target_parent[0]:
1234
 
                # use a cached hit for non-root target entries.
 
1203
            if new_dirname == self.last_target_parent[0]:
1235
1204
                target_parent_id = self.last_target_parent[1]
1236
1205
            else:
1237
1206
                try:
1254
1223
                    self.last_target_parent[1] = target_parent_id
1255
1224
 
1256
1225
            source_exec = source_details[3]
1257
 
            changed = (content_change
 
1226
            if (self.include_unchanged
 
1227
                or content_change
1258
1228
                or source_parent_id != target_parent_id
1259
1229
                or old_basename != entry[0][1]
1260
1230
                or source_exec != target_exec
1261
 
                )
1262
 
            if not changed and not self.include_unchanged:
1263
 
                return None, False
1264
 
            else:
 
1231
                ):
1265
1232
                if old_path is None:
1266
1233
                    path = self.pathjoin(old_dirname, old_basename)
1267
1234
                    old_path = path
1281
1248
                       (source_parent_id, target_parent_id),
1282
1249
                       (self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
1283
1250
                       (source_kind, target_kind),
1284
 
                       (source_exec, target_exec)), changed
 
1251
                       (source_exec, target_exec))
 
1252
            else:
 
1253
                return self.uninteresting
1285
1254
        elif source_minikind == c'a' and _versioned_minikind(target_minikind):
1286
1255
            # looks like a new file
1287
1256
            path = self.pathjoin(entry[0][0], entry[0][1])
1288
1257
            # parent id is the entry for the path in the target tree
1289
1258
            # TODO: these are the same for an entire directory: cache em.
1290
 
            parent_entry = self.state._get_entry(self.target_index,
1291
 
                                                 path_utf8=entry[0][0])
1292
 
            if parent_entry is None:
1293
 
                raise errors.DirstateCorrupt(self.state,
1294
 
                    "We could not find the parent entry in index %d"
1295
 
                    " for the entry: %s"
1296
 
                    % (self.target_index, entry[0]))
1297
 
            parent_id = parent_entry[0][2]
 
1259
            parent_id = self.state._get_entry(self.target_index,
 
1260
                                         path_utf8=entry[0][0])[0][2]
1298
1261
            if parent_id == entry[0][2]:
1299
1262
                parent_id = None
1300
1263
            if path_info is not None:
1314
1277
                       (None, parent_id),
1315
1278
                       (None, self.utf8_decode(entry[0][1])[0]),
1316
1279
                       (None, path_info[2]),
1317
 
                       (None, target_exec)), True
 
1280
                       (None, target_exec))
1318
1281
            else:
1319
1282
                # Its a missing file, report it as such.
1320
1283
                return (entry[0][2],
1324
1287
                       (None, parent_id),
1325
1288
                       (None, self.utf8_decode(entry[0][1])[0]),
1326
1289
                       (None, None),
1327
 
                       (None, False)), True
 
1290
                       (None, False))
1328
1291
        elif _versioned_minikind(source_minikind) and target_minikind == c'a':
1329
1292
            # unversioned, possibly, or possibly not deleted: we dont care.
1330
1293
            # if its still on disk, *and* theres no other entry at this
1342
1305
                   (parent_id, None),
1343
1306
                   (self.utf8_decode(entry[0][1])[0], None),
1344
1307
                   (_minikind_to_kind(source_minikind), None),
1345
 
                   (source_details[3], None)), True
 
1308
                   (source_details[3], None))
1346
1309
        elif _versioned_minikind(source_minikind) and target_minikind == c'r':
1347
1310
            # a rename; could be a true rename, or a rename inherited from
1348
1311
            # a renamed parent. TODO: handle this efficiently. Its not
1349
1312
            # common case to rename dirs though, so a correct but slow
1350
1313
            # implementation will do.
1351
 
            if (not self.doing_consistency_expansion and 
1352
 
                not osutils.is_inside_any(self.searched_specific_files,
1353
 
                    target_details[1])):
 
1314
            if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
1354
1315
                self.search_specific_files.add(target_details[1])
1355
 
                # We don't expand the specific files parents list here as
1356
 
                # the path is absent in target and won't create a delta with
1357
 
                # missing parent.
1358
1316
        elif ((source_minikind == c'r' or source_minikind == c'a') and
1359
1317
              (target_minikind == c'r' or target_minikind == c'a')):
1360
1318
            # neither of the selected trees contain this path,
1366
1324
                "source_minikind=%r, target_minikind=%r"
1367
1325
                % (source_minikind, target_minikind))
1368
1326
            ## import pdb;pdb.set_trace()
1369
 
        return None, None
 
1327
        return None
1370
1328
 
1371
1329
    def __iter__(self):
1372
1330
        return self
1374
1332
    def iter_changes(self):
1375
1333
        return self
1376
1334
 
1377
 
    cdef int _gather_result_for_consistency(self, result) except -1:
1378
 
        """Check a result we will yield to make sure we are consistent later.
1379
 
        
1380
 
        This gathers result's parents into a set to output later.
1381
 
 
1382
 
        :param result: A result tuple.
1383
 
        """
1384
 
        if not self.partial or not result[0]:
1385
 
            return 0
1386
 
        self.seen_ids.add(result[0])
1387
 
        new_path = result[1][1]
1388
 
        if new_path:
1389
 
            # Not the root and not a delete: queue up the parents of the path.
1390
 
            self.search_specific_file_parents.update(
1391
 
                osutils.parent_directories(new_path.encode('utf8')))
1392
 
            # Add the root directory which parent_directories does not
1393
 
            # provide.
1394
 
            self.search_specific_file_parents.add('')
1395
 
        return 0
1396
 
 
1397
 
    cdef int _update_current_block(self) except -1:
 
1335
    cdef void _update_current_block(self):
1398
1336
        if (self.block_index < len(self.state._dirblocks) and
1399
1337
            osutils.is_inside(self.current_root, self.state._dirblocks[self.block_index][0])):
1400
1338
            self.current_block = self.state._dirblocks[self.block_index]
1403
1341
        else:
1404
1342
            self.current_block = None
1405
1343
            self.current_block_list = None
1406
 
        return 0
1407
1344
 
1408
1345
    def __next__(self):
1409
1346
        # Simple thunk to allow tail recursion without pyrex confusion
1461
1398
        cdef char * current_dirname_c, * current_blockname_c
1462
1399
        cdef int advance_entry, advance_path
1463
1400
        cdef int path_handled
 
1401
        uninteresting = self.uninteresting
1464
1402
        searched_specific_files = self.searched_specific_files
1465
1403
        # Are we walking a root?
1466
1404
        while self.root_entries_pos < self.root_entries_len:
1467
1405
            entry = self.root_entries[self.root_entries_pos]
1468
1406
            self.root_entries_pos = self.root_entries_pos + 1
1469
 
            result, changed = self._process_entry(entry, self.root_dir_info)
1470
 
            if changed is not None:
1471
 
                if changed:
1472
 
                    self._gather_result_for_consistency(result)
1473
 
                if changed or self.include_unchanged:
1474
 
                    return result
 
1407
            result = self._process_entry(entry, self.root_dir_info)
 
1408
            if result is not None and result is not self.uninteresting:
 
1409
                return result
1475
1410
        # Have we finished the prior root, or never started one ?
1476
1411
        if self.current_root is None:
1477
1412
            # TODO: the pending list should be lexically sorted?  the
1480
1415
                self.current_root = self.search_specific_files.pop()
1481
1416
            except KeyError:
1482
1417
                raise StopIteration()
 
1418
            self.current_root_unicode = self.current_root.decode('utf8')
1483
1419
            self.searched_specific_files.add(self.current_root)
1484
1420
            # process the entries for this containing directory: the rest will be
1485
1421
            # found by their parents recursively.
1486
1422
            self.root_entries = self.state._entries_for_path(self.current_root)
1487
1423
            self.root_entries_len = len(self.root_entries)
1488
 
            self.current_root_unicode = self.current_root.decode('utf8')
1489
1424
            self.root_abspath = self.tree.abspath(self.current_root_unicode)
1490
1425
            try:
1491
1426
                root_stat = os.lstat(self.root_abspath)
1519
1454
            while self.root_entries_pos < self.root_entries_len:
1520
1455
                entry = self.root_entries[self.root_entries_pos]
1521
1456
                self.root_entries_pos = self.root_entries_pos + 1
1522
 
                result, changed = self._process_entry(entry, self.root_dir_info)
1523
 
                if changed is not None:
 
1457
                result = self._process_entry(entry, self.root_dir_info)
 
1458
                if result is not None:
1524
1459
                    path_handled = -1
1525
 
                    if changed:
1526
 
                        self._gather_result_for_consistency(result)
1527
 
                    if changed or self.include_unchanged:
 
1460
                    if result is not self.uninteresting:
1528
1461
                        return result
1529
1462
            # handle unversioned specified paths:
1530
1463
            if self.want_unversioned and not path_handled and self.root_dir_info:
1542
1475
                      )
1543
1476
            # If we reach here, the outer flow continues, which enters into the
1544
1477
            # per-root setup logic.
1545
 
        if (self.current_dir_info is None and self.current_block is None and not
1546
 
            self.doing_consistency_expansion):
 
1478
        if self.current_dir_info is None and self.current_block is None:
1547
1479
            # setup iteration of this root:
1548
1480
            self.current_dir_list = None
1549
1481
            if self.root_dir_info and self.root_dir_info[2] == 'tree-reference':
1671
1603
                        self.current_block_pos = self.current_block_pos + 1
1672
1604
                        # entry referring to file not present on disk.
1673
1605
                        # advance the entry only, after processing.
1674
 
                        result, changed = self._process_entry(current_entry, None)
1675
 
                        if changed is not None:
1676
 
                            if changed:
1677
 
                                self._gather_result_for_consistency(result)
1678
 
                            if changed or self.include_unchanged:
 
1606
                        result = self._process_entry(current_entry, None)
 
1607
                        if result is not None:
 
1608
                            if result is not self.uninteresting:
1679
1609
                                return result
1680
1610
                    self.block_index = self.block_index + 1
1681
1611
                    self._update_current_block()
1687
1617
            # More supplied paths to process
1688
1618
            self.current_root = None
1689
1619
            return self._iter_next()
1690
 
        # Start expanding more conservatively, adding paths the user may not
1691
 
        # have intended but required for consistent deltas.
1692
 
        self.doing_consistency_expansion = 1
1693
 
        if not self._pending_consistent_entries:
1694
 
            self._pending_consistent_entries = self._next_consistent_entries()
1695
 
        while self._pending_consistent_entries:
1696
 
            result, changed = self._pending_consistent_entries.pop()
1697
 
            if changed is not None:
1698
 
                return result
1699
1620
        raise StopIteration()
1700
1621
 
1701
1622
    cdef object _maybe_tree_ref(self, current_path_info):
1751
1672
                    pass
1752
1673
                elif current_path_info is None:
1753
1674
                    # no path is fine: the per entry code will handle it.
1754
 
                    result, changed = self._process_entry(current_entry,
1755
 
                        current_path_info)
 
1675
                    result = self._process_entry(current_entry, current_path_info)
 
1676
                    if result is not None:
 
1677
                        if result is self.uninteresting:
 
1678
                            result = None
1756
1679
                else:
1757
1680
                    minikind = _minikind_from_string(
1758
1681
                        current_entry[1][self.target_index][0])
1773
1696
                        else:
1774
1697
                            # entry referring to file not present on disk.
1775
1698
                            # advance the entry only, after processing.
1776
 
                            result, changed = self._process_entry(current_entry,
1777
 
                                None)
 
1699
                            result = self._process_entry(current_entry, None)
 
1700
                            if result is not None:
 
1701
                                if result is self.uninteresting:
 
1702
                                    result = None
1778
1703
                            advance_path = 0
1779
1704
                    else:
1780
1705
                        # paths are the same,and the dirstate entry is not
1781
1706
                        # absent or renamed.
1782
 
                        result, changed = self._process_entry(current_entry,
1783
 
                            current_path_info)
1784
 
                        if changed is not None:
 
1707
                        result = self._process_entry(current_entry, current_path_info)
 
1708
                        if result is not None:
1785
1709
                            path_handled = -1
1786
 
                            if not changed and not self.include_unchanged:
1787
 
                                changed = None
 
1710
                            if result is self.uninteresting:
 
1711
                                result = None
1788
1712
                # >- loop control starts here:
1789
1713
                # >- entry
1790
1714
                if advance_entry and current_entry is not None:
1806
1730
                            except UnicodeDecodeError:
1807
1731
                                raise errors.BadFilenameEncoding(
1808
1732
                                    current_path_info[0], osutils._fs_enc)
1809
 
                            if changed is not None:
 
1733
                            if result is not None:
1810
1734
                                raise AssertionError(
1811
1735
                                    "result is not None: %r" % result)
1812
1736
                            result = (None,
1817
1741
                                (None, self.utf8_decode(current_path_info[1])[0]),
1818
1742
                                (None, current_path_info[2]),
1819
1743
                                (None, new_executable))
1820
 
                            changed = True
1821
1744
                        # dont descend into this unversioned path if it is
1822
1745
                        # a dir
1823
1746
                        if current_path_info[2] in ('directory'):
1836
1759
                                current_path_info)
1837
1760
                    else:
1838
1761
                        current_path_info = None
1839
 
                if changed is not None:
 
1762
                if result is not None:
1840
1763
                    # Found a result on this pass, yield it
1841
 
                    if changed:
1842
 
                        self._gather_result_for_consistency(result)
1843
 
                    if changed or self.include_unchanged:
1844
 
                        return result
 
1764
                    return result
1845
1765
            if self.current_block is not None:
1846
1766
                self.block_index = self.block_index + 1
1847
1767
                self._update_current_block()
1853
1773
                    self.current_dir_list = self.current_dir_info[1]
1854
1774
                except StopIteration:
1855
1775
                    self.current_dir_info = None
1856
 
 
1857
 
    cdef object _next_consistent_entries(self):
1858
 
        """Grabs the next specific file parent case to consider.
1859
 
        
1860
 
        :return: A list of the results, each of which is as for _process_entry.
1861
 
        """
1862
 
        results = []
1863
 
        while self.search_specific_file_parents:
1864
 
            # Process the parent directories for the paths we were iterating.
1865
 
            # Even in extremely large trees this should be modest, so currently
1866
 
            # no attempt is made to optimise.
1867
 
            path_utf8 = self.search_specific_file_parents.pop()
1868
 
            if path_utf8 in self.searched_exact_paths:
1869
 
                # We've examined this path.
1870
 
                continue
1871
 
            if osutils.is_inside_any(self.searched_specific_files, path_utf8):
1872
 
                # We've examined this path.
1873
 
                continue
1874
 
            path_entries = self.state._entries_for_path(path_utf8)
1875
 
            # We need either one or two entries. If the path in
1876
 
            # self.target_index has moved (so the entry in source_index is in
1877
 
            # 'ar') then we need to also look for the entry for this path in
1878
 
            # self.source_index, to output the appropriate delete-or-rename.
1879
 
            selected_entries = []
1880
 
            found_item = False
1881
 
            for candidate_entry in path_entries:
1882
 
                # Find entries present in target at this path:
1883
 
                if candidate_entry[1][self.target_index][0] not in 'ar':
1884
 
                    found_item = True
1885
 
                    selected_entries.append(candidate_entry)
1886
 
                # Find entries present in source at this path:
1887
 
                elif (self.source_index is not None and
1888
 
                    candidate_entry[1][self.source_index][0] not in 'ar'):
1889
 
                    found_item = True
1890
 
                    if candidate_entry[1][self.target_index][0] == 'a':
1891
 
                        # Deleted, emit it here.
1892
 
                        selected_entries.append(candidate_entry)
1893
 
                    else:
1894
 
                        # renamed, emit it when we process the directory it
1895
 
                        # ended up at.
1896
 
                        self.search_specific_file_parents.add(
1897
 
                            candidate_entry[1][self.target_index][1])
1898
 
            if not found_item:
1899
 
                raise AssertionError(
1900
 
                    "Missing entry for specific path parent %r, %r" % (
1901
 
                    path_utf8, path_entries))
1902
 
            path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
1903
 
            for entry in selected_entries:
1904
 
                if entry[0][2] in self.seen_ids:
1905
 
                    continue
1906
 
                result, changed = self._process_entry(entry, path_info)
1907
 
                if changed is None:
1908
 
                    raise AssertionError(
1909
 
                        "Got entry<->path mismatch for specific path "
1910
 
                        "%r entry %r path_info %r " % (
1911
 
                        path_utf8, entry, path_info))
1912
 
                # Only include changes - we're outside the users requested
1913
 
                # expansion.
1914
 
                if changed:
1915
 
                    self._gather_result_for_consistency(result)
1916
 
                    if (result[6][0] == 'directory' and
1917
 
                        result[6][1] != 'directory'):
1918
 
                        # This stopped being a directory, the old children have
1919
 
                        # to be included.
1920
 
                        if entry[1][self.source_index][0] == 'r':
1921
 
                            # renamed, take the source path
1922
 
                            entry_path_utf8 = entry[1][self.source_index][1]
1923
 
                        else:
1924
 
                            entry_path_utf8 = path_utf8
1925
 
                        initial_key = (entry_path_utf8, '', '')
1926
 
                        block_index, _ = self.state._find_block_index_from_key(
1927
 
                            initial_key)
1928
 
                        if block_index == 0:
1929
 
                            # The children of the root are in block index 1.
1930
 
                            block_index = block_index + 1
1931
 
                        current_block = None
1932
 
                        if block_index < len(self.state._dirblocks):
1933
 
                            current_block = self.state._dirblocks[block_index]
1934
 
                            if not osutils.is_inside(
1935
 
                                entry_path_utf8, current_block[0]):
1936
 
                                # No entries for this directory at all.
1937
 
                                current_block = None
1938
 
                        if current_block is not None:
1939
 
                            for entry in current_block[1]:
1940
 
                                if entry[1][self.source_index][0] in 'ar':
1941
 
                                    # Not in the source tree, so doesn't have to be
1942
 
                                    # included.
1943
 
                                    continue
1944
 
                                # Path of the entry itself.
1945
 
                                self.search_specific_file_parents.add(
1946
 
                                    self.pathjoin(*entry[0][:2]))
1947
 
                if changed or self.include_unchanged:
1948
 
                    results.append((result, changed))
1949
 
            self.searched_exact_paths.add(path_utf8)
1950
 
        return results
1951
 
 
1952
 
    cdef object _path_info(self, utf8_path, unicode_path):
1953
 
        """Generate path_info for unicode_path.
1954
 
 
1955
 
        :return: None if unicode_path does not exist, or a path_info tuple.
1956
 
        """
1957
 
        abspath = self.tree.abspath(unicode_path)
1958
 
        try:
1959
 
            stat = os.lstat(abspath)
1960
 
        except OSError, e:
1961
 
            if e.errno == errno.ENOENT:
1962
 
                # the path does not exist.
1963
 
                return None
1964
 
            else:
1965
 
                raise
1966
 
        utf8_basename = utf8_path.rsplit('/', 1)[-1]
1967
 
        dir_info = (utf8_path, utf8_basename,
1968
 
            osutils.file_kind_from_stat_mode(stat.st_mode), stat,
1969
 
            abspath)
1970
 
        if dir_info[2] == 'directory':
1971
 
            if self.tree._directory_is_tree_reference(
1972
 
                unicode_path):
1973
 
                self.root_dir_info = self.root_dir_info[:2] + \
1974
 
                    ('tree-reference',) + self.root_dir_info[3:]
1975
 
        return dir_info