/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 breezy/transform.py

Split out git and bzr-specific transforms.

Merged from https://code.launchpad.net/~jelmer/brz/transform-file-id/+merge/386859

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
    osutils,
30
30
    registry,
31
31
    trace,
32
 
    tree,
33
32
    )
34
33
lazy_import.lazy_import(globals(), """
35
34
from breezy import (
36
 
    annotate,
37
35
    cleanup,
38
 
    commit,
39
36
    conflicts,
40
 
    lock,
41
37
    multiparent,
42
38
    revision as _mod_revision,
43
39
    ui,
44
40
    urlutils,
45
41
    )
46
42
from breezy.i18n import gettext
47
 
from breezy.bzr import (
48
 
    inventory,
49
 
    inventorytree,
50
 
    )
51
43
""")
52
44
 
53
45
from .errors import (DuplicateKey,
54
 
                     CantMoveRoot,
55
46
                     BzrError, InternalBzrError)
56
47
from .filters import filtered_output_bytes, ContentFilterContext
57
48
from .mutabletree import MutableTree
101
92
    _fmt = "Tree transform is malformed %(conflicts)r"
102
93
 
103
94
 
 
95
class CantMoveRoot(BzrError):
 
96
 
 
97
    _fmt = "Moving the root directory is not supported at this time"
 
98
 
 
99
 
104
100
class ImmortalLimbo(BzrError):
105
101
 
106
102
    _fmt = """Unable to delete transform temporary directory %(limbo_dir)s.
112
108
        self.limbo_dir = limbo_dir
113
109
 
114
110
 
 
111
class TransformRenameFailed(BzrError):
 
112
 
 
113
    _fmt = "Failed to rename %(from_path)s to %(to_path)s: %(why)s"
 
114
 
 
115
    def __init__(self, from_path, to_path, why, errno):
 
116
        self.from_path = from_path
 
117
        self.to_path = to_path
 
118
        self.why = why
 
119
        self.errno = errno
 
120
 
 
121
 
115
122
def unique_add(map, key, value):
116
123
    if key in map:
117
124
        raise DuplicateKey(key=key)
410
417
 
411
418
    def version_file(self, trans_id, file_id=None):
412
419
        """Schedule a file to become versioned."""
413
 
        if file_id is None:
414
 
            raise ValueError()
415
 
        unique_add(self._new_id, trans_id, file_id)
416
 
        unique_add(self._r_new_id, file_id, trans_id)
 
420
        raise NotImplementedError(self.version_file)
417
421
 
418
422
    def cancel_versioning(self, trans_id):
419
423
        """Undo a previous versioning of a file"""
420
 
        file_id = self._new_id[trans_id]
421
 
        del self._new_id[trans_id]
422
 
        del self._r_new_id[file_id]
 
424
        raise NotImplementedError(self.cancel_versioning)
423
425
 
424
426
    def new_paths(self, filesystem_only=False):
425
427
        """Determine the paths of all new and changed files.
442
444
            new_ids.update(id_set)
443
445
        return sorted(FinalPaths(self).get_paths(new_ids))
444
446
 
445
 
    def _inventory_altered(self):
446
 
        """Determine which trans_ids need new Inventory entries.
447
 
 
448
 
        An new entry is needed when anything that would be reflected by an
449
 
        inventory entry changes, including file name, file_id, parent file_id,
450
 
        file kind, and the execute bit.
451
 
 
452
 
        Some care is taken to return entries with real changes, not cases
453
 
        where the value is deleted and then restored to its original value,
454
 
        but some actually unchanged values may be returned.
455
 
 
456
 
        :returns: A list of (path, trans_id) for all items requiring an
457
 
            inventory change. Ordered by path.
458
 
        """
459
 
        changed_ids = set()
460
 
        # Find entries whose file_ids are new (or changed).
461
 
        new_file_id = set(t for t in self._new_id
462
 
                          if self._new_id[t] != self.tree_file_id(t))
463
 
        for id_set in [self._new_name, self._new_parent, new_file_id,
464
 
                       self._new_executability]:
465
 
            changed_ids.update(id_set)
466
 
        # removing implies a kind change
467
 
        changed_kind = set(self._removed_contents)
468
 
        # so does adding
469
 
        changed_kind.intersection_update(self._new_contents)
470
 
        # Ignore entries that are already known to have changed.
471
 
        changed_kind.difference_update(changed_ids)
472
 
        #  to keep only the truly changed ones
473
 
        changed_kind = (t for t in changed_kind
474
 
                        if self.tree_kind(t) != self.final_kind(t))
475
 
        # all kind changes will alter the inventory
476
 
        changed_ids.update(changed_kind)
477
 
        # To find entries with changed parent_ids, find parents which existed,
478
 
        # but changed file_id.
479
 
        # Now add all their children to the set.
480
 
        for parent_trans_id in new_file_id:
481
 
            changed_ids.update(self.iter_tree_children(parent_trans_id))
482
 
        return sorted(FinalPaths(self).get_paths(changed_ids))
483
 
 
484
447
    def final_kind(self, trans_id):
