~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_dirstate_helpers_pyx.pyx

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-09-01 08:02:42 UTC
  • mfrom: (5390.3.3 faster-revert-593560)
  • Revision ID: pqm@pqm.ubuntu.com-20100901080242-esg62ody4frwmy66
(spiv) Avoid repeatedly calling self.target.all_file_ids() in
 InterTree.iter_changes. (Andrew Bennetts)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2007-2010 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
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
118
118
    # ??? memrchr is a GNU extension :(
119
119
    # void *memrchr(void *s, int c, size_t len)
120
120
 
121
 
 
122
 
cdef void* _my_memrchr(void *s, int c, size_t n):
 
121
# cimport all of the definitions we will need to access
 
122
from _static_tuple_c cimport import_static_tuple_c, StaticTuple, \
 
123
    StaticTuple_New, StaticTuple_SET_ITEM
 
124
 
 
125
import_static_tuple_c()
 
126
 
 
127
cdef void* _my_memrchr(void *s, int c, size_t n): # cannot_raise
123
128
    # memrchr seems to be a GNU extension, so we have to implement it ourselves
124
129
    cdef char *pos
125
130
    cdef char *start
156
161
        return None
157
162
    return <char*>found - <char*>_s
158
163
 
 
164
 
159
165
cdef object safe_string_from_size(char *s, Py_ssize_t size):
160
166
    if size < 0:
161
 
        # XXX: On 64-bit machines the <int> cast causes a C compiler warning.
162
167
        raise AssertionError(
163
 
            'tried to create a string with an invalid size: %d @0x%x'
164
 
            % (size, <int>s))
 
168
            'tried to create a string with an invalid size: %d'
 
169
            % (size))
165
170
    return PyString_FromStringAndSize(s, size)
166
171
 
167
172
 
168
 
cdef int _is_aligned(void *ptr):
 
173
cdef int _is_aligned(void *ptr): # cannot_raise
169
174
    """Is this pointer aligned to an integer size offset?
170
175
 
171
176
    :return: 1 if this pointer is aligned, 0 otherwise.
173
178
    return ((<intptr_t>ptr) & ((sizeof(int))-1)) == 0
174
179
 
175
180
 
176
 
cdef int _cmp_by_dirs(char *path1, int size1, char *path2, int size2):
 
181
cdef int _cmp_by_dirs(char *path1, int size1, char *path2, int size2): # cannot_raise
177
182
    cdef unsigned char *cur1
178
183
    cdef unsigned char *cur2
179
184
    cdef unsigned char *end1
295
300
 
296
301
 
297
302
cdef int _cmp_path_by_dirblock_intern(char *path1, int path1_len,
298
 
                                      char *path2, int path2_len):
 
303
                                      char *path2, int path2_len): # cannot_raise
299
304
    """Compare two paths by what directory they are in.
300
305
 
301
306
    see ``_cmp_path_by_dirblock`` for details.
610
615
        :param new_block: This is to let the caller know that it needs to
611
616
            create a new directory block to store the next entry.
