/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/git/tree.py

  • Committer: Jelmer Vernooij
  • Date: 2020-08-10 15:00:17 UTC
  • mfrom: (7490.40.99 work)
  • mto: This revision was merged to the branch mainline in revision 7521.
  • Revision ID: jelmer@jelmer.uk-20200810150017-vs7xnrd1vat4iktg
Merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
76
76
    TransportObjectStore,
77
77
    TransportRepo,
78
78
    )
 
79
from ..bzr.inventorytree import InventoryTreeChange
79
80
 
80
81
 
81
82
class GitTreeDirectory(_mod_tree.TreeDirectory):
82
83
 
83
 
    __slots__ = ['file_id', 'name', 'parent_id', 'children']
 
84
    __slots__ = ['file_id', 'name', 'parent_id']
84
85
 
85
86
    def __init__(self, file_id, name, parent_id):
86
87
        self.file_id = file_id
87
88
        self.name = name
88
89
        self.parent_id = parent_id
89
 
        # TODO(jelmer)
90
 
        self.children = {}
91
90
 
92
91
    @property
93
92
    def kind(self):
115
114
 
116
115
class GitTreeFile(_mod_tree.TreeFile):
117
116
 
118
 
    __slots__ = ['file_id', 'name', 'parent_id', 'text_size', 'text_sha1',
119
 
                 'executable']
 
117
    __slots__ = ['file_id', 'name', 'parent_id', 'text_size',
 
118
                 'executable', 'git_sha1']
120
119
 
121
120
    def __init__(self, file_id, name, parent_id, text_size=None,
122
 
                 text_sha1=None, executable=None):
 
121
                 git_sha1=None, executable=None):
123
122
        self.file_id = file_id
124
123
        self.name = name
125
124
        self.parent_id = parent_id
126
125
        self.text_size = text_size
127
 
        self.text_sha1 = text_sha1
 
126
        self.git_sha1 = git_sha1
128
127
        self.executable = executable
129
128
 
130
129
    @property
136
135
                self.file_id == other.file_id and
137
136
                self.name == other.name and
138
137
                self.parent_id == other.parent_id and
139
 
                self.text_sha1 == other.text_sha1 and
 
138
                self.git_sha1 == other.git_sha1 and
140
139
                self.text_size == other.text_size and
141
140
                self.executable == other.executable)
142
141
 
143
142
    def __repr__(self):
144
143
        return ("%s(file_id=%r, name=%r, parent_id=%r, text_size=%r, "
145
 
                "text_sha1=%r, executable=%r)") % (
 
144
                "git_sha1=%r, executable=%r)") % (
146
145
            type(self).__name__, self.file_id, self.name, self.parent_id,
147
 
            self.text_size, self.text_sha1, self.executable)
 
146
            self.text_size, self.git_sha1, self.executable)
148
147
 
149
148
    def copy(self):
150
149
        ret = self.__class__(
151
150
            self.file_id, self.name, self.parent_id)
152
 
        ret.text_sha1 = self.text_sha1
 
151
        ret.git_sha1 = self.git_sha1
153
152
        ret.text_size = self.text_size
154
153
        ret.executable = self.executable
155
154
        return ret
257
256
    return path
258
257
 
259
258
 
260
 
class GitRevisionTree(revisiontree.RevisionTree):
 
259
class GitTree(_mod_tree.Tree):
 
260
 
 
261
    def iter_git_objects(self):
 
262
        """Iterate over all the objects in the tree.
 
263
 
 
264
        :return :Yields tuples with (path, sha, mode)
 
265
        """
 
266
        raise NotImplementedError(self.iter_git_objects)
 
267
 
 
268
    def git_snapshot(self, want_unversioned=False):
 
269
        """Snapshot a tree, and return tree object.
 
270
 
 
271
        :return: Tree sha and set of extras
 
272
        """
 
273
        raise NotImplementedError(self.snapshot)
 
274
 
 
275
 
 
276
class GitRevisionTree(revisiontree.RevisionTree, GitTree):
261
277
    """Revision tree implementation based on Git objects."""
262
278
 
263
279
    def __init__(self, repository, revision_id):
