~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-09-26 04:27:28 UTC
  • mfrom: (3696.4.20 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080926042728-8dzaiolv2q1duutw
(robertc) Create a pyrex optimised iter_changes for dirstate trees.
        (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
98
98
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
99
99
 
100
100
 
101
 
# This is the Windows equivalent of ENOTDIR
102
 
# It is defined in pywin32.winerror, but we don't want a strong dependency for
103
 
# just an error code.
104
 
ERROR_PATH_NOT_FOUND = 3
105
 
ERROR_DIRECTORY = 267
106
 
 
107
 
 
108
101
class WorkingTree4(WorkingTree3):
109
102
    """This is the Format 4 working tree.
110
103
 
149
142
        self._setup_directory_is_tree_reference()
150
143
        self._detect_case_handling()
151
144
        self._rules_searcher = None
 
145
        #--- allow tests to select the dirstate iter_changes implementation
 
146
        self._iter_changes = dirstate._process_entry
152
147
 
153
148
    @needs_tree_write_lock
154
149
    def _add(self, files, ids, kinds):
409
404
                    return None
410
405
                else:
411
406
                    raise
412
 
        link_or_sha1 = state.update_entry(entry, file_abspath,
413
 
                                          stat_value=stat_value)
 
407
        link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
 
408
            stat_value=stat_value)
414
409
        if entry[1][0][0] == 'f':
415
410
            return link_or_sha1
416
411
        return None
1810
1805
        target.set_parent_ids([revid])
1811
1806
        return target.basis_tree(), target
1812
1807
 
 
1808
    @classmethod
 
1809
    def make_source_parent_tree_python_dirstate(klass, test_case, source, target):
 
1810
        result = klass.make_source_parent_tree(source, target)
 
1811
        result[1]._iter_changes = dirstate.ProcessEntryPython
 
1812
        return result
 
1813
 
 
1814
    @classmethod
 
1815
    def make_source_parent_tree_compiled_dirstate(klass, test_case, source, target):
 
1816
        from bzrlib.tests.test__dirstate_helpers import \
 
1817
            CompiledDirstateHelpersFeature
 
1818
        if not CompiledDirstateHelpersFeature.available():
 
1819
            from bzrlib.tests import UnavailableFeature
 
1820
            raise UnavailableFeature(CompiledDirstateHelpersFeature)
 
1821
        from bzrlib._dirstate_helpers_c import ProcessEntryC
 
1822
        result = klass.make_source_parent_tree(source, target)
 
1823
        result[1]._iter_changes = ProcessEntryC
 
1824
        return result
 
1825
 
1813
1826
    _matching_from_tree_format = WorkingTreeFormat4()
1814
1827
    _matching_to_tree_format = WorkingTreeFormat4()
1815
1828
 
1816
1829
    @classmethod
1817
1830
    def _test_mutable_trees_to_test_trees(klass, test_case, source, target):
1818
 
        return klass.make_source_parent_tree(source, target)
 
1831
        # This method shouldn't be called, because we have python and C
 
1832
        # specific flavours.
 
1833
        raise NotImplementedError
1819
1834
 
1820
1835
    def iter_changes(self, include_unchanged=False,
1821
1836
                      specific_files=None, pb=None, extra_trees=[],
1839
1854
            output. An unversioned file is defined as one with (False, False)
1840
1855
            for the versioned pair.
1841
1856
        """
1842
 
        utf8_decode = cache_utf8._utf8_decode
1843
 
        _minikind_to_kind = dirstate.DirState._minikind_to_kind
1844
 
        cmp_by_dirs = dirstate.cmp_by_dirs
1845
1857
        # NB: show_status depends on being able to pass in non-versioned files
1846
1858
        # and report them as unknown
1847
1859
        # TODO: handle extra trees in the dirstate.
1848
1860
        if (extra_trees or specific_files == []):
1849
1861
            # we can't fast-path these cases (yet)
1850
 
            for f in super(InterDirStateTree, self).iter_changes(
 
1862
            return super(InterDirStateTree, self).iter_changes(
1851
1863
                include_unchanged, specific_files, pb, extra_trees,
1852
 
                require_versioned, want_unversioned=want_unversioned):
1853
 
                yield f
1854
 
            return
 
1864
                require_versioned, want_unversioned=want_unversioned)
1855
1865
        parent_ids = self.target.get_parent_ids()
1856
1866
        if not (self.source._revision_id in parent_ids
1857
1867
                or self.source._revision_id == NULL_REVISION):
1874
1884
        if specific_files:
1875
1885
            specific_files_utf8 = set()
1876
1886
            for path in specific_files:
 
1887
                # Note, if there are many specific files, using cache_utf8
 
1888
                # would be good here.
1877
1889
                specific_files_utf8.add(path.encode('utf8'))
1878
1890
            specific_files = specific_files_utf8
1879
1891
        else:
1880
1892
            specific_files = set([''])
1881
1893
        # -- specific_files is now a utf8 path set --
 
1894
        search_specific_files = set()
1882
1895
        # -- get the state object and prepare it.
1883
1896
        state = self.target.current_dirstate()
1884
1897
        state._read_dirblocks_if_needed()
1885
 
        def _entries_for_path(path):
1886
 
            """Return a list with all the entries that match path for all ids.
1887
 
            """
1888
 
            dirname, basename = os.path.split(path)
1889
 
            key = (dirname, basename, '')
1890
 
            block_index, present = state._find_block_index_from_key(key)
1891
 
            if not present:
1892
 
                # the block which should contain path is absent.
1893
 
                return []
1894
 
            result = []
1895
 
            block = state._dirblocks[block_index][1]
1896
 
            entry_index, _ = state._find_entry_index(key, block)
1897
 
            # we may need to look at multiple entries at this path: walk while the specific_files match.
1898
 
            while (entry_index < len(block) and
1899
 
                block[entry_index][0][0:2] == key[0:2]):
1900
 
                result.append(block[entry_index])
1901
 
                entry_index += 1
1902
 
            return result
1903
1898
        if require_versioned:
1904
1899
            # -- check all supplied paths are versioned in a search tree. --
1905
1900
            all_versioned = True
1906
1901
            for path in specific_files:
1907
 
                path_entries = _entries_for_path(path)
 
1902
                path_entries = state._entries_for_path(path)
1908
1903
                if not path_entries:
1909
1904
                    # this specified path is not present at all: error
1910
1905
                    all_versioned = False
1926
1921
            if not all_versioned:
1927
1922
                raise errors.PathsNotVersionedError(specific_files)
1928
1923
        # -- remove redundancy in supplied specific_files to prevent over-scanning --
1929
 
        search_specific_files = set()
1930
1924
        for path in specific_files:
1931
1925
            other_specific_files = specific_files.difference(set([path]))
1932
1926
            if not osutils.is_inside_any(other_specific_files, path):
1933
1927
                # this is a top level path, we must check it.
1934
1928
                search_specific_files.add(path)
1935
 
        # sketch: 
1936
 
        # compare source_index and target_index at or under each element of search_specific_files.
1937
 
        # follow the following comparison table. Note that we only want to do diff operations when
1938
 
        # the target is fdl because thats when the walkdirs logic will have exposed the pathinfo 
1939
 
        # for the target.
1940
 
        # cases:
1941
 
        # 
1942
 
        # Source | Target | disk | action
1943
 
        #   r    | fdlt   |      | add source to search, add id path move and perform
1944
 
        #        |        |      | diff check on source-target
1945
 
        #   r    | fdlt   |  a   | dangling file that was present in the basis. 
1946
 
        #        |        |      | ???
1947
 
        #   r    |  a     |      | add source to search
1948
 
        #   r    |  a     |  a   | 
1949
 
        #   r    |  r     |      | this path is present in a non-examined tree, skip.
1950
 
        #   r    |  r     |  a   | this path is present in a non-examined tree, skip.
1951
 
        #   a    | fdlt   |      | add new id
1952
 
        #   a    | fdlt   |  a   | dangling locally added file, skip
1953
 
        #   a    |  a     |      | not present in either tree, skip
1954
 
        #   a    |  a     |  a   | not present in any tree, skip
1955
 
        #   a    |  r     |      | not present in either tree at this path, skip as it
1956
 
        #        |        |      | may not be selected by the users list of paths.
1957
 
        #   a    |  r     |  a   | not present in either tree at this path, skip as it
1958
 
        #        |        |      | may not be selected by the users list of paths.
1959
 
        #  fdlt  | fdlt   |      | content in both: diff them
1960
 
        #  fdlt  | fdlt   |  a   | deleted locally, but not unversioned - show as deleted ?
1961
 
        #  fdlt  |  a     |      | unversioned: output deleted id for now
1962
 
        #  fdlt  |  a     |  a   | unversioned and deleted: output deleted id
1963
 
        #  fdlt  |  r     |      | relocated in this tree, so add target to search.
1964
 
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
1965
 
        #        |        |      | this id at the other path.
1966
 
        #  fdlt  |  r     |  a   | relocated in this tree, so add target to search.
1967
 
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
1968
 
        #        |        |      | this id at the other path.
1969
 
 
1970
 
        # for all search_indexs in each path at or under each element of
1971
 
        # search_specific_files, if the detail is relocated: add the id, and add the
1972
 
        # relocated path as one to search if its not searched already. If the
1973
 
        # detail is not relocated, add the id.
1974
 
        searched_specific_files = set()
1975
 
        NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1976
 
        # Using a list so that we can access the values and change them in
1977
 
        # nested scope. Each one is [path, file_id, entry]
1978
 
        last_source_parent = [None, None]
1979
 
        last_target_parent = [None, None]
1980
1929
 
1981
1930
        use_filesystem_for_exec = (sys.platform != 'win32')
1982
 
 
1983
 
        # Just a sentry, so that _process_entry can say that this
1984
 
        # record is handled, but isn't interesting to process (unchanged)
1985
 
        uninteresting = object()
1986
 
 
1987
 
        old_dirname_to_file_id = {}
1988
 
        new_dirname_to_file_id = {}
1989
 
        # TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
1990
 
        #       keeping a cache of directories that we have seen.
1991
 
 
1992
 
        def _process_entry(entry, path_info):
1993
 
            """Compare an entry and real disk to generate delta information.
1994
 
 
1995
 
            :param path_info: top_relpath, basename, kind, lstat, abspath for
1996
 
                the path of entry. If None, then the path is considered absent.
1997
 
                (Perhaps we should pass in a concrete entry for this ?)
1998
 
                Basename is returned as a utf8 string because we expect this
1999
 
                tuple will be ignored, and don't want to take the time to
2000
 
                decode.
2001
 
            :return: None if these don't match
2002
 
                     A tuple of information about the change, or
2003
 
                     the object 'uninteresting' if these match, but are
2004
 
                     basically identical.
2005
 
            """
2006
 
            if source_index is None:
2007
 
                source_details = NULL_PARENT_DETAILS
2008
 
            else:
2009
 
                source_details = entry[1][source_index]
2010
 
            target_details = entry[1][target_index]
2011
 
            target_minikind = target_details[0]
2012
 
            if path_info is not None and target_minikind in 'fdlt':
2013
 
                if not (target_index == 0):
2014
 
                    raise AssertionError()
2015
 
                link_or_sha1 = state.update_entry(entry, abspath=path_info[4],
2016
 
                                                  stat_value=path_info[3])
2017
 
                # The entry may have been modified by update_entry
2018
 
                target_details = entry[1][target_index]
2019
 
                target_minikind = target_details[0]
2020
 
            else:
2021
 
                link_or_sha1 = None
2022
 
            file_id = entry[0][2]
2023
 
            source_minikind = source_details[0]
2024
 
            if source_minikind in 'fdltr' and target_minikind in 'fdlt':
2025
 
                # claimed content in both: diff
2026
 
                #   r    | fdlt   |      | add source to search, add id path move and perform
2027
 
                #        |        |      | diff check on source-target
2028
 
                #   r    | fdlt   |  a   | dangling file that was present in the basis.
2029
 
                #        |        |      | ???
2030
 
                if source_minikind in 'r':
2031
 
                    # add the source to the search path to find any children it
2032
 
                    # has.  TODO ? : only add if it is a container ?
2033
 
                    if not osutils.is_inside_any(searched_specific_files,
2034
 
                                                 source_details[1]):
2035
 
                        search_specific_files.add(source_details[1])
2036
 
                    # generate the old path; this is needed for stating later
2037
 
                    # as well.
2038
 
                    old_path = source_details[1]
2039
 
                    old_dirname, old_basename = os.path.split(old_path)
2040
 
                    path = pathjoin(entry[0][0], entry[0][1])
2041
 
                    old_entry = state._get_entry(source_index,
2042
 
                                                 path_utf8=old_path)
2043
 
                    # update the source details variable to be the real
2044
 
                    # location.
2045
 
                    if old_entry == (None, None):
2046
 
                        raise errors.CorruptDirstate(state._filename,
2047
 
                            "entry '%s/%s' is considered renamed from %r"
2048
 
                            " but source does not exist\n"
2049
 
                            "entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
2050
 
                    source_details = old_entry[1][source_index]
2051
 
                    source_minikind = source_details[0]
2052
 
                else:
2053
 
                    old_dirname = entry[0][0]
2054
 
                    old_basename = entry[0][1]
2055
 
                    old_path = path = None
2056
 
                if path_info is None:
2057
 
                    # the file is missing on disk, show as removed.
2058
 
                    content_change = True
2059
 
                    target_kind = None
2060
 
                    target_exec = False
2061
 
                else:
2062
 
                    # source and target are both versioned and disk file is present.
2063
 
                    target_kind = path_info[2]
2064
 
                    if target_kind == 'directory':
2065
 
                        if path is None:
2066
 
                            old_path = path = pathjoin(old_dirname, old_basename)
2067
 
                        new_dirname_to_file_id[path] = file_id
2068
 
                        if source_minikind != 'd':
2069
 
                            content_change = True
2070
 
                        else:
2071
 
                            # directories have no fingerprint
2072
 
                            content_change = False
2073
 
                        target_exec = False
2074
 
                    elif target_kind == 'file':
2075
 
                        if source_minikind != 'f':
2076
 
                            content_change = True
2077
 
                        else:
2078
 
                            # We could check the size, but we already have the
2079
 
                            # sha1 hash.
2080
 
                            content_change = (link_or_sha1 != source_details[1])
2081
 
                        # Target details is updated at update_entry time
2082
 
                        if use_filesystem_for_exec:
2083
 
                            # We don't need S_ISREG here, because we are sure
2084
 
                            # we are dealing with a file.
2085
 
                            target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
2086
 
                        else:
2087
 
                            target_exec = target_details[3]
2088
 
                    elif target_kind == 'symlink':
2089
 
                        if source_minikind != 'l':
2090
 
                            content_change = True
2091
 
                        else:
2092
 
                            content_change = (link_or_sha1 != source_details[1])
2093
 
                        target_exec = False
2094
 
                    elif target_kind == 'tree-reference':
2095
 
                        if source_minikind != 't':
2096
 
                            content_change = True
2097
 
                        else:
2098
 
                            content_change = False
2099
 
                        target_exec = False
2100
 
                    else:
2101
 
                        raise Exception, "unknown kind %s" % path_info[2]
2102
 
                if source_minikind == 'd':
2103
 
                    if path is None:
2104
 
                        old_path = path = pathjoin(old_dirname, old_basename)
2105
 
                    old_dirname_to_file_id[old_path] = file_id
2106
 
                # parent id is the entry for the path in the target tree
2107
 
                if old_dirname == last_source_parent[0]:
2108
 
                    source_parent_id = last_source_parent[1]
2109
 
                else:
2110
 
                    try:
2111
 
                        source_parent_id = old_dirname_to_file_id[old_dirname]
2112
 
                    except KeyError:
2113
 
                        source_parent_entry = state._get_entry(source_index,
2114
 
                                                               path_utf8=old_dirname)
2115
 
                        source_parent_id = source_parent_entry[0][2]
2116
 
                    if source_parent_id == entry[0][2]:
2117
 
                        # This is the root, so the parent is None
2118
 
                        source_parent_id = None
2119
 
                    else:
2120
 
                        last_source_parent[0] = old_dirname
2121
 
                        last_source_parent[1] = source_parent_id
2122
 
                new_dirname = entry[0][0]
2123
 
                if new_dirname == last_target_parent[0]:
2124
 
                    target_parent_id = last_target_parent[1]
2125
 
                else:
2126
 
                    try:
2127
 
                        target_parent_id = new_dirname_to_file_id[new_dirname]
2128
 
                    except KeyError:
2129
 
                        # TODO: We don't always need to do the lookup, because the
2130
 
                        #       parent entry will be the same as the source entry.
2131
 
                        target_parent_entry = state._get_entry(target_index,
2132
 
                                                               path_utf8=new_dirname)
2133
 
                        if target_parent_entry == (None, None):
2134
 
                            raise AssertionError(
2135
 
                                "Could not find target parent in wt: %s\nparent of: %s"
2136
 
                                % (new_dirname, entry))
2137
 
                        target_parent_id = target_parent_entry[0][2]
2138
 
                    if target_parent_id == entry[0][2]:
2139
 
                        # This is the root, so the parent is None
2140
 
                        target_parent_id = None
2141
 
                    else:
2142
 
                        last_target_parent[0] = new_dirname
2143
 
                        last_target_parent[1] = target_parent_id
2144
 
 
2145
 
                source_exec = source_details[3]
2146
 
                if (include_unchanged
2147
 
                    or content_change
2148
 
                    or source_parent_id != target_parent_id
2149
 
                    or old_basename != entry[0][1]
2150
 
                    or source_exec != target_exec
2151
 
                    ):
2152
 
                    if old_path is None:
2153
 
                        old_path = path = pathjoin(old_dirname, old_basename)
2154
 
                        old_path_u = utf8_decode(old_path)[0]
2155
 
                        path_u = old_path_u
2156
 
                    else:
2157
 
                        old_path_u = utf8_decode(old_path)[0]
2158
 
                        if old_path == path:
2159
 
                            path_u = old_path_u
2160
 
                        else:
2161
 
                            path_u = utf8_decode(path)[0]
2162
 
                    source_kind = _minikind_to_kind[source_minikind]
2163
 
                    return (entry[0][2],
2164
 
                           (old_path_u, path_u),
2165
 
                           content_change,
2166
 
                           (True, True),
2167
 
                           (source_parent_id, target_parent_id),
2168
 
                           (utf8_decode(old_basename)[0], utf8_decode(entry[0][1])[0]),
2169
 
                           (source_kind, target_kind),
2170
 
                           (source_exec, target_exec))
2171
 
                else:
2172
 
                    return uninteresting
2173
 
            elif source_minikind in 'a' and target_minikind in 'fdlt':
2174
 
                # looks like a new file
2175
 
                path = pathjoin(entry[0][0], entry[0][1])
2176
 
                # parent id is the entry for the path in the target tree
2177
 
                # TODO: these are the same for an entire directory: cache em.
2178
 
                parent_id = state._get_entry(target_index,
2179
 
                                             path_utf8=entry[0][0])[0][2]
2180
 
                if parent_id == entry[0][2]:
2181
 
                    parent_id = None
2182
 
                if path_info is not None:
2183
 
                    # Present on disk:
2184
 
                    if use_filesystem_for_exec:
2185
 
                        # We need S_ISREG here, because we aren't sure if this
2186
 
                        # is a file or not.
2187
 
                        target_exec = bool(
2188
 
                            stat.S_ISREG(path_info[3].st_mode)
2189
 
                            and stat.S_IEXEC & path_info[3].st_mode)
2190
 
                    else:
2191
 
                        target_exec = target_details[3]
2192
 
                    return (entry[0][2],
2193
 
                           (None, utf8_decode(path)[0]),
2194
 
                           True,
2195
 
                           (False, True),
2196
 
                           (None, parent_id),
2197
 
                           (None, utf8_decode(entry[0][1])[0]),
2198
 
                           (None, path_info[2]),
2199
 
                           (None, target_exec))
2200
 
                else:
2201
 
                    # Its a missing file, report it as such.
2202
 
                    return (entry[0][2],
2203
 
                           (None, utf8_decode(path)[0]),
2204
 
                           False,
2205
 
                           (False, True),
2206
 
                           (None, parent_id),
2207
 
                           (None, utf8_decode(entry[0][1])[0]),
2208
 
                           (None, None),
2209
 
                           (None, False))
2210
 
            elif source_minikind in 'fdlt' and target_minikind in 'a':
2211
 
                # unversioned, possibly, or possibly not deleted: we dont care.
2212
 
                # if its still on disk, *and* theres no other entry at this
2213
 
                # path [we dont know this in this routine at the moment -
2214
 
                # perhaps we should change this - then it would be an unknown.
2215
 
                old_path = pathjoin(entry[0][0], entry[0][1])
2216
 
                # parent id is the entry for the path in the target tree
2217
 
                parent_id = state._get_entry(source_index, path_utf8=entry[0][0])[0][2]
2218
 
                if parent_id == entry[0][2]:
2219
 
                    parent_id = None
2220
 
                return (entry[0][2],
2221
 
                       (utf8_decode(old_path)[0], None),
2222
 
                       True,
2223
 
                       (True, False),
2224
 
                       (parent_id, None),
2225
 
                       (utf8_decode(entry[0][1])[0], None),
2226
 
                       (_minikind_to_kind[source_minikind], None),
2227
 
                       (source_details[3], None))
2228
 
            elif source_minikind in 'fdlt' and target_minikind in 'r':
2229
 
                # a rename; could be a true rename, or a rename inherited from
2230
 
                # a renamed parent. TODO: handle this efficiently. Its not
2231
 
                # common case to rename dirs though, so a correct but slow
2232
 
                # implementation will do.
2233
 
                if not osutils.is_inside_any(searched_specific_files, target_details[1]):
2234
 
                    search_specific_files.add(target_details[1])
2235
 
            elif source_minikind in 'ra' and target_minikind in 'ra':
2236
 
                # neither of the selected trees contain this file,
2237
 
                # so skip over it. This is not currently directly tested, but
2238
 
                # is indirectly via test_too_much.TestCommands.test_conflicts.
2239
 
                pass
2240
 
            else:
2241
 
                raise AssertionError("don't know how to compare "
2242
 
                    "source_minikind=%r, target_minikind=%r"
2243
 
                    % (source_minikind, target_minikind))
2244
 
                ## import pdb;pdb.set_trace()
2245
 
            return None
2246
 
 
2247
 
        while search_specific_files:
2248
 
            # TODO: the pending list should be lexically sorted?  the
2249
 
            # interface doesn't require it.
2250
 
            current_root = search_specific_files.pop()
2251
 
            current_root_unicode = current_root.decode('utf8')
2252
 
            searched_specific_files.add(current_root)
2253
 
            # process the entries for this containing directory: the rest will be
2254
 
            # found by their parents recursively.
2255
 
            root_entries = _entries_for_path(current_root)
2256
 
            root_abspath = self.target.abspath(current_root_unicode)
2257
 
            try:
2258
 
                root_stat = os.lstat(root_abspath)
2259
 
            except OSError, e:
2260
 
                if e.errno == errno.ENOENT:
2261
 
                    # the path does not exist: let _process_entry know that.
2262
 
                    root_dir_info = None
2263
 
                else:
2264
 
                    # some other random error: hand it up.
2265
 
                    raise
2266
 
            else:
2267
 
                root_dir_info = ('', current_root,
2268
 
                    osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
2269
 
                    root_abspath)
2270
 
                if root_dir_info[2] == 'directory':
2271
 
                    if self.target._directory_is_tree_reference(
2272
 
                        current_root.decode('utf8')):
2273
 
                        root_dir_info = root_dir_info[:2] + \
2274
 
                            ('tree-reference',) + root_dir_info[3:]
2275
 
 
2276
 
            if not root_entries and not root_dir_info:
2277
 
                # this specified path is not present at all, skip it.
2278
 
                continue
2279
 
            path_handled = False
2280
 
            for entry in root_entries:
2281
 
                result = _process_entry(entry, root_dir_info)
2282
 
                if result is not None:
2283
 
                    path_handled = True
2284
 
                    if result is not uninteresting:
2285
 
                        yield result
2286
 
            if want_unversioned and not path_handled and root_dir_info:
2287
 
                new_executable = bool(
2288
 
                    stat.S_ISREG(root_dir_info[3].st_mode)
2289
 
                    and stat.S_IEXEC & root_dir_info[3].st_mode)
2290
 
                yield (None,
2291
 
                       (None, current_root_unicode),
2292
 
                       True,
2293
 
                       (False, False),
2294
 
                       (None, None),
2295
 
                       (None, splitpath(current_root_unicode)[-1]),
2296
 
                       (None, root_dir_info[2]),
2297
 
                       (None, new_executable)
2298
 
                      )
2299
 
            initial_key = (current_root, '', '')
2300
 
            block_index, _ = state._find_block_index_from_key(initial_key)
2301
 
            if block_index == 0:
2302
 
                # we have processed the total root already, but because the
2303
 
                # initial key matched it we should skip it here.
2304
 
                block_index +=1
2305
 
            if root_dir_info and root_dir_info[2] == 'tree-reference':
2306
 
                current_dir_info = None
2307
 
            else:
2308
 
                dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
2309
 
                try:
2310
 
                    current_dir_info = dir_iterator.next()
2311
 
                except OSError, e:
2312
 
                    # on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
2313
 
                    # python 2.5 has e.errno == EINVAL,
2314
 
                    #            and e.winerror == ERROR_DIRECTORY
2315
 
                    e_winerror = getattr(e, 'winerror', None)
2316
 
                    win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
2317
 
                    # there may be directories in the inventory even though
2318
 
                    # this path is not a file on disk: so mark it as end of
2319
 
                    # iterator
2320
 
                    if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
2321
 
                        current_dir_info = None
2322
 
                    elif (sys.platform == 'win32'
2323
 
                          and (e.errno in win_errors
2324
 
                               or e_winerror in win_errors)):
2325
 
                        current_dir_info = None
2326
 
                    else:
2327
 
                        raise
2328
 
                else:
2329
 
                    if current_dir_info[0][0] == '':
2330
 
                        # remove .bzr from iteration
2331
 
                        bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
2332
 
                        if current_dir_info[1][bzr_index][0] != '.bzr':
2333
 
                            raise AssertionError()
2334
 
                        del current_dir_info[1][bzr_index]
2335
 
            # walk until both the directory listing and the versioned metadata
2336
 
            # are exhausted. 
2337
 
            if (block_index < len(state._dirblocks) and
2338
 
                osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2339
 
                current_block = state._dirblocks[block_index]
2340
 
            else:
2341
 
                current_block = None
2342
 
            while (current_dir_info is not None or
2343
 
                   current_block is not None):
2344
 
                if (current_dir_info and current_block
2345
 
                    and current_dir_info[0][0] != current_block[0]):
2346
 
                    if cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
2347
 
                        # filesystem data refers to paths not covered by the dirblock.
2348
 
                        # this has two possibilities:
2349
 
                        # A) it is versioned but empty, so there is no block for it
2350
 
                        # B) it is not versioned.
2351
 
 
2352
 
                        # if (A) then we need to recurse into it to check for
2353
 
                        # new unknown files or directories.
2354
 
                        # if (B) then we should ignore it, because we don't
2355
 
                        # recurse into unknown directories.
2356
 
                        path_index = 0
2357
 
                        while path_index < len(current_dir_info[1]):
2358
 
                                current_path_info = current_dir_info[1][path_index]
2359
 
                                if want_unversioned:
2360
 
                                    if current_path_info[2] == 'directory':
2361
 
                                        if self.target._directory_is_tree_reference(
2362
 
                                            current_path_info[0].decode('utf8')):
2363
 
                                            current_path_info = current_path_info[:2] + \
2364
 
                                                ('tree-reference',) + current_path_info[3:]
2365
 
                                    new_executable = bool(
2366
 
                                        stat.S_ISREG(current_path_info[3].st_mode)
2367
 
                                        and stat.S_IEXEC & current_path_info[3].st_mode)
2368
 
                                    yield (None,
2369
 
                                        (None, utf8_decode(current_path_info[0])[0]),
2370
 
                                        True,
2371
 
                                        (False, False),
2372
 
                                        (None, None),
2373
 
                                        (None, utf8_decode(current_path_info[1])[0]),
2374
 
                                        (None, current_path_info[2]),
2375
 
                                        (None, new_executable))
2376
 
                                # dont descend into this unversioned path if it is
2377
 
                                # a dir
2378
 
                                if current_path_info[2] in ('directory',
2379
 
                                                            'tree-reference'):
2380
 
                                    del current_dir_info[1][path_index]
2381
 
                                    path_index -= 1
2382
 
                                path_index += 1
2383
 
 
2384
 
                        # This dir info has been handled, go to the next
2385
 
                        try:
2386
 
                            current_dir_info = dir_iterator.next()
2387
 
                        except StopIteration:
2388
 
                            current_dir_info = None
2389
 
                    else:
2390
 
                        # We have a dirblock entry for this location, but there
2391
 
                        # is no filesystem path for this. This is most likely
2392
 
                        # because a directory was removed from the disk.
2393
 
                        # We don't have to report the missing directory,
2394
 
                        # because that should have already been handled, but we
2395
 
                        # need to handle all of the files that are contained
2396
 
                        # within.
2397
 
                        for current_entry in current_block[1]:
2398
 
                            # entry referring to file not present on disk.
2399
 
                            # advance the entry only, after processing.
2400
 
                            result = _process_entry(current_entry, None)
2401
 
                            if result is not None:
2402
 
                                if result is not uninteresting:
2403
 
                                    yield result
2404
 
                        block_index +=1
2405
 
                        if (block_index < len(state._dirblocks) and
2406
 
                            osutils.is_inside(current_root,
2407
 
                                              state._dirblocks[block_index][0])):
2408
 
                            current_block = state._dirblocks[block_index]
2409
 
                        else:
2410
 
                            current_block = None
2411
 
                    continue
2412
 
                entry_index = 0
2413
 
                if current_block and entry_index < len(current_block[1]):
2414
 
                    current_entry = current_block[1][entry_index]
2415
 
                else:
2416
 
                    current_entry = None
2417
 
                advance_entry = True
2418
 
                path_index = 0
2419
 
                if current_dir_info and path_index < len(current_dir_info[1]):
2420
 
                    current_path_info = current_dir_info[1][path_index]
2421
 
                    if current_path_info[2] == 'directory':
2422
 
                        if self.target._directory_is_tree_reference(
2423
 
                            current_path_info[0].decode('utf8')):
2424
 
                            current_path_info = current_path_info[:2] + \
2425
 
                                ('tree-reference',) + current_path_info[3:]
2426
 
                else:
2427
 
                    current_path_info = None
2428
 
                advance_path = True
2429
 
                path_handled = False
2430
 
                while (current_entry is not None or
2431
 
                    current_path_info is not None):
2432
 
                    if current_entry is None:
2433
 
                        # the check for path_handled when the path is adnvaced
2434
 
                        # will yield this path if needed.
2435
 
                        pass
2436
 
                    elif current_path_info is None:
2437
 
                        # no path is fine: the per entry code will handle it.
2438
 
                        result = _process_entry(current_entry, current_path_info)
2439
 
                        if result is not None:
2440
 
                            if result is not uninteresting:
2441
 
                                yield result
2442
 
                    elif (current_entry[0][1] != current_path_info[1]
2443
 
                          or current_entry[1][target_index][0] in 'ar'):
2444
 
                        # The current path on disk doesn't match the dirblock
2445
 
                        # record. Either the dirblock is marked as absent, or
2446
 
                        # the file on disk is not present at all in the
2447
 
                        # dirblock. Either way, report about the dirblock
2448
 
                        # entry, and let other code handle the filesystem one.
2449
 
 
2450
 
                        # Compare the basename for these files to determine
2451
 
                        # which comes first
2452
 
                        if current_path_info[1] < current_entry[0][1]:
2453
 
                            # extra file on disk: pass for now, but only
2454
 
                            # increment the path, not the entry
2455
 
                            advance_entry = False
2456
 
                        else:
2457
 
                            # entry referring to file not present on disk.
2458
 
                            # advance the entry only, after processing.
2459
 
                            result = _process_entry(current_entry, None)
2460
 
                            if result is not None:
2461
 
                                if result is not uninteresting:
2462
 
                                    yield result
2463
 
                            advance_path = False
2464
 
                    else:
2465
 
                        result = _process_entry(current_entry, current_path_info)
2466
 
                        if result is not None:
2467
 
                            path_handled = True
2468
 
                            if result is not uninteresting:
2469
 
                                yield result
2470
 
                    if advance_entry and current_entry is not None:
2471
 
                        entry_index += 1
2472
 
                        if entry_index < len(current_block[1]):
2473
 
                            current_entry = current_block[1][entry_index]
2474
 
                        else:
2475
 
                            current_entry = None
2476
 
                    else:
2477
 
                        advance_entry = True # reset the advance flaga
2478
 
                    if advance_path and current_path_info is not None:
2479
 
                        if not path_handled:
2480
 
                            # unversioned in all regards
2481
 
                            if want_unversioned:
2482
 
                                new_executable = bool(
2483
 
                                    stat.S_ISREG(current_path_info[3].st_mode)
2484
 
                                    and stat.S_IEXEC & current_path_info[3].st_mode)
2485
 
                                try:
2486
 
                                    relpath_unicode = utf8_decode(current_path_info[0])[0]
2487
 
                                except UnicodeDecodeError:
2488
 
                                    raise errors.BadFilenameEncoding(
2489
 
                                        current_path_info[0], osutils._fs_enc)
2490
 
                                yield (None,
2491
 
                                    (None, relpath_unicode),
2492
 
                                    True,
2493
 
                                    (False, False),
2494
 
                                    (None, None),
2495
 
                                    (None, utf8_decode(current_path_info[1])[0]),
2496
 
                                    (None, current_path_info[2]),
2497
 
                                    (None, new_executable))
2498
 
                            # dont descend into this unversioned path if it is
2499
 
                            # a dir
2500
 
                            if current_path_info[2] in ('directory'):
2501
 
                                del current_dir_info[1][path_index]
2502
 
                                path_index -= 1
2503
 
                        # dont descend the disk iterator into any tree 
2504
 
                        # paths.
2505
 
                        if current_path_info[2] == 'tree-reference':
2506
 
                            del current_dir_info[1][path_index]
2507
 
                            path_index -= 1
2508
 
                        path_index += 1
2509
 
                        if path_index < len(current_dir_info[1]):
2510
 
                            current_path_info = current_dir_info[1][path_index]
2511
 
                            if current_path_info[2] == 'directory':
2512
 
                                if self.target._directory_is_tree_reference(
2513
 
                                    current_path_info[0].decode('utf8')):
2514
 
                                    current_path_info = current_path_info[:2] + \
2515
 
                                        ('tree-reference',) + current_path_info[3:]
2516
 
                        else:
2517
 
                            current_path_info = None
2518
 
                        path_handled = False
2519
 
                    else:
2520
 
                        advance_path = True # reset the advance flagg.
2521
 
                if current_block is not None:
2522
 
                    block_index += 1
2523
 
                    if (block_index < len(state._dirblocks) and
2524
 
                        osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2525
 
                        current_block = state._dirblocks[block_index]
2526
 
                    else:
2527
 
                        current_block = None
2528
 
                if current_dir_info is not None:
2529
 
                    try:
2530
 
                        current_dir_info = dir_iterator.next()
2531
 
                    except StopIteration:
2532
 
                        current_dir_info = None
 
1931
        iter_changes = self.target._iter_changes(include_unchanged,
 
1932
            use_filesystem_for_exec, search_specific_files, state,
 
1933
            source_index, target_index, want_unversioned, self.target)
 
1934
        return iter_changes.iter_changes()
2533
1935
 
2534
1936
    @staticmethod
2535
1937
    def is_compatible(source, target):