~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_dirstate_helpers_pyx.pyx

merge 2.0 branch rev 4647

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
 
29
29
from bzrlib import cache_utf8, errors, osutils
30
30
from bzrlib.dirstate import DirState
31
 
from bzrlib.osutils import pathjoin, splitpath
 
31
from bzrlib.osutils import parent_directories, 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_c.h":
 
57
cdef extern from "_dirstate_helpers_pyx.h":
58
58
    ctypedef int intptr_t
59
59
 
60
60
 
237
237
    return 0
238
238
 
239
239
 
240
 
def cmp_by_dirs_c(path1, path2):
 
240
def cmp_by_dirs(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_c(path1, path2):
 
269
def _cmp_path_by_dirblock(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(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):
 
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):
299
299
    """Compare two paths by what directory they are in.
300
300
 
301
 
    see ``_cmp_path_by_dirblock_c`` for details.
 
301
    see ``_cmp_path_by_dirblock`` 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_c(paths, path):
 
371
def _bisect_path_left(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(cur_cstr, cur_size, path_cstr, path_size) < 0:
 
416
        if _cmp_path_by_dirblock_intern(cur_cstr, cur_size,
 
417
                                        path_cstr, path_size) < 0:
417
418
            _lo = _mid + 1
418
419
        else:
419
420
            _hi = _mid
420
421
    return _lo
421
422
 
422
423
 
423
 
def _bisect_path_right_c(paths, path):
 
424
def _bisect_path_right(paths, path):
424
425
    """Return the index where to insert path into paths.
425
426
 
426
427
    This uses a path-wise comparison so we get::
465
466
        cur = PyList_GetItem_object_void(paths, _mid)
466
467
        cur_cstr = PyString_AS_STRING_void(cur)
467
468
        cur_size = PyString_GET_SIZE_void(cur)
468
 
        if _cmp_path_by_dirblock(path_cstr, path_size, cur_cstr, cur_size) < 0:
 
469
        if _cmp_path_by_dirblock_intern(path_cstr, path_size,
 
470
                                        cur_cstr, cur_size) < 0:
469
471
            _hi = _mid
470
472
        else:
471
473
            _lo = _mid + 1
472
474
    return _lo
473
475
 
474
476
 
475
 
def bisect_dirblock_c(dirblocks, dirname, lo=0, hi=None, cache=None):
 
477
def bisect_dirblock(dirblocks, dirname, lo=0, hi=None, cache=None):
476
478
    """Return the index where to insert dirname into the dirblocks.
477
479
 
478
480
    The return value idx is such that all directories blocks in dirblock[:idx]
744
746
        self.state._split_root_dirblock_into_contents()
745
747
 
746
748
 
747
 
def _read_dirblocks_c(state):
 
749
def _read_dirblocks(state):
748
750
    """Read in the dirblocks for the given DirState object.
749
751
 
750
752
    This is tightly bound to the DirState internal representation. It should be
961
963
 
962
964
cdef class ProcessEntryC:
963
965
 
 
966
    cdef int doing_consistency_expansion
964
967
    cdef object old_dirname_to_file_id # dict
965
968
    cdef object new_dirname_to_file_id # dict
966
 
    cdef readonly object uninteresting
967
969
    cdef object last_source_parent
968
970
    cdef object last_target_parent
969
 
    cdef object include_unchanged
 
971
    cdef int include_unchanged
 
972
    cdef int partial
970
973
    cdef object use_filesystem_for_exec
971
974
    cdef object utf8_decode
972
975
    cdef readonly object searched_specific_files
 
976
    cdef readonly object searched_exact_paths
973
977
    cdef object search_specific_files
 
978
    # The parents up to the root of the paths we are searching.
 
979
    # After all normal paths are returned, these specific items are returned.
 
980
    cdef object search_specific_file_parents
974
981
    cdef object state
975
982
    # Current iteration variables:
976
983
    cdef object current_root
988
995
    cdef object current_block_list
989
996
    cdef object current_dir_info
990
997
    cdef object current_dir_list
 
998
    cdef object _pending_consistent_entries # list
991
999
    cdef int path_index
992
1000
    cdef object root_dir_info
993
1001
    cdef object bisect_left
994
1002
    cdef object pathjoin
995
1003
    cdef object fstat
 
1004
    # A set of the ids we've output when doing partial output.
 
1005
    cdef object seen_ids
996
1006
    cdef object sha_file
997
1007
 
998
1008
    def __init__(self, include_unchanged, use_filesystem_for_exec,
999
1009
        search_specific_files, state, source_index, target_index,
1000
1010
        want_unversioned, tree):
 
1011
        self.doing_consistency_expansion = 0
1001
1012
        self.old_dirname_to_file_id = {}
1002
1013
        self.new_dirname_to_file_id = {}
1003
 
        # Just a sentry, so that _process_entry can say that this
1004
 
        # record is handled, but isn't interesting to process (unchanged)
1005
 
        self.uninteresting = object()
 
1014
        # Are we doing a partial iter_changes?
 
1015
        self.partial = set(['']).__ne__(search_specific_files)
1006
1016
        # Using a list so that we can access the values and change them in
1007
1017
        # nested scope. Each one is [path, file_id, entry]
1008
1018
        self.last_source_parent = [None, None]
1009
1019
        self.last_target_parent = [None, None]
1010
 
        self.include_unchanged = include_unchanged
 
1020
        if include_unchanged is None:
 
1021
            self.include_unchanged = False
 
1022
        else:
 
1023
            self.include_unchanged = int(include_unchanged)
1011
1024
        self.use_filesystem_for_exec = use_filesystem_for_exec
1012
1025
        self.utf8_decode = cache_utf8._utf8_decode
1013
1026
        # for all search_indexs in each path at or under each element of
1014
 
        # search_specific_files, if the detail is relocated: add the id, and add the
1015
 
        # relocated path as one to search if its not searched already. If the
1016
 
        # detail is not relocated, add the id.
 
1027
        # search_specific_files, if the detail is relocated: add the id, and
 
1028
        # add the relocated path as one to search if its not searched already.
 
1029
        # If the detail is not relocated, add the id.
1017
1030
        self.searched_specific_files = set()
 
1031
        # When we search exact paths without expanding downwards, we record
 
1032
        # that here.
 
1033
        self.searched_exact_paths = set()
1018
1034
        self.search_specific_files = search_specific_files
 
1035
        # The parents up to the root of the paths we are searching.
 
1036
        # After all normal paths are returned, these specific items are returned.
 
1037
        self.search_specific_file_parents = set()
 
1038
        # The ids we've sent out in the delta.
 
1039
        self.seen_ids = set()
1019
1040
        self.state = state
1020
1041
        self.current_root = None
1021
1042
        self.current_root_unicode = None
1037
1058
        self.current_block_pos = -1
1038
1059
        self.current_dir_info = None
1039
1060
        self.current_dir_list = None
 
1061
        self._pending_consistent_entries = []
1040
1062
        self.path_index = 0
1041
1063
        self.root_dir_info = None
1042
1064
        self.bisect_left = bisect.bisect_left
1043
1065
        self.pathjoin = osutils.pathjoin
1044
1066
        self.fstat = os.fstat
1045
1067
        self.sha_file = osutils.sha_file
 
1068
        if target_index != 0:
 
1069
            # A lot of code in here depends on target_index == 0
 
1070
            raise errors.BzrError('unsupported target index')
1046
1071
 
1047
1072
    cdef _process_entry(self, entry, path_info):
1048
1073
        """Compare an entry and real disk to generate delta information.
1049
1074
 
1050
1075
        :param path_info: top_relpath, basename, kind, lstat, abspath for
1051
 
            the path of entry. If None, then the path is considered absent.
1052
 
            (Perhaps we should pass in a concrete entry for this ?)
 
1076
            the path of entry. If None, then the path is considered absent in 
 
1077
            the target (Perhaps we should pass in a concrete entry for this ?)
1053
1078
            Basename is returned as a utf8 string because we expect this
1054
1079
            tuple will be ignored, and don't want to take the time to
1055
1080
            decode.
1056
 
        :return: None if the these don't match
1057
 
                 A tuple of information about the change, or
1058
 
                 the object 'uninteresting' if these match, but are
1059
 
                 basically identical.
 
1081
        :return: (iter_changes_result, changed). If the entry has not been
 
1082
            handled then changed is None. Otherwise it is False if no content
 
1083
            or metadata changes have occured, and True if any content or
 
1084
            metadata change has occurred. If self.include_unchanged is True then
 
1085
            if changed is not None, iter_changes_result will always be a result
 
1086
            tuple. Otherwise, iter_changes_result is None unless changed is
 
1087
            True.
1060
1088
        """
1061
1089
        cdef char target_minikind
1062
1090
        cdef char source_minikind
1098
1126
            else:
1099
1127
                # add the source to the search path to find any children it
1100
1128
                # has.  TODO ? : only add if it is a container ?
1101
 
                if not osutils.is_inside_any(self.searched_specific_files,
1102
 
                                             source_details[1]):
 
1129
                if (not self.doing_consistency_expansion and 
 
1130
                    not osutils.is_inside_any(self.searched_specific_files,
 
1131
                                             source_details[1])):
1103
1132
                    self.search_specific_files.add(source_details[1])
 
1133
                    # expanding from a user requested path, parent expansion
 
1134
                    # for delta consistency happens later.
1104
1135
                # generate the old path; this is needed for stating later
1105
1136
                # as well.
1106
1137
                old_path = source_details[1]
1179
1210
                    file_id = entry[0][2]
1180
1211
                self.old_dirname_to_file_id[old_path] = file_id
1181
1212
            # parent id is the entry for the path in the target tree
1182
 
            if old_dirname == self.last_source_parent[0]:
 
1213
            if old_basename and old_dirname == self.last_source_parent[0]:
 
1214
                # use a cached hit for non-root source entries.
1183
1215
                source_parent_id = self.last_source_parent[1]
1184
1216
            else:
1185
1217
                try:
1195
1227
                    self.last_source_parent[0] = old_dirname
1196
1228
                    self.last_source_parent[1] = source_parent_id
1197
1229
            new_dirname = entry[0][0]
1198
 
            if new_dirname == self.last_target_parent[0]:
 
1230
            if entry[0][1] and new_dirname == self.last_target_parent[0]:
 
1231
                # use a cached hit for non-root target entries.
1199
1232
                target_parent_id = self.last_target_parent[1]
1200
1233
            else:
1201
1234
                try:
1218
1251
                    self.last_target_parent[1] = target_parent_id
1219
1252
 
1220
1253
            source_exec = source_details[3]
1221
 
            if (self.include_unchanged
1222
 
                or content_change
 
1254
            changed = (content_change
1223
1255
                or source_parent_id != target_parent_id
1224
1256
                or old_basename != entry[0][1]
1225
1257
                or source_exec != target_exec
1226
 
                ):
 
1258
                )
 
1259
            if not changed and not self.include_unchanged:
 
1260
                return None, False
 
1261
            else:
1227
1262
                if old_path is None:
1228
1263
                    path = self.pathjoin(old_dirname, old_basename)
1229
1264
                    old_path = path
1243
1278
                       (source_parent_id, target_parent_id),
1244
1279
                       (self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
1245
1280
                       (source_kind, target_kind),
1246
 
                       (source_exec, target_exec))
1247
 
            else:
1248
 
                return self.uninteresting
 
1281
                       (source_exec, target_exec)), changed