279
295
                raise errors.NoSuchRevision(repository, revision_id)
280
296
            self.tree = commit.tree
281
297
 
 
298
    def git_snapshot(self, want_unversioned=False):
 
299
        return self.tree, set()
 
300
 
282
301
    def _submodule_info(self):
283
302
        if self._submodules is None:
284
303
            try:
496
515
            ie.reference_revision = self.mapping.revision_id_foreign_to_bzr(
497
516
                hexsha)
498
517
        else:
499
 
            data = store[hexsha].data
500
 
            ie.text_sha1 = osutils.sha_string(data)
501
 
            ie.text_size = len(data)
 
518
            ie.git_sha1 = hexsha
 
519
            ie.text_size = None
502
520
            ie.executable = mode_is_executable(mode)
503
521
        return ie
504
522
 
699
717
    def walkdirs(self, prefix=u""):
700
718
        (store, mode, hexsha) = self._lookup_path(prefix)
701
719
        todo = deque(
702
 
            [(store, encode_git_path(prefix), hexsha, self.path2id(prefix))])
 
720
            [(store, encode_git_path(prefix), hexsha)])
703
721
        while todo:
704
 
            store, path, tree_sha, parent_id = todo.popleft()
 
722
            store, path, tree_sha = todo.popleft()
705
723
            path_decoded = decode_git_path(path)
706
724
            tree = store[tree_sha]
707
725
            children = []
709
727
                if self.mapping.is_special_file(name):
710
728
                    continue
711
729
                child_path = posixpath.join(path, name)
712
 
                file_id = self.path2id(decode_git_path(child_path))
713
730
                if stat.S_ISDIR(mode):
714
 
                    todo.append((store, child_path, hexsha, file_id))
 
731
                    todo.append((store, child_path, hexsha))
715
732
                children.append(
716
733
                    (decode_git_path(child_path), decode_git_path(name),
717
734
                        mode_kind(mode), None,
718
 
                        file_id, mode_kind(mode)))
719
 
            yield (path_decoded, parent_id), children
 
735
                        mode_kind(mode)))
 
736
            yield path_decoded, children
720
737
 
721
738
    def preview_transform(self, pb=None):
722
739
        from .transform import GitTransformPreview
814
831
            newpath = None
815
832
        if oldpath is None and newpath is None:
816
833
            continue
