199
163
"""Change the path that is assigned to a transaction id."""
200
164
if trans_id == self._new_root:
201
165
raise CantMoveRoot
202
previous_parent = self._new_parent.get(trans_id)
203
previous_name = self._new_name.get(trans_id)
204
166
self._new_name[trans_id] = name
205
167
self._new_parent[trans_id] = parent
206
168
if parent == ROOT_PARENT:
207
169
if self._new_root is not None:
208
170
raise ValueError("Cannot have multiple roots.")
209
171
self._new_root = trans_id
210
if (trans_id in self._limbo_files and
211
trans_id not in self._needs_rename):
212
self._rename_in_limbo([trans_id])
213
self._limbo_children[previous_parent].remove(trans_id)
214
del self._limbo_children_names[previous_parent][previous_name]
216
def _rename_in_limbo(self, trans_ids):
217
"""Fix limbo names so that the right final path is produced.
219
This means we outsmarted ourselves-- we tried to avoid renaming
220
these files later by creating them with their final names in their
221
final parents. But now the previous name or parent is no longer
222
suitable, so we have to rename them.
224
Even for trans_ids that have no new contents, we must remove their
225
entries from _limbo_files, because they are now stale.
227
for trans_id in trans_ids:
228
old_path = self._limbo_files.pop(trans_id)
229
if trans_id not in self._new_contents:
231
new_path = self._limbo_name(trans_id)
232
os.rename(old_path, new_path)
234
173
def adjust_root_path(self, name, parent):
235
174
"""Emulate moving the root by moving all children, instead.
331
251
return ROOT_PARENT
332
252
return self.trans_id_tree_path(os.path.dirname(path))
334
def create_file(self, contents, trans_id, mode_id=None):
335
"""Schedule creation of a new file.
339
Contents is an iterator of strings, all of which will be written
340
to the target destination.
342
New file takes the permissions of any existing file with that id,
343
unless mode_id is specified.
345
name = self._limbo_name(trans_id)
349
unique_add(self._new_contents, trans_id, 'file')
351
# Clean up the file, it never got registered so
352
# TreeTransform.finalize() won't clean it up.
357
f.writelines(contents)
360
self._set_mode(trans_id, mode_id, S_ISREG)
362
def _set_mode(self, trans_id, mode_id, typefunc):
363
"""Set the mode of new file contents.
364
The mode_id is the existing file to get the mode from (often the same
365
as trans_id). The operation is only performed if there's a mode match
366
according to typefunc.
371
old_path = self._tree_id_paths[mode_id]
375
mode = os.stat(self._tree.abspath(old_path)).st_mode
377
if e.errno in (errno.ENOENT, errno.ENOTDIR):
378
# Either old_path doesn't exist, or the parent of the
379
# target is not a directory (but will be one eventually)
380
# Either way, we know it doesn't exist *right now*
381
# See also bug #248448
386
os.chmod(self._limbo_name(trans_id), mode)
388
def create_hardlink(self, path, trans_id):
389
"""Schedule creation of a hard link"""
390
name = self._limbo_name(trans_id)
394
if e.errno != errno.EPERM:
396
raise errors.HardLinkNotSupported(path)
398
unique_add(self._new_contents, trans_id, 'file')
400
# Clean up the file, it never got registered so
401
# TreeTransform.finalize() won't clean it up.
405
def create_directory(self, trans_id):
406
"""Schedule creation of a new directory.
408
See also new_directory.
410
os.mkdir(self._limbo_name(trans_id))
411
unique_add(self._new_contents, trans_id, 'directory')
413
def create_symlink(self, target, trans_id):
414
"""Schedule creation of a new symbolic link.
416
target is a bytestring.
417
See also new_symlink.
420
os.symlink(target, self._limbo_name(trans_id))
421
unique_add(self._new_contents, trans_id, 'symlink')
424
path = FinalPaths(self).get_path(trans_id)
427
raise UnableCreateSymlink(path=path)
429
def cancel_creation(self, trans_id):
430
"""Cancel the creation of new file contents."""
431
del self._new_contents[trans_id]
432
children = self._limbo_children.get(trans_id)
433
# if this is a limbo directory with children, move them before removing
435
if children is not None:
436
self._rename_in_limbo(children)
437
del self._limbo_children[trans_id]
438
del self._limbo_children_names[trans_id]
439
delete_any(self._limbo_name(trans_id))
441
254
def delete_contents(self, trans_id):
442
255
"""Schedule the contents of a path entry for deletion"""
443
256
self.tree_kind(trans_id)
869
def _limbo_name(self, trans_id):
870
"""Generate the limbo name of a file"""
871
limbo_name = self._limbo_files.get(trans_id)
872
if limbo_name is not None:
874
parent = self._new_parent.get(trans_id)
875
# if the parent directory is already in limbo (e.g. when building a
876
# tree), choose a limbo name inside the parent, to reduce further
878
use_direct_path = False
879
if self._new_contents.get(parent) == 'directory':
880
filename = self._new_name.get(trans_id)
881
if filename is not None:
882
if parent not in self._limbo_children:
883
self._limbo_children[parent] = set()
884
self._limbo_children_names[parent] = {}
885
use_direct_path = True
886
# the direct path can only be used if no other file has
887
# already taken this pathname, i.e. if the name is unused, or
888
# if it is already associated with this trans_id.
889
elif self._case_sensitive_target:
890
if (self._limbo_children_names[parent].get(filename)
891
in (trans_id, None)):
892
use_direct_path = True
894
for l_filename, l_trans_id in\
895
self._limbo_children_names[parent].iteritems():
896
if l_trans_id == trans_id:
898
if l_filename.lower() == filename.lower():
901
use_direct_path = True
904
limbo_name = pathjoin(self._limbo_files[parent], filename)
905
self._limbo_children[parent].add(trans_id)
906
self._limbo_children_names[parent][filename] = trans_id
908
limbo_name = pathjoin(self._limbodir, trans_id)
909
self._needs_rename.add(trans_id)
910
self._limbo_files[trans_id] = limbo_name
913
646
def _set_executability(self, path, trans_id):
914
647
"""Set the executability of versioned files """
915
648
if supports_executable():
1226
955
self.create_symlink(content.decode('utf-8'), trans_id)
1229
class TreeTransform(TreeTransformBase):
958
class DiskTreeTransform(TreeTransformBase):
959
"""Tree transform storing its contents on disk."""
961
def __init__(self, tree, limbodir, pb=DummyProgress(),
962
case_sensitive=True):
964
:param tree: The tree that will be transformed, but not necessarily
966
:param limbodir: A directory where new files can be stored until
967
they are installed in their proper places
968
:param pb: A ProgressBar indicating how much progress is being made
969
:param case_sensitive: If True, the target of the transform is
970
case sensitive, not just case preserving.
972
TreeTransformBase.__init__(self, tree, pb, case_sensitive)
973
self._limbodir = limbodir
974
self._deletiondir = None
975
# A mapping of transform ids to their limbo filename
976
self._limbo_files = {}
977
# A mapping of transform ids to a set of the transform ids of children
978
# that their limbo directory has
979
self._limbo_children = {}
980
# Map transform ids to maps of child filename to child transform id
981
self._limbo_children_names = {}
982
# List of transform ids that need to be renamed from limbo into place
983
self._needs_rename = set()
986
"""Release the working tree lock, if held, clean up limbo dir.
988
This is required if apply has not been invoked, but can be invoked
991
if self._tree is None:
994
entries = [(self._limbo_name(t), t, k) for t, k in
995
self._new_contents.iteritems()]
996
entries.sort(reverse=True)
997
for path, trans_id, kind in entries:
998
if kind == "directory":
1003
os.rmdir(self._limbodir)
1005
# We don't especially care *why* the dir is immortal.
1006
raise ImmortalLimbo(self._limbodir)
1008
if self._deletiondir is not None:
1009
os.rmdir(self._deletiondir)
1011
raise errors.ImmortalPendingDeletion(self._deletiondir)
1013
TreeTransformBase.finalize(self)
1015
def _limbo_name(self, trans_id):
1016
"""Generate the limbo name of a file"""
1017
limbo_name = self._limbo_files.get(trans_id)
1018
if limbo_name is not None:
1020
parent = self._new_parent.get(trans_id)
1021
# if the parent directory is already in limbo (e.g. when building a
1022
# tree), choose a limbo name inside the parent, to reduce further
1024
use_direct_path = False
1025
if self._new_contents.get(parent) == 'directory':
1026
filename = self._new_name.get(trans_id)
1027
if filename is not None:
1028
if parent not in self._limbo_children:
1029
self._limbo_children[parent] = set()
1030
self._limbo_children_names[parent] = {}
1031
use_direct_path = True
1032
# the direct path can only be used if no other file has
1033
# already taken this pathname, i.e. if the name is unused, or
1034
# if it is already associated with this trans_id.
1035
elif self._case_sensitive_target:
1036
if (self._limbo_children_names[parent].get(filename)
1037
in (trans_id, None)):
1038
use_direct_path = True
1040
for l_filename, l_trans_id in\
1041
self._limbo_children_names[parent].iteritems():
1042
if l_trans_id == trans_id:
1044
if l_filename.lower() == filename.lower():
1047
use_direct_path = True
1050
limbo_name = pathjoin(self._limbo_files[parent], filename)
1051
self._limbo_children[parent].add(trans_id)
1052
self._limbo_children_names[parent][filename] = trans_id
1054
limbo_name = pathjoin(self._limbodir, trans_id)
1055
self._needs_rename.add(trans_id)
1056
self._limbo_files[trans_id] = limbo_name
1059
def adjust_path(self, name, parent, trans_id):
1060
previous_parent = self._new_parent.get(trans_id)
1061
previous_name = self._new_name.get(trans_id)
1062
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1063
if (trans_id in self._limbo_files and
1064
trans_id not in self._needs_rename):
1065
self._rename_in_limbo([trans_id])
1066
self._limbo_children[previous_parent].remove(trans_id)
1067
del self._limbo_children_names[previous_parent][previous_name]
1069
def _rename_in_limbo(self, trans_ids):
1070
"""Fix limbo names so that the right final path is produced.
1072
This means we outsmarted ourselves-- we tried to avoid renaming
1073
these files later by creating them with their final names in their
1074
final parents. But now the previous name or parent is no longer
1075
suitable, so we have to rename them.
1077
Even for trans_ids that have no new contents, we must remove their
1078
entries from _limbo_files, because they are now stale.
1080
for trans_id in trans_ids:
1081
old_path = self._limbo_files.pop(trans_id)
1082
if trans_id not in self._new_contents:
1084
new_path = self._limbo_name(trans_id)
1085
os.rename(old_path, new_path)
1087
def create_file(self, contents, trans_id, mode_id=None):
1088
"""Schedule creation of a new file.
1092
Contents is an iterator of strings, all of which will be written
1093
to the target destination.
1095
New file takes the permissions of any existing file with that id,
1096
unless mode_id is specified.
1098
name = self._limbo_name(trans_id)
1099
f = open(name, 'wb')
1102
unique_add(self._new_contents, trans_id, 'file')
1104
# Clean up the file, it never got registered so
1105
# TreeTransform.finalize() won't clean it up.
1110
f.writelines(contents)
1113
self._set_mode(trans_id, mode_id, S_ISREG)
1115
def _read_file_chunks(self, trans_id):
1116
cur_file = open(self._limbo_name(trans_id), 'rb')
1118
return cur_file.readlines()
1122
def _read_symlink_target(self, trans_id):
1123
return os.readlink(self._limbo_name(trans_id))
1125
def create_hardlink(self, path, trans_id):
1126
"""Schedule creation of a hard link"""
1127
name = self._limbo_name(trans_id)
1131
if e.errno != errno.EPERM:
1133
raise errors.HardLinkNotSupported(path)
1135
unique_add(self._new_contents, trans_id, 'file')
1137
# Clean up the file, it never got registered so
1138
# TreeTransform.finalize() won't clean it up.
1142
def create_directory(self, trans_id):
1143
"""Schedule creation of a new directory.
1145
See also new_directory.
1147
os.mkdir(self._limbo_name(trans_id))
1148
unique_add(self._new_contents, trans_id, 'directory')
1150
def create_symlink(self, target, trans_id):
1151
"""Schedule creation of a new symbolic link.
1153
target is a bytestring.
1154
See also new_symlink.
1157
os.symlink(target, self._limbo_name(trans_id))
1158
unique_add(self._new_contents, trans_id, 'symlink')
1161
path = FinalPaths(self).get_path(trans_id)
1164
raise UnableCreateSymlink(path=path)
1166
def cancel_creation(self, trans_id):
1167
"""Cancel the creation of new file contents."""
1168
del self._new_contents[trans_id]
1169
children = self._limbo_children.get(trans_id)
1170
# if this is a limbo directory with children, move them before removing
1172
if children is not None:
1173
self._rename_in_limbo(children)
1174
del self._limbo_children[trans_id]
1175
del self._limbo_children_names[trans_id]
1176
delete_any(self._limbo_name(trans_id))
1179
class TreeTransform(DiskTreeTransform):
1230
1180
"""Represent a tree transformation.
1232
1182
This object is designed to support incremental generation of the transform,
1321
TreeTransformBase.__init__(self, tree, limbodir, pb,
1271
# Cache of realpath results, to speed up canonical_path
1272
self._realpaths = {}
1273
# Cache of relpath results, to speed up canonical_path
1275
DiskTreeTransform.__init__(self, tree, limbodir, pb,
1322
1276
tree.case_sensitive)
1323
1277
self._deletiondir = deletiondir
1279
def canonical_path(self, path):
1280
"""Get the canonical tree-relative path"""
1281
# don't follow final symlinks
1282
abs = self._tree.abspath(path)
1283
if abs in self._relpaths:
1284
return self._relpaths[abs]
1285
dirname, basename = os.path.split(abs)
1286
if dirname not in self._realpaths:
1287
self._realpaths[dirname] = os.path.realpath(dirname)
1288
dirname = self._realpaths[dirname]
1289
abs = pathjoin(dirname, basename)
1290
if dirname in self._relpaths:
1291
relpath = pathjoin(self._relpaths[dirname], basename)
1292
relpath = relpath.rstrip('/\\')
1294
relpath = self._tree.relpath(abs)
1295
self._relpaths[abs] = relpath
1298
def tree_kind(self, trans_id):
1299
"""Determine the file kind in the working tree.
1301
Raises NoSuchFile if the file does not exist
1303
path = self._tree_id_paths.get(trans_id)
1305
raise NoSuchFile(None)
1307
return file_kind(self._tree.abspath(path))
1309
if e.errno != errno.ENOENT:
1312
raise NoSuchFile(path)
1314
def _set_mode(self, trans_id, mode_id, typefunc):
1315
"""Set the mode of new file contents.
1316
The mode_id is the existing file to get the mode from (often the same
1317
as trans_id). The operation is only performed if there's a mode match
1318
according to typefunc.
1323
old_path = self._tree_id_paths[mode_id]
1327
mode = os.stat(self._tree.abspath(old_path)).st_mode
1329
if e.errno in (errno.ENOENT, errno.ENOTDIR):
1330
# Either old_path doesn't exist, or the parent of the
1331
# target is not a directory (but will be one eventually)
1332
# Either way, we know it doesn't exist *right now*
1333
# See also bug #248448
1338
os.chmod(self._limbo_name(trans_id), mode)
1340
def iter_tree_children(self, parent_id):
1341
"""Iterate through the entry's tree children, if any"""
1343
path = self._tree_id_paths[parent_id]
1347
children = os.listdir(self._tree.abspath(path))
1349
if not (osutils._is_error_enotdir(e)
1350
or e.errno in (errno.ENOENT, errno.ESRCH)):
1354
for child in children:
1355
childpath = joinpath(path, child)
1356
if self._tree.is_control_filename(childpath):
1358
yield self.trans_id_tree_path(childpath)
1325
1361
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1326
1362
"""Apply all changes to the inventory and filesystem.