446
446
parent_path = posixpath.dirname(from_dir)
447
447
parent_id = self.mapping.generate_file_id(parent_path)
448
448
if mode_kind(mode) == 'directory':
449
root_ie = self._get_dir_ie(encode_git_path(from_dir), parent_id)
449
root_ie = self._get_dir_ie(from_dir.encode("utf-8"), parent_id)
451
451
root_ie = self._get_file_ie(
452
store, encode_git_path(from_dir),
452
store, from_dir.encode("utf-8"),
453
453
posixpath.basename(from_dir), mode, hexsha)
455
455
yield (from_dir, "V", root_ie.kind, root_ie)
457
457
if root_ie.kind == 'directory':
458
todo.append((store, encode_git_path(from_dir),
458
todo.append((store, from_dir.encode("utf-8"),
459
459
b"", hexsha, root_ie.file_id))
461
461
(store, path, relpath, hexsha, parent_id) = todo.pop()
699
699
def walkdirs(self, prefix=u""):
700
700
(store, mode, hexsha) = self._lookup_path(prefix)
702
[(store, encode_git_path(prefix), hexsha, self.path2id(prefix))])
702
[(store, prefix.encode('utf-8'), hexsha, self.path2id(prefix))])
704
704
store, path, tree_sha, parent_id = todo.popleft()
705
path_decoded = decode_git_path(path)
705
path_decoded = path.decode('utf-8')
706
706
tree = store[tree_sha]
708
708
for name, mode, hexsha in tree.iteritems():
709
709
if self.mapping.is_special_file(name):
711
711
child_path = posixpath.join(path, name)
712
file_id = self.path2id(decode_git_path(child_path))
712
file_id = self.path2id(child_path.decode('utf-8'))
713
713
if stat.S_ISDIR(mode):
714
714
todo.append((store, child_path, hexsha, file_id))
716
(decode_git_path(child_path), decode_git_path(name),
716
(child_path.decode('utf-8'), name.decode('utf-8'),
717
717
mode_kind(mode), None,
718
718
file_id, mode_kind(mode)))
719
719
yield (path_decoded, parent_id), children
721
def preview_transform(self, pb=None):
722
from .transform import GitTransformPreview
723
return GitTransformPreview(self, pb=pb)
726
722
def tree_delta_from_git_changes(changes, mappings,
727
723
specific_files=None,
728
724
require_versioned=False, include_root=False,
729
source_extras=None, target_extras=None):
730
726
"""Create a TreeDelta from two git trees.
732
728
source and target are iterators over tuples with:
735
731
(old_mapping, new_mapping) = mappings
736
732
if target_extras is None:
737
733
target_extras = set()
738
if source_extras is None:
739
source_extras = set()
740
734
ret = delta.TreeDelta()
742
for (change_type, old, new) in changes:
743
(oldpath, oldmode, oldsha) = old
744
(newpath, newmode, newsha) = new
736
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
745
737
if newpath == b'' and not include_root:
747
copied = (change_type == 'copy')
748
739
if oldpath is not None:
749
oldpath_decoded = decode_git_path(oldpath)
740
oldpath_decoded = oldpath.decode('utf-8')
751
742
oldpath_decoded = None
752
743
if newpath is not None:
753
newpath_decoded = decode_git_path(newpath)
744
newpath_decoded = newpath.decode('utf-8')
755
746
newpath_decoded = None
756
747
if not (specific_files is None or
818
805
fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
819
806
(oldversioned, newversioned),
820
807
(oldparent, newparent), (oldname, newname),
821
(oldkind, newkind), (oldexe, newexe),
823
if newpath is not None and not newversioned and newkind != 'directory':
824
change.file_id = None
825
ret.unversioned.append(change)
826
elif change_type == 'add':
808
(oldkind, newkind), (oldexe, newexe))
827
810
added.append((newpath, newkind))
828
811
elif newpath is None or newmode == 0:
829
812
ret.removed.append(change)
830
elif change_type == 'delete':
831
ret.removed.append(change)
832
elif change_type == 'copy':
833
if stat.S_ISDIR(oldmode) and stat.S_ISDIR(newmode):
835
ret.copied.append(change)
836
elif change_type == 'rename':
837
if stat.S_ISDIR(oldmode) and stat.S_ISDIR(newmode):
813
elif oldpath != newpath:
839
814
ret.renamed.append(change)
840
815
elif mode_kind(oldmode) != mode_kind(newmode):
841
816
ret.kind_changed.append(change)
855
830
for path, kind in added:
856
831
if kind == 'directory' and path not in implicit_dirs:
858
path_decoded = decode_git_path(path)
833
path_decoded = osutils.normalized_filename(path)[0]
859
834
parent_path, basename = osutils.split(path_decoded)
860
835
parent_id = new_mapping.generate_file_id(parent_path)
861
file_id = new_mapping.generate_file_id(path_decoded)
863
_mod_tree.TreeChange(
864
file_id, (None, path_decoded), True,
836
if path in target_extras:
837
ret.unversioned.append(_mod_tree.TreeChange(
838
None, (None, path_decoded),
839
True, (False, False), (None, parent_id),
867
840
(None, basename), (None, kind), (None, False)))
842
file_id = new_mapping.generate_file_id(path_decoded)
844
_mod_tree.TreeChange(
845
file_id, (None, path_decoded), True,
848
(None, basename), (None, kind), (None, False)))
872
853
def changes_from_git_changes(changes, mapping, specific_files=None,
873
include_unchanged=False, source_extras=None,
854
include_unchanged=False, target_extras=None):
875
855
"""Create a iter_changes-like generator from a git stream.
877
857
source and target are iterators over tuples with:
947
923
newparentpath, newname = osutils.split(newpath_decoded)
948
924
newparent = mapping.generate_file_id(newparentpath)
949
925
if (not include_unchanged and
950
oldkind == 'directory' and newkind == 'directory' and
926
oldkind == 'directory' and newkind == 'directory' and
951
927
oldpath_decoded == newpath_decoded):
953
if oldversioned and change_type != 'copy':
954
fileid = mapping.generate_file_id(oldpath_decoded)
956
fileid = mapping.generate_file_id(newpath_decoded)
959
929
yield _mod_tree.TreeChange(
960
930
fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
961
931
(oldversioned, newversioned),
962
932
(oldparent, newparent), (oldname, newname),
963
(oldkind, newkind), (oldexe, newexe),
964
copied=(change_type == 'copy'))
933
(oldkind, newkind), (oldexe, newexe))
967
936
class InterGitTrees(_mod_tree.InterTree):
989
958
return tree_delta_from_git_changes(
990
959
changes, (self.source.mapping, self.target.mapping),
991
960
specific_files=specific_files,
992
include_root=include_root,
993
source_extras=source_extras, target_extras=target_extras)
961
include_root=include_root, target_extras=target_extras)
995
963
def iter_changes(self, include_unchanged=False, specific_files=None,
996
964
pb=None, extra_trees=[], require_versioned=True,
997
965
want_unversioned=False):
998
966
with self.lock_read():
999
changes, source_extras, target_extras = self._iter_git_changes(
967
changes, target_extras = self._iter_git_changes(
1000
968
want_unchanged=include_unchanged,
1001
969
require_versioned=require_versioned,
1002
970
specific_files=specific_files,
1628
1585
def _live_entry(self, relpath):
1629
1586
raise NotImplementedError(self._live_entry)
1631
def transform(self, pb=None):
1632
from .transform import GitTreeTransform
1633
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):
1588
def get_transform(self, pb=None):
1589
from ..transform import TreeTransform
1590
return TreeTransform(self, pb=pb)
1594
class InterIndexGitTree(InterGitTrees):
1641
1595
"""InterTree that works between a Git revision tree and an index."""
1643
1597
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)
1598
super(InterIndexGitTree, self).__init__(source, target)
1599
self._index = target.index
1653
1602
def is_compatible(cls, source, target):
1666
1615
require_versioned=require_versioned)
1667
1616
# TODO(jelmer): Restrict to specific_files, for performance reasons.
1668
1617
with self.lock_read():
1669
changes, target_extras = changes_between_git_tree_and_working_copy(
1618
return changes_between_git_tree_and_working_copy(
1670
1619
self.source.store, self.source.tree,
1671
1620
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
1621
want_unversioned=want_unversioned)
1762
1624
_mod_tree.InterTree.register_optimiser(InterIndexGitTree)
1765
def snapshot_workingtree(target, want_unversioned=False):
1627
def changes_between_git_tree_and_working_copy(store, from_tree_sha, target,
1628
want_unchanged=False,
1629
want_unversioned=False):
1630
"""Determine the changes between a git tree and a working tree with index.
1768
1635
# Report dirified directories to commit_tree first, so that they can be
1797
if live_entry.sha != index_entry.sha:
1798
rp = decode_git_path(path)
1799
if stat.S_ISREG(live_entry.mode):
1801
with target.get_file(rp) as f:
1802
blob.data = f.read()
1803
elif stat.S_ISLNK(live_entry.mode):
1805
blob.data = target.get_symlink_target(rp).encode(osutils._fs_enc)
1808
if blob is not None:
1809
target.store.add_object(blob)
1810
1664
blobs[path] = (live_entry.sha, cleanup_mode(live_entry.mode))
1811
1665
if want_unversioned:
1812
for e in target._iter_files_recursive(include_dirs=False):
1666
for e in target.extras():
1667
st = target._lstat(e)
1814
e, accessible = osutils.normalized_filename(e)
1669
np, accessible = osutils.normalized_filename(e)
1815
1670
except UnicodeDecodeError:
1816
1671
raise errors.BadFilenameEncoding(
1817
1672
e, osutils._fs_enc)
1818
np = encode_git_path(e)
1821
st = target._lstat(e)
1822
1673
if stat.S_ISDIR(st.st_mode):
1824
elif stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
1825
1676
blob = blob_from_path_and_stat(
1826
1677
target.abspath(e).encode(osutils._fs_enc), st)
1829
target.store.add_object(blob)
1678
store.add_object(blob)
1679
np = np.encode('utf-8')
1830
1680
blobs[np] = (blob.id, cleanup_mode(st.st_mode))
1833
target.store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()]), extras
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.
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,
1682
to_tree_sha = commit_tree(
1683
store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()])
1684
return store.tree_changes(
1685
from_tree_sha, to_tree_sha, include_trees=True,
1849
1686
want_unchanged=want_unchanged, change_type_same=True), extras