1
# Copyright (C) 2009-2018 Jelmer Vernooij <jelmer@jelmer.uk>
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
from __future__ import absolute_import
23
from io import BytesIO
26
from dulwich.index import (
27
index_entry_from_stat,
29
from dulwich.object_store import (
33
from dulwich.objects import (
52
from ...revision import NULL_REVISION
54
from .mapping import (
62
class GitTreeDirectory(_mod_tree.TreeDirectory):
64
__slots__ = ['file_id', 'name', 'parent_id', 'children', 'revision']
66
def __init__(self, file_id, name, parent_id, revision=None):
67
self.file_id = file_id
69
self.parent_id = parent_id
72
self.revision = revision
83
return self.__class__(
84
self.file_id, self.name, self.parent_id, self.revision)
87
return "%s(file_id=%r, name=%r, parent_id=%r, revision=%r)" % (
88
self.__class__.__name__, self.file_id, self.name,
89
self.parent_id, self.revision)
91
def __eq__(self, other):
92
return (self.kind == other.kind and
93
self.file_id == other.file_id and
94
self.name == other.name and
95
self.parent_id == other.parent_id and
96
self.revision == other.revision)
99
class GitTreeFile(_mod_tree.TreeFile):
101
__slots__ = ['file_id', 'name', 'parent_id', 'text_size', 'text_sha1', 'revision',
104
def __init__(self, file_id, name, parent_id, revision=None):
105
self.file_id = file_id
107
self.parent_id = parent_id
108
self.revision = revision
109
self.text_size = None
110
self.text_sha1 = None
111
self.executable = None
117
def __eq__(self, other):
118
return (self.kind == other.kind and
119
self.file_id == other.file_id and
120
self.name == other.name and
121
self.parent_id == other.parent_id and
122
self.revision == other.revision and
123
self.text_sha1 == other.text_sha1 and
124
self.text_size == other.text_size and
125
self.executable == other.executable)
128
ret = self.__class__(
129
self.file_id, self.name, self.parent_id,
131
ret.text_sha1 = self.text_sha1
132
ret.text_size = self.text_size
133
ret.executable = self.executable
137
class GitTreeSymlink(_mod_tree.TreeLink):
139
__slots__ = ['file_id', 'name', 'parent_id', 'symlink_target', 'revision']
141
def __init__(self, file_id, name, parent_id, revision=None,
142
symlink_target=None):
143
self.file_id = file_id
145
self.parent_id = parent_id
146
self.revision = revision
147
self.symlink_target = symlink_target
154
def executable(self):
161
def __eq__(self, other):
162
return (self.kind == other.kind and
163
self.file_id == other.file_id and
164
self.name == other.name and
165
self.parent_id == other.parent_id and
166
self.revision == other.revision and
167
self.symlink_target == other.symlink_target)
170
return self.__class__(
171
self.file_id, self.name, self.parent_id,
172
self.revision, self.symlink_target)
176
'directory': GitTreeDirectory,
178
'symlink': GitTreeSymlink,
182
def ensure_normalized_path(path):
183
"""Check whether path is normalized.
185
:raises InvalidNormalization: When path is not normalized, and cannot be
186
accessed on this platform by the normalized path.
187
:return: The NFC normalised version of path.
189
norm_path, can_access = osutils.normalized_filename(path)
190
if norm_path != path:
194
raise errors.InvalidNormalization(path)
198
class GitRevisionTree(revisiontree.RevisionTree):
199
"""Revision tree implementation based on Git objects."""
201
def __init__(self, repository, revision_id):
202
self._revision_id = revision_id
203
self._repository = repository
204
self.store = repository._git.object_store
205
if type(revision_id) is not str:
206
raise TypeError(revision_id)
207
self.commit_id, self.mapping = repository.lookup_bzr_revision_id(revision_id)
208
if revision_id == NULL_REVISION:
210
self.mapping = default_mapping
211
self._fileid_map = GitFileIdMap(
216
commit = self.store[self.commit_id]
218
raise errors.NoSuchRevision(repository, revision_id)
219
self.tree = commit.tree
220
self._fileid_map = self.mapping.get_fileid_map(self.store.__getitem__, self.tree)
222
def supports_rename_tracking(self):
225
def get_file_revision(self, path, file_id=None):
226
change_scanner = self._repository._file_change_scanner
227
if self.commit_id == ZERO_SHA:
229
(path, commit_id) = change_scanner.find_last_change_revision(
230
path.encode('utf-8'), self.commit_id)
231
return self._repository.lookup_foreign_revision_id(commit_id, self.mapping)
233
def get_file_mtime(self, path, file_id=None):
234
revid = self.get_file_revision(path, file_id)
236
rev = self._repository.get_revision(revid)
237
except errors.NoSuchRevision:
238
raise errors.FileTimestampUnavailable(path)
241
def id2path(self, file_id):
243
path = self._fileid_map.lookup_path(file_id)
245
raise errors.NoSuchId(self, file_id)
246
path = path.decode('utf-8')
247
if self.is_versioned(path):
249
raise errors.NoSuchId(self, file_id)
251
def is_versioned(self, path):
252
return self.has_filename(path)
254
def path2id(self, path):
255
if self.mapping.is_special_file(path):
257
return self._fileid_map.lookup_file_id(path.encode('utf-8'))
259
def all_file_ids(self):
260
return set(self._fileid_map.all_file_ids())
262
def all_versioned_paths(self):
264
todo = set([('', self.tree)])
266
(path, tree_id) = todo.pop()
269
tree = self.store[tree_id]
270
for name, mode, hexsha in tree.items():
271
subpath = posixpath.join(path, name)
272
if stat.S_ISDIR(mode):
273
todo.add((subpath, hexsha))
278
def get_root_id(self):
279
if self.tree is None:
281
return self.path2id("")
283
def has_or_had_id(self, file_id):
285
path = self.id2path(file_id)
286
except errors.NoSuchId:
290
def has_id(self, file_id):
292
path = self.id2path(file_id)
293
except errors.NoSuchId:
295
return self.has_filename(path)
297
def _lookup_path(self, path):
298
if self.tree is None:
299
raise errors.NoSuchFile(path)
301
return tree_lookup_path(self.store.__getitem__, self.tree,
302
path.encode('utf-8'))
304
raise errors.NoSuchFile(self, path)
306
def is_executable(self, path, file_id=None):
307
(mode, hexsha) = self._lookup_path(path)
309
# the tree root is a directory
311
return mode_is_executable(mode)
313
def kind(self, path, file_id=None):
314
(mode, hexsha) = self._lookup_path(path)
316
# the tree root is a directory
318
return mode_kind(mode)
320
def has_filename(self, path):
322
self._lookup_path(path)
323
except errors.NoSuchFile:
328
def list_files(self, include_root=False, from_dir=None, recursive=True):
329
if self.tree is None:
333
(mode, hexsha) = self._lookup_path(from_dir)
334
if mode is None: # Root
335
root_ie = self._get_dir_ie(b"", None)
337
parent_path = posixpath.dirname(from_dir.encode("utf-8"))
338
parent_id = self._fileid_map.lookup_file_id(parent_path)
339
if mode_kind(mode) == 'directory':
340
root_ie = self._get_dir_ie(from_dir.encode("utf-8"), parent_id)
342
root_ie = self._get_file_ie(from_dir.encode("utf-8"),
343
posixpath.basename(from_dir), mode, hexsha)
344
if from_dir != "" or include_root:
345
yield (from_dir, "V", root_ie.kind, root_ie.file_id, root_ie)
347
if root_ie.kind == 'directory':
348
todo.add((from_dir.encode("utf-8"), hexsha, root_ie.file_id))
350
(path, hexsha, parent_id) = todo.pop()
351
tree = self.store[hexsha]
352
for name, mode, hexsha in tree.iteritems():
353
if self.mapping.is_special_file(name):
355
child_path = posixpath.join(path, name)
356
if stat.S_ISDIR(mode):
357
ie = self._get_dir_ie(child_path, parent_id)
359
todo.add((child_path, hexsha, ie.file_id))
361
ie = self._get_file_ie(child_path, name, mode, hexsha, parent_id)
362
yield child_path.decode('utf-8'), "V", ie.kind, ie.file_id, ie
364
def _get_file_ie(self, path, name, mode, hexsha, parent_id):
365
if type(path) is not bytes:
366
raise TypeError(path)
367
if type(name) is not bytes:
368
raise TypeError(name)
369
kind = mode_kind(mode)
370
file_id = self._fileid_map.lookup_file_id(path)
371
ie = entry_factory[kind](file_id, name.decode("utf-8"), parent_id)
372
if kind == 'symlink':
373
ie.symlink_target = self.store[hexsha].data.decode('utf-8')
374
elif kind == 'tree-reference':
375
ie.reference_revision = self.mapping.revision_id_foreign_to_bzr(hexsha)
377
data = self.store[hexsha].data
378
ie.text_sha1 = osutils.sha_string(data)
379
ie.text_size = len(data)
380
ie.executable = mode_is_executable(mode)
381
ie.revision = self.get_file_revision(path.decode('utf-8'))
384
def _get_dir_ie(self, path, parent_id):
385
file_id = self._fileid_map.lookup_file_id(path)
386
ie = GitTreeDirectory(file_id,
387
posixpath.basename(path).decode("utf-8"), parent_id)
388
ie.revision = self.get_file_revision(path.decode('utf-8'))
391
def iter_child_entries(self, path, file_id=None):
392
(mode, tree_sha) = self._lookup_path(path)
394
if not stat.S_ISDIR(mode):
397
encoded_path = path.encode('utf-8')
398
file_id = self.path2id(path)
399
tree = self.store[tree_sha]
400
for name, mode, hexsha in tree.iteritems():
401
if self.mapping.is_special_file(name):
403
child_path = posixpath.join(encoded_path, name)
404
if stat.S_ISDIR(mode):
405
yield self._get_dir_ie(child_path, file_id)
407
yield self._get_file_ie(child_path, name, mode, hexsha,
410
def iter_entries_by_dir(self, specific_files=None, yield_parents=False):
411
if self.tree is None:
414
# TODO(jelmer): Support yield parents
415
raise NotImplementedError
416
if specific_files is not None:
417
if specific_files in ([""], []):
418
specific_files = None
420
specific_files = set([p.encode('utf-8') for p in specific_files])
421
todo = set([("", self.tree, None)])
423
path, tree_sha, parent_id = todo.pop()
424
ie = self._get_dir_ie(path, parent_id)
425
if specific_files is None or path in specific_files:
426
yield path.decode("utf-8"), ie
427
tree = self.store[tree_sha]
428
for name, mode, hexsha in tree.iteritems():
429
if self.mapping.is_special_file(name):
431
child_path = posixpath.join(path, name)
432
if stat.S_ISDIR(mode):
433
if (specific_files is None or
434
any(filter(lambda p: p.startswith(child_path), specific_files))):
435
todo.add((child_path, hexsha, ie.file_id))
436
elif specific_files is None or child_path in specific_files:
437
yield (child_path.decode("utf-8"),
438
self._get_file_ie(child_path, name, mode, hexsha,
441
def get_revision_id(self):
442
"""See RevisionTree.get_revision_id."""
443
return self._revision_id
445
def get_file_sha1(self, path, file_id=None, stat_value=None):
446
if self.tree is None:
447
raise errors.NoSuchFile(path)
448
return osutils.sha_string(self.get_file_text(path, file_id))
450
def get_file_verifier(self, path, file_id=None, stat_value=None):
451
(mode, hexsha) = self._lookup_path(path)
452
return ("GIT", hexsha)
454
def get_file_text(self, path, file_id=None):
455
"""See RevisionTree.get_file_text."""
456
(mode, hexsha) = self._lookup_path(path)
457
if stat.S_ISREG(mode):
458
return self.store[hexsha].data
462
def get_symlink_target(self, path, file_id=None):
463
"""See RevisionTree.get_symlink_target."""
464
(mode, hexsha) = self._lookup_path(path)
465
if stat.S_ISLNK(mode):
466
return self.store[hexsha].data.decode('utf-8')
470
def _comparison_data(self, entry, path):
472
return None, False, None
473
return entry.kind, entry.executable, None
475
def path_content_summary(self, path):
476
"""See Tree.path_content_summary."""
478
(mode, hexsha) = self._lookup_path(path)
479
except errors.NoSuchFile:
480
return ('missing', None, None, None)
481
kind = mode_kind(mode)
483
executable = mode_is_executable(mode)
484
contents = self.store[hexsha].data
485
return (kind, len(contents), executable, osutils.sha_string(contents))
486
elif kind == 'symlink':
487
return (kind, None, None, self.store[hexsha].data)
489
return (kind, None, None, None)
491
def find_related_paths_across_trees(self, paths, trees=[],
492
require_versioned=True):
495
if require_versioned:
496
trees = [self] + (trees if trees is not None else [])
500
if t.is_versioned(p):
505
raise errors.PathsNotVersionedError(unversioned)
506
return filter(self.is_versioned, paths)
509
def tree_delta_from_git_changes(changes, mapping,
510
(old_fileid_map, new_fileid_map), specific_files=None,
511
require_versioned=False, include_root=False):
512
"""Create a TreeDelta from two git trees.
514
source and target are iterators over tuples with:
515
(filename, sha, mode)
517
ret = delta.TreeDelta()
518
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
519
if newpath == u'' and not include_root:
521
if not (specific_files is None or
522
(oldpath is not None and osutils.is_inside_or_parent_of_any(specific_files, oldpath)) or
523
(newpath is not None and osutils.is_inside_or_parent_of_any(specific_files, newpath))):
525
if mapping.is_special_file(oldpath):
527
if mapping.is_special_file(newpath):
529
if oldpath is None and newpath is None:
532
file_id = new_fileid_map.lookup_file_id(newpath)
533
ret.added.append((newpath.decode('utf-8'), file_id, mode_kind(newmode)))
534
elif newpath is None:
535
file_id = old_fileid_map.lookup_file_id(oldpath)
536
ret.removed.append((oldpath.decode('utf-8'), file_id, mode_kind(oldmode)))
537
elif oldpath != newpath:
538
file_id = old_fileid_map.lookup_file_id(oldpath)
540
(oldpath.decode('utf-8'), newpath.decode('utf-8'), file_id,
541
mode_kind(newmode), (oldsha != newsha),
542
(oldmode != newmode)))
543
elif mode_kind(oldmode) != mode_kind(newmode):
544
file_id = new_fileid_map.lookup_file_id(newpath)
545
ret.kind_changed.append(
546
(newpath.decode('utf-8'), file_id, mode_kind(oldmode),
548
elif oldsha != newsha or oldmode != newmode:
549
if stat.S_ISDIR(oldmode) and stat.S_ISDIR(newmode):
551
file_id = new_fileid_map.lookup_file_id(newpath)
553
(newpath.decode('utf-8'), file_id, mode_kind(newmode),
554
(oldsha != newsha), (oldmode != newmode)))
556
file_id = new_fileid_map.lookup_file_id(newpath)
557
ret.unchanged.append((newpath.decode('utf-8'), file_id, mode_kind(newmode)))
561
def changes_from_git_changes(changes, mapping, specific_files=None, include_unchanged=False):
562
"""Create a iter_changes-like generator from a git stream.
564
source and target are iterators over tuples with:
565
(filename, sha, mode)
567
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
568
if not (specific_files is None or
569
(oldpath is not None and osutils.is_inside_or_parent_of_any(specific_files, oldpath)) or
570
(newpath is not None and osutils.is_inside_or_parent_of_any(specific_files, newpath))):
572
path = (oldpath, newpath)
573
if oldpath is not None and mapping.is_special_file(oldpath):
575
if newpath is not None and mapping.is_special_file(newpath):
578
fileid = mapping.generate_file_id(newpath)
584
oldpath = oldpath.decode("utf-8")
587
oldexe = mode_is_executable(oldmode)
588
oldkind = mode_kind(oldmode)
593
(oldparentpath, oldname) = osutils.split(oldpath)
594
oldparent = mapping.generate_file_id(oldparentpath)
595
fileid = mapping.generate_file_id(oldpath)
602
newpath = newpath.decode("utf-8")
603
if newmode is not None:
604
newexe = mode_is_executable(newmode)
605
newkind = mode_kind(newmode)
613
newparentpath, newname = osutils.split(newpath)
614
newparent = mapping.generate_file_id(newparentpath)
615
if (not include_unchanged and
616
oldkind == 'directory' and newkind == 'directory' and
619
yield (fileid, (oldpath, newpath), (oldsha != newsha),
620
(oldpath is not None, newpath is not None),
621
(oldparent, newparent), (oldname, newname),
622
(oldkind, newkind), (oldexe, newexe))
625
class InterGitTrees(_mod_tree.InterTree):
626
"""InterTree that works between two git trees."""
628
_matching_from_tree_format = None
629
_matching_to_tree_format = None
630
_test_mutable_trees_to_test_trees = None
633
def is_compatible(cls, source, target):
634
return (isinstance(source, GitRevisionTree) and
635
isinstance(target, GitRevisionTree))
637
def compare(self, want_unchanged=False, specific_files=None,
638
extra_trees=None, require_versioned=False, include_root=False,
639
want_unversioned=False):
640
changes = self._iter_git_changes(want_unchanged=want_unchanged,
641
require_versioned=require_versioned,
642
specific_files=specific_files,
643
extra_trees=extra_trees)
644
source_fileid_map = self.source._fileid_map
645
target_fileid_map = self.target._fileid_map
646
return tree_delta_from_git_changes(changes, self.target.mapping,
647
(source_fileid_map, target_fileid_map),
648
specific_files=specific_files, include_root=include_root)
650
def iter_changes(self, include_unchanged=False, specific_files=None,
651
pb=None, extra_trees=[], require_versioned=True,
652
want_unversioned=False):
653
changes = self._iter_git_changes(want_unchanged=include_unchanged,
654
require_versioned=require_versioned,
655
specific_files=specific_files,
656
extra_trees=extra_trees)
657
return changes_from_git_changes(changes, self.target.mapping,
658
specific_files=specific_files, include_unchanged=include_unchanged)
660
def _iter_git_changes(self, want_unchanged=False, specific_files=None,
661
require_versioned=False, extra_trees=None):
662
raise NotImplementedError(self._iter_git_changes)
665
class InterGitRevisionTrees(InterGitTrees):
666
"""InterTree that works between two git revision trees."""
668
_matching_from_tree_format = None
669
_matching_to_tree_format = None
670
_test_mutable_trees_to_test_trees = None
673
def is_compatible(cls, source, target):
674
return (isinstance(source, GitRevisionTree) and
675
isinstance(target, GitRevisionTree))
677
def _iter_git_changes(self, want_unchanged=False, specific_files=None,
678
require_versioned=True, extra_trees=None):
679
trees = [self.source]
680
if extra_trees is not None:
681
trees.extend(extra_trees)
682
if specific_files is not None:
683
specific_files = self.target.find_related_paths_across_trees(
684
specific_files, trees,
685
require_versioned=require_versioned)
687
if self.source._repository._git.object_store != self.target._repository._git.object_store:
688
store = OverlayObjectStore([self.source._repository._git.object_store,
689
self.target._repository._git.object_store])
691
store = self.source._repository._git.object_store
692
return self.source._repository._git.object_store.tree_changes(
693
self.source.tree, self.target.tree, want_unchanged=want_unchanged,
694
include_trees=True, change_type_same=True)
697
_mod_tree.InterTree.register_optimiser(InterGitRevisionTrees)
700
class MutableGitIndexTree(mutabletree.MutableTree):
703
self._lock_mode = None
705
self._versioned_dirs = None
707
def is_versioned(self, path):
708
with self.lock_read():
709
path = path.rstrip('/').encode('utf-8')
710
return (path in self.index or self._has_dir(path))
712
def _has_dir(self, path):
715
if self._versioned_dirs is None:
717
return path in self._versioned_dirs
719
def _load_dirs(self):
720
if self._lock_mode is None:
721
raise errors.ObjectNotLocked(self)
722
self._versioned_dirs = set()
724
self._ensure_versioned_dir(posixpath.dirname(p))
726
def _ensure_versioned_dir(self, dirname):
727
if dirname in self._versioned_dirs:
730
self._ensure_versioned_dir(posixpath.dirname(dirname))
731
self._versioned_dirs.add(dirname)
733
def path2id(self, path):
734
with self.lock_read():
735
path = path.rstrip('/')
736
if self.is_versioned(path.rstrip('/')):
737
return self._fileid_map.lookup_file_id(path.encode("utf-8"))
740
def has_id(self, file_id):
742
self.id2path(file_id)
743
except errors.NoSuchId:
748
def id2path(self, file_id):
751
if type(file_id) is not bytes:
752
raise TypeError(file_id)
753
with self.lock_read():
755
path = self._fileid_map.lookup_path(file_id)
757
raise errors.NoSuchId(self, file_id)
758
path = path.decode('utf-8')
759
if self.is_versioned(path):
761
raise errors.NoSuchId(self, file_id)
763
def _set_root_id(self, file_id):
764
self._fileid_map.set_file_id("", file_id)
766
def get_root_id(self):
767
return self.path2id("")
769
def _add(self, files, ids, kinds):
770
for (path, file_id, kind) in zip(files, ids, kinds):
771
if file_id is not None:
772
raise workingtree.SettingFileIdUnsupported()
773
path, can_access = osutils.normalized_filename(path)
775
raise errors.InvalidNormalization(path)
776
self._index_add_entry(path, kind)
778
def _index_add_entry(self, path, kind, flags=0):
779
if not isinstance(path, basestring):
780
raise TypeError(path)
781
if kind == "directory":
782
# Git indexes don't contain directories
787
file, stat_val = self.get_file_with_stat(path)
788
except (errors.NoSuchFile, IOError):
789
# TODO: Rather than come up with something here, use the old index
791
stat_val = os.stat_result(
792
(stat.S_IFREG | 0644, 0, 0, 0, 0, 0, 0, 0, 0, 0))
793
blob.set_raw_string(file.read())
794
elif kind == "symlink":
797
stat_val = self._lstat(path)
798
except (errors.NoSuchFile, OSError):
799
# TODO: Rather than come up with something here, use the
801
stat_val = os.stat_result(
802
(stat.S_IFLNK, 0, 0, 0, 0, 0, 0, 0, 0, 0))
804
self.get_symlink_target(path).encode("utf-8"))
806
raise AssertionError("unknown kind '%s'" % kind)
807
# Add object to the repository if it didn't exist yet
808
if not blob.id in self.store:
809
self.store.add_object(blob)
810
# Add an entry to the index or update the existing entry
811
ensure_normalized_path(path)
812
encoded_path = path.encode("utf-8")
813
if b'\r' in encoded_path or b'\n' in encoded_path:
814
# TODO(jelmer): Why do we need to do this?
815
trace.mutter('ignoring path with invalid newline in it: %r', path)
817
self.index[encoded_path] = index_entry_from_stat(
818
stat_val, blob.id, flags)
819
if self._versioned_dirs is not None:
820
self._ensure_versioned_dir(encoded_path)
822
def iter_entries_by_dir(self, specific_files=None, yield_parents=False):
824
raise NotImplementedError(self.iter_entries_by_dir)
825
with self.lock_read():
826
if specific_files is not None:
827
specific_files = set(specific_files)
829
specific_files = None
830
root_ie = self._get_dir_ie(u"", None)
832
if specific_files is None or u"" in specific_files:
833
ret[(None, u"")] = root_ie
834
dir_ids = {u"": root_ie.file_id}
835
for path, value in self.index.iteritems():
836
if self.mapping.is_special_file(path):
838
path = path.decode("utf-8")
839
if specific_files is not None and not path in specific_files:
841
(parent, name) = posixpath.split(path)
843
file_ie = self._get_file_ie(name, path, value, None)
844
except errors.NoSuchFile:
846
if yield_parents or specific_files is None:
847
for (dir_path, dir_ie) in self._add_missing_parent_ids(parent,
849
ret[(posixpath.dirname(dir_path), dir_path)] = dir_ie
850
file_ie.parent_id = self.path2id(parent)
851
ret[(posixpath.dirname(path), path)] = file_ie
852
return ((path, ie) for ((_, path), ie) in sorted(ret.items()))
855
def _get_dir_ie(self, path, parent_id):
856
file_id = self.path2id(path)
857
return GitTreeDirectory(file_id,
858
posixpath.basename(path).strip("/"), parent_id)
860
def _get_file_ie(self, name, path, value, parent_id):
861
if type(name) is not unicode:
862
raise TypeError(name)
863
if type(path) is not unicode:
864
raise TypeError(path)
865
if not isinstance(value, tuple) or len(value) != 10:
866
raise TypeError(value)
867
(ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = value
868
file_id = self.path2id(path)
869
if type(file_id) != str:
871
kind = mode_kind(mode)
872
ie = entry_factory[kind](file_id, name, parent_id)
873
if kind == 'symlink':
874
ie.symlink_target = self.get_symlink_target(path, file_id)
877
data = self.get_file_text(path, file_id)
878
except errors.NoSuchFile:
881
if e.errno != errno.ENOENT:
885
data = self.branch.repository._git.object_store[sha].data
886
ie.text_sha1 = osutils.sha_string(data)
887
ie.text_size = len(data)
888
ie.executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
892
def _add_missing_parent_ids(self, path, dir_ids):
895
parent = posixpath.dirname(path).strip("/")
896
ret = self._add_missing_parent_ids(parent, dir_ids)
897
parent_id = dir_ids[parent]
898
ie = self._get_dir_ie(path, parent_id)
899
dir_ids[path] = ie.file_id
900
ret.append((path, ie))
903
def _comparison_data(self, entry, path):
905
return None, False, None
906
return entry.kind, entry.executable, None
908
def _unversion_path(self, path):
909
if self._lock_mode is None:
910
raise errors.ObjectNotLocked(self)
911
encoded_path = path.encode("utf-8")
914
del self.index[encoded_path]
916
# A directory, perhaps?
917
for p in list(self.index):
918
if p.startswith(encoded_path+b"/"):
923
self._versioned_dirs = None
926
def unversion(self, paths, file_ids=None):
927
with self.lock_tree_write():
929
if self._unversion_path(path) == 0:
930
raise errors.NoSuchFile(path)
931
self._versioned_dirs = None
937
def update_basis_by_delta(self, revid, delta):
938
# TODO(jelmer): This shouldn't be called, it's inventory specific.
939
for (old_path, new_path, file_id, ie) in delta:
940
if old_path is not None and old_path.encode('utf-8') in self.index:
941
del self.index[old_path.encode('utf-8')]
942
self._versioned_dirs = None
943
if new_path is not None and ie.kind != 'directory':
944
self._index_add_entry(new_path, ie.kind)
946
self._set_merges_from_parent_ids([])
948
def move(self, from_paths, to_dir=None, after=None):
950
with self.lock_tree_write():
951
to_abs = self.abspath(to_dir)
952
if not os.path.isdir(to_abs):
953
raise errors.BzrMoveFailedError('', to_dir,
954
errors.NotADirectory(to_abs))
956
for from_rel in from_paths:
957
from_tail = os.path.split(from_rel)[-1]
958
to_rel = os.path.join(to_dir, from_tail)
959
self.rename_one(from_rel, to_rel, after=after)
960
rename_tuples.append((from_rel, to_rel))
964
def rename_one(self, from_rel, to_rel, after=None):
965
from_path = from_rel.encode("utf-8")
966
to_rel, can_access = osutils.normalized_filename(to_rel)
968
raise errors.InvalidNormalization(to_rel)
969
to_path = to_rel.encode("utf-8")
970
with self.lock_tree_write():
972
# Perhaps it's already moved?
974
not self.has_filename(from_rel) and
975
self.has_filename(to_rel) and
976
not self.is_versioned(to_rel))
978
if not self.has_filename(to_rel):
979
raise errors.BzrMoveFailedError(from_rel, to_rel,
980
errors.NoSuchFile(to_rel))
981
if self.basis_tree().is_versioned(to_rel):
982
raise errors.BzrMoveFailedError(from_rel, to_rel,
983
errors.AlreadyVersionedError(to_rel))
985
kind = self.kind(to_rel)
988
to_kind = self.kind(to_rel)
989
except errors.NoSuchFile:
990
exc_type = errors.BzrRenameFailedError
993
exc_type = errors.BzrMoveFailedError
994
if self.is_versioned(to_rel):
995
raise exc_type(from_rel, to_rel,
996
errors.AlreadyVersionedError(to_rel))
997
if not self.has_filename(from_rel):
998
raise errors.BzrMoveFailedError(from_rel, to_rel,
999
errors.NoSuchFile(from_rel))
1000
if not self.is_versioned(from_rel):
1001
raise exc_type(from_rel, to_rel,
1002
errors.NotVersionedError(from_rel))
1003
if self.has_filename(to_rel):
1004
raise errors.RenameFailedFilesExist(
1005
from_rel, to_rel, errors.FileExists(to_rel))
1007
kind = self.kind(from_rel)
1009
if not after and not from_path in self.index and kind != 'directory':
1011
raise errors.BzrMoveFailedError(from_rel, to_rel,
1012
errors.NotVersionedError(path=from_rel))
1016
self._rename_one(from_rel, to_rel)
1017
except OSError as e:
1018
if e.errno == errno.ENOENT:
1019
raise errors.BzrMoveFailedError(from_rel, to_rel,
1020
errors.NoSuchFile(to_rel))
1022
if kind != 'directory':
1024
del self.index[from_path]
1027
self._index_add_entry(to_rel, kind)
1029
todo = [p for p in self.index if p.startswith(from_path+'/')]
1031
self.index[posixpath.join(to_path, posixpath.relpath(p, from_path))] = self.index[p]
1034
self._versioned_dirs = None
1037
def find_related_paths_across_trees(self, paths, trees=[],
1038
require_versioned=True):
1042
if require_versioned:
1043
trees = [self] + (trees if trees is not None else [])
1047
if t.is_versioned(p):
1052
raise errors.PathsNotVersionedError(unversioned)
1054
return filter(self.is_versioned, paths)