612
617
        """
613
 
        cdef object path_name_file_id_key
 
618
        cdef StaticTuple path_name_file_id_key
 
619
        cdef StaticTuple tmp
614
620
        cdef char *entry_size_cstr
615
621
        cdef unsigned long int entry_size
616
622
        cdef char* executable_cstr
650
656
        # Build up the key that will be used.
651
657
        # By using <object>(void *) Pyrex will automatically handle the
652
658
        # Py_INCREF that we need.
653
 
        path_name_file_id_key = (<object>p_current_dirname[0],
654
 
                                 self.get_next_str(),
655
 
                                 self.get_next_str(),
656
 
                                )
 
659
        cur_dirname = <object>p_current_dirname[0]
 
660
        # Use StaticTuple_New to pre-allocate, rather than creating a regular
 
661
        # tuple and passing it to the StaticTuple constructor.
 
662
        # path_name_file_id_key = StaticTuple(<object>p_current_dirname[0],
 
663
        #                          self.get_next_str(),
 
664
        #                          self.get_next_str(),
 
665
        #                         )
 
666
        tmp = StaticTuple_New(3)
 
667
        Py_INCREF(cur_dirname); StaticTuple_SET_ITEM(tmp, 0, cur_dirname)
 
668
        cur_basename = self.get_next_str()
 
669
        cur_file_id = self.get_next_str()
 
670
        Py_INCREF(cur_basename); StaticTuple_SET_ITEM(tmp, 1, cur_basename)
 
671
        Py_INCREF(cur_file_id); StaticTuple_SET_ITEM(tmp, 2, cur_file_id)
 
672
        path_name_file_id_key = tmp
657
673
 
658
674
        # Parse all of the per-tree information. current has the information in
659
675
        # the same location as parent trees. The only difference is that 'info'
677
693
            executable_cstr = self.get_next(&cur_size)
678
694
            is_executable = (executable_cstr[0] == c'y')
679
695
            info = self.get_next_str()
680
 
            PyList_Append(trees, (
 
696
            # TODO: If we want to use StaticTuple_New here we need to be pretty
 
697
            #       careful. We are relying on a bit of Pyrex
 
698
            #       automatic-conversion from 'int' to PyInt, and that doesn't
 
699
            #       play well with the StaticTuple_SET_ITEM macro.
 
700
            #       Timing doesn't (yet) show a worthwile improvement in speed
 
701
            #       versus complexity and maintainability.
 
702
            # tmp = StaticTuple_New(5)
 
703
            # Py_INCREF(minikind); StaticTuple_SET_ITEM(tmp, 0, minikind)
 
704
            # Py_INCREF(fingerprint); StaticTuple_SET_ITEM(tmp, 1, fingerprint)
 
705
            # Py_INCREF(entry_size); StaticTuple_SET_ITEM(tmp, 2, entry_size)
 
706
            # Py_INCREF(is_executable); StaticTuple_SET_ITEM(tmp, 3, is_executable)
 
707
            # Py_INCREF(info); StaticTuple_SET_ITEM(tmp, 4, info)
 
708
            # PyList_Append(trees, tmp)
 
709
            PyList_Append(trees, StaticTuple(
681
710
                minikind,     # minikind
682
711
                fingerprint,  # fingerprint
683
712
                entry_size,   # size
768
797
    state._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
769
798
 
770
799
 
771
 
cdef int minikind_from_mode(int mode):
 
800
cdef int minikind_from_mode(int mode): # cannot_raise
772
801
    # in order of frequency:
773
802
    if S_ISREG(mode):
774
803
        return c"f"
915
944
    return link_or_sha1
916
945
 
917
946
 
918
 
cdef char _minikind_from_string(object string):
 
947
# TODO: Do we want to worry about exceptions here?
 
948
cdef char _minikind_from_string(object string) except? -1:
919
949
    """Convert a python string to a char."""
920
950
    return PyString_AsString(string)[0]
921
951
 
953
983
    raise KeyError(PyString_FromStringAndSize(_minikind, 1))
954
984
 
955
985
 
956
 
cdef int _versioned_minikind(char minikind):
 
986
cdef int _versioned_minikind(char minikind): # cannot_raise
957
987
    """Return non-zero if minikind is in fltd"""
958
988
    return (minikind == c'f' or
959
989
            minikind == c'd' or
963
993
 
964
994
cdef class ProcessEntryC:
965
995
 
 
996
    cdef int doing_consistency_expansion
966
997
    cdef object old_dirname_to_file_id # dict
967
998
    cdef object new_dirname_to_file_id # dict
968
999
    cdef object last_source_parent
969
1000
    cdef object last_target_parent
970
 
    cdef object include_unchanged
 
1001
    cdef int include_unchanged
 
1002
    cdef int partial
971
1003
    cdef object use_filesystem_for_exec
972
1004
    cdef object utf8_decode
973
1005
    cdef readonly object searched_specific_files
 
1006
    cdef readonly object searched_exact_paths
974
1007
    cdef object search_specific_files
 
1008
    # The parents up to the root of the paths we are searching.
 
1009
    # After all normal paths are returned, these specific items are returned.
 
1010
    cdef object search_specific_file_parents
975
1011
    cdef object state
976
1012
    # Current iteration variables:
977
1013
    cdef object current_root
989
1025
    cdef object current_block_list
990
1026
    cdef object current_dir_info
991
1027
    cdef object current_dir_list
 
1028
    cdef object _pending_consistent_entries # list
992
1029
    cdef int path_index
993
1030
    cdef object root_dir_info
994
1031
    cdef object bisect_left
995
1032
    cdef object pathjoin
996
1033
    cdef object fstat
 
1034
    # A set of the ids we've output when doing partial output.
 
1035
    cdef object seen_ids
997
1036
    cdef object sha_file
998
1037
 
999
1038
    def __init__(self, include_unchanged, use_filesystem_for_exec,
1000
1039
        search_specific_files, state, source_index, target_index,
1001
1040
        want_unversioned, tree):
 
1041
        self.doing_consistency_expansion = 0
1002
1042
        self.old_dirname_to_file_id = {}
1003
1043
        self.new_dirname_to_file_id = {}
 
1044
        # Are we doing a partial iter_changes?
 
1045
        self.partial = set(['']).__ne__(search_specific_files)
1004
1046
        # Using a list so that we can access the values and change them in
1005
1047
        # nested scope. Each one is [path, file_id, entry]
1006
1048
        self.last_source_parent = [None, None]
1007
1049
        self.last_target_parent = [None, None]
1008
 
        self.include_unchanged = include_unchanged
 
1050
        if include_unchanged is None:
 
1051
            self.include_unchanged = False
 
1052
        else:
 
1053
            self.include_unchanged = int(include_unchanged)
1009
1054
        self.use_filesystem_for_exec = use_filesystem_for_exec
1010
1055
        self.utf8_decode = cache_utf8._utf8_decode
1011
1056
        # for all search_indexs in each path at or under each element of
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.
 
1057
        # search_specific_files, if the detail is relocated: add the id, and
 
1058
        # add the relocated path as one to search if its not searched already.
 
1059
        # If the detail is not relocated, add the id.
1015
1060
        self.searched_specific_files = set()
 
1061
        # When we search exact paths without expanding downwards, we record
 
1062
        # that here.
 
1063
        self.searched_exact_paths = set()
1016
1064
        self.search_specific_files = search_specific_files
 
1065
        # The parents up to the root of the paths we are searching.
 
1066
        # After all normal paths are returned, these specific items are returned.
 
1067
        self.search_specific_file_parents = set()
 
1068
        # The ids we've sent out in the delta.
 
1069
        self.seen_ids = set()
1017
1070
        self.state = state
1018
1071
        self.current_root = None
1019
1072
        self.current_root_unicode = None
1035
1088
        self.current_block_pos = -1
1036
1089
        self.current_dir_info = None
1037
1090
        self.current_dir_list = None
 
1091
        self._pending_consistent_entries = []
1038
1092
        self.path_index = 0
1039
1093
        self.root_dir_info = None
1040
1094
        self.bisect_left = bisect.bisect_left
1041
1095
        self.pathjoin = osutils.pathjoin
1042
1096
        self.fstat = os.fstat
1043
1097
        self.sha_file = osutils.sha_file
 
1098
        if target_index != 0:
 
1099
            # A lot of code in here depends on target_index == 0
 
1100
            raise errors.BzrError('unsupported target index')
1044
1101
 
1045
1102
    cdef _process_entry(self, entry, path_info):
1046
1103
        """Compare an entry and real disk to generate delta information.
