29
27
ConfigFile as GitConfigFile,
31
from dulwich.diff_tree import tree_changes
29
from dulwich.diff_tree import tree_changes, RenameDetector
32
30
from dulwich.errors import NotTreeError
33
31
from dulwich.index import (
34
32
blob_from_path_and_stat,
726
720
def tree_delta_from_git_changes(changes, mappings,
727
721
specific_files=None,
728
722
require_versioned=False, include_root=False,
723
source_extras=None, target_extras=None):
730
724
"""Create a TreeDelta from two git trees.
732
726
source and target are iterators over tuples with:
735
729
(old_mapping, new_mapping) = mappings
736
730
if target_extras is None:
737
731
target_extras = set()
732
if source_extras is None:
733
source_extras = set()
738
734
ret = delta.TreeDelta()
740
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
736
for (change_type, old, new) in changes:
737
(oldpath, oldmode, oldsha) = old
738
(newpath, newmode, newsha) = new
741
739
if newpath == b'' and not include_root:
741
copied = (change_type == 'copy')
743
742
if oldpath is not None:
744
743
oldpath_decoded = oldpath.decode('utf-8')
757
756
specific_files, newpath_decoded))):
760
if oldpath_decoded is None:
761
fileid = new_mapping.generate_file_id(newpath_decoded)
766
764
oldversioned = False
766
oldversioned = (oldpath not in source_extras)
770
768
oldexe = mode_is_executable(oldmode)
771
769
oldkind = mode_kind(oldmode)
775
if oldpath_decoded == u'':
779
777
(oldparentpath, oldname) = osutils.split(oldpath_decoded)
780
778
oldparent = old_mapping.generate_file_id(oldparentpath)
786
newversioned = (newpath not in target_extras)
788
newexe = mode_is_executable(newmode)
789
newkind = mode_kind(newmode)
793
if newpath_decoded == u'':
797
newparentpath, newname = osutils.split(newpath_decoded)
798
newparent = new_mapping.generate_file_id(newparentpath)
799
if oldversioned and not copied:
781
800
fileid = old_mapping.generate_file_id(oldpath_decoded)
782
if newpath_decoded is None:
802
fileid = new_mapping.generate_file_id(newpath_decoded)
789
newversioned = (newpath_decoded not in target_extras)
791
newexe = mode_is_executable(newmode)
792
newkind = mode_kind(newmode)
796
if newpath_decoded == u'':
800
newparentpath, newname = osutils.split(newpath_decoded)
801
newparent = new_mapping.generate_file_id(newparentpath)
802
805
if old_mapping.is_special_file(oldpath):
804
807
if new_mapping.is_special_file(newpath):
809
812
fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
810
813
(oldversioned, newversioned),
811
814
(oldparent, newparent), (oldname, newname),
812
(oldkind, newkind), (oldexe, newexe))
815
(oldkind, newkind), (oldexe, newexe),
817
if newpath is not None and not newversioned and newkind != 'directory':
818
change.file_id = None
819
ret.unversioned.append(change)
820
elif change_type == 'add':
814
821
added.append((newpath, newkind))
815
822
elif newpath is None or newmode == 0:
816
823
ret.removed.append(change)
817
elif oldpath != newpath:
824
elif change_type == 'delete':
825
ret.removed.append(change)
826
elif change_type == 'copy':
827
if stat.S_ISDIR(oldmode) and stat.S_ISDIR(newmode):
829
ret.copied.append(change)
830
elif change_type == 'rename':
831
if stat.S_ISDIR(oldmode) and stat.S_ISDIR(newmode):
818
833
ret.renamed.append(change)
819
834
elif mode_kind(oldmode) != mode_kind(newmode):
820
835
ret.kind_changed.append(change)
837
852
path_decoded = osutils.normalized_filename(path)[0]
838
853
parent_path, basename = osutils.split(path_decoded)
839
854
parent_id = new_mapping.generate_file_id(parent_path)
840
if path in target_extras:
841
ret.unversioned.append(_mod_tree.TreeChange(
842
None, (None, path_decoded),
843
True, (False, False), (None, parent_id),
855
file_id = new_mapping.generate_file_id(path_decoded)
857
_mod_tree.TreeChange(
858
file_id, (None, path_decoded), True,
844
861
(None, basename), (None, kind), (None, False)))
846
file_id = new_mapping.generate_file_id(path_decoded)
848
_mod_tree.TreeChange(
849
file_id, (None, path_decoded), True,
852
(None, basename), (None, kind), (None, False)))
857
866
def changes_from_git_changes(changes, mapping, specific_files=None,
858
include_unchanged=False, target_extras=None):
867
include_unchanged=False, source_extras=None,
859
869
"""Create a iter_changes-like generator from a git stream.
861
871
source and target are iterators over tuples with:
864
874
if target_extras is None:
865
875
target_extras = set()
866
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
876
if source_extras is None:
877
source_extras = set()
878
for (change_type, old, new) in changes:
879
if change_type == 'unchanged' and not include_unchanged:
881
(oldpath, oldmode, oldsha) = old
882
(newpath, newmode, newsha) = new
867
883
if oldpath is not None:
868
884
oldpath_decoded = oldpath.decode('utf-8')
885
901
if newpath is not None and mapping.is_special_file(newpath):
887
if oldpath_decoded is None:
888
fileid = mapping.generate_file_id(newpath_decoded)
893
908
oldversioned = False
910
oldversioned = (oldpath not in source_extras)
897
912
oldexe = mode_is_executable(oldmode)
898
913
oldkind = mode_kind(oldmode)
906
921
(oldparentpath, oldname) = osutils.split(oldpath_decoded)
907
922
oldparent = mapping.generate_file_id(oldparentpath)
908
fileid = mapping.generate_file_id(oldpath_decoded)
909
if newpath_decoded is None:
914
928
newversioned = False
916
newversioned = (newpath_decoded not in target_extras)
930
newversioned = (newpath not in target_extras)
918
932
newexe = mode_is_executable(newmode)
919
933
newkind = mode_kind(newmode)
927
941
newparentpath, newname = osutils.split(newpath_decoded)
928
942
newparent = mapping.generate_file_id(newparentpath)
929
943
if (not include_unchanged and
930
oldkind == 'directory' and newkind == 'directory' and
944
oldkind == 'directory' and newkind == 'directory' and
931
945
oldpath_decoded == newpath_decoded):
947
if oldversioned and change_type != 'copy':
948
fileid = mapping.generate_file_id(oldpath_decoded)
950
fileid = mapping.generate_file_id(newpath_decoded)
933
953
yield _mod_tree.TreeChange(
934
954
fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
935
955
(oldversioned, newversioned),
936
956
(oldparent, newparent), (oldname, newname),
937
(oldkind, newkind), (oldexe, newexe))
957
(oldkind, newkind), (oldexe, newexe),
958
copied=(change_type == 'copy'))
940
961
class InterGitTrees(_mod_tree.InterTree):
953
974
extra_trees=None, require_versioned=False, include_root=False,
954
975
want_unversioned=False):
955
976
with self.lock_read():
956
changes, target_extras = self._iter_git_changes(
977
changes, source_extras, target_extras = self._iter_git_changes(
957
978
want_unchanged=want_unchanged,
958
979
require_versioned=require_versioned,
959
980
specific_files=specific_files,
962
983
return tree_delta_from_git_changes(
963
984
changes, (self.source.mapping, self.target.mapping),
964
985
specific_files=specific_files,
965
include_root=include_root, target_extras=target_extras)
986
include_root=include_root,
987
source_extras=source_extras, target_extras=target_extras)
967
989
def iter_changes(self, include_unchanged=False, specific_files=None,
968
990
pb=None, extra_trees=[], require_versioned=True,
969
991
want_unversioned=False):
970
992
with self.lock_read():
971
changes, target_extras = self._iter_git_changes(
993
changes, source_extras, target_extras = self._iter_git_changes(
972
994
want_unchanged=include_unchanged,
973
995
require_versioned=require_versioned,
974
996
specific_files=specific_files,
978
1000
changes, self.target.mapping,
979
1001
specific_files=specific_files,
980
1002
include_unchanged=include_unchanged,
1003
source_extras=source_extras,
981
1004
target_extras=target_extras)
983
1006
def _iter_git_changes(self, want_unchanged=False, specific_files=None,
997
1020
paths = set(paths)
999
1022
changes = self._iter_git_changes(specific_files=paths)[0]
1000
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
1023
for (change_type, old, new) in changes:
1001
1026
if oldpath in paths:
1002
1027
ret[oldpath] = newpath
1003
1028
for path in paths:
1015
1040
paths = set(paths)
1017
1042
changes = self._iter_git_changes(specific_files=paths)[0]
1018
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
1043
for (change_type, old, new) in changes:
1019
1046
if newpath in paths:
1020
1047
ret[newpath] = oldpath
1021
1048
for path in paths:
1060
1087
self.target._repository._git.object_store])
1062
1089
store = self.source._repository._git.object_store
1063
return store.tree_changes(
1064
self.source.tree, self.target.tree, want_unchanged=want_unchanged,
1065
include_trees=True, change_type_same=True), set()
1090
rename_detector = RenameDetector(store)
1091
changes = tree_changes(
1092
store, self.source.tree, self.target.tree,
1093
want_unchanged=want_unchanged, include_trees=True,
1094
change_type_same=True, rename_detector=rename_detector)
1095
return changes, set(), set()
1068
1098
_mod_tree.InterTree.register_optimiser(InterGitRevisionTrees)
1310
1340
key = (posixpath.dirname(path), path)
1311
1341
if key not in ret and self.is_versioned(path):
1312
1342
ret[key] = self._get_dir_ie(path, self.path2id(key[0]))
1313
return ((path, ie) for ((_, path), ie) in sorted(viewitems(ret)))
1343
return ((path, ie) for ((_, path), ie) in sorted(ret.items()))
1315
1345
def iter_references(self):
1316
1346
if self.supports_tree_reference():
1325
1355
posixpath.basename(path).strip("/"), parent_id)
1327
1357
def _get_file_ie(self, name, path, value, parent_id):
1328
if not isinstance(name, text_type):
1358
if not isinstance(name, str):
1329
1359
raise TypeError(name)
1330
if not isinstance(path, text_type):
1360
if not isinstance(path, str):
1331
1361
raise TypeError(path)
1332
1362
if not isinstance(value, tuple) or len(value) != 10:
1333
1363
raise TypeError(value)
1601
1631
def __init__(self, source, target):
1602
1632
super(InterIndexGitTree, self).__init__(source, target)
1603
1633
self._index = target.index
1634
if self.source.store == self.target.store:
1635
self.store = self.source.store
1637
self.store = OverlayObjectStore(
1638
[self.source.store, self.target.store])
1639
self.rename_detector = RenameDetector(self.store)
1606
1642
def is_compatible(cls, source, target):
1619
1655
require_versioned=require_versioned)
1620
1656
# TODO(jelmer): Restrict to specific_files, for performance reasons.
1621
1657
with self.lock_read():
1622
return changes_between_git_tree_and_working_copy(
1658
changes, target_extras = changes_between_git_tree_and_working_copy(
1623
1659
self.source.store, self.source.tree,
1624
1660
self.target, want_unchanged=want_unchanged,
1625
want_unversioned=want_unversioned)
1661
want_unversioned=want_unversioned,
1662
rename_detector=self.rename_detector)
1663
return changes, set(), target_extras
1628
1666
_mod_tree.InterTree.register_optimiser(InterIndexGitTree)
1631
def changes_between_git_tree_and_working_copy(store, from_tree_sha, target,
1669
def changes_between_git_tree_and_working_copy(source_store, from_tree_sha, target,
1632
1670
want_unchanged=False,
1633
want_unversioned=False):
1671
want_unversioned=False,
1672
rename_detector=None):
1634
1673
"""Determine the changes between a git tree and a working tree with index.
1707
if live_entry.sha != index_entry.sha:
1708
rp = path.decode('utf-8')
1709
if stat.S_ISREG(live_entry.mode):
1711
with target.get_file(rp) as f:
1712
blob.data = f.read()
1713
elif stat.S_ISLNK(live_entry.mode):
1715
blob.data = target.get_symlink_target(rp).encode(osutils._fs_enc)
1718
if blob is not None:
1719
target.store.add_object(blob)
1668
1720
blobs[path] = (live_entry.sha, cleanup_mode(live_entry.mode))
1669
1721
if want_unversioned:
1670
for e in target.extras():
1671
st = target._lstat(e)
1722
for e in target._iter_files_recursive(include_dirs=False):
1673
np, accessible = osutils.normalized_filename(e)
1724
e, accessible = osutils.normalized_filename(e)
1674
1725
except UnicodeDecodeError:
1675
1726
raise errors.BadFilenameEncoding(
1676
1727
e, osutils._fs_enc)
1728
np = e.encode('utf-8')
1731
st = target._lstat(e)
1677
1732
if stat.S_ISDIR(st.st_mode):
1734
elif stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
1680
1735
blob = blob_from_path_and_stat(
1681
1736
target.abspath(e).encode(osutils._fs_enc), st)
1682
store.add_object(blob)
1683
np = np.encode('utf-8')
1739
target.store.add_object(blob)
1684
1740
blobs[np] = (blob.id, cleanup_mode(st.st_mode))
1686
1742
to_tree_sha = commit_tree(
1687
store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()])
1688
return store.tree_changes(
1689
from_tree_sha, to_tree_sha, include_trees=True,
1743
target.store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()])
1744
store = OverlayObjectStore([source_store, target.store])
1745
return tree_changes(
1746
store, from_tree_sha, to_tree_sha, include_trees=True,
1747
rename_detector=rename_detector,
1690
1748
want_unchanged=want_unchanged, change_type_same=True), extras