624
649
def lock_write(self):
625
650
"""See MutableTree.lock_write, and WorkingTree.unlock.
627
:return: A bzrlib.lock.LogicalLockResult.
652
:return: A breezy.lock.LogicalLockResult.
629
654
self.branch.lock_write()
630
655
return self._lock_self_write()
632
@needs_tree_write_lock
633
657
def move(self, from_paths, to_dir, after=False):
634
658
"""See WorkingTree.move()."""
636
660
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
662
with self.lock_tree_write():
663
state = self.current_dirstate()
664
if isinstance(from_paths, (str, bytes)):
666
to_dir_utf8 = to_dir.encode('utf8')
667
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
668
# check destination directory
669
# get the details for it
670
(to_entry_block_index, to_entry_entry_index, dir_present,
671
entry_present) = state._get_block_entry_index(
672
to_entry_dirname, to_basename, 0)
673
if not entry_present:
674
raise errors.BzrMoveFailedError(
675
'', to_dir, errors.NotVersionedError(to_dir))
676
to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
677
# get a handle on the block itself.
678
to_block_index = state._ensure_block(
679
to_entry_block_index, to_entry_entry_index, to_dir_utf8)
680
to_block = state._dirblocks[to_block_index]
681
to_abs = self.abspath(to_dir)
682
if not isdir(to_abs):
683
raise errors.BzrMoveFailedError('', to_dir,
684
errors.NotADirectory(to_abs))
686
if to_entry[1][0][0] != b'd':
687
raise errors.BzrMoveFailedError('', to_dir,
688
errors.NotADirectory(to_abs))
690
if self._inventory is not None:
691
update_inventory = True
692
inv = self.root_inventory
693
to_dir_id = to_entry[0][2]
695
update_inventory = False
697
# GZ 2017-03-28: The rollbacks variable was shadowed in the loop below
698
# missing those added here, but there's also no test coverage for this.
699
rollbacks = cleanup.ObjectWithCleanups()
701
def move_one(old_entry, from_path_utf8, minikind, executable,
702
fingerprint, packed_stat, size,
703
to_block, to_key, to_path_utf8):
704
state._make_absent(old_entry)
705
from_key = old_entry[0]
706
rollbacks.add_cleanup(
707
state.update_minimal,
710
executable=executable,
711
fingerprint=fingerprint,
712
packed_stat=packed_stat,
714
path_utf8=from_path_utf8)
715
state.update_minimal(to_key,
717
executable=executable,
718
fingerprint=fingerprint,
719
packed_stat=packed_stat,
721
path_utf8=to_path_utf8)
722
added_entry_index, _ = state._find_entry_index(
724
new_entry = to_block[1][added_entry_index]
725
rollbacks.add_cleanup(state._make_absent, new_entry)
727
for from_rel in from_paths:
728
# from_rel is 'pathinroot/foo/bar'
729
from_rel_utf8 = from_rel.encode('utf8')
730
from_dirname, from_tail = osutils.split(from_rel)
731
from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
732
from_entry = self._get_entry(path=from_rel)
733
if from_entry == (None, None):
734
raise errors.BzrMoveFailedError(
736
errors.NotVersionedError(path=from_rel))
738
from_id = from_entry[0][2]
739
to_rel = pathjoin(to_dir, from_tail)
740
to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
741
item_to_entry = self._get_entry(path=to_rel)
742
if item_to_entry != (None, None):
743
raise errors.BzrMoveFailedError(
744
from_rel, to_rel, "Target is already versioned.")
746
if from_rel == to_rel:
747
raise errors.BzrMoveFailedError(
748
from_rel, to_rel, "Source and target are identical.")
750
from_missing = not self.has_filename(from_rel)
751
to_missing = not self.has_filename(to_rel)
737
753
move_file = False
739
raise errors.RenameFailedFilesExist(from_rel, to_rel)
758
raise errors.BzrMoveFailedError(
762
extra="New file has not been created yet"))
764
# neither path exists
765
raise errors.BzrRenameFailedError(
767
errors.PathsDoNotExist(paths=(from_rel, to_rel)))
769
if from_missing: # implicitly just update our path mapping
772
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):
774
# perform the disk move first - its the most likely failure point.
776
from_rel_abs = self.abspath(from_rel)
777
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)
779
osutils.rename(from_rel_abs, to_rel_abs)
781
raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
782
rollbacks.add_cleanup(
783
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)
785
# perform the rename in the inventory next if needed: its easy
789
from_entry = inv.get_entry(from_id)
790
current_parent = from_entry.parent_id
791
inv.rename(from_id, to_dir_id, from_tail)
792
rollbacks.add_cleanup(
793
inv.rename, from_id, current_parent, from_tail)
794
# finally do the rename in the dirstate, which is a little
795
# tricky to rollback, but least likely to need it.
796
old_block_index, old_entry_index, dir_present, file_present = \
797
state._get_block_entry_index(
798
from_dirname, from_tail_utf8, 0)
799
old_block = state._dirblocks[old_block_index][1]
800
old_entry = old_block[old_entry_index]
801
from_key, old_entry_details = old_entry
802
cur_details = old_entry_details[0]
804
to_key = ((to_block[0],) + from_key[1:3])
805
minikind = cur_details[0]
806
move_one(old_entry, from_path_utf8=from_rel_utf8,
808
executable=cur_details[3],
809
fingerprint=cur_details[1],
810
packed_stat=cur_details[4],
814
to_path_utf8=to_rel_utf8)
817
def update_dirblock(from_dir, to_key, to_dir_utf8):
818
"""Recursively update all entries in this dirblock."""
820
raise AssertionError(
821
"renaming root not supported")
822
from_key = (from_dir, '')
823
from_block_idx, present = \
824
state._find_block_index_from_key(from_key)
826
# This is the old record, if it isn't present,
827
# then there is theoretically nothing to
828
# update. (Unless it isn't present because of
829
# lazy loading, but we don't do that yet)
831
from_block = state._dirblocks[from_block_idx]
832
to_block_index, to_entry_index, _, _ = \
833
state._get_block_entry_index(
834
to_key[0], to_key[1], 0)
835
to_block_index = state._ensure_block(
836
to_block_index, to_entry_index, to_dir_utf8)
837
to_block = state._dirblocks[to_block_index]
839
# Grab a copy since move_one may update the list.
840
for entry in from_block[1][:]:
841
if not (entry[0][0] == from_dir):
842
raise AssertionError()
843
cur_details = entry[1][0]
845
to_dir_utf8, entry[0][1], entry[0][2])
846
from_path_utf8 = osutils.pathjoin(
847
entry[0][0], entry[0][1])
848
to_path_utf8 = osutils.pathjoin(
849
to_dir_utf8, entry[0][1])
850
minikind = cur_details[0]
851
if minikind in (b'a', b'r'):
852
# Deleted children of a renamed directory
853
# Do not need to be updated. Children that
854
# have been renamed out of this directory
855
# should also not be updated
857
move_one(entry, from_path_utf8=from_path_utf8,
859
executable=cur_details[3],
860
fingerprint=cur_details[1],
861
packed_stat=cur_details[4],
865
to_path_utf8=to_path_utf8)
867
# We need to move all the children of this
869
update_dirblock(from_path_utf8, to_key,
871
update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
872
except BaseException:
873
rollbacks.cleanup_now()
875
result.append((from_rel, to_rel))
876
state._mark_modified()
877
self._make_dirty(reset_inventory=False)
858
881
def _must_be_locked(self):
859
882
if not self._control_files._lock_count:
1083
1114
If tree is None, then that element is treated as an unreachable
1084
1115
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)
1117
with self.lock_tree_write():
1118
dirstate = self.current_dirstate()
1119
if len(parents_list) > 0:
1120
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1121
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1125
parent_ids = [rev_id for rev_id, tree in parents_list]
1126
graph = self.branch.repository.get_graph()
1127
heads = graph.heads(parent_ids)
1128
accepted_revisions = set()
1130
# convert absent trees to the null tree, which we convert back to
1131
# missing on access.
1132
for rev_id, tree in parents_list:
1133
if len(accepted_revisions) > 0:
1134
# we always accept the first tree
1135
if rev_id in accepted_revisions or rev_id not in heads:
1136
# We have already included either this tree, or its
1137
# descendent, so we skip it.
1139
_mod_revision.check_not_reserved_id(rev_id)
1140
if tree is not None:
1141
real_trees.append((rev_id, tree))
1143
real_trees.append((rev_id,
1144
self.branch.repository.revision_tree(
1145
_mod_revision.NULL_REVISION)))
1146
ghosts.append(rev_id)
1147
accepted_revisions.add(rev_id)
1149
if (len(real_trees) == 1
1151
and self.branch.repository._format.fast_deltas
1152
and isinstance(real_trees[0][1], InventoryRevisionTree)
1153
and self.get_parent_ids()):
1154
rev_id, rev_tree = real_trees[0]
1155
basis_id = self.get_parent_ids()[0]
1156
# There are times when basis_tree won't be in
1157
# self.branch.repository, (switch, for example)
1159
basis_tree = self.branch.repository.revision_tree(basis_id)
1160
except errors.NoSuchRevision:
1161
# Fall back to the set_parent_trees(), since we can't use
1162
# _make_delta if we can't get the RevisionTree
1165
delta = rev_tree.root_inventory._make_delta(
1166
basis_tree.root_inventory)
1167
dirstate.update_basis_by_delta(delta, rev_id)
1170
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1171
self._make_dirty(reset_inventory=False)
1119
1173
def _set_root_id(self, file_id):
1120
1174
"""See WorkingTree.set_root_id."""
1121
1175
state = self.current_dirstate()
1122
state.set_path_id('', file_id)
1176
state.set_path_id(b'', file_id)
1123
1177
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1124
1178
self._make_dirty(reset_inventory=True)
1165
1218
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.
1220
def unversion(self, paths):
1221
"""Remove the file ids in paths from the current versioned set.
1171
When a file_id is unversioned, all of its children are automatically
1223
When a directory is unversioned, all of its children are automatically
1174
:param file_ids: The file ids to stop versioning.
1226
:param paths: The file ids to stop versioning.
1175
1227
: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.
1229
with self.lock_tree_write():
1232
state = self.current_dirstate()
1233
state._read_dirblocks_if_needed()
1236
file_id = self.path2id(path)
1238
raise errors.NoSuchFile(self, path)
1239
file_ids.add(file_id)
1240
ids_to_unversion = set(file_ids)
1241
paths_to_unversion = set()
1243
# check if the root is to be unversioned, if so, assert for now.
1244
# walk the state marking unversioned things as absent.
1245
# if there are any un-unversioned ids at the end, raise
1246
for key, details in state._dirblocks[0][1]:
1247
if (details[0][0] not in (b'a', b'r') and # absent or relocated
1248
key[2] in ids_to_unversion):
1249
# I haven't written the code to unversion / yet - it should
1251
raise errors.BzrError(
1252
'Unversioning the / is not currently supported')
1254
while block_index < len(state._dirblocks):
1255
# process one directory at a time.
1256
block = state._dirblocks[block_index]
1257
# first check: is the path one to remove - it or its children
1258
delete_block = False
1259
for path in paths_to_unversion:
1260
if (block[0].startswith(path) and
1261
(len(block[0]) == len(path) or
1262
block[0][len(path)] == '/')):
1263
# this entire block should be deleted - its the block for a
1264
# path to unversion; or the child of one
1267
# TODO: trim paths_to_unversion as we pass by paths
1269
# this block is to be deleted: process it.
1270
# TODO: we can special case the no-parents case and
1271
# just forget the whole block.
1273
while entry_index < len(block[1]):
1274
entry = block[1][entry_index]
1275
if entry[1][0][0] in (b'a', b'r'):
1276
# don't remove absent or renamed entries
1279
# Mark this file id as having been removed
1280
ids_to_unversion.discard(entry[0][2])
1281
if not state._make_absent(entry):
1282
# The block has not shrunk.
1284
# go to the next block. (At the moment we dont delete empty
1212
1288
entry_index = 0
1213
1289
while entry_index < len(block[1]):
1214
1290
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
1291
if (entry[1][0][0] in (b'a', b'r') or # absent, relocated
1292
# ^ some parent row.
1293
entry[0][2] not in ids_to_unversion):
1294
# ^ not an id to unversion
1297
if entry[1][0][0] == b'd':
1298
paths_to_unversion.add(
1299
pathjoin(entry[0][0], entry[0][1]))
1300
if not state._make_absent(entry):
1302
# we have unversioned this id
1303
ids_to_unversion.remove(entry[0][2])
1226
1304
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)
1305
if ids_to_unversion:
1306
raise errors.NoSuchId(self, next(iter(ids_to_unversion)))
1307
self._make_dirty(reset_inventory=False)
1308
# have to change the legacy inventory too.
1309
if self._inventory is not None:
1310
for file_id in file_ids:
1311
if self._inventory.has_id(file_id):
1312
self._inventory.remove_recursive_id(file_id)
1252
@needs_tree_write_lock
1253
1314
def rename_one(self, from_rel, to_rel, after=False):
1254
1315
"""See WorkingTree.rename_one"""
1256
WorkingTree.rename_one(self, from_rel, to_rel, after)
1316
with self.lock_tree_write():
1318
super(DirStateWorkingTree, self).rename_one(
1319
from_rel, to_rel, after)
1258
@needs_tree_write_lock
1259
1321
def apply_inventory_delta(self, changes):
1260
1322
"""See MutableTree.apply_inventory_delta"""
1261
state = self.current_dirstate()
1262
state.update_by_delta(changes)
1263
self._make_dirty(reset_inventory=True)
1323
with self.lock_tree_write():
1324
state = self.current_dirstate()
1325
state.update_by_delta(changes)
1326
self._make_dirty(reset_inventory=True)
1265
1328
def update_basis_by_delta(self, new_revid, delta):
1266
1329
"""See MutableTree.update_basis_by_delta."""
1735
1849
inv_entry.text_size = size
1736
1850
inv_entry.text_sha1 = fingerprint
1737
1851
elif kind == 'directory':
1738
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1852
parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry
1739
1853
elif kind == 'symlink':
1740
inv_entry.executable = False
1741
inv_entry.text_size = None
1742
1854
inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1743
1855
elif kind == 'tree-reference':
1744
1856
inv_entry.reference_revision = fingerprint or None
1746
raise AssertionError("cannot convert entry %r into an InventoryEntry"
1858
raise AssertionError(
1859
"cannot convert entry %r into an InventoryEntry"
1748
1861
# These checks cost us around 40ms on a 55k entry tree
1749
1862
if file_id in inv_byid:
1750
raise AssertionError('file_id %s already in'
1863
raise AssertionError(
1864
'file_id %s already in'
1751
1865
' inventory as %s' % (file_id, inv_byid[file_id]))
1752
1866
if name_unicode in parent_ie.children:
1753
1867
raise AssertionError('name %r already in parent'
1755
1869
inv_byid[file_id] = inv_entry
1756
1870
parent_ie.children[name_unicode] = inv_entry
1757
1871
self._inventory = inv
1759
def get_file_mtime(self, file_id, path=None):
1873
def get_file_mtime(self, path):
1760
1874
"""Return the modification time for this record.
1762
1876
We return the timestamp of the last-changed revision.
1764
1878
# Make sure the file exists
1765
entry = self._get_entry(file_id, path=path)
1879
entry = self._get_entry(path=path)
1766
1880
if entry == (None, None): # do we raise?
1881
raise errors.NoSuchFile(path)
1768
1882
parent_index = self._get_parent_index()
1769
1883
last_changed_revision = entry[1][parent_index][4]
1771
1885
rev = self._repository.get_revision(last_changed_revision)
1772
1886
except errors.NoSuchRevision:
1773
raise errors.FileTimestampUnavailable(self.id2path(file_id))
1887
raise FileTimestampUnavailable(path)
1774
1888
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)
1890
def get_file_sha1(self, path, stat_value=None):
1891
entry = self._get_entry(path=path)
1778
1892
parent_index = self._get_parent_index()
1779
1893
parent_details = entry[1][parent_index]
1780
if parent_details[0] == 'f':
1894
if parent_details[0] == b'f':
1781
1895
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):
1898
def get_file_revision(self, path):
1899
with self.lock_read():
1900
inv, inv_file_id = self._path2inv_file_id(path)
1901
return inv.get_entry(inv_file_id).revision
1903
def get_file(self, path):
1904
return BytesIO(self.get_file_text(path))
1906
def get_file_size(self, path):
1788
1907
"""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
1908
inv, inv_file_id = self._path2inv_file_id(path)
1909
return inv.get_entry(inv_file_id).text_size
1911
def get_file_text(self, path):
1913
for _, content_iter in self.iter_files_bytes([(path, None)]):
1914
if content is not None:
1915
raise AssertionError('iter_files_bytes returned'
1916
' too many entries')
1917
# For each entry returned by iter_files_bytes, we must consume the
1918
# content_iter before we step the files iterator.
1919
content = b''.join(content_iter)
1921
raise AssertionError('iter_files_bytes did not return'
1922
' the requested data')
1925
def get_reference_revision(self, path):
1926
inv, inv_file_id = self._path2inv_file_id(path)
1927
return inv.get_entry(inv_file_id).reference_revision
1798
1929
def iter_files_bytes(self, desired_files):
1799
1930
"""See Tree.iter_files_bytes.