163
200
"""Change the path that is assigned to a transaction id."""
164
201
if trans_id == self._new_root:
165
202
raise CantMoveRoot
203
previous_parent = self._new_parent.get(trans_id)
204
previous_name = self._new_name.get(trans_id)
166
205
self._new_name[trans_id] = name
167
206
self._new_parent[trans_id] = parent
168
207
if parent == ROOT_PARENT:
169
208
if self._new_root is not None:
170
209
raise ValueError("Cannot have multiple roots.")
171
210
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)
173
235
def adjust_root_path(self, name, parent):
174
236
"""Emulate moving the root by moving all children, instead.
251
332
return ROOT_PARENT
252
333
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))
254
442
def delete_contents(self, trans_id):
255
443
"""Schedule the contents of a path entry for deletion"""
256
444
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
651
914
def _set_executability(self, path, trans_id):
652
915
"""Set the executability of versioned files """
653
916
if supports_executable():
865
1128
return _PreviewTree(self)
867
def commit(self, branch, message, merge_parents=None, strict=False):
868
"""Commit the result of this TreeTransform to a branch.
870
:param branch: The branch to commit to.
871
:param message: The message to attach to the commit.
872
:param merge_parents: Additional parents specified by pending merges.
873
:return: The revision_id of the revision committed.
875
self._check_malformed()
877
unversioned = set(self._new_contents).difference(set(self._new_id))
878
for trans_id in unversioned:
879
if self.final_file_id(trans_id) is None:
880
raise errors.StrictCommitFailed()
882
revno, last_rev_id = branch.last_revision_info()
883
if last_rev_id == _mod_revision.NULL_REVISION:
884
if merge_parents is not None:
885
raise ValueError('Cannot supply merge parents for first'
889
parent_ids = [last_rev_id]
890
if merge_parents is not None:
891
parent_ids.extend(merge_parents)
892
if self._tree.get_revision_id() != last_rev_id:
893
raise ValueError('TreeTransform not based on branch basis: %s' %
894
self._tree.get_revision_id())
895
builder = branch.get_commit_builder(parent_ids)
896
preview = self.get_preview_tree()
897
list(builder.record_iter_changes(preview, last_rev_id,
898
self.iter_changes()))
899
builder.finish_inventory()
900
revision_id = builder.commit(message)
901
branch.set_last_revision_info(revno + 1, revision_id)
904
1130
def _text_parent(self, trans_id):
905
1131
file_id = self.tree_file_id(trans_id)
997
1227
self.create_symlink(content.decode('utf-8'), trans_id)
1000
class DiskTreeTransform(TreeTransformBase):
1001
"""Tree transform storing its contents on disk."""
1003
def __init__(self, tree, limbodir, pb=DummyProgress(),
1004
case_sensitive=True):
1006
:param tree: The tree that will be transformed, but not necessarily
1008
:param limbodir: A directory where new files can be stored until
1009
they are installed in their proper places
1010
:param pb: A ProgressBar indicating how much progress is being made
1011
:param case_sensitive: If True, the target of the transform is
1012
case sensitive, not just case preserving.
1014
TreeTransformBase.__init__(self, tree, pb, case_sensitive)
1015
self._limbodir = limbodir
1016
self._deletiondir = None
1017
# A mapping of transform ids to their limbo filename
1018
self._limbo_files = {}
1019
# A mapping of transform ids to a set of the transform ids of children
1020
# that their limbo directory has
1021
self._limbo_children = {}
1022
# Map transform ids to maps of child filename to child transform id
1023
self._limbo_children_names = {}
1024
# List of transform ids that need to be renamed from limbo into place
1025
self._needs_rename = set()
1028
"""Release the working tree lock, if held, clean up limbo dir.
1030
This is required if apply has not been invoked, but can be invoked
1033
if self._tree is None:
1036
entries = [(self._limbo_name(t), t, k) for t, k in
1037
self._new_contents.iteritems()]
1038
entries.sort(reverse=True)
1039
for path, trans_id, kind in entries:
1042
delete_any(self._limbodir)
1044
# We don't especially care *why* the dir is immortal.
1045
raise ImmortalLimbo(self._limbodir)
1047
if self._deletiondir is not None:
1048
delete_any(self._deletiondir)
1050
raise errors.ImmortalPendingDeletion(self._deletiondir)
1052
TreeTransformBase.finalize(self)
1054
def _limbo_name(self, trans_id):
1055
"""Generate the limbo name of a file"""
1056
limbo_name = self._limbo_files.get(trans_id)
1057
if limbo_name is not None:
1059
parent = self._new_parent.get(trans_id)
1060
# if the parent directory is already in limbo (e.g. when building a
1061
# tree), choose a limbo name inside the parent, to reduce further
1063
use_direct_path = False
1064
if self._new_contents.get(parent) == 'directory':
1065
filename = self._new_name.get(trans_id)
1066
if filename is not None:
1067
if parent not in self._limbo_children:
1068
self._limbo_children[parent] = set()
1069
self._limbo_children_names[parent] = {}
1070
use_direct_path = True
1071
# the direct path can only be used if no other file has
1072
# already taken this pathname, i.e. if the name is unused, or
1073
# if it is already associated with this trans_id.
1074
elif self._case_sensitive_target:
1075
if (self._limbo_children_names[parent].get(filename)
1076
in (trans_id, None)):
1077
use_direct_path = True
1079
for l_filename, l_trans_id in\
1080
self._limbo_children_names[parent].iteritems():
1081
if l_trans_id == trans_id:
1083
if l_filename.lower() == filename.lower():
1086
use_direct_path = True
1089
limbo_name = pathjoin(self._limbo_files[parent], filename)
1090
self._limbo_children[parent].add(trans_id)
1091
self._limbo_children_names[parent][filename] = trans_id
1093
limbo_name = pathjoin(self._limbodir, trans_id)
1094
self._needs_rename.add(trans_id)
1095
self._limbo_files[trans_id] = limbo_name
1098
def adjust_path(self, name, parent, trans_id):
1099
previous_parent = self._new_parent.get(trans_id)
1100
previous_name = self._new_name.get(trans_id)
1101
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1102
if (trans_id in self._limbo_files and
1103
trans_id not in self._needs_rename):
1104
self._rename_in_limbo([trans_id])
1105
self._limbo_children[previous_parent].remove(trans_id)
1106
del self._limbo_children_names[previous_parent][previous_name]
1108
def _rename_in_limbo(self, trans_ids):
1109
"""Fix limbo names so that the right final path is produced.
1111
This means we outsmarted ourselves-- we tried to avoid renaming
1112
these files later by creating them with their final names in their
1113
final parents. But now the previous name or parent is no longer
1114
suitable, so we have to rename them.
1116
Even for trans_ids that have no new contents, we must remove their
1117
entries from _limbo_files, because they are now stale.
1119
for trans_id in trans_ids:
1120
old_path = self._limbo_files.pop(trans_id)
1121
if trans_id not in self._new_contents:
1123
new_path = self._limbo_name(trans_id)
1124
os.rename(old_path, new_path)
1126
def create_file(self, contents, trans_id, mode_id=None):
1127
"""Schedule creation of a new file.
1131
Contents is an iterator of strings, all of which will be written
1132
to the target destination.
1134
New file takes the permissions of any existing file with that id,
1135
unless mode_id is specified.
1137
name = self._limbo_name(trans_id)
1138
f = open(name, 'wb')
1141
unique_add(self._new_contents, trans_id, 'file')
1143
# Clean up the file, it never got registered so
1144
# TreeTransform.finalize() won't clean it up.
1149
f.writelines(contents)
1152
self._set_mode(trans_id, mode_id, S_ISREG)
1154
def _read_file_chunks(self, trans_id):
1155
cur_file = open(self._limbo_name(trans_id), 'rb')
1157
return cur_file.readlines()
1161
def _read_symlink_target(self, trans_id):
1162
return os.readlink(self._limbo_name(trans_id))
1164
def create_hardlink(self, path, trans_id):
1165
"""Schedule creation of a hard link"""
1166
name = self._limbo_name(trans_id)
1170
if e.errno != errno.EPERM:
1172
raise errors.HardLinkNotSupported(path)
1174
unique_add(self._new_contents, trans_id, 'file')
1176
# Clean up the file, it never got registered so
1177
# TreeTransform.finalize() won't clean it up.
1181
def create_directory(self, trans_id):
1182
"""Schedule creation of a new directory.
1184
See also new_directory.
1186
os.mkdir(self._limbo_name(trans_id))
1187
unique_add(self._new_contents, trans_id, 'directory')
1189
def create_symlink(self, target, trans_id):
1190
"""Schedule creation of a new symbolic link.
1192
target is a bytestring.
1193
See also new_symlink.
1196
os.symlink(target, self._limbo_name(trans_id))
1197
unique_add(self._new_contents, trans_id, 'symlink')
1200
path = FinalPaths(self).get_path(trans_id)
1203
raise UnableCreateSymlink(path=path)
1205
def cancel_creation(self, trans_id):
1206
"""Cancel the creation of new file contents."""
1207
del self._new_contents[trans_id]
1208
children = self._limbo_children.get(trans_id)
1209
# if this is a limbo directory with children, move them before removing
1211
if children is not None:
1212
self._rename_in_limbo(children)
1213
del self._limbo_children[trans_id]
1214
del self._limbo_children_names[trans_id]
1215
delete_any(self._limbo_name(trans_id))
1218
class TreeTransform(DiskTreeTransform):
1230
class TreeTransform(TreeTransformBase):
1219
1231
"""Represent a tree transformation.
1221
1233
This object is designed to support incremental generation of the transform,
1310
# Cache of realpath results, to speed up canonical_path
1311
self._realpaths = {}
1312
# Cache of relpath results, to speed up canonical_path
1314
DiskTreeTransform.__init__(self, tree, limbodir, pb,
1322
TreeTransformBase.__init__(self, tree, limbodir, pb,
1315
1323
tree.case_sensitive)
1316
1324
self._deletiondir = deletiondir
1318
def canonical_path(self, path):
1319
"""Get the canonical tree-relative path"""
1320
# don't follow final symlinks
1321
abs = self._tree.abspath(path)
1322
if abs in self._relpaths:
1323
return self._relpaths[abs]
1324
dirname, basename = os.path.split(abs)
1325
if dirname not in self._realpaths:
1326
self._realpaths[dirname] = os.path.realpath(dirname)
1327
dirname = self._realpaths[dirname]
1328
abs = pathjoin(dirname, basename)
1329
if dirname in self._relpaths:
1330
relpath = pathjoin(self._relpaths[dirname], basename)
1331
relpath = relpath.rstrip('/\\')
1333
relpath = self._tree.relpath(abs)
1334
self._relpaths[abs] = relpath
1337
def tree_kind(self, trans_id):
1338
"""Determine the file kind in the working tree.
1340
Raises NoSuchFile if the file does not exist
1342
path = self._tree_id_paths.get(trans_id)
1344
raise NoSuchFile(None)
1346
return file_kind(self._tree.abspath(path))
1348
if e.errno != errno.ENOENT:
1351
raise NoSuchFile(path)
1353
def _set_mode(self, trans_id, mode_id, typefunc):
1354
"""Set the mode of new file contents.
1355
The mode_id is the existing file to get the mode from (often the same
1356
as trans_id). The operation is only performed if there's a mode match
1357
according to typefunc.
1362
old_path = self._tree_id_paths[mode_id]
1366
mode = os.stat(self._tree.abspath(old_path)).st_mode
1368
if e.errno in (errno.ENOENT, errno.ENOTDIR):
1369
# Either old_path doesn't exist, or the parent of the
1370
# target is not a directory (but will be one eventually)
1371
# Either way, we know it doesn't exist *right now*
1372
# See also bug #248448
1377
os.chmod(self._limbo_name(trans_id), mode)
1379
def iter_tree_children(self, parent_id):
1380
"""Iterate through the entry's tree children, if any"""
1382
path = self._tree_id_paths[parent_id]
1386
children = os.listdir(self._tree.abspath(path))
1388
if not (osutils._is_error_enotdir(e)
1389
or e.errno in (errno.ENOENT, errno.ESRCH)):
1393
for child in children:
1394
childpath = joinpath(path, child)
1395
if self._tree.is_control_filename(childpath):
1397
yield self.trans_id_tree_path(childpath)
1399
1326
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1400
1327
"""Apply all changes to the inventory and filesystem.
1833
1756
specific_file_ids):
1834
1757
yield unicode(self._final_paths.get_path(trans_id)), entry
1836
def _iter_entries_for_dir(self, dir_path):
1837
"""Return path, entry for items in a directory without recursing down."""
1838
dir_file_id = self.path2id(dir_path)
1840
for file_id in self.iter_children(dir_file_id):
1841
trans_id = self._transform.trans_id_file_id(file_id)
1842
ordered_ids.append((trans_id, file_id))
1843
for entry, trans_id in self._make_inv_entries(ordered_ids):
1844
yield unicode(self._final_paths.get_path(trans_id)), entry
1846
def list_files(self, include_root=False, from_dir=None, recursive=True):
1847
"""See WorkingTree.list_files."""
1759
def list_files(self, include_root=False):
1760
"""See Tree.list_files."""
1848
1761
# XXX This should behave like WorkingTree.list_files, but is really
1849
1762
# more like RevisionTree.list_files.
1853
prefix = from_dir + '/'
1854
entries = self.iter_entries_by_dir()
1855
for path, entry in entries:
1856
if entry.name == '' and not include_root:
1859
if not path.startswith(prefix):
1861
path = path[len(prefix):]
1862
yield path, 'V', entry.kind, entry.file_id, entry
1864
if from_dir is None and include_root is True:
1865
root_entry = inventory.make_entry('directory', '',
1866
ROOT_PARENT, self.get_root_id())
1867
yield '', 'V', 'directory', root_entry.file_id, root_entry
1868
entries = self._iter_entries_for_dir(from_dir or '')
1869
for path, entry in entries:
1870
yield path, 'V', entry.kind, entry.file_id, entry
1763
for path, entry in self.iter_entries_by_dir():
1764
if entry.name == '' and not include_root:
1766
yield path, 'V', entry.kind, entry.file_id, entry
1872
1768
def kind(self, file_id):
1873
1769
trans_id = self._transform.trans_id_file_id(file_id)