1249
1282
        elif source_minikind == c'a' and _versioned_minikind(target_minikind):
1250
1283
            # looks like a new file
1251
1284
            path = self.pathjoin(entry[0][0], entry[0][1])
1278
1311
                       (None, parent_id),
1279
1312
                       (None, self.utf8_decode(entry[0][1])[0]),
1280
1313
                       (None, path_info[2]),
1281
 
                       (None, target_exec))
 
1314
                       (None, target_exec)), True
1282
1315
            else:
1283
1316
                # Its a missing file, report it as such.
1284
1317
                return (entry[0][2],
1288
1321
                       (None, parent_id),
1289
1322
                       (None, self.utf8_decode(entry[0][1])[0]),
1290
1323
                       (None, None),
1291
 
                       (None, False))
 
1324
                       (None, False)), True
1292
1325
        elif _versioned_minikind(source_minikind) and target_minikind == c'a':
1293
1326
            # unversioned, possibly, or possibly not deleted: we dont care.
1294
1327
            # if its still on disk, *and* theres no other entry at this
1306
1339
                   (parent_id, None),
1307
1340
                   (self.utf8_decode(entry[0][1])[0], None),
1308
1341
                   (_minikind_to_kind(source_minikind), None),
1309
 
                   (source_details[3], None))
 
