/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:
17
17
 
18
18
"""Git Trees."""
19
19
 
20
 
from __future__ import absolute_import
21
 
 
22
20
from collections import deque
23
21
import errno
24
22
from io import BytesIO
66
64
    CURRENT_REVISION,
67
65
    NULL_REVISION,
68
66
    )
69
 
from ..sixish import (
70
 
    text_type,
71
 
    viewitems,
72
 
    )
73
67
 
74
68
from .mapping import (
75
69
    encode_git_path,
82
76
    TransportObjectStore,
83
77
    TransportRepo,
84
78
    )
 
79
from ..bzr.inventorytree import InventoryTreeChange
85
80
 
86
81
 
87
82
class GitTreeDirectory(_mod_tree.TreeDirectory):
88
83
 
89
 
    __slots__ = ['file_id', 'name', 'parent_id', 'children']
 
84
    __slots__ = ['file_id', 'name', 'parent_id']
90
85
 
91
86
    def __init__(self, file_id, name, parent_id):
92
87
        self.file_id = file_id
93
88
        self.name = name
94
89
        self.parent_id = parent_id
95
 
        # TODO(jelmer)
96
 
        self.children = {}
97
90
 
98
91
    @property
99
92
    def kind(self):
121
114
 
122
115
class GitTreeFile(_mod_tree.TreeFile):
123
116
 
124
 
    __slots__ = ['file_id', 'name', 'parent_id', 'text_size', 'text_sha1',
125
 
                 'executable']
 
117
    __slots__ = ['file_id', 'name', 'parent_id', 'text_size',
 
118
                 'executable', 'git_sha1']
126
119
 
127
120
    def __init__(self, file_id, name, parent_id, text_size=None,
128
 
                 text_sha1=None, executable=None):
 
121
                 git_sha1=None, executable=None):
129
122
        self.file_id = file_id
130
123
        self.name = name
131
124
        self.parent_id = parent_id
132
125
        self.text_size = text_size
133
 
        self.text_sha1 = text_sha1
 
126
        self.git_sha1 = git_sha1
134
127
        self.executable = executable
135
128
 
136
129
    @property
142
135
                self.file_id == other.file_id and
143
136
                self.name == other.name and
144
137
                self.parent_id == other.parent_id and
145
 
                self.text_sha1 == other.text_sha1 and
 
138
                self.git_sha1 == other.git_sha1 and
146
139
                self.text_size == other.text_size and
147
140
                self.executable == other.executable)
148
141
 
149
142
    def __repr__(self):
150
143
        return ("%s(file_id=%r, name=%r, parent_id=%r, text_size=%r, "
151
 
                "text_sha1=%r, executable=%r)") % (
 
144
                "git_sha1=%r, executable=%r)") % (
152
145
            type(self).__name__, self.file_id, self.name, self.parent_id,
153
 
            self.text_size, self.text_sha1, self.executable)
 
146
            self.text_size, self.git_sha1, self.executable)
154
147
 
155
148
    def copy(self):
156
149
        ret = self.__class__(
157
150
            self.file_id, self.name, self.parent_id)
158
 
        ret.text_sha1 = self.text_sha1
 
151
        ret.git_sha1 = self.git_sha1
159
152
        ret.text_size = self.text_size
160
153
        ret.executable = self.executable
161
154
        return ret
263
256
    return path
264
257
 
265
258
 
266
 
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):
267
277
    """Revision tree implementation based on Git objects."""
268
278
 
269
279
    def __init__(self, repository, revision_id):
285
295
                raise errors.NoSuchRevision(repository, revision_id)
286
296
            self.tree = commit.tree
287
297
 
 
298
    def git_snapshot(self, want_unversioned=False):
 
299
        return self.tree, set()
 
300
 
288
301
    def _submodule_info(self):
289
302
        if self._submodules is None:
290
303
            try:
502
515
            ie.reference_revision = self.mapping.revision_id_foreign_to_bzr(
503
516
                hexsha)
504
517
        else:
505
 
            data = store[hexsha].data
506
 
            ie.text_sha1 = osutils.sha_string(data)
507
 
            ie.text_size = len(data)
 
518
            ie.git_sha1 = hexsha
 
519
            ie.text_size = None
508
520
            ie.executable = mode_is_executable(mode)
509
521
        return ie
510
522
 
705
717
    def walkdirs(self, prefix=u""):
706
718
        (store, mode, hexsha) = self._lookup_path(prefix)
