369
419
state = self.current_dirstate()
370
420
if stat_value is None:
372
stat_value = os.lstat(file_abspath)
422
stat_value = osutils.lstat(file_abspath)
374
424
if e.errno == errno.ENOENT:
378
428
link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
379
stat_value=stat_value)
380
if entry[1][0][0] == 'f':
429
stat_value=stat_value)
430
if entry[1][0][0] == b'f':
381
431
if link_or_sha1 is None:
382
file_obj, statvalue = self.get_file_with_stat(file_id, path)
432
file_obj, statvalue = self.get_file_with_stat(path)
384
434
sha1 = osutils.sha_file(file_obj)
387
self._observed_sha1(file_id, path, (sha1, statvalue))
437
self._observed_sha1(path, (sha1, statvalue))
390
440
return link_or_sha1
393
def _get_inventory(self):
443
def _get_root_inventory(self):
394
444
"""Get the inventory for the tree. This is only valid within a lock."""
395
445
if 'evil' in debug.debug_flags:
396
trace.mutter_callsite(2,
397
"accessing .inventory forces a size of tree translation.")
446
trace.mutter_callsite(
447
2, "accessing .inventory forces a size of tree translation.")
398
448
if self._inventory is not None:
399
449
return self._inventory
400
450
self._must_be_locked()
401
451
self._generate_inventory()
402
452
return self._inventory
404
inventory = property(_get_inventory,
405
doc="Inventory of this Tree")
454
root_inventory = property(_get_root_inventory,
455
"Root inventory of this tree")
408
457
def get_parent_ids(self):
409
458
"""See Tree.get_parent_ids.
411
460
This implementation requests the ids list from the dirstate file.
413
return self.current_dirstate().get_parent_ids()
462
with self.lock_read():
463
return self.current_dirstate().get_parent_ids()
415
def get_reference_revision(self, file_id, path=None):
465
def get_reference_revision(self, path):
416
466
# referenced tree's revision is whatever's currently there
417
return self.get_nested_tree(file_id, path).last_revision()
467
return self.get_nested_tree(path).last_revision()
419
def get_nested_tree(self, file_id, path=None):
421
path = self.id2path(file_id)
422
# else: check file_id is at path?
469
def get_nested_tree(self, path):
423
470
return WorkingTree.open(self.abspath(path))
426
def get_root_id(self):
427
"""Return the id of this trees root"""
428
return self._get_entry(path='')[0][2]
430
def has_id(self, file_id):
431
state = self.current_dirstate()
432
row, parents = self._get_entry(file_id=file_id)
435
return osutils.lexists(pathjoin(
436
self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
438
def has_or_had_id(self, file_id):
439
state = self.current_dirstate()
440
row, parents = self._get_entry(file_id=file_id)
441
return row is not None
444
def id2path(self, file_id):
472
def id2path(self, file_id, recurse='down'):
445
473
"Convert a file-id to a path."
446
state = self.current_dirstate()
447
entry = self._get_entry(file_id=file_id)
448
if entry == (None, None):
449
raise errors.NoSuchId(tree=self, file_id=file_id)
450
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
451
return path_utf8.decode('utf8')
474
with self.lock_read():
475
state = self.current_dirstate()
476
entry = self._get_entry(file_id=file_id)
477
if entry == (None, None):
478
if recurse == 'down':
479
if 'evil' in debug.debug_flags:
480
trace.mutter_callsite(
481
2, "Tree.id2path scans all nested trees.")
482
for nested_path in self.iter_references():
483
nested_tree = self.get_nested_tree(nested_path)
485
return osutils.pathjoin(
486
nested_path, nested_tree.id2path(file_id))
487
except errors.NoSuchId:
489
raise errors.NoSuchId(tree=self, file_id=file_id)
490
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
491
return path_utf8.decode('utf8')
453
493
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
454
494
entry = self._get_entry(path=path)
455
495
if entry == (None, None):
456
return False # Missing entries are not executable
457
return entry[1][0][3] # Executable?
459
if not osutils.supports_executable():
460
def is_executable(self, file_id, path=None):
461
"""Test if a file is executable or not.
463
Note: The caller is expected to take a read-lock before calling this.
465
entry = self._get_entry(file_id=file_id, path=path)
496
return False # Missing entries are not executable
497
return entry[1][0][3] # Executable?
499
def is_executable(self, path):
500
"""Test if a file is executable or not.
502
Note: The caller is expected to take a read-lock before calling this.
504
if not self._supports_executable():
505
entry = self._get_entry(path=path)
466
506
if entry == (None, None):
468
508
return entry[1][0][3]
470
_is_executable_from_path_and_stat = \
471
_is_executable_from_path_and_stat_from_basis
473
def is_executable(self, file_id, path=None):
474
"""Test if a file is executable or not.
476
Note: The caller is expected to take a read-lock before calling this.
478
510
self._must_be_locked()
480
path = self.id2path(file_id)
481
mode = os.lstat(self.abspath(path)).st_mode
511
mode = osutils.lstat(self.abspath(path)).st_mode
482
512
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
484
514
def all_file_ids(self):
486
516
self._must_be_locked()
488
518
for key, tree_details in self.current_dirstate()._iter_entries():
489
if tree_details[0][0] in ('a', 'r'): # relocated
519
if tree_details[0][0] in (b'a', b'r'): # relocated
491
521
result.add(key[2])
524
def all_versioned_paths(self):
525
self._must_be_locked()
526
return {path for path, entry in
527
self.root_inventory.iter_entries(recursive=True)}
495
529
def __iter__(self):
496
530
"""Iterate through file_ids for this tree.
498
532
file_ids are in a WorkingTree if they are in the working inventory
499
533
and the working file exists.
502
for key, tree_details in self.current_dirstate()._iter_entries():
503
if tree_details[0][0] in ('a', 'r'): # absent, relocated
504
# not relevant to the working tree
506
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
507
if osutils.lexists(path):
508
result.append(key[2])
535
with self.lock_read():
537
for key, tree_details in self.current_dirstate()._iter_entries():
538
if tree_details[0][0] in (b'a', b'r'): # absent, relocated
539
# not relevant to the working tree
541
path = pathjoin(self.basedir, key[0].decode(
542
'utf8'), key[1].decode('utf8'))
543
if osutils.lexists(path):
544
result.append(key[2])
511
547
def iter_references(self):
512
548
if not self._repo_supports_tree_reference:
513
549
# When the repo doesn't support references, we will have nothing to
516
for key, tree_details in self.current_dirstate()._iter_entries():
517
if tree_details[0][0] in ('a', 'r'): # absent, relocated
518
# not relevant to the working tree
521
# the root is not a reference.
523
relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
525
if self._kind(relpath) == 'tree-reference':
526
yield relpath, key[2]
527
except errors.NoSuchFile:
528
# path is missing on disk.
552
with self.lock_read():
553
for key, tree_details in self.current_dirstate()._iter_entries():
554
if tree_details[0][0] in (b'a', b'r'): # absent, relocated
555
# not relevant to the working tree
558
# the root is not a reference.
560
relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
562
if self.kind(relpath) == 'tree-reference':
564
except errors.NoSuchFile:
565
# path is missing on disk.
531
def _observed_sha1(self, file_id, path, (sha1, statvalue)):
568
def _observed_sha1(self, path, sha_and_stat):
532
569
"""See MutableTree._observed_sha1."""
533
570
state = self.current_dirstate()
534
entry = self._get_entry(file_id=file_id, path=path)
535
state._observed_sha1(entry, sha1, statvalue)
537
def kind(self, file_id):
538
"""Return the kind of a file.
540
This is always the actual kind that's on disk, regardless of what it
543
Note: The caller is expected to take a read-lock before calling this.
545
relpath = self.id2path(file_id)
547
raise AssertionError(
548
"path for id {%s} is None!" % file_id)
549
return self._kind(relpath)
551
def _kind(self, relpath):
571
entry = self._get_entry(path=path)
572
state._observed_sha1(entry, *sha_and_stat)
574
def kind(self, relpath):
552
575
abspath = self.abspath(relpath)
553
576
kind = file_kind(abspath)
554
577
if (self._repo_supports_tree_reference and kind == 'directory'):
555
entry = self._get_entry(path=relpath)
556
if entry[1] is not None:
557
if entry[1][0][0] == 't':
558
kind = 'tree-reference'
578
with self.lock_read():
579
entry = self._get_entry(path=relpath)
580
if entry[1] is not None:
581
if entry[1][0][0] == b't':
582
kind = 'tree-reference'
562
585
def _last_revision(self):
563
586
"""See Mutable.last_revision."""
564
parent_ids = self.current_dirstate().get_parent_ids()
568
return _mod_revision.NULL_REVISION
587
with self.lock_read():
588
parent_ids = self.current_dirstate().get_parent_ids()
592
return _mod_revision.NULL_REVISION
570
594
def lock_read(self):
571
595
"""See Branch.lock_read, and WorkingTree.unlock.
573
:return: A bzrlib.lock.LogicalLockResult.
597
:return: A breezy.lock.LogicalLockResult.
575
599
self.branch.lock_read()
624
648
def lock_write(self):
625
649
"""See MutableTree.lock_write, and WorkingTree.unlock.
627
:return: A bzrlib.lock.LogicalLockResult.
651
:return: A breezy.lock.LogicalLockResult.
629
653
self.branch.lock_write()
630
654
return self._lock_self_write()
632
@needs_tree_write_lock
633
656
def move(self, from_paths, to_dir, after=False):
634
657
"""See WorkingTree.move()."""
636
659
if not from_paths:
638
state = self.current_dirstate()
639
if isinstance(from_paths, basestring):
641
to_dir_utf8 = to_dir.encode('utf8')
642
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
643
id_index = state._get_id_index()
644
# check destination directory
645
# get the details for it
646
to_entry_block_index, to_entry_entry_index, dir_present, entry_present = \
647
state._get_block_entry_index(to_entry_dirname, to_basename, 0)
648
if not entry_present:
649
raise errors.BzrMoveFailedError('', to_dir,
650
errors.NotVersionedError(to_dir))
651
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
652
# get a handle on the block itself.
653
to_block_index = state._ensure_block(
654
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
655
to_block = state._dirblocks[to_block_index]
656
to_abs = self.abspath(to_dir)
657
if not isdir(to_abs):
658
raise errors.BzrMoveFailedError('',to_dir,
659
errors.NotADirectory(to_abs))
661
if to_entry[1][0][0] != 'd':
662
raise errors.BzrMoveFailedError('',to_dir,
663
errors.NotADirectory(to_abs))
665
if self._inventory is not None:
666
update_inventory = True
668
to_dir_id = to_entry[0][2]
669
to_dir_ie = inv[to_dir_id]
671
update_inventory = False
674
def move_one(old_entry, from_path_utf8, minikind, executable,
675
fingerprint, packed_stat, size,
676
to_block, to_key, to_path_utf8):
677
state._make_absent(old_entry)
678
from_key = old_entry[0]
680
lambda:state.update_minimal(from_key,
682
executable=executable,
683
fingerprint=fingerprint,
684
packed_stat=packed_stat,
686
path_utf8=from_path_utf8))
687
state.update_minimal(to_key,
689
executable=executable,
690
fingerprint=fingerprint,
691
packed_stat=packed_stat,
693
path_utf8=to_path_utf8)
694
added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
695
new_entry = to_block[1][added_entry_index]
696
rollbacks.append(lambda:state._make_absent(new_entry))
698
for from_rel in from_paths:
699
# from_rel is 'pathinroot/foo/bar'
700
from_rel_utf8 = from_rel.encode('utf8')
701
from_dirname, from_tail = osutils.split(from_rel)
702
from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
703
from_entry = self._get_entry(path=from_rel)
704
if from_entry == (None, None):
705
raise errors.BzrMoveFailedError(from_rel,to_dir,
706
errors.NotVersionedError(path=from_rel))
708
from_id = from_entry[0][2]
709
to_rel = pathjoin(to_dir, from_tail)
710
to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
711
item_to_entry = self._get_entry(path=to_rel)
712
if item_to_entry != (None, None):
713
raise errors.BzrMoveFailedError(from_rel, to_rel,
714
"Target is already versioned.")
716
if from_rel == to_rel:
717
raise errors.BzrMoveFailedError(from_rel, to_rel,
718
"Source and target are identical.")
720
from_missing = not self.has_filename(from_rel)
721
to_missing = not self.has_filename(to_rel)
728
raise errors.BzrMoveFailedError(from_rel, to_rel,
729
errors.NoSuchFile(path=to_rel,
730
extra="New file has not been created yet"))
732
# neither path exists
733
raise errors.BzrRenameFailedError(from_rel, to_rel,
734
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
736
if from_missing: # implicitly just update our path mapping
661
with self.lock_tree_write():
662
state = self.current_dirstate()
663
if isinstance(from_paths, (str, bytes)):
665
to_dir_utf8 = to_dir.encode('utf8')
666
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
667
# check destination directory
668
# get the details for it
669
(to_entry_block_index, to_entry_entry_index, dir_present,
670
entry_present) = state._get_block_entry_index(
671
to_entry_dirname, to_basename, 0)
672
if not entry_present:
673
raise errors.BzrMoveFailedError(
674
'', to_dir, errors.NotVersionedError(to_dir))
675
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
676
# get a handle on the block itself.
677
to_block_index = state._ensure_block(
678
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
679
to_block = state._dirblocks[to_block_index]
680
to_abs = self.abspath(to_dir)
681
if not isdir(to_abs):
682
raise errors.BzrMoveFailedError('', to_dir,
683
errors.NotADirectory(to_abs))
685
if to_entry[1][0][0] != b'd':
686
raise errors.BzrMoveFailedError('', to_dir,
687
errors.NotADirectory(to_abs))
689
if self._inventory is not None:
690
update_inventory = True
691
inv = self.root_inventory
692
to_dir_id = to_entry[0][2]
694
update_inventory = False
696
# GZ 2017-03-28: The rollbacks variable was shadowed in the loop below
697
# missing those added here, but there's also no test coverage for this.
698
rollbacks = contextlib.ExitStack()
700
def move_one(old_entry, from_path_utf8, minikind, executable,
701
fingerprint, packed_stat, size,
702
to_block, to_key, to_path_utf8):
703
state._make_absent(old_entry)
704
from_key = old_entry[0]
706
state.update_minimal,
709
executable=executable,
710
fingerprint=fingerprint,
711
packed_stat=packed_stat,
713
path_utf8=from_path_utf8)
714
state.update_minimal(to_key,
716
executable=executable,
717
fingerprint=fingerprint,
718
packed_stat=packed_stat,
720
path_utf8=to_path_utf8)
721
added_entry_index, _ = state._find_entry_index(
723
new_entry = to_block[1][added_entry_index]
724
rollbacks.callback(state._make_absent, new_entry)
726
for from_rel in from_paths:
727
# from_rel is 'pathinroot/foo/bar'
728
from_rel_utf8 = from_rel.encode('utf8')
729
from_dirname, from_tail = osutils.split(from_rel)
730
from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
731
from_entry = self._get_entry(path=from_rel)
732
if from_entry == (None, None):
733
raise errors.BzrMoveFailedError(
735
errors.NotVersionedError(path=from_rel))
737
from_id = from_entry[0][2]
738
to_rel = pathjoin(to_dir, from_tail)
739
to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
740
item_to_entry = self._get_entry(path=to_rel)
741
if item_to_entry != (None, None):
742
raise errors.BzrMoveFailedError(
743
from_rel, to_rel, "Target is already versioned.")
745
if from_rel == to_rel:
746
raise errors.BzrMoveFailedError(
747
from_rel, to_rel, "Source and target are identical.")
749
from_missing = not self.has_filename(from_rel)
750
to_missing = not self.has_filename(to_rel)
737
752
move_file = False
739
raise errors.RenameFailedFilesExist(from_rel, to_rel)
757
raise errors.BzrMoveFailedError(
761
extra="New file has not been created yet"))
763
# neither path exists
764
raise errors.BzrRenameFailedError(
766
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
768
if from_missing: # implicitly just update our path mapping
771
raise errors.RenameFailedFilesExist(from_rel, to_rel)
742
def rollback_rename():
743
"""A single rename has failed, roll it back."""
744
# roll back everything, even if we encounter trouble doing one
747
# TODO: at least log the other exceptions rather than just
748
# losing them mbp 20070307
750
for rollback in reversed(rollbacks):
773
# perform the disk move first - its the most likely failure point.
775
from_rel_abs = self.abspath(from_rel)
776
to_rel_abs = self.abspath(to_rel)
754
exc_info = sys.exc_info()
756
raise exc_info[0], exc_info[1], exc_info[2]
758
# perform the disk move first - its the most likely failure point.
760
from_rel_abs = self.abspath(from_rel)
761
to_rel_abs = self.abspath(to_rel)
778
osutils.rename(from_rel_abs, to_rel_abs)
780
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
782
osutils.rename, to_rel_abs, from_rel_abs)
763
osutils.rename(from_rel_abs, to_rel_abs)
765
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
766
rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
768
# perform the rename in the inventory next if needed: its easy
772
from_entry = inv[from_id]
773
current_parent = from_entry.parent_id
774
inv.rename(from_id, to_dir_id, from_tail)
776
lambda: inv.rename(from_id, current_parent, from_tail))
777
# finally do the rename in the dirstate, which is a little
778
# tricky to rollback, but least likely to need it.
779
old_block_index, old_entry_index, dir_present, file_present = \
780
state._get_block_entry_index(from_dirname, from_tail_utf8, 0)
781
old_block = state._dirblocks[old_block_index][1]
782
old_entry = old_block[old_entry_index]
783
from_key, old_entry_details = old_entry
784
cur_details = old_entry_details[0]
786
to_key = ((to_block[0],) + from_key[1:3])
787
minikind = cur_details[0]
788
move_one(old_entry, from_path_utf8=from_rel_utf8,
790
executable=cur_details[3],
791
fingerprint=cur_details[1],
792
packed_stat=cur_details[4],
796
to_path_utf8=to_rel_utf8)
799
def update_dirblock(from_dir, to_key, to_dir_utf8):
800
"""Recursively update all entries in this dirblock."""
802
raise AssertionError("renaming root not supported")
803
from_key = (from_dir, '')
804
from_block_idx, present = \
805
state._find_block_index_from_key(from_key)
807
# This is the old record, if it isn't present, then
808
# there is theoretically nothing to update.
809
# (Unless it isn't present because of lazy loading,
810
# but we don't do that yet)
812
from_block = state._dirblocks[from_block_idx]
813
to_block_index, to_entry_index, _, _ = \
814
state._get_block_entry_index(to_key[0], to_key[1], 0)
815
to_block_index = state._ensure_block(
816
to_block_index, to_entry_index, to_dir_utf8)
817
to_block = state._dirblocks[to_block_index]
819
# Grab a copy since move_one may update the list.
820
for entry in from_block[1][:]:
821
if not (entry[0][0] == from_dir):
822
raise AssertionError()
823
cur_details = entry[1][0]
824
to_key = (to_dir_utf8, entry[0][1], entry[0][2])
825
from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
826
to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
827
minikind = cur_details[0]
829
# Deleted children of a renamed directory
830
# Do not need to be updated.
831
# Children that have been renamed out of this
832
# directory should also not be updated
834
move_one(entry, from_path_utf8=from_path_utf8,
836
executable=cur_details[3],
837
fingerprint=cur_details[1],
838
packed_stat=cur_details[4],
842
to_path_utf8=to_path_utf8)
844
# We need to move all the children of this
846
update_dirblock(from_path_utf8, to_key,
848
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
852
result.append((from_rel, to_rel))
853
state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
854
self._make_dirty(reset_inventory=False)
784
# perform the rename in the inventory next if needed: its easy
788
from_entry = inv.get_entry(from_id)
789
current_parent = from_entry.parent_id
790
inv.rename(from_id, to_dir_id, from_tail)
792
inv.rename, from_id, current_parent, from_tail)
793
# finally do the rename in the dirstate, which is a little
794
# tricky to rollback, but least likely to need it.
795
old_block_index, old_entry_index, dir_present, file_present = \
796
state._get_block_entry_index(
797
from_dirname, from_tail_utf8, 0)
798
old_block = state._dirblocks[old_block_index][1]
799
old_entry = old_block[old_entry_index]
800
from_key, old_entry_details = old_entry
801
cur_details = old_entry_details[0]
803
to_key = ((to_block[0],) + from_key[1:3])
804
minikind = cur_details[0]
805
move_one(old_entry, from_path_utf8=from_rel_utf8,
807
executable=cur_details[3],
808
fingerprint=cur_details[1],
809
packed_stat=cur_details[4],
813
to_path_utf8=to_rel_utf8)
816
def update_dirblock(from_dir, to_key, to_dir_utf8):
817
"""Recursively update all entries in this dirblock."""
819
raise AssertionError(
820
"renaming root not supported")
821
from_key = (from_dir, '')
822
from_block_idx, present = \
823
state._find_block_index_from_key(from_key)
825
# This is the old record, if it isn't present,
826
# then there is theoretically nothing to
827
# update. (Unless it isn't present because of
828
# lazy loading, but we don't do that yet)
830
from_block = state._dirblocks[from_block_idx]
831
to_block_index, to_entry_index, _, _ = \
832
state._get_block_entry_index(
833
to_key[0], to_key[1], 0)
834
to_block_index = state._ensure_block(
835
to_block_index, to_entry_index, to_dir_utf8)
836
to_block = state._dirblocks[to_block_index]
838
# Grab a copy since move_one may update the list.
839
for entry in from_block[1][:]:
840
if not (entry[0][0] == from_dir):
841
raise AssertionError()
842
cur_details = entry[1][0]
844
to_dir_utf8, entry[0][1], entry[0][2])
845
from_path_utf8 = osutils.pathjoin(
846
entry[0][0], entry[0][1])
847
to_path_utf8 = osutils.pathjoin(
848
to_dir_utf8, entry[0][1])
849
minikind = cur_details[0]
850
if minikind in (b'a', b'r'):
851
# Deleted children of a renamed directory
852
# Do not need to be updated. Children that
853
# have been renamed out of this directory
854
# should also not be updated
856
move_one(entry, from_path_utf8=from_path_utf8,
858
executable=cur_details[3],
859
fingerprint=cur_details[1],
860
packed_stat=cur_details[4],
864
to_path_utf8=to_path_utf8)
866
# We need to move all the children of this
868
update_dirblock(from_path_utf8, to_key,
870
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
871
except BaseException:
874
result.append((from_rel, to_rel))
875
state._mark_modified()
876
self._make_dirty(reset_inventory=False)
858
880
def _must_be_locked(self):
859
881
if not self._control_files._lock_count:
1083
1117
If tree is None, then that element is treated as an unreachable
1084
1118
parent tree - i.e. a ghost.
1086
dirstate = self.current_dirstate()
1087
if len(parents_list) > 0:
1088
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1089
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1093
parent_ids = [rev_id for rev_id, tree in parents_list]
1094
graph = self.branch.repository.get_graph()
1095
heads = graph.heads(parent_ids)
1096
accepted_revisions = set()
1098
# convert absent trees to the null tree, which we convert back to
1099
# missing on access.
1100
for rev_id, tree in parents_list:
1101
if len(accepted_revisions) > 0:
1102
# we always accept the first tree
1103
if rev_id in accepted_revisions or rev_id not in heads:
1104
# We have already included either this tree, or its
1105
# descendent, so we skip it.
1107
_mod_revision.check_not_reserved_id(rev_id)
1108
if tree is not None:
1109
real_trees.append((rev_id, tree))
1111
real_trees.append((rev_id,
1112
self.branch.repository.revision_tree(
1113
_mod_revision.NULL_REVISION)))
1114
ghosts.append(rev_id)
1115
accepted_revisions.add(rev_id)
1116
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1117
self._make_dirty(reset_inventory=False)
1120
with self.lock_tree_write():
1121
dirstate = self.current_dirstate()
1122
if len(parents_list) > 0:
1123
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1124
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1128
parent_ids = [rev_id for rev_id, tree in parents_list]
1129
graph = self.branch.repository.get_graph()
1130
heads = graph.heads(parent_ids)
1131
accepted_revisions = set()
1133
# convert absent trees to the null tree, which we convert back to
1134
# missing on access.
1135
for rev_id, tree in parents_list:
1136
if len(accepted_revisions) > 0:
1137
# we always accept the first tree
1138
if rev_id in accepted_revisions or rev_id not in heads:
1139
# We have already included either this tree, or its
1140
# descendent, so we skip it.
1142
_mod_revision.check_not_reserved_id(rev_id)
1143
if tree is not None:
1144
real_trees.append((rev_id, tree))
1146
real_trees.append((rev_id,
1147
self.branch.repository.revision_tree(
1148
_mod_revision.NULL_REVISION)))
1149
ghosts.append(rev_id)
1150
accepted_revisions.add(rev_id)
1152
if (len(real_trees) == 1
1154
and self.branch.repository._format.fast_deltas
1155
and isinstance(real_trees[0][1], InventoryRevisionTree)
1156
and self.get_parent_ids()):
1157
rev_id, rev_tree = real_trees[0]
1158
basis_id = self.get_parent_ids()[0]
1159
# There are times when basis_tree won't be in
1160
# self.branch.repository, (switch, for example)
1162
basis_tree = self.branch.repository.revision_tree(basis_id)
1163
except errors.NoSuchRevision:
1164
# Fall back to the set_parent_trees(), since we can't use
1165
# _make_delta if we can't get the RevisionTree
1168
delta = rev_tree.root_inventory._make_delta(
1169
basis_tree.root_inventory)
1170
dirstate.update_basis_by_delta(delta, rev_id)
1173
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1174
self._make_dirty(reset_inventory=False)
1119
1176
def _set_root_id(self, file_id):
1120
1177
"""See WorkingTree.set_root_id."""
1121
1178
state = self.current_dirstate()
1122
state.set_path_id('', file_id)
1179
state.set_path_id(b'', file_id)
1123
1180
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1124
1181
self._make_dirty(reset_inventory=True)
1165
1221
self.branch.unlock()
1167
@needs_tree_write_lock
1168
def unversion(self, file_ids):
1169
"""Remove the file ids in file_ids from the current versioned set.
1223
def unversion(self, paths):
1224
"""Remove the file ids in paths from the current versioned set.
1171
When a file_id is unversioned, all of its children are automatically
1226
When a directory is unversioned, all of its children are automatically
1174
:param file_ids: The file ids to stop versioning.
1229
:param paths: The file ids to stop versioning.
1175
1230
:raises: NoSuchId if any fileid is not currently versioned.
1179
state = self.current_dirstate()
1180
state._read_dirblocks_if_needed()
1181
ids_to_unversion = set(file_ids)
1182
paths_to_unversion = set()
1184
# check if the root is to be unversioned, if so, assert for now.
1185
# walk the state marking unversioned things as absent.
1186
# if there are any un-unversioned ids at the end, raise
1187
for key, details in state._dirblocks[0][1]:
1188
if (details[0][0] not in ('a', 'r') and # absent or relocated
1189
key[2] in ids_to_unversion):
1190
# I haven't written the code to unversion / yet - it should be
1192
raise errors.BzrError('Unversioning the / is not currently supported')
1194
while block_index < len(state._dirblocks):
1195
# process one directory at a time.
1196
block = state._dirblocks[block_index]
1197
# first check: is the path one to remove - it or its children
1198
delete_block = False
1199
for path in paths_to_unversion:
1200
if (block[0].startswith(path) and
1201
(len(block[0]) == len(path) or
1202
block[0][len(path)] == '/')):
1203
# this entire block should be deleted - its the block for a
1204
# path to unversion; or the child of one
1207
# TODO: trim paths_to_unversion as we pass by paths
1209
# this block is to be deleted: process it.
1210
# TODO: we can special case the no-parents case and
1211
# just forget the whole block.
1232
with self.lock_tree_write():
1235
state = self.current_dirstate()
1236
state._read_dirblocks_if_needed()
1239
file_id = self.path2id(path)
1241
raise errors.NoSuchFile(self, path)
1242
file_ids.add(file_id)
1243
ids_to_unversion = set(file_ids)
1244
paths_to_unversion = set()
1246
# check if the root is to be unversioned, if so, assert for now.
1247
# walk the state marking unversioned things as absent.
1248
# if there are any un-unversioned ids at the end, raise
1249
for key, details in state._dirblocks[0][1]:
1250
if (details[0][0] not in (b'a', b'r') and # absent or relocated
1251
key[2] in ids_to_unversion):
1252
# I haven't written the code to unversion / yet - it should
1254
raise errors.BzrError(
1255
'Unversioning the / is not currently supported')
1257
while block_index < len(state._dirblocks):
1258
# process one directory at a time.
1259
block = state._dirblocks[block_index]
1260
# first check: is the path one to remove - it or its children
1261
delete_block = False
1262
for path in paths_to_unversion:
1263
if (block[0].startswith(path) and
1264
(len(block[0]) == len(path) or
1265
block[0][len(path)] == '/')):
1266
# this entire block should be deleted - its the block for a
1267
# path to unversion; or the child of one
1270
# TODO: trim paths_to_unversion as we pass by paths
1272
# this block is to be deleted: process it.
1273
# TODO: we can special case the no-parents case and
1274
# just forget the whole block.
1276
while entry_index < len(block[1]):
1277
entry = block[1][entry_index]
1278
if entry[1][0][0] in (b'a', b'r'):
1279
# don't remove absent or renamed entries
1282
# Mark this file id as having been removed
1283
ids_to_unversion.discard(entry[0][2])
1284
if not state._make_absent(entry):
1285
# The block has not shrunk.
1287
# go to the next block. (At the moment we dont delete empty
1212
1291
entry_index = 0
1213
1292
while entry_index < len(block[1]):
1214
1293
entry = block[1][entry_index]
1215
if entry[1][0][0] in 'ar':
1216
# don't remove absent or renamed entries
1219
# Mark this file id as having been removed
1220
ids_to_unversion.discard(entry[0][2])
1221
if not state._make_absent(entry):
1222
# The block has not shrunk.
1224
# go to the next block. (At the moment we dont delete empty
1294
if (entry[1][0][0] in (b'a', b'r') or # absent, relocated
1295
# ^ some parent row.
1296
entry[0][2] not in ids_to_unversion):
1297
# ^ not an id to unversion
1300
if entry[1][0][0] == b'd':
1301
paths_to_unversion.add(
1302
pathjoin(entry[0][0], entry[0][1]))
1303
if not state._make_absent(entry):
1305
# we have unversioned this id
1306
ids_to_unversion.remove(entry[0][2])
1226
1307
block_index += 1
1229
while entry_index < len(block[1]):
1230
entry = block[1][entry_index]
1231
if (entry[1][0][0] in ('a', 'r') or # absent, relocated
1232
# ^ some parent row.
1233
entry[0][2] not in ids_to_unversion):
1234
# ^ not an id to unversion
1237
if entry[1][0][0] == 'd':
1238
paths_to_unversion.add(pathjoin(entry[0][0], entry[0][1]))
1239
if not state._make_absent(entry):
1241
# we have unversioned this id
1242
ids_to_unversion.remove(entry[0][2])
1244
if ids_to_unversion:
1245
raise errors.NoSuchId(self, iter(ids_to_unversion).next())
1246
self._make_dirty(reset_inventory=False)
1247
# have to change the legacy inventory too.
1248
if self._inventory is not None:
1249
for file_id in file_ids:
1250
self._inventory.remove_recursive_id(file_id)
1308
if ids_to_unversion:
1309
raise errors.NoSuchId(self, next(iter(ids_to_unversion)))
1310
self._make_dirty(reset_inventory=False)
1311
# have to change the legacy inventory too.
1312
if self._inventory is not None:
1313
for file_id in file_ids:
1314
if self._inventory.has_id(file_id):
1315
self._inventory.remove_recursive_id(file_id)
1252
@needs_tree_write_lock
1253
1317
def rename_one(self, from_rel, to_rel, after=False):
1254
1318
"""See WorkingTree.rename_one"""
1256
WorkingTree.rename_one(self, from_rel, to_rel, after)
1319
with self.lock_tree_write():
1321
super(DirStateWorkingTree, self).rename_one(
1322
from_rel, to_rel, after)
1258
@needs_tree_write_lock
1259
1324
def apply_inventory_delta(self, changes):
1260
1325
"""See MutableTree.apply_inventory_delta"""
1261
state = self.current_dirstate()
1262
state.update_by_delta(changes)
1263
self._make_dirty(reset_inventory=True)
1326
with self.lock_tree_write():
1327
state = self.current_dirstate()
1328
state.update_by_delta(changes)
1329
self._make_dirty(reset_inventory=True)
1265
1331
def update_basis_by_delta(self, new_revid, delta):
1266
1332
"""See MutableTree.update_basis_by_delta."""
1735
1891
inv_entry.text_size = size
1736
1892
inv_entry.text_sha1 = fingerprint
1737
1893
elif kind == 'directory':
1738
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1894
parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry
1739
1895
elif kind == 'symlink':
1740
inv_entry.executable = False
1741
inv_entry.text_size = None
1742
1896
inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1743
1897
elif kind == 'tree-reference':
1744
1898
inv_entry.reference_revision = fingerprint or None
1746
raise AssertionError("cannot convert entry %r into an InventoryEntry"
1900
raise AssertionError(
1901
"cannot convert entry %r into an InventoryEntry"
1748
1903
# These checks cost us around 40ms on a 55k entry tree
1749
1904
if file_id in inv_byid:
1750
raise AssertionError('file_id %s already in'
1905
raise AssertionError(
1906
'file_id %s already in'
1751
1907
' inventory as %s' % (file_id, inv_byid[file_id]))
1752
1908
if name_unicode in parent_ie.children:
1753
1909
raise AssertionError('name %r already in parent'
1755
1911
inv_byid[file_id] = inv_entry
1756
1912
parent_ie.children[name_unicode] = inv_entry
1757
1913
self._inventory = inv
1759
def get_file_mtime(self, file_id, path=None):
1915
def get_file_mtime(self, path):
1760
1916
"""Return the modification time for this record.
1762
1918
We return the timestamp of the last-changed revision.
1764
1920
# Make sure the file exists
1765
entry = self._get_entry(file_id, path=path)
1921
entry = self._get_entry(path=path)
1766
1922
if entry == (None, None): # do we raise?
1923
raise errors.NoSuchFile(path)
1768
1924
parent_index = self._get_parent_index()
1769
1925
last_changed_revision = entry[1][parent_index][4]
1771
1927
rev = self._repository.get_revision(last_changed_revision)
1772
1928
except errors.NoSuchRevision:
1773
raise errors.FileTimestampUnavailable(self.id2path(file_id))
1929
raise FileTimestampUnavailable(path)
1774
1930
return rev.timestamp
1776
def get_file_sha1(self, file_id, path=None, stat_value=None):
1777
entry = self._get_entry(file_id=file_id, path=path)
1932
def get_file_sha1(self, path, stat_value=None):
1933
entry = self._get_entry(path=path)
1778
1934
parent_index = self._get_parent_index()
1779
1935
parent_details = entry[1][parent_index]
1780
if parent_details[0] == 'f':
1936
if parent_details[0] == b'f':
1781
1937
return parent_details[1]
1784
def get_file(self, file_id, path=None):
1785
return StringIO(self.get_file_text(file_id))
1787
def get_file_size(self, file_id):
1940
def get_file_revision(self, path):
1941
with self.lock_read():
1942
inv, inv_file_id = self._path2inv_file_id(path)
1943
return inv.get_entry(inv_file_id).revision
1945
def get_file(self, path):
1946
return BytesIO(self.get_file_text(path))
1948
def get_file_size(self, path):
1788
1949
"""See Tree.get_file_size"""
1789
return self.inventory[file_id].text_size
1791
def get_file_text(self, file_id, path=None):
1792
_, content = list(self.iter_files_bytes([(file_id, None)]))[0]
1793
return ''.join(content)
1795
def get_reference_revision(self, file_id, path=None):
1796
return self.inventory[file_id].reference_revision
1950
inv, inv_file_id = self._path2inv_file_id(path)
1951
return inv.get_entry(inv_file_id).text_size
1953
def get_file_text(self, path):
1955
for _, content_iter in self.iter_files_bytes([(path, None)]):
1956
if content is not None:
1957
raise AssertionError('iter_files_bytes returned'
1958
' too many entries')
1959
# For each entry returned by iter_files_bytes, we must consume the
1960
# content_iter before we step the files iterator.
1961
content = b''.join(content_iter)
1963
raise AssertionError('iter_files_bytes did not return'
1964
' the requested data')
1967
def get_reference_revision(self, path):
1968
inv, inv_file_id = self._path2inv_file_id(path)
1969
return inv.get_entry(inv_file_id).reference_revision
1798
1971
def iter_files_bytes(self, desired_files):
1799
1972
"""See Tree.iter_files_bytes.