879
977
self.create_symlink(target, trans_id)
980
def _affected_ids(self):
981
"""Return the set of transform ids affected by the transform"""
982
trans_ids = set(self._removed_id)
983
trans_ids.update(self._new_id.keys())
984
trans_ids.update(self._removed_contents)
985
trans_ids.update(self._new_contents.keys())
986
trans_ids.update(self._new_executability.keys())
987
trans_ids.update(self._new_name.keys())
988
trans_ids.update(self._new_parent.keys())
991
def _get_file_id_maps(self):
992
"""Return mapping of file_ids to trans_ids in the to and from states"""
993
trans_ids = self._affected_ids()
996
# Build up two dicts: trans_ids associated with file ids in the
997
# FROM state, vs the TO state.
998
for trans_id in trans_ids:
999
from_file_id = self.tree_file_id(trans_id)
1000
if from_file_id is not None:
1001
from_trans_ids[from_file_id] = trans_id
1002
to_file_id = self.final_file_id(trans_id)
1003
if to_file_id is not None:
1004
to_trans_ids[to_file_id] = trans_id
1005
return from_trans_ids, to_trans_ids
1007
def _from_file_data(self, from_trans_id, from_versioned, file_id):
1008
"""Get data about a file in the from (tree) state
1010
Return a (name, parent, kind, executable) tuple
1012
from_path = self._tree_id_paths.get(from_trans_id)
1014
# get data from working tree if versioned
1015
from_entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
1016
from_name = from_entry.name
1017
from_parent = from_entry.parent_id
1020
if from_path is None:
1021
# File does not exist in FROM state
1025
# File exists, but is not versioned. Have to use path-
1027
from_name = os.path.basename(from_path)
1028
tree_parent = self.get_tree_parent(from_trans_id)
1029
from_parent = self.tree_file_id(tree_parent)
1030
if from_path is not None:
1031
from_kind, from_executable, from_stats = \
1032
self._tree._comparison_data(from_entry, from_path)
1035
from_executable = False
1036
return from_name, from_parent, from_kind, from_executable
1038
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1039
"""Get data about a file in the to (target) state
1041
Return a (name, parent, kind, executable) tuple
1043
to_name = self.final_name(to_trans_id)
1045
to_kind = self.final_kind(to_trans_id)
1048
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1049
if to_trans_id in self._new_executability:
1050
to_executable = self._new_executability[to_trans_id]
1051
elif to_trans_id == from_trans_id:
1052
to_executable = from_executable
1054
to_executable = False
1055
return to_name, to_parent, to_kind, to_executable
1057
def iter_changes(self):
1058
"""Produce output in the same format as Tree.iter_changes.
1060
Will produce nonsensical results if invoked while inventory/filesystem
1061
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1063
This reads the Transform, but only reproduces changes involving a
1064
file_id. Files that are not versioned in either of the FROM or TO
1065
states are not reflected.
1067
final_paths = FinalPaths(self)
1068
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1070
# Now iterate through all active file_ids
1071
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1073
from_trans_id = from_trans_ids.get(file_id)
1074
# find file ids, and determine versioning state
1075
if from_trans_id is None:
1076
from_versioned = False
1077
from_trans_id = to_trans_ids[file_id]
1079
from_versioned = True
1080
to_trans_id = to_trans_ids.get(file_id)
1081
if to_trans_id is None:
1082
to_versioned = False
1083
to_trans_id = from_trans_id
1087
from_name, from_parent, from_kind, from_executable = \
1088
self._from_file_data(from_trans_id, from_versioned, file_id)
1090
to_name, to_parent, to_kind, to_executable = \
1091
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1093
if not from_versioned:
1096
from_path = self._tree_id_paths.get(from_trans_id)
1097
if not to_versioned:
1100
to_path = final_paths.get_path(to_trans_id)
1101
if from_kind != to_kind:
1103
elif to_kind in ('file', 'symlink') and (
1104
to_trans_id != from_trans_id or
1105
to_trans_id in self._new_contents):
1107
if (not modified and from_versioned == to_versioned and
1108
from_parent==to_parent and from_name == to_name and
1109
from_executable == to_executable):
1111
results.append((file_id, (from_path, to_path), modified,
1112
(from_versioned, to_versioned),
1113
(from_parent, to_parent),
1114
(from_name, to_name),
1115
(from_kind, to_kind),
1116
(from_executable, to_executable)))
1117
return iter(sorted(results, key=lambda x:x[1]))
1119
def get_preview_tree(self):
1120
"""Return a tree representing the result of the transform.
1122
This tree only supports the subset of Tree functionality required
1123
by show_diff_trees. It must only be compared to tt._tree.
1125
return _PreviewTree(self)
1128
class TreeTransform(TreeTransformBase):
1129
"""Represent a tree transformation.
1131
This object is designed to support incremental generation of the transform,
1134
However, it gives optimum performance when parent directories are created
1135
before their contents. The transform is then able to put child files
1136
directly in their parent directory, avoiding later renames.
1138
It is easy to produce malformed transforms, but they are generally
1139
harmless. Attempting to apply a malformed transform will cause an
1140
exception to be raised before any modifications are made to the tree.
1142
Many kinds of malformed transforms can be corrected with the
1143
resolve_conflicts function. The remaining ones indicate programming error,
1144
such as trying to create a file with no path.
1146
Two sets of file creation methods are supplied. Convenience methods are:
1151
These are composed of the low-level methods:
1153
* create_file or create_directory or create_symlink
1157
Transform/Transaction ids
1158
-------------------------
1159
trans_ids are temporary ids assigned to all files involved in a transform.
1160
It's possible, even common, that not all files in the Tree have trans_ids.
1162
trans_ids are used because filenames and file_ids are not good enough
1163
identifiers; filenames change, and not all files have file_ids. File-ids
1164
are also associated with trans-ids, so that moving a file moves its
1167
trans_ids are only valid for the TreeTransform that generated them.
1171
Limbo is a temporary directory use to hold new versions of files.
1172
Files are added to limbo by create_file, create_directory, create_symlink,
1173
and their convenience variants (new_*). Files may be removed from limbo
1174
using cancel_creation. Files are renamed from limbo into their final
1175
location as part of TreeTransform.apply
1177
Limbo must be cleaned up, by either calling TreeTransform.apply or
1178
calling TreeTransform.finalize.
1180
Files are placed into limbo inside their parent directories, where
1181
possible. This reduces subsequent renames, and makes operations involving
1182
lots of files faster. This optimization is only possible if the parent
1183
directory is created *before* creating any of its children, so avoid
1184
creating children before parents, where possible.
1188
This temporary directory is used by _FileMover for storing files that are
1189
about to be deleted. In case of rollback, the files will be restored.
1190
FileMover does not delete files until it is sure that a rollback will not
1193
def __init__(self, tree, pb=DummyProgress()):
1194
"""Note: a tree_write lock is taken on the tree.
1196
Use TreeTransform.finalize() to release the lock (can be omitted if
1197
TreeTransform.apply() called).
1199
tree.lock_tree_write()
1202
limbodir = urlutils.local_path_from_url(
1203
tree._transport.abspath('limbo'))
1207
if e.errno == errno.EEXIST:
1208
raise ExistingLimbo(limbodir)
1209
deletiondir = urlutils.local_path_from_url(
1210
tree._transport.abspath('pending-deletion'))
1212
os.mkdir(deletiondir)
1214
if e.errno == errno.EEXIST:
1215
raise errors.ExistingPendingDeletion(deletiondir)
1220
TreeTransformBase.__init__(self, tree, limbodir, pb,
1221
tree.case_sensitive)
1222
self._deletiondir = deletiondir
1224
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1225
"""Apply all changes to the inventory and filesystem.
1227
If filesystem or inventory conflicts are present, MalformedTransform
1230
If apply succeeds, finalize is not necessary.
1232
:param no_conflicts: if True, the caller guarantees there are no
1233
conflicts, so no check is made.
1234
:param precomputed_delta: An inventory delta to use instead of
1236
:param _mover: Supply an alternate FileMover, for testing
1238
if not no_conflicts:
1239
conflicts = self.find_conflicts()
1240
if len(conflicts) != 0:
1241
raise MalformedTransform(conflicts=conflicts)
1242
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1244
if precomputed_delta is None:
1245
child_pb.update('Apply phase', 0, 2)
1246
inventory_delta = self._generate_inventory_delta()
1249
inventory_delta = precomputed_delta
1252
mover = _FileMover()
1256
child_pb.update('Apply phase', 0 + offset, 2 + offset)
1257
self._apply_removals(mover)
1258
child_pb.update('Apply phase', 1 + offset, 2 + offset)
1259
modified_paths = self._apply_insertions(mover)
1264
mover.apply_deletions()
1267
self._tree.apply_inventory_delta(inventory_delta)
1270
return _TransformResults(modified_paths, self.rename_count)
1272
def _generate_inventory_delta(self):
1273
"""Generate an inventory delta for the current transform."""
1274
inventory_delta = []
1275
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1276
new_paths = self._inventory_altered()
1277
total_entries = len(new_paths) + len(self._removed_id)
1279
for num, trans_id in enumerate(self._removed_id):
1281
child_pb.update('removing file', num, total_entries)
1282
if trans_id == self._new_root:
1283
file_id = self._tree.get_root_id()
1285
file_id = self.tree_file_id(trans_id)
1286
# File-id isn't really being deleted, just moved
1287
if file_id in self._r_new_id:
1289
path = self._tree_id_paths[trans_id]
1290
inventory_delta.append((path, None, file_id, None))
1291
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1293
entries = self._tree.iter_entries_by_dir(
1294
new_path_file_ids.values())
1295
old_paths = dict((e.file_id, p) for p, e in entries)
1297
for num, (path, trans_id) in enumerate(new_paths):
1299
child_pb.update('adding file',
1300
num + len(self._removed_id), total_entries)
1301
file_id = new_path_file_ids[trans_id]
1306
kind = self.final_kind(trans_id)
1308
kind = self._tree.stored_kind(file_id)
1309
parent_trans_id = self.final_parent(trans_id)
1310
parent_file_id = new_path_file_ids.get(parent_trans_id)
1311
if parent_file_id is None:
1312
parent_file_id = self.final_file_id(parent_trans_id)
1313
if trans_id in self._new_reference_revision:
1314
new_entry = inventory.TreeReference(
1316
self._new_name[trans_id],
1317
self.final_file_id(self._new_parent[trans_id]),
1318
None, self._new_reference_revision[trans_id])
1320
new_entry = inventory.make_entry(kind,
1321
self.final_name(trans_id),
1322
parent_file_id, file_id)
1323
old_path = old_paths.get(new_entry.file_id)
1324
new_executability = self._new_executability.get(trans_id)
1325
if new_executability is not None:
1326
new_entry.executable = new_executability
1327
inventory_delta.append(
1328
(old_path, path, new_entry.file_id, new_entry))
1331
return inventory_delta
1333
def _apply_removals(self, mover):
1334
"""Perform tree operations that remove directory/inventory names.
1336
That is, delete files that are to be deleted, and put any files that
1337
need renaming into limbo. This must be done in strict child-to-parent
1340
If inventory_delta is None, no inventory delta generation is performed.
1342
tree_paths = list(self._tree_path_ids.iteritems())
1343
tree_paths.sort(reverse=True)
1344
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1346
for num, data in enumerate(tree_paths):
1347
path, trans_id = data
1348
child_pb.update('removing file', num, len(tree_paths))
1349
full_path = self._tree.abspath(path)
1350
if trans_id in self._removed_contents:
1351
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1353
elif trans_id in self._new_name or trans_id in \
1356
mover.rename(full_path, self._limbo_name(trans_id))
1358
if e.errno != errno.ENOENT:
1361
self.rename_count += 1
1365
def _apply_insertions(self, mover):
1366
"""Perform tree operations that insert directory/inventory names.
1368
That is, create any files that need to be created, and restore from
1369
limbo any files that needed renaming. This must be done in strict
1370
parent-to-child order.
1372
If inventory_delta is None, no inventory delta is calculated, and
1373
no list of modified paths is returned.
1375
new_paths = self.new_paths(filesystem_only=True)
1377
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1379
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1381
for num, (path, trans_id) in enumerate(new_paths):
1383
child_pb.update('adding file', num, len(new_paths))
1384
full_path = self._tree.abspath(path)
1385
if trans_id in self._needs_rename:
1387
mover.rename(self._limbo_name(trans_id), full_path)
1389
# We may be renaming a dangling inventory id
1390
if e.errno != errno.ENOENT:
1393
self.rename_count += 1
1394
if (trans_id in self._new_contents or
1395
self.path_changed(trans_id)):
1396
if trans_id in self._new_contents:
1397
modified_paths.append(full_path)
1398
if trans_id in self._new_executability:
1399
self._set_executability(path, trans_id)
1402
self._new_contents.clear()
1403
return modified_paths
1406
class TransformPreview(TreeTransformBase):
1407
"""A TreeTransform for generating preview trees.
1409
Unlike TreeTransform, this version works when the input tree is a
1410
RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
1411
unversioned files in the input tree.
1414
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1416
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1417
TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1419
def canonical_path(self, path):
1422
def tree_kind(self, trans_id):
1423
path = self._tree_id_paths.get(trans_id)
1425
raise NoSuchFile(None)
1426
file_id = self._tree.path2id(path)
1427
return self._tree.kind(file_id)
1429
def _set_mode(self, trans_id, mode_id, typefunc):
1430
"""Set the mode of new file contents.
1431
The mode_id is the existing file to get the mode from (often the same
1432
as trans_id). The operation is only performed if there's a mode match
1433
according to typefunc.
1435
# is it ok to ignore this? probably
1438
def iter_tree_children(self, parent_id):
1439
"""Iterate through the entry's tree children, if any"""
1441
path = self._tree_id_paths[parent_id]
1444
file_id = self.tree_file_id(parent_id)
1447
entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
1448
children = getattr(entry, 'children', {})
1449
for child in children:
1450
childpath = joinpath(path, child)
1451
yield self.trans_id_tree_path(childpath)
1454
class _PreviewTree(tree.Tree):
1455
"""Partial implementation of Tree to support show_diff_trees"""
1457
def __init__(self, transform):
1458
self._transform = transform
1459
self._final_paths = FinalPaths(transform)
1460
self.__by_parent = None
1461
self._parent_ids = []
1462
self._all_children_cache = {}
1463
self._path2trans_id_cache = {}
1464
self._final_name_cache = {}
1466
def _changes(self, file_id):
1467
for changes in self._transform.iter_changes():
1468
if changes[0] == file_id:
1471
def _content_change(self, file_id):
1472
"""Return True if the content of this file changed"""
1473
changes = self._changes(file_id)
1474
# changes[2] is true if the file content changed. See
1475
# InterTree.iter_changes.
1476
return (changes is not None and changes[2])
1478
def _get_repository(self):
1479
repo = getattr(self._transform._tree, '_repository', None)
1481
repo = self._transform._tree.branch.repository
1484
def _iter_parent_trees(self):
1485
for revision_id in self.get_parent_ids():
1487
yield self.revision_tree(revision_id)
1488
except errors.NoSuchRevisionInTree:
1489
yield self._get_repository().revision_tree(revision_id)
1491
def _get_file_revision(self, file_id, vf, tree_revision):
1492
parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1493
self._iter_parent_trees()]
1494
vf.add_lines((file_id, tree_revision), parent_keys,
1495
self.get_file(file_id).readlines())
1496
repo = self._get_repository()
1497
base_vf = repo.texts
1498
if base_vf not in vf.fallback_versionedfiles:
1499
vf.fallback_versionedfiles.append(base_vf)
1500
return tree_revision
1502
def _stat_limbo_file(self, file_id):
1503
trans_id = self._transform.trans_id_file_id(file_id)
1504
name = self._transform._limbo_name(trans_id)
1505
return os.lstat(name)
1508
def _by_parent(self):
1509
if self.__by_parent is None:
1510
self.__by_parent = self._transform.by_parent()
1511
return self.__by_parent
1513
def _comparison_data(self, entry, path):
1514
kind, size, executable, link_or_sha1 = self.path_content_summary(path)
1515
if kind == 'missing':
1519
file_id = self._transform.final_file_id(self._path2trans_id(path))
1520
executable = self.is_executable(file_id, path)
1521
return kind, executable, None
1523
def lock_read(self):
1524
# Perhaps in theory, this should lock the TreeTransform?
1531
def inventory(self):
1532
"""This Tree does not use inventory as its backing data."""
1533
raise NotImplementedError(_PreviewTree.inventory)
1535
def get_root_id(self):
1536
return self._transform.final_file_id(self._transform.root)
1538
def all_file_ids(self):
1539
tree_ids = set(self._transform._tree.all_file_ids())
1540
tree_ids.difference_update(self._transform.tree_file_id(t)
1541
for t in self._transform._removed_id)
1542
tree_ids.update(self._transform._new_id.values())
1546
return iter(self.all_file_ids())
1548
def has_id(self, file_id):
1549
if file_id in self._transform._r_new_id:
1551
elif file_id in self._transform._removed_id:
1554
return self._transform._tree.has_id(file_id)
1556
def _path2trans_id(self, path):
1557
# We must not use None here, because that is a valid value to store.
1558
trans_id = self._path2trans_id_cache.get(path, object)
1559
if trans_id is not object:
1561
segments = splitpath(path)
1562
cur_parent = self._transform.root
1563
for cur_segment in segments:
1564
for child in self._all_children(cur_parent):
1565
final_name = self._final_name_cache.get(child)
1566
if final_name is None:
1567
final_name = self._transform.final_name(child)
1568
self._final_name_cache[child] = final_name
1569
if final_name == cur_segment:
1573
self._path2trans_id_cache[path] = None
1575
self._path2trans_id_cache[path] = cur_parent
1578
def path2id(self, path):
1579
return self._transform.final_file_id(self._path2trans_id(path))
1581
def id2path(self, file_id):
1582
trans_id = self._transform.trans_id_file_id(file_id)
1584
return self._final_paths._determine_path(trans_id)
1586
raise errors.NoSuchId(self, file_id)
1588
def _all_children(self, trans_id):
1589
children = self._all_children_cache.get(trans_id)
1590
if children is not None:
1592
children = set(self._transform.iter_tree_children(trans_id))
1593
# children in the _new_parent set are provided by _by_parent.
1594
children.difference_update(self._transform._new_parent.keys())
1595
children.update(self._by_parent.get(trans_id, []))
1596
self._all_children_cache[trans_id] = children
1599
def iter_children(self, file_id):
1600
trans_id = self._transform.trans_id_file_id(file_id)
1601
for child_trans_id in self._all_children(trans_id):
1602
yield self._transform.final_file_id(child_trans_id)
1605
possible_extras = set(self._transform.trans_id_tree_path(p) for p
1606
in self._transform._tree.extras())
1607
possible_extras.update(self._transform._new_contents)
1608
possible_extras.update(self._transform._removed_id)
1609
for trans_id in possible_extras:
1610
if self._transform.final_file_id(trans_id) is None:
1611
yield self._final_paths._determine_path(trans_id)
1613
def _make_inv_entries(self, ordered_entries, specific_file_ids):
1614
for trans_id, parent_file_id in ordered_entries:
1615
file_id = self._transform.final_file_id(trans_id)
1618
if (specific_file_ids is not None
1619
and file_id not in specific_file_ids):
1622
kind = self._transform.final_kind(trans_id)
1624
kind = self._transform._tree.stored_kind(file_id)
1625
new_entry = inventory.make_entry(
1627
self._transform.final_name(trans_id),
1628
parent_file_id, file_id)
1629
yield new_entry, trans_id
1631
def _list_files_by_dir(self):
1632
todo = [ROOT_PARENT]
1634
while len(todo) > 0:
1636
parent_file_id = self._transform.final_file_id(parent)
1637
children = list(self._all_children(parent))
1638
paths = dict(zip(children, self._final_paths.get_paths(children)))
1639
children.sort(key=paths.get)
1640
todo.extend(reversed(children))
1641
for trans_id in children:
1642
ordered_ids.append((trans_id, parent_file_id))
1645
def iter_entries_by_dir(self, specific_file_ids=None):
1646
# This may not be a maximally efficient implementation, but it is
1647
# reasonably straightforward. An implementation that grafts the
1648
# TreeTransform changes onto the tree's iter_entries_by_dir results
1649
# might be more efficient, but requires tricky inferences about stack
1651
ordered_ids = self._list_files_by_dir()
1652
for entry, trans_id in self._make_inv_entries(ordered_ids,
1654
yield unicode(self._final_paths.get_path(trans_id)), entry
1656
def list_files(self, include_root=False):
1657
"""See Tree.list_files."""
1658
# XXX This should behave like WorkingTree.list_files, but is really
1659
# more like RevisionTree.list_files.
1660
for path, entry in self.iter_entries_by_dir():
1661
if entry.name == '' and not include_root:
1663
yield path, 'V', entry.kind, entry.file_id, entry
1665
def kind(self, file_id):
1666
trans_id = self._transform.trans_id_file_id(file_id)
1667
return self._transform.final_kind(trans_id)
1669
def stored_kind(self, file_id):
1670
trans_id = self._transform.trans_id_file_id(file_id)
1672
return self._transform._new_contents[trans_id]
1674
return self._transform._tree.stored_kind(file_id)
1676
def get_file_mtime(self, file_id, path=None):
1677
"""See Tree.get_file_mtime"""
1678
if not self._content_change(file_id):
1679
return self._transform._tree.get_file_mtime(file_id, path)
1680
return self._stat_limbo_file(file_id).st_mtime
1682
def _file_size(self, entry, stat_value):
1683
return self.get_file_size(entry.file_id)
1685
def get_file_size(self, file_id):
1686
"""See Tree.get_file_size"""
1687
if self.kind(file_id) == 'file':
1688
return self._transform._tree.get_file_size(file_id)
1692
def get_file_sha1(self, file_id, path=None, stat_value=None):
1693
trans_id = self._transform.trans_id_file_id(file_id)
1694
kind = self._transform._new_contents.get(trans_id)
1696
return self._transform._tree.get_file_sha1(file_id)
1698
fileobj = self.get_file(file_id)
1700
return sha_file(fileobj)
1704
def is_executable(self, file_id, path=None):
1707
trans_id = self._transform.trans_id_file_id(file_id)
1709
return self._transform._new_executability[trans_id]
1712
return self._transform._tree.is_executable(file_id, path)
1714
if e.errno == errno.ENOENT:
1717
except errors.NoSuchId:
1720
def path_content_summary(self, path):
1721
trans_id = self._path2trans_id(path)
1722
tt = self._transform
1723
tree_path = tt._tree_id_paths.get(trans_id)
1724
kind = tt._new_contents.get(trans_id)
1726
if tree_path is None or trans_id in tt._removed_contents:
1727
return 'missing', None, None, None
1728
summary = tt._tree.path_content_summary(tree_path)
1729
kind, size, executable, link_or_sha1 = summary
1732
limbo_name = tt._limbo_name(trans_id)
1733
if trans_id in tt._new_reference_revision:
1734
kind = 'tree-reference'
1736
statval = os.lstat(limbo_name)
1737
size = statval.st_size
1738
if not supports_executable():
1741
executable = statval.st_mode & S_IEXEC
1745
if kind == 'symlink':
1746
link_or_sha1 = os.readlink(limbo_name)
1747
if supports_executable():
1748
executable = tt._new_executability.get(trans_id, executable)
1749
return kind, size, executable, link_or_sha1
1751
def iter_changes(self, from_tree, include_unchanged=False,
1752
specific_files=None, pb=None, extra_trees=None,
1753
require_versioned=True, want_unversioned=False):
1754
"""See InterTree.iter_changes.
1756
This has a fast path that is only used when the from_tree matches
1757
the transform tree, and no fancy options are supplied.
1759
if (from_tree is not self._transform._tree or include_unchanged or
1760
specific_files or want_unversioned):
1761
return tree.InterTree(from_tree, self).iter_changes(
1762
include_unchanged=include_unchanged,
1763
specific_files=specific_files,
1765
extra_trees=extra_trees,
1766
require_versioned=require_versioned,
1767
want_unversioned=want_unversioned)
1768
if want_unversioned:
1769
raise ValueError('want_unversioned is not supported')
1770
return self._transform.iter_changes()
1772
def get_file(self, file_id, path=None):
1773
"""See Tree.get_file"""
1774
if not self._content_change(file_id):
1775
return self._transform._tree.get_file(file_id, path)
1776
trans_id = self._transform.trans_id_file_id(file_id)
1777
name = self._transform._limbo_name(trans_id)
1778
return open(name, 'rb')
1780
def get_file_text(self, file_id):
1781
text_file = self.get_file(file_id)
1783
return text_file.read()
1787
def annotate_iter(self, file_id,
1788
default_revision=_mod_revision.CURRENT_REVISION):
1789
changes = self._changes(file_id)
1793
changed_content, versioned, kind = (changes[2], changes[3],
1797
get_old = (kind[0] == 'file' and versioned[0])
1799
old_annotation = self._transform._tree.annotate_iter(file_id,
1800
default_revision=default_revision)
1804
return old_annotation
1805
if not changed_content:
1806
return old_annotation
1807
return annotate.reannotate([old_annotation],
1808
self.get_file(file_id).readlines(),
1811
def get_symlink_target(self, file_id):
1812
"""See Tree.get_symlink_target"""
1813
if not self._content_change(file_id):
1814
return self._transform._tree.get_symlink_target(file_id)
1815
trans_id = self._transform.trans_id_file_id(file_id)
1816
name = self._transform._limbo_name(trans_id)
1817
return os.readlink(name)
1819
def walkdirs(self, prefix=''):
1820
pending = [self._transform.root]
1821
while len(pending) > 0:
1822
parent_id = pending.pop()
1825
prefix = prefix.rstrip('/')
1826
parent_path = self._final_paths.get_path(parent_id)
1827
parent_file_id = self._transform.final_file_id(parent_id)
1828
for child_id in self._all_children(parent_id):
1829
path_from_root = self._final_paths.get_path(child_id)
1830
basename = self._transform.final_name(child_id)
1831
file_id = self._transform.final_file_id(child_id)
1833
kind = self._transform.final_kind(child_id)
1834
versioned_kind = kind
1837
versioned_kind = self._transform._tree.stored_kind(file_id)
1838
if versioned_kind == 'directory':
1839
subdirs.append(child_id)
1840
children.append((path_from_root, basename, kind, None,
1841
file_id, versioned_kind))
1843
if parent_path.startswith(prefix):
1844
yield (parent_path, parent_file_id), children
1845
pending.extend(sorted(subdirs, key=self._final_paths.get_path,
1848
def get_parent_ids(self):
1849
return self._parent_ids
1851
def set_parent_ids(self, parent_ids):
1852
self._parent_ids = parent_ids
1854
def get_revision_tree(self, revision_id):
1855
return self._transform._tree.get_revision_tree(revision_id)
882
1858
def joinpath(parent, child):
883
1859
"""Join tree-relative paths, handling the tree root specially"""
884
1860
if parent is None or parent == "":
914
1890
self._known_paths[trans_id] = self._determine_path(trans_id)
915
1891
return self._known_paths[trans_id]
1893
def get_paths(self, trans_ids):
1894
return [(self.get_path(t), t) for t in trans_ids]
917
1898
def topology_sorted_ids(tree):
918
1899
"""Determine the topological order of the ids in a tree"""
919
1900
file_ids = list(tree)
920
1901
file_ids.sort(key=tree.id2path)
923
def build_tree(tree, wt):
924
"""Create working tree for a branch, using a Transaction."""
1905
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
1906
delta_from_tree=False):
1907
"""Create working tree for a branch, using a TreeTransform.
1909
This function should be used on empty trees, having a tree root at most.
1910
(see merge and revert functionality for working with existing trees)
1912
Existing files are handled like so:
1914
- Existing bzrdirs take precedence over creating new items. They are
1915
created as '%s.diverted' % name.
1916
- Otherwise, if the content on disk matches the content we are building,
1917
it is silently replaced.
1918
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1920
:param tree: The tree to convert wt into a copy of
1921
:param wt: The working tree that files will be placed into
1922
:param accelerator_tree: A tree which can be used for retrieving file
1923
contents more quickly than tree itself, i.e. a workingtree. tree
1924
will be used for cases where accelerator_tree's content is different.
1925
:param hardlink: If true, hard-link files to accelerator_tree, where
1926
possible. accelerator_tree must implement abspath, i.e. be a
1928
:param delta_from_tree: If true, build_tree may use the input Tree to
1929
generate the inventory delta.
1931
wt.lock_tree_write()
1935
if accelerator_tree is not None:
1936
accelerator_tree.lock_read()
1938
return _build_tree(tree, wt, accelerator_tree, hardlink,
1941
if accelerator_tree is not None:
1942
accelerator_tree.unlock()
1949
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
1950
"""See build_tree."""
1951
for num, _unused in enumerate(wt.all_file_ids()):
1952
if num > 0: # more than just a root
1953
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1954
existing_files = set()
1955
for dir, files in wt.walkdirs():
1956
existing_files.update(f[0] for f in files)
925
1957
file_trans_id = {}
926
1958
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
927
1959
pp = ProgressPhase("Build phase", 2, top_pb)
1960
if tree.inventory.root is not None:
1961
# This is kind of a hack: we should be altering the root
1962
# as part of the regular tree shape diff logic.
1963
# The conditional test here is to avoid doing an
1964
# expensive operation (flush) every time the root id
1965
# is set within the tree, nor setting the root and thus
1966
# marking the tree as dirty, because we use two different
1967
# idioms here: tree interfaces and inventory interfaces.
1968
if wt.get_root_id() != tree.get_root_id():
1969
wt.set_root_id(tree.get_root_id())
928
1971
tt = TreeTransform(wt)
931
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
932
file_ids = topology_sorted_ids(tree)
1975
file_trans_id[wt.get_root_id()] = \
1976
tt.trans_id_tree_file_id(wt.get_root_id())
933
1977
pb = bzrlib.ui.ui_factory.nested_progress_bar()
935
for num, file_id in enumerate(file_ids):
936
pb.update("Building tree", num, len(file_ids))
937
entry = tree.inventory[file_id]
1979
deferred_contents = []
1981
total = len(tree.inventory)
1983
precomputed_delta = []
1985
precomputed_delta = None
1986
for num, (tree_path, entry) in \
1987
enumerate(tree.inventory.iter_entries_by_dir()):
1988
pb.update("Building tree", num - len(deferred_contents), total)
938
1989
if entry.parent_id is None:
940
if entry.parent_id not in file_trans_id:
941
raise repr(entry.parent_id)
1992
file_id = entry.file_id
1994
precomputed_delta.append((None, tree_path, file_id, entry))
1995
if tree_path in existing_files:
1996
target_path = wt.abspath(tree_path)
1997
kind = file_kind(target_path)
1998
if kind == "directory":
2000
bzrdir.BzrDir.open(target_path)
2001
except errors.NotBranchError:
2005
if (file_id not in divert and
2006
_content_match(tree, entry, file_id, kind,
2008
tt.delete_contents(tt.trans_id_tree_path(tree_path))
2009
if kind == 'directory':
942
2011
parent_id = file_trans_id[entry.parent_id]
943
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
2012
if entry.kind == 'file':
2013
# We *almost* replicate new_by_entry, so that we can defer
2014
# getting the file text, and get them all at once.
2015
trans_id = tt.create_path(entry.name, parent_id)
2016
file_trans_id[file_id] = trans_id
2017
tt.version_file(file_id, trans_id)
2018
executable = tree.is_executable(file_id, tree_path)
2020
tt.set_executability(executable, trans_id)
2021
deferred_contents.append((file_id, trans_id))
2023
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
2026
new_trans_id = file_trans_id[file_id]
2027
old_parent = tt.trans_id_tree_path(tree_path)
2028
_reparent_children(tt, old_parent, new_trans_id)
2029
offset = num + 1 - len(deferred_contents)
2030
_create_files(tt, tree, deferred_contents, pb, offset,
2031
accelerator_tree, hardlink)
2035
divert_trans = set(file_trans_id[f] for f in divert)
2036
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
2037
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
2038
if len(raw_conflicts) > 0:
2039
precomputed_delta = None
2040
conflicts = cook_conflicts(raw_conflicts, tt)
2041
for conflict in conflicts:
2044
wt.add_conflicts(conflicts)
2045
except errors.UnsupportedOperation:
2047
result = tt.apply(no_conflicts=True,
2048
precomputed_delta=precomputed_delta)
951
2051
top_pb.finished()
2055
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
2057
total = len(desired_files) + offset
2058
if accelerator_tree is None:
2059
new_desired_files = desired_files
2061
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2062
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2063
in iter if not (c or e[0] != e[1]))
2064
new_desired_files = []
2066
for file_id, trans_id in desired_files:
2067
accelerator_path = unchanged.get(file_id)
2068
if accelerator_path is None:
2069
new_desired_files.append((file_id, trans_id))
2071
pb.update('Adding file contents', count + offset, total)
2073
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2076
contents = accelerator_tree.get_file(file_id, accelerator_path)
2078
tt.create_file(contents, trans_id)
2083
for count, (trans_id, contents) in enumerate(tree.iter_files_bytes(
2084
new_desired_files)):
2085
tt.create_file(contents, trans_id)
2086
pb.update('Adding file contents', count + offset, total)
2089
def _reparent_children(tt, old_parent, new_parent):
2090
for child in tt.iter_tree_children(old_parent):
2091
tt.adjust_path(tt.final_name(child), new_parent, child)
2093
def _reparent_transform_children(tt, old_parent, new_parent):
2094
by_parent = tt.by_parent()
2095
for child in by_parent[old_parent]:
2096
tt.adjust_path(tt.final_name(child), new_parent, child)
2097
return by_parent[old_parent]
2099
def _content_match(tree, entry, file_id, kind, target_path):
2100
if entry.kind != kind:
2102
if entry.kind == "directory":
2104
if entry.kind == "file":
2105
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
2107
elif entry.kind == "symlink":
2108
if tree.get_symlink_target(file_id) == os.readlink(target_path):
2113
def resolve_checkout(tt, conflicts, divert):
2114
new_conflicts = set()
2115
for c_type, conflict in ((c[0], c) for c in conflicts):
2116
# Anything but a 'duplicate' would indicate programmer error
2117
if c_type != 'duplicate':
2118
raise AssertionError(c_type)
2119
# Now figure out which is new and which is old
2120
if tt.new_contents(conflict[1]):
2121
new_file = conflict[1]
2122
old_file = conflict[2]
2124
new_file = conflict[2]
2125
old_file = conflict[1]
2127
# We should only get here if the conflict wasn't completely
2129
final_parent = tt.final_parent(old_file)
2130
if new_file in divert:
2131
new_name = tt.final_name(old_file)+'.diverted'
2132
tt.adjust_path(new_name, final_parent, new_file)
2133
new_conflicts.add((c_type, 'Diverted to',
2134
new_file, old_file))
2136
new_name = tt.final_name(old_file)+'.moved'
2137
tt.adjust_path(new_name, final_parent, old_file)
2138
new_conflicts.add((c_type, 'Moved existing file to',
2139
old_file, new_file))
2140
return new_conflicts
953
2143
def new_by_entry(tt, entry, parent_id, tree):
954
2144
"""Create a new file according to its inventory entry"""
1083
2245
return has_contents, contents_mod, meta_mod
1086
def revert(working_tree, target_tree, filenames, backups=False,
1087
pb=DummyProgress()):
2248
def revert(working_tree, target_tree, filenames, backups=False,
2249
pb=DummyProgress(), change_reporter=None):
1088
2250
"""Revert a working tree's contents to those of a target tree."""
1089
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1090
def interesting(file_id):
1091
return interesting_ids is None or file_id in interesting_ids
2251
target_tree.lock_read()
1093
2252
tt = TreeTransform(working_tree, pb)
1095
merge_modified = working_tree.merge_modified()
1097
def trans_id_file_id(file_id):
1099
return trans_id[file_id]
1101
return tt.trans_id_tree_file_id(file_id)
1103
pp = ProgressPhase("Revert phase", 4, pb)
1105
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1107
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1109
by_parent = tt.by_parent()
1110
for id_num, file_id in enumerate(sorted_interesting):
1111
child_pb.update("Reverting file", id_num+1,
1112
len(sorted_interesting))
1113
if file_id not in working_tree.inventory:
1114
entry = target_tree.inventory[file_id]
1115
parent_id = trans_id_file_id(entry.parent_id)
1116
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1117
trans_id[file_id] = e_trans_id
1119
backup_this = backups
1120
if file_id in merge_modified:
1122
del merge_modified[file_id]
1123
change_entry(tt, file_id, working_tree, target_tree,
1124
trans_id_file_id, backup_this, trans_id,
1129
wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1130
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1132
for id_num, file_id in enumerate(wt_interesting):
1133
child_pb.update("New file check", id_num+1,
1134
len(sorted_interesting))
1135
if file_id not in target_tree:
1136
trans_id = tt.trans_id_tree_file_id(file_id)
1137
tt.unversion_file(trans_id)
1138
if file_id in merge_modified:
2254
pp = ProgressPhase("Revert phase", 3, pb)
2255
conflicts, merge_modified = _prepare_revert_transform(
2256
working_tree, target_tree, tt, filenames, backups, pp)
2258
change_reporter = delta._ChangeReporter(
2259
unversioned_filter=working_tree.is_ignored)
2260
delta.report_changes(tt.iter_changes(), change_reporter)
2261
for conflict in conflicts:
2265
working_tree.set_merge_modified(merge_modified)
2267
target_tree.unlock()
2273
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2274
backups, pp, basis_tree=None,
2275
merge_modified=None):
2277
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2279
if merge_modified is None:
2280
merge_modified = working_tree.merge_modified()
2281
merge_modified = _alter_files(working_tree, target_tree, tt,
2282
child_pb, filenames, backups,
2283
merge_modified, basis_tree)
2287
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2289
raw_conflicts = resolve_conflicts(tt, child_pb,
2290
lambda t, c: conflict_pass(t, c, target_tree))
2293
conflicts = cook_conflicts(raw_conflicts, tt)
2294
return conflicts, merge_modified
2297
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
2298
backups, merge_modified, basis_tree=None):
2299
if basis_tree is not None:
2300
basis_tree.lock_read()
2301
change_list = target_tree.iter_changes(working_tree,
2302
specific_files=specific_files, pb=pb)
2303
if target_tree.get_root_id() is None:
2309
for id_num, (file_id, path, changed_content, versioned, parent, name,
2310
kind, executable) in enumerate(change_list):
2311
if skip_root and file_id[0] is not None and parent[0] is None:
2313
trans_id = tt.trans_id_file_id(file_id)
2316
keep_content = False
2317
if kind[0] == 'file' and (backups or kind[1] is None):
2318
wt_sha1 = working_tree.get_file_sha1(file_id)
2319
if merge_modified.get(file_id) != wt_sha1:
2320
# acquire the basis tree lazily to prevent the
2321
# expense of accessing it when it's not needed ?
2322
# (Guessing, RBC, 200702)
2323
if basis_tree is None:
2324
basis_tree = working_tree.basis_tree()
2325
basis_tree.lock_read()
2326
if file_id in basis_tree:
2327
if wt_sha1 != basis_tree.get_file_sha1(file_id):
2329
elif kind[1] is None and not versioned[1]:
2331
if kind[0] is not None:
2332
if not keep_content:
1139
2333
tt.delete_contents(trans_id)
1140
del merge_modified[file_id]
1144
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1146
raw_conflicts = resolve_conflicts(tt, child_pb)
1149
conflicts = cook_conflicts(raw_conflicts, tt)
1150
for conflict in conflicts:
1154
working_tree.set_merge_modified({})
2334
elif kind[1] is not None:
2335
parent_trans_id = tt.trans_id_file_id(parent[0])
2336
by_parent = tt.by_parent()
2337
backup_name = _get_backup_name(name[0], by_parent,
2338
parent_trans_id, tt)
2339
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2340
new_trans_id = tt.create_path(name[0], parent_trans_id)
2341
if versioned == (True, True):
2342
tt.unversion_file(trans_id)
2343
tt.version_file(file_id, new_trans_id)
2344
# New contents should have the same unix perms as old
2347
trans_id = new_trans_id
2348
if kind[1] in ('directory', 'tree-reference'):
2349
tt.create_directory(trans_id)
2350
if kind[1] == 'tree-reference':
2351
revision = target_tree.get_reference_revision(file_id,
2353
tt.set_tree_reference(revision, trans_id)
2354
elif kind[1] == 'symlink':
2355
tt.create_symlink(target_tree.get_symlink_target(file_id),
2357
elif kind[1] == 'file':
2358
deferred_files.append((file_id, (trans_id, mode_id)))
2359
if basis_tree is None:
2360
basis_tree = working_tree.basis_tree()
2361
basis_tree.lock_read()
2362
new_sha1 = target_tree.get_file_sha1(file_id)
2363
if (file_id in basis_tree and new_sha1 ==
2364
basis_tree.get_file_sha1(file_id)):
2365
if file_id in merge_modified:
2366
del merge_modified[file_id]
2368
merge_modified[file_id] = new_sha1
2370
# preserve the execute bit when backing up
2371
if keep_content and executable[0] == executable[1]:
2372
tt.set_executability(executable[1], trans_id)
2373
elif kind[1] is not None:
2374
raise AssertionError(kind[1])
2375
if versioned == (False, True):
2376
tt.version_file(file_id, trans_id)
2377
if versioned == (True, False):
2378
tt.unversion_file(trans_id)
2379
if (name[1] is not None and
2380
(name[0] != name[1] or parent[0] != parent[1])):
2381
if name[1] == '' and parent[1] is None:
2382
parent_trans = ROOT_PARENT
2384
parent_trans = tt.trans_id_file_id(parent[1])
2385
tt.adjust_path(name[1], parent_trans, trans_id)
2386
if executable[0] != executable[1] and kind[1] == "file":
2387
tt.set_executability(executable[1], trans_id)
2388
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2390
tt.create_file(bytes, trans_id, mode_id)
1161
def resolve_conflicts(tt, pb=DummyProgress()):
2392
if basis_tree is not None:
2394
return merge_modified
2397
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1162
2398
"""Make many conflict-resolution attempts, but die if they fail"""
2399
if pass_func is None:
2400
pass_func = conflict_pass
1163
2401
new_conflicts = set()
1165
2403
for n in range(10):