485
448
        """Determine the final file kind, after any changes applied.
486
449
 
591
554
        conflicts.extend(self._unversioned_parents(by_parent))
592
555
        conflicts.extend(self._parent_loops())
593
556
        conflicts.extend(self._duplicate_entries(by_parent))
594
 
        conflicts.extend(self._duplicate_ids())
595
557
        conflicts.extend(self._parent_type_conflicts(by_parent))
596
558
        conflicts.extend(self._improper_versioning())
597
559
        conflicts.extend(self._executability_conflicts())
773
735
                last_trans_id = trans_id
774
736
        return conflicts
775
737
 
776
 
    def _duplicate_ids(self):
777
 
        """Each inventory id may only be used once"""
778
 
        conflicts = []
779
 
        try:
780
 
            all_ids = self._tree.all_file_ids()
781
 
        except errors.UnsupportedOperation:
782
 
            # it's okay for non-file-id trees to raise UnsupportedOperation.
783
 
            return []
784
 
        removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
785
 
                                self._removed_id))
786
 
        active_tree_ids = all_ids.difference(removed_tree_ids)
787
 
        for trans_id, file_id in viewitems(self._new_id):
788
 
            if file_id in active_tree_ids:
789
 
                path = self._tree.id2path(file_id)
790
 
                old_trans_id = self.trans_id_tree_path(path)
791
 
                conflicts.append(('duplicate id', old_trans_id, trans_id))
792
 
        return conflicts
793
 
 
794
738
    def _parent_type_conflicts(self, by_parent):
795
739
        """Children must have a directory parent"""
796
740
        conflicts = []
1075
1019
        The tree is a snapshot, and altering the TreeTransform will invalidate
1076
1020
        it.
1077
1021
        """
1078
 
        return _PreviewTree(self)
 
1022
        raise NotImplementedError(self.get_preview)
1079
1023
 