1047
1104
 
1048
1105
        :param path_info: top_relpath, basename, kind, lstat, abspath for
1049
 
            the path of entry. If None, then the path is considered absent.
1050
 
            (Perhaps we should pass in a concrete entry for this ?)
 
1106
            the path of entry. If None, then the path is considered absent in 
 
1107
            the target (Perhaps we should pass in a concrete entry for this ?)
1051
1108
            Basename is returned as a utf8 string because we expect this
1052
1109
            tuple will be ignored, and don't want to take the time to
1053
1110
            decode.
1054
1111
        :return: (iter_changes_result, changed). If the entry has not been
1055
1112
            handled then changed is None. Otherwise it is False if no content
1056
 
            or metadata changes have occured, and None if any content or
1057
 
            metadata change has occured. If self.include_unchanged is True then
 
1113
            or metadata changes have occured, and True if any content or
 
1114
            metadata change has occurred. If self.include_unchanged is True then
1058
1115
            if changed is not None, iter_changes_result will always be a result
1059
1116
            tuple. Otherwise, iter_changes_result is None unless changed is
1060
1117
            True.
1099
1156
            else:
1100
1157
                # add the source to the search path to find any children it
1101
1158
                # has.  TODO ? : only add if it is a container ?
1102
 
                if not osutils.is_inside_any(self.searched_specific_files,
1103
 
                                             source_details[1]):
 
1159
                if (not self.doing_consistency_expansion and 
 
1160
                    not osutils.is_inside_any(self.searched_specific_files,
 
1161
                                             source_details[1])):
