120
115
class GitTreeFile(_mod_tree.TreeFile):
122
__slots__ = ['file_id', 'name', 'parent_id', 'text_size', 'text_sha1',
117
__slots__ = ['file_id', 'name', 'parent_id', 'text_size',
118
'executable', 'git_sha1']
125
120
def __init__(self, file_id, name, parent_id, text_size=None,
126
text_sha1=None, executable=None):
121
git_sha1=None, executable=None):
127
122
self.file_id = file_id
129
124
self.parent_id = parent_id
130
125
self.text_size = text_size
131
self.text_sha1 = text_sha1
126
self.git_sha1 = git_sha1
132
127
self.executable = executable
140
135
self.file_id == other.file_id and
141
136
self.name == other.name and
142
137
self.parent_id == other.parent_id and
143
self.text_sha1 == other.text_sha1 and
138
self.git_sha1 == other.git_sha1 and
144
139
self.text_size == other.text_size and
145
140
self.executable == other.executable)
147
142
def __repr__(self):
148
143
return ("%s(file_id=%r, name=%r, parent_id=%r, text_size=%r, "
149
"text_sha1=%r, executable=%r)") % (
144
"git_sha1=%r, executable=%r)") % (
150
145
type(self).__name__, self.file_id, self.name, self.parent_id,
151
self.text_size, self.text_sha1, self.executable)
146
self.text_size, self.git_sha1, self.executable)
154
149
ret = self.__class__(
155
150
self.file_id, self.name, self.parent_id)
156
ret.text_sha1 = self.text_sha1
151
ret.git_sha1 = self.git_sha1
157
152
ret.text_size = self.text_size
158
153
ret.executable = self.executable
264
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):
265
310
"""Revision tree implementation based on Git objects."""
267
312
def __init__(self, repository, revision_id):
283
328
raise errors.NoSuchRevision(repository, revision_id)
284
329
self.tree = commit.tree
286
def _submodule_info(self):
287
if self._submodules is None:
289
with self.get_file('.gitmodules') as f:
290
config = GitConfigFile.from_file(f)
293
for path, url, section in parse_submodules(config)}
294
except errors.NoSuchFile:
295
self._submodules = {}
296
return self._submodules
331
def git_snapshot(self, want_unversioned=False):
332
return self.tree, set()
298
334
def _get_submodule_repository(self, relpath):
299
335
if not isinstance(relpath, bytes):
450
474
parent_path = posixpath.dirname(from_dir)
451
475
parent_id = self.mapping.generate_file_id(parent_path)
452
476
if mode_kind(mode) == 'directory':
453
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)
455
479
root_ie = self._get_file_ie(
456
store, from_dir.encode("utf-8"),
480
store, encode_git_path(from_dir),
457
481
posixpath.basename(from_dir), mode, hexsha)
459
483
yield (from_dir, "V", root_ie.kind, root_ie)
461
485
if root_ie.kind == 'directory':
462
todo.append((store, from_dir.encode("utf-8"),
486
todo.append((store, encode_git_path(from_dir),
463
487
b"", hexsha, root_ie.file_id))
465
489
(store, path, relpath, hexsha, parent_id) = todo.pop()
483
507
ie = self._get_file_ie(
484
508
store, child_path, name, mode, hexsha, parent_id)
485
yield (child_relpath.decode('utf-8'), "V", ie.kind, ie)
509
yield (decode_git_path(child_relpath), "V", ie.kind, ie)
487
511
def _get_file_ie(self, store, path, name, mode, hexsha, parent_id):
488
512
if not isinstance(path, bytes):
490
514
if not isinstance(name, bytes):
491
515
raise TypeError(name)
492
516
kind = mode_kind(mode)
493
path = path.decode('utf-8')
494
name = name.decode("utf-8")
517
path = decode_git_path(path)
518
name = decode_git_path(name)
495
519
file_id = self.mapping.generate_file_id(path)
496
520
ie = entry_factory[kind](file_id, name, parent_id)
497
521
if kind == 'symlink':
498
ie.symlink_target = store[hexsha].data.decode('utf-8')
522
ie.symlink_target = decode_git_path(store[hexsha].data)
499
523
elif kind == 'tree-reference':
500
524
ie.reference_revision = self.mapping.revision_id_foreign_to_bzr(
503
data = store[hexsha].data
504
ie.text_sha1 = osutils.sha_string(data)
505
ie.text_size = len(data)
506
529
ie.executable = mode_is_executable(mode)
509
532
def _get_dir_ie(self, path, parent_id):
510
path = path.decode('utf-8')
533
path = decode_git_path(path)
511
534
file_id = self.mapping.generate_file_id(path)
512
535
return GitTreeDirectory(file_id, posixpath.basename(path), parent_id)
645
668
return (kind, len(contents), executable,
646
669
osutils.sha_string(contents))
647
670
elif kind == 'symlink':
648
return (kind, None, None, store[hexsha].data.decode('utf-8'))
671
return (kind, None, None, decode_git_path(store[hexsha].data))
649
672
elif kind == 'tree-reference':
650
nested_repo = self._get_submodule_repository(path.encode('utf-8'))
673
nested_repo = self._get_submodule_repository(encode_git_path(path))
651
674
return (kind, None, None,
652
675
nested_repo.lookup_foreign_revision_id(hexsha))
654
677
return (kind, None, None, None)
656
def find_related_paths_across_trees(self, paths, trees=[],
657
require_versioned=True):
660
if require_versioned:
661
trees = [self] + (trees if trees is not None else [])
665
if t.is_versioned(p):
670
raise errors.PathsNotVersionedError(unversioned)
671
return filter(self.is_versioned, paths)
673
679
def _iter_tree_contents(self, include_trees=False):
674
680
if self.tree is None:
703
709
def walkdirs(self, prefix=u""):
704
710
(store, mode, hexsha) = self._lookup_path(prefix)
706
[(store, prefix.encode('utf-8'), hexsha, self.path2id(prefix))])
712
[(store, encode_git_path(prefix), hexsha)])
708
store, path, tree_sha, parent_id = todo.popleft()
709
path_decoded = path.decode('utf-8')
714
store, path, tree_sha = todo.popleft()
715
path_decoded = decode_git_path(path)
710
716
tree = store[tree_sha]
712
718
for name, mode, hexsha in tree.iteritems():
713
719
if self.mapping.is_special_file(name):
715
721
child_path = posixpath.join(path, name)
716
file_id = self.path2id(child_path.decode('utf-8'))
717
722
if stat.S_ISDIR(mode):
718
todo.append((store, child_path, hexsha, file_id))
723
todo.append((store, child_path, hexsha))
720
(child_path.decode('utf-8'), name.decode('utf-8'),
725
(decode_git_path(child_path), decode_git_path(name),
721
726
mode_kind(mode), None,
722
file_id, mode_kind(mode)))
723
yield (path_decoded, parent_id), children
728
yield path_decoded, children
726
731
def tree_delta_from_git_changes(changes, mappings,
727
732
specific_files=None,
728
733
require_versioned=False, include_root=False,
734
source_extras=None, target_extras=None):
730
735
"""Create a TreeDelta from two git trees.
732
737
source and target are iterators over tuples with:
735
740
(old_mapping, new_mapping) = mappings
736
741
if target_extras is None:
737
742
target_extras = set()
743
if source_extras is None:
744
source_extras = set()
738
745
ret = delta.TreeDelta()
740
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
741
750
if newpath == b'' and not include_root:
752
copied = (change_type == 'copy')
743
753
if oldpath is not None:
744
oldpath_decoded = oldpath.decode('utf-8')
754
oldpath_decoded = decode_git_path(oldpath)
746
756
oldpath_decoded = None
747
757
if newpath is not None:
748
newpath_decoded = newpath.decode('utf-8')
758
newpath_decoded = decode_git_path(newpath)
750
760
newpath_decoded = None
751
761
if not (specific_files is None or
757
767
specific_files, newpath_decoded))):
760
if oldpath_decoded is None:
761
fileid = new_mapping.generate_file_id(newpath_decoded)
766
775
oldversioned = False
777
oldversioned = (oldpath not in source_extras)
770
779
oldexe = mode_is_executable(oldmode)
771
780
oldkind = mode_kind(oldmode)
775
if oldpath_decoded == u'':
779
788
(oldparentpath, oldname) = osutils.split(oldpath_decoded)
780
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:
781
811
fileid = old_mapping.generate_file_id(oldpath_decoded)
782
if newpath_decoded is None:
813
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
816
if old_mapping.is_special_file(oldpath):
804
818
if new_mapping.is_special_file(newpath):
806
820
if oldpath is None and newpath is None:
808
change = _mod_tree.TreeChange(
822
change = InventoryTreeChange(
809
823
fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
810
824
(oldversioned, newversioned),
811
825
(oldparent, newparent), (oldname, newname),
812
(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':
814
832
added.append((newpath, newkind))
815
833
elif newpath is None or newmode == 0:
816
834
ret.removed.append(change)
817
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):
818
844
ret.renamed.append(change)
819
845
elif mode_kind(oldmode) != mode_kind(newmode):
820
846
ret.kind_changed.append(change)
834
860
for path, kind in added:
835
861
if kind == 'directory' and path not in implicit_dirs:
837
path_decoded = osutils.normalized_filename(path)[0]
863
path_decoded = decode_git_path(path)
838
864
parent_path, basename = osutils.split(path_decoded)
839
865
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),
866
file_id = new_mapping.generate_file_id(path_decoded)
869
file_id, (None, path_decoded), True,
844
872
(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
877
def changes_from_git_changes(changes, mapping, specific_files=None,
858
include_unchanged=False, target_extras=None):
878
include_unchanged=False, source_extras=None,
859
880
"""Create a iter_changes-like generator from a git stream.
861
882
source and target are iterators over tuples with:
864
885
if target_extras is None:
865
886
target_extras = set()
866
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
867
894
if oldpath is not None:
868
oldpath_decoded = oldpath.decode('utf-8')
895
oldpath_decoded = decode_git_path(oldpath)
870
897
oldpath_decoded = None
871
898
if newpath is not None:
872
newpath_decoded = newpath.decode('utf-8')
899
newpath_decoded = decode_git_path(newpath)
874
901
newpath_decoded = None
875
902
if not (specific_files is None or
927
952
newparentpath, newname = osutils.split(newpath_decoded)
928
953
newparent = mapping.generate_file_id(newparentpath)
929
954
if (not include_unchanged and
930
oldkind == 'directory' and newkind == 'directory' and
955
oldkind == 'directory' and newkind == 'directory' and
931
956
oldpath_decoded == newpath_decoded):
933
yield _mod_tree.TreeChange(
934
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),
935
971
(oldversioned, newversioned),
936
972
(oldparent, newparent), (oldname, newname),
937
(oldkind, newkind), (oldexe, newexe))
973
(oldkind, newkind), (oldexe, newexe),
974
copied=(change_type == 'copy'))
940
977
class InterGitTrees(_mod_tree.InterTree):
944
981
_matching_to_tree_format = None
945
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)
948
994
def is_compatible(cls, source, target):
949
return (isinstance(source, GitRevisionTree) and
950
isinstance(target, GitRevisionTree))
995
return isinstance(source, GitTree) and isinstance(target, GitTree)
952
997
def compare(self, want_unchanged=False, specific_files=None,
953
998
extra_trees=None, require_versioned=False, include_root=False,
954
999
want_unversioned=False):
955
1000
with self.lock_read():
956
changes, target_extras = self._iter_git_changes(
1001
changes, source_extras, target_extras = self._iter_git_changes(
957
1002
want_unchanged=want_unchanged,
958
1003
require_versioned=require_versioned,
959
1004
specific_files=specific_files,
962
1007
return tree_delta_from_git_changes(
963
1008
changes, (self.source.mapping, self.target.mapping),
964
1009
specific_files=specific_files,
965
include_root=include_root, target_extras=target_extras)
1010
include_root=include_root,
1011
source_extras=source_extras, target_extras=target_extras)
967
1013
def iter_changes(self, include_unchanged=False, specific_files=None,
968
1014
pb=None, extra_trees=[], require_versioned=True,
969
1015
want_unversioned=False):
970
1016
with self.lock_read():
971
changes, target_extras = self._iter_git_changes(
1017
changes, source_extras, target_extras = self._iter_git_changes(
972
1018
want_unchanged=include_unchanged,
973
1019
require_versioned=require_versioned,
974
1020
specific_files=specific_files,
978
1024
changes, self.target.mapping,
979
1025
specific_files=specific_files,
980
1026
include_unchanged=include_unchanged,
1027
source_extras=source_extras,
981
1028
target_extras=target_extras)
983
1030
def _iter_git_changes(self, want_unchanged=False, specific_files=None,
984
1031
require_versioned=False, extra_trees=None,
985
want_unversioned=False):
986
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
988
1053
def find_target_path(self, path, recurse='none'):
989
1054
ret = self.find_target_paths([path], recurse=recurse)
996
1061
def find_target_paths(self, paths, recurse='none'):
997
1062
paths = set(paths)
999
changes = self._iter_git_changes(specific_files=paths)[0]
1000
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])
1001
1070
if oldpath in paths:
1002
ret[oldpath] = newpath
1071
ret[oldpath] = decode_git_path(new[0]) if new[0] else None
1003
1072
for path in paths:
1004
1073
if path not in ret:
1005
1074
if self.source.has_filename(path):
1014
1083
def find_source_paths(self, paths, recurse='none'):
1015
1084
paths = set(paths)
1017
changes = self._iter_git_changes(specific_files=paths)[0]
1018
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])
1019
1092
if newpath in paths:
1020
ret[newpath] = oldpath
1093
ret[newpath] = decode_git_path(old[0]) if old[0] else None
1021
1094
for path in paths:
1022
1095
if path not in ret:
1023
1096
if self.target.has_filename(path):
1033
class InterGitRevisionTrees(InterGitTrees):
1034
"""InterTree that works between two git revision trees."""
1036
_matching_from_tree_format = None
1037
_matching_to_tree_format = None
1038
_test_mutable_trees_to_test_trees = None
1041
def is_compatible(cls, source, target):
1042
return (isinstance(source, GitRevisionTree) and
1043
isinstance(target, GitRevisionTree))
1045
def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1046
require_versioned=True, extra_trees=None,
1047
want_unversioned=False):
1048
trees = [self.source]
1049
if extra_trees is not None:
1050
trees.extend(extra_trees)
1051
if specific_files is not None:
1052
specific_files = self.target.find_related_paths_across_trees(
1053
specific_files, trees,
1054
require_versioned=require_versioned)
1056
if (self.source._repository._git.object_store !=
1057
self.target._repository._git.object_store):
1058
store = OverlayObjectStore(
1059
[self.source._repository._git.object_store,
1060
self.target._repository._git.object_store])
1062
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()
1068
_mod_tree.InterTree.register_optimiser(InterGitRevisionTrees)
1071
class MutableGitIndexTree(mutabletree.MutableTree):
1106
_mod_tree.InterTree.register_optimiser(InterGitTrees)
1109
class MutableGitIndexTree(mutabletree.MutableTree, GitTree):
1073
1111
def __init__(self):
1074
1112
self._lock_mode = None
1145
1186
def _read_submodule_head(self, path):
1146
1187
raise NotImplementedError(self._read_submodule_head)
1148
def _submodule_info(self):
1149
if self._submodules is None:
1151
with self.get_file('.gitmodules') as f:
1152
config = GitConfigFile.from_file(f)
1153
self._submodules = {
1154
path: (url, section)
1155
for path, url, section in parse_submodules(config)}
1156
except errors.NoSuchFile:
1157
self._submodules = {}
1158
return self._submodules
1160
1189
def _lookup_index(self, encoded_path):
1161
1190
if not isinstance(encoded_path, bytes):
1162
1191
raise TypeError(encoded_path)
1192
1221
# TODO(jelmer): Keep track of dirty per index
1193
1222
self._index_dirty = True
1195
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):
1196
1246
if kind == "directory":
1197
1247
# Git indexes don't contain directories
1249
elif kind == "file":
1202
1252
file, stat_val = self.get_file_with_stat(path)
1222
1272
stat_val = os.stat_result(
1223
1273
(stat.S_IFLNK, 0, 0, 0, 0, 0, 0, 0, 0, 0))
1224
blob.set_raw_string(
1225
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))
1226
1277
# Add object to the repository if it didn't exist yet
1227
1278
if blob.id not in self.store:
1228
1279
self.store.add_object(blob)
1310
1365
key = (posixpath.dirname(path), path)
1311
1366
if key not in ret and self.is_versioned(path):
1312
1367
ret[key] = self._get_dir_ie(path, self.path2id(key[0]))
1313
return ((path, ie) for ((_, path), ie) in sorted(viewitems(ret)))
1368
return ((path, ie) for ((_, path), ie) in sorted(ret.items()))
1315
1370
def iter_references(self):
1316
1371
if self.supports_tree_reference():
1325
1380
posixpath.basename(path).strip("/"), parent_id)
1327
1382
def _get_file_ie(self, name, path, value, parent_id):
1328
if not isinstance(name, text_type):
1383
if not isinstance(name, str):
1329
1384
raise TypeError(name)
1330
if not isinstance(path, text_type):
1385
if not isinstance(path, str):
1331
1386
raise TypeError(path)
1332
1387
if not isinstance(value, tuple) or len(value) != 10:
1333
1388
raise TypeError(value)
1435
1480
return rename_tuples
1437
1482
def rename_one(self, from_rel, to_rel, after=None):
1438
from_path = from_rel.encode("utf-8")
1483
from_path = encode_git_path(from_rel)
1439
1484
to_rel, can_access = osutils.normalized_filename(to_rel)
1440
1485
if not can_access:
1441
1486
raise errors.InvalidNormalization(to_rel)
1442
to_path = to_rel.encode("utf-8")
1487
to_path = encode_git_path(to_rel)
1443
1488
with self.lock_tree_write():
1445
1490
# Perhaps it's already moved?
1589
1619
def _live_entry(self, relpath):
1590
1620
raise NotImplementedError(self._live_entry)
1592
def get_transform(self, pb=None):
1593
from ..transform import TreeTransform
1594
return TreeTransform(self, pb=pb)
1598
class InterIndexGitTree(InterGitTrees):
1599
"""InterTree that works between a Git revision tree and an index."""
1601
def __init__(self, source, target):
1602
super(InterIndexGitTree, self).__init__(source, target)
1603
self._index = target.index
1606
def is_compatible(cls, source, target):
1607
return (isinstance(source, GitRevisionTree) and
1608
isinstance(target, MutableGitIndexTree))
1610
def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1611
require_versioned=False, extra_trees=None,
1612
want_unversioned=False):
1613
trees = [self.source]
1614
if extra_trees is not None:
1615
trees.extend(extra_trees)
1616
if specific_files is not None:
1617
specific_files = self.target.find_related_paths_across_trees(
1618
specific_files, trees,
1619
require_versioned=require_versioned)
1620
# 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
1621
1634
with self.lock_read():
1622
return changes_between_git_tree_and_working_copy(
1623
self.source.store, self.source.tree,
1624
self.target, want_unchanged=want_unchanged,
1625
want_unversioned=want_unversioned)
1628
_mod_tree.InterTree.register_optimiser(InterIndexGitTree)
1631
def changes_between_git_tree_and_working_copy(store, from_tree_sha, target,
1632
want_unchanged=False,
1633
want_unversioned=False):
1634
"""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):
1639
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)
1668
1716
blobs[path] = (live_entry.sha, cleanup_mode(live_entry.mode))
1669
1717
if want_unversioned:
1670
for e in target.extras():
1671
st = target._lstat(e)
1718
for extra in target._iter_files_recursive(include_dirs=False):
1673
np, accessible = osutils.normalized_filename(e)
1720
extra, accessible = osutils.normalized_filename(extra)
1674
1721
except UnicodeDecodeError:
1675
1722
raise errors.BadFilenameEncoding(
1723
extra, osutils._fs_enc)
1724
np = encode_git_path(extra)
1727
st = target._lstat(extra)
1677
1728
if stat.S_ISDIR(st.st_mode):
1730
elif stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
1680
1731
blob = blob_from_path_and_stat(
1681
target.abspath(e).encode(osutils._fs_enc), st)
1682
store.add_object(blob)
1683
np = np.encode('utf-8')
1732
target.abspath(extra).encode(osutils._fs_enc), st)
1735
target.store.add_object(blob)
1684
1736
blobs[np] = (blob.id, cleanup_mode(st.st_mode))
1686
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,
1690
want_unchanged=want_unchanged, change_type_same=True), extras
1739
target.store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()]), extras