1080
1024
    def commit(self, branch, message, merge_parents=None, strict=False,
1081
1025
               timestamp=None, timezone=None, committer=None, authors=None,
1120
1064
        if self._tree.get_revision_id() != last_rev_id:
1121
1065
            raise ValueError('TreeTransform not based on branch basis: %s' %
1122
1066
                             self._tree.get_revision_id().decode('utf-8'))
 
1067
        from . import commit
1123
1068
        revprops = commit.Commit.update_revprops(revprops, branch, authors)
1124
1069
        builder = branch.get_commit_builder(parent_ids,
1125
1070
                                            timestamp=timestamp,
1836
1781
            calculating one.
1837
1782
        :param _mover: Supply an alternate FileMover, for testing
1838
1783
        """
1839
 
        for hook in MutableTree.hooks['pre_transform']:
1840
 
            hook(self._tree, self)
1841
 
        if not no_conflicts:
1842
 
            self._check_malformed()
1843
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
1844
 
            if precomputed_delta is None:
1845
 
                child_pb.update(gettext('Apply phase'), 0, 2)
1846
 
                inventory_delta = self._generate_inventory_delta()
1847
 
                offset = 1
1848
 
            else:
1849
 
                inventory_delta = precomputed_delta
1850
 
                offset = 0
1851
 
            if _mover is None:
1852
 
                mover = _FileMover()
1853
 
            else:
1854
 
                mover = _mover
1855
 
            try:
1856
 
                child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1857
 
                self._apply_removals(mover)
1858
 
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1859
 
                modified_paths = self._apply_insertions(mover)
1860
 
            except BaseException:
1861
 
                mover.rollback()
1862
 
                raise
1863
 
            else:
1864
 
                mover.apply_deletions()
1865
 
        if not self.final_is_versioned(self.root):
1866
 
            inventory_delta = [e for e in inventory_delta if e[0] != '']
1867
 
        self._tree.apply_inventory_delta(inventory_delta)
1868
 
        self._apply_observed_sha1s()
1869
 
        self._done = True
1870
 
        self.finalize()
1871
 
        return _TransformResults(modified_paths, self.rename_count)
1872
 
 
1873
 
    def _generate_inventory_delta(self):
1874
 
        """Generate an inventory delta for the current transform."""
1875
 
        inventory_delta = []
1876
 
        new_paths = self._inventory_altered()
1877
 
        total_entries = len(new_paths) + len(self._removed_id)
1878
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
1879
 
            for num, trans_id in enumerate(self._removed_id):
1880
 
                if (num % 10) == 0:
1881
 
                    child_pb.update(gettext('removing file'),
1882
 
                                    num, total_entries)
1883
 
                if trans_id == self._new_root:
1884
 
                    file_id = self._tree.path2id('')
1885
 
                else:
1886
 
                    file_id = self.tree_file_id(trans_id)
1887
 
                # File-id isn't really being deleted, just moved
1888
 
                if file_id in self._r_new_id:
1889
 
                    continue
1890
 
                path = self._tree_id_paths[trans_id]
1891
 
                inventory_delta.append((path, None, file_id, None))
1892
 
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1893
 
                                     new_paths)
1894
 
            for num, (path, trans_id) in enumerate(new_paths):
1895
 
                if (num % 10) == 0:
1896
 
                    child_pb.update(gettext('adding file'),
1897
 
                                    num + len(self._removed_id), total_entries)
1898
 
                file_id = new_path_file_ids[trans_id]
1899
 
                if file_id is None:
1900
 
                    continue
1901
 
                kind = self.final_kind(trans_id)
1902
 
                if kind is None:
1903
 
                    kind = self._tree.stored_kind(self._tree.id2path(file_id))
1904
 
                parent_trans_id = self.final_parent(trans_id)
1905
 
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1906
 
                if parent_file_id is None:
1907
 
                    parent_file_id = self.final_file_id(parent_trans_id)
1908
 
                if trans_id in self._new_reference_revision:
1909
 
                    new_entry = inventory.TreeReference(
1910
 
                        file_id,
1911
 
                        self._new_name[trans_id],
1912
 
                        self.final_file_id(self._new_parent[trans_id]),
1913
 
                        None, self._new_reference_revision[trans_id])
1914
 
                else:
1915
 
                    new_entry = inventory.make_entry(kind,
1916
 
                                                     self.final_name(trans_id),
1917
 
                                                     parent_file_id, file_id)
1918
 
                try:
1919
 
                    old_path = self._tree.id2path(new_entry.file_id)
1920
 
                except errors.NoSuchId:
1921
 
                    old_path = None
1922
 
                new_executability = self._new_executability.get(trans_id)
1923
 
                if new_executability is not None:
1924
 
                    new_entry.executable = new_executability
1925
 
                inventory_delta.append(
1926
 
                    (old_path, path, new_entry.file_id, new_entry))
1927
 
        return inventory_delta
1928
 
 
1929
 
    def _apply_removals(self, mover):
1930
 
        """Perform tree operations that remove directory/inventory names.
1931
 
 
1932
 
        That is, delete files that are to be deleted, and put any files that
1933
 
        need renaming into limbo.  This must be done in strict child-to-parent
1934
 
        order.
1935
 
 
1936
 
        If inventory_delta is None, no inventory delta generation is performed.
1937
 
        """
1938
 
        tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True)
1939
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
1940
 
            for num, (path, trans_id) in enumerate(tree_paths):
1941
 
                # do not attempt to move root into a subdirectory of itself.
1942
 
                if path == '':
1943
 
                    continue
1944
 
                child_pb.update(gettext('removing file'), num, len(tree_paths))
1945
 
                full_path = self._tree.abspath(path)
1946
 
                if trans_id in self._removed_contents:
1947
 
                    delete_path = os.path.join(self._deletiondir, trans_id)
1948
 
                    mover.pre_delete(full_path, delete_path)
1949
 
                elif (trans_id in self._new_name or
1950
 
                      trans_id in self._new_parent):
1951
 
                    try:
1952
 
                        mover.rename(full_path, self._limbo_name(trans_id))
1953
 
                    except errors.TransformRenameFailed as e:
1954
 
                        if e.errno != errno.ENOENT:
1955
 
                            raise
1956
 
                    else:
1957
 
                        self.rename_count += 1
1958
 
 
1959
 
    def _apply_insertions(self, mover):
1960
 
        """Perform tree operations that insert directory/inventory names.
1961
 
 
1962
 
        That is, create any files that need to be created, and restore from
1963
 
        limbo any files that needed renaming.  This must be done in strict
1964
 
        parent-to-child order.
1965
 
 
1966
 
        If inventory_delta is None, no inventory delta is calculated, and
1967
 
        no list of modified paths is returned.
1968
 
        """
1969
 
        new_paths = self.new_paths(filesystem_only=True)
1970
 
        modified_paths = []
1971
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
1972
 
            for num, (path, trans_id) in enumerate(new_paths):
1973
 
                if (num % 10) == 0:
1974
 
                    child_pb.update(gettext('adding file'),
1975
 
                                    num, len(new_paths))
1976
 
                full_path = self._tree.abspath(path)
1977
 
                if trans_id in self._needs_rename:
1978
 
                    try:
1979
 
                        mover.rename(self._limbo_name(trans_id), full_path)
1980
 
                    except errors.TransformRenameFailed as e:
1981
 
                        # We may be renaming a dangling inventory id
1982
 
                        if e.errno != errno.ENOENT:
1983
 
                            raise
1984
 
                    else:
1985
 
                        self.rename_count += 1
1986
 
                    # TODO: if trans_id in self._observed_sha1s, we should
1987
 
                    #       re-stat the final target, since ctime will be
1988
 
                    #       updated by the change.
1989
 
                if (trans_id in self._new_contents
1990
 
                        or self.path_changed(trans_id)):
1991
 
                    if trans_id in self._new_contents:
1992
 
                        modified_paths.append(full_path)
1993
 
                if trans_id in self._new_executability:
1994
 
                    self._set_executability(path, trans_id)
1995
 
                if trans_id in self._observed_sha1s:
1996
 
                    o_sha1, o_st_val = self._observed_sha1s[trans_id]
1997
 
                    st = osutils.lstat(full_path)
1998
 
                    self._observed_sha1s[trans_id] = (o_sha1, st)
1999
 
        for path, trans_id in new_paths:
2000
 
            # new_paths includes stuff like workingtree conflicts. Only the
2001
 
            # stuff in new_contents actually comes from limbo.
2002
 
            if trans_id in self._limbo_files:
2003
 
                del self._limbo_files[trans_id]
2004
 
        self._new_contents.clear()
2005
 
        return modified_paths
2006
 
 
2007
 
    def _apply_observed_sha1s(self):
2008
 
        """After we have finished renaming everything, update observed sha1s
2009
 
 
2010
 
        This has to be done after self._tree.apply_inventory_delta, otherwise
2011
 
        it doesn't know anything about the files we are updating. Also, we want
2012
 
        to do this as late as possible, so that most entries end up cached.
2013
 
        """
2014
 
        # TODO: this doesn't update the stat information for directories. So
2015
 
        #       the first 'bzr status' will still need to rewrite
2016
 
        #       .bzr/checkout/dirstate. However, we at least don't need to
2017
 
        #       re-read all of the files.
2018
 
        # TODO: If the operation took a while, we could do a time.sleep(3) here
2019
 
        #       to allow the clock to tick over and ensure we won't have any
2020
 
        #       problems. (we could observe start time, and finish time, and if
2021
 
        #       it is less than eg 10% overhead, add a sleep call.)
2022
 
        paths = FinalPaths(self)
2023
 
        for trans_id, observed in viewitems(self._observed_sha1s):
2024
 
            path = paths.get_path(trans_id)
2025
 
            self._tree._observed_sha1(path, observed)
2026
 
 
2027
 
 
2028
 
class TransformPreview(DiskTreeTransform):
2029
 
    """A TreeTransform for generating preview trees.
2030
 
 
2031
 
    Unlike TreeTransform, this version works when the input tree is a
2032
 
    RevisionTree, rather than a WorkingTree.  As a result, it tends to ignore
2033
 
    unversioned files in the input tree.
2034
 
    """
2035
 
 
2036
 
    def __init__(self, tree, pb=None, case_sensitive=True):
2037
 
        tree.lock_read()
2038
 
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
2039
 
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
2040
 
 
2041
 
    def tree_kind(self, trans_id):
2042
 
        path = self._tree_id_paths.get(trans_id)
2043
 
        if path is None:
2044
 
            return None
2045
 
        kind = self._tree.path_content_summary(path)[0]
2046
 
        if kind == 'missing':
2047
 
            kind = None
2048
 
        return kind
2049
 
 
2050
 
    def _set_mode(self, trans_id, mode_id, typefunc):
2051
 
        """Set the mode of new file contents.
2052
 
        The mode_id is the existing file to get the mode from (often the same
2053
 
        as trans_id).  The operation is only performed if there's a mode match
2054
 
        according to typefunc.
2055
 
        """
2056
 
        # is it ok to ignore this?  probably
2057
 
        pass
2058
 
 
2059
 
    def iter_tree_children(self, parent_id):
2060
 
        """Iterate through the entry's tree children, if any"""
2061
 
        try:
2062
 
            path = self._tree_id_paths[parent_id]
2063
 
        except KeyError:
2064
 
            return
2065
 
        try:
2066
 
            entry = next(self._tree.iter_entries_by_dir(
2067
 
                specific_files=[path]))[1]
2068
 
        except StopIteration:
2069
 
            return
2070
 
        children = getattr(entry, 'children', {})
2071
 
        for child in children:
2072
 
            childpath = joinpath(path, child)
2073
 
            yield self.trans_id_tree_path(childpath)
2074
 
 
2075
 
    def new_orphan(self, trans_id, parent_id):
2076
 
        raise NotImplementedError(self.new_orphan)
2077
 
 
2078
 
 
2079
 
class _PreviewTree(inventorytree.InventoryTree):
2080
 
    """Partial implementation of Tree to support show_diff_trees"""
2081
 
 
2082
 
    def __init__(self, transform):
2083
 
        self._transform = transform
2084
 
        self._final_paths = FinalPaths(transform)
2085
 
        self.__by_parent = None
2086
 
        self._parent_ids = []
2087
 
        self._all_children_cache = {}
2088
 
        self._path2trans_id_cache = {}
2089
 
        self._final_name_cache = {}
2090
 
        self._iter_changes_cache = dict((c.file_id, c) for c in
2091
 
                                        self._transform.iter_changes())
2092
 
 
2093
 
    def supports_tree_reference(self):
2094
 
        # TODO(jelmer): Support tree references in _PreviewTree.
2095
 
        # return self._transform._tree.supports_tree_reference()
2096
 
        return False
2097
 
 
2098
 
    def _content_change(self, file_id):
2099
 
        """Return True if the content of this file changed"""
2100
 
        changes = self._iter_changes_cache.get(file_id)
2101
 
        return (changes is not None and changes.changed_content)
2102
 
 
2103
 
    def _get_repository(self):
2104
 
        repo = getattr(self._transform._tree, '_repository', None)
2105
 
        if repo is None:
2106
 
            repo = self._transform._tree.branch.repository
2107
 
        return repo
2108
 
 
2109
 
    def _iter_parent_trees(self):
2110
 
        for revision_id in self.get_parent_ids():
2111
 
            try:
2112
 
                yield self.revision_tree(revision_id)
2113
 
            except errors.NoSuchRevisionInTree:
2114
 
                yield self._get_repository().revision_tree(revision_id)
2115
 
 
2116
 
    def _get_file_revision(self, path, file_id, vf, tree_revision):
2117
 
        parent_keys = [
2118
 
            (file_id, t.get_file_revision(t.id2path(file_id)))
2119
 
            for t in self._iter_parent_trees()]
2120
 
        vf.add_lines((file_id, tree_revision), parent_keys,
2121
 
                     self.get_file_lines(path))
2122
 
        repo = self._get_repository()
2123
 
        base_vf = repo.texts
2124
 
        if base_vf not in vf.fallback_versionedfiles:
2125
 
            vf.fallback_versionedfiles.append(base_vf)
2126
 
        return tree_revision
2127
 
 
2128
 
    def _stat_limbo_file(self, trans_id):
2129
 
        name = self._transform._limbo_name(trans_id)
2130
 
        return os.lstat(name)
2131
 
 
2132
 
    @property
2133
 
    def _by_parent(self):
2134
 
        if self.__by_parent is None:
2135
 
            self.__by_parent = self._transform.by_parent()
2136
 
        return self.__by_parent
2137
 
 
2138
 
    def _comparison_data(self, entry, path):
2139
 
        kind, size, executable, link_or_sha1 = self.path_content_summary(path)
2140
 
        if kind == 'missing':
2141
 
            kind = None
2142
 
            executable = False
2143
 
        else:
2144
 
            executable = self.is_executable(path)
2145
 
        return kind, executable, None
2146
 
 
2147
 
    def is_locked(self):
2148
 
        return False
2149
 
 
2150
 
    def lock_read(self):
2151
 
        # Perhaps in theory, this should lock the TreeTransform?
2152
 
        return lock.LogicalLockResult(self.unlock)
2153
 
 
2154
 
    def unlock(self):
2155
 
        pass
2156
 
 
2157
 
    @property
2158
 
    def root_inventory(self):
2159
 
        """This Tree does not use inventory as its backing data."""
2160
 
        raise NotImplementedError(_PreviewTree.root_inventory)
2161
 
 
2162
 
    def all_file_ids(self):
2163
 
        tree_ids = set(self._transform._tree.all_file_ids())
2164
 
        tree_ids.difference_update(self._transform.tree_file_id(t)
2165
 
                                   for t in self._transform._removed_id)
2166
 
        tree_ids.update(viewvalues(self._transform._new_id))
2167
 
        return tree_ids
2168
 
 
2169
 
    def all_versioned_paths(self):
2170
 
        tree_paths = set(self._transform._tree.all_versioned_paths())
2171
 
 
2172
 
        tree_paths.difference_update(
2173
 
            self._transform.trans_id_tree_path(t)
2174
 
            for t in self._transform._removed_id)
2175
 
 
2176
 
        tree_paths.update(
2177
 
            self._final_paths._determine_path(t)
2178
 
            for t in self._transform._new_id)
2179
 
 
2180
 
        return tree_paths
2181
 
 
2182
 
    def _path2trans_id(self, path):
2183
 
        # We must not use None here, because that is a valid value to store.
2184
 
        trans_id = self._path2trans_id_cache.get(path, object)
2185
 
        if trans_id is not object:
2186
 
            return trans_id
2187
 
        segments = splitpath(path)
2188
 
        cur_parent = self._transform.root
2189
 
        for cur_segment in segments:
2190
 
            for child in self._all_children(cur_parent):
2191
 
                final_name = self._final_name_cache.get(child)
2192
 
                if final_name is None:
2193
 
                    final_name = self._transform.final_name(child)
2194
 
                    self._final_name_cache[child] = final_name
2195
 
                if final_name == cur_segment:
2196
 
                    cur_parent = child
2197
 
                    break
2198
 
            else:
2199
 
                self._path2trans_id_cache[path] = None
2200
 
                return None
2201
 
        self._path2trans_id_cache[path] = cur_parent
2202
 
        return cur_parent
2203
 
 
2204
 
    def path2id(self, path):
2205
 
        if isinstance(path, list):
2206
 
            if path == []:
2207
 
                path = [""]
2208
 
            path = osutils.pathjoin(*path)
2209
 
        return self._transform.final_file_id(self._path2trans_id(path))
2210
 
 
2211
 
    def id2path(self, file_id, recurse='down'):
2212
 
        trans_id = self._transform.trans_id_file_id(file_id)
2213
 
        try:
2214
 
            return self._final_paths._determine_path(trans_id)
2215
 
        except NoFinalPath:
2216
 
            raise errors.NoSuchId(self, file_id)
2217
 
 
2218
 
    def _all_children(self, trans_id):
2219
 
        children = self._all_children_cache.get(trans_id)
2220
 
        if children is not None:
2221
 
            return children
2222
 
        children = set(self._transform.iter_tree_children(trans_id))
2223
 
        # children in the _new_parent set are provided by _by_parent.
2224
 
        children.difference_update(self._transform._new_parent)
2225
 
        children.update(self._by_parent.get(trans_id, []))
2226
 
        self._all_children_cache[trans_id] = children
2227
 
        return children
2228
 
 
2229
 
    def extras(self):
2230
 
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
2231
 
                              in self._transform._tree.extras())
2232
 
        possible_extras.update(self._transform._new_contents)
2233
 
        possible_extras.update(self._transform._removed_id)
2234
 
        for trans_id in possible_extras:
2235
 
            if not self._transform.final_is_versioned(trans_id):
2236
 
                yield self._final_paths._determine_path(trans_id)
2237
 
 
2238
 
    def _make_inv_entries(self, ordered_entries, specific_files=None):
2239
 
        for trans_id, parent_file_id in ordered_entries:
2240
 
            file_id = self._transform.final_file_id(trans_id)
2241
 
            if file_id is None:
2242
 
                continue
2243
 
            if (specific_files is not None
2244
 
                    and self._final_paths.get_path(trans_id) not in specific_files):
2245
 
                continue
2246
 
            kind = self._transform.final_kind(trans_id)
2247
 
            if kind is None:
2248
 
                kind = self._transform._tree.stored_kind(
2249
 
                    self._transform._tree.id2path(file_id))
2250
 
            new_entry = inventory.make_entry(
2251
 
                kind,
2252
 
                self._transform.final_name(trans_id),
2253
 
                parent_file_id, file_id)
2254
 
            yield new_entry, trans_id
2255
 
 
2256
 
    def _list_files_by_dir(self):
2257
 
        todo = [ROOT_PARENT]
2258
 
        ordered_ids = []
2259
 
        while len(todo) > 0:
2260
 
            parent = todo.pop()
2261
 
            parent_file_id = self._transform.final_file_id(parent)
2262
 
            children = list(self._all_children(parent))
2263
 
            paths = dict(zip(children, self._final_paths.get_paths(children)))
2264
 
            children.sort(key=paths.get)
2265
 
            todo.extend(reversed(children))
2266
 
            for trans_id in children:
2267
 
                ordered_ids.append((trans_id, parent_file_id))
2268
 
        return ordered_ids
2269
 
 
2270
 
    def iter_child_entries(self, path):
2271
 
        trans_id = self._path2trans_id(path)
2272
 
        if trans_id is None:
2273
 
            raise errors.NoSuchFile(path)
2274
 
        todo = [(child_trans_id, trans_id) for child_trans_id in
2275
 
                self._all_children(trans_id)]
2276
 
        for entry, trans_id in self._make_inv_entries(todo):
2277
 
            yield entry
2278
 
 
2279
 
    def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
2280
 
        if recurse_nested:
2281
 
            raise NotImplementedError(
2282
 
                'follow tree references not yet supported')
2283
 
 
2284
 
        # This may not be a maximally efficient implementation, but it is
2285
 
        # reasonably straightforward.  An implementation that grafts the
2286
 
        # TreeTransform changes onto the tree's iter_entries_by_dir results
2287
 
        # might be more efficient, but requires tricky inferences about stack
2288
 
        # position.
2289
 
        ordered_ids = self._list_files_by_dir()
2290
 
        for entry, trans_id in self._make_inv_entries(ordered_ids,
2291
 
                                                      specific_files):
2292
 
            yield self._final_paths.get_path(trans_id), entry
2293
 
 
2294
 
    def _iter_entries_for_dir(self, dir_path):
2295
 
        """Return path, entry for items in a directory without recursing down."""
2296
 
        ordered_ids = []
2297
 
        dir_trans_id = self._path2trans_id(dir_path)
2298
 
        dir_id = self._transform.final_file_id(dir_trans_id)
2299
 
        for child_trans_id in self._all_children(dir_trans_id):
2300
 
            ordered_ids.append((child_trans_id, dir_id))
2301
 
        path_entries = []
2302
 
        for entry, trans_id in self._make_inv_entries(ordered_ids):
2303
 
            path_entries.append((self._final_paths.get_path(trans_id), entry))
2304
 
        path_entries.sort()
2305
 
        return path_entries
2306
 
 
2307
 
    def list_files(self, include_root=False, from_dir=None, recursive=True,
2308
 
                   recurse_nested=False):
2309
 
        """See WorkingTree.list_files."""
2310
 
        if recurse_nested:
2311
 
            raise NotImplementedError(
2312
 
                'follow tree references not yet supported')
2313
 
 
2314
 
        # XXX This should behave like WorkingTree.list_files, but is really
2315
 
        # more like RevisionTree.list_files.
2316
 
        if from_dir == '.':
2317
 
            from_dir = None
2318
 
        if recursive:
2319
 
            prefix = None
2320
 
            if from_dir:
2321
 
                prefix = from_dir + '/'
2322
 
            entries = self.iter_entries_by_dir()
2323
 
            for path, entry in entries:
2324
 
                if entry.name == '' and not include_root:
2325
 
                    continue
2326
 
                if prefix:
2327
 
                    if not path.startswith(prefix):
2328
 
                        continue
2329
 
                    path = path[len(prefix):]
2330
 
                yield path, 'V', entry.kind, entry
2331
 
        else:
2332
 
            if from_dir is None and include_root is True:
2333
 
                root_entry = inventory.make_entry(
2334
 
                    'directory', '', ROOT_PARENT, self.path2id(''))
2335
 
                yield '', 'V', 'directory', root_entry
2336
 
            entries = self._iter_entries_for_dir(from_dir or '')
2337
 
            for path, entry in entries:
2338
 
                yield path, 'V', entry.kind, entry
2339
 
 
2340
 
    def kind(self, path):
2341
 
        trans_id = self._path2trans_id(path)
2342
 
        if trans_id is None:
2343
 
            raise errors.NoSuchFile(path)
2344
 
        return self._transform.final_kind(trans_id)
2345
 
 
2346
 
    def stored_kind(self, path):
2347
 
        trans_id = self._path2trans_id(path)
2348
 
        if trans_id is None:
2349
 
            raise errors.NoSuchFile(path)
2350
 
        try:
2351
 
            return self._transform._new_contents[trans_id]
2352
 
        except KeyError:
2353
 
            return self._transform._tree.stored_kind(path)
2354
 
 
2355
 
    def get_file_mtime(self, path):
2356
 
        """See Tree.get_file_mtime"""
2357
 
        file_id = self.path2id(path)
2358
 
        if file_id is None:
2359
 
            raise errors.NoSuchFile(path)
2360
 
        if not self._content_change(file_id):
2361
 
            return self._transform._tree.get_file_mtime(
2362
 
                self._transform._tree.id2path(file_id))
2363
 
        trans_id = self._path2trans_id(path)
2364
 
        return self._stat_limbo_file(trans_id).st_mtime
2365
 
 
2366
 
    def get_file_size(self, path):
2367
 
        """See Tree.get_file_size"""
2368
 
        trans_id = self._path2trans_id(path)
2369
 
        if trans_id is None:
2370
 
            raise errors.NoSuchFile(path)
2371
 
        kind = self._transform.final_kind(trans_id)
2372
 
        if kind != 'file':
2373
 
            return None
2374
 
        if trans_id in self._transform._new_contents:
2375
 
            return self._stat_limbo_file(trans_id).st_size
2376
 
        if self.kind(path) == 'file':
2377
 
            return self._transform._tree.get_file_size(path)
2378
 
        else:
2379
 
            return None
2380
 
 
2381
 
    def get_file_verifier(self, path, stat_value=None):
2382
 
        trans_id = self._path2trans_id(path)
2383
 
        if trans_id is None:
2384
 
            raise errors.NoSuchFile(path)
2385
 
        kind = self._transform._new_contents.get(trans_id)
2386
 
        if kind is None:
2387
 
            return self._transform._tree.get_file_verifier(path)
2388
 
        if kind == 'file':
2389
 
            with self.get_file(path) as fileobj:
2390
 
                return ("SHA1", sha_file(fileobj))
2391
 
 
2392
 
    def get_file_sha1(self, path, stat_value=None):
2393
 
        trans_id = self._path2trans_id(path)
2394
 
        if trans_id is None:
2395
 
            raise errors.NoSuchFile(path)
2396
 
        kind = self._transform._new_contents.get(trans_id)
2397
 
        if kind is None:
2398
 
            return self._transform._tree.get_file_sha1(path)
2399
 
        if kind == 'file':
2400
 
            with self.get_file(path) as fileobj:
2401
 
                return sha_file(fileobj)
2402
 
 
2403
 
    def get_reference_revision(self, path):
2404
 
        trans_id = self._path2trans_id(path)
2405
 
        if trans_id is None:
2406
 
            raise errors.NoSuchFile(path)
2407
 
        reference_revision = self._transform._new_reference_revision.get(trans_id)
2408
 
        if reference_revision is None:
2409
 
            return self._transform._tree.get_reference_revision(path)
2410
 
        return reference_revision
2411
 
 
2412
 
    def is_executable(self, path):
2413
 
        trans_id = self._path2trans_id(path)
2414
 
        if trans_id is None:
2415
 
            return False
2416
 
        try:
2417
 
            return self._transform._new_executability[trans_id]
2418
 
        except KeyError:
2419
 
            try:
2420
 
                return self._transform._tree.is_executable(path)
2421
 
            except OSError as e:
2422
 
                if e.errno == errno.ENOENT:
2423
 
                    return False
2424
 
                raise
2425
 
            except errors.NoSuchFile:
2426
 
                return False
2427
 
 
2428
 
    def has_filename(self, path):
2429
 
        trans_id = self._path2trans_id(path)
2430
 
        if trans_id in self._transform._new_contents:
2431
 
            return True
2432
 
        elif trans_id in self._transform._removed_contents:
2433
 
            return False
2434
 
        else:
2435
 
            return self._transform._tree.has_filename(path)
2436
 
 
2437
 
    def path_content_summary(self, path):
2438
 
        trans_id = self._path2trans_id(path)
2439
 
        tt = self._transform
2440
 
        tree_path = tt._tree_id_paths.get(trans_id)
2441
 
        kind = tt._new_contents.get(trans_id)
2442
 
        if kind is None:
2443
 
            if tree_path is None or trans_id in tt._removed_contents:
2444
 
                return 'missing', None, None, None
2445
 
            summary = tt._tree.path_content_summary(tree_path)
2446
 
            kind, size, executable, link_or_sha1 = summary
2447
 
        else:
2448
 
            link_or_sha1 = None
2449
 
            limbo_name = tt._limbo_name(trans_id)
2450
 
            if trans_id in tt._new_reference_revision:
2451
 
                kind = 'tree-reference'
2452
 
            if kind == 'file':
2453
 
                statval = os.lstat(limbo_name)
2454
 
                size = statval.st_size
2455
 
                if not tt._limbo_supports_executable():
2456
 
                    executable = False
2457
 
                else:
2458
 
                    executable = statval.st_mode & S_IEXEC
2459
 
            else:
2460
 
                size = None
2461
 
                executable = None
2462
 
            if kind == 'symlink':
2463
 
                link_or_sha1 = os.readlink(limbo_name)
2464
 
                if not isinstance(link_or_sha1, text_type):
2465
 
                    link_or_sha1 = link_or_sha1.decode(osutils._fs_enc)
2466
 
        executable = tt._new_executability.get(trans_id, executable)
2467
 
        return kind, size, executable, link_or_sha1
2468
 
 
2469
 
    def iter_changes(self, from_tree, include_unchanged=False,
2470
 
                     specific_files=None, pb=None, extra_trees=None,
2471
 
                     require_versioned=True, want_unversioned=False):
2472
 
        """See InterTree.iter_changes.
2473
 
 
2474
 
        This has a fast path that is only used when the from_tree matches
2475
 
        the transform tree, and no fancy options are supplied.
2476
 
        """
2477
 
        if (from_tree is not self._transform._tree or include_unchanged
2478
 
                or specific_files or want_unversioned):
2479
 
            from .bzr.inventorytree import InterInventoryTree
2480
 
            return InterInventoryTree(from_tree, self).iter_changes(
2481
 
                include_unchanged=include_unchanged,
2482
 
                specific_files=specific_files,
2483
 
                pb=pb,
2484
 
                extra_trees=extra_trees,
2485
 
                require_versioned=require_versioned,
2486
 
                want_unversioned=want_unversioned)
2487
 
        if want_unversioned:
2488
 
            raise ValueError('want_unversioned is not supported')
2489
 
        return self._transform.iter_changes()
2490
 
 
2491
 
    def get_file(self, path):
2492
 
        """See Tree.get_file"""
2493
 
        file_id = self.path2id(path)
2494
 
        if not self._content_change(file_id):
2495
 
            return self._transform._tree.get_file(path)
2496
 
        trans_id = self._path2trans_id(path)
2497
 
        name = self._transform._limbo_name(trans_id)
2498
 
        return open(name, 'rb')
2499
 
 
2500
 
    def get_file_with_stat(self, path):
2501
 
        return self.get_file(path), None
2502
 
 
2503
 
    def annotate_iter(self, path,
2504
 
                      default_revision=_mod_revision.CURRENT_REVISION):
2505
 
        file_id = self.path2id(path)
2506
 
        changes = self._iter_changes_cache.get(file_id)
2507
 
        if changes is None:
2508
 
            get_old = True
2509
 
        else:
2510
 
            changed_content, versioned, kind = (
2511
 
                changes.changed_content, changes.versioned, changes.kind)
2512
 
            if kind[1] is None:
2513
 
                return None
2514
 
            get_old = (kind[0] == 'file' and versioned[0])
2515
 
        if get_old:
2516
 
            old_annotation = self._transform._tree.annotate_iter(
2517
 
                path, default_revision=default_revision)
2518
 
        else:
2519
 
            old_annotation = []
2520
 
        if changes is None:
2521
 
            return old_annotation
2522
 
        if not changed_content:
2523
 
            return old_annotation
2524
 
        # TODO: This is doing something similar to what WT.annotate_iter is
2525
 
        #       doing, however it fails slightly because it doesn't know what
2526
 
        #       the *other* revision_id is, so it doesn't know how to give the
2527
 
        #       other as the origin for some lines, they all get
2528
 
        #       'default_revision'
2529
 
        #       It would be nice to be able to use the new Annotator based
2530
 
        #       approach, as well.
2531
 
        return annotate.reannotate([old_annotation],
2532
 
                                   self.get_file(path).readlines(),
2533
 
                                   default_revision)
2534
 
 
2535
 
    def get_symlink_target(self, path):
2536
 
        """See Tree.get_symlink_target"""
2537
 
        file_id = self.path2id(path)
2538
 
        if not self._content_change(file_id):
2539
 
            return self._transform._tree.get_symlink_target(path)
2540
 
        trans_id = self._path2trans_id(path)
2541
 
        name = self._transform._limbo_name(trans_id)
2542
 
        return osutils.readlink(name)
2543
 
 
2544
 
    def walkdirs(self, prefix=''):
2545
 
        pending = [self._transform.root]
2546
 
        while len(pending) > 0:
2547
 
            parent_id = pending.pop()
2548
 
            children = []
2549
 
            subdirs = []
2550
 
            prefix = prefix.rstrip('/')
2551
 
            parent_path = self._final_paths.get_path(parent_id)
2552
 
            parent_file_id = self._transform.final_file_id(parent_id)
2553
 
            for child_id in self._all_children(parent_id):
2554
 
                path_from_root = self._final_paths.get_path(child_id)
2555
 
                basename = self._transform.final_name(child_id)
2556
 
                file_id = self._transform.final_file_id(child_id)
2557
 
                kind = self._transform.final_kind(child_id)
2558
 
                if kind is not None:
2559
 
                    versioned_kind = kind
2560
 
                else:
2561
 
                    kind = 'unknown'
2562
 
                    versioned_kind = self._transform._tree.stored_kind(
2563
 
                        self._transform._tree.id2path(file_id))
2564
 
                if versioned_kind == 'directory':
2565
 
                    subdirs.append(child_id)
2566
 
                children.append((path_from_root, basename, kind, None,
2567
 
                                 file_id, versioned_kind))
2568
 
            children.sort()
2569
 
            if parent_path.startswith(prefix):
2570
 
                yield (parent_path, parent_file_id), children
2571
 
            pending.extend(sorted(subdirs, key=self._final_paths.get_path,
2572
 
                                  reverse=True))
2573
 
 
2574
 
    def get_parent_ids(self):
2575
 
        return self._parent_ids
2576
 
 
2577
 
    def set_parent_ids(self, parent_ids):
2578
 
        self._parent_ids = parent_ids
2579
 
 
2580
 
    def get_revision_tree(self, revision_id):
2581
 
        return self._transform._tree.get_revision_tree(revision_id)
 
1784
        raise NotImplementedError(self.apply)
2582
1785
 
2583
1786
 
2584
1787
def joinpath(parent, child):
3278
2481
                raise errors.FileExists(to, str(e))
3279
2482
            # normal OSError doesn't include filenames so it's hard to see where
3280
2483
            # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
3281
 
            raise errors.TransformRenameFailed(from_, to, str(e), e.errno)
 
2484
            raise TransformRenameFailed(from_, to, str(e), e.errno)
3282
2485
        self.past_renames.append((from_, to))
3283
2486
 
3284
2487
    def pre_delete(self, from_, to):
3297
2500
            try:
3298
2501
                os.rename(to, from_)
3299
2502
            except OSError as e:
3300
 
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
 
2503
                raise TransformRenameFailed(to, from_, str(e), e.errno)
3301
2504
        # after rollback, don't reuse _FileMover
3302
2505
        self.past_renames = None
3303
2506
        self.pending_deletions = None