/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

  • Committer: Ian Clatworthy
  • Date: 2008-12-15 06:18:29 UTC
  • mfrom: (3905 +trunk)
  • mto: (3586.1.23 views-ui)
  • mto: This revision was merged to the branch mainline in revision 4030.
  • Revision ID: ian.clatworthy@canonical.com-20081215061829-c8qwa93g71u9fsh5
merge bzr.dev 3905

Show diffs side-by-side

added added

removed removed

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