200
163
"""Change the path that is assigned to a transaction id."""
201
164
if trans_id == self._new_root:
202
165
raise CantMoveRoot
203
previous_parent = self._new_parent.get(trans_id)
204
previous_name = self._new_name.get(trans_id)
205
166
self._new_name[trans_id] = name
206
167
self._new_parent[trans_id] = parent
207
168
if parent == ROOT_PARENT:
208
169
if self._new_root is not None:
209
170
raise ValueError("Cannot have multiple roots.")
210
171
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
173
def adjust_root_path(self, name, parent):
236
174
"""Emulate moving the root by moving all children, instead.
332
251
return ROOT_PARENT
333
252
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
254
def delete_contents(self, trans_id):
443
255
"""Schedule the contents of a path entry for deletion"""
444
256
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
646
def _set_executability(self, path, trans_id):
915
647
"""Set the executability of versioned files """
916
648
if supports_executable():
1227
955
self.create_symlink(content.decode('utf-8'), trans_id)
1230
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):
1231
1180
"""Represent a tree transformation.
1233
1182
This object is designed to support incremental generation of the transform,
1322
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,
1323
1276
tree.case_sensitive)
1324
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)
1326
1361
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1327
1362
"""Apply all changes to the inventory and filesystem.