1342
                   (source_details[3], None)), True
1310
1343
        elif _versioned_minikind(source_minikind) and target_minikind == c'r':
1311
1344
            # a rename; could be a true rename, or a rename inherited from
1312
1345
            # a renamed parent. TODO: handle this efficiently. Its not
1313
1346
            # common case to rename dirs though, so a correct but slow
1314
1347
            # implementation will do.
1315
 
            if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
 
1348
            if (not self.doing_consistency_expansion and 
 
1349
                not osutils.is_inside_any(self.searched_specific_files,
 
1350
                    target_details[1])):
1316
1351
                self.search_specific_files.add(target_details[1])
 
1352
                # We don't expand the specific files parents list here as
 
1353
                # the path is absent in target and won't create a delta with
 
1354
                # missing parent.
1317
1355
        elif ((source_minikind == c'r' or source_minikind == c'a') and
1318
1356
              (target_minikind == c'r' or target_minikind == c'a')):
1319
1357
            # neither of the selected trees contain this path,
1325
1363
                "source_minikind=%r, target_minikind=%r"
1326
1364
                % (source_minikind, target_minikind))
1327
1365
            ## import pdb;pdb.set_trace()
1328
 
        return None
 
1366
        return None, None
1329
1367
 
1330
1368
    def __iter__(self):
