114
115
class GitTreeFile(_mod_tree.TreeFile):
116
__slots__ = ['file_id', 'name', 'parent_id', 'text_size', 'text_sha1',
117
__slots__ = ['file_id', 'name', 'parent_id', 'text_size',
118
'executable', 'git_sha1']
119
120
def __init__(self, file_id, name, parent_id, text_size=None,
120
text_sha1=None, executable=None):
121
git_sha1=None, executable=None):
121
122
self.file_id = file_id
123
124
self.parent_id = parent_id
124
125
self.text_size = text_size
125
self.text_sha1 = text_sha1
126
self.git_sha1 = git_sha1
126
127
self.executable = executable
134
135
self.file_id == other.file_id and
135
136
self.name == other.name and
136
137
self.parent_id == other.parent_id and
137
self.text_sha1 == other.text_sha1 and
138
self.git_sha1 == other.git_sha1 and
138
139
self.text_size == other.text_size and
139
140
self.executable == other.executable)
141
142
def __repr__(self):
142
143
return ("%s(file_id=%r, name=%r, parent_id=%r, text_size=%r, "
143
"text_sha1=%r, executable=%r)") % (
144
"git_sha1=%r, executable=%r)") % (
144
145
type(self).__name__, self.file_id, self.name, self.parent_id,
145
self.text_size, self.text_sha1, self.executable)
146
self.text_size, self.git_sha1, self.executable)
148
149
ret = self.__class__(
149
150
self.file_id, self.name, self.parent_id)
150
ret.text_sha1 = self.text_sha1
151
ret.git_sha1 = self.git_sha1
151
152
ret.text_size = self.text_size
152
153
ret.executable = self.executable
258
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):
259
310
"""Revision tree implementation based on Git objects."""
261
312
def __init__(self, repository, revision_id):
277
328
raise errors.NoSuchRevision(repository, revision_id)
278
329
self.tree = commit.tree
280
def _submodule_info(self):
281
if self._submodules is None:
283
with self.get_file('.gitmodules') as f:
284
config = GitConfigFile.from_file(f)
287
for path, url, section in parse_submodules(config)}
288
except errors.NoSuchFile:
289
self._submodules = {}
290
return self._submodules
331
def git_snapshot(self, want_unversioned=False):
332
return self.tree, set()
292
334
def _get_submodule_repository(self, relpath):
293
335
if not isinstance(relpath, bytes):
444
474
parent_path = posixpath.dirname(from_dir)
445
475
parent_id = self.mapping.generate_file_id(parent_path)
446
476
if mode_kind(mode) == 'directory':
447
root_ie = self._get_dir_ie(from_dir.encode("utf-8"), parent_id)
477
root_ie = self._get_dir_ie(encode_git_path(from_dir), parent_id)
449
479
root_ie = self._get_file_ie(
450
store, from_dir.encode("utf-8"),
480
store, encode_git_path(from_dir),
451
481
posixpath.basename(from_dir), mode, hexsha)
453
483
yield (from_dir, "V", root_ie.kind, root_ie)
455
485
if root_ie.kind == 'directory':
456
todo.append((store, from_dir.encode("utf-8"),
486
todo.append((store, encode_git_path(from_dir),
457
487
b"", hexsha, root_ie.file_id))
459
489
(store, path, relpath, hexsha, parent_id) = todo.pop()
477
507
ie = self._get_file_ie(
478
508
store, child_path, name, mode, hexsha, parent_id)
479
yield (child_relpath.decode('utf-8'), "V", ie.kind, ie)
509
yield (decode_git_path(child_relpath), "V", ie.kind, ie)
481
511
def _get_file_ie(self, store, path, name, mode, hexsha, parent_id):
482
512
if not isinstance(path, bytes):
484
514
if not isinstance(name, bytes):
485
515
raise TypeError(name)
486
516
kind = mode_kind(mode)
487
path = path.decode('utf-8')
488
name = name.decode("utf-8")
517
path = decode_git_path(path)
518
name = decode_git_path(name)
489
519
file_id = self.mapping.generate_file_id(path)
490
520
ie = entry_factory[kind](file_id, name, parent_id)
491
521
if kind == 'symlink':
492
ie.symlink_target = store[hexsha].data.decode('utf-8')
522
ie.symlink_target = decode_git_path(store[hexsha].data)
493
523
elif kind == 'tree-reference':
494
524
ie.reference_revision = self.mapping.revision_id_foreign_to_bzr(
497
data = store[hexsha].data
498
ie.text_sha1 = osutils.sha_string(data)
499
ie.text_size = len(data)
500
529
ie.executable = mode_is_executable(mode)
503
532
def _get_dir_ie(self, path, parent_id):
504
path = path.decode('utf-8')
533
path = decode_git_path(path)
505
534
file_id = self.mapping.generate_file_id(path)
506
535
return GitTreeDirectory(file_id, posixpath.basename(path), parent_id)
639
668
return (kind, len(contents), executable,
640
669
osutils.sha_string(contents))
641
670
elif kind == 'symlink':
642
return (kind, None, None, store[hexsha].data.decode('utf-8'))
671
return (kind, None, None, decode_git_path(store[hexsha].data))
643
672
elif kind == 'tree-reference':
644
nested_repo = self._get_submodule_repository(path.encode('utf-8'))
673
nested_repo = self._get_submodule_repository(encode_git_path(path))
645
674
return (kind, None, None,
646
675
nested_repo.lookup_foreign_revision_id(hexsha))
648
677
return (kind, None, None, None)
650
def find_related_paths_across_trees(self, paths, trees=[],
651
require_versioned=True):
654
if require_versioned:
655
trees = [self] + (trees if trees is not None else [])
659
if t.is_versioned(p):
664
raise errors.PathsNotVersionedError(unversioned)
665
return filter(self.is_versioned, paths)
667
679
def _iter_tree_contents(self, include_trees=False):
668
680
if self.tree is None:
697
709
def walkdirs(self, prefix=u""):
698
710
(store, mode, hexsha) = self._lookup_path(prefix)
700
[(store, prefix.encode('utf-8'), hexsha, self.path2id(prefix))])
712
[(store, encode_git_path(prefix), hexsha)])
702
store, path, tree_sha, parent_id = todo.popleft()
703
path_decoded = path.decode('utf-8')
714
store, path, tree_sha = todo.popleft()
715
path_decoded = decode_git_path(path)
704
716
tree = store[tree_sha]
706
718
for name, mode, hexsha in tree.iteritems():
707
719
if self.mapping.is_special_file(name):
709
721
child_path = posixpath.join(path, name)
710
file_id = self.path2id(child_path.decode('utf-8'))
711
722
if stat.S_ISDIR(mode):
712
todo.append((store, child_path, hexsha, file_id))
723
todo.append((store, child_path, hexsha))
714
(child_path.decode('utf-8'), name.decode('utf-8'),
725
(decode_git_path(child_path), decode_git_path(name),
715
726
mode_kind(mode), None,
716
file_id, mode_kind(mode)))
717
yield (path_decoded, parent_id), children
728
yield path_decoded, children
720
731
def tree_delta_from_git_changes(changes, mappings,
721
732
specific_files=None,
722
733
require_versioned=False, include_root=False,
734
source_extras=None, target_extras=None):
724
735
"""Create a TreeDelta from two git trees.
726
737
source and target are iterators over tuples with:
729
740
(old_mapping, new_mapping) = mappings
730
741
if target_extras is None:
731
742
target_extras = set()
743
if source_extras is None:
744
source_extras = set()
732
745
ret = delta.TreeDelta()
734
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
747
for (change_type, old, new) in changes:
748
(oldpath, oldmode, oldsha) = old
749
(newpath, newmode, newsha) = new
735
750
if newpath == b'' and not include_root:
752
copied = (change_type == 'copy')
737
753
if oldpath is not None:
738
oldpath_decoded = oldpath.decode('utf-8')
754
oldpath_decoded = decode_git_path(oldpath)
740
756
oldpath_decoded = None
741
757
if newpath is not None:
742
newpath_decoded = newpath.decode('utf-8')
758
newpath_decoded = decode_git_path(newpath)
744
760
newpath_decoded = None
745
761
if not (specific_files is None or
751
767
specific_files, newpath_decoded))):
754
if oldpath_decoded is None:
755
fileid = new_mapping.generate_file_id(newpath_decoded)
760
775
oldversioned = False
777
oldversioned = (oldpath not in source_extras)
764
779
oldexe = mode_is_executable(oldmode)
765
780
oldkind = mode_kind(oldmode)
769
if oldpath_decoded == u'':
773
788
(oldparentpath, oldname) = osutils.split(oldpath_decoded)
774
789
oldparent = old_mapping.generate_file_id(oldparentpath)
797
newversioned = (newpath not in target_extras)
799
newexe = mode_is_executable(newmode)
800
newkind = mode_kind(newmode)
804
if newpath_decoded == u'':
808
newparentpath, newname = osutils.split(newpath_decoded)
809
newparent = new_mapping.generate_file_id(newparentpath)
810
if oldversioned and not copied:
775
811
fileid = old_mapping.generate_file_id(oldpath_decoded)
776
if newpath_decoded is None:
813
fileid = new_mapping.generate_file_id(newpath_decoded)
783
newversioned = (newpath_decoded not in target_extras)
785
newexe = mode_is_executable(newmode)
786
newkind = mode_kind(newmode)
790
if newpath_decoded == u'':
794
newparentpath, newname = osutils.split(newpath_decoded)
795
newparent = new_mapping.generate_file_id(newparentpath)
796
816
if old_mapping.is_special_file(oldpath):
798
818
if new_mapping.is_special_file(newpath):
800
820
if oldpath is None and newpath is None:
802
change = _mod_tree.TreeChange(
822
change = InventoryTreeChange(
803
823
fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
804
824
(oldversioned, newversioned),
805
825
(oldparent, newparent), (oldname, newname),
806
(oldkind, newkind), (oldexe, newexe))
826
(oldkind, newkind), (oldexe, newexe),
828
if newpath is not None and not newversioned and newkind != 'directory':
829
change.file_id = None
830
ret.unversioned.append(change)
831
elif change_type == 'add':
808
832
added.append((newpath, newkind))
809
833
elif newpath is None or newmode == 0:
810
834
ret.removed.append(change)
811
elif oldpath != newpath:
835
elif change_type == 'delete':
836
ret.removed.append(change)
837
elif change_type == 'copy':
838
if stat.S_ISDIR(oldmode) and stat.S_ISDIR(newmode):
840
ret.copied.append(change)
841
elif change_type == 'rename':
842
if stat.S_ISDIR(oldmode) and stat.S_ISDIR(newmode):
812
844
ret.renamed.append(change)
813
845
elif mode_kind(oldmode) != mode_kind(newmode):
814
846
ret.kind_changed.append(change)
828
860
for path, kind in added:
829
861
if kind == 'directory' and path not in implicit_dirs:
831
path_decoded = osutils.normalized_filename(path)[0]
863
path_decoded = decode_git_path(path)
832
864
parent_path, basename = osutils.split(path_decoded)
833
865
parent_id = new_mapping.generate_file_id(parent_path)
834
if path in target_extras:
835
ret.unversioned.append(_mod_tree.TreeChange(
836
None, (None, path_decoded),
837
True, (False, False), (None, parent_id),
866
file_id = new_mapping.generate_file_id(path_decoded)
869
file_id, (None, path_decoded), True,
838
872
(None, basename), (None, kind), (None, False)))
840
file_id = new_mapping.generate_file_id(path_decoded)
842
_mod_tree.TreeChange(
843
file_id, (None, path_decoded), True,
846
(None, basename), (None, kind), (None, False)))
851
877
def changes_from_git_changes(changes, mapping, specific_files=None,
852
include_unchanged=False, target_extras=None):
878
include_unchanged=False, source_extras=None,
853
880
"""Create a iter_changes-like generator from a git stream.
855
882
source and target are iterators over tuples with:
858
885
if target_extras is None:
859
886
target_extras = set()
860
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
887
if source_extras is None:
888
source_extras = set()
889
for (change_type, old, new) in changes:
890
if change_type == 'unchanged' and not include_unchanged:
892
(oldpath, oldmode, oldsha) = old
893
(newpath, newmode, newsha) = new
861
894
if oldpath is not None:
862
oldpath_decoded = oldpath.decode('utf-8')
895
oldpath_decoded = decode_git_path(oldpath)
864
897
oldpath_decoded = None
865
898
if newpath is not None:
866
newpath_decoded = newpath.decode('utf-8')
899
newpath_decoded = decode_git_path(newpath)
868
901
newpath_decoded = None
869
902
if not (specific_files is None or
921
952
newparentpath, newname = osutils.split(newpath_decoded)
922
953
newparent = mapping.generate_file_id(newparentpath)
923
954
if (not include_unchanged and
924
oldkind == 'directory' and newkind == 'directory' and
955
oldkind == 'directory' and newkind == 'directory' and
925
956
oldpath_decoded == newpath_decoded):
927
yield _mod_tree.TreeChange(
928
fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
958
if oldversioned and change_type != 'copy':
959
fileid = mapping.generate_file_id(oldpath_decoded)
961
fileid = mapping.generate_file_id(newpath_decoded)
964
if oldkind == 'directory' and newkind == 'directory':
967
modified = (oldsha != newsha) or (oldmode != newmode)
968
yield InventoryTreeChange(
969
fileid, (oldpath_decoded, newpath_decoded),
929
971
(oldversioned, newversioned),
930
972
(oldparent, newparent), (oldname, newname),
931
(oldkind, newkind), (oldexe, newexe))
973
(oldkind, newkind), (oldexe, newexe),
974
copied=(change_type == 'copy'))
934
977
class InterGitTrees(_mod_tree.InterTree):
938
981
_matching_to_tree_format = None
939
982
_test_mutable_trees_to_test_trees = None
984
def __init__(self, source, target):
985
super(InterGitTrees, self).__init__(source, target)
986
if self.source.store == self.target.store:
987
self.store = self.source.store
989
self.store = OverlayObjectStore(
990
[self.source.store, self.target.store])
991
self.rename_detector = RenameDetector(self.store)
942
994
def is_compatible(cls, source, target):
943
return (isinstance(source, GitRevisionTree) and
944
isinstance(target, GitRevisionTree))
995
return isinstance(source, GitTree) and isinstance(target, GitTree)
946
997
def compare(self, want_unchanged=False, specific_files=None,
947
998
extra_trees=None, require_versioned=False, include_root=False,
948
999
want_unversioned=False):
949
1000
with self.lock_read():
950
changes, target_extras = self._iter_git_changes(
1001
changes, source_extras, target_extras = self._iter_git_changes(
951
1002
want_unchanged=want_unchanged,
952
1003
require_versioned=require_versioned,
953
1004
specific_files=specific_files,
956
1007
return tree_delta_from_git_changes(
957
1008
changes, (self.source.mapping, self.target.mapping),
958
1009
specific_files=specific_files,
959
include_root=include_root, target_extras=target_extras)
1010
include_root=include_root,
1011
source_extras=source_extras, target_extras=target_extras)
961
1013
def iter_changes(self, include_unchanged=False, specific_files=None,
962
1014
pb=None, extra_trees=[], require_versioned=True,
963
1015
want_unversioned=False):
964
1016
with self.lock_read():
965
changes, target_extras = self._iter_git_changes(
1017
changes, source_extras, target_extras = self._iter_git_changes(
966
1018
want_unchanged=include_unchanged,
967
1019
require_versioned=require_versioned,
968
1020
specific_files=specific_files,
972
1024
changes, self.target.mapping,
973
1025
specific_files=specific_files,
974
1026
include_unchanged=include_unchanged,
1027
source_extras=source_extras,
975
1028
target_extras=target_extras)
977
1030
def _iter_git_changes(self, want_unchanged=False, specific_files=None,
978
1031
require_versioned=False, extra_trees=None,
979
want_unversioned=False):
980
raise NotImplementedError(self._iter_git_changes)
1032
want_unversioned=False, include_trees=True):
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
982
1053
def find_target_path(self, path, recurse='none'):
983
1054
ret = self.find_target_paths([path], recurse=recurse)
990
1061
def find_target_paths(self, paths, recurse='none'):
991
1062
paths = set(paths)
993
changes = self._iter_git_changes(specific_files=paths)[0]
994
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
1064
changes = self._iter_git_changes(
1065
specific_files=paths, include_trees=False)[0]
1066
for (change_type, old, new) in changes:
1069
oldpath = decode_git_path(old[0])
995
1070
if oldpath in paths:
996
ret[oldpath] = newpath
1071
ret[oldpath] = decode_git_path(new[0]) if new[0] else None
997
1072
for path in paths:
998
1073
if path not in ret:
999
1074
if self.source.has_filename(path):
1008
1083
def find_source_paths(self, paths, recurse='none'):
1009
1084
paths = set(paths)
1011
changes = self._iter_git_changes(specific_files=paths)[0]
1012
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
1086
changes = self._iter_git_changes(
1087
specific_files=paths, include_trees=False)[0]
1088
for (change_type, old, new) in changes:
1091
newpath = decode_git_path(new[0])
1013
1092
if newpath in paths:
1014
ret[newpath] = oldpath
1093
ret[newpath] = decode_git_path(old[0]) if old[0] else None
1015
1094
for path in paths:
1016
1095
if path not in ret:
1017
1096
if self.target.has_filename(path):
1027
class InterGitRevisionTrees(InterGitTrees):
1028
"""InterTree that works between two git revision trees."""
1030
_matching_from_tree_format = None
1031
_matching_to_tree_format = None
1032
_test_mutable_trees_to_test_trees = None
1035
def is_compatible(cls, source, target):
1036
return (isinstance(source, GitRevisionTree) and
1037
isinstance(target, GitRevisionTree))
1039
def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1040
require_versioned=True, extra_trees=None,
1041
want_unversioned=False):
1042
trees = [self.source]
1043
if extra_trees is not None:
1044
trees.extend(extra_trees)
1045
if specific_files is not None:
1046
specific_files = self.target.find_related_paths_across_trees(
1047
specific_files, trees,
1048
require_versioned=require_versioned)
1050
if (self.source._repository._git.object_store !=
1051
self.target._repository._git.object_store):
1052
store = OverlayObjectStore(
1053
[self.source._repository._git.object_store,
1054
self.target._repository._git.object_store])
1056
store = self.source._repository._git.object_store
1057
return store.tree_changes(
1058
self.source.tree, self.target.tree, want_unchanged=want_unchanged,
1059
include_trees=True, change_type_same=True), set()
1062
_mod_tree.InterTree.register_optimiser(InterGitRevisionTrees)
1065
class MutableGitIndexTree(mutabletree.MutableTree):
1106
_mod_tree.InterTree.register_optimiser(InterGitTrees)
1109
class MutableGitIndexTree(mutabletree.MutableTree, GitTree):
1067
1111
def __init__(self):
1068
1112
self._lock_mode = None
1139
1186
def _read_submodule_head(self, path):
1140
1187
raise NotImplementedError(self._read_submodule_head)
1142
def _submodule_info(self):
1143
if self._submodules is None:
1145
with self.get_file('.gitmodules') as f:
1146
config = GitConfigFile.from_file(f)
1147
self._submodules = {
1148
path: (url, section)
1149
for path, url, section in parse_submodules(config)}
1150
except errors.NoSuchFile:
1151
self._submodules = {}
1152
return self._submodules
1154
1189
def _lookup_index(self, encoded_path):
1155
1190
if not isinstance(encoded_path, bytes):
1156
1191
raise TypeError(encoded_path)
1186
1221
# TODO(jelmer): Keep track of dirty per index
1187
1222
self._index_dirty = True
1189
def _index_add_entry(self, path, kind, flags=0, reference_revision=None):
1224
def _apply_index_changes(self, changes):
1225
for (path, kind, executability, reference_revision,
1226
symlink_target) in changes:
1227
if kind is None or kind == 'directory':
1228
(index, subpath) = self._lookup_index(
1229
encode_git_path(path))
1231
self._index_del_entry(index, subpath)
1235
self._versioned_dirs = None
1237
self._index_add_entry(
1239
reference_revision=reference_revision,
1240
symlink_target=symlink_target)
1243
def _index_add_entry(
1244
self, path, kind, flags=0, reference_revision=None,
1245
symlink_target=None):
1190
1246
if kind == "directory":
1191
1247
# Git indexes don't contain directories
1249
elif kind == "file":
1196
1252
file, stat_val = self.get_file_with_stat(path)
1216
1272
stat_val = os.stat_result(
1217
1273
(stat.S_IFLNK, 0, 0, 0, 0, 0, 0, 0, 0, 0))
1218
blob.set_raw_string(
1219
self.get_symlink_target(path).encode("utf-8"))
1274
if symlink_target is None:
1275
symlink_target = self.get_symlink_target(path)
1276
blob.set_raw_string(encode_git_path(symlink_target))
1220
1277
# Add object to the repository if it didn't exist yet
1221
1278
if blob.id not in self.store:
1222
1279
self.store.add_object(blob)
1429
1480
return rename_tuples
1431
1482
def rename_one(self, from_rel, to_rel, after=None):
1432
from_path = from_rel.encode("utf-8")
1483
from_path = encode_git_path(from_rel)
1433
1484
to_rel, can_access = osutils.normalized_filename(to_rel)
1434
1485
if not can_access:
1435
1486
raise errors.InvalidNormalization(to_rel)
1436
to_path = to_rel.encode("utf-8")
1487
to_path = encode_git_path(to_rel)
1437
1488
with self.lock_tree_write():
1439
1490
# Perhaps it's already moved?
1583
1619
def _live_entry(self, relpath):
1584
1620
raise NotImplementedError(self._live_entry)
1586
def get_transform(self, pb=None):
1587
from ..transform import TreeTransform
1588
return TreeTransform(self, pb=pb)
1592
class InterIndexGitTree(InterGitTrees):
1593
"""InterTree that works between a Git revision tree and an index."""
1595
def __init__(self, source, target):
1596
super(InterIndexGitTree, self).__init__(source, target)
1597
self._index = target.index
1600
def is_compatible(cls, source, target):
1601
return (isinstance(source, GitRevisionTree) and
1602
isinstance(target, MutableGitIndexTree))
1604
def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1605
require_versioned=False, extra_trees=None,
1606
want_unversioned=False):
1607
trees = [self.source]
1608
if extra_trees is not None:
1609
trees.extend(extra_trees)
1610
if specific_files is not None:
1611
specific_files = self.target.find_related_paths_across_trees(
1612
specific_files, trees,
1613
require_versioned=require_versioned)
1614
# TODO(jelmer): Restrict to specific_files, for performance reasons.
1622
def transform(self, pb=None):
1623
from .transform import GitTreeTransform
1624
return GitTreeTransform(self, pb=pb)
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
1615
1634
with self.lock_read():
1616
return changes_between_git_tree_and_working_copy(
1617
self.source.store, self.source.tree,
1618
self.target, want_unchanged=want_unchanged,
1619
want_unversioned=want_unversioned)
1622
_mod_tree.InterTree.register_optimiser(InterIndexGitTree)
1625
def changes_between_git_tree_and_working_copy(store, from_tree_sha, target,
1626
want_unchanged=False,
1627
want_unversioned=False):
1628
"""Determine the changes between a git tree and a working tree with index.
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:
1671
def snapshot_workingtree(target, want_unversioned=False):
1633
1674
# Report dirified directories to commit_tree first, so that they can be
1703
if live_entry.sha != index_entry.sha:
1704
rp = decode_git_path(path)
1705
if stat.S_ISREG(live_entry.mode):
1707
with target.get_file(rp) as f:
1708
blob.data = f.read()
1709
elif stat.S_ISLNK(live_entry.mode):
1711
blob.data = target.get_symlink_target(rp).encode(osutils._fs_enc)
1714
if blob is not None:
1715
target.store.add_object(blob)
1662
1716
blobs[path] = (live_entry.sha, cleanup_mode(live_entry.mode))
1663
1717
if want_unversioned:
1664
for e in target.extras():
1665
st = target._lstat(e)
1718
for extra in target._iter_files_recursive(include_dirs=False):
1667
np, accessible = osutils.normalized_filename(e)
1720
extra, accessible = osutils.normalized_filename(extra)
1668
1721
except UnicodeDecodeError:
1669
1722
raise errors.BadFilenameEncoding(
1723
extra, osutils._fs_enc)
1724
np = encode_git_path(extra)
1727
st = target._lstat(extra)
1671
1728
if stat.S_ISDIR(st.st_mode):
1673
1730
elif stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
1674
1731
blob = blob_from_path_and_stat(
1675
target.abspath(e).encode(osutils._fs_enc), st)
1732
target.abspath(extra).encode(osutils._fs_enc), st)
1678
store.add_object(blob)
1679
np = np.encode('utf-8')
1735
target.store.add_object(blob)
1680
1736
blobs[np] = (blob.id, cleanup_mode(st.st_mode))
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,
1686
want_unchanged=want_unchanged, change_type_same=True), extras
1739
target.store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()]), extras