424
369
state = self.current_dirstate()
425
370
if stat_value is None:
427
stat_value = osutils.lstat(file_abspath)
372
stat_value = os.lstat(file_abspath)
429
374
if e.errno == errno.ENOENT:
433
378
link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
434
stat_value=stat_value)
435
if entry[1][0][0] == b'f':
379
stat_value=stat_value)
380
if entry[1][0][0] == 'f':
436
381
if link_or_sha1 is None:
437
file_obj, statvalue = self.get_file_with_stat(path)
382
file_obj, statvalue = self.get_file_with_stat(file_id, path)
439
384
sha1 = osutils.sha_file(file_obj)
442
self._observed_sha1(path, (sha1, statvalue))
387
self._observed_sha1(file_id, path, (sha1, statvalue))
445
390
return link_or_sha1
448
def _get_root_inventory(self):
393
def _get_inventory(self):
449
394
"""Get the inventory for the tree. This is only valid within a lock."""
450
395
if 'evil' in debug.debug_flags:
451
trace.mutter_callsite(
452
2, "accessing .inventory forces a size of tree translation.")
396
trace.mutter_callsite(2,
397
"accessing .inventory forces a size of tree translation.")
453
398
if self._inventory is not None:
454
399
return self._inventory
455
400
self._must_be_locked()
456
401
self._generate_inventory()
457
402
return self._inventory
459
root_inventory = property(_get_root_inventory,
460
"Root inventory of this tree")
404
inventory = property(_get_inventory,
405
doc="Inventory of this Tree")
462
408
def get_parent_ids(self):
463
409
"""See Tree.get_parent_ids.
465
411
This implementation requests the ids list from the dirstate file.
467
with self.lock_read():
468
return self.current_dirstate().get_parent_ids()
413
return self.current_dirstate().get_parent_ids()
470
def get_reference_revision(self, path):
415
def get_reference_revision(self, file_id, path=None):
471
416
# referenced tree's revision is whatever's currently there
472
return self.get_nested_tree(path).last_revision()
417
return self.get_nested_tree(file_id, path).last_revision()
474
def get_nested_tree(self, path):
419
def get_nested_tree(self, file_id, path=None):
421
path = self.id2path(file_id)
422
# else: check file_id is at path?
475
423
return WorkingTree.open(self.abspath(path))
477
def id2path(self, file_id, recurse='down'):
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):
478
445
"Convert a file-id to a path."
479
with self.lock_read():
480
state = self.current_dirstate()
481
entry = self._get_entry(file_id=file_id)
482
if entry == (None, None):
483
if recurse == 'down':
484
if 'evil' in debug.debug_flags:
485
trace.mutter_callsite(
486
2, "Tree.id2path scans all nested trees.")
487
for nested_path in self.iter_references():
488
nested_tree = self.get_nested_tree(nested_path)
490
return osutils.pathjoin(
491
nested_path, nested_tree.id2path(file_id))
492
except errors.NoSuchId:
494
raise errors.NoSuchId(tree=self, file_id=file_id)
495
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
496
return path_utf8.decode('utf8')
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')
498
453
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
499
454
entry = self._get_entry(path=path)
500
455
if entry == (None, None):
501
return False # Missing entries are not executable
502
return entry[1][0][3] # Executable?
504
def is_executable(self, path):
505
"""Test if a file is executable or not.
507
Note: The caller is expected to take a read-lock before calling this.
509
if not self._supports_executable():
510
entry = self._get_entry(path=path)
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)
511
466
if entry == (None, None):
513
468
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.
515
478
self._must_be_locked()
516
mode = osutils.lstat(self.abspath(path)).st_mode
480
path = self.id2path(file_id)
481
mode = os.lstat(self.abspath(path)).st_mode
517
482
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
519
484
def all_file_ids(self):
521
486
self._must_be_locked()
523
488
for key, tree_details in self.current_dirstate()._iter_entries():
524
if tree_details[0][0] in (b'a', b'r'): # relocated
489
if tree_details[0][0] in ('a', 'r'): # relocated
526
491
result.add(key[2])
529
def all_versioned_paths(self):
530
self._must_be_locked()
531
return {path for path, entry in
532
self.root_inventory.iter_entries(recursive=True)}
534
495
def __iter__(self):
535
496
"""Iterate through file_ids for this tree.
537
498
file_ids are in a WorkingTree if they are in the working inventory
538
499
and the working file exists.
540
with self.lock_read():
542
for key, tree_details in self.current_dirstate()._iter_entries():
543
if tree_details[0][0] in (b'a', b'r'): # absent, relocated
544
# not relevant to the working tree
546
path = pathjoin(self.basedir, key[0].decode(
547
'utf8'), key[1].decode('utf8'))
548
if osutils.lexists(path):
549
result.append(key[2])
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])
552
511
def iter_references(self):
553
512
if not self._repo_supports_tree_reference:
554
513
# When the repo doesn't support references, we will have nothing to
557
with self.lock_read():
558
for key, tree_details in self.current_dirstate()._iter_entries():
559
if tree_details[0][0] in (b'a', b'r'): # absent, relocated
560
# not relevant to the working tree
563
# the root is not a reference.
565
relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
567
if self.kind(relpath) == 'tree-reference':
569
except errors.NoSuchFile:
570
# path is missing on disk.
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.
573
def _observed_sha1(self, path, sha_and_stat):
531
def _observed_sha1(self, file_id, path, (sha1, statvalue)):
574
532
"""See MutableTree._observed_sha1."""
575
533
state = self.current_dirstate()
576
entry = self._get_entry(path=path)
577
state._observed_sha1(entry, *sha_and_stat)
579
def kind(self, relpath):
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):
580
552
abspath = self.abspath(relpath)
581
553
kind = file_kind(abspath)
582
554
if (self._repo_supports_tree_reference and kind == 'directory'):
583
with self.lock_read():
584
entry = self._get_entry(path=relpath)
585
if entry[1] is not None:
586
if entry[1][0][0] == b't':
587
kind = 'tree-reference'
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'
590
562
def _last_revision(self):
591
563
"""See Mutable.last_revision."""
592
with self.lock_read():
593
parent_ids = self.current_dirstate().get_parent_ids()
597
return _mod_revision.NULL_REVISION
564
parent_ids = self.current_dirstate().get_parent_ids()
568
return _mod_revision.NULL_REVISION
599
570
def lock_read(self):
600
571
"""See Branch.lock_read, and WorkingTree.unlock.
602
:return: A breezy.lock.LogicalLockResult.
573
:return: A bzrlib.lock.LogicalLockResult.
604
575
self.branch.lock_read()
653
624
def lock_write(self):
654
625
"""See MutableTree.lock_write, and WorkingTree.unlock.
656
:return: A breezy.lock.LogicalLockResult.
627
:return: A bzrlib.lock.LogicalLockResult.
658
629
self.branch.lock_write()
659
630
return self._lock_self_write()
632
@needs_tree_write_lock
661
633
def move(self, from_paths, to_dir, after=False):
662
634
"""See WorkingTree.move()."""
664
636
if not from_paths:
666
with self.lock_tree_write():
667
state = self.current_dirstate()
668
if isinstance(from_paths, (str, bytes)):
670
to_dir_utf8 = to_dir.encode('utf8')
671
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
672
# check destination directory
673
# get the details for it
674
(to_entry_block_index, to_entry_entry_index, dir_present,
675
entry_present) = state._get_block_entry_index(
676
to_entry_dirname, to_basename, 0)
677
if not entry_present:
678
raise errors.BzrMoveFailedError(
679
'', to_dir, errors.NotVersionedError(to_dir))
680
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
681
# get a handle on the block itself.
682
to_block_index = state._ensure_block(
683
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
684
to_block = state._dirblocks[to_block_index]
685
to_abs = self.abspath(to_dir)
686
if not isdir(to_abs):
687
raise errors.BzrMoveFailedError('', to_dir,
688
errors.NotADirectory(to_abs))
690
if to_entry[1][0][0] != b'd':
691
raise errors.BzrMoveFailedError('', to_dir,
692
errors.NotADirectory(to_abs))
694
if self._inventory is not None:
695
update_inventory = True
696
inv = self.root_inventory
697
to_dir_id = to_entry[0][2]
699
update_inventory = False
701
# GZ 2017-03-28: The rollbacks variable was shadowed in the loop below
702
# missing those added here, but there's also no test coverage for this.
703
rollbacks = cleanup.ExitStack()
705
def move_one(old_entry, from_path_utf8, minikind, executable,
706
fingerprint, packed_stat, size,
707
to_block, to_key, to_path_utf8):
708
state._make_absent(old_entry)
709
from_key = old_entry[0]
711
state.update_minimal,
714
executable=executable,
715
fingerprint=fingerprint,
716
packed_stat=packed_stat,
718
path_utf8=from_path_utf8)
719
state.update_minimal(to_key,
721
executable=executable,
722
fingerprint=fingerprint,
723
packed_stat=packed_stat,
725
path_utf8=to_path_utf8)
726
added_entry_index, _ = state._find_entry_index(
728
new_entry = to_block[1][added_entry_index]
729
rollbacks.callback(state._make_absent, new_entry)
731
for from_rel in from_paths:
732
# from_rel is 'pathinroot/foo/bar'
733
from_rel_utf8 = from_rel.encode('utf8')
734
from_dirname, from_tail = osutils.split(from_rel)
735
from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
736
from_entry = self._get_entry(path=from_rel)
737
if from_entry == (None, None):
738
raise errors.BzrMoveFailedError(
740
errors.NotVersionedError(path=from_rel))
742
from_id = from_entry[0][2]
743
to_rel = pathjoin(to_dir, from_tail)
744
to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
745
item_to_entry = self._get_entry(path=to_rel)
746
if item_to_entry != (None, None):
747
raise errors.BzrMoveFailedError(
748
from_rel, to_rel, "Target is already versioned.")
750
if from_rel == to_rel:
751
raise errors.BzrMoveFailedError(
752
from_rel, to_rel, "Source and target are identical.")
754
from_missing = not self.has_filename(from_rel)
755
to_missing = not self.has_filename(to_rel)
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
757
737
move_file = False
762
raise errors.BzrMoveFailedError(
766
extra="New file has not been created yet"))
768
# neither path exists
769
raise errors.BzrRenameFailedError(
771
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
773
if from_missing: # implicitly just update our path mapping
776
raise errors.RenameFailedFilesExist(from_rel, to_rel)
739
raise errors.RenameFailedFilesExist(from_rel, to_rel)
778
# perform the disk move first - its the most likely failure point.
780
from_rel_abs = self.abspath(from_rel)
781
to_rel_abs = self.abspath(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):
783
osutils.rename(from_rel_abs, to_rel_abs)
785
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
787
osutils.rename, to_rel_abs, from_rel_abs)
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)
789
# perform the rename in the inventory next if needed: its easy
793
from_entry = inv.get_entry(from_id)
794
current_parent = from_entry.parent_id
795
inv.rename(from_id, to_dir_id, from_tail)
797
inv.rename, from_id, current_parent, from_tail)
798
# finally do the rename in the dirstate, which is a little
799
# tricky to rollback, but least likely to need it.
800
old_block_index, old_entry_index, dir_present, file_present = \
801
state._get_block_entry_index(
802
from_dirname, from_tail_utf8, 0)
803
old_block = state._dirblocks[old_block_index][1]
804
old_entry = old_block[old_entry_index]
805
from_key, old_entry_details = old_entry
806
cur_details = old_entry_details[0]
808
to_key = ((to_block[0],) + from_key[1:3])
809
minikind = cur_details[0]
810
move_one(old_entry, from_path_utf8=from_rel_utf8,
812
executable=cur_details[3],
813
fingerprint=cur_details[1],
814
packed_stat=cur_details[4],
818
to_path_utf8=to_rel_utf8)
821
def update_dirblock(from_dir, to_key, to_dir_utf8):
822
"""Recursively update all entries in this dirblock."""
824
raise AssertionError(
825
"renaming root not supported")
826
from_key = (from_dir, '')
827
from_block_idx, present = \
828
state._find_block_index_from_key(from_key)
830
# This is the old record, if it isn't present,
831
# then there is theoretically nothing to
832
# update. (Unless it isn't present because of
833
# lazy loading, but we don't do that yet)
835
from_block = state._dirblocks[from_block_idx]
836
to_block_index, to_entry_index, _, _ = \
837
state._get_block_entry_index(
838
to_key[0], to_key[1], 0)
839
to_block_index = state._ensure_block(
840
to_block_index, to_entry_index, to_dir_utf8)
841
to_block = state._dirblocks[to_block_index]
843
# Grab a copy since move_one may update the list.
844
for entry in from_block[1][:]:
845
if not (entry[0][0] == from_dir):
846
raise AssertionError()
847
cur_details = entry[1][0]
849
to_dir_utf8, entry[0][1], entry[0][2])
850
from_path_utf8 = osutils.pathjoin(
851
entry[0][0], entry[0][1])
852
to_path_utf8 = osutils.pathjoin(
853
to_dir_utf8, entry[0][1])
854
minikind = cur_details[0]
855
if minikind in (b'a', b'r'):
856
# Deleted children of a renamed directory
857
# Do not need to be updated. Children that
858
# have been renamed out of this directory
859
# should also not be updated
861
move_one(entry, from_path_utf8=from_path_utf8,
863
executable=cur_details[3],
864
fingerprint=cur_details[1],
865
packed_stat=cur_details[4],
869
to_path_utf8=to_path_utf8)
871
# We need to move all the children of this
873
update_dirblock(from_path_utf8, to_key,
875
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
876
except BaseException:
879
result.append((from_rel, to_rel))
880
state._mark_modified()
881
self._make_dirty(reset_inventory=False)
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)
885
858
def _must_be_locked(self):
886
859
if not self._control_files._lock_count:
1122
1083
If tree is None, then that element is treated as an unreachable
1123
1084
parent tree - i.e. a ghost.
1125
with self.lock_tree_write():
1126
dirstate = self.current_dirstate()
1127
if len(parents_list) > 0:
1128
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1129
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1133
parent_ids = [rev_id for rev_id, tree in parents_list]
1134
graph = self.branch.repository.get_graph()
1135
heads = graph.heads(parent_ids)
1136
accepted_revisions = set()
1138
# convert absent trees to the null tree, which we convert back to
1139
# missing on access.
1140
for rev_id, tree in parents_list:
1141
if len(accepted_revisions) > 0:
1142
# we always accept the first tree
1143
if rev_id in accepted_revisions or rev_id not in heads:
1144
# We have already included either this tree, or its
1145
# descendent, so we skip it.
1147
_mod_revision.check_not_reserved_id(rev_id)
1148
if tree is not None:
1149
real_trees.append((rev_id, tree))
1151
real_trees.append((rev_id,
1152
self.branch.repository.revision_tree(
1153
_mod_revision.NULL_REVISION)))
1154
ghosts.append(rev_id)
1155
accepted_revisions.add(rev_id)
1157
if (len(real_trees) == 1
1159
and self.branch.repository._format.fast_deltas
1160
and isinstance(real_trees[0][1], InventoryRevisionTree)
1161
and self.get_parent_ids()):
1162
rev_id, rev_tree = real_trees[0]
1163
basis_id = self.get_parent_ids()[0]
1164
# There are times when basis_tree won't be in
1165
# self.branch.repository, (switch, for example)
1167
basis_tree = self.branch.repository.revision_tree(basis_id)
1168
except errors.NoSuchRevision:
1169
# Fall back to the set_parent_trees(), since we can't use
1170
# _make_delta if we can't get the RevisionTree
1173
delta = rev_tree.root_inventory._make_delta(
1174
basis_tree.root_inventory)
1175
dirstate.update_basis_by_delta(delta, rev_id)
1178
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1179
self._make_dirty(reset_inventory=False)
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)
1181
1119
def _set_root_id(self, file_id):
1182
1120
"""See WorkingTree.set_root_id."""
1183
1121
state = self.current_dirstate()
1184
state.set_path_id(b'', file_id)
1122
state.set_path_id('', file_id)
1185
1123
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1186
1124
self._make_dirty(reset_inventory=True)
1226
1165
self.branch.unlock()
1228
def unversion(self, paths):
1229
"""Remove the file ids in paths from the current versioned set.
1167
@needs_tree_write_lock
1168
def unversion(self, file_ids):
1169
"""Remove the file ids in file_ids from the current versioned set.
1231
When a directory is unversioned, all of its children are automatically
1171
When a file_id is unversioned, all of its children are automatically
1234
:param paths: The file ids to stop versioning.
1174
:param file_ids: The file ids to stop versioning.
1235
1175
:raises: NoSuchId if any fileid is not currently versioned.
1237
with self.lock_tree_write():
1240
state = self.current_dirstate()
1241
state._read_dirblocks_if_needed()
1244
file_id = self.path2id(path)
1246
raise errors.NoSuchFile(self, path)
1247
file_ids.add(file_id)
1248
ids_to_unversion = set(file_ids)
1249
paths_to_unversion = set()
1251
# check if the root is to be unversioned, if so, assert for now.
1252
# walk the state marking unversioned things as absent.
1253
# if there are any un-unversioned ids at the end, raise
1254
for key, details in state._dirblocks[0][1]:
1255
if (details[0][0] not in (b'a', b'r') and # absent or relocated
1256
key[2] in ids_to_unversion):
1257
# I haven't written the code to unversion / yet - it should
1259
raise errors.BzrError(
1260
'Unversioning the / is not currently supported')
1262
while block_index < len(state._dirblocks):
1263
# process one directory at a time.
1264
block = state._dirblocks[block_index]
1265
# first check: is the path one to remove - it or its children
1266
delete_block = False
1267
for path in paths_to_unversion:
1268
if (block[0].startswith(path) and
1269
(len(block[0]) == len(path) or
1270
block[0][len(path)] == '/')):
1271
# this entire block should be deleted - its the block for a
1272
# path to unversion; or the child of one
1275
# TODO: trim paths_to_unversion as we pass by paths
1277
# this block is to be deleted: process it.
1278
# TODO: we can special case the no-parents case and
1279
# just forget the whole block.
1281
while entry_index < len(block[1]):
1282
entry = block[1][entry_index]
1283
if entry[1][0][0] in (b'a', b'r'):
1284
# don't remove absent or renamed entries
1287
# Mark this file id as having been removed
1288
ids_to_unversion.discard(entry[0][2])
1289
if not state._make_absent(entry):
1290
# The block has not shrunk.
1292
# go to the next block. (At the moment we dont delete empty
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.
1296
1212
entry_index = 0
1297
1213
while entry_index < len(block[1]):
1298
1214
entry = block[1][entry_index]
1299
if (entry[1][0][0] in (b'a', b'r') or # absent, relocated
1300
# ^ some parent row.
1301
entry[0][2] not in ids_to_unversion):
1302
# ^ not an id to unversion
1305
if entry[1][0][0] == b'd':
1306
paths_to_unversion.add(
1307
pathjoin(entry[0][0], entry[0][1]))
1308
if not state._make_absent(entry):
1310
# we have unversioned this id
1311
ids_to_unversion.remove(entry[0][2])
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
1312
1226
block_index += 1
1313
if ids_to_unversion:
1314
raise errors.NoSuchId(self, next(iter(ids_to_unversion)))
1315
self._make_dirty(reset_inventory=False)
1316
# have to change the legacy inventory too.
1317
if self._inventory is not None:
1318
for file_id in file_ids:
1319
if self._inventory.has_id(file_id):
1320
self._inventory.remove_recursive_id(file_id)
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)
1252
@needs_tree_write_lock
1322
1253
def rename_one(self, from_rel, to_rel, after=False):
1323
1254
"""See WorkingTree.rename_one"""
1324
with self.lock_tree_write():
1326
super(DirStateWorkingTree, self).rename_one(
1327
from_rel, to_rel, after)
1256
WorkingTree.rename_one(self, from_rel, to_rel, after)
1258
@needs_tree_write_lock
1329
1259
def apply_inventory_delta(self, changes):
1330
1260
"""See MutableTree.apply_inventory_delta"""
1331
with self.lock_tree_write():
1332
state = self.current_dirstate()
1333
state.update_by_delta(changes)
1334
self._make_dirty(reset_inventory=True)
1261
state = self.current_dirstate()
1262
state.update_by_delta(changes)
1263
self._make_dirty(reset_inventory=True)
1336
1265
def update_basis_by_delta(self, new_revid, delta):
1337
1266
"""See MutableTree.update_basis_by_delta."""
1896
1735
inv_entry.text_size = size
1897
1736
inv_entry.text_sha1 = fingerprint
1898
1737
elif kind == 'directory':
1899
parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry
1738
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1900
1739
elif kind == 'symlink':
1740
inv_entry.executable = False
1741
inv_entry.text_size = None
1901
1742
inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1902
1743
elif kind == 'tree-reference':
1903
1744
inv_entry.reference_revision = fingerprint or None
1905
raise AssertionError(
1906
"cannot convert entry %r into an InventoryEntry"
1746
raise AssertionError("cannot convert entry %r into an InventoryEntry"
1908
1748
# These checks cost us around 40ms on a 55k entry tree
1909
1749
if file_id in inv_byid:
1910
raise AssertionError(
1911
'file_id %s already in'
1750
raise AssertionError('file_id %s already in'
1912
1751
' inventory as %s' % (file_id, inv_byid[file_id]))
1913
1752
if name_unicode in parent_ie.children:
1914
1753
raise AssertionError('name %r already in parent'
1916
1755
inv_byid[file_id] = inv_entry
1917
1756
parent_ie.children[name_unicode] = inv_entry
1918
1757
self._inventory = inv
1920
def get_file_mtime(self, path):
1759
def get_file_mtime(self, file_id, path=None):
1921
1760
"""Return the modification time for this record.
1923
1762
We return the timestamp of the last-changed revision.
1925
1764
# Make sure the file exists
1926
entry = self._get_entry(path=path)
1765
entry = self._get_entry(file_id, path=path)
1927
1766
if entry == (None, None): # do we raise?
1928
raise errors.NoSuchFile(path)
1929
1768
parent_index = self._get_parent_index()
1930
1769
last_changed_revision = entry[1][parent_index][4]
1932
1771
rev = self._repository.get_revision(last_changed_revision)
1933
1772
except errors.NoSuchRevision:
1934
raise FileTimestampUnavailable(path)
1773
raise errors.FileTimestampUnavailable(self.id2path(file_id))
1935
1774
return rev.timestamp
1937
def get_file_sha1(self, path, stat_value=None):
1938
entry = self._get_entry(path=path)
1776
def get_file_sha1(self, file_id, path=None, stat_value=None):
1777
entry = self._get_entry(file_id=file_id, path=path)
1939
1778
parent_index = self._get_parent_index()
1940
1779
parent_details = entry[1][parent_index]
1941
if parent_details[0] == b'f':
1780
if parent_details[0] == 'f':
1942
1781
return parent_details[1]
1945
def get_file_revision(self, path):
1946
with self.lock_read():
1947
inv, inv_file_id = self._path2inv_file_id(path)
1948
return inv.get_entry(inv_file_id).revision
1950
def get_file(self, path):
1951
return BytesIO(self.get_file_text(path))
1953
def get_file_size(self, path):
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):
1954
1788
"""See Tree.get_file_size"""
1955
inv, inv_file_id = self._path2inv_file_id(path)
1956
return inv.get_entry(inv_file_id).text_size
1958
def get_file_text(self, path):
1960
for _, content_iter in self.iter_files_bytes([(path, None)]):
1961
if content is not None:
1962
raise AssertionError('iter_files_bytes returned'
1963
' too many entries')
1964
# For each entry returned by iter_files_bytes, we must consume the
1965
# content_iter before we step the files iterator.
1966
content = b''.join(content_iter)
1968
raise AssertionError('iter_files_bytes did not return'
1969
' the requested data')
1972
def get_reference_revision(self, path):
1973
inv, inv_file_id = self._path2inv_file_id(path)
1974
return inv.get_entry(inv_file_id).reference_revision
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
1976
1798
def iter_files_bytes(self, desired_files):
1977
1799
"""See Tree.iter_files_bytes.