983
974
new_children.sort()
984
975
new_children = collections.deque(new_children)
985
976
stack.append((f_ie.file_id, fp, fap, new_children))
986
# Break out of inner loop, so that we start outer loop with child
977
# Break out of inner loop,
978
# so that we start outer loop with child
989
981
# if we finished all children, pop it off the stack
992
984
@needs_tree_write_lock
993
def move(self, from_paths, to_name):
985
def move(self, from_paths, to_dir=None, after=False, **kwargs):
996
to_name must exist in the inventory.
988
to_dir must exist in the inventory.
998
If to_name exists and is a directory, the files are moved into
990
If to_dir exists and is a directory, the files are moved into
999
991
it, keeping their old names.
1001
Note that to_name is only the last component of the new name;
993
Note that to_dir is only the last component of the new name;
1002
994
this doesn't change the directory.
996
For each entry in from_paths the move mode will be determined
999
The first mode moves the file in the filesystem and updates the
1000
inventory. The second mode only updates the inventory without
1001
touching the file on the filesystem. This is the new mode introduced
1004
move uses the second mode if 'after == True' and the target is not
1005
versioned but present in the working tree.
1007
move uses the second mode if 'after == False' and the source is
1008
versioned but no longer in the working tree, and the target is not
1009
versioned but present in the working tree.
1011
move uses the first mode if 'after == False' and the source is
1012
versioned and present in the working tree, and the target is not
1013
versioned and not present in the working tree.
1015
Everything else results in an error.
1004
1017
This returns a list of (from_path, to_path) pairs for each
1005
1018
entry that is moved.
1008
## TODO: Option to move IDs only
1023
# check for deprecated use of signature
1025
to_dir = kwargs.get('to_name', None)
1027
raise TypeError('You must supply a target directory')
1029
symbol_versioning.warn('The parameter to_name was deprecated'
1030
' in version 0.13. Use to_dir instead',
1033
# check destination directory
1009
1034
assert not isinstance(from_paths, basestring)
1010
1035
inv = self.inventory
1011
to_abs = self.abspath(to_name)
1036
to_abs = self.abspath(to_dir)
1012
1037
if not isdir(to_abs):
1013
raise BzrError("destination %r is not a directory" % to_abs)
1014
if not self.has_filename(to_name):
1015
raise BzrError("destination %r not in working directory" % to_abs)
1016
to_dir_id = inv.path2id(to_name)
1017
if to_dir_id is None and to_name != '':
1018
raise BzrError("destination %r is not a versioned directory" % to_name)
1038
raise errors.BzrMoveFailedError('',to_dir,
1039
errors.NotADirectory(to_abs))
1040
if not self.has_filename(to_dir):
1041
raise errors.BzrMoveFailedError('',to_dir,
1042
errors.NotInWorkingDirectory(to_dir))
1043
to_dir_id = inv.path2id(to_dir)
1044
if to_dir_id is None:
1045
raise errors.BzrMoveFailedError('',to_dir,
1046
errors.NotVersionedError(path=str(to_dir)))
1019
1048
to_dir_ie = inv[to_dir_id]
1020
1049
if to_dir_ie.kind != 'directory':
1021
raise BzrError("destination %r is not a directory" % to_abs)
1023
to_idpath = inv.get_idpath(to_dir_id)
1025
for f in from_paths:
1026
if not self.has_filename(f):
1027
raise BzrError("%r does not exist in working tree" % f)
1028
f_id = inv.path2id(f)
1030
raise BzrError("%r is not versioned" % f)
1031
name_tail = splitpath(f)[-1]
1032
dest_path = pathjoin(to_name, name_tail)
1033
if self.has_filename(dest_path):
1034
raise BzrError("destination %r already exists" % dest_path)
1035
if f_id in to_idpath:
1036
raise BzrError("can't move %r to a subdirectory of itself" % f)
1038
# OK, so there's a race here, it's possible that someone will
1039
# create a file in this interval and then the rename might be
1040
# left half-done. But we should have caught most problems.
1041
orig_inv = deepcopy(self.inventory)
1050
raise errors.BzrMoveFailedError('',to_dir,
1051
errors.NotADirectory(to_abs))
1053
# create rename entries and tuples
1054
for from_rel in from_paths:
1055
from_tail = splitpath(from_rel)[-1]
1056
from_id = inv.path2id(from_rel)
1058
raise errors.BzrMoveFailedError(from_rel,to_dir,
1059
errors.NotVersionedError(path=str(from_rel)))
1061
from_entry = inv[from_id]
1062
from_parent_id = from_entry.parent_id
1063
to_rel = pathjoin(to_dir, from_tail)
1064
rename_entry = WorkingTree._RenameEntry(from_rel=from_rel,
1066
from_tail=from_tail,
1067
from_parent_id=from_parent_id,
1068
to_rel=to_rel, to_tail=from_tail,
1069
to_parent_id=to_dir_id)
1070
rename_entries.append(rename_entry)
1071
rename_tuples.append((from_rel, to_rel))
1073
# determine which move mode to use. checks also for movability
1074
rename_entries = self._determine_mv_mode(rename_entries, after)
1042
1076
original_modified = self._inventory_is_modified
1044
1078
if len(from_paths):
1045
1079
self._inventory_is_modified = True
1046
for f in from_paths:
1047
name_tail = splitpath(f)[-1]
1048
dest_path = pathjoin(to_name, name_tail)
1049
result.append((f, dest_path))
1050
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1052
osutils.rename(self.abspath(f), self.abspath(dest_path))
1054
raise BzrError("failed to rename %r to %r: %s" %
1055
(f, dest_path, e[1]))
1080
self._move(rename_entries)
1057
1082
# restore the inventory on error
1058
self._set_inventory(orig_inv, dirty=original_modified)
1083
self._inventory_is_modified = original_modified
1060
1085
self._write_inventory(inv)
1086
return rename_tuples
1088
def _determine_mv_mode(self, rename_entries, after=False):
1089
"""Determines for each from-to pair if both inventory and working tree
1090
or only the inventory has to be changed.
1092
Also does basic plausability tests.
1094
inv = self.inventory
1096
for rename_entry in rename_entries:
1097
# store to local variables for easier reference
1098
from_rel = rename_entry.from_rel
1099
from_id = rename_entry.from_id
1100
to_rel = rename_entry.to_rel
1101
to_id = inv.path2id(to_rel)
1102
only_change_inv = False
1104
# check the inventory for source and destination
1106
raise errors.BzrMoveFailedError(from_rel,to_rel,
1107
errors.NotVersionedError(path=str(from_rel)))
1108
if to_id is not None:
1109
raise errors.BzrMoveFailedError(from_rel,to_rel,
1110
errors.AlreadyVersionedError(path=str(to_rel)))
1112
# try to determine the mode for rename (only change inv or change
1113
# inv and file system)
1115
if not self.has_filename(to_rel):
1116
raise errors.BzrMoveFailedError(from_id,to_rel,
1117
errors.NoSuchFile(path=str(to_rel),
1118
extra="New file has not been created yet"))
1119
only_change_inv = True
1120
elif not self.has_filename(from_rel) and self.has_filename(to_rel):
1121
only_change_inv = True
1122
elif self.has_filename(from_rel) and not self.has_filename(to_rel):
1123
only_change_inv = False
1125
# something is wrong, so lets determine what exactly
1126
if not self.has_filename(from_rel) and \
1127
not self.has_filename(to_rel):
1128
raise errors.BzrRenameFailedError(from_rel,to_rel,
1129
errors.PathsDoNotExist(paths=(str(from_rel),
1132
raise errors.RenameFailedFilesExist(from_rel, to_rel,
1133
extra="(Use --after to update the Bazaar id)")
1134
rename_entry.only_change_inv = only_change_inv
1135
return rename_entries
1137
def _move(self, rename_entries):
1138
"""Moves a list of files.
1140
Depending on the value of the flag 'only_change_inv', the
1141
file will be moved on the file system or not.
1143
inv = self.inventory
1146
for entry in rename_entries:
1148
self._move_entry(entry)
1150
self._rollback_move(moved)
1154
def _rollback_move(self, moved):
1155
"""Try to rollback a previous move in case of an filesystem error."""
1156
inv = self.inventory
1159
self._move_entry(_RenameEntry(entry.to_rel, entry.from_id,
1160
entry.to_tail, entry.to_parent_id, entry.from_rel,
1161
entry.from_tail, entry.from_parent_id,
1162
entry.only_change_inv))
1163
except errors.BzrMoveFailedError, e:
1164
raise errors.BzrMoveFailedError( '', '', "Rollback failed."
1165
" The working tree is in an inconsistent state."
1166
" Please consider doing a 'bzr revert'."
1167
" Error message is: %s" % e)
1169
def _move_entry(self, entry):
1170
inv = self.inventory
1171
from_rel_abs = self.abspath(entry.from_rel)
1172
to_rel_abs = self.abspath(entry.to_rel)
1173
if from_rel_abs == to_rel_abs:
1174
raise errors.BzrMoveFailedError(entry.from_rel, entry.to_rel,
1175
"Source and target are identical.")
1177
if not entry.only_change_inv:
1179
osutils.rename(from_rel_abs, to_rel_abs)
1181
raise errors.BzrMoveFailedError(entry.from_rel,
1183
inv.rename(entry.from_id, entry.to_parent_id, entry.to_tail)
1063
1185
@needs_tree_write_lock
1064
def rename_one(self, from_rel, to_rel):
1186
def rename_one(self, from_rel, to_rel, after=False):
1065
1187
"""Rename one file.
1067
1189
This can change the directory or the filename or both.
1191
rename_one has several 'modes' to work. First, it can rename a physical
1192
file and change the file_id. That is the normal mode. Second, it can
1193
only change the file_id without touching any physical file. This is
1194
the new mode introduced in version 0.15.
1196
rename_one uses the second mode if 'after == True' and 'to_rel' is not
1197
versioned but present in the working tree.
1199
rename_one uses the second mode if 'after == False' and 'from_rel' is
1200
versioned but no longer in the working tree, and 'to_rel' is not
1201
versioned but present in the working tree.
1203
rename_one uses the first mode if 'after == False' and 'from_rel' is
1204
versioned and present in the working tree, and 'to_rel' is not
1205
versioned and not present in the working tree.
1207
Everything else results in an error.
1069
1209
inv = self.inventory
1070
if not self.has_filename(from_rel):
1071
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1072
if self.has_filename(to_rel):
1073
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1075
file_id = inv.path2id(from_rel)
1077
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1079
entry = inv[file_id]
1080
from_parent = entry.parent_id
1081
from_name = entry.name
1083
if inv.path2id(to_rel):
1084
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1212
# create rename entries and tuples
1213
from_tail = splitpath(from_rel)[-1]
1214
from_id = inv.path2id(from_rel)
1216
raise errors.BzrRenameFailedError(from_rel,to_rel,
1217
errors.NotVersionedError(path=str(from_rel)))
1218
from_entry = inv[from_id]
1219
from_parent_id = from_entry.parent_id
1086
1220
to_dir, to_tail = os.path.split(to_rel)
1087
1221
to_dir_id = inv.path2id(to_dir)
1088
if to_dir_id is None and to_dir != '':
1089
raise BzrError("can't determine destination directory id for %r" % to_dir)
1091
mutter("rename_one:")
1092
mutter(" file_id {%s}" % file_id)
1093
mutter(" from_rel %r" % from_rel)
1094
mutter(" to_rel %r" % to_rel)
1095
mutter(" to_dir %r" % to_dir)
1096
mutter(" to_dir_id {%s}" % to_dir_id)
1098
inv.rename(file_id, to_dir_id, to_tail)
1100
from_abs = self.abspath(from_rel)
1101
to_abs = self.abspath(to_rel)
1103
osutils.rename(from_abs, to_abs)
1105
inv.rename(file_id, from_parent, from_name)
1106
raise BzrError("failed to rename %r to %r: %s"
1107
% (from_abs, to_abs, e[1]))
1222
rename_entry = WorkingTree._RenameEntry(from_rel=from_rel,
1224
from_tail=from_tail,
1225
from_parent_id=from_parent_id,
1226
to_rel=to_rel, to_tail=to_tail,
1227
to_parent_id=to_dir_id)
1228
rename_entries.append(rename_entry)
1230
# determine which move mode to use. checks also for movability
1231
rename_entries = self._determine_mv_mode(rename_entries, after)
1233
# check if the target changed directory and if the target directory is
1235
if to_dir_id is None:
1236
raise errors.BzrMoveFailedError(from_rel,to_rel,
1237
errors.NotVersionedError(path=str(to_dir)))
1239
# all checks done. now we can continue with our actual work
1240
mutter('rename_one:\n'
1245
' to_dir_id {%s}\n',
1246
from_id, from_rel, to_rel, to_dir, to_dir_id)
1248
self._move(rename_entries)
1108
1249
self._write_inventory(inv)
1251
class _RenameEntry(object):
1252
def __init__(self, from_rel, from_id, from_tail, from_parent_id,
1253
to_rel, to_tail, to_parent_id, only_change_inv=False):
1254
self.from_rel = from_rel
1255
self.from_id = from_id
1256
self.from_tail = from_tail
1257
self.from_parent_id = from_parent_id
1258
self.to_rel = to_rel
1259
self.to_tail = to_tail
1260
self.to_parent_id = to_parent_id
1261
self.only_change_inv = only_change_inv
1110
1263
@needs_read_lock
1111
1264
def unknowns(self):
1112
1265
"""Return all unknown files.