1331
1369
        return self
1333
1371
    def iter_changes(self):
1334
1372
        return self
1335
1373
 
 
1374
    cdef void _gather_result_for_consistency(self, result):
 
1375
        """Check a result we will yield to make sure we are consistent later.
 
1376
        
 
1377
        This gathers result's parents into a set to output later.
 
1378
 
 
1379
        :param result: A result tuple.
 
1380
        """
 
1381
        if not self.partial or not result[0]:
 
1382
            return
 
1383
        self.seen_ids.add(result[0])
 
1384
        new_path = result[1][1]
 
1385
        if new_path:
 
1386
            # Not the root and not a delete: queue up the parents of the path.
 
1387
            self.search_specific_file_parents.update(
 
1388
                osutils.parent_directories(new_path.encode('utf8')))
 
1389
            # Add the root directory which parent_directories does not
 
1390
            # provide.
 
1391
            self.search_specific_file_parents.add('')
 
1392
 
1336
1393
    cdef void _update_current_block(self):
1337
1394
        if (self.block_index < len(self.state._dirblocks) and
1338
1395
            osutils.is_inside(self.current_root, self.state._dirblocks[self.block_index][0])):
1399
1456
        cdef char * current_dirname_c, * current_blockname_c
1400
1457
        cdef int advance_entry, advance_path
1401
1458
        cdef int path_handled
1402
 
        uninteresting = self.uninteresting
1403
1459
        searched_specific_files = self.searched_specific_files
1404
1460
        # Are we walking a root?
1405
1461
        while self.root_entries_pos < self.root_entries_len:
1406
1462
            entry = self.root_entries[self.root_entries_pos]
1407
1463
            self.root_entries_pos = self.root_entries_pos + 1
1408
 
            result = self._process_entry(entry, self.root_dir_info)
1409
 
            if result is not None and result is not self.uninteresting:
1410
 
                return result
 
1464
            result, changed = self._process_entry(entry, self.root_dir_info)
 
1465
            if changed is not None:
 
1466
                if changed:
 
1467
                    self._gather_result_for_consistency(result)
 
1468
                if changed or self.include_unchanged:
 
1469
                    return result
1411
1470
        # Have we finished the prior root, or never started one ?
1412
1471
        if self.current_root is None:
1413
1472
            # TODO: the pending list should be lexically sorted?  the
1416
1475
                self.current_root = self.search_specific_files.pop()
1417
1476
            except KeyError:
1418
1477
                raise StopIteration()
