260
class GitRevisionTree(revisiontree.RevisionTree):
259
class GitTree(_mod_tree.Tree):
261
def iter_git_objects(self):
262
"""Iterate over all the objects in the tree.
264
:return :Yields tuples with (path, sha, mode)
266
raise NotImplementedError(self.iter_git_objects)
268
def git_snapshot(self, want_unversioned=False):
269
"""Snapshot a tree, and return tree object.
271
:return: Tree sha and set of extras
273
raise NotImplementedError(self.snapshot)
275
def preview_transform(self, pb=None):
276
from .transform import GitTransformPreview
277
return GitTransformPreview(self, pb=pb)
279
def find_related_paths_across_trees(self, paths, trees=[],
280
require_versioned=True):
283
if require_versioned:
284
trees = [self] + (trees if trees is not None else [])
288
if t.is_versioned(p):
293
raise errors.PathsNotVersionedError(unversioned)
294
return filter(self.is_versioned, paths)
296
def _submodule_info(self):
297
if self._submodules is None:
299
with self.get_file('.gitmodules') as f:
300
config = GitConfigFile.from_file(f)
303
for path, url, section in parse_submodules(config)}
304
except errors.NoSuchFile:
305
self._submodules = {}
306
return self._submodules
309
class GitRevisionTree(revisiontree.RevisionTree, GitTree):
261
310
"""Revision tree implementation based on Git objects."""
263
312
def __init__(self, repository, revision_id):
1012
1030
def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1013
1031
require_versioned=False, extra_trees=None,
1014
1032
want_unversioned=False, include_trees=True):
1015
raise NotImplementedError(self._iter_git_changes)
1033
trees = [self.source]
1034
if extra_trees is not None:
1035
trees.extend(extra_trees)
1036
if specific_files is not None:
1037
specific_files = self.target.find_related_paths_across_trees(
1038
specific_files, trees,
1039
require_versioned=require_versioned)
1040
# TODO(jelmer): Restrict to specific_files, for performance reasons.
1041
with self.lock_read():
1042
from_tree_sha, from_extras = self.source.git_snapshot(
1043
want_unversioned=want_unversioned)
1044
to_tree_sha, to_extras = self.target.git_snapshot(
1045
want_unversioned=want_unversioned)
1046
changes = tree_changes(
1047
self.store, from_tree_sha, to_tree_sha,
1048
include_trees=include_trees,
1049
rename_detector=self.rename_detector,
1050
want_unchanged=want_unchanged, change_type_same=True)
1051
return changes, from_extras, to_extras
1017
1053
def find_target_path(self, path, recurse='none'):
1018
1054
ret = self.find_target_paths([path], recurse=recurse)
1070
class InterGitRevisionTrees(InterGitTrees):
1071
"""InterTree that works between two git revision trees."""
1073
_matching_from_tree_format = None
1074
_matching_to_tree_format = None
1075
_test_mutable_trees_to_test_trees = None
1078
def is_compatible(cls, source, target):
1079
return (isinstance(source, GitRevisionTree) and
1080
isinstance(target, GitRevisionTree))
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)
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])
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()
1108
_mod_tree.InterTree.register_optimiser(InterGitRevisionTrees)
1111
class MutableGitIndexTree(mutabletree.MutableTree):
1106
_mod_tree.InterTree.register_optimiser(InterGitTrees)
1109
class MutableGitIndexTree(mutabletree.MutableTree, GitTree):
1113
1111
def __init__(self):
1114
1112
self._lock_mode = None
1632
1623
from .transform import GitTreeTransform
1633
1624
return GitTreeTransform(self, pb=pb)
1635
def preview_transform(self, pb=None):
1636
from .transform import GitTransformPreview
1637
return GitTransformPreview(self, pb=pb)
1640
class InterToIndexGitTree(InterGitTrees):
1641
"""InterTree that works between a Git revision tree and an index."""
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
1648
self.store = OverlayObjectStore(
1649
[self.source.store, self.target.store])
1650
self.rename_detector = RenameDetector(self.store)
1653
def is_compatible(cls, source, target):
1654
return (isinstance(source, GitRevisionTree) and
1655
isinstance(target, MutableGitIndexTree))
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
1678
_mod_tree.InterTree.register_optimiser(InterToIndexGitTree)
1681
class InterFromIndexGitTree(InterGitTrees):
1682
"""InterTree that works between a Git revision tree and an index."""
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
1689
self.store = OverlayObjectStore(
1690
[self.source.store, self.target.store])
1691
self.rename_detector = RenameDetector(self.store)
1694
def is_compatible(cls, source, target):
1695
return (isinstance(target, GitRevisionTree) and
1696
isinstance(source, MutableGitIndexTree))
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
1718
_mod_tree.InterTree.register_optimiser(InterFromIndexGitTree)
1721
class InterIndexGitTree(InterGitTrees):
1722
"""InterTree that works between a Git revision tree and an index."""
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
1729
self.store = OverlayObjectStore(
1730
[self.source.store, self.target.store])
1731
self.rename_detector = RenameDetector(self.store)
1734
def is_compatible(cls, source, target):
1735
return (isinstance(target, MutableGitIndexTree) and
1736
isinstance(source, MutableGitIndexTree))
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
1762
_mod_tree.InterTree.register_optimiser(InterIndexGitTree)
1626
def has_changes(self, _from_tree=None):
1627
"""Quickly check that the tree contains at least one commitable change.
1629
:param _from_tree: tree to compare against to find changes (default to
1630
the basis tree and is intended to be used by tests).
1632
:return: True if a change is found. False otherwise
1634
with self.lock_read():
1635
# Check pending merges
1636
if len(self.get_parent_ids()) > 1:
1638
if _from_tree is None:
1639
_from_tree = self.basis_tree()
1640
changes = self.iter_changes(_from_tree)
1641
if self.supports_symlinks():
1642
# Fast path for has_changes.
1644
change = next(changes)
1645
if change.path[1] == '':
1648
except StopIteration:
1652
# Slow path for has_changes.
1653
# Handle platforms that do not support symlinks in the
1654
# conditional below. This is slower than the try/except
1655
# approach below that but we don't have a choice as we
1656
# need to be sure that all symlinks are removed from the
1657
# entire changeset. This is because in platforms that
1658
# do not support symlinks, they show up as None in the
1659
# working copy as compared to the repository.
1660
# Also, exclude root as mention in the above fast path.
1662
lambda c: c[6][0] != 'symlink' and c[4] != (None, None),
1666
except StopIteration:
1765
1671
def snapshot_workingtree(target, want_unversioned=False):