707
719
        todo = deque(
708
 
            [(store, encode_git_path(prefix), hexsha, self.path2id(prefix))])
 
720
            [(store, encode_git_path(prefix), hexsha)])
709
721
        while todo:
710
 
            store, path, tree_sha, parent_id = todo.popleft()
 
722
            store, path, tree_sha = todo.popleft()
711
723
            path_decoded = decode_git_path(path)
712
724
            tree = store[tree_sha]
713
725
            children = []
715
727
                if self.mapping.is_special_file(name):
716
728
                    continue
717
729
                child_path = posixpath.join(path, name)
718
 
                file_id = self.path2id(decode_git_path(child_path))
719
730
                if stat.S_ISDIR(mode):
720
 
                    todo.append((store, child_path, hexsha, file_id))
 
731
                    todo.append((store, child_path, hexsha))
721
732
                children.append(
722
733
                    (decode_git_path(child_path), decode_git_path(name),
723
734
                        mode_kind(mode), None,
724
 
                        file_id, mode_kind(mode)))
725
 
            yield (path_decoded, parent_id), children
 
735
                        mode_kind(mode)))
 
736
            yield path_decoded, children
 
737
 
 
738
    def preview_transform(self, pb=None):
 
739
        from .transform import GitTransformPreview
 
740
        return GitTransformPreview(self, pb=pb)
726
741
 
727
742
 