817
 
        change = _mod_tree.TreeChange(
 
834
        change = InventoryTreeChange(
818
835
            fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
819
836
            (oldversioned, newversioned),
820
837
            (oldparent, newparent), (oldname, newname),
860
877
        parent_id = new_mapping.generate_file_id(parent_path)
861
878
        file_id = new_mapping.generate_file_id(path_decoded)
862
879
        ret.added.append(
863
 
            _mod_tree.TreeChange(
 
880
            InventoryTreeChange(
864
881
                file_id, (None, path_decoded), True,
865
882
                (False, True),
866
883
                (None, parent_id),
956
973
            fileid = mapping.generate_file_id(newpath_decoded)
957
974
        else:
958
975
            fileid = None
959
 
        yield _mod_tree.TreeChange(
 
976
        yield InventoryTreeChange(
960
977
            fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
961
978
            (oldversioned, newversioned),
962
979
            (oldparent, newparent), (oldname, newname),
971
988
    _matching_to_tree_format = None
972
989
    _test_mutable_trees_to_test_trees = None
973
990
 
 
991
    def __init__(self, source, target):
 
992
        super(InterGitTrees, self).__init__(source, target)
 
993
        if self.source.store == self.target.store:
 
994
            self.store = self.source.store
 
995
        else:
 
996
            self.store = OverlayObjectStore(
 
997
                [self.source.store, self.target.store])
 
998
        self.rename_detector = RenameDetector(self.store)
 
999
 
974
1000
    @classmethod
975
1001
    def is_compatible(cls, source, target):
976
 
        return (isinstance(source, GitRevisionTree) and
977
 
                isinstance(target, GitRevisionTree))
 
1002
        return isinstance(source, GitTree) and isinstance(target, GitTree)
978
1003
 
979
1004
    def compare(self, want_unchanged=False, specific_files=None,
980
1005
                extra_trees=None, require_versioned=False, include_root=False,
1012
1037
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1013
1038
                          require_versioned=False, extra_trees=None,
1014
1039
                          want_unversioned=False, include_trees=True):
1015
 
        raise NotImplementedError(self._iter_git_changes)
 
1040
        trees = [self.source]
 
1041
        if extra_trees is not None:
 
1042
            trees.extend(extra_trees)
 
1043
        if specific_files is not None:
 
1044
            specific_files = self.target.find_related_paths_across_trees(
 
1045
                specific_files, trees,
 
1046
                require_versioned=require_versioned)
 
1047
        # TODO(jelmer): Restrict to specific_files, for performance reasons.
 
1048
        with self.lock_read():
 
1049
            from_tree_sha, from_extras = self.source.git_snapshot(
 
1050
                want_unversioned=want_unversioned)
 
1051
            to_tree_sha, to_extras = self.target.git_snapshot(
 
1052
                want_unversioned=want_unversioned)
 
1053
            changes = tree_changes(
 
1054
                self.store, from_tree_sha, to_tree_sha,
 
1055
                include_trees=include_trees,
 
1056
                rename_detector=self.rename_detector,
 
1057
                want_unchanged=want_unchanged, change_type_same=True)
 
1058
            return changes, from_extras, to_extras
1016
1059
 
1017
1060
    def find_target_path(self, path, recurse='none'):
1018
1061
        ret = self.find_target_paths([path], recurse=recurse)
1067
1110
        return ret
1068
1111
 
1069
1112
 
1070
 
class InterGitRevisionTrees(InterGitTrees):
1071
 
    """InterTree that works between two git revision trees."""
1072
 
 
1073
 
    _matching_from_tree_format = None
1074
 
    _matching_to_tree_format = None
1075
 
    _test_mutable_trees_to_test_trees = None
1076
 
 
1077
 
    @classmethod
1078
 
    def is_compatible(cls, source, target):
1079
 
        return (isinstance(source, GitRevisionTree) and
1080
 
                isinstance(target, GitRevisionTree))
1081
 
 
1082
 
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1083
 
                          require_versioned=True, extra_trees=None,
1084
 
                          want_unversioned=False, include_trees=True):
1085
 
        trees = [self.source]
1086
 
        if extra_trees is not None:
1087
 
            trees.extend(extra_trees)
1088
 
        if specific_files is not None:
1089
 
            specific_files = self.target.find_related_paths_across_trees(
1090
 
                specific_files, trees,
1091
 
                require_versioned=require_versioned)
1092
 
 
1093
 
        if (self.source._repository._git.object_store !=
1094
 
                self.target._repository._git.object_store):
1095
 
            store = OverlayObjectStore(
1096
 
                [self.source._repository._git.object_store,
1097
 
                    self.target._repository._git.object_store])
1098
 
        else:
1099
 
            store = self.source._repository._git.object_store
1100
 
        rename_detector = RenameDetector(store)
1101
 
        changes = tree_changes(
1102
 
            store, self.source.tree, self.target.tree,
1103
 
            want_unchanged=want_unchanged, include_trees=include_trees,
1104
 
            change_type_same=True, rename_detector=rename_detector)
1105
 
        return changes, set(), set()
1106
 
 
1107
 
 
1108
 
_mod_tree.InterTree.register_optimiser(InterGitRevisionTrees)
1109
 
 
1110
 
 
1111
 
class MutableGitIndexTree(mutabletree.MutableTree):
 
1113
_mod_tree.InterTree.register_optimiser(InterGitTrees)
 
1114
 
 
1115
 
 
1116
class MutableGitIndexTree(mutabletree.MutableTree, GitTree):
1112
1117
 
1113
1118
    def __init__(self):
1114
1119
        self._lock_mode = None
1117
1122
        self._index_dirty = False
1118
1123
        self._submodules = None
1119
1124
 
 
1125
    def git_snapshot(self, want_unversioned=False):
 
1126
        return snapshot_workingtree(self, want_unversioned=want_unversioned)
 
1127
 
1120
1128
    def is_versioned(self, path):
1121
1129
        with self.lock_read():
1122
1130
            path = encode_git_path(path.rstrip('/'))
1136
1144
        if self._lock_mode is None:
1137
1145
            raise errors.ObjectNotLocked(self)
1138
1146
        self._versioned_dirs = set()
1139
 
        for p, i in self._recurse_index_entries():
 
1147
        for p, sha, mode in self.iter_git_objects():
1140
1148
            self._ensure_versioned_dir(posixpath.dirname(p))
1141
1149
 
1142
1150
    def _ensure_versioned_dir(self, dirname):
1295
1303
        if self._versioned_dirs is not None:
1296
1304
            self._ensure_versioned_dir(index_path)
1297
1305
 
 
1306
    def iter_git_objects(self):
 
1307
        for p, entry in self._recurse_index_entries():
 
1308
            yield p, entry.sha, entry.mode
 
1309
 
1298
1310
    def _recurse_index_entries(self, index=None, basepath=b"",
1299
1311
                               recurse_nested=False):
1300
1312
        # Iterate over all index entries
1381
1393
        elif kind == 'tree-reference':
1382
1394
            ie.reference_revision = self.get_reference_revision(path)
1383
1395
        else:
1384
 
            try:
1385
 
                data = self.get_file_text(path)
1386
 
            except errors.NoSuchFile:
1387
 
                data = None
1388
 
            except IOError as e:
1389
 
                if e.errno != errno.ENOENT:
1390
 
                    raise
1391
 
                data = None
1392
 
            if data is None:
1393
 
                data = self.branch.repository._git.object_store[sha].data
1394
 
            ie.text_sha1 = osutils.sha_string(data)
1395
 
            ie.text_size = len(data)
 
1396
            ie.git_sha1 = sha
 
1397
            ie.text_size = size
1396
1398
            ie.executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
1397
1399
        return ie
1398
1400
 
1636
1638
        from .transform import GitTransformPreview
1637
1639
        return GitTransformPreview(self, pb=pb)
1638
1640
 
1639
 
 
1640
 
class InterToIndexGitTree(InterGitTrees):
1641
 
    """InterTree that works between a Git revision tree and an index."""
1642
 
 
1643
 
    def __init__(self, source, target):
1644
 
        super(InterToIndexGitTree, self).__init__(source, target)
1645
 
        if self.source.store == self.target.store:
1646
 
            self.store = self.source.store
1647
 
        else:
1648
 
            self.store = OverlayObjectStore(
1649
 
                [self.source.store, self.target.store])
1650
 
        self.rename_detector = RenameDetector(self.store)
1651
 
 
1652
 
    @classmethod
1653
 
    def is_compatible(cls, source, target):
1654
 
        return (isinstance(source, GitRevisionTree) and
1655
 
                isinstance(target, MutableGitIndexTree))
1656
 
 
1657
 
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1658
 
                          require_versioned=False, extra_trees=None,
1659
 
                          want_unversioned=False, include_trees=True):
1660
 
        trees = [self.source]
1661
 
        if extra_trees is not None:
1662
 
            trees.extend(extra_trees)
1663
 
        if specific_files is not None:
1664
 
            specific_files = self.target.find_related_paths_across_trees(
1665
 
                specific_files, trees,
1666
 
                require_versioned=require_versioned)
1667
 
        # TODO(jelmer): Restrict to specific_files, for performance reasons.
1668
 
        with self.lock_read():
1669
 
            changes, target_extras = changes_between_git_tree_and_working_copy(
1670
 
                self.source.store, self.source.tree,
1671
 
                self.target, want_unchanged=want_unchanged,
1672
 
                want_unversioned=want_unversioned,
1673
 
                rename_detector=self.rename_detector,
1674
 
                include_trees=include_trees)
1675
 
            return changes, set(), target_extras
1676
 
 
1677
 
 
1678
 
_mod_tree.InterTree.register_optimiser(InterToIndexGitTree)
1679
 
 
1680
 
 
1681
 
class InterFromIndexGitTree(InterGitTrees):
1682
 
    """InterTree that works between a Git revision tree and an index."""
1683
 
 
1684
 
    def __init__(self, source, target):
1685
 
        super(InterFromIndexGitTree, self).__init__(source, target)
1686
 
        if self.source.store == self.target.store:
1687
 
            self.store = self.source.store
1688
 
        else:
1689
 
            self.store = OverlayObjectStore(
1690
 
                [self.source.store, self.target.store])
1691
 
        self.rename_detector = RenameDetector(self.store)
1692
 
 
1693
 
    @classmethod
1694
 
    def is_compatible(cls, source, target):
1695
 
        return (isinstance(target, GitRevisionTree) and
1696
 
                isinstance(source, MutableGitIndexTree))
1697
 
 
1698
 
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1699
 
                          require_versioned=False, extra_trees=None,
1700
 
                          want_unversioned=False, include_trees=True):
1701
 
        trees = [self.source]
1702
 
        if extra_trees is not None:
1703
 
            trees.extend(extra_trees)
1704
 
        if specific_files is not None:
1705
 
            specific_files = self.target.find_related_paths_across_trees(
1706
 
                specific_files, trees,
1707
 
                require_versioned=require_versioned)
1708
 
        # TODO(jelmer): Restrict to specific_files, for performance reasons.
1709
 
        with self.lock_read():
1710
 
            from_tree_sha, extras = snapshot_workingtree(self.source, want_unversioned=want_unversioned)
1711
 
            return tree_changes(
1712
 
                self.store, from_tree_sha, self.target.tree,
1713
 
                include_trees=include_trees,
1714
 
                rename_detector=self.rename_detector,
1715
 
                want_unchanged=want_unchanged, change_type_same=True), extras
1716
 
 
1717
 
 
1718
 
_mod_tree.InterTree.register_optimiser(InterFromIndexGitTree)
1719
 
 
1720
 
 
1721
 
class InterIndexGitTree(InterGitTrees):
1722
 
    """InterTree that works between a Git revision tree and an index."""
1723
 
 
1724
 
    def __init__(self, source, target):
1725
 
        super(InterIndexGitTree, self).__init__(source, target)
1726
 
        if self.source.store == self.target.store:
1727
 
            self.store = self.source.store
1728
 
        else:
1729
 
            self.store = OverlayObjectStore(
1730
 
                [self.source.store, self.target.store])
1731
 
        self.rename_detector = RenameDetector(self.store)
1732
 
 
1733
 
    @classmethod
1734
 
    def is_compatible(cls, source, target):
1735
 
        return (isinstance(target, MutableGitIndexTree) and
1736
 
                isinstance(source, MutableGitIndexTree))
1737
 
 
1738
 
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1739
 
                          require_versioned=False, extra_trees=None,
1740
 
                          want_unversioned=False, include_trees=True):
1741
 
        trees = [self.source]
1742
 
        if extra_trees is not None:
1743
 
            trees.extend(extra_trees)
1744
 
        if specific_files is not None:
1745
 
            specific_files = self.target.find_related_paths_across_trees(
1746
 
                specific_files, trees,
1747
 
                require_versioned=require_versioned)
1748
 
        # TODO(jelmer): Restrict to specific_files, for performance reasons.
1749
 
        with self.lock_read():
1750
 
            from_tree_sha, from_extras = snapshot_workingtree(
1751
 
                self.source, want_unversioned=want_unversioned)
1752
 
            to_tree_sha, to_extras = snapshot_workingtree(
1753
 
                self.target, want_unversioned=want_unversioned)
1754
 
            changes = tree_changes(
1755
 
                self.store, from_tree_sha, to_tree_sha,
1756
 
                include_trees=include_trees,
1757
 
                rename_detector=self.rename_detector,
1758
 
                want_unchanged=want_unchanged, change_type_same=True)
1759
 
            return changes, from_extras, to_extras
1760
 
 
1761
 
 
1762
 
_mod_tree.InterTree.register_optimiser(InterIndexGitTree)
 
1641
    def has_changes(self, _from_tree=None):
 
1642
        """Quickly check that the tree contains at least one commitable change.
 
1643
 
 
1644
        :param _from_tree: tree to compare against to find changes (default to
 
1645
            the basis tree and is intended to be used by tests).
 
1646
 
 
1647
        :return: True if a change is found. False otherwise
 
1648
        """
 
1649
        with self.lock_read():
 
1650
            # Check pending merges
 
1651
            if len(self.get_parent_ids()) > 1:
 
1652
                return True
 
1653
            if _from_tree is None:
 
1654
                _from_tree = self.basis_tree()
 
1655
            changes = self.iter_changes(_from_tree)
 
1656
            if self.supports_symlinks():
 
1657
                # Fast path for has_changes.
 
1658
                try:
 
1659
                    change = next(changes)
 
1660
                    if change.path[1] == '':
 
1661
                        next(changes)
 
1662
                    return True
 
1663
                except StopIteration:
 
1664
                    # No changes
 
1665
                    return False
 
1666
            else:
 
1667
                # Slow path for has_changes.
 
1668
                # Handle platforms that do not support symlinks in the
 
1669
                # conditional below. This is slower than the try/except
 
1670
                # approach below that but we don't have a choice as we
 
1671
                # need to be sure that all symlinks are removed from the
 
1672
                # entire changeset. This is because in platforms that
 
1673
                # do not support symlinks, they show up as None in the
 
1674
                # working copy as compared to the repository.
 
1675
                # Also, exclude root as mention in the above fast path.
 
1676
                changes = filter(
 
1677
                    lambda c: c[6][0] != 'symlink' and c[4] != (None, None),
 
1678
                    changes)
 
1679
                try:
 
1680
                    next(iter(changes))
 
1681
                except StopIteration:
 
1682
                    return False
 
1683
                return True
1763
1684
 
1764
1685
 
1765
1686
def snapshot_workingtree(target, want_unversioned=False):
1809
1730
                        target.store.add_object(blob)
1810
1731
                blobs[path] = (live_entry.sha, cleanup_mode(live_entry.mode))
1811
1732
    if want_unversioned:
1812
 
        for e in target._iter_files_recursive(include_dirs=False):
 
1733
        for extra in target._iter_files_recursive(include_dirs=False):
1813
1734
            try:
1814
 
                e, accessible = osutils.normalized_filename(e)
 
1735
                extra, accessible = osutils.normalized_filename(extra)
1815
1736
            except UnicodeDecodeError:
1816
1737
                raise errors.BadFilenameEncoding(
1817
 
                    e, osutils._fs_enc)
1818
 
            np = encode_git_path(e)
 
1738
                    extra, osutils._fs_enc)
 
1739
            np = encode_git_path(extra)
1819
1740
            if np in blobs:
1820
1741
                continue
1821
 
            st = target._lstat(e)
 
1742
            st = target._lstat(extra)
1822
1743
            if stat.S_ISDIR(st.st_mode):
1823
1744
                blob = Tree()
1824
1745
            elif stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
1825
1746
                blob = blob_from_path_and_stat(
1826
 
                    target.abspath(e).encode(osutils._fs_enc), st)
 
1747
                    target.abspath(extra).encode(osutils._fs_enc), st)
1827
1748
            else:
1828
1749
                continue
1829
1750
            target.store.add_object(blob)
1831
1752
            extras.add(np)
1832
1753
    return commit_tree(
1833
1754
        target.store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()]), extras
1834
 
 
1835
 
 
1836
 
def changes_between_git_tree_and_working_copy(source_store, from_tree_sha, target,
1837
 
                                              want_unchanged=False,
1838
 
                                              want_unversioned=False,
1839
 
                                              rename_detector=None,
1840
 
                                              include_trees=True):
1841
 
    """Determine the changes between a git tree and a working tree with index.
1842
 
 
1843
 
    """
1844
 
    to_tree_sha, extras = snapshot_workingtree(target, want_unversioned=want_unversioned)
1845
 
    store = OverlayObjectStore([source_store, target.store])
1846
 
    return tree_changes(
1847
 
        store, from_tree_sha, to_tree_sha, include_trees=include_trees,
1848
 
        rename_detector=rename_detector,
1849
 
        want_unchanged=want_unchanged, change_type_same=True), extras