1104
1162
                    self.search_specific_files.add(source_details[1])
 
1163
                    # expanding from a user requested path, parent expansion
 
1164
                    # for delta consistency happens later.
1105
1165
                # generate the old path; this is needed for stating later
1106
1166
                # as well.
1107
1167
                old_path = source_details[1]
1172
1232
                        content_change = 0
1173
1233
                    target_exec = False
1174
1234
                else:
1175
 
                    raise Exception, "unknown kind %s" % path_info[2]
 
1235
                    if path is None:
 
1236
                        path = self.pathjoin(old_dirname, old_basename)
 
1237
                    raise errors.BadFileKindError(path, path_info[2])
1176
1238
            if source_minikind == c'd':
1177
1239
                if path is None:
1178
1240
                    old_path = path = self.pathjoin(old_dirname, old_basename)
1180
1242
                    file_id = entry[0][2]
1181
1243
                self.old_dirname_to_file_id[old_path] = file_id
1182
1244
            # parent id is the entry for the path in the target tree
1183
 
            if old_dirname == self.last_source_parent[0]:
 
1245
            if old_basename and old_dirname == self.last_source_parent[0]:
 
1246
                # use a cached hit for non-root source entries.
1184
1247
                source_parent_id = self.last_source_parent[1]
1185
1248
            else:
1186
1249
                try:
1187
1250
                    source_parent_id = self.old_dirname_to_file_id[old_dirname]
1188
 
                except KeyError:
 
1251
                except KeyError, _:
1189
1252
                    source_parent_entry = self.state._get_entry(self.source_index,
1190
1253
                                                           path_utf8=old_dirname)
1191
1254
                    source_parent_id = source_parent_entry[0][2]
1196
1259
                    self.last_source_parent[0] = old_dirname
1197
1260
                    self.last_source_parent[1] = source_parent_id
1198
1261
            new_dirname = entry[0][0]
1199
 
            if new_dirname == self.last_target_parent[0]:
 
1262
            if entry[0][1] and new_dirname == self.last_target_parent[0]:
 
1263
                # use a cached hit for non-root target entries.
1200
1264
                target_parent_id = self.last_target_parent[1]
1201
1265
            else:
1202
1266
                try:
1203
1267
                    target_parent_id = self.new_dirname_to_file_id[new_dirname]
1204
 
                except KeyError:
 
1268
                except KeyError, _:
1205
1269
                    # TODO: We don't always need to do the lookup, because the
1206
1270
                    #       parent entry will be the same as the source entry.
