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 is_executable(self, path, file_id=None):
299
(mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
302
raise errors.NoSuchId(self, path)
304
# the tree root is a directory
306
return mode_is_executable(mode)
308
def kind(self, path, file_id=None):
309
if self.tree is None:
310
raise errors.NoSuchFile(path)
312
(mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
315
raise errors.NoSuchFile(path)
317
# the tree root is a directory
319
return mode_kind(mode)
321
def has_filename(self, path):
322
if self.tree is None:
325
tree_lookup_path(self.store.__getitem__, self.tree,
326
path.encode("utf-8"))
332
def list_files(self, include_root=False, from_dir=None, recursive=True):
335
if self.tree is None:
337
(mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
338
from_dir.encode("utf-8"))
339
if mode is None: # Root
340
root_ie = self._get_dir_ie(b"", None)
342
parent_path = posixpath.dirname(from_dir.encode("utf-8"))
343
parent_id = self._fileid_map.lookup_file_id(parent_path)
344
if mode_kind(mode) == 'directory':
345
root_ie = self._get_dir_ie(from_dir.encode("utf-8"), parent_id)
347
root_ie = self._get_file_ie(from_dir.encode("utf-8"),
348
posixpath.basename(from_dir), mode, hexsha)
349
if from_dir != "" or include_root:
350
yield (from_dir, "V", root_ie.kind, root_ie.file_id, root_ie)
352
if root_ie.kind == 'directory':
353
todo.add((from_dir.encode("utf-8"), hexsha, root_ie.file_id))
355
(path, hexsha, parent_id) = todo.pop()
356
tree = self.store[hexsha]
357
for name, mode, hexsha in tree.iteritems():
358
if self.mapping.is_special_file(name):
360
child_path = posixpath.join(path, name)
361
if stat.S_ISDIR(mode):
362
ie = self._get_dir_ie(child_path, parent_id)
364
todo.add((child_path, hexsha, ie.file_id))
366
ie = self._get_file_ie(child_path, name, mode, hexsha, parent_id)
367
yield child_path.decode('utf-8'), "V", ie.kind, ie.file_id, ie
369
def _get_file_ie(self, path, name, mode, hexsha, parent_id):
370
if type(path) is not bytes:
371
raise TypeError(path)
372
if type(name) is not bytes:
373
raise TypeError(name)
374
kind = mode_kind(mode)
375
file_id = self._fileid_map.lookup_file_id(path)
376
ie = entry_factory[kind](file_id, name.decode("utf-8"), parent_id)
377
if kind == 'symlink':
378
ie.symlink_target = self.store[hexsha].data.decode('utf-8')
379
elif kind == 'tree-reference':
380
ie.reference_revision = self.mapping.revision_id_foreign_to_bzr(hexsha)
382
data = self.store[hexsha].data
383
ie.text_sha1 = osutils.sha_string(data)
384
ie.text_size = len(data)
385
ie.executable = mode_is_executable(mode)
386
ie.revision = self.get_file_revision(path.decode('utf-8'))
389
def _get_dir_ie(self, path, parent_id):
390
file_id = self._fileid_map.lookup_file_id(path)
391
ie = GitTreeDirectory(file_id,
392
posixpath.basename(path).decode("utf-8"), parent_id)
393
ie.revision = self.get_file_revision(path.decode('utf-8'))
396
def iter_children(self, file_id):
397
path = self._fileid_map.lookup_path(file_id)
398
mode, tree_sha = tree_lookup_path(self.store.__getitem__, self.tree, path)
399
if stat.S_ISDIR(mode):
400
for name, mode, hexsha in self.store[tree_sha].iteritems():
401
yield self._fileid_map.lookup_file_id(posixpath.join(path, name))
403
def iter_child_entries(self, path, file_id=None):
404
encoded_path = path.encode('utf-8')
405
(mode, tree_sha) = tree_lookup_path(self.store.__getitem__, self.tree,
408
if not stat.S_ISDIR(mode):
411
file_id = self.path2id(path)
412
tree = self.store[tree_sha]
413
for name, mode, hexsha in tree.iteritems():
414
if self.mapping.is_special_file(name):
416
child_path = posixpath.join(encoded_path, name)
417
if stat.S_ISDIR(mode):
418
yield self._get_dir_ie(child_path, file_id)
420
yield self._get_file_ie(child_path, name, mode, hexsha,
423
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
424
if self.tree is None:
427
# TODO(jelmer): Support yield parents
428
raise NotImplementedError
429
if specific_file_ids is not None:
430
specific_paths = [self.id2path(file_id).encode('utf-8') for file_id in specific_file_ids]
431
if specific_paths in ([""], []):
432
specific_paths = None
434
specific_paths = set(specific_paths)
436
specific_paths = None
437
todo = set([("", self.tree, None)])
439
path, tree_sha, parent_id = todo.pop()
440
ie = self._get_dir_ie(path, parent_id)
441
if specific_paths is None or path in specific_paths:
442
yield path.decode("utf-8"), ie
443
tree = self.store[tree_sha]
444
for name, mode, hexsha in tree.iteritems():
445
if self.mapping.is_special_file(name):
447
child_path = posixpath.join(path, name)
448
if stat.S_ISDIR(mode):
449
if (specific_paths is None or
450
any(filter(lambda p: p.startswith(child_path), specific_paths))):
451
todo.add((child_path, hexsha, ie.file_id))
452
elif specific_paths is None or child_path in specific_paths:
453
yield (child_path.decode("utf-8"),
454
self._get_file_ie(child_path, name, mode, hexsha,
457
def get_revision_id(self):
458
"""See RevisionTree.get_revision_id."""
459
return self._revision_id
461
def get_file_sha1(self, path, file_id=None, stat_value=None):
462
if self.tree is None:
463
raise errors.NoSuchFile(path)
464
return osutils.sha_string(self.get_file_text(path, file_id))
466
def get_file_verifier(self, path, file_id=None, stat_value=None):
467
(mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
468
path.encode('utf-8'))
469
return ("GIT", hexsha)
471
def get_file_text(self, path, file_id=None):
472
"""See RevisionTree.get_file_text."""
473
if self.tree is None:
474
raise errors.NoSuchFile(path)
475
(mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
476
path.encode('utf-8'))
477
if stat.S_ISREG(mode):
478
return self.store[hexsha].data
482
def get_symlink_target(self, path, file_id=None):
483
"""See RevisionTree.get_symlink_target."""
484
(mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
485
path.encode('utf-8'))
486
if stat.S_ISLNK(mode):
487
return self.store[hexsha].data.decode('utf-8')
491
def _comparison_data(self, entry, path):
493
return None, False, None
494
return entry.kind, entry.executable, None
496
def path_content_summary(self, path):
497
"""See Tree.path_content_summary."""
499
(mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree, path.encode('utf-8'))
501
return ('missing', None, None, None)
502
kind = mode_kind(mode)
504
executable = mode_is_executable(mode)
505
contents = self.store[hexsha].data
506
return (kind, len(contents), executable, osutils.sha_string(contents))
507
elif kind == 'symlink':
508
return (kind, None, None, self.store[hexsha].data)
510
return (kind, None, None, None)
513
def tree_delta_from_git_changes(changes, mapping,
514
(old_fileid_map, new_fileid_map), specific_files=None,
515
require_versioned=False, include_root=False):
516
"""Create a TreeDelta from two git trees.
518
source and target are iterators over tuples with:
519
(filename, sha, mode)
521
ret = delta.TreeDelta()
522
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
523
if newpath == u'' and not include_root:
525
if not (specific_files is None or
526
(oldpath is not None and osutils.is_inside_or_parent_of_any(specific_files, oldpath)) or
527
(newpath is not None and osutils.is_inside_or_parent_of_any(specific_files, newpath))):
529
if mapping.is_special_file(oldpath):
531
if mapping.is_special_file(newpath):
533
if oldpath is None and newpath is None:
536
file_id = new_fileid_map.lookup_file_id(newpath)
537
ret.added.append((newpath.decode('utf-8'), file_id, mode_kind(newmode)))
538
elif newpath is None:
539
file_id = old_fileid_map.lookup_file_id(oldpath)
540
ret.removed.append((oldpath.decode('utf-8'), file_id, mode_kind(oldmode)))
541
elif oldpath != newpath:
542
file_id = old_fileid_map.lookup_file_id(oldpath)
544
(oldpath.decode('utf-8'), newpath.decode('utf-8'), file_id,
545
mode_kind(newmode), (oldsha != newsha),
546
(oldmode != newmode)))
547
elif mode_kind(oldmode) != mode_kind(newmode):
548
file_id = new_fileid_map.lookup_file_id(newpath)
549
ret.kind_changed.append(
550
(newpath.decode('utf-8'), file_id, mode_kind(oldmode),
552
elif oldsha != newsha or oldmode != newmode:
553
if stat.S_ISDIR(oldmode) and stat.S_ISDIR(newmode):
555
file_id = new_fileid_map.lookup_file_id(newpath)
557
(newpath.decode('utf-8'), file_id, mode_kind(newmode),
558
(oldsha != newsha), (oldmode != newmode)))
560
file_id = new_fileid_map.lookup_file_id(newpath)
561
ret.unchanged.append((newpath.decode('utf-8'), file_id, mode_kind(newmode)))
565
def changes_from_git_changes(changes, mapping, specific_files=None, include_unchanged=False):
566
"""Create a iter_changes-like generator from a git stream.
568
source and target are iterators over tuples with:
569
(filename, sha, mode)
571
for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
572
if not (specific_files is None or
573
(oldpath is not None and osutils.is_inside_or_parent_of_any(specific_files, oldpath)) or
574
(newpath is not None and osutils.is_inside_or_parent_of_any(specific_files, newpath))):
576
path = (oldpath, newpath)
577
if oldpath is not None and mapping.is_special_file(oldpath):
579
if newpath is not None and mapping.is_special_file(newpath):
582
fileid = mapping.generate_file_id(newpath)
588
oldpath = oldpath.decode("utf-8")
591
oldexe = mode_is_executable(oldmode)
592
oldkind = mode_kind(oldmode)
597
(oldparentpath, oldname) = osutils.split(oldpath)
598
oldparent = mapping.generate_file_id(oldparentpath)
599
fileid = mapping.generate_file_id(oldpath)
606
newpath = newpath.decode("utf-8")
607
if newmode is not None:
608
newexe = mode_is_executable(newmode)
609
newkind = mode_kind(newmode)
617
newparentpath, newname = osutils.split(newpath)
618
newparent = mapping.generate_file_id(newparentpath)
619
if (not include_unchanged and
620
oldkind == 'directory' and newkind == 'directory' and
623
yield (fileid, (oldpath, newpath), (oldsha != newsha),
624
(oldpath is not None, newpath is not None),
625
(oldparent, newparent), (oldname, newname),
626
(oldkind, newkind), (oldexe, newexe))
629
class InterGitTrees(_mod_tree.InterTree):
630
"""InterTree that works between two git trees."""
632
_matching_from_tree_format = None
633
_matching_to_tree_format = None
634
_test_mutable_trees_to_test_trees = None
637
def is_compatible(cls, source, target):
638
return (isinstance(source, GitRevisionTree) and
639
isinstance(target, GitRevisionTree))
641
def compare(self, want_unchanged=False, specific_files=None,
642
extra_trees=None, require_versioned=False, include_root=False,
643
want_unversioned=False):
644
changes = self._iter_git_changes(want_unchanged=want_unchanged,
645
require_versioned=require_versioned,
646
specific_files=specific_files)
647
source_fileid_map = self.source._fileid_map
648
target_fileid_map = self.target._fileid_map
649
return tree_delta_from_git_changes(changes, self.target.mapping,
650
(source_fileid_map, target_fileid_map),
651
specific_files=specific_files, include_root=include_root)
653
def iter_changes(self, include_unchanged=False, specific_files=None,
654
pb=None, extra_trees=[], require_versioned=True,
655
want_unversioned=False):
656
changes = self._iter_git_changes(want_unchanged=include_unchanged,
657
require_versioned=require_versioned,
658
specific_files=specific_files)
659
return changes_from_git_changes(changes, self.target.mapping,
660
specific_files=specific_files, include_unchanged=include_unchanged)
662
def _iter_git_changes(self, want_unchanged=False):
663
raise NotImplementedError(self._iter_git_changes)
666
class InterGitRevisionTrees(InterGitTrees):
667
"""InterTree that works between two git revision trees."""
669
_matching_from_tree_format = None
670
_matching_to_tree_format = None
671
_test_mutable_trees_to_test_trees = None
674
def is_compatible(cls, source, target):
675
return (isinstance(source, GitRevisionTree) and
676
isinstance(target, GitRevisionTree))
678
def _iter_git_changes(self, want_unchanged=False, require_versioned=False,
679
specific_files=None):
680
if require_versioned and specific_files:
681
for path in specific_files:
682
if (not self.source.is_versioned(path) and
683
not self.target.is_versioned(path)):
684
raise errors.PathsNotVersionedError(path)
686
if self.source._repository._git.object_store != self.target._repository._git.object_store:
687
store = OverlayObjectStore([self.source._repository._git.object_store,
688
self.target._repository._git.object_store])
690
store = self.source._repository._git.object_store
691
return self.source._repository._git.object_store.tree_changes(
692
self.source.tree, self.target.tree, want_unchanged=want_unchanged,
693
include_trees=True, change_type_same=True)
696
_mod_tree.InterTree.register_optimiser(InterGitRevisionTrees)
699
class MutableGitIndexTree(mutabletree.MutableTree):
702
self._lock_mode = None
704
self._versioned_dirs = None
706
def is_versioned(self, path):
707
with self.lock_read():
708
path = path.rstrip('/').encode('utf-8')
709
return (path in self.index or self._has_dir(path))
711
def _has_dir(self, path):
714
if self._versioned_dirs is None:
716
return path in self._versioned_dirs
718
def _load_dirs(self):
719
if self._lock_mode is None:
720
raise errors.ObjectNotLocked(self)
721
self._versioned_dirs = set()
723
self._ensure_versioned_dir(posixpath.dirname(p))
725
def _ensure_versioned_dir(self, dirname):
726
if dirname in self._versioned_dirs:
729
self._ensure_versioned_dir(posixpath.dirname(dirname))
730
self._versioned_dirs.add(dirname)
732
def path2id(self, path):
733
with self.lock_read():
734
path = path.rstrip('/')
735
if self.is_versioned(path.rstrip('/')):
736
return self._fileid_map.lookup_file_id(path.encode("utf-8"))
739
def has_id(self, file_id):
741
self.id2path(file_id)
742
except errors.NoSuchId:
747
def id2path(self, file_id):
750
if type(file_id) is not bytes:
751
raise TypeError(file_id)
752
with self.lock_read():
754
path = self._fileid_map.lookup_path(file_id)
756
raise errors.NoSuchId(self, file_id)
757
path = path.decode('utf-8')
758
if self.is_versioned(path):
760
raise errors.NoSuchId(self, file_id)
762
def _set_root_id(self, file_id):
763
self._fileid_map.set_file_id("", file_id)
765
def get_root_id(self):
766
return self.path2id("")
768
def _add(self, files, ids, kinds):
769
for (path, file_id, kind) in zip(files, ids, kinds):
770
if file_id is not None:
771
raise workingtree.SettingFileIdUnsupported()
772
path, can_access = osutils.normalized_filename(path)
774
raise errors.InvalidNormalization(path)
775
self._index_add_entry(path, kind)
777
def _index_add_entry(self, path, kind, flags=0):
778
if not isinstance(path, basestring):
779
raise TypeError(path)
780
if kind == "directory":
781
# Git indexes don't contain directories
786
file, stat_val = self.get_file_with_stat(path)
787
except (errors.NoSuchFile, IOError):
788
# TODO: Rather than come up with something here, use the old index
790
stat_val = os.stat_result(
791
(stat.S_IFREG | 0644, 0, 0, 0, 0, 0, 0, 0, 0, 0))
792
blob.set_raw_string(file.read())
793
elif kind == "symlink":
796
stat_val = self._lstat(path)
797
except (errors.NoSuchFile, OSError):
798
# TODO: Rather than come up with something here, use the
800
stat_val = os.stat_result(
801
(stat.S_IFLNK, 0, 0, 0, 0, 0, 0, 0, 0, 0))
803
self.get_symlink_target(path).encode("utf-8"))
805
raise AssertionError("unknown kind '%s'" % kind)
806
# Add object to the repository if it didn't exist yet
807
if not blob.id in self.store:
808
self.store.add_object(blob)
809
# Add an entry to the index or update the existing entry
810
ensure_normalized_path(path)
811
encoded_path = path.encode("utf-8")
812
if b'\r' in encoded_path or b'\n' in encoded_path:
813
# TODO(jelmer): Why do we need to do this?
814
trace.mutter('ignoring path with invalid newline in it: %r', path)
816
self.index[encoded_path] = index_entry_from_stat(
817
stat_val, blob.id, flags)
818
if self._versioned_dirs is not None:
819
self._ensure_versioned_dir(encoded_path)
821
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
823
raise NotImplementedError(self.iter_entries_by_dir)
824
with self.lock_read():
825
if specific_file_ids is not None:
827
for file_id in specific_file_ids:
831
specific_paths.append(self.id2path(file_id))
832
except errors.NoSuchId:
834
specific_paths = set(specific_paths)
836
specific_paths = None
837
root_ie = self._get_dir_ie(u"", None)
839
if specific_paths is None or u"" in specific_paths:
840
ret[(None, u"")] = root_ie
841
dir_ids = {u"": root_ie.file_id}
842
for path, value in self.index.iteritems():
843
if self.mapping.is_special_file(path):
845
path = path.decode("utf-8")
846
if specific_paths is not None and not path in specific_paths:
848
(parent, name) = posixpath.split(path)
850
file_ie = self._get_file_ie(name, path, value, None)
851
except errors.NoSuchFile:
853
if yield_parents or specific_file_ids is None:
854
for (dir_path, dir_ie) in self._add_missing_parent_ids(parent,
856
ret[(posixpath.dirname(dir_path), dir_path)] = dir_ie
857
file_ie.parent_id = self.path2id(parent)
858
ret[(posixpath.dirname(path), path)] = file_ie
859
return ((path, ie) for ((_, path), ie) in sorted(ret.items()))
862
def _get_dir_ie(self, path, parent_id):
863
file_id = self.path2id(path)
864
return GitTreeDirectory(file_id,
865
posixpath.basename(path).strip("/"), parent_id)
867
def _get_file_ie(self, name, path, value, parent_id):
868
if type(name) is not unicode:
869
raise TypeError(name)
870
if type(path) is not unicode:
871
raise TypeError(path)
872
if not isinstance(value, tuple) or len(value) != 10:
873
raise TypeError(value)
874
(ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = value
875
file_id = self.path2id(path)
876
if type(file_id) != str:
878
kind = mode_kind(mode)
879
ie = entry_factory[kind](file_id, name, parent_id)
880
if kind == 'symlink':
881
ie.symlink_target = self.get_symlink_target(path, file_id)
884
data = self.get_file_text(path, file_id)
885
except errors.NoSuchFile:
888
if e.errno != errno.ENOENT:
892
data = self.branch.repository._git.object_store[sha].data
893
ie.text_sha1 = osutils.sha_string(data)
894
ie.text_size = len(data)
895
ie.executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
899
def _add_missing_parent_ids(self, path, dir_ids):
902
parent = posixpath.dirname(path).strip("/")
903
ret = self._add_missing_parent_ids(parent, dir_ids)
904
parent_id = dir_ids[parent]
905
ie = self._get_dir_ie(path, parent_id)
906
dir_ids[path] = ie.file_id
907
ret.append((path, ie))
910
def _comparison_data(self, entry, path):
912
return None, False, None
913
return entry.kind, entry.executable, None
915
def _unversion_path(self, path):
916
if self._lock_mode is None:
917
raise errors.ObjectNotLocked(self)
918
encoded_path = path.encode("utf-8")
921
del self.index[encoded_path]
923
# A directory, perhaps?
924
for p in list(self.index):
925
if p.startswith(encoded_path+b"/"):
930
self._versioned_dirs = None
933
def unversion(self, paths, file_ids=None):
934
with self.lock_tree_write():
936
if self._unversion_path(path) == 0:
937
raise errors.NoSuchFile(path)
938
self._versioned_dirs = None
944
def update_basis_by_delta(self, revid, delta):
945
# TODO(jelmer): This shouldn't be called, it's inventory specific.
946
for (old_path, new_path, file_id, ie) in delta:
947
if old_path is not None and old_path.encode('utf-8') in self.index:
948
del self.index[old_path.encode('utf-8')]
949
self._versioned_dirs = None
950
if new_path is not None and ie.kind != 'directory':
951
self._index_add_entry(new_path, ie.kind)
953
self._set_merges_from_parent_ids([])
955
def move(self, from_paths, to_dir=None, after=None):
957
with self.lock_tree_write():
958
to_abs = self.abspath(to_dir)
959
if not os.path.isdir(to_abs):
960
raise errors.BzrMoveFailedError('', to_dir,
961
errors.NotADirectory(to_abs))
963
for from_rel in from_paths:
964
from_tail = os.path.split(from_rel)[-1]
965
to_rel = os.path.join(to_dir, from_tail)
966
self.rename_one(from_rel, to_rel, after=after)
967
rename_tuples.append((from_rel, to_rel))
971
def rename_one(self, from_rel, to_rel, after=None):
972
from_path = from_rel.encode("utf-8")
973
to_rel, can_access = osutils.normalized_filename(to_rel)
975
raise errors.InvalidNormalization(to_rel)
976
to_path = to_rel.encode("utf-8")
977
with self.lock_tree_write():
979
# Perhaps it's already moved?
981
not self.has_filename(from_rel) and
982
self.has_filename(to_rel) and
983
not self.is_versioned(to_rel))
985
if not self.has_filename(to_rel):
986
raise errors.BzrMoveFailedError(from_rel, to_rel,
987
errors.NoSuchFile(to_rel))
988
if self.basis_tree().is_versioned(to_rel):
989
raise errors.BzrMoveFailedError(from_rel, to_rel,
990
errors.AlreadyVersionedError(to_rel))
992
kind = self.kind(to_rel)
995
to_kind = self.kind(to_rel)
996
except errors.NoSuchFile:
997
exc_type = errors.BzrRenameFailedError
1000
exc_type = errors.BzrMoveFailedError
1001
if self.is_versioned(to_rel):
1002
raise exc_type(from_rel, to_rel,
1003
errors.AlreadyVersionedError(to_rel))
1004
if not self.has_filename(from_rel):
1005
raise errors.BzrMoveFailedError(from_rel, to_rel,
1006
errors.NoSuchFile(from_rel))
1007
if not self.is_versioned(from_rel):
1008
raise exc_type(from_rel, to_rel,
1009
errors.NotVersionedError(from_rel))
1010
if self.has_filename(to_rel):
1011
raise errors.RenameFailedFilesExist(
1012
from_rel, to_rel, errors.FileExists(to_rel))
1014
kind = self.kind(from_rel)
1016
if not after and not from_path in self.index and kind != 'directory':
1018
raise errors.BzrMoveFailedError(from_rel, to_rel,
1019
errors.NotVersionedError(path=from_rel))
1023
self._rename_one(from_rel, to_rel)
1024
except OSError as e:
1025
if e.errno == errno.ENOENT:
1026
raise errors.BzrMoveFailedError(from_rel, to_rel,
1027
errors.NoSuchFile(to_rel))
1029
if kind != 'directory':
1031
del self.index[from_path]
1034
self._index_add_entry(to_rel, kind)
1036
todo = [p for p in self.index if p.startswith(from_path+'/')]
1038
self.index[posixpath.join(to_path, posixpath.relpath(p, from_path))] = self.index[p]
1041
self._versioned_dirs = None