267
281
a transaction has been unversioned, it is deliberately still returned.
268
282
(this will likely lead to an unversioned parent conflict.)
285
raise ValueError('None is not a valid file id')
270
286
if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
271
287
return self._r_new_id[file_id]
272
elif file_id in self._tree.inventory:
273
return self.trans_id_tree_file_id(file_id)
274
elif file_id in self._non_present_ids:
275
return self._non_present_ids[file_id]
277
trans_id = self._assign_id()
278
self._non_present_ids[file_id] = trans_id
290
self._tree.iter_entries_by_dir([file_id]).next()
291
except StopIteration:
292
if file_id in self._non_present_ids:
293
return self._non_present_ids[file_id]
295
trans_id = self._assign_id()
296
self._non_present_ids[file_id] = trans_id
299
return self.trans_id_tree_file_id(file_id)
281
301
def canonical_path(self, path):
282
302
"""Get the canonical tree-relative path"""
479
504
new_ids.update(id_set)
480
505
return sorted(FinalPaths(self).get_paths(new_ids))
507
def _inventory_altered(self):
508
"""Get the trans_ids and paths of files needing new inv entries."""
510
for id_set in [self._new_name, self._new_parent, self._new_id,
511
self._new_executability]:
512
new_ids.update(id_set)
513
changed_kind = set(self._removed_contents)
514
changed_kind.intersection_update(self._new_contents)
515
changed_kind.difference_update(new_ids)
516
changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
518
new_ids.update(changed_kind)
519
return sorted(FinalPaths(self).get_paths(new_ids))
482
521
def tree_kind(self, trans_id):
483
522
"""Determine the file kind in the working tree.
1090
1128
return _PreviewTree(self)
1130
def _text_parent(self, trans_id):
1131
file_id = self.tree_file_id(trans_id)
1133
if file_id is None or self._tree.kind(file_id) != 'file':
1135
except errors.NoSuchFile:
1139
def _get_parents_texts(self, trans_id):
1140
"""Get texts for compression parents of this file."""
1141
file_id = self._text_parent(trans_id)
1144
return (self._tree.get_file_text(file_id),)
1146
def _get_parents_lines(self, trans_id):
1147
"""Get lines for compression parents of this file."""
1148
file_id = self._text_parent(trans_id)
1151
return (self._tree.get_file_lines(file_id),)
1153
def serialize(self, serializer):
1154
"""Serialize this TreeTransform.
1156
:param serializer: A Serialiser like pack.ContainerSerializer.
1158
new_name = dict((k, v.encode('utf-8')) for k, v in
1159
self._new_name.items())
1160
new_executability = dict((k, int(v)) for k, v in
1161
self._new_executability.items())
1162
tree_path_ids = dict((k.encode('utf-8'), v)
1163
for k, v in self._tree_path_ids.items())
1165
'_id_number': self._id_number,
1166
'_new_name': new_name,
1167
'_new_parent': self._new_parent,
1168
'_new_executability': new_executability,
1169
'_new_id': self._new_id,
1170
'_tree_path_ids': tree_path_ids,
1171
'_removed_id': list(self._removed_id),
1172
'_removed_contents': list(self._removed_contents),
1173
'_non_present_ids': self._non_present_ids,
1175
yield serializer.bytes_record(bencode.bencode(attribs),
1177
for trans_id, kind in self._new_contents.items():
1179
cur_file = open(self._limbo_name(trans_id), 'rb')
1181
lines = osutils.chunks_to_lines(cur_file.readlines())
1184
parents = self._get_parents_lines(trans_id)
1185
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1186
content = ''.join(mpdiff.to_patch())
1187
if kind == 'directory':
1189
if kind == 'symlink':
1190
content = os.readlink(self._limbo_name(trans_id))
1191
yield serializer.bytes_record(content, ((trans_id, kind),))
1194
def deserialize(self, records):
1195
"""Deserialize a stored TreeTransform.
1197
:param records: An iterable of (names, content) tuples, as per
1198
pack.ContainerPushParser.
1200
names, content = records.next()
1201
attribs = bencode.bdecode(content)
1202
self._id_number = attribs['_id_number']
1203
self._new_name = dict((k, v.decode('utf-8'))
1204
for k, v in attribs['_new_name'].items())
1205
self._new_parent = attribs['_new_parent']
1206
self._new_executability = dict((k, bool(v)) for k, v in
1207
attribs['_new_executability'].items())
1208
self._new_id = attribs['_new_id']
1209
self._r_new_id = dict((v, k) for k, v in self._new_id.items())
1210
self._tree_path_ids = {}
1211
self._tree_id_paths = {}
1212
for bytepath, trans_id in attribs['_tree_path_ids'].items():
1213
path = bytepath.decode('utf-8')
1214
self._tree_path_ids[path] = trans_id
1215
self._tree_id_paths[trans_id] = path
1216
self._removed_id = set(attribs['_removed_id'])
1217
self._removed_contents = set(attribs['_removed_contents'])
1218
self._non_present_ids = attribs['_non_present_ids']
1219
for ((trans_id, kind),), content in records:
1221
mpdiff = multiparent.MultiParent.from_patch(content)
1222
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1223
self.create_file(lines, trans_id)
1224
if kind == 'directory':
1225
self.create_directory(trans_id)
1226
if kind == 'symlink':
1227
self.create_symlink(content.decode('utf-8'), trans_id)
1093
1230
class TreeTransform(TreeTransformBase):
1094
1231
"""Represent a tree transformation.
1204
1341
conflicts = self.find_conflicts()
1205
1342
if len(conflicts) != 0:
1206
1343
raise MalformedTransform(conflicts=conflicts)
1207
if precomputed_delta is None:
1208
new_inventory_delta = []
1209
inventory_delta = new_inventory_delta
1211
new_inventory_delta = None
1212
inventory_delta = precomputed_delta
1213
1344
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1346
if precomputed_delta is None:
1347
child_pb.update('Apply phase', 0, 2)
1348
inventory_delta = self._generate_inventory_delta()
1351
inventory_delta = precomputed_delta
1215
1353
if _mover is None:
1216
1354
mover = _FileMover()
1220
child_pb.update('Apply phase', 0, 2)
1221
self._apply_removals(new_inventory_delta, mover)
1222
child_pb.update('Apply phase', 1, 2)
1223
modified_paths = self._apply_insertions(new_inventory_delta,
1358
child_pb.update('Apply phase', 0 + offset, 2 + offset)
1359
self._apply_removals(mover)
1360
child_pb.update('Apply phase', 1 + offset, 2 + offset)
1361
modified_paths = self._apply_insertions(mover)
1226
1363
mover.rollback()
1234
1371
self.finalize()
1235
1372
return _TransformResults(modified_paths, self.rename_count)
1237
def _apply_removals(self, inventory_delta, mover):
1374
def _generate_inventory_delta(self):
1375
"""Generate an inventory delta for the current transform."""
1376
inventory_delta = []
1377
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1378
new_paths = self._inventory_altered()
1379
total_entries = len(new_paths) + len(self._removed_id)
1381
for num, trans_id in enumerate(self._removed_id):
1383
child_pb.update('removing file', num, total_entries)
1384
if trans_id == self._new_root:
1385
file_id = self._tree.get_root_id()
1387
file_id = self.tree_file_id(trans_id)
1388
# File-id isn't really being deleted, just moved
1389
if file_id in self._r_new_id:
1391
path = self._tree_id_paths[trans_id]
1392
inventory_delta.append((path, None, file_id, None))
1393
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1395
entries = self._tree.iter_entries_by_dir(
1396
new_path_file_ids.values())
1397
old_paths = dict((e.file_id, p) for p, e in entries)
1399
for num, (path, trans_id) in enumerate(new_paths):
1401
child_pb.update('adding file',
1402
num + len(self._removed_id), total_entries)
1403
file_id = new_path_file_ids[trans_id]
1408
kind = self.final_kind(trans_id)
1410
kind = self._tree.stored_kind(file_id)
1411
parent_trans_id = self.final_parent(trans_id)
1412
parent_file_id = new_path_file_ids.get(parent_trans_id)
1413
if parent_file_id is None:
1414
parent_file_id = self.final_file_id(parent_trans_id)
1415
if trans_id in self._new_reference_revision:
1416
new_entry = inventory.TreeReference(
1418
self._new_name[trans_id],
1419
self.final_file_id(self._new_parent[trans_id]),
1420
None, self._new_reference_revision[trans_id])
1422
new_entry = inventory.make_entry(kind,
1423
self.final_name(trans_id),
1424
parent_file_id, file_id)
1425
old_path = old_paths.get(new_entry.file_id)
1426
new_executability = self._new_executability.get(trans_id)
1427
if new_executability is not None:
1428
new_entry.executable = new_executability
1429
inventory_delta.append(
1430
(old_path, path, new_entry.file_id, new_entry))
1433
return inventory_delta
1435
def _apply_removals(self, mover):
1238
1436
"""Perform tree operations that remove directory/inventory names.
1240
1438
That is, delete files that are to be deleted, and put any files that
1265
1463
self.rename_count += 1
1266
if (trans_id in self._removed_id
1267
and inventory_delta is not None):
1268
if trans_id == self._new_root:
1269
file_id = self._tree.get_root_id()
1271
file_id = self.tree_file_id(trans_id)
1272
# File-id isn't really being deleted, just moved
1273
if file_id in self._r_new_id:
1275
inventory_delta.append((path, None, file_id, None))
1277
1465
child_pb.finished()
1279
def _apply_insertions(self, inventory_delta, mover):
1467
def _apply_insertions(self, mover):
1280
1468
"""Perform tree operations that insert directory/inventory names.
1282
1470
That is, create any files that need to be created, and restore from
1286
1474
If inventory_delta is None, no inventory delta is calculated, and
1287
1475
no list of modified paths is returned.
1289
new_paths = self.new_paths(filesystem_only=(inventory_delta is None))
1477
new_paths = self.new_paths(filesystem_only=True)
1290
1478
modified_paths = []
1292
1479
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1294
if inventory_delta is not None:
1295
entries = self._tree.iter_entries_by_dir(
1296
new_path_file_ids.values())
1297
old_paths = dict((e.file_id, p) for p, e in entries)
1298
1481
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1300
1483
for num, (path, trans_id) in enumerate(new_paths):
1302
1484
if (num % 10) == 0:
1303
1485
child_pb.update('adding file', num, len(new_paths))
1304
1486
full_path = self._tree.abspath(path)
1313
1495
self.rename_count += 1
1314
if inventory_delta is not None:
1315
if (trans_id in self._new_contents or
1316
self.path_changed(trans_id)):
1317
if trans_id in self._new_contents:
1318
modified_paths.append(full_path)
1319
completed_new.append(trans_id)
1320
file_id = new_path_file_ids[trans_id]
1321
if file_id is not None and (trans_id in self._new_id or
1322
trans_id in self._new_name or
1323
trans_id in self._new_parent
1324
or trans_id in self._new_executability):
1326
kind = self.final_kind(trans_id)
1328
kind = self._tree.stored_kind(file_id)
1329
parent_trans_id = self.final_parent(trans_id)
1330
parent_file_id = new_path_file_ids.get(parent_trans_id)
1331
if parent_file_id is None:
1332
parent_file_id = self.final_file_id(
1334
if trans_id in self._new_reference_revision:
1335
new_entry = inventory.TreeReference(
1337
self._new_name[trans_id],
1338
self.final_file_id(self._new_parent[trans_id]),
1339
None, self._new_reference_revision[trans_id])
1341
new_entry = inventory.make_entry(kind,
1342
self.final_name(trans_id),
1343
parent_file_id, file_id)
1344
old_path = old_paths.get(new_entry.file_id)
1345
inventory_delta.append(
1346
(old_path, path, new_entry.file_id, new_entry))
1496
if (trans_id in self._new_contents or
1497
self.path_changed(trans_id)):
1498
if trans_id in self._new_contents:
1499
modified_paths.append(full_path)
1348
1500
if trans_id in self._new_executability:
1349
self._set_executability(path, new_entry, trans_id)
1501
self._set_executability(path, trans_id)
1351
1503
child_pb.finished()
1352
if inventory_delta is None:
1353
self._new_contents.clear()
1355
for trans_id in completed_new:
1356
del self._new_contents[trans_id]
1504
self._new_contents.clear()
1357
1505
return modified_paths
1485
1647
def __iter__(self):
1486
1648
return iter(self.all_file_ids())
1488
def paths2ids(self, specific_files, trees=None, require_versioned=False):
1489
"""See Tree.paths2ids"""
1490
to_find = set(specific_files)
1492
for (file_id, paths, changed, versioned, parent, name, kind,
1493
executable) in self._transform.iter_changes():
1494
if paths[1] in to_find:
1495
result.append(file_id)
1496
to_find.remove(paths[1])
1497
result.update(self._transform._tree.paths2ids(to_find,
1498
trees=[], require_versioned=require_versioned))
1650
def has_id(self, file_id):
1651
if file_id in self._transform._r_new_id:
1653
elif file_id in self._transform._removed_id:
1656
return self._transform._tree.has_id(file_id)
1501
1658
def _path2trans_id(self, path):
1659
# We must not use None here, because that is a valid value to store.
1660
trans_id = self._path2trans_id_cache.get(path, object)
1661
if trans_id is not object:
1502
1663
segments = splitpath(path)
1503
1664
cur_parent = self._transform.root
1504
1665
for cur_segment in segments:
1505
1666
for child in self._all_children(cur_parent):
1506
if self._transform.final_name(child) == cur_segment:
1667
final_name = self._final_name_cache.get(child)
1668
if final_name is None:
1669
final_name = self._transform.final_name(child)
1670
self._final_name_cache[child] = final_name
1671
if final_name == cur_segment:
1507
1672
cur_parent = child
1675
self._path2trans_id_cache[path] = None
1677
self._path2trans_id_cache[path] = cur_parent
1511
1678
return cur_parent
1513
1680
def path2id(self, path):
1521
1688
raise errors.NoSuchId(self, file_id)
1523
1690
def _all_children(self, trans_id):
1691
children = self._all_children_cache.get(trans_id)
1692
if children is not None:
1524
1694
children = set(self._transform.iter_tree_children(trans_id))
1525
1695
# children in the _new_parent set are provided by _by_parent.
1526
1696
children.difference_update(self._transform._new_parent.keys())
1527
1697
children.update(self._by_parent.get(trans_id, []))
1698
self._all_children_cache[trans_id] = children
1528
1699
return children
1701
def iter_children(self, file_id):
1702
trans_id = self._transform.trans_id_file_id(file_id)
1703
for child_trans_id in self._all_children(trans_id):
1704
yield self._transform.final_file_id(child_trans_id)
1707
possible_extras = set(self._transform.trans_id_tree_path(p) for p
1708
in self._transform._tree.extras())
1709
possible_extras.update(self._transform._new_contents)
1710
possible_extras.update(self._transform._removed_id)
1711
for trans_id in possible_extras:
1712
if self._transform.final_file_id(trans_id) is None:
1713
yield self._final_paths._determine_path(trans_id)
1530
1715
def _make_inv_entries(self, ordered_entries, specific_file_ids):
1531
1716
for trans_id, parent_file_id in ordered_entries:
1532
1717
file_id = self._transform.final_file_id(trans_id)
1562
1742
todo.extend(reversed(children))
1563
1743
for trans_id in children:
1564
1744
ordered_ids.append((trans_id, parent_file_id))
1747
def iter_entries_by_dir(self, specific_file_ids=None):
1748
# This may not be a maximally efficient implementation, but it is
1749
# reasonably straightforward. An implementation that grafts the
1750
# TreeTransform changes onto the tree's iter_entries_by_dir results
1751
# might be more efficient, but requires tricky inferences about stack
1753
ordered_ids = self._list_files_by_dir()
1565
1754
for entry, trans_id in self._make_inv_entries(ordered_ids,
1566
1755
specific_file_ids):
1567
1756
yield unicode(self._final_paths.get_path(trans_id)), entry
1758
def list_files(self, include_root=False):
1759
"""See Tree.list_files."""
1760
# XXX This should behave like WorkingTree.list_files, but is really
1761
# more like RevisionTree.list_files.
1762
for path, entry in self.iter_entries_by_dir():
1763
if entry.name == '' and not include_root:
1765
yield path, 'V', entry.kind, entry.file_id, entry
1569
1767
def kind(self, file_id):
1570
1768
trans_id = self._transform.trans_id_file_id(file_id)
1571
1769
return self._transform.final_kind(trans_id)
1593
1794
def get_file_sha1(self, file_id, path=None, stat_value=None):
1594
return self._transform._tree.get_file_sha1(file_id)
1795
trans_id = self._transform.trans_id_file_id(file_id)
1796
kind = self._transform._new_contents.get(trans_id)
1798
return self._transform._tree.get_file_sha1(file_id)
1800
fileobj = self.get_file(file_id)
1802
return sha_file(fileobj)
1596
1806
def is_executable(self, file_id, path=None):
1597
1809
trans_id = self._transform.trans_id_file_id(file_id)
1599
1811
return self._transform._new_executability[trans_id]
1600
1812
except KeyError:
1601
return self._transform._tree.is_executable(file_id, path)
1814
return self._transform._tree.is_executable(file_id, path)
1816
if e.errno == errno.ENOENT:
1819
except errors.NoSuchId:
1603
1822
def path_content_summary(self, path):
1604
1823
trans_id = self._path2trans_id(path)
1636
1855
require_versioned=True, want_unversioned=False):
1637
1856
"""See InterTree.iter_changes.
1639
This implementation does not support include_unchanged, specific_files,
1640
or want_unversioned. extra_trees, require_versioned, and pb are
1858
This has a fast path that is only used when the from_tree matches
1859
the transform tree, and no fancy options are supplied.
1643
if from_tree is not self._transform._tree:
1644
raise ValueError('from_tree must be transform source tree.')
1645
if include_unchanged:
1646
raise ValueError('include_unchanged is not supported')
1647
if specific_files is not None:
1648
raise ValueError('specific_files is not supported')
1861
if (from_tree is not self._transform._tree or include_unchanged or
1862
specific_files or want_unversioned):
1863
return tree.InterTree(from_tree, self).iter_changes(
1864
include_unchanged=include_unchanged,
1865
specific_files=specific_files,
1867
extra_trees=extra_trees,
1868
require_versioned=require_versioned,
1869
want_unversioned=want_unversioned)
1649
1870
if want_unversioned:
1650
1871
raise ValueError('want_unversioned is not supported')
1651
1872
return self._transform.iter_changes()
1697
1911
name = self._transform._limbo_name(trans_id)
1698
1912
return os.readlink(name)
1700
def list_files(self, include_root=False):
1701
return self._transform._tree.list_files(include_root)
1703
def walkdirs(self, prefix=""):
1704
return self._transform._tree.walkdirs(prefix)
1914
def walkdirs(self, prefix=''):
1915
pending = [self._transform.root]
1916
while len(pending) > 0:
1917
parent_id = pending.pop()
1920
prefix = prefix.rstrip('/')
1921
parent_path = self._final_paths.get_path(parent_id)
1922
parent_file_id = self._transform.final_file_id(parent_id)
1923
for child_id in self._all_children(parent_id):
1924
path_from_root = self._final_paths.get_path(child_id)
1925
basename = self._transform.final_name(child_id)
1926
file_id = self._transform.final_file_id(child_id)
1928
kind = self._transform.final_kind(child_id)
1929
versioned_kind = kind
1932
versioned_kind = self._transform._tree.stored_kind(file_id)
1933
if versioned_kind == 'directory':
1934
subdirs.append(child_id)
1935
children.append((path_from_root, basename, kind, None,
1936
file_id, versioned_kind))
1938
if parent_path.startswith(prefix):
1939
yield (parent_path, parent_file_id), children
1940
pending.extend(sorted(subdirs, key=self._final_paths.get_path,
1706
1943
def get_parent_ids(self):
1707
1944
return self._parent_ids
2042
2283
tt.create_directory(trans_id)
2286
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2287
"""Create new file contents according to tree contents."""
2288
kind = tree.kind(file_id)
2289
if kind == 'directory':
2290
tt.create_directory(trans_id)
2291
elif kind == "file":
2293
tree_file = tree.get_file(file_id)
2295
bytes = tree_file.readlines()
2298
tt.create_file(bytes, trans_id)
2299
elif kind == "symlink":
2300
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2302
raise AssertionError('Unknown kind %r' % kind)
2045
2305
def create_entry_executability(tt, entry, trans_id):
2046
2306
"""Set the executability of a trans_id according to an inventory entry"""
2047
2307
if entry.kind == "file":
2098
2358
tt = TreeTransform(working_tree, pb)
2100
2360
pp = ProgressPhase("Revert phase", 3, pb)
2102
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2104
merge_modified = _alter_files(working_tree, target_tree, tt,
2105
child_pb, filenames, backups)
2109
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2111
raw_conflicts = resolve_conflicts(tt, child_pb,
2112
lambda t, c: conflict_pass(t, c, target_tree))
2115
conflicts = cook_conflicts(raw_conflicts, tt)
2361
conflicts, merge_modified = _prepare_revert_transform(
2362
working_tree, target_tree, tt, filenames, backups, pp)
2116
2363
if change_reporter:
2117
2364
change_reporter = delta._ChangeReporter(
2118
2365
unversioned_filter=working_tree.is_ignored)
2129
2376
return conflicts
2379
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2380
backups, pp, basis_tree=None,
2381
merge_modified=None):
2383
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2385
if merge_modified is None:
2386
merge_modified = working_tree.merge_modified()
2387
merge_modified = _alter_files(working_tree, target_tree, tt,
2388
child_pb, filenames, backups,
2389
merge_modified, basis_tree)
2393
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2395
raw_conflicts = resolve_conflicts(tt, child_pb,
2396
lambda t, c: conflict_pass(t, c, target_tree))
2399
conflicts = cook_conflicts(raw_conflicts, tt)
2400
return conflicts, merge_modified
2132
2403
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
2134
merge_modified = working_tree.merge_modified()
2404
backups, merge_modified, basis_tree=None):
2405
if basis_tree is not None:
2406
basis_tree.lock_read()
2135
2407
change_list = target_tree.iter_changes(working_tree,
2136
2408
specific_files=specific_files, pb=pb)
2137
if target_tree.inventory.root is None:
2409
if target_tree.get_root_id() is None:
2138
2410
skip_root = True
2140
2412
skip_root = False
2143
2414
deferred_files = []
2144
2415
for id_num, (file_id, path, changed_content, versioned, parent, name,
2207
2482
tt.version_file(file_id, trans_id)
2208
2483
if versioned == (True, False):
2209
2484
tt.unversion_file(trans_id)
2210
if (name[1] is not None and
2485
if (name[1] is not None and
2211
2486
(name[0] != name[1] or parent[0] != parent[1])):
2213
name[1], tt.trans_id_file_id(parent[1]), trans_id)
2487
if name[1] == '' and parent[1] is None:
2488
parent_trans = ROOT_PARENT
2490
parent_trans = tt.trans_id_file_id(parent[1])
2491
tt.adjust_path(name[1], parent_trans, trans_id)
2214
2492
if executable[0] != executable[1] and kind[1] == "file":
2215
2493
tt.set_executability(executable[1], trans_id)
2216
2494
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(