728
743
def tree_delta_from_git_changes(changes, mappings,
816
831
            newpath = None
817
832
        if oldpath is None and newpath is None:
818
833
            continue
819
 
        change = _mod_tree.TreeChange(
 
834
        change = InventoryTreeChange(
820
835
            fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
821
836
            (oldversioned, newversioned),
822
837
            (oldparent, newparent), (oldname, newname),
862
877
        parent_id = new_mapping.generate_file_id(parent_path)
863
878
        file_id = new_mapping.generate_file_id(path_decoded)
864
879
        ret.added.append(
865
 
            _mod_tree.TreeChange(
 
880
            InventoryTreeChange(
866
881
                file_id, (None, path_decoded), True,
867
882
                (False, True),
868
883
                (None, parent_id),
958
973
            fileid = mapping.generate_file_id(newpath_decoded)
959
974
        else:
960
975
            fileid = None
961
 
        yield _mod_tree.TreeChange(
 
976
        yield InventoryTreeChange(
962
977
            fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
963
978
            (oldversioned, newversioned),
964
979
            (oldparent, newparent), (oldname, newname),
973
988
    _matching_to_tree_format = None
974
989
    _test_mutable_trees_to_test_trees = None
975
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
 
976
1000
    @classmethod
977
1001
    def is_compatible(cls, source, target):
978
 
        return (isinstance(source, GitRevisionTree) and
979
 
                isinstance(target, GitRevisionTree))
 
1002
        return isinstance(source, GitTree) and isinstance(target, GitTree)
980
1003
 
981
1004
    def compare(self, want_unchanged=False, specific_files=None,
982
1005
                extra_trees=None, require_versioned=False, include_root=False,
1014
1037
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1015
1038
                          require_versioned=False, extra_trees=None,
1016
1039
                          want_unversioned=False, include_trees=True):
1017
 
        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
1018
1059
 
1019
1060
    def find_target_path(self, path, recurse='none'):
1020
1061
        ret = self.find_target_paths([path], recurse=recurse)
1069
1110
        return ret
1070
1111
 
1071
1112
 
1072
 
class InterGitRevisionTrees(InterGitTrees):
1073
 
    """InterTree that works between two git revision trees."""
1074
 
 
1075
 
    _matching_from_tree_format = None
1076
 
    _matching_to_tree_format = None
1077
 
    _test_mutable_trees_to_test_trees = None
1078
 
 
1079
 
    @classmethod
1080
 
    def is_compatible(cls, source, target):
1081
 
        return (isinstance(source, GitRevisionTree) and
1082
 
                isinstance(target, GitRevisionTree))
1083
 
 
1084
 
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1085
 
                          require_versioned=True, extra_trees=None,
1086
 
                          want_unversioned=False, include_trees=True):
1087
 
        trees = [self.source]
1088
 
        if extra_trees is not None:
1089
 
            trees.extend(extra_trees)
1090
 
        if specific_files is not None:
1091
 
            specific_files = self.target.find_related_paths_across_trees(
1092
 
                specific_files, trees,
1093
 
                require_versioned=require_versioned)
1094
 
 
1095
 
        if (self.source._repository._git.object_store !=
1096
 
                self.target._repository._git.object_store):
1097
 
            store = OverlayObjectStore(
1098
 
                [self.source._repository._git.object_store,
1099
 
                    self.target._repository._git.object_store])
1100
 
        else:
1101
 
            store = self.source._repository._git.object_store
1102
 
        rename_detector = RenameDetector(store)
1103
 
        changes = tree_changes(
1104
 
            store, self.source.tree, self.target.tree,
1105
 
            want_unchanged=want_unchanged, include_trees=include_trees,
1106
 
            change_type_same=True, rename_detector=rename_detector)
1107
 
        return changes, set(), set()
1108
 
 
1109
 
 
1110
 
_mod_tree.InterTree.register_optimiser(InterGitRevisionTrees)
1111
 
 
1112
 
 
1113
 
class MutableGitIndexTree(mutabletree.MutableTree):
 
1113
_mod_tree.InterTree.register_optimiser(InterGitTrees)
 
1114
 
 
1115
 
 
1116
class MutableGitIndexTree(mutabletree.MutableTree, GitTree):
1114
1117
 
1115
1118
    def __init__(self):
1116
1119
        self._lock_mode = None
1119
1122
        self._index_dirty = False
1120
1123
        self._submodules = None
1121
1124
 
 
1125
    def git_snapshot(self, want_unversioned=False):
 
1126
        return snapshot_workingtree(self, want_unversioned=want_unversioned)
 
1127
 
1122
1128
    def is_versioned(self, path):
1123
1129
        with self.lock_read():
1124
1130
            path = encode_git_path(path.rstrip('/'))
1138
1144
        if self._lock_mode is None:
1139
1145
            raise errors.ObjectNotLocked(self)
1140
1146
        self._versioned_dirs = set()
1141
 
        for p, i in self._recurse_index_entries():
 
1147
        for p, sha, mode in self.iter_git_objects():
1142
1148
            self._ensure_versioned_dir(posixpath.dirname(p))
1143
1149
 
1144
1150
    def _ensure_versioned_dir(self, dirname):
1297
1303
        if self._versioned_dirs is not None:
1298
1304
            self._ensure_versioned_dir(index_path)
1299
1305
 
 
1306
    def iter_git_objects(self):
 
1307
        for p, entry in self._recurse_index_entries():
 
1308
            yield p, entry.sha, entry.mode
 
1309
 
1300
1310
    def _recurse_index_entries(self, index=None, basepath=b"",
1301
1311
                               recurse_nested=False):
1302
1312
        # Iterate over all index entries
1351
1361
                    key = (posixpath.dirname(path), path)
1352
1362
                    if key not in ret and self.is_versioned(path):
1353
1363
                        ret[key] = self._get_dir_ie(path, self.path2id(key[0]))
1354
 
            return ((path, ie) for ((_, path), ie) in sorted(viewitems(ret)))
 
1364
            return ((path, ie) for ((_, path), ie) in sorted(ret.items()))
1355
1365
 
1356
1366
    def iter_references(self):
1357
1367
        if self.supports_tree_reference():
1366
1376
                                posixpath.basename(path).strip("/"), parent_id)
1367
1377
 
1368
1378
    def _get_file_ie(self, name, path, value, parent_id):
1369
 
        if not isinstance(name, text_type):
 
1379
        if not isinstance(name, str):
1370
1380
            raise TypeError(name)
1371
 
        if not isinstance(path, text_type):
 
1381
        if not isinstance(path, str):
1372
1382
            raise TypeError(path)
1373
1383
        if not isinstance(value, tuple) or len(value) != 10:
1374
1384
            raise TypeError(value)
1383
1393
        elif kind == 'tree-reference':
1384
1394
            ie.reference_revision = self.get_reference_revision(path)
1385
1395
        else:
1386
 
            try:
1387
 
                data = self.get_file_text(path)
1388
 
            except errors.NoSuchFile:
1389
 
                data = None
1390
 
            except IOError as e:
1391
 
                if e.errno != errno.ENOENT:
1392
 
                    raise
1393
 
                data = None
1394
 
            if data is None:
1395
 
                data = self.branch.repository._git.object_store[sha].data
1396
 
            ie.text_sha1 = osutils.sha_string(data)
1397
 
            ie.text_size = len(data)
 
1396
            ie.git_sha1 = sha
 
1397
            ie.text_size = size
1398
1398
            ie.executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
1399
1399
        return ie
1400
1400
 
1631
1631
        raise NotImplementedError(self._live_entry)
1632
1632
 
1633
1633
    def transform(self, pb=None):
1634
 
        from ..transform import TreeTransform
1635
 
        return TreeTransform(self, pb=pb)
1636
 
 
1637
 
 
1638
 
 
1639
 
class InterToIndexGitTree(InterGitTrees):
1640
 
    """InterTree that works between a Git revision tree and an index."""
1641
 
 
1642
 
    def __init__(self, source, target):
1643
 
        super(InterToIndexGitTree, self).__init__(source, target)
1644
 
        if self.source.store == self.target.store:
1645
 
            self.store = self.source.store
1646
 
        else:
1647
 
            self.store = OverlayObjectStore(
1648
 
                [self.source.store, self.target.store])
1649
 
        self.rename_detector = RenameDetector(self.store)
1650
 
 
1651
 
    @classmethod
1652
 
    def is_compatible(cls, source, target):
1653
 
        return (isinstance(source, GitRevisionTree) and
1654
 
                isinstance(target, MutableGitIndexTree))
1655
 
 
1656
 
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1657
 
                          require_versioned=False, extra_trees=None,
1658
 
                          want_unversioned=False, include_trees=True):
1659
 
        trees = [self.source]
1660
 
        if extra_trees is not None:
1661
 
            trees.extend(extra_trees)
1662
 
        if specific_files is not None:
1663
 
            specific_files = self.target.find_related_paths_across_trees(
1664
 
                specific_files, trees,
1665
 
                require_versioned=require_versioned)
1666
 
        # TODO(jelmer): Restrict to specific_files, for performance reasons.
1667
 
        with self.lock_read():
1668
 
            changes, target_extras = changes_between_git_tree_and_working_copy(
1669
 
                self.source.store, self.source.tree,
1670
 
                self.target, want_unchanged=want_unchanged,
1671
 
                want_unversioned=want_unversioned,
1672
 
                rename_detector=self.rename_detector,
1673
 
                include_trees=include_trees)
1674
 
            return changes, set(), target_extras
1675
 
 
1676
 
 
1677
 
_mod_tree.InterTree.register_optimiser(InterToIndexGitTree)
1678
 
 
1679
 
 
1680
 
class InterFromIndexGitTree(InterGitTrees):
1681
 
    """InterTree that works between a Git revision tree and an index."""
1682
 
 
1683
 
    def __init__(self, source, target):
1684
 
        super(InterFromIndexGitTree, self).__init__(source, target)
1685
 
        if self.source.store == self.target.store:
1686
 
            self.store = self.source.store
1687
 
        else:
1688
 
            self.store = OverlayObjectStore(
1689
 
                [self.source.store, self.target.store])
1690
 
        self.rename_detector = RenameDetector(self.store)
1691
 
 
1692
 
    @classmethod
1693
 
    def is_compatible(cls, source, target):
1694
 
        return (isinstance(target, GitRevisionTree) and
1695
 
                isinstance(source, MutableGitIndexTree))
1696
 
 
1697
 
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1698
 
                          require_versioned=False, extra_trees=None,
1699
 
                          want_unversioned=False, include_trees=True):
1700
 
        trees = [self.source]
1701
 
        if extra_trees is not None:
1702
 
            trees.extend(extra_trees)
1703
 
        if specific_files is not None:
1704
 
            specific_files = self.target.find_related_paths_across_trees(
1705
 
                specific_files, trees,
1706
 
                require_versioned=require_versioned)
1707
 
        # TODO(jelmer): Restrict to specific_files, for performance reasons.
1708
 
        with self.lock_read():
1709
 
            from_tree_sha, extras = snapshot_workingtree(self.source, want_unversioned=want_unversioned)
1710
 
            return tree_changes(
1711
 
                self.store, from_tree_sha, self.target.tree,
1712
 
                include_trees=include_trees,
1713
 
                rename_detector=self.rename_detector,
1714
 
                want_unchanged=want_unchanged, change_type_same=True), extras
1715
 
 
1716
 
 
1717
 
_mod_tree.InterTree.register_optimiser(InterFromIndexGitTree)
1718
 
 
1719
 
 
1720
 
class InterIndexGitTree(InterGitTrees):
1721
 
    """InterTree that works between a Git revision tree and an index."""
1722
 
 
1723
 
    def __init__(self, source, target):
1724
 
        super(InterIndexGitTree, self).__init__(source, target)
1725
 
        if self.source.store == self.target.store:
1726
 
            self.store = self.source.store
1727
 
        else:
1728
 
            self.store = OverlayObjectStore(
1729
 
                [self.source.store, self.target.store])
1730
 
        self.rename_detector = RenameDetector(self.store)
1731
 
 
1732
 
    @classmethod
1733
 
    def is_compatible(cls, source, target):
1734
 
        return (isinstance(target, MutableGitIndexTree) and
1735
 
                isinstance(source, MutableGitIndexTree))
1736
 
 
1737
 
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1738
 
                          require_versioned=False, extra_trees=None,
1739
 
                          want_unversioned=False, include_trees=True):
1740
 
        trees = [self.source]
1741
 
        if extra_trees is not None:
1742
 
            trees.extend(extra_trees)
1743
 
        if specific_files is not None:
1744
 
            specific_files = self.target.find_related_paths_across_trees(
1745
 
                specific_files, trees,
1746
 
                require_versioned=require_versioned)
1747
 
        # TODO(jelmer): Restrict to specific_files, for performance reasons.
1748
 
        with self.lock_read():
1749
 
            from_tree_sha, from_extras = snapshot_workingtree(
1750
 
                self.source, want_unversioned=want_unversioned)
1751
 
            to_tree_sha, to_extras = snapshot_workingtree(
1752
 
                self.target, want_unversioned=want_unversioned)
1753
 
            changes = tree_changes(
1754
 
                self.store, from_tree_sha, to_tree_sha,
1755
 
                include_trees=include_trees,
1756
 
                rename_detector=self.rename_detector,
1757
 
                want_unchanged=want_unchanged, change_type_same=True)
1758
 
            return changes, from_extras, to_extras
1759
 
 
1760
 
 
1761
 
_mod_tree.InterTree.register_optimiser(InterIndexGitTree)
 
1634
        from .transform import GitTreeTransform
 
1635
        return GitTreeTransform(self, pb=pb)
 
1636
 
 
1637
    def preview_transform(self, pb=None):
 
1638
        from .transform import GitTransformPreview
 
1639
        return GitTransformPreview(self, pb=pb)
 
1640
 
 
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
1762
1684
 
1763
1685
 
1764
1686
def snapshot_workingtree(target, want_unversioned=False):
1808
1730
                        target.store.add_object(blob)
1809
1731
                blobs[path] = (live_entry.sha, cleanup_mode(live_entry.mode))
1810
1732
    if want_unversioned:
1811
 
        for e in target._iter_files_recursive(include_dirs=False):
 
1733
        for extra in target._iter_files_recursive(include_dirs=False):
1812
1734
            try:
1813
 
                e, accessible = osutils.normalized_filename(e)
 
1735
                extra, accessible = osutils.normalized_filename(extra)
1814
1736
            except UnicodeDecodeError:
1815
1737
                raise errors.BadFilenameEncoding(
1816
 
                    e, osutils._fs_enc)
1817
 
            np = encode_git_path(e)
 
1738
                    extra, osutils._fs_enc)
 
1739
            np = encode_git_path(extra)
1818
1740
            if np in blobs:
1819
1741
                continue
1820
 
            st = target._lstat(e)
 
1742
            st = target._lstat(extra)
1821
1743
            if stat.S_ISDIR(st.st_mode):
1822
1744
                blob = Tree()
1823
1745
            elif stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
1824
1746
                blob = blob_from_path_and_stat(
1825
 
                    target.abspath(e).encode(osutils._fs_enc), st)
 
1747
                    target.abspath(extra).encode(osutils._fs_enc), st)
1826
1748
            else:
1827
1749
                continue
1828
1750
            target.store.add_object(blob)
1830
1752
            extras.add(np)
1831
1753
    return commit_tree(
1832
1754
        target.store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()]), extras
1833
 
 
1834
 
 
1835
 
def changes_between_git_tree_and_working_copy(source_store, from_tree_sha, target,
1836
 
                                              want_unchanged=False,
1837
 
                                              want_unversioned=False,
1838
 
                                              rename_detector=None,
1839
 
                                              include_trees=True):
1840
 
    """Determine the changes between a git tree and a working tree with index.
1841
 
 
1842
 
    """
1843
 
    to_tree_sha, extras = snapshot_workingtree(target, want_unversioned=want_unversioned)
1844
 
    store = OverlayObjectStore([source_store, target.store])
1845
 
    return tree_changes(
1846
 
        store, from_tree_sha, to_tree_sha, include_trees=include_trees,
1847
 
        rename_detector=rename_detector,
1848
 
        want_unchanged=want_unchanged, change_type_same=True), extras