1419
 
            self.current_root_unicode = self.current_root.decode('utf8')
1420
1478
            self.searched_specific_files.add(self.current_root)
1421
1479
            # process the entries for this containing directory: the rest will be
1422
1480
            # found by their parents recursively.
1423
1481
            self.root_entries = self.state._entries_for_path(self.current_root)
1424
1482
            self.root_entries_len = len(self.root_entries)
 
1483
            self.current_root_unicode = self.current_root.decode('utf8')
1425
1484
            self.root_abspath = self.tree.abspath(self.current_root_unicode)
1426
1485
            try:
1427
1486
                root_stat = os.lstat(self.root_abspath)
1455
1514
            while self.root_entries_pos < self.root_entries_len:
1456
1515
                entry = self.root_entries[self.root_entries_pos]
1457
1516
                self.root_entries_pos = self.root_entries_pos + 1
1458
 
                result = self._process_entry(entry, self.root_dir_info)
1459
 
                if result is not None:
 
1517
                result, changed = self._process_entry(entry, self.root_dir_info)
 
1518
                if changed is not None:
1460
1519
                    path_handled = -1
1461
 
                    if result is not self.uninteresting:
 
1520
                    if changed:
 
1521
                        self._gather_result_for_consistency(result)
 
1522
                    if changed or self.include_unchanged:
1462
1523
                        return result
1463
1524
            # handle unversioned specified paths:
1464
1525
            if self.want_unversioned and not path_handled and self.root_dir_info:
1476
1537
                      )
1477
1538
            # If we reach here, the outer flow continues, which enters into the
1478
1539
            # per-root setup logic.
1479
 
        if self.current_dir_info is None and self.current_block is None:
 
1540
        if (self.current_dir_info is None and self.current_block is None and not
 
1541
            self.doing_consistency_expansion):
1480
1542
            # setup iteration of this root:
1481
1543
            self.current_dir_list = None
1482
1544
            if self.root_dir_info and self.root_dir_info[2] == 'tree-reference':
1604
1666
                        self.current_block_pos = self.current_block_pos + 1
1605
1667
                        # entry referring to file not present on disk.
1606
1668
                        # advance the entry only, after processing.
1607
 
                        result = self._process_entry(current_entry, None)
1608
 
                        if result is not None:
1609
 
                            if result is not self.uninteresting:
 
1669
                        result, changed = self._process_entry(current_entry, None)
 
1670
                        if changed is not None:
 
1671
                            if changed:
 
1672
                                self._gather_result_for_consistency(result)
 
1673
                            if changed or self.include_unchanged:
1610
1674
                                return result
1611
1675
                    self.block_index = self.block_index + 1
1612
1676
                    self._update_current_block()
1618
1682
            # More supplied paths to process
1619
1683
            self.current_root = None
1620
1684
            return self._iter_next()
 
1685
        # Start expanding more conservatively, adding paths the user may not
 
1686
        # have intended but required for consistent deltas.
 
1687
        self.doing_consistency_expansion = 1
 
1688
        if not self._pending_consistent_entries:
 
1689
            self._pending_consistent_entries = self._next_consistent_entries()
 
1690
        while self._pending_consistent_entries:
 
1691
            result, changed = self._pending_consistent_entries.pop()
 
1692
            if changed is not None:
 
1693
                return result
1621
1694
        raise StopIteration()
1622
1695
 
1623
1696
    cdef object _maybe_tree_ref(self, current_path_info):
1673
1746
                    pass
1674
1747
                elif current_path_info is None:
1675
1748
                    # no path is fine: the per entry code will handle it.
1676
 
                    result = self._process_entry(current_entry, current_path_info)
1677
 
                    if result is not None:
1678
 
                        if result is self.uninteresting:
1679
 
                            result = None
 
1749
                    result, changed = self._process_entry(current_entry,
 
1750
                        current_path_info)
1680
1751
                else:
1681
1752
                    minikind = _minikind_from_string(
1682
1753
                        current_entry[1][self.target_index][0])
1697
1768
                        else:
1698
1769
                            # entry referring to file not present on disk.
1699
1770
                            # advance the entry only, after processing.
1700
 
                            result = self._process_entry(current_entry, None)