1207
1271
                    target_parent_entry = self.state._get_entry(self.target_index,
1313
1377
            # a renamed parent. TODO: handle this efficiently. Its not
1314
1378
            # common case to rename dirs though, so a correct but slow
1315
1379
            # implementation will do.
1316
 
            if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
 
1380
            if (not self.doing_consistency_expansion and 
 
1381
                not osutils.is_inside_any(self.searched_specific_files,
 
1382
                    target_details[1])):
1317
1383
                self.search_specific_files.add(target_details[1])
 
1384
                # We don't expand the specific files parents list here as
 
1385
                # the path is absent in target and won't create a delta with
 
1386
                # missing parent.
1318
1387
        elif ((source_minikind == c'r' or source_minikind == c'a') and
1319
1388
              (target_minikind == c'r' or target_minikind == c'a')):
1320
1389
            # neither of the selected trees contain this path,
1334
1403
    def iter_changes(self):
1335
1404
        return self
1336
1405
 
1337
 
    cdef void _update_current_block(self):
 
1406
    cdef int _gather_result_for_consistency(self, result) except -1:
 
1407
        """Check a result we will yield to make sure we are consistent later.
 
1408
        
 
1409
        This gathers result's parents into a set to output later.
 
1410
 
 
1411
        :param result: A result tuple.
 
1412
        """
 
1413
        if not self.partial or not result[0]:
 
1414
            return 0
 
1415
        self.seen_ids.add(result[0])
 
1416
        new_path = result[1][1]
 
1417
        if new_path:
 
1418
            # Not the root and not a delete: queue up the parents of the path.
 
1419
            self.search_specific_file_parents.update(
 
1420
                osutils.parent_directories(new_path.encode('utf8')))
 
1421
            # Add the root directory which parent_directories does not
 
1422
            # provide.
 
1423
            self.search_specific_file_parents.add('')
 
1424
        return 0
 
1425
 
 
1426
    cdef int _update_current_block(self) except -1:
1338
1427
        if (self.block_index < len(self.state._dirblocks) and
1339
1428
            osutils.is_inside(self.current_root, self.state._dirblocks[self.block_index][0])):
1340
1429
            self.current_block = self.state._dirblocks[self.block_index]
1343
1432
        else:
1344
1433
            self.current_block = None
1345
1434
            self.current_block_list = None
 
1435
        return 0
1346
1436
 
1347
1437
    def __next__(self):
1348
1438
        # Simple thunk to allow tail recursion without pyrex confusion
1406
1496
            entry = self.root_entries[self.root_entries_pos]
1407
1497
            self.root_entries_pos = self.root_entries_pos + 1
1408
1498
            result, changed = self._process_entry(entry, self.root_dir_info)
1409
 
            if changed is not None and changed or self.include_unchanged:
1410
 
                return result
 
1499
            if changed is not None:
 
1500
                if changed:
 
1501
                    self._gather_result_for_consistency(result)
 
1502
                if changed or self.include_unchanged:
 
1503
                    return result
1411
1504
        # Have we finished the prior root, or never started one ?
1412
1505
        if self.current_root is None:
1413
1506
            # TODO: the pending list should be lexically sorted?  the
1414
1507
            # interface doesn't require it.
1415
1508
            try:
1416
1509
                self.current_root = self.search_specific_files.pop()
1417
 
            except KeyError:
 
1510
            except KeyError, _:
1418
1511
                raise StopIteration()
1419
 
            self.current_root_unicode = self.current_root.decode('utf8')
1420
1512
            self.searched_specific_files.add(self.current_root)
1421
1513
            # process the entries for this containing directory: the rest will be
1422
1514
            # found by their parents recursively.
1423
1515
            self.root_entries = self.state._entries_for_path(self.current_root)
1424
1516
            self.root_entries_len = len(self.root_entries)
 
1517
            self.current_root_unicode = self.current_root.decode('utf8')
1425
1518
            self.root_abspath = self.tree.abspath(self.current_root_unicode)
1426
1519
            try:
1427
1520
                root_stat = os.lstat(self.root_abspath)
1458
1551
                result, changed = self._process_entry(entry, self.root_dir_info)
1459
1552
                if changed is not None:
1460
1553
                    path_handled = -1
 
1554
                    if changed:
 
1555
                        self._gather_result_for_consistency(result)
1461
1556
                    if changed or self.include_unchanged:
1462
1557
                        return result
1463
1558
            # handle unversioned specified paths:
1476
1571
                      )
1477
1572
            # If we reach here, the outer flow continues, which enters into the
1478
1573
            # per-root setup logic.
1479
 
        if self.current_dir_info is None and self.current_block is None:
 
1574
        if (self.current_dir_info is None and self.current_block is None and not
 
1575
            self.doing_consistency_expansion):
1480
1576
            # setup iteration of this root:
1481
1577
            self.current_dir_list = None
1482
1578
            if self.root_dir_info and self.root_dir_info[2] == 'tree-reference':
1500
1596
                        #            and e.winerror == ERROR_DIRECTORY
1501
1597
                        try:
1502
1598
                            e_winerror = e.winerror
1503
 
                        except AttributeError:
 
1599
                        except AttributeError, _:
1504
1600
                            e_winerror = None
1505
1601
                        win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
1506
1602
                        if (e.errno in win_errors or e_winerror in win_errors):
1589
1685
                    try:
1590
1686
                        self.current_dir_info = self.dir_iterator.next()
1591
1687
                        self.current_dir_list = self.current_dir_info[1]
1592
 
                    except StopIteration:
 
1688
                    except StopIteration, _:
1593
1689
                        self.current_dir_info = None
1594
1690
                else: #(dircmp > 0)
1595
1691
                    # We have a dirblock entry for this location, but there
1606
1702
                        # advance the entry only, after processing.
1607
1703
                        result, changed = self._process_entry(current_entry, None)
1608
1704
                        if changed is not None:
 
1705
                            if changed:
 
1706
                                self._gather_result_for_consistency(result)
1609
1707
                            if changed or self.include_unchanged:
1610
1708
                                return result
1611
1709
                    self.block_index = self.block_index + 1
1618
1716
            # More supplied paths to process
1619
1717
            self.current_root = None
