200
167
"""Change the path that is assigned to a transaction id."""
201
168
if trans_id == self._new_root:
202
169
raise CantMoveRoot
203
previous_parent = self._new_parent.get(trans_id)
204
previous_name = self._new_name.get(trans_id)
205
170
self._new_name[trans_id] = name
206
171
self._new_parent[trans_id] = parent
207
172
if parent == ROOT_PARENT:
208
173
if self._new_root is not None:
209
174
raise ValueError("Cannot have multiple roots.")
210
175
self._new_root = trans_id
211
if (trans_id in self._limbo_files and
212
trans_id not in self._needs_rename):
213
self._rename_in_limbo([trans_id])
214
self._limbo_children[previous_parent].remove(trans_id)
215
del self._limbo_children_names[previous_parent][previous_name]
217
def _rename_in_limbo(self, trans_ids):
218
"""Fix limbo names so that the right final path is produced.
220
This means we outsmarted ourselves-- we tried to avoid renaming
221
these files later by creating them with their final names in their
222
final parents. But now the previous name or parent is no longer
223
suitable, so we have to rename them.
225
Even for trans_ids that have no new contents, we must remove their
226
entries from _limbo_files, because they are now stale.
228
for trans_id in trans_ids:
229
old_path = self._limbo_files.pop(trans_id)
230
if trans_id not in self._new_contents:
232
new_path = self._limbo_name(trans_id)
233
os.rename(old_path, new_path)
235
177
def adjust_root_path(self, name, parent):
236
178
"""Emulate moving the root by moving all children, instead.
332
274
return ROOT_PARENT
333
275
return self.trans_id_tree_path(os.path.dirname(path))
335
def create_file(self, contents, trans_id, mode_id=None):
336
"""Schedule creation of a new file.
340
Contents is an iterator of strings, all of which will be written
341
to the target destination.
343
New file takes the permissions of any existing file with that id,
344
unless mode_id is specified.
346
name = self._limbo_name(trans_id)
350
unique_add(self._new_contents, trans_id, 'file')
352
# Clean up the file, it never got registered so
353
# TreeTransform.finalize() won't clean it up.
358
f.writelines(contents)
361
self._set_mode(trans_id, mode_id, S_ISREG)
363
def _set_mode(self, trans_id, mode_id, typefunc):
364
"""Set the mode of new file contents.
365
The mode_id is the existing file to get the mode from (often the same
366
as trans_id). The operation is only performed if there's a mode match
367
according to typefunc.
372
old_path = self._tree_id_paths[mode_id]
376
mode = os.stat(self._tree.abspath(old_path)).st_mode
378
if e.errno in (errno.ENOENT, errno.ENOTDIR):
379
# Either old_path doesn't exist, or the parent of the
380
# target is not a directory (but will be one eventually)
381
# Either way, we know it doesn't exist *right now*
382
# See also bug #248448
387
os.chmod(self._limbo_name(trans_id), mode)
389
def create_hardlink(self, path, trans_id):
390
"""Schedule creation of a hard link"""
391
name = self._limbo_name(trans_id)
395
if e.errno != errno.EPERM:
397
raise errors.HardLinkNotSupported(path)
399
unique_add(self._new_contents, trans_id, 'file')
401
# Clean up the file, it never got registered so
402
# TreeTransform.finalize() won't clean it up.
406
def create_directory(self, trans_id):
407
"""Schedule creation of a new directory.
409
See also new_directory.
411
os.mkdir(self._limbo_name(trans_id))
412
unique_add(self._new_contents, trans_id, 'directory')
414
def create_symlink(self, target, trans_id):
415
"""Schedule creation of a new symbolic link.
417
target is a bytestring.
418
See also new_symlink.
421
os.symlink(target, self._limbo_name(trans_id))
422
unique_add(self._new_contents, trans_id, 'symlink')
425
path = FinalPaths(self).get_path(trans_id)
428
raise UnableCreateSymlink(path=path)
430
def cancel_creation(self, trans_id):
431
"""Cancel the creation of new file contents."""
432
del self._new_contents[trans_id]
433
children = self._limbo_children.get(trans_id)
434
# if this is a limbo directory with children, move them before removing
436
if children is not None:
437
self._rename_in_limbo(children)
438
del self._limbo_children[trans_id]
439
del self._limbo_children_names[trans_id]
440
delete_any(self._limbo_name(trans_id))
442
277
def delete_contents(self, trans_id):
443
278
"""Schedule the contents of a path entry for deletion"""
444
279
self.tree_kind(trans_id)
870
def _limbo_name(self, trans_id):
871
"""Generate the limbo name of a file"""
872
limbo_name = self._limbo_files.get(trans_id)
873
if limbo_name is not None:
875
parent = self._new_parent.get(trans_id)
876
# if the parent directory is already in limbo (e.g. when building a
877
# tree), choose a limbo name inside the parent, to reduce further
879
use_direct_path = False
880
if self._new_contents.get(parent) == 'directory':
881
filename = self._new_name.get(trans_id)
882
if filename is not None:
883
if parent not in self._limbo_children:
884
self._limbo_children[parent] = set()
885
self._limbo_children_names[parent] = {}
886
use_direct_path = True
887
# the direct path can only be used if no other file has
888
# already taken this pathname, i.e. if the name is unused, or
889
# if it is already associated with this trans_id.
890
elif self._case_sensitive_target:
891
if (self._limbo_children_names[parent].get(filename)
892
in (trans_id, None)):
893
use_direct_path = True
895
for l_filename, l_trans_id in\
896
self._limbo_children_names[parent].iteritems():
897
if l_trans_id == trans_id:
899
if l_filename.lower() == filename.lower():
902
use_direct_path = True
905
limbo_name = pathjoin(self._limbo_files[parent], filename)
906
self._limbo_children[parent].add(trans_id)
907
self._limbo_children_names[parent][filename] = trans_id
909
limbo_name = pathjoin(self._limbodir, trans_id)
910
self._needs_rename.add(trans_id)
911
self._limbo_files[trans_id] = limbo_name
914
705
def _set_executability(self, path, trans_id):
915
706
"""Set the executability of versioned files """
916
707
if supports_executable():
1227
1014
self.create_symlink(content.decode('utf-8'), trans_id)
1230
class TreeTransform(TreeTransformBase):
1017
class DiskTreeTransform(TreeTransformBase):
1018
"""Tree transform storing its contents on disk."""
1020
def __init__(self, tree, limbodir, pb=DummyProgress(),
1021
case_sensitive=True):
1023
:param tree: The tree that will be transformed, but not necessarily
1025
:param limbodir: A directory where new files can be stored until
1026
they are installed in their proper places
1027
:param pb: A ProgressBar indicating how much progress is being made
1028
:param case_sensitive: If True, the target of the transform is
1029
case sensitive, not just case preserving.
1031
TreeTransformBase.__init__(self, tree, pb, case_sensitive)
1032
self._limbodir = limbodir
1033
self._deletiondir = None
1034
# A mapping of transform ids to their limbo filename
1035
self._limbo_files = {}
1036
# A mapping of transform ids to a set of the transform ids of children
1037
# that their limbo directory has
1038
self._limbo_children = {}
1039
# Map transform ids to maps of child filename to child transform id
1040
self._limbo_children_names = {}
1041
# List of transform ids that need to be renamed from limbo into place
1042
self._needs_rename = set()
1045
"""Release the working tree lock, if held, clean up limbo dir.
1047
This is required if apply has not been invoked, but can be invoked
1050
if self._tree is None:
1053
entries = [(self._limbo_name(t), t, k) for t, k in
1054
self._new_contents.iteritems()]
1055
entries.sort(reverse=True)
1056
for path, trans_id, kind in entries:
1057
if kind == "directory":
1062
os.rmdir(self._limbodir)
1064
# We don't especially care *why* the dir is immortal.
1065
raise ImmortalLimbo(self._limbodir)
1067
if self._deletiondir is not None:
1068
os.rmdir(self._deletiondir)
1070
raise errors.ImmortalPendingDeletion(self._deletiondir)
1072
TreeTransformBase.finalize(self)
1074
def _limbo_name(self, trans_id):
1075
"""Generate the limbo name of a file"""
1076
limbo_name = self._limbo_files.get(trans_id)
1077
if limbo_name is not None:
1079
parent = self._new_parent.get(trans_id)
1080
# if the parent directory is already in limbo (e.g. when building a
1081
# tree), choose a limbo name inside the parent, to reduce further
1083
use_direct_path = False
1084
if self._new_contents.get(parent) == 'directory':
1085
filename = self._new_name.get(trans_id)
1086
if filename is not None:
1087
if parent not in self._limbo_children:
1088
self._limbo_children[parent] = set()
1089
self._limbo_children_names[parent] = {}
1090
use_direct_path = True
1091
# the direct path can only be used if no other file has
1092
# already taken this pathname, i.e. if the name is unused, or
1093
# if it is already associated with this trans_id.
1094
elif self._case_sensitive_target:
1095
if (self._limbo_children_names[parent].get(filename)
1096
in (trans_id, None)):
1097
use_direct_path = True
1099
for l_filename, l_trans_id in\
1100
self._limbo_children_names[parent].iteritems():
1101
if l_trans_id == trans_id:
1103
if l_filename.lower() == filename.lower():
1106
use_direct_path = True
1109
limbo_name = pathjoin(self._limbo_files[parent], filename)
1110
self._limbo_children[parent].add(trans_id)
1111
self._limbo_children_names[parent][filename] = trans_id
1113
limbo_name = pathjoin(self._limbodir, trans_id)
1114
self._needs_rename.add(trans_id)
1115
self._limbo_files[trans_id] = limbo_name
1118
def adjust_path(self, name, parent, trans_id):
1119
previous_parent = self._new_parent.get(trans_id)
1120
previous_name = self._new_name.get(trans_id)
1121
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1122
if (trans_id in self._limbo_files and
1123
trans_id not in self._needs_rename):
1124
self._rename_in_limbo([trans_id])
1125
self._limbo_children[previous_parent].remove(trans_id)
1126
del self._limbo_children_names[previous_parent][previous_name]
1128
def _rename_in_limbo(self, trans_ids):
1129
"""Fix limbo names so that the right final path is produced.
1131
This means we outsmarted ourselves-- we tried to avoid renaming
1132
these files later by creating them with their final names in their
1133
final parents. But now the previous name or parent is no longer
1134
suitable, so we have to rename them.
1136
Even for trans_ids that have no new contents, we must remove their
1137
entries from _limbo_files, because they are now stale.
1139
for trans_id in trans_ids:
1140
old_path = self._limbo_files.pop(trans_id)
1141
if trans_id not in self._new_contents:
1143
new_path = self._limbo_name(trans_id)
1144
os.rename(old_path, new_path)
1146
def create_file(self, contents, trans_id, mode_id=None):
1147
"""Schedule creation of a new file.
1151
Contents is an iterator of strings, all of which will be written
1152
to the target destination.
1154
New file takes the permissions of any existing file with that id,
1155
unless mode_id is specified.
1157
name = self._limbo_name(trans_id)
1158
f = open(name, 'wb')
1161
unique_add(self._new_contents, trans_id, 'file')
1163
# Clean up the file, it never got registered so
1164
# TreeTransform.finalize() won't clean it up.
1169
f.writelines(contents)
1172
self._set_mode(trans_id, mode_id, S_ISREG)
1174
def _read_file_chunks(self, trans_id):
1175
cur_file = open(self._limbo_name(trans_id), 'rb')
1177
return cur_file.readlines()
1181
def _read_symlink_target(self, trans_id):
1182
return os.readlink(self._limbo_name(trans_id))
1184
def _set_mode(self, trans_id, mode_id, typefunc):
1185
"""Set the mode of new file contents.
1186
The mode_id is the existing file to get the mode from (often the same
1187
as trans_id). The operation is only performed if there's a mode match
1188
according to typefunc.
1193
old_path = self._tree_id_paths[mode_id]
1197
mode = os.stat(self._tree.abspath(old_path)).st_mode
1199
if e.errno in (errno.ENOENT, errno.ENOTDIR):
1200
# Either old_path doesn't exist, or the parent of the
1201
# target is not a directory (but will be one eventually)
1202
# Either way, we know it doesn't exist *right now*
1203
# See also bug #248448
1208
os.chmod(self._limbo_name(trans_id), mode)
1210
def create_hardlink(self, path, trans_id):
1211
"""Schedule creation of a hard link"""
1212
name = self._limbo_name(trans_id)
1216
if e.errno != errno.EPERM:
1218
raise errors.HardLinkNotSupported(path)
1220
unique_add(self._new_contents, trans_id, 'file')
1222
# Clean up the file, it never got registered so
1223
# TreeTransform.finalize() won't clean it up.
1227
def create_directory(self, trans_id):
1228
"""Schedule creation of a new directory.
1230
See also new_directory.
1232
os.mkdir(self._limbo_name(trans_id))
1233
unique_add(self._new_contents, trans_id, 'directory')
1235
def create_symlink(self, target, trans_id):
1236
"""Schedule creation of a new symbolic link.
1238
target is a bytestring.
1239
See also new_symlink.
1242
os.symlink(target, self._limbo_name(trans_id))
1243
unique_add(self._new_contents, trans_id, 'symlink')
1246
path = FinalPaths(self).get_path(trans_id)
1249
raise UnableCreateSymlink(path=path)
1251
def cancel_creation(self, trans_id):
1252
"""Cancel the creation of new file contents."""
1253
del self._new_contents[trans_id]
1254
children = self._limbo_children.get(trans_id)
1255
# if this is a limbo directory with children, move them before removing
1257
if children is not None:
1258
self._rename_in_limbo(children)
1259
del self._limbo_children[trans_id]
1260
del self._limbo_children_names[trans_id]
1261
delete_any(self._limbo_name(trans_id))
1264
class TreeTransform(DiskTreeTransform):
1231
1265
"""Represent a tree transformation.
1233
1267
This object is designed to support incremental generation of the transform,