~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-06-05 04:05:05 UTC
  • mfrom: (3473.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080605040505-i9kqxg2fps2qjdi0
Add the 'alias' command (Tim Penhey)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
 
101
108
class WorkingTree4(WorkingTree3):
102
109
    """This is the Format 4 working tree.
103
110
 
141
148
        #-------------
142
149
        self._setup_directory_is_tree_reference()
143
150
        self._detect_case_handling()
144
 
        self._rules_searcher = None
145
 
        #--- allow tests to select the dirstate iter_changes implementation
146
 
        self._iter_changes = dirstate._process_entry
147
151
 
148
152
    @needs_tree_write_lock
149
153
    def _add(self, files, ids, kinds):
404
408
                    return None
405
409
                else:
406
410
                    raise
407
 
        link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
408
 
            stat_value=stat_value)
 
411
        link_or_sha1 = state.update_entry(entry, file_abspath,
 
412
                                          stat_value=stat_value)
409
413
        if entry[1][0][0] == 'f':
410
 
            if link_or_sha1 is None:
411
 
                file_obj, statvalue = self.get_file_with_stat(file_id, path)
412
 
                try:
413
 
                    sha1 = osutils.sha_file(file_obj)
414
 
                finally:
415
 
                    file_obj.close()
416
 
                self._observed_sha1(file_id, path, (sha1, statvalue))
417
 
                return sha1
418
 
            else:
419
 
                return link_or_sha1
 
414
            return link_or_sha1
420
415
        return None
421
416
 
422
417
    def _get_inventory(self):
533
528
        return iter(result)
534
529
 
535
530
    def iter_references(self):
536
 
        if not self._repo_supports_tree_reference:
537
 
            # When the repo doesn't support references, we will have nothing to
538
 
            # return
539
 
            return
540
531
        for key, tree_details in self.current_dirstate()._iter_entries():
541
532
            if tree_details[0][0] in ('a', 'r'): # absent, relocated
542
533
                # not relevant to the working tree
544
535
            if not key[1]:
545
536
                # the root is not a reference.
546
537
                continue
547
 
            relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
 
538
            path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
548
539
            try:
549
 
                if self._kind(relpath) == 'tree-reference':
550
 
                    yield relpath, key[2]
 
540
                if self._kind(path) == 'tree-reference':
 
541
                    yield path, key[2]
551
542
            except errors.NoSuchFile:
552
543
                # path is missing on disk.
553
544
                continue
554
545
 
555
 
    def _observed_sha1(self, file_id, path, (sha1, statvalue)):
556
 
        """See MutableTree._observed_sha1."""
557
 
        state = self.current_dirstate()
558
 
        entry = self._get_entry(file_id=file_id, path=path)
559
 
        state._observed_sha1(entry, sha1, statvalue)
560
 
 
561
546
    def kind(self, file_id):
562
547
        """Return the kind of a file.
563
548
 
1120
1105
                real_trees.append((rev_id, tree))
1121
1106
            else:
1122
1107
                real_trees.append((rev_id,
1123
 
                    self.branch.repository.revision_tree(
1124
 
                        _mod_revision.NULL_REVISION)))
 
1108
                    self.branch.repository.revision_tree(None)))
1125
1109
                ghosts.append(rev_id)
1126
1110
            accepted_revisions.add(rev_id)
1127
1111
        dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1307
1291
 
1308
1292
    upgrade_recommended = False
1309
1293
 
1310
 
    _tree_class = WorkingTree4
1311
 
 
1312
1294
    def get_format_string(self):
1313
1295
        """See WorkingTreeFormat.get_format_string()."""
1314
1296
        return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1352
1334
        state = dirstate.DirState.initialize(local_path)
1353
1335
        state.unlock()
1354
1336
        del state
1355
 
        wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
 
1337
        wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1356
1338
                         branch,
1357
1339
                         _format=self,
1358
1340
                         _bzrdir=a_bzrdir,
1360
1342
        wt._new_tree()
1361
1343
        wt.lock_tree_write()
1362
1344
        try:
1363
 
            self._init_custom_control_files(wt)
1364
1345
            if revision_id in (None, NULL_REVISION):
1365
1346
                if branch.repository.supports_rich_root():
1366
1347
                    wt._set_root_id(generate_ids.gen_root_id())
1392
1373
                if basis_root_id is not None:
1393
1374
                    wt._set_root_id(basis_root_id)
1394
1375
                    wt.flush()
1395
 
                # delta_from_tree is safe even for DirStateRevisionTrees,
1396
 
                # because wt4.apply_inventory_delta does not mutate the input
1397
 
                # inventory entries.
1398
1376
                transform.build_tree(basis, wt, accelerator_tree,
1399
 
                                     hardlink=hardlink, delta_from_tree=True)
 
1377
                                     hardlink=hardlink)
1400
1378
            finally:
1401
1379
                basis.unlock()
1402
1380
        finally:
1404
1382
            wt.unlock()
1405
1383
        return wt
1406
1384
 
1407
 
    def _init_custom_control_files(self, wt):
1408
 
        """Subclasses with custom control files should override this method.
1409
 
        
1410
 
        The working tree and control files are locked for writing when this
1411
 
        method is called.
1412
 
        
1413
 
        :param wt: the WorkingTree object
1414
 
        """
1415
 
 
1416
1385
    def _open(self, a_bzrdir, control_files):
1417
1386
        """Open the tree itself.
1418
1387
 
1419
1388
        :param a_bzrdir: the dir for the tree.
1420
1389
        :param control_files: the control files for the tree.
1421
1390
        """