1701
 
                            if result is not None:
1702
 
                                if result is self.uninteresting:
1703
 
                                    result = None
 
1771
                            result, changed = self._process_entry(current_entry,
 
1772
                                None)
1704
1773
                            advance_path = 0
1705
1774
                    else:
1706
1775
                        # paths are the same,and the dirstate entry is not
1707
1776
                        # absent or renamed.
1708
 
                        result = self._process_entry(current_entry, current_path_info)
1709
 
                        if result is not None:
 
1777
                        result, changed = self._process_entry(current_entry,
 
1778
                            current_path_info)
 
1779
                        if changed is not None:
1710
1780
                            path_handled = -1
1711
 
                            if result is self.uninteresting:
1712
 
                                result = None
 
1781
                            if not changed and not self.include_unchanged:
 
1782
                                changed = None
1713
1783
                # >- loop control starts here:
1714
1784
                # >- entry
1715
1785
                if advance_entry and current_entry is not None:
1731
1801
                            except UnicodeDecodeError:
1732
1802
                                raise errors.BadFilenameEncoding(
1733
1803
                                    current_path_info[0], osutils._fs_enc)
1734
 
                            if result is not None:
 
1804
                            if changed is not None:
1735
1805
                                raise AssertionError(
1736
1806
                                    "result is not None: %r" % result)
1737
1807
                            result = (None,
1742
1812
                                (None, self.utf8_decode(current_path_info[1])[0]),
1743
1813
                                (None, current_path_info[2]),
1744
1814
                                (None, new_executable))
 
1815
                            changed = True
1745
1816
                        # dont descend into this unversioned path if it is
1746
1817
                        # a dir
1747
1818
                        if current_path_info[2] in ('directory'):
1760
1831
                                current_path_info)
1761
1832
                    else:
1762
1833
                        current_path_info = None
1763
 
                if result is not None:
 
1834
                if changed is not None:
1764
1835
                    # Found a result on this pass, yield it
1765
 
                    return result
 
1836
                    if changed:
 
1837
                        self._gather_result_for_consistency(result)
 
1838
                    if changed or self.include_unchanged:
 
1839
                        return result
1766
1840
            if self.current_block is not None:
1767
1841
                self.block_index = self.block_index + 1
1768
1842
                self._update_current_block()
1774
1848
                    self.current_dir_list = self.current_dir_info[1]
1775
1849
                except StopIteration:
1776
1850
                    self.current_dir_info = None
 
1851
 
 
1852
    cdef object _next_consistent_entries(self):
 
1853
        """Grabs the next specific file parent case to consider.
 
1854
        
 
1855
        :return: A list of the results, each of which is as for _process_entry.
 
1856
        """
 
1857
        results = []
 
1858
        while self.search_specific_file_parents:
 
1859
            # Process the parent directories for the paths we were iterating.
 
1860
            # Even in extremely large trees this should be modest, so currently
 
1861
            # no attempt is made to optimise.
 
1862
            path_utf8 = self.search_specific_file_parents.pop()
 
1863
            if path_utf8 in self.searched_exact_paths:
 
1864
                # We've examined this path.
 
1865
                continue
 
1866
            if osutils.is_inside_any(self.searched_specific_files, path_utf8):
 
1867
                # We've examined this path.
 
1868
                continue
 
1869
            path_entries = self.state._entries_for_path(path_utf8)
 
1870
            # We need either one or two entries. If the path in
 
1871
            # self.target_index has moved (so the entry in source_index is in
 
1872
            # 'ar') then we need to also look for the entry for this path in
 
1873
            # self.source_index, to output the appropriate delete-or-rename.
 
1874
            selected_entries = []
 
1875
            found_item = False
 
1876
            for candidate_entry in path_entries:
 
1877
                # Find entries present in target at this path:
 
1878
                if candidate_entry[1][self.target_index][0] not in 'ar':
 
1879
                    found_item = True
 
1880
                    selected_entries.append(candidate_entry)
 
1881
                # Find entries present in source at this path:
 
1882
                elif (self.source_index is not None and
 
1883
                    candidate_entry[1][self.source_index][0] not in 'ar'):
 
1884
                    found_item = True
 
