163
162
def adjust_path(self, name, parent, trans_id):
164
163
"""Change the path that is assigned to a transaction id."""
166
raise ValueError("Parent trans-id may not be None")
167
164
if trans_id == self._new_root:
168
165
raise CantMoveRoot
169
166
self._new_name[trans_id] = name
170
167
self._new_parent[trans_id] = parent
168
if parent == ROOT_PARENT:
169
if self._new_root is not None:
170
raise ValueError("Cannot have multiple roots.")
171
self._new_root = trans_id
172
173
def adjust_root_path(self, name, parent):
173
174
"""Emulate moving the root by moving all children, instead.
201
202
self.version_file(old_root_file_id, old_root)
202
203
self.unversion_file(self._new_root)
204
def fixup_new_roots(self):
205
"""Reinterpret requests to change the root directory
207
Instead of creating a root directory, or moving an existing directory,
208
all the attributes and children of the new root are applied to the
209
existing root directory.
211
This means that the old root trans-id becomes obsolete, so it is
212
recommended only to invoke this after the root trans-id has become
215
new_roots = [k for k, v in self._new_parent.iteritems() if v is
217
if len(new_roots) < 1:
219
if len(new_roots) != 1:
220
raise ValueError('A tree cannot have two roots!')
221
if self._new_root is None:
222
self._new_root = new_roots[0]
224
old_new_root = new_roots[0]
225
# TODO: What to do if a old_new_root is present, but self._new_root is
226
# not listed as being removed? This code explicitly unversions
227
# the old root and versions it with the new file_id. Though that
228
# seems like an incomplete delta
230
# unversion the new root's directory.
231
file_id = self.final_file_id(old_new_root)
232
if old_new_root in self._new_id:
233
self.cancel_versioning(old_new_root)
235
self.unversion_file(old_new_root)
236
# if, at this stage, root still has an old file_id, zap it so we can
237
# stick a new one in.
238
if (self.tree_file_id(self._new_root) is not None and
239
self._new_root not in self._removed_id):
240
self.unversion_file(self._new_root)
241
self.version_file(file_id, self._new_root)
243
# Now move children of new root into old root directory.
244
# Ensure all children are registered with the transaction, but don't
245
# use directly-- some tree children have new parents
246
list(self.iter_tree_children(old_new_root))
247
# Move all children of new root into old root directory.
248
for child in self.by_parent().get(old_new_root, []):
249
self.adjust_path(self.final_name(child), self._new_root, child)
251
# Ensure old_new_root has no directory.
252
if old_new_root in self._new_contents:
253
self.cancel_creation(old_new_root)
255
self.delete_contents(old_new_root)
257
# prevent deletion of root directory.
258
if self._new_root in self._removed_contents:
259
self.cancel_deletion(self._new_root)
261
# destroy path info for old_new_root.
262
del self._new_parent[old_new_root]
263
del self._new_name[old_new_root]
265
205
def trans_id_tree_file_id(self, inventory_id):
266
206
"""Determine the transaction id of a working tree file.
1117
1054
def _limbo_name(self, trans_id):
1118
1055
"""Generate the limbo name of a file"""
1119
1056
limbo_name = self._limbo_files.get(trans_id)
1120
if limbo_name is None:
1121
limbo_name = self._generate_limbo_path(trans_id)
1122
self._limbo_files[trans_id] = limbo_name
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
1123
1096
return limbo_name
1125
def _generate_limbo_path(self, trans_id):
1126
"""Generate a limbo path using the trans_id as the relative path.
1128
This is suitable as a fallback, and when the transform should not be
1129
sensitive to the path encoding of the limbo directory.
1131
self._needs_rename.add(trans_id)
1132
return pathjoin(self._limbodir, trans_id)
1134
1098
def adjust_path(self, name, parent, trans_id):
1135
1099
previous_parent = self._new_parent.get(trans_id)
1136
1100
previous_name = self._new_name.get(trans_id)
1138
1102
if (trans_id in self._limbo_files and
1139
1103
trans_id not in self._needs_rename):
1140
1104
self._rename_in_limbo([trans_id])
1141
if previous_parent != parent:
1142
self._limbo_children[previous_parent].remove(trans_id)
1143
if previous_parent != parent or previous_name != name:
1144
del self._limbo_children_names[previous_parent][previous_name]
1105
self._limbo_children[previous_parent].remove(trans_id)
1106
del self._limbo_children_names[previous_parent][previous_name]
1146
1108
def _rename_in_limbo(self, trans_ids):
1147
1109
"""Fix limbo names so that the right final path is produced.
1161
1123
new_path = self._limbo_name(trans_id)
1162
1124
os.rename(old_path, new_path)
1163
for descendant in self._limbo_descendants(trans_id):
1164
desc_path = self._limbo_files[descendant]
1165
desc_path = new_path + desc_path[len(old_path):]
1166
self._limbo_files[descendant] = desc_path
1168
def _limbo_descendants(self, trans_id):
1169
"""Return the set of trans_ids whose limbo paths descend from this."""
1170
descendants = set(self._limbo_children.get(trans_id, []))
1171
for descendant in list(descendants):
1172
descendants.update(self._limbo_descendants(descendant))
1175
1126
def create_file(self, contents, trans_id, mode_id=None):
1176
1127
"""Schedule creation of a new file.
1211
1161
def _read_symlink_target(self, trans_id):
1212
1162
return os.readlink(self._limbo_name(trans_id))
1214
def _set_mtime(self, path):
1215
"""All files that are created get the same mtime.
1217
This time is set by the first object to be created.
1219
if self._creation_mtime is None:
1220
self._creation_mtime = time.time()
1221
os.utime(path, (self._creation_mtime, self._creation_mtime))
1223
1164
def create_hardlink(self, path, trans_id):
1224
1165
"""Schedule creation of a hard link"""
1225
1166
name = self._limbo_name(trans_id)
1456
1397
yield self.trans_id_tree_path(childpath)
1458
def _generate_limbo_path(self, trans_id):
1459
"""Generate a limbo path using the final path if possible.
1461
This optimizes the performance of applying the tree transform by
1462
avoiding renames. These renames can be avoided only when the parent
1463
directory is already scheduled for creation.
1465
If the final path cannot be used, falls back to using the trans_id as
1468
parent = self._new_parent.get(trans_id)
1469
# if the parent directory is already in limbo (e.g. when building a
1470
# tree), choose a limbo name inside the parent, to reduce further
1472
use_direct_path = False
1473
if self._new_contents.get(parent) == 'directory':
1474
filename = self._new_name.get(trans_id)
1475
if filename is not None:
1476
if parent not in self._limbo_children:
1477
self._limbo_children[parent] = set()
1478
self._limbo_children_names[parent] = {}
1479
use_direct_path = True
1480
# the direct path can only be used if no other file has
1481
# already taken this pathname, i.e. if the name is unused, or
1482
# if it is already associated with this trans_id.
1483
elif self._case_sensitive_target:
1484
if (self._limbo_children_names[parent].get(filename)
1485
in (trans_id, None)):
1486
use_direct_path = True
1488
for l_filename, l_trans_id in\
1489
self._limbo_children_names[parent].iteritems():
1490
if l_trans_id == trans_id:
1492
if l_filename.lower() == filename.lower():
1495
use_direct_path = True
1497
if not use_direct_path:
1498
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1500
limbo_name = pathjoin(self._limbo_files[parent], filename)
1501
self._limbo_children[parent].add(trans_id)
1502
self._limbo_children_names[parent][filename] = trans_id
1506
1399
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1507
1400
"""Apply all changes to the inventory and filesystem.
1628
1521
child_pb.update('removing file', num, len(tree_paths))
1629
1522
full_path = self._tree.abspath(path)
1630
1523
if trans_id in self._removed_contents:
1631
delete_path = os.path.join(self._deletiondir, trans_id)
1632
mover.pre_delete(full_path, delete_path)
1633
elif (trans_id in self._new_name
1634
or trans_id in self._new_parent):
1524
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1526
elif trans_id in self._new_name or trans_id in \
1636
1529
mover.rename(full_path, self._limbo_name(trans_id))
1637
1530
except OSError, e:
1742
1635
self._all_children_cache = {}
1743
1636
self._path2trans_id_cache = {}
1744
1637
self._final_name_cache = {}
1745
self._iter_changes_cache = dict((c[0], c) for c in
1746
self._transform.iter_changes())
1639
def _changes(self, file_id):
1640
for changes in self._transform.iter_changes():
1641
if changes[0] == file_id:
1748
1644
def _content_change(self, file_id):
1749
1645
"""Return True if the content of this file changed"""
1750
changes = self._iter_changes_cache.get(file_id)
1646
changes = self._changes(file_id)
1751
1647
# changes[2] is true if the file content changed. See
1752
1648
# InterTree.iter_changes.
1753
1649
return (changes is not None and changes[2])
1988
1884
def get_file_mtime(self, file_id, path=None):
1989
1885
"""See Tree.get_file_mtime"""
1990
1886
if not self._content_change(file_id):
1991
return self._transform._tree.get_file_mtime(file_id)
1887
return self._transform._tree.get_file_mtime(file_id, path)
1992
1888
return self._stat_limbo_file(file_id).st_mtime
1994
1890
def _file_size(self, entry, stat_value):
2056
1952
executable = None
2057
1953
if kind == 'symlink':
2058
1954
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2059
executable = tt._new_executability.get(trans_id, executable)
1955
if supports_executable():
1956
executable = tt._new_executability.get(trans_id, executable)
2060
1957
return kind, size, executable, link_or_sha1
2062
1959
def iter_changes(self, from_tree, include_unchanged=False,
2375
2272
new_desired_files = desired_files
2377
2274
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2378
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2379
in iter if not (c or e[0] != e[1])]
2380
if accelerator_tree.supports_content_filtering():
2381
unchanged = [(f, p) for (f, p) in unchanged
2382
if not accelerator_tree.iter_search_rules([p]).next()]
2383
unchanged = dict(unchanged)
2275
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2276
in iter if not (c or e[0] != e[1]))
2384
2277
new_desired_files = []
2386
2279
for file_id, (trans_id, tree_path) in desired_files:
2509
2402
tt.create_directory(trans_id)
2512
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2513
filter_tree_path=None):
2514
"""Create new file contents according to tree contents.
2516
:param filter_tree_path: the tree path to use to lookup
2517
content filters to apply to the bytes output in the working tree.
2518
This only applies if the working tree supports content filtering.
2405
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2406
"""Create new file contents according to tree contents."""
2520
2407
kind = tree.kind(file_id)
2521
2408
if kind == 'directory':
2522
2409
tt.create_directory(trans_id)
2527
2414
bytes = tree_file.readlines()
2529
2416
tree_file.close()
2531
if wt.supports_content_filtering() and filter_tree_path is not None:
2532
filters = wt._content_filter_stack(filter_tree_path)
2533
bytes = filtered_output_bytes(bytes, filters,
2534
ContentFilterContext(filter_tree_path, tree))
2535
2417
tt.create_file(bytes, trans_id)
2536
2418
elif kind == "symlink":
2537
2419
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2725
2607
parent_trans = ROOT_PARENT
2727
2609
parent_trans = tt.trans_id_file_id(parent[1])
2728
if parent[0] is None and versioned[0]:
2729
tt.adjust_root_path(name[1], parent_trans)
2731
tt.adjust_path(name[1], parent_trans, trans_id)
2610
tt.adjust_path(name[1], parent_trans, trans_id)
2732
2611
if executable[0] != executable[1] and kind[1] == "file":
2733
2612
tt.set_executability(executable[1], trans_id)
2734
if working_tree.supports_content_filtering():
2735
for index, ((trans_id, mode_id), bytes) in enumerate(
2736
target_tree.iter_files_bytes(deferred_files)):
2737
file_id = deferred_files[index][0]
2738
# We're reverting a tree to the target tree so using the
2739
# target tree to find the file path seems the best choice
2740
# here IMO - Ian C 27/Oct/2009
2741
filter_tree_path = target_tree.id2path(file_id)
2742
filters = working_tree._content_filter_stack(filter_tree_path)
2743
bytes = filtered_output_bytes(bytes, filters,
2744
ContentFilterContext(filter_tree_path, working_tree))
2745
tt.create_file(bytes, trans_id, mode_id)
2747
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2749
tt.create_file(bytes, trans_id, mode_id)
2750
tt.fixup_new_roots()
2613
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2615
tt.create_file(bytes, trans_id, mode_id)
2752
2617
if basis_tree is not None:
2753
2618
basis_tree.unlock()