266
280
a transaction has been unversioned, it is deliberately still returned.
267
281
(this will likely lead to an unversioned parent conflict.)
284
raise ValueError('None is not a valid file id')
269
285
if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
270
286
return self._r_new_id[file_id]
271
elif file_id in self._tree.inventory:
272
return self.trans_id_tree_file_id(file_id)
273
elif file_id in self._non_present_ids:
274
return self._non_present_ids[file_id]
276
trans_id = self._assign_id()
277
self._non_present_ids[file_id] = trans_id
289
self._tree.iter_entries_by_dir([file_id]).next()
290
except StopIteration:
291
if file_id in self._non_present_ids:
292
return self._non_present_ids[file_id]
294
trans_id = self._assign_id()
295
self._non_present_ids[file_id] = trans_id
298
return self.trans_id_tree_file_id(file_id)
280
300
def canonical_path(self, path):
281
301
"""Get the canonical tree-relative path"""
478
503
new_ids.update(id_set)
479
504
return sorted(FinalPaths(self).get_paths(new_ids))
506
def _inventory_altered(self):
507
"""Get the trans_ids and paths of files needing new inv entries."""
509
for id_set in [self._new_name, self._new_parent, self._new_id,
510
self._new_executability]:
511
new_ids.update(id_set)
512
changed_kind = set(self._removed_contents)
513
changed_kind.intersection_update(self._new_contents)
514
changed_kind.difference_update(new_ids)
515
changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
517
new_ids.update(changed_kind)
518
return sorted(FinalPaths(self).get_paths(new_ids))
481
520
def tree_kind(self, trans_id):
482
521
"""Determine the file kind in the working tree.
1089
1127
return _PreviewTree(self)
1129
def _text_parent(self, trans_id):
1130
file_id = self.tree_file_id(trans_id)
1132
if file_id is None or self._tree.kind(file_id) != 'file':
1134
except errors.NoSuchFile:
1138
def _get_parents_texts(self, trans_id):
1139
"""Get texts for compression parents of this file."""
1140
file_id = self._text_parent(trans_id)
1143
return (self._tree.get_file_text(file_id),)
1145
def _get_parents_lines(self, trans_id):
1146
"""Get lines for compression parents of this file."""
1147
file_id = self._text_parent(trans_id)
1150
return (self._tree.get_file_lines(file_id),)
1152
def serialize(self, serializer):
1153
"""Serialize this TreeTransform.
1155
:param serializer: A Serialiser like pack.ContainerSerializer.
1157
new_name = dict((k, v.encode('utf-8')) for k, v in
1158
self._new_name.items())
1159
new_executability = dict((k, int(v)) for k, v in
1160
self._new_executability.items())
1161
tree_path_ids = dict((k.encode('utf-8'), v)
1162
for k, v in self._tree_path_ids.items())
1164
'_id_number': self._id_number,
1165
'_new_name': new_name,
1166
'_new_parent': self._new_parent,
1167
'_new_executability': new_executability,
1168
'_new_id': self._new_id,
1169
'_tree_path_ids': tree_path_ids,
1170
'_removed_id': list(self._removed_id),
1171
'_removed_contents': list(self._removed_contents),
1172
'_non_present_ids': self._non_present_ids,
1174
yield serializer.bytes_record(bencode.bencode(attribs),
1176
for trans_id, kind in self._new_contents.items():
1178
cur_file = open(self._limbo_name(trans_id), 'rb')
1180
lines = osutils.chunks_to_lines(cur_file.readlines())
1183
parents = self._get_parents_lines(trans_id)
1184
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1185
content = ''.join(mpdiff.to_patch())
1186
if kind == 'directory':
1188
if kind == 'symlink':
1189
content = os.readlink(self._limbo_name(trans_id))
1190
yield serializer.bytes_record(content, ((trans_id, kind),))
1193
def deserialize(self, records):
1194
"""Deserialize a stored TreeTransform.
1196
:param records: An iterable of (names, content) tuples, as per
1197
pack.ContainerPushParser.
1199
names, content = records.next()
1200
attribs = bencode.bdecode(content)
1201
self._id_number = attribs['_id_number']
1202
self._new_name = dict((k, v.decode('utf-8'))
1203
for k, v in attribs['_new_name'].items())
1204
self._new_parent = attribs['_new_parent']
1205
self._new_executability = dict((k, bool(v)) for k, v in
1206
attribs['_new_executability'].items())
1207
self._new_id = attribs['_new_id']
1208
self._r_new_id = dict((v, k) for k, v in self._new_id.items())
1209
self._tree_path_ids = {}
1210
self._tree_id_paths = {}
1211
for bytepath, trans_id in attribs['_tree_path_ids'].items():
1212
path = bytepath.decode('utf-8')
1213
self._tree_path_ids[path] = trans_id
1214
self._tree_id_paths[trans_id] = path
1215
self._removed_id = set(attribs['_removed_id'])
1216
self._removed_contents = set(attribs['_removed_contents'])
1217
self._non_present_ids = attribs['_non_present_ids']
1218
for ((trans_id, kind),), content in records:
1220
mpdiff = multiparent.MultiParent.from_patch(content)
1221
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1222
self.create_file(lines, trans_id)
1223
if kind == 'directory':
1224
self.create_directory(trans_id)
1225
if kind == 'symlink':
1226
self.create_symlink(content.decode('utf-8'), trans_id)
1092
1229
class TreeTransform(TreeTransformBase):
1093
1230
"""Represent a tree transformation.
1203
1340
conflicts = self.find_conflicts()
1204
1341
if len(conflicts) != 0:
1205
1342
raise MalformedTransform(conflicts=conflicts)
1206
if precomputed_delta is None:
1207
new_inventory_delta = []
1208
inventory_delta = new_inventory_delta
1210
new_inventory_delta = None
1211
inventory_delta = precomputed_delta
1212
1343
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1345
if precomputed_delta is None:
1346
child_pb.update('Apply phase', 0, 2)
1347
inventory_delta = self._generate_inventory_delta()
1350
inventory_delta = precomputed_delta
1214
1352
if _mover is None:
1215
1353
mover = _FileMover()
1219
child_pb.update('Apply phase', 0, 2)
1220
self._apply_removals(new_inventory_delta, mover)
1221
child_pb.update('Apply phase', 1, 2)
1222
modified_paths = self._apply_insertions(new_inventory_delta,
1357
child_pb.update('Apply phase', 0 + offset, 2 + offset)
1358
self._apply_removals(mover)
1359
child_pb.update('Apply phase', 1 + offset, 2 + offset)
1360
modified_paths = self._apply_insertions(mover)
1225
1362
mover.rollback()
1233
1370
self.finalize()
1234
1371
return _TransformResults(modified_paths, self.rename_count)
1236
def _apply_removals(self, inventory_delta, mover):
1373
def _generate_inventory_delta(self):
1374
"""Generate an inventory delta for the current transform."""
1375
inventory_delta = []
1376
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1377
new_paths = self._inventory_altered()
1378
total_entries = len(new_paths) + len(self._removed_id)
1380
for num, trans_id in enumerate(self._removed_id):
1382
child_pb.update('removing file', num, total_entries)
1383
if trans_id == self._new_root:
1384
file_id = self._tree.get_root_id()
1386
file_id = self.tree_file_id(trans_id)
1387
# File-id isn't really being deleted, just moved
1388
if file_id in self._r_new_id:
1390
path = self._tree_id_paths[trans_id]
1391
inventory_delta.append((path, None, file_id, None))
1392
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1394
entries = self._tree.iter_entries_by_dir(
1395
new_path_file_ids.values())
1396
old_paths = dict((e.file_id, p) for p, e in entries)
1398
for num, (path, trans_id) in enumerate(new_paths):
1400
child_pb.update('adding file',
1401
num + len(self._removed_id), total_entries)
1402
file_id = new_path_file_ids[trans_id]
1407
kind = self.final_kind(trans_id)
1409
kind = self._tree.stored_kind(file_id)
1410
parent_trans_id = self.final_parent(trans_id)
1411
parent_file_id = new_path_file_ids.get(parent_trans_id)
1412
if parent_file_id is None:
1413
parent_file_id = self.final_file_id(parent_trans_id)
1414
if trans_id in self._new_reference_revision:
1415
new_entry = inventory.TreeReference(
1417
self._new_name[trans_id],
1418
self.final_file_id(self._new_parent[trans_id]),
1419
None, self._new_reference_revision[trans_id])
1421
new_entry = inventory.make_entry(kind,
1422
self.final_name(trans_id),
1423
parent_file_id, file_id)
1424
old_path = old_paths.get(new_entry.file_id)
1425
new_executability = self._new_executability.get(trans_id)
1426
if new_executability is not None:
1427
new_entry.executable = new_executability
1428
inventory_delta.append(
1429
(old_path, path, new_entry.file_id, new_entry))
1432
return inventory_delta
1434
def _apply_removals(self, mover):
1237
1435
"""Perform tree operations that remove directory/inventory names.
1239
1437
That is, delete files that are to be deleted, and put any files that
1264
1462
self.rename_count += 1
1265
if (trans_id in self._removed_id
1266
and inventory_delta is not None):
1267
if trans_id == self._new_root:
1268
file_id = self._tree.get_root_id()
1270
file_id = self.tree_file_id(trans_id)
1271
# File-id isn't really being deleted, just moved
1272
if file_id in self._r_new_id:
1274
inventory_delta.append((path, None, file_id, None))
1276
1464
child_pb.finished()
1278
def _apply_insertions(self, inventory_delta, mover):
1466
def _apply_insertions(self, mover):
1279
1467
"""Perform tree operations that insert directory/inventory names.
1281
1469
That is, create any files that need to be created, and restore from
1285
1473
If inventory_delta is None, no inventory delta is calculated, and
1286
1474
no list of modified paths is returned.
1288
new_paths = self.new_paths(filesystem_only=(inventory_delta is None))
1476
new_paths = self.new_paths(filesystem_only=True)
1289
1477
modified_paths = []
1291
1478
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1293
if inventory_delta is not None:
1294
entries = self._tree.iter_entries_by_dir(
1295
new_path_file_ids.values())
1296
old_paths = dict((e.file_id, p) for p, e in entries)
1297
1480
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1299
1482
for num, (path, trans_id) in enumerate(new_paths):
1301
1483
if (num % 10) == 0:
1302
1484
child_pb.update('adding file', num, len(new_paths))
1303
1485
full_path = self._tree.abspath(path)
1312
1494
self.rename_count += 1
1313
if inventory_delta is not None:
1314
if (trans_id in self._new_contents or
1315
self.path_changed(trans_id)):
1316
if trans_id in self._new_contents:
1317
modified_paths.append(full_path)
1318
completed_new.append(trans_id)
1319
file_id = new_path_file_ids[trans_id]
1320
if file_id is not None and (trans_id in self._new_id or
1321
trans_id in self._new_name or
1322
trans_id in self._new_parent
1323
or trans_id in self._new_executability):
1325
kind = self.final_kind(trans_id)
1327
kind = self._tree.stored_kind(file_id)
1328
parent_trans_id = self.final_parent(trans_id)
1329
parent_file_id = new_path_file_ids.get(parent_trans_id)
1330
if parent_file_id is None:
1331
parent_file_id = self.final_file_id(
1333
if trans_id in self._new_reference_revision:
1334
new_entry = inventory.TreeReference(
1336
self._new_name[trans_id],
1337
self.final_file_id(self._new_parent[trans_id]),
1338
None, self._new_reference_revision[trans_id])
1340
new_entry = inventory.make_entry(kind,
1341
self.final_name(trans_id),
1342
parent_file_id, file_id)
1343
old_path = old_paths.get(new_entry.file_id)
1344
inventory_delta.append(
1345
(old_path, path, new_entry.file_id, new_entry))
1495
if (trans_id in self._new_contents or
1496
self.path_changed(trans_id)):
1497
if trans_id in self._new_contents:
1498
modified_paths.append(full_path)
1347
1499
if trans_id in self._new_executability:
1348
self._set_executability(path, new_entry, trans_id)
1500
self._set_executability(path, trans_id)
1350
1502
child_pb.finished()
1351
if inventory_delta is None:
1352
self._new_contents.clear()
1354
for trans_id in completed_new:
1355
del self._new_contents[trans_id]
1503
self._new_contents.clear()
1356
1504
return modified_paths
1484
1646
def __iter__(self):
1485
1647
return iter(self.all_file_ids())
1487
def paths2ids(self, specific_files, trees=None, require_versioned=False):
1488
"""See Tree.paths2ids"""
1489
to_find = set(specific_files)
1491
for (file_id, paths, changed, versioned, parent, name, kind,
1492
executable) in self._transform.iter_changes():
1493
if paths[1] in to_find:
1494
result.append(file_id)
1495
to_find.remove(paths[1])
1496
result.update(self._transform._tree.paths2ids(to_find,
1497
trees=[], require_versioned=require_versioned))
1649
def has_id(self, file_id):
1650
if file_id in self._transform._r_new_id:
1652
elif file_id in self._transform._removed_id:
1655
return self._transform._tree.has_id(file_id)
1500
1657
def _path2trans_id(self, path):
1658
# We must not use None here, because that is a valid value to store.
1659
trans_id = self._path2trans_id_cache.get(path, object)
1660
if trans_id is not object:
1501
1662
segments = splitpath(path)
1502
1663
cur_parent = self._transform.root
1503
1664
for cur_segment in segments:
1504
1665
for child in self._all_children(cur_parent):
1505
if self._transform.final_name(child) == cur_segment:
1666
final_name = self._final_name_cache.get(child)
1667
if final_name is None:
1668
final_name = self._transform.final_name(child)
1669
self._final_name_cache[child] = final_name
1670
if final_name == cur_segment:
1506
1671
cur_parent = child
1674
self._path2trans_id_cache[path] = None
1676
self._path2trans_id_cache[path] = cur_parent
1510
1677
return cur_parent
1512
1679
def path2id(self, path):
1520
1687
raise errors.NoSuchId(self, file_id)
1522
1689
def _all_children(self, trans_id):
1690
children = self._all_children_cache.get(trans_id)
1691
if children is not None:
1523
1693
children = set(self._transform.iter_tree_children(trans_id))
1524
1694
# children in the _new_parent set are provided by _by_parent.
1525
1695
children.difference_update(self._transform._new_parent.keys())
1526
1696
children.update(self._by_parent.get(trans_id, []))
1697
self._all_children_cache[trans_id] = children
1527
1698
return children
1700
def iter_children(self, file_id):
1701
trans_id = self._transform.trans_id_file_id(file_id)
1702
for child_trans_id in self._all_children(trans_id):
1703
yield self._transform.final_file_id(child_trans_id)
1706
possible_extras = set(self._transform.trans_id_tree_path(p) for p
1707
in self._transform._tree.extras())
1708
possible_extras.update(self._transform._new_contents)
1709
possible_extras.update(self._transform._removed_id)
1710
for trans_id in possible_extras:
1711
if self._transform.final_file_id(trans_id) is None:
1712
yield self._final_paths._determine_path(trans_id)
1529
1714
def _make_inv_entries(self, ordered_entries, specific_file_ids):
1530
1715
for trans_id, parent_file_id in ordered_entries:
1531
1716
file_id = self._transform.final_file_id(trans_id)
1561
1741
todo.extend(reversed(children))
1562
1742
for trans_id in children:
1563
1743
ordered_ids.append((trans_id, parent_file_id))
1746
def iter_entries_by_dir(self, specific_file_ids=None):
1747
# This may not be a maximally efficient implementation, but it is
1748
# reasonably straightforward. An implementation that grafts the
1749
# TreeTransform changes onto the tree's iter_entries_by_dir results
1750
# might be more efficient, but requires tricky inferences about stack
1752
ordered_ids = self._list_files_by_dir()
1564
1753
for entry, trans_id in self._make_inv_entries(ordered_ids,
1565
1754
specific_file_ids):
1566
1755
yield unicode(self._final_paths.get_path(trans_id)), entry
1757
def list_files(self, include_root=False):
1758
"""See Tree.list_files."""
1759
# XXX This should behave like WorkingTree.list_files, but is really
1760
# more like RevisionTree.list_files.
1761
for path, entry in self.iter_entries_by_dir():
1762
if entry.name == '' and not include_root:
1764
yield path, 'V', entry.kind, entry.file_id, entry
1568
1766
def kind(self, file_id):
1569
1767
trans_id = self._transform.trans_id_file_id(file_id)
1570
1768
return self._transform.final_kind(trans_id)
1592
1793
def get_file_sha1(self, file_id, path=None, stat_value=None):
1593
return self._transform._tree.get_file_sha1(file_id)
1794
trans_id = self._transform.trans_id_file_id(file_id)
1795
kind = self._transform._new_contents.get(trans_id)
1797
return self._transform._tree.get_file_sha1(file_id)
1799
fileobj = self.get_file(file_id)
1801
return sha_file(fileobj)
1595
1805
def is_executable(self, file_id, path=None):
1596
1808
trans_id = self._transform.trans_id_file_id(file_id)
1598
1810
return self._transform._new_executability[trans_id]
1599
1811
except KeyError:
1600
return self._transform._tree.is_executable(file_id, path)
1813
return self._transform._tree.is_executable(file_id, path)
1815
if e.errno == errno.ENOENT:
1818
except errors.NoSuchId:
1602
1821
def path_content_summary(self, path):
1603
1822
trans_id = self._path2trans_id(path)
1635
1854
require_versioned=True, want_unversioned=False):
1636
1855
"""See InterTree.iter_changes.
1638
This implementation does not support include_unchanged, specific_files,
1639
or want_unversioned. extra_trees, require_versioned, and pb are
1857
This has a fast path that is only used when the from_tree matches
1858
the transform tree, and no fancy options are supplied.
1642
if from_tree is not self._transform._tree:
1643
raise ValueError('from_tree must be transform source tree.')
1644
if include_unchanged:
1645
raise ValueError('include_unchanged is not supported')
1646
if specific_files is not None:
1647
raise ValueError('specific_files is not supported')
1860
if (from_tree is not self._transform._tree or include_unchanged or
1861
specific_files or want_unversioned):
1862
return tree.InterTree(from_tree, self).iter_changes(
1863
include_unchanged=include_unchanged,
1864
specific_files=specific_files,
1866
extra_trees=extra_trees,
1867
require_versioned=require_versioned,
1868
want_unversioned=want_unversioned)
1648
1869
if want_unversioned:
1649
1870
raise ValueError('want_unversioned is not supported')
1650
1871
return self._transform.iter_changes()
1696
1910
name = self._transform._limbo_name(trans_id)
1697
1911
return os.readlink(name)
1699
def list_files(self, include_root=False):
1700
return self._transform._tree.list_files(include_root)
1702
def walkdirs(self, prefix=""):
1703
return self._transform._tree.walkdirs(prefix)
1913
def walkdirs(self, prefix=''):
1914
pending = [self._transform.root]
1915
while len(pending) > 0:
1916
parent_id = pending.pop()
1919
prefix = prefix.rstrip('/')
1920
parent_path = self._final_paths.get_path(parent_id)
1921
parent_file_id = self._transform.final_file_id(parent_id)
1922
for child_id in self._all_children(parent_id):
1923
path_from_root = self._final_paths.get_path(child_id)
1924
basename = self._transform.final_name(child_id)
1925
file_id = self._transform.final_file_id(child_id)
1927
kind = self._transform.final_kind(child_id)
1928
versioned_kind = kind
1931
versioned_kind = self._transform._tree.stored_kind(file_id)
1932
if versioned_kind == 'directory':
1933
subdirs.append(child_id)
1934
children.append((path_from_root, basename, kind, None,
1935
file_id, versioned_kind))
1937
if parent_path.startswith(prefix):
1938
yield (parent_path, parent_file_id), children
1939
pending.extend(sorted(subdirs, key=self._final_paths.get_path,
1705
1942
def get_parent_ids(self):
1706
1943
return self._parent_ids
2030
2271
tt.create_directory(trans_id)
2274
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2275
"""Create new file contents according to tree contents."""
2276
kind = tree.kind(file_id)
2277
if kind == 'directory':
2278
tt.create_directory(trans_id)
2279
elif kind == "file":
2281
tree_file = tree.get_file(file_id)
2283
bytes = tree_file.readlines()
2286
tt.create_file(bytes, trans_id)
2287
elif kind == "symlink":
2288
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2290
raise AssertionError('Unknown kind %r' % kind)
2033
2293
def create_entry_executability(tt, entry, trans_id):
2034
2294
"""Set the executability of a trans_id according to an inventory entry"""
2035
2295
if entry.kind == "file":
2086
2346
tt = TreeTransform(working_tree, pb)
2088
2348
pp = ProgressPhase("Revert phase", 3, pb)
2090
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2092
merge_modified = _alter_files(working_tree, target_tree, tt,
2093
child_pb, filenames, backups)
2097
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2099
raw_conflicts = resolve_conflicts(tt, child_pb,
2100
lambda t, c: conflict_pass(t, c, target_tree))
2103
conflicts = cook_conflicts(raw_conflicts, tt)
2349
conflicts, merge_modified = _prepare_revert_transform(
2350
working_tree, target_tree, tt, filenames, backups, pp)
2104
2351
if change_reporter:
2105
2352
change_reporter = delta._ChangeReporter(
2106
2353
unversioned_filter=working_tree.is_ignored)
2117
2364
return conflicts
2367
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2368
backups, pp, basis_tree=None,
2369
merge_modified=None):
2371
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2373
if merge_modified is None:
2374
merge_modified = working_tree.merge_modified()
2375
merge_modified = _alter_files(working_tree, target_tree, tt,
2376
child_pb, filenames, backups,
2377
merge_modified, basis_tree)
2381
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2383
raw_conflicts = resolve_conflicts(tt, child_pb,
2384
lambda t, c: conflict_pass(t, c, target_tree))
2387
conflicts = cook_conflicts(raw_conflicts, tt)
2388
return conflicts, merge_modified
2120
2391
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
2122
merge_modified = working_tree.merge_modified()
2392
backups, merge_modified, basis_tree=None):
2393
if basis_tree is not None:
2394
basis_tree.lock_read()
2123
2395
change_list = target_tree.iter_changes(working_tree,
2124
2396
specific_files=specific_files, pb=pb)
2125
if target_tree.inventory.root is None:
2397
if target_tree.get_root_id() is None:
2126
2398
skip_root = True
2128
2400
skip_root = False
2131
2402
deferred_files = []
2132
2403
for id_num, (file_id, path, changed_content, versioned, parent, name,
2195
2470
tt.version_file(file_id, trans_id)
2196
2471
if versioned == (True, False):
2197
2472
tt.unversion_file(trans_id)
2198
if (name[1] is not None and
2473
if (name[1] is not None and
2199
2474
(name[0] != name[1] or parent[0] != parent[1])):
2201
name[1], tt.trans_id_file_id(parent[1]), trans_id)
2475
if name[1] == '' and parent[1] is None:
2476
parent_trans = ROOT_PARENT
2478
parent_trans = tt.trans_id_file_id(parent[1])
2479
tt.adjust_path(name[1], parent_trans, trans_id)
2202
2480
if executable[0] != executable[1] and kind[1] == "file":
2203
2481
tt.set_executability(executable[1], trans_id)
2204
2482
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(