1620
1718
            return self._iter_next()
 
1719
        # Start expanding more conservatively, adding paths the user may not
 
1720
        # have intended but required for consistent deltas.
 
1721
        self.doing_consistency_expansion = 1
 
1722
        if not self._pending_consistent_entries:
 
1723
            self._pending_consistent_entries = self._next_consistent_entries()
 
1724
        while self._pending_consistent_entries:
 
1725
            result, changed = self._pending_consistent_entries.pop()
 
1726
            if changed is not None:
 
1727
                return result
1621
1728
        raise StopIteration()
1622
1729
 
1623
1730
    cdef object _maybe_tree_ref(self, current_path_info):
1705
1812
                            current_path_info)
1706
1813
                        if changed is not None:
1707
1814
                            path_handled = -1
 
1815
                            if not changed and not self.include_unchanged:
 
1816
                                changed = None
1708
1817
                # >- loop control starts here:
1709
1818
                # >- entry
1710
1819
                if advance_entry and current_entry is not None:
1723
1832
                                and stat.S_IEXEC & current_path_info[3].st_mode)
1724
1833
                            try:
1725
1834
                                relpath_unicode = self.utf8_decode(current_path_info[0])[0]
1726
 
                            except UnicodeDecodeError:
 
1835
                            except UnicodeDecodeError, _:
1727
1836
                                raise errors.BadFilenameEncoding(
1728
1837
                                    current_path_info[0], osutils._fs_enc)
1729
 
                            if result is not None:
 
1838
                            if changed is not None:
1730
1839
                                raise AssertionError(
1731
1840
                                    "result is not None: %r" % result)
1732
1841
                            result = (None,
1737
1846
                                (None, self.utf8_decode(current_path_info[1])[0]),
1738
1847
                                (None, current_path_info[2]),
1739
1848
                                (None, new_executable))
 
1849
                            changed = True
1740
1850
                        # dont descend into this unversioned path if it is
1741
1851
                        # a dir
1742
1852
                        if current_path_info[2] in ('directory'):
1755
1865
                                current_path_info)
1756
1866
                    else:
1757
1867
                        current_path_info = None
1758
 
                if result is not None:
 
1868
                if changed is not None:
1759
1869
                    # Found a result on this pass, yield it
1760
 
                    return result
 
1870
                    if changed:
 
1871
                        self._gather_result_for_consistency(result)
 
1872
                    if changed or self.include_unchanged:
 
1873
                        return result
1761
1874
            if self.current_block is not None:
1762
1875
                self.block_index = self.block_index + 1
1763
1876
                self._update_current_block()
1767
1880
                try:
1768
1881
                    self.current_dir_info = self.dir_iterator.next()
1769
1882
                    self.current_dir_list = self.current_dir_info[1]
1770
 
                except StopIteration:
 
1883
                except StopIteration, _:
1771
1884
                    self.current_dir_info = None
 
1885
 
 
1886
    cdef object _next_consistent_entries(self):
 
1887
        """Grabs the next specific file parent case to consider.
 
1888
        
 
1889
        :return: A list of the results, each of which is as for _process_entry.
 
1890
        """
 
1891
        results = []
 
1892
        while self.search_specific_file_parents:
 
1893
            # Process the parent directories for the paths we were iterating.
 
1894
            # Even in extremely large trees this should be modest, so currently
 
1895
            # no attempt is made to optimise.
 
1896
            path_utf8 = self.search_specific_file_parents.pop()
 
1897
            if path_utf8 in self.searched_exact_paths:
 
1898
                # We've examined this path.
 
1899
                continue
 
1900
            if osutils.is_inside_any(self.searched_specific_files, path_utf8):
 
1901
                # We've examined this path.
 
1902
                continue
 
1903
            path_entries = self.state._entries_for_path(path_utf8)
 
1904
            # We need either one or two entries. If the path in
 
1905
            # self.target_index has moved (so the entry in source_index is in
 
1906
            # 'ar') then we need to also look for the entry for this path in
 
1907
            # self.source_index, to output the appropriate delete-or-rename.
 
1908
            selected_entries = []
 
1909
            found_item = False
 
1910
            for candidate_entry in path_entries:
 
1911
                # Find entries present in target at this path:
 
1912
                if candidate_entry[1][self.target_index][0] not in 'ar':
 
1913
                    found_item = True
 
1914
                    selected_entries.append(candidate_entry)
 