1422
 
        return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
 
1391
        return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1423
1392
                           branch=a_bzrdir.open_branch(),
1424
1393
                           _format=self,
1425
1394
                           _bzrdir=a_bzrdir,
1443
1412
        self._inventory = None
1444
1413
        self._locked = 0
1445
1414
        self._dirstate_locked = False
1446
 
        self._repo_supports_tree_reference = getattr(
1447
 
            repository._format, "supports_tree_reference",
1448
 
            False)
1449
1415
 
1450
1416
    def __repr__(self):
1451
1417
        return "<%s of %s in %s>" % \
1454
1420
    def annotate_iter(self, file_id,
1455
1421
                      default_revision=_mod_revision.CURRENT_REVISION):
1456
1422
        """See Tree.annotate_iter"""
1457
 
        text_key = (file_id, self.inventory[file_id].revision)
1458
 
        annotations = self._repository.texts.annotate(text_key)
1459
 
        return [(key[-1], line) for (key, line) in annotations]
 
1423
        w = self._get_weave(file_id)
 
1424
        return w.annotate(self.inventory[file_id].revision)
1460
1425
 
1461
1426
    def _get_ancestors(self, default_revision):
1462
1427
        return set(self._repository.get_ancestry(self._revision_id,
1491
1456
        path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
1492
1457
        return path_utf8.decode('utf8')
1493
1458
 
1494
 
    def iter_references(self):
1495
 
        if not self._repo_supports_tree_reference:
1496
 
            # When the repo doesn't support references, we will have nothing to
1497
 
            # return
1498
 
            return iter([])
1499
 
        # Otherwise, fall back to the default implementation
1500
 
        return super(DirStateRevisionTree, self).iter_references()
1501
 
 
1502
1459
    def _get_parent_index(self):
1503
1460
        """Return the index in the dirstate referenced by this tree."""
1504
1461
        return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1622
1579
            return parent_details[1]
1623
1580
        return None
1624
1581
 
 
1582
    def _get_weave(self, file_id):
 
1583
        return self._repository.weave_store.get_weave(file_id,
 
1584
                self._repository.get_transaction())
 
1585
 
1625
1586
    def get_file(self, file_id, path=None):
1626
1587
        return StringIO(self.get_file_text(file_id))
1627
1588
 
 
1589
    def get_file_lines(self, file_id):
 
1590
        entry = self._get_entry(file_id=file_id)[1]
 
1591
        if entry is None:
 
1592
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
1593
        return self._get_weave(file_id).get_lines(entry[1][4])
 
1594
 
1628
1595
    def get_file_size(self, file_id):
1629
1596
        """See Tree.get_file_size"""
1630
1597
        return self.inventory[file_id].text_size
1631
1598
 
1632
 
    def get_file_text(self, file_id, path=None):
1633
 
        return list(self.iter_files_bytes([(file_id, None)]))[0][1]
 
1599
    def get_file_text(self, file_id):
 
1600
        return ''.join(self.get_file_lines(file_id))
1634
1601
 
1635
1602
    def get_reference_revision(self, file_id, path=None):
1636
1603
        return self.inventory[file_id].reference_revision
1755
1722
                self._dirstate_locked = False
1756
1723
            self._repository.unlock()
1757
1724
 
1758
 
    @needs_read_lock
1759
 
    def supports_tree_reference(self):
1760
 
        return self._repo_supports_tree_reference
1761
 
 
1762
1725
    def walkdirs(self, prefix=""):
1763
1726
        # TODO: jam 20070215 This is the lazy way by using the RevisionTree
1764
1727
        # implementation based on an inventory.  
1817
1780
        target.set_parent_ids([revid])
1818
1781
        return target.basis_tree(), target
1819
1782
 
1820
 
    @classmethod
1821
 
    def make_source_parent_tree_python_dirstate(klass, test_case, source, target):
1822
 
        result = klass.make_source_parent_tree(source, target)
1823
 
        result[1]._iter_changes = dirstate.ProcessEntryPython
1824
 
        return result
1825
 
 
1826
 
    @classmethod
1827
 
    def make_source_parent_tree_compiled_dirstate(klass, test_case, source, target):
1828
 
        from bzrlib.tests.test__dirstate_helpers import \
1829
 
            CompiledDirstateHelpersFeature
1830
 
        if not CompiledDirstateHelpersFeature.available():
1831
 
            from bzrlib.tests import UnavailableFeature
1832
 
            raise UnavailableFeature(CompiledDirstateHelpersFeature)
1833
 
        from bzrlib._dirstate_helpers_c import ProcessEntryC
1834
 
        result = klass.make_source_parent_tree(source, target)
1835
 
        result[1]._iter_changes = ProcessEntryC
1836
 
        return result
1837
 
 
1838
1783
    _matching_from_tree_format = WorkingTreeFormat4()
1839
1784
    _matching_to_tree_format = WorkingTreeFormat4()
1840
 
 
1841
 
    @classmethod
1842
 
    def _test_mutable_trees_to_test_trees(klass, test_case, source, target):
1843
 
        # This method shouldn't be called, because we have python and C
1844
 
        # specific flavours.
1845
 
        raise NotImplementedError
 
1785
    _test_mutable_trees_to_test_trees = make_source_parent_tree
1846
1786
 
1847
1787
    def iter_changes(self, include_unchanged=False,
1848
1788
                      specific_files=None, pb=None, extra_trees=[],
1866
1806
            output. An unversioned file is defined as one with (False, False)
1867
1807
            for the versioned pair.
1868
1808
        """
 
1809
        utf8_decode = cache_utf8._utf8_decode
 
1810
        _minikind_to_kind = dirstate.DirState._minikind_to_kind
 
1811
        cmp_by_dirs = dirstate.cmp_by_dirs
1869
1812
        # NB: show_status depends on being able to pass in non-versioned files
1870
1813
        # and report them as unknown
1871
1814
        # TODO: handle extra trees in the dirstate.
1872
1815
        if (extra_trees or specific_files == []):
1873
1816
            # we can't fast-path these cases (yet)
1874
 
            return super(InterDirStateTree, self).iter_changes(
 
1817
            for f in super(InterDirStateTree, self).iter_changes(
1875
1818
                include_unchanged, specific_files, pb, extra_trees,
1876
 
                require_versioned, want_unversioned=want_unversioned)
 
1819
                require_versioned, want_unversioned=want_unversioned):
 
1820
                yield f
 
1821
            return
1877
1822
        parent_ids = self.target.get_parent_ids()
1878
1823
        if not (self.source._revision_id in parent_ids
1879
1824
                or self.source._revision_id == NULL_REVISION):
1896
1841
        if specific_files:
1897
1842
            specific_files_utf8 = set()
1898
1843
            for path in specific_files:
1899
 
                # Note, if there are many specific files, using cache_utf8
1900
 
                # would be good here.
1901
1844
                specific_files_utf8.add(path.encode('utf8'))
1902
1845
            specific_files = specific_files_utf8
1903
1846
        else:
1904
1847
            specific_files = set([''])
1905
1848
        # -- specific_files is now a utf8 path set --
1906
 
        search_specific_files = set()
1907
1849
        # -- get the state object and prepare it.
1908
1850
        state = self.target.current_dirstate()
1909
1851
        state._read_dirblocks_if_needed()
 
1852
        def _entries_for_path(path):
 
1853
            """Return a list with all the entries that match path for all ids.
 
1854
            """
 
1855
            dirname, basename = os.path.split(path)
 
1856
            key = (dirname, basename, '')
 
1857
            block_index, present = state._find_block_index_from_key(key)
 
1858
            if not present:
 
1859
                # the block which should contain path is absent.
 
1860
                return []
 
1861
            result = []
 
1862
            block = state._dirblocks[block_index][1]
 
1863
            entry_index, _ = state._find_entry_index(key, block)
 
1864
            # we may need to look at multiple entries at this path: walk while the specific_files match.
 
1865
            while (entry_index < len(block) and
 
1866
                block[entry_index][0][0:2] == key[0:2]):
 
1867
                result.append(block[entry_index])
 
1868
                entry_index += 1
 
1869
            return result
1910
1870
        if require_versioned:
1911
1871
            # -- check all supplied paths are versioned in a search tree. --
1912
1872
            all_versioned = True
1913
1873
            for path in specific_files:
1914
 
                path_entries = state._entries_for_path(path)
 
1874
                path_entries = _entries_for_path(path)
1915
1875
                if not path_entries:
1916
1876
                    # this specified path is not present at all: error
1917
1877
                    all_versioned = False
1933
1893
            if not all_versioned:
1934
1894
                raise errors.PathsNotVersionedError(specific_files)
1935
1895
        # -- remove redundancy in supplied specific_files to prevent over-scanning --
 
1896
        search_specific_files = set()
1936
1897
        for path in specific_files:
1937
1898
            other_specific_files = specific_files.difference(set([path]))
1938
1899
            if not osutils.is_inside_any(other_specific_files, path):
1939
1900
                # this is a top level path, we must check it.
1940
1901
                search_specific_files.add(path)
 
1902
        # sketch: 
 
1903
        # compare source_index and target_index at or under each element of search_specific_files.
 
1904
        # follow the following comparison table. Note that we only want to do diff operations when
 
1905
        # the target is fdl because thats when the walkdirs logic will have exposed the pathinfo 
 
1906
        # for the target.
 
1907
        # cases:
 
1908
        # 
 
1909
        # Source | Target | disk | action
 
1910
        #   r    | fdlt   |      | add source to search, add id path move and perform
 
1911
        #        |        |      | diff check on source-target
 
1912
        #   r    | fdlt   |  a   | dangling file that was present in the basis. 
 
1913
        #        |        |      | ???
 
1914
        #   r    |  a     |      | add source to search
 
1915
        #   r    |  a     |  a   | 
 
1916
        #   r    |  r     |      | this path is present in a non-examined tree, skip.
 
1917
        #   r    |  r     |  a   | this path is present in a non-examined tree, skip.
 
1918
        #   a    | fdlt   |      | add new id
 
1919
        #   a    | fdlt   |  a   | dangling locally added file, skip
 
1920
        #   a    |  a     |      | not present in either tree, skip
 
1921
        #   a    |  a     |  a   | not present in any tree, skip
 
1922
        #   a    |  r     |      | not present in either tree at this path, skip as it
 
1923
        #        |        |      | may not be selected by the users list of paths.
 
1924
        #   a    |  r     |  a   | not present in either tree at this path, skip as it
 
1925
        #        |        |      | may not be selected by the users list of paths.
 
1926
        #  fdlt  | fdlt   |      | content in both: diff them
 
1927
        #  fdlt  | fdlt   |  a   | deleted locally, but not unversioned - show as deleted ?
 
1928
        #  fdlt  |  a     |      | unversioned: output deleted id for now
 
1929
        #  fdlt  |  a     |  a   | unversioned and deleted: output deleted id
 
1930
        #  fdlt  |  r     |      | relocated in this tree, so add target to search.
 
1931
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
 
1932
        #        |        |      | this id at the other path.
 
1933
        #  fdlt  |  r     |  a   | relocated in this tree, so add target to search.
 
1934
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
 
1935
        #        |        |      | this id at the other path.
 
1936
 
 
1937
        # for all search_indexs in each path at or under each element of
 
1938
        # search_specific_files, if the detail is relocated: add the id, and add the
 
1939
        # relocated path as one to search if its not searched already. If the
 
1940
        # detail is not relocated, add the id.
 
1941
        searched_specific_files = set()
 
1942
        NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
 
1943
        # Using a list so that we can access the values and change them in
 
1944
        # nested scope. Each one is [path, file_id, entry]
 
1945
        last_source_parent = [None, None]
 
1946
        last_target_parent = [None, None]
1941
1947
 
1942
1948
        use_filesystem_for_exec = (sys.platform != 'win32')
1943
 
        iter_changes = self.target._iter_changes(include_unchanged,
1944
 
            use_filesystem_for_exec, search_specific_files, state,
1945
 
            source_index, target_index, want_unversioned, self.target)
1946
 
        return iter_changes.iter_changes()
 
1949
 
 
1950
        # Just a sentry, so that _process_entry can say that this
 
1951
        # record is handled, but isn't interesting to process (unchanged)
 
1952
        uninteresting = object()
 
1953
 
 
1954
 
 
1955
        old_dirname_to_file_id = {}
 
1956
        new_dirname_to_file_id = {}
 
1957
        # TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
 
1958
        #       keeping a cache of directories that we have seen.
 
1959
 
 
1960
        def _process_entry(entry, path_info):
 
1961
            """Compare an entry and real disk to generate delta information.
 
1962
 
 
1963
            :param path_info: top_relpath, basename, kind, lstat, abspath for
 
1964
                the path of entry. If None, then the path is considered absent.
 
1965
                (Perhaps we should pass in a concrete entry for this ?)
 
1966
                Basename is returned as a utf8 string because we expect this
 
1967
                tuple will be ignored, and don't want to take the time to
 
1968
                decode.
 
1969
            :return: None if these don't match
 
1970
                     A tuple of information about the change, or
 
1971
                     the object 'uninteresting' if these match, but are
 
1972
                     basically identical.
 
1973
            """
 
1974
            if source_index is None:
 
1975
                source_details = NULL_PARENT_DETAILS
 
1976
            else:
 
1977
                source_details = entry[1][source_index]
 
1978
            target_details = entry[1][target_index]
 
1979
            target_minikind = target_details[0]
 
1980
            if path_info is not None and target_minikind in 'fdlt':
 
1981
                if not (target_index == 0):
 
1982
                    raise AssertionError()
 
1983
                link_or_sha1 = state.update_entry(entry, abspath=path_info[4],
 
1984
                                                  stat_value=path_info[3])
 
1985
                # The entry may have been modified by update_entry
 
1986
                target_details = entry[1][target_index]
 
1987
                target_minikind = target_details[0]
 
1988
            else:
 
1989
                link_or_sha1 = None
 
1990
            file_id = entry[0][2]
 
1991
            source_minikind = source_details[0]
 
1992
            if source_minikind in 'fdltr' and target_minikind in 'fdlt':
 
1993
                # claimed content in both: diff
 
1994
                #   r    | fdlt   |      | add source to search, add id path move and perform
 
1995
                #        |        |      | diff check on source-target
 
1996
                #   r    | fdlt   |  a   | dangling file that was present in the basis.
 
1997
                #        |        |      | ???
 
1998
                if source_minikind in 'r':
 
1999
                    # add the source to the search path to find any children it
 
2000
                    # has.  TODO ? : only add if it is a container ?
 
2001
                    if not osutils.is_inside_any(searched_specific_files,
 
2002
                                                 source_details[1]):
 
2003
                        search_specific_files.add(source_details[1])
 
2004
                    # generate the old path; this is needed for stating later
 
2005
                    # as well.
 
2006
                    old_path = source_details[1]
 
2007
                    old_dirname, old_basename = os.path.split(old_path)
 
2008
                    path = pathjoin(entry[0][0], entry[0][1])
 
2009
                    old_entry = state._get_entry(source_index,
 
2010
                                                 path_utf8=old_path)
 
2011
                    # update the source details variable to be the real
 
2012
                    # location.
 
2013
                    if old_entry == (None, None):
 
2014
                        raise errors.CorruptDirstate(state._filename,
 
2015
                            "entry '%s/%s' is considered renamed from %r"
 
2016
                            " but source does not exist\n"
 
2017
                            "entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
 
2018
                    source_details = old_entry[1][source_index]
 
2019
                    source_minikind = source_details[0]
 
2020
                else:
 
2021
                    old_dirname = entry[0][0]
 
2022
                    old_basename = entry[0][1]
 
2023
                    old_path = path = None
 
2024
                if path_info is None:
 
2025
                    # the file is missing on disk, show as removed.
 
2026
                    content_change = True
 
2027
                    target_kind = None
 
2028
                    target_exec = False
 
2029
                else:
 
2030
                    # source and target are both versioned and disk file is present.
 
2031
                    target_kind = path_info[2]
 
2032
                    if target_kind == 'directory':
 
2033
                        if path is None:
 
2034
                            old_path = path = pathjoin(old_dirname, old_basename)
 
2035
                        new_dirname_to_file_id[path] = file_id
 
2036
                        if source_minikind != 'd':
 
2037
                            content_change = True
 
2038
                        else:
 
2039
                            # directories have no fingerprint
 
2040
                            content_change = False
 
2041
                        target_exec = False
 
2042
                    elif target_kind == 'file':
 
2043
                        if source_minikind != 'f':
 
2044
                            content_change = True
 
2045
                        else:
 
2046
                            # We could check the size, but we already have the
 
2047
                            # sha1 hash.
 
2048
                            content_change = (link_or_sha1 != source_details[1])
 
2049
                        # Target details is updated at update_entry time
 
2050
                        if use_filesystem_for_exec:
 
2051
                            # We don't need S_ISREG here, because we are sure
 
2052
                            # we are dealing with a file.
 
2053
                            target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
 
2054
                        else:
 
2055
                            target_exec = target_details[3]
 
2056
                    elif target_kind == 'symlink':
 
2057
                        if source_minikind != 'l':
 
2058
                            content_change = True
 
2059
                        else:
 
2060
                            content_change = (link_or_sha1 != source_details[1])
 
2061
                        target_exec = False
 
2062
                    elif target_kind == 'tree-reference':
 
2063
                        if source_minikind != 't':
 
2064
                            content_change = True
 
2065
                        else:
 
2066
                            content_change = False
 
2067
                        target_exec = False
 
2068
                    else:
 
2069
                        raise Exception, "unknown kind %s" % path_info[2]
 
2070
                if source_minikind == 'd':
 
2071
                    if path is None:
 
2072
                        old_path = path = pathjoin(old_dirname, old_basename)
 
2073
                    old_dirname_to_file_id[old_path] = file_id
 
2074
                # parent id is the entry for the path in the target tree
 
2075
                if old_dirname == last_source_parent[0]:
 
2076
                    source_parent_id = last_source_parent[1]
 
2077
                else:
 
2078
                    try:
 
2079
                        source_parent_id = old_dirname_to_file_id[old_dirname]
 
2080
                    except KeyError:
 
2081
                        source_parent_entry = state._get_entry(source_index,
 
2082
                                                               path_utf8=old_dirname)
 
2083
                        source_parent_id = source_parent_entry[0][2]
 
2084
                    if source_parent_id == entry[0][2]:
 
2085
                        # This is the root, so the parent is None
 
2086
                        source_parent_id = None
 
2087
                    else:
 
2088
                        last_source_parent[0] = old_dirname
 
2089
                        last_source_parent[1] = source_parent_id
 
2090
                new_dirname = entry[0][0]
 
2091
                if new_dirname == last_target_parent[0]:
 
2092
                    target_parent_id = last_target_parent[1]
 
2093
                else:
 
2094
                    try:
 
2095
                        target_parent_id = new_dirname_to_file_id[new_dirname]
 
2096
                    except KeyError:
 
2097
                        # TODO: We don't always need to do the lookup, because the
 
2098
                        #       parent entry will be the same as the source entry.
 
2099
                        target_parent_entry = state._get_entry(target_index,
 
2100
                                                               path_utf8=new_dirname)
 
2101
                        if target_parent_entry == (None, None):
 
2102
                            raise AssertionError(
 
2103
                                "Could not find target parent in wt: %s\nparent of: %s"
 
2104
                                % (new_dirname, entry))
 
2105
                        target_parent_id = target_parent_entry[0][2]
 
2106
                    if target_parent_id == entry[0][2]:
 
2107
                        # This is the root, so the parent is None
 
2108
                        target_parent_id = None
 
2109
                    else:
 
2110
                        last_target_parent[0] = new_dirname
 
2111
                        last_target_parent[1] = target_parent_id
 
2112
 
 
2113
                source_exec = source_details[3]
 
2114
                if (include_unchanged
 
2115
                    or content_change
 
2116
                    or source_parent_id != target_parent_id
 
2117
                    or old_basename != entry[0][1]
 
2118
                    or source_exec != target_exec
 
2119
                    ):
 
2120
                    if old_path is None:
 
2121
                        old_path = path = pathjoin(old_dirname, old_basename)
 
2122
                        old_path_u = utf8_decode(old_path)[0]
 
2123
                        path_u = old_path_u
 
2124
                    else:
 
2125
                        old_path_u = utf8_decode(old_path)[0]
 
2126
                        if old_path == path:
 
2127
                            path_u = old_path_u
 
2128
                        else:
 
2129
                            path_u = utf8_decode(path)[0]
 
2130
                    source_kind = _minikind_to_kind[source_minikind]
 
2131
                    return (entry[0][2],
 
2132
                           (old_path_u, path_u),
 
2133
                           content_change,
 
2134
                           (True, True),
 
2135
                           (source_parent_id, target_parent_id),
 
2136
                           (utf8_decode(old_basename)[0], utf8_decode(entry[0][1])[0]),
 
2137
                           (source_kind, target_kind),
 
2138
                           (source_exec, target_exec))
 
2139
                else:
 
2140
                    return uninteresting
 
2141
            elif source_minikind in 'a' and target_minikind in 'fdlt':
 
2142
                # looks like a new file
 
2143
                if path_info is not None:
 
2144
                    path = pathjoin(entry[0][0], entry[0][1])
 
2145
                    # parent id is the entry for the path in the target tree
 
2146
                    # TODO: these are the same for an entire directory: cache em.
 
2147
                    parent_id = state._get_entry(target_index,
 
2148
                                                 path_utf8=entry[0][0])[0][2]
 
2149
                    if parent_id == entry[0][2]:
 
2150
                        parent_id = None
 
2151
                    if use_filesystem_for_exec:
 
2152
                        # We need S_ISREG here, because we aren't sure if this
 
2153
                        # is a file or not.
 
2154
                        target_exec = bool(
 
2155
                            stat.S_ISREG(path_info[3].st_mode)
 
2156
                            and stat.S_IEXEC & path_info[3].st_mode)
 
2157
                    else:
 
2158
                        target_exec = target_details[3]
 
2159
                    return (entry[0][2],
 
2160
                           (None, utf8_decode(path)[0]),
 
2161
                           True,
 
2162
                           (False, True),
 
2163
                           (None, parent_id),
 
2164
                           (None, utf8_decode(entry[0][1])[0]),
 
2165
                           (None, path_info[2]),
 
2166
                           (None, target_exec))
 
2167
                else:
 
2168
                    # but its not on disk: we deliberately treat this as just
 
2169
                    # never-present. (Why ?! - RBC 20070224)
 
2170
                    pass
 
2171
            elif source_minikind in 'fdlt' and target_minikind in 'a':
 
2172
                # unversioned, possibly, or possibly not deleted: we dont care.
 
2173
                # if its still on disk, *and* theres no other entry at this
 
2174
                # path [we dont know this in this routine at the moment -
 
2175
                # perhaps we should change this - then it would be an unknown.
 
2176
                old_path = pathjoin(entry[0][0], entry[0][1])
 
2177
                # parent id is the entry for the path in the target tree
 
2178
                parent_id = state._get_entry(source_index, path_utf8=entry[0][0])[0][2]
 
2179
                if parent_id == entry[0][2]:
 
2180
                    parent_id = None
 
2181
                return (entry[0][2],
 
2182
                       (utf8_decode(old_path)[0], None),
 
2183
                       True,
 
2184
                       (True, False),
 
2185
                       (parent_id, None),
 
2186
                       (utf8_decode(entry[0][1])[0], None),
 
2187
                       (_minikind_to_kind[source_minikind], None),
 
2188
                       (source_details[3], None))
 
2189
            elif source_minikind in 'fdlt' and target_minikind in 'r':
 
2190
                # a rename; could be a true rename, or a rename inherited from
 
2191
                # a renamed parent. TODO: handle this efficiently. Its not
 
2192
                # common case to rename dirs though, so a correct but slow
 
2193
                # implementation will do.
 
2194
                if not osutils.is_inside_any(searched_specific_files, target_details[1]):
 
2195
                    search_specific_files.add(target_details[1])
 
2196
            elif source_minikind in 'ra' and target_minikind in 'ra':
 
2197
                # neither of the selected trees contain this file,
 
2198
                # so skip over it. This is not currently directly tested, but
 
2199
                # is indirectly via test_too_much.TestCommands.test_conflicts.
 
2200
                pass
 
2201
            else:
 
2202
                raise AssertionError("don't know how to compare "
 
2203
                    "source_minikind=%r, target_minikind=%r"
 
2204
                    % (source_minikind, target_minikind))
 
2205
                ## import pdb;pdb.set_trace()
 
2206
            return None
 
2207
 
 
2208
        while search_specific_files:
 
2209
            # TODO: the pending list should be lexically sorted?  the
 
2210
            # interface doesn't require it.
 
2211
            current_root = search_specific_files.pop()
 
2212
            current_root_unicode = current_root.decode('utf8')
 
2213
            searched_specific_files.add(current_root)
 
2214
            # process the entries for this containing directory: the rest will be
 
2215
            # found by their parents recursively.
 
2216
            root_entries = _entries_for_path(current_root)
 
2217
            root_abspath = self.target.abspath(current_root_unicode)
 
2218
            try:
 
2219
                root_stat = os.lstat(root_abspath)
 
2220
            except OSError, e:
 
2221
                if e.errno == errno.ENOENT:
 
2222
                    # the path does not exist: let _process_entry know that.
 
2223
                    root_dir_info = None
 
2224
                else:
 
2225
                    # some other random error: hand it up.
 
2226
                    raise
 
2227
            else:
 
2228
                root_dir_info = ('', current_root,
 
2229
                    osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
 
2230
                    root_abspath)
 
2231
                if root_dir_info[2] == 'directory':
 
2232
                    if self.target._directory_is_tree_reference(
 
2233
                        current_root.decode('utf8')):
 
2234
                        root_dir_info = root_dir_info[:2] + \
 
2235
                            ('tree-reference',) + root_dir_info[3:]
 
2236
 
 
2237
            if not root_entries and not root_dir_info:
 
2238
                # this specified path is not present at all, skip it.
 
2239
                continue
 
2240
            path_handled = False
 
2241
            for entry in root_entries:
 
2242
                result = _process_entry(entry, root_dir_info)
 
2243
                if result is not None:
 
2244
                    path_handled = True
 
2245
                    if result is not uninteresting:
 
2246
                        yield result
 
2247
            if want_unversioned and not path_handled and root_dir_info:
 
2248
                new_executable = bool(
 
2249
                    stat.S_ISREG(root_dir_info[3].st_mode)
 
2250
                    and stat.S_IEXEC & root_dir_info[3].st_mode)
 
2251
                yield (None,
 
2252
                       (None, current_root_unicode),
 
2253
                       True,
 
2254
                       (False, False),
 
2255
                       (None, None),
 
2256
                       (None, splitpath(current_root_unicode)[-1]),
 
2257
                       (None, root_dir_info[2]),
 
2258
                       (None, new_executable)
 
2259
                      )
 
2260
            initial_key = (current_root, '', '')
 
2261
            block_index, _ = state._find_block_index_from_key(initial_key)
 
2262
            if block_index == 0:
 
2263
                # we have processed the total root already, but because the
 
2264
                # initial key matched it we should skip it here.
 
2265
                block_index +=1
 
2266
            if root_dir_info and root_dir_info[2] == 'tree-reference':
 
2267
                current_dir_info = None
 
2268
            else:
 
2269
                dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
 
2270
                try:
 
2271
                    current_dir_info = dir_iterator.next()
 
2272
                except OSError, e:
 
2273
                    # on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
 
2274
                    # python 2.5 has e.errno == EINVAL,
 
2275
                    #            and e.winerror == ERROR_DIRECTORY
 
2276
                    e_winerror = getattr(e, 'winerror', None)
 
2277
                    win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
 
2278
                    # there may be directories in the inventory even though
 
2279
                    # this path is not a file on disk: so mark it as end of
 
2280
                    # iterator
 
2281
                    if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
 
2282
                        current_dir_info = None
 
2283
                    elif (sys.platform == 'win32'
 
2284
                          and (e.errno in win_errors
 
2285
                               or e_winerror in win_errors)):
 
2286
                        current_dir_info = None
 
2287
                    else:
 
2288
                        raise
 
2289
                else:
 
2290
                    if current_dir_info[0][0] == '':
 
2291
                        # remove .bzr from iteration
 
2292
                        bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
 
2293
                        if current_dir_info[1][bzr_index][0] != '.bzr':
 
2294
                            raise AssertionError()
 
2295
                        del current_dir_info[1][bzr_index]
 
2296
            # walk until both the directory listing and the versioned metadata
 
2297
            # are exhausted. 
 
2298
            if (block_index < len(state._dirblocks) and
 
2299
                osutils.is_inside(current_root, state._dirblocks[block_index][0])):
 
2300
                current_block = state._dirblocks[block_index]
 
2301
            else:
 
2302
                current_block = None
 
2303
            while (current_dir_info is not None or
 
2304
                   current_block is not None):
 
2305
                if (current_dir_info and current_block
 
2306
                    and current_dir_info[0][0] != current_block[0]):
 
2307
                    if cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
 
2308
                        # filesystem data refers to paths not covered by the dirblock.
 
2309
                        # this has two possibilities:
 
2310
                        # A) it is versioned but empty, so there is no block for it
 
2311
                        # B) it is not versioned.
 
2312
 
 
2313
                        # if (A) then we need to recurse into it to check for
 
2314
                        # new unknown files or directories.
 
2315
                        # if (B) then we should ignore it, because we don't
 
2316
                        # recurse into unknown directories.
 
2317
                        path_index = 0
 
2318
                        while path_index < len(current_dir_info[1]):
 
2319
                                current_path_info = current_dir_info[1][path_index]
 
2320
                                if want_unversioned:
 
2321
                                    if current_path_info[2] == 'directory':
 
2322
                                        if self.target._directory_is_tree_reference(
 
2323
                                            current_path_info[0].decode('utf8')):
 
2324
                                            current_path_info = current_path_info[:2] + \
 
2325
                                                ('tree-reference',) + current_path_info[3:]
 
2326
                                    new_executable = bool(
 
2327
                                        stat.S_ISREG(current_path_info[3].st_mode)
 
2328
                                        and stat.S_IEXEC & current_path_info[3].st_mode)
 
2329
                                    yield (None,
 
2330
                                        (None, utf8_decode(current_path_info[0])[0]),
 
2331
                                        True,
 
2332
                                        (False, False),
 
2333
                                        (None, None),
 
2334
                                        (None, utf8_decode(current_path_info[1])[0]),
 
2335
                                        (None, current_path_info[2]),
 
2336
                                        (None, new_executable))
 
2337
                                # dont descend into this unversioned path if it is
 
2338
                                # a dir
 
2339
                                if current_path_info[2] in ('directory',
 
2340
                                                            'tree-reference'):
 
2341
                                    del current_dir_info[1][path_index]
 
2342
                                    path_index -= 1
 
2343
                                path_index += 1
 
2344
 
 
2345
                        # This dir info has been handled, go to the next
 
2346
                        try:
 
2347
                            current_dir_info = dir_iterator.next()
 
2348
                        except StopIteration:
 
2349
                            current_dir_info = None
 
2350
                    else:
 
2351
                        # We have a dirblock entry for this location, but there
 
2352
                        # is no filesystem path for this. This is most likely
 
2353
                        # because a directory was removed from the disk.
 
2354
                        # We don't have to report the missing directory,
 
2355
                        # because that should have already been handled, but we
 
2356
                        # need to handle all of the files that are contained
 
2357
                        # within.
 
2358
                        for current_entry in current_block[1]:
 
2359
                            # entry referring to file not present on disk.
 
2360
                            # advance the entry only, after processing.
 
2361
                            result = _process_entry(current_entry, None)
 
2362
                            if result is not None:
 
2363
                                if result is not uninteresting:
 
2364
                                    yield result
 
2365
                        block_index +=1
 
2366
                        if (block_index < len(state._dirblocks) and
 
2367
                            osutils.is_inside(current_root,
 
2368
                                              state._dirblocks[block_index][0])):
 
2369
                            current_block = state._dirblocks[block_index]
 
2370
                        else:
 
2371
                            current_block = None
 
2372
                    continue
 
2373
                entry_index = 0
 
2374
                if current_block and entry_index < len(current_block[1]):
 
2375
                    current_entry = current_block[1][entry_index]
 
2376
                else:
 
2377
                    current_entry = None
 
2378
                advance_entry = True
 
2379
                path_index = 0
 
2380
                if current_dir_info and path_index < len(current_dir_info[1]):
 
2381
                    current_path_info = current_dir_info[1][path_index]
 
2382
                    if current_path_info[2] == 'directory':
 
2383
                        if self.target._directory_is_tree_reference(
 
2384
                            current_path_info[0].decode('utf8')):
 
2385
                            current_path_info = current_path_info[:2] + \
 
2386
                                ('tree-reference',) + current_path_info[3:]
 
2387
                else:
 
2388
                    current_path_info = None
 
2389
                advance_path = True
 
2390
                path_handled = False
 
2391
                while (current_entry is not None or
 
2392
                    current_path_info is not None):
 
2393
                    if current_entry is None:
 
2394
                        # the check for path_handled when the path is adnvaced
 
2395
                        # will yield this path if needed.
 
2396
                        pass
 
2397
                    elif current_path_info is None:
 
2398
                        # no path is fine: the per entry code will handle it.
 
2399
                        result = _process_entry(current_entry, current_path_info)
 
2400
                        if result is not None:
 
2401
                            if result is not uninteresting:
 
2402
                                yield result
 
2403
                    elif (current_entry[0][1] != current_path_info[1]
 
2404
                          or current_entry[1][target_index][0] in 'ar'):
 
2405
                        # The current path on disk doesn't match the dirblock
 
2406
                        # record. Either the dirblock is marked as absent, or
 
2407
                        # the file on disk is not present at all in the
 
2408
                        # dirblock. Either way, report about the dirblock
 
2409
                        # entry, and let other code handle the filesystem one.
 
2410
 
 
2411
                        # Compare the basename for these files to determine
 
2412
                        # which comes first
 
2413
                        if current_path_info[1] < current_entry[0][1]:
 
2414
                            # extra file on disk: pass for now, but only
 
2415
                            # increment the path, not the entry
 
2416
                            advance_entry = False
 
2417
                        else:
 
2418
                            # entry referring to file not present on disk.
 
2419
                            # advance the entry only, after processing.
 
2420
                            result = _process_entry(current_entry, None)
 
2421
                            if result is not None:
 
2422
                                if result is not uninteresting:
 
2423
                                    yield result
 
2424
                            advance_path = False
 
2425
                    else:
 
2426
                        result = _process_entry(current_entry, current_path_info)
 
2427
                        if result is not None:
 
2428
                            path_handled = True
 
2429
                            if result is not uninteresting:
 
2430
                                yield result
 
2431
                    if advance_entry and current_entry is not None:
 
2432
                        entry_index += 1
 
2433
                        if entry_index < len(current_block[1]):
 
2434
                            current_entry = current_block[1][entry_index]
 
2435
                        else:
 
2436
                            current_entry = None
 
2437
                    else:
 
2438
                        advance_entry = True # reset the advance flaga
 
2439
                    if advance_path and current_path_info is not None:
 
2440
                        if not path_handled:
 
2441
                            # unversioned in all regards
 
2442
                            if want_unversioned:
 
2443
                                new_executable = bool(
 
2444
                                    stat.S_ISREG(current_path_info[3].st_mode)
 
2445
                                    and stat.S_IEXEC & current_path_info[3].st_mode)
 
2446
                                yield (None,
 
2447
                                    (None, utf8_decode(current_path_info[0])[0]),
 
2448
                                    True,
 
2449
                                    (False, False),
 
2450
                                    (None, None),
 
2451
                                    (None, utf8_decode(current_path_info[1])[0]),
 
2452
                                    (None, current_path_info[2]),
 
2453
                                    (None, new_executable))
 
2454
                            # dont descend into this unversioned path if it is
 
2455
                            # a dir
 
2456
                            if current_path_info[2] in ('directory'):
 
2457
                                del current_dir_info[1][path_index]
 
2458
                                path_index -= 1
 
2459
                        # dont descend the disk iterator into any tree 
 
2460
                        # paths.
 
2461
                        if current_path_info[2] == 'tree-reference':
 
2462
                            del current_dir_info[1][path_index]
 
2463
                            path_index -= 1
 
2464
                        path_index += 1
 
2465
                        if path_index < len(current_dir_info[1]):
 
2466
                            current_path_info = current_dir_info[1][path_index]
 
2467
                            if current_path_info[2] == 'directory':
 
2468
                                if self.target._directory_is_tree_reference(
 
2469
                                    current_path_info[0].decode('utf8')):
 
2470
                                    current_path_info = current_path_info[:2] + \
 
2471
                                        ('tree-reference',) + current_path_info[3:]
 
2472
                        else:
 
2473
                            current_path_info = None
 
2474
                        path_handled = False
 
2475
                    else:
 
2476
                        advance_path = True # reset the advance flagg.
 
2477
                if current_block is not None:
 
2478
                    block_index += 1
 
2479
                    if (block_index < len(state._dirblocks) and
 
2480
                        osutils.is_inside(current_root, state._dirblocks[block_index][0])):
 
2481
                        current_block = state._dirblocks[block_index]
 
2482
                    else:
 
2483
                        current_block = None
 
2484
                if current_dir_info is not None:
 
2485
                    try:
 
2486
                        current_dir_info = dir_iterator.next()
 
2487
                    except StopIteration:
 
2488
                        current_dir_info = None
 
2489
 
1947
2490
 
1948
2491
    @staticmethod
1949
2492
    def is_compatible(source, target):