1885
                    if candidate_entry[1][self.target_index][0] == 'a':
 
1886
                        # Deleted, emit it here.
 
1887
                        selected_entries.append(candidate_entry)
 
1888
                    else:
 
1889
                        # renamed, emit it when we process the directory it
 
1890
                        # ended up at.
 
1891
                        self.search_specific_file_parents.add(
 
1892
                            candidate_entry[1][self.target_index][1])
 
1893
            if not found_item:
 
1894
                raise AssertionError(
 
1895
                    "Missing entry for specific path parent %r, %r" % (
 
1896
                    path_utf8, path_entries))
 
1897
            path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
 
1898
            for entry in selected_entries:
 
1899
                if entry[0][2] in self.seen_ids:
 
1900
                    continue
 
1901
                result, changed = self._process_entry(entry, path_info)
 
1902
                if changed is None:
 
1903
                    raise AssertionError(
 
1904
                        "Got entry<->path mismatch for specific path "
 
1905
                        "%r entry %r path_info %r " % (
 
1906
                        path_utf8, entry, path_info))
 
1907
                # Only include changes - we're outside the users requested
 
1908
                # expansion.
 
1909
                if changed:
 
1910
                    self._gather_result_for_consistency(result)
 
1911
                    if (result[6][0] == 'directory' and
 
1912
                        result[6][1] != 'directory'):
 
1913
                        # This stopped being a directory, the old children have
 
1914
                        # to be included.
 
1915
                        if entry[1][self.source_index][0] == 'r':
 
1916
                            # renamed, take the source path
 
1917
                            entry_path_utf8 = entry[1][self.source_index][1]
 
1918
                        else:
 
1919
                            entry_path_utf8 = path_utf8
 
1920
                        initial_key = (entry_path_utf8, '', '')
 
1921
                        block_index, _ = self.state._find_block_index_from_key(
 
1922
                            initial_key)
 
1923
                        if block_index == 0:
 
1924
                            # The children of the root are in block index 1.
 
1925
                            block_index = block_index + 1
 
1926
                        current_block = None
 
1927
                        if block_index < len(self.state._dirblocks):
 
1928
                            current_block = self.state._dirblocks[block_index]
 
1929
                            if not osutils.is_inside(
 
1930
                                entry_path_utf8, current_block[0]):
 
1931
                                # No entries for this directory at all.
 
1932
                                current_block = None
 
1933
                        if current_block is not None:
 
1934
                            for entry in current_block[1]:
 
1935
                                if entry[1][self.source_index][0] in 'ar':
 
1936
                                    # Not in the source tree, so doesn't have to be
 
1937
                                    # included.
 
1938
                                    continue
 
1939
                                # Path of the entry itself.
 
1940
                                self.search_specific_file_parents.add(
 
1941
                                    self.pathjoin(*entry[0][:2]))
 
1942
                if changed or self.include_unchanged:
 
1943
                    results.append((result, changed))
 
1944
            self.searched_exact_paths.add(path_utf8)
 
1945
        return results
 
1946
 
 
1947
    cdef object _path_info(self, utf8_path, unicode_path):
 
1948
        """Generate path_info for unicode_path.
 
1949
 
 
1950
        :return: None if unicode_path does not exist, or a path_info tuple.
 
1951
        """
 
1952
        abspath = self.tree.abspath(unicode_path)
 
1953
        try:
 
1954
            stat = os.lstat(abspath)
 
1955
        except OSError, e:
 
1956
            if e.errno == errno.ENOENT:
 
1957
                # the path does not exist.
 
1958
                return None
 
1959
            else:
 
1960
                raise
 
1961
        utf8_basename = utf8_path.rsplit('/', 1)[-1]
 
1962
        dir_info = (utf8_path, utf8_basename,
 
1963
            osutils.file_kind_from_stat_mode(stat.st_mode), stat,
 
1964
            abspath)
 
1965
        if dir_info[2] == 'directory':
 
1966
            if self.tree._directory_is_tree_reference(
 
1967
                unicode_path):
 
1968
                self.root_dir_info = self.root_dir_info[:2] + \
 
1969
                    ('tree-reference',) + self.root_dir_info[3:]
 
1970
        return dir_info