1915
                # Find entries present in source at this path:
 
1916
                elif (self.source_index is not None and
 
1917
                    candidate_entry[1][self.source_index][0] not in 'ar'):
 
1918
                    found_item = True
 
1919
                    if candidate_entry[1][self.target_index][0] == 'a':
 
1920
                        # Deleted, emit it here.
 
1921
                        selected_entries.append(candidate_entry)
 
1922
                    else:
 
1923
                        # renamed, emit it when we process the directory it
 
1924
                        # ended up at.
 
1925
                        self.search_specific_file_parents.add(
 
1926
                            candidate_entry[1][self.target_index][1])
 
1927
            if not found_item:
 
1928
                raise AssertionError(
 
1929
                    "Missing entry for specific path parent %r, %r" % (
 
1930
                    path_utf8, path_entries))
 
1931
            path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
 
1932
            for entry in selected_entries:
 
1933
                if entry[0][2] in self.seen_ids:
 
1934
                    continue
 
1935
                result, changed = self._process_entry(entry, path_info)
 
1936
                if changed is None:
 
1937
                    raise AssertionError(
 
1938
                        "Got entry<->path mismatch for specific path "
 
1939
                        "%r entry %r path_info %r " % (
 
1940
                        path_utf8, entry, path_info))
 
1941
                # Only include changes - we're outside the users requested
 
1942
                # expansion.
 
1943
                if changed:
 
1944
                    self._gather_result_for_consistency(result)
 
1945
                    if (result[6][0] == 'directory' and
 
1946
                        result[6][1] != 'directory'):
 
1947
                        # This stopped being a directory, the old children have
 
1948
                        # to be included.
 
1949
                        if entry[1][self.source_index][0] == 'r':
 
1950
                            # renamed, take the source path
 
1951
                            entry_path_utf8 = entry[1][self.source_index][1]
 
1952
                        else:
 
1953
                            entry_path_utf8 = path_utf8
 
1954
                        initial_key = (entry_path_utf8, '', '')
 
1955
                        block_index, _ = self.state._find_block_index_from_key(
 
1956
                            initial_key)
 
1957
                        if block_index == 0:
 
1958
                            # The children of the root are in block index 1.
 
1959
                            block_index = block_index + 1
 
1960
                        current_block = None
 
1961
                        if block_index < len(self.state._dirblocks):
 
1962
                            current_block = self.state._dirblocks[block_index]
 
1963
                            if not osutils.is_inside(
 
1964
                                entry_path_utf8, current_block[0]):
 
1965
                                # No entries for this directory at all.
 
1966
                                current_block = None
 
1967
                        if current_block is not None:
 
1968
                            for entry in current_block[1]:
 
1969
                                if entry[1][self.source_index][0] in 'ar':
 
1970
                                    # Not in the source tree, so doesn't have to be
 
1971
                                    # included.
 
1972
                                    continue
 
1973
                                # Path of the entry itself.
 
1974
                                self.search_specific_file_parents.add(
 
1975
                                    self.pathjoin(*entry[0][:2]))
 
1976
                if changed or self.include_unchanged:
 
1977
                    results.append((result, changed))
 
1978
            self.searched_exact_paths.add(path_utf8)
 
1979
        return results
 
1980
 
 
1981
    cdef object _path_info(self, utf8_path, unicode_path):
 
1982
        """Generate path_info for unicode_path.
 
1983
 
 
1984
        :return: None if unicode_path does not exist, or a path_info tuple.
 
1985
        """
 
1986
        abspath = self.tree.abspath(unicode_path)
 
1987
        try:
 
1988
            stat = os.lstat(abspath)
 
1989
        except OSError, e:
 
1990
            if e.errno == errno.ENOENT:
 
1991
                # the path does not exist.
 
1992
                return None
 
1993
            else:
 
1994
                raise
 
1995
        utf8_basename = utf8_path.rsplit('/', 1)[-1]
 
1996
        dir_info = (utf8_path, utf8_basename,
 
1997
            osutils.file_kind_from_stat_mode(stat.st_mode), stat,
 
1998
            abspath)
 
1999
        if dir_info[2] == 'directory':
 
2000
            if self.tree._directory_is_tree_reference(
 
2001
                unicode_path):
 
2002
                self.root_dir_info = self.root_dir_info[:2] + \
 
2003
                    ('tree-reference',) + self.root_dir_info[3:]
 
2004
        return dir_info