680
"""Apply all changes to the inventory and filesystem.
682
If filesystem or inventory conflicts are present, MalformedTransform
685
conflicts = self.find_conflicts()
686
if len(conflicts) != 0:
687
raise MalformedTransform(conflicts=conflicts)
689
inv = self._tree.inventory
690
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
692
child_pb.update('Apply phase', 0, 2)
693
self._apply_removals(inv, limbo_inv)
694
child_pb.update('Apply phase', 1, 2)
695
modified_paths = self._apply_insertions(inv, limbo_inv)
698
self._tree._write_inventory(inv)
701
return _TransformResults(modified_paths)
703
817
def _limbo_name(self, trans_id):
704
818
"""Generate the limbo name of a file"""
705
return pathjoin(self._limbodir, trans_id)
707
def _apply_removals(self, inv, limbo_inv):
708
"""Perform tree operations that remove directory/inventory names.
710
That is, delete files that are to be deleted, and put any files that
711
need renaming into limbo. This must be done in strict child-to-parent
714
tree_paths = list(self._tree_path_ids.iteritems())
715
tree_paths.sort(reverse=True)
716
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
718
for num, data in enumerate(tree_paths):
719
path, trans_id = data
720
child_pb.update('removing file', num, len(tree_paths))
721
full_path = self._tree.abspath(path)
722
if trans_id in self._removed_contents:
723
delete_any(full_path)
724
elif trans_id in self._new_name or trans_id in \
727
os.rename(full_path, self._limbo_name(trans_id))
729
if e.errno != errno.ENOENT:
731
if trans_id in self._removed_id:
732
if trans_id == self._new_root:
733
file_id = self._tree.inventory.root.file_id
819
limbo_name = self._limbo_files.get(trans_id)
820
if limbo_name is not None:
822
parent = self._new_parent.get(trans_id)
823
# if the parent directory is already in limbo (e.g. when building a
824
# tree), choose a limbo name inside the parent, to reduce further
826
use_direct_path = False
827
if self._new_contents.get(parent) == 'directory':
828
filename = self._new_name.get(trans_id)
829
if filename is not None:
830
if parent not in self._limbo_children:
831
self._limbo_children[parent] = set()
832
self._limbo_children_names[parent] = {}
833
use_direct_path = True
834
# the direct path can only be used if no other file has
835
# already taken this pathname, i.e. if the name is unused, or
836
# if it is already associated with this trans_id.
837
elif self._case_sensitive_target:
838
if (self._limbo_children_names[parent].get(filename)
839
in (trans_id, None)):
840
use_direct_path = True
842
for l_filename, l_trans_id in\
843
self._limbo_children_names[parent].iteritems():
844
if l_trans_id == trans_id:
846
if l_filename.lower() == filename.lower():
735
file_id = self.tree_file_id(trans_id)
737
elif trans_id in self._new_name or trans_id in self._new_parent:
738
file_id = self.tree_file_id(trans_id)
739
if file_id is not None:
740
limbo_inv[trans_id] = inv[file_id]
745
def _apply_insertions(self, inv, limbo_inv):
746
"""Perform tree operations that insert directory/inventory names.
748
That is, create any files that need to be created, and restore from
749
limbo any files that needed renaming. This must be done in strict
750
parent-to-child order.
752
new_paths = self.new_paths()
754
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
756
for num, (path, trans_id) in enumerate(new_paths):
757
child_pb.update('adding file', num, len(new_paths))
759
kind = self._new_contents[trans_id]
761
kind = contents = None
762
if trans_id in self._new_contents or \
763
self.path_changed(trans_id):
764
full_path = self._tree.abspath(path)
766
os.rename(self._limbo_name(trans_id), full_path)
768
# We may be renaming a dangling inventory id
769
if e.errno != errno.ENOENT:
771
if trans_id in self._new_contents:
772
modified_paths.append(full_path)
773
del self._new_contents[trans_id]
775
if trans_id in self._new_id:
777
kind = file_kind(self._tree.abspath(path))
778
inv.add_path(path, kind, self._new_id[trans_id])
779
elif trans_id in self._new_name or trans_id in\
781
entry = limbo_inv.get(trans_id)
782
if entry is not None:
783
entry.name = self.final_name(trans_id)
784
parent_path = os.path.dirname(path)
786
self._tree.inventory.path2id(parent_path)
789
# requires files and inventory entries to be in place
790
if trans_id in self._new_executability:
791
self._set_executability(path, inv, trans_id)
794
return modified_paths
796
def _set_executability(self, path, inv, trans_id):
849
use_direct_path = True
852
limbo_name = pathjoin(self._limbo_files[parent], filename)
853
self._limbo_children[parent].add(trans_id)
854
self._limbo_children_names[parent][filename] = trans_id
856
limbo_name = pathjoin(self._limbodir, trans_id)
857
self._needs_rename.add(trans_id)
858
self._limbo_files[trans_id] = limbo_name
861
def _set_executability(self, path, entry, trans_id):
797
862
"""Set the executability of versioned files """
798
file_id = inv.path2id(path)
799
863
new_executability = self._new_executability[trans_id]
800
inv[file_id].executable = new_executability
864
if entry is not None:
865
entry.executable = new_executability
801
866
if supports_executable():
802
867
abspath = self._tree.abspath(path)
803
868
current_mode = os.stat(abspath).st_mode
861
929
self.create_symlink(target, trans_id)
932
def _affected_ids(self):
933
"""Return the set of transform ids affected by the transform"""
934
trans_ids = set(self._removed_id)
935
trans_ids.update(self._new_id.keys())
936
trans_ids.update(self._removed_contents)
937
trans_ids.update(self._new_contents.keys())
938
trans_ids.update(self._new_executability.keys())
939
trans_ids.update(self._new_name.keys())
940
trans_ids.update(self._new_parent.keys())
943
def _get_file_id_maps(self):
944
"""Return mapping of file_ids to trans_ids in the to and from states"""
945
trans_ids = self._affected_ids()
948
# Build up two dicts: trans_ids associated with file ids in the
949
# FROM state, vs the TO state.
950
for trans_id in trans_ids:
951
from_file_id = self.tree_file_id(trans_id)
952
if from_file_id is not None:
953
from_trans_ids[from_file_id] = trans_id
954
to_file_id = self.final_file_id(trans_id)
955
if to_file_id is not None:
956
to_trans_ids[to_file_id] = trans_id
957
return from_trans_ids, to_trans_ids
959
def _from_file_data(self, from_trans_id, from_versioned, file_id):
960
"""Get data about a file in the from (tree) state
962
Return a (name, parent, kind, executable) tuple
964
from_path = self._tree_id_paths.get(from_trans_id)
966
# get data from working tree if versioned
967
from_entry = self._tree.inventory[file_id]
968
from_name = from_entry.name
969
from_parent = from_entry.parent_id
972
if from_path is None:
973
# File does not exist in FROM state
977
# File exists, but is not versioned. Have to use path-
979
from_name = os.path.basename(from_path)
980
tree_parent = self.get_tree_parent(from_trans_id)
981
from_parent = self.tree_file_id(tree_parent)
982
if from_path is not None:
983
from_kind, from_executable, from_stats = \
984
self._tree._comparison_data(from_entry, from_path)
987
from_executable = False
988
return from_name, from_parent, from_kind, from_executable
990
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
991
"""Get data about a file in the to (target) state
993
Return a (name, parent, kind, executable) tuple
995
to_name = self.final_name(to_trans_id)
997
to_kind = self.final_kind(to_trans_id)
1000
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1001
if to_trans_id in self._new_executability:
1002
to_executable = self._new_executability[to_trans_id]
1003
elif to_trans_id == from_trans_id:
1004
to_executable = from_executable
1006
to_executable = False
1007
return to_name, to_parent, to_kind, to_executable
1009
def iter_changes(self):
1010
"""Produce output in the same format as Tree.iter_changes.
1012
Will produce nonsensical results if invoked while inventory/filesystem
1013
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1015
This reads the Transform, but only reproduces changes involving a
1016
file_id. Files that are not versioned in either of the FROM or TO
1017
states are not reflected.
1019
final_paths = FinalPaths(self)
1020
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1022
# Now iterate through all active file_ids
1023
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1025
from_trans_id = from_trans_ids.get(file_id)
1026
# find file ids, and determine versioning state
1027
if from_trans_id is None:
1028
from_versioned = False
1029
from_trans_id = to_trans_ids[file_id]
1031
from_versioned = True
1032
to_trans_id = to_trans_ids.get(file_id)
1033
if to_trans_id is None:
1034
to_versioned = False
1035
to_trans_id = from_trans_id
1039
from_name, from_parent, from_kind, from_executable = \
1040
self._from_file_data(from_trans_id, from_versioned, file_id)
1042
to_name, to_parent, to_kind, to_executable = \
1043
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1045
if not from_versioned:
1048
from_path = self._tree_id_paths.get(from_trans_id)
1049
if not to_versioned:
1052
to_path = final_paths.get_path(to_trans_id)
1053
if from_kind != to_kind:
1055
elif to_kind in ('file', 'symlink') and (
1056
to_trans_id != from_trans_id or
1057
to_trans_id in self._new_contents):
1059
if (not modified and from_versioned == to_versioned and
1060
from_parent==to_parent and from_name == to_name and
1061
from_executable == to_executable):
1063
results.append((file_id, (from_path, to_path), modified,
1064
(from_versioned, to_versioned),
1065
(from_parent, to_parent),
1066
(from_name, to_name),
1067
(from_kind, to_kind),
1068
(from_executable, to_executable)))
1069
return iter(sorted(results, key=lambda x:x[1]))
1071
def get_preview_tree(self):
1072
"""Return a tree representing the result of the transform.
1074
This tree only supports the subset of Tree functionality required
1075
by show_diff_trees. It must only be compared to tt._tree.
1077
return _PreviewTree(self)
1080
class TreeTransform(TreeTransformBase):
1081
"""Represent a tree transformation.
1083
This object is designed to support incremental generation of the transform,
1086
However, it gives optimum performance when parent directories are created
1087
before their contents. The transform is then able to put child files
1088
directly in their parent directory, avoiding later renames.
1090
It is easy to produce malformed transforms, but they are generally
1091
harmless. Attempting to apply a malformed transform will cause an
1092
exception to be raised before any modifications are made to the tree.
1094
Many kinds of malformed transforms can be corrected with the
1095
resolve_conflicts function. The remaining ones indicate programming error,
1096
such as trying to create a file with no path.
1098
Two sets of file creation methods are supplied. Convenience methods are:
1103
These are composed of the low-level methods:
1105
* create_file or create_directory or create_symlink
1109
Transform/Transaction ids
1110
-------------------------
1111
trans_ids are temporary ids assigned to all files involved in a transform.
1112
It's possible, even common, that not all files in the Tree have trans_ids.
1114
trans_ids are used because filenames and file_ids are not good enough
1115
identifiers; filenames change, and not all files have file_ids. File-ids
1116
are also associated with trans-ids, so that moving a file moves its
1119
trans_ids are only valid for the TreeTransform that generated them.
1123
Limbo is a temporary directory use to hold new versions of files.
1124
Files are added to limbo by create_file, create_directory, create_symlink,
1125
and their convenience variants (new_*). Files may be removed from limbo
1126
using cancel_creation. Files are renamed from limbo into their final
1127
location as part of TreeTransform.apply
1129
Limbo must be cleaned up, by either calling TreeTransform.apply or
1130
calling TreeTransform.finalize.
1132
Files are placed into limbo inside their parent directories, where
1133
possible. This reduces subsequent renames, and makes operations involving
1134
lots of files faster. This optimization is only possible if the parent
1135
directory is created *before* creating any of its children, so avoid
1136
creating children before parents, where possible.
1140
This temporary directory is used by _FileMover for storing files that are
1141
about to be deleted. In case of rollback, the files will be restored.
1142
FileMover does not delete files until it is sure that a rollback will not
1145
def __init__(self, tree, pb=DummyProgress()):
1146
"""Note: a tree_write lock is taken on the tree.
1148
Use TreeTransform.finalize() to release the lock (can be omitted if
1149
TreeTransform.apply() called).
1151
tree.lock_tree_write()
1154
limbodir = urlutils.local_path_from_url(
1155
tree._transport.abspath('limbo'))
1159
if e.errno == errno.EEXIST:
1160
raise ExistingLimbo(limbodir)
1161
deletiondir = urlutils.local_path_from_url(
1162
tree._transport.abspath('pending-deletion'))
1164
os.mkdir(deletiondir)
1166
if e.errno == errno.EEXIST:
1167
raise errors.ExistingPendingDeletion(deletiondir)
1172
TreeTransformBase.__init__(self, tree, limbodir, pb,
1173
tree.case_sensitive)
1174
self._deletiondir = deletiondir
1176
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1177
"""Apply all changes to the inventory and filesystem.
1179
If filesystem or inventory conflicts are present, MalformedTransform
1182
If apply succeeds, finalize is not necessary.
1184
:param no_conflicts: if True, the caller guarantees there are no
1185
conflicts, so no check is made.
1186
:param precomputed_delta: An inventory delta to use instead of
1188
:param _mover: Supply an alternate FileMover, for testing
1190
if not no_conflicts:
1191
conflicts = self.find_conflicts()
1192
if len(conflicts) != 0:
1193
raise MalformedTransform(conflicts=conflicts)
1194
if precomputed_delta is None:
1195
new_inventory_delta = []
1196
inventory_delta = new_inventory_delta
1198
new_inventory_delta = None
1199
inventory_delta = precomputed_delta
1200
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1203
mover = _FileMover()
1207
child_pb.update('Apply phase', 0, 2)
1208
self._apply_removals(new_inventory_delta, mover)
1209
child_pb.update('Apply phase', 1, 2)
1210
modified_paths = self._apply_insertions(new_inventory_delta,
1216
mover.apply_deletions()
1219
self._tree.apply_inventory_delta(inventory_delta)
1222
return _TransformResults(modified_paths, self.rename_count)
1224
def _apply_removals(self, inventory_delta, mover):
1225
"""Perform tree operations that remove directory/inventory names.
1227
That is, delete files that are to be deleted, and put any files that
1228
need renaming into limbo. This must be done in strict child-to-parent
1231
If inventory_delta is None, no inventory delta generation is performed.
1233
tree_paths = list(self._tree_path_ids.iteritems())
1234
tree_paths.sort(reverse=True)
1235
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1237
for num, data in enumerate(tree_paths):
1238
path, trans_id = data
1239
child_pb.update('removing file', num, len(tree_paths))
1240
full_path = self._tree.abspath(path)
1241
if trans_id in self._removed_contents:
1242
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1244
elif trans_id in self._new_name or trans_id in \
1247
mover.rename(full_path, self._limbo_name(trans_id))
1249
if e.errno != errno.ENOENT:
1252
self.rename_count += 1
1253
if (trans_id in self._removed_id
1254
and inventory_delta is not None):
1255
if trans_id == self._new_root:
1256
file_id = self._tree.get_root_id()
1258
file_id = self.tree_file_id(trans_id)
1259
# File-id isn't really being deleted, just moved
1260
if file_id in self._r_new_id:
1262
inventory_delta.append((path, None, file_id, None))
1266
def _apply_insertions(self, inventory_delta, mover):
1267
"""Perform tree operations that insert directory/inventory names.
1269
That is, create any files that need to be created, and restore from
1270
limbo any files that needed renaming. This must be done in strict
1271
parent-to-child order.
1273
If inventory_delta is None, no inventory delta is calculated, and
1274
no list of modified paths is returned.
1276
new_paths = self.new_paths(filesystem_only=(inventory_delta is None))
1279
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1281
if inventory_delta is not None:
1282
entries = self._tree.iter_entries_by_dir(
1283
new_path_file_ids.values())
1284
old_paths = dict((e.file_id, p) for p, e in entries)
1285
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1287
for num, (path, trans_id) in enumerate(new_paths):
1290
child_pb.update('adding file', num, len(new_paths))
1291
full_path = self._tree.abspath(path)
1292
if trans_id in self._needs_rename:
1294
mover.rename(self._limbo_name(trans_id), full_path)
1296
# We may be renaming a dangling inventory id
1297
if e.errno != errno.ENOENT:
1300
self.rename_count += 1
1301
if inventory_delta is not None:
1302
if (trans_id in self._new_contents or
1303
self.path_changed(trans_id)):
1304
if trans_id in self._new_contents:
1305
modified_paths.append(full_path)
1306
completed_new.append(trans_id)
1307
file_id = new_path_file_ids[trans_id]
1308
if file_id is not None and (trans_id in self._new_id or
1309
trans_id in self._new_name or
1310
trans_id in self._new_parent
1311
or trans_id in self._new_executability):
1313
kind = self.final_kind(trans_id)
1315
kind = self._tree.stored_kind(file_id)
1316
parent_trans_id = self.final_parent(trans_id)
1317
parent_file_id = new_path_file_ids.get(parent_trans_id)
1318
if parent_file_id is None:
1319
parent_file_id = self.final_file_id(
1321
if trans_id in self._new_reference_revision:
1322
new_entry = inventory.TreeReference(
1324
self._new_name[trans_id],
1325
self.final_file_id(self._new_parent[trans_id]),
1326
None, self._new_reference_revision[trans_id])
1328
new_entry = inventory.make_entry(kind,
1329
self.final_name(trans_id),
1330
parent_file_id, file_id)
1331
old_path = old_paths.get(new_entry.file_id)
1332
inventory_delta.append(
1333
(old_path, path, new_entry.file_id, new_entry))
1335
if trans_id in self._new_executability:
1336
self._set_executability(path, new_entry, trans_id)
1339
if inventory_delta is None:
1340
self._new_contents.clear()
1342
for trans_id in completed_new:
1343
del self._new_contents[trans_id]
1344
return modified_paths
1347
class TransformPreview(TreeTransformBase):
1348
"""A TreeTransform for generating preview trees.
1350
Unlike TreeTransform, this version works when the input tree is a
1351
RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
1352
unversioned files in the input tree.
1355
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1357
limbodir = tempfile.mkdtemp(prefix='bzr-limbo-')
1358
TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1360
def canonical_path(self, path):
1363
def tree_kind(self, trans_id):
1364
path = self._tree_id_paths.get(trans_id)
1366
raise NoSuchFile(None)
1367
file_id = self._tree.path2id(path)
1368
return self._tree.kind(file_id)
1370
def _set_mode(self, trans_id, mode_id, typefunc):
1371
"""Set the mode of new file contents.
1372
The mode_id is the existing file to get the mode from (often the same
1373
as trans_id). The operation is only performed if there's a mode match
1374
according to typefunc.
1376
# is it ok to ignore this? probably
1379
def iter_tree_children(self, parent_id):
1380
"""Iterate through the entry's tree children, if any"""
1382
path = self._tree_id_paths[parent_id]
1385
file_id = self.tree_file_id(parent_id)
1386
for child in self._tree.inventory[file_id].children.iterkeys():
1387
childpath = joinpath(path, child)
1388
yield self.trans_id_tree_path(childpath)
1391
class _PreviewTree(tree.Tree):
1392
"""Partial implementation of Tree to support show_diff_trees"""
1394
def __init__(self, transform):
1395
self._transform = transform
1396
self._final_paths = FinalPaths(transform)
1398
def _changes(self, file_id):
1399
for changes in self._transform.iter_changes():
1400
if changes[0] == file_id:
1403
def _content_change(self, file_id):
1404
"""Return True if the content of this file changed"""
1405
changes = self._changes(file_id)
1406
# changes[2] is true if the file content changed. See
1407
# InterTree.iter_changes.
1408
return (changes is not None and changes[2])
1410
def _get_file_revision(self, file_id, vf, tree_revision):
1411
return self._transform._tree._get_file_revision(file_id, vf,
1414
def _stat_limbo_file(self, file_id):
1415
trans_id = self._transform.trans_id_file_id(file_id)
1416
name = self._transform._limbo_name(trans_id)
1417
return os.lstat(name)
1419
def lock_read(self):
1420
# Perhaps in theory, this should lock the TreeTransform?
1427
def inventory(self):
1428
"""This Tree does not use inventory as its backing data."""
1429
raise NotImplementedError(_PreviewTree.inventory)
1431
def get_root_id(self):
1432
return self._transform.final_file_id(self._transform.root)
1434
def all_file_ids(self):
1435
return self._transform._tree.all_file_ids()
1438
return iter(self.all_file_ids())
1440
def paths2ids(self, specific_files, trees=None, require_versioned=False):
1441
"""See Tree.paths2ids"""
1442
to_find = set(specific_files)
1444
for (file_id, paths, changed, versioned, parent, name, kind,
1445
executable) in self._transform.iter_changes():
1446
if paths[1] in to_find:
1447
result.append(file_id)
1448
to_find.remove(paths[1])
1449
result.update(self._transform._tree.paths2ids(to_find,
1450
trees=[], require_versioned=require_versioned))
1453
def path2id(self, path):
1454
return self._transform._tree.path2id(path)
1456
def id2path(self, file_id):
1457
trans_id = self._transform.trans_id_file_id(file_id)
1459
return self._final_paths._determine_path(trans_id)
1461
raise errors.NoSuchId(self, file_id)
1463
def iter_entries_by_dir(self, specific_file_ids=None):
1464
return self._transform._tree.iter_entries_by_dir(specific_file_ids)
1466
def kind(self, file_id):
1467
trans_id = self._transform.trans_id_file_id(file_id)
1468
return self._transform.final_kind(trans_id)
1470
def stored_kind(self, file_id):
1471
return self._transform._tree.stored_kind(file_id)
1473
def get_file_mtime(self, file_id, path=None):
1474
"""See Tree.get_file_mtime"""
1475
if not self._content_change(file_id):
1476
return self._transform._tree.get_file_mtime(file_id, path)
1477
return self._stat_limbo_file(file_id).st_mtime
1479
def get_file_size(self, file_id):
1480
"""See Tree.get_file_size"""
1481
if self.kind(file_id) == 'file':
1482
return self._transform._tree.get_file_size(file_id)
1486
def get_file_sha1(self, file_id, path=None, stat_value=None):
1487
return self._transform._tree.get_file_sha1(file_id)
1489
def is_executable(self, file_id, path=None):
1490
return self._transform._tree.is_executable(file_id, path)
1492
def path_content_summary(self, path):
1493
return self._transform._tree.path_content_summary(path)
1495
def iter_changes(self, from_tree, include_unchanged=False,
1496
specific_files=None, pb=None, extra_trees=None,
1497
require_versioned=True, want_unversioned=False):
1498
"""See InterTree.iter_changes.
1500
This implementation does not support include_unchanged, specific_files,
1501
or want_unversioned. extra_trees, require_versioned, and pb are
1504
if from_tree is not self._transform._tree:
1505
raise ValueError('from_tree must be transform source tree.')
1506
if include_unchanged:
1507
raise ValueError('include_unchanged is not supported')
1508
if specific_files is not None:
1509
raise ValueError('specific_files is not supported')
1510
if want_unversioned:
1511
raise ValueError('want_unversioned is not supported')
1512
return self._transform.iter_changes()
1514
def get_file(self, file_id, path=None):
1515
"""See Tree.get_file"""
1516
if not self._content_change(file_id):
1517
return self._transform._tree.get_file(file_id, path)
1518
trans_id = self._transform.trans_id_file_id(file_id)
1519
name = self._transform._limbo_name(trans_id)
1520
return open(name, 'rb')
1522
def get_file_text(self, file_id):
1523
text_file = self.get_file(file_id)
1525
return text_file.read()
1529
def annotate_iter(self, file_id,
1530
default_revision=_mod_revision.CURRENT_REVISION):
1531
return self._transform._tree.annotate_iter(file_id,
1532
default_revision=default_revision)
1534
def get_symlink_target(self, file_id):
1535
"""See Tree.get_symlink_target"""
1536
if not self._content_change(file_id):
1537
return self._transform._tree.get_symlink_target(file_id)
1538
trans_id = self._transform.trans_id_file_id(file_id)
1539
name = self._transform._limbo_name(trans_id)
1540
return os.readlink(name)
1542
def list_files(self, include_root=False):
1543
return self._transform._tree.list_files(include_root)
1545
def walkdirs(self, prefix=""):
1546
return self._transform._tree.walkdirs(prefix)
1548
def get_parent_ids(self):
1549
return self._transform._tree.get_parent_ids()
1551
def get_revision_tree(self, revision_id):
1552
return self._transform._tree.get_revision_tree(revision_id)
864
1555
def joinpath(parent, child):
865
1556
"""Join tree-relative paths, handling the tree root specially"""
866
1557
if parent is None or parent == "":
896
1587
self._known_paths[trans_id] = self._determine_path(trans_id)
897
1588
return self._known_paths[trans_id]
1590
def get_paths(self, trans_ids):
1591
return [(self.get_path(t), t) for t in trans_ids]
899
1595
def topology_sorted_ids(tree):
900
1596
"""Determine the topological order of the ids in a tree"""
901
1597
file_ids = list(tree)
902
1598
file_ids.sort(key=tree.id2path)
905
def build_tree(tree, wt):
906
"""Create working tree for a branch, using a Transaction."""
1602
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
1603
delta_from_tree=False):
1604
"""Create working tree for a branch, using a TreeTransform.
1606
This function should be used on empty trees, having a tree root at most.
1607
(see merge and revert functionality for working with existing trees)
1609
Existing files are handled like so:
1611
- Existing bzrdirs take precedence over creating new items. They are
1612
created as '%s.diverted' % name.
1613
- Otherwise, if the content on disk matches the content we are building,
1614
it is silently replaced.
1615
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1617
:param tree: The tree to convert wt into a copy of
1618
:param wt: The working tree that files will be placed into
1619
:param accelerator_tree: A tree which can be used for retrieving file
1620
contents more quickly than tree itself, i.e. a workingtree. tree
1621
will be used for cases where accelerator_tree's content is different.
1622
:param hardlink: If true, hard-link files to accelerator_tree, where
1623
possible. accelerator_tree must implement abspath, i.e. be a
1625
:param delta_from_tree: If true, build_tree may use the input Tree to
1626
generate the inventory delta.
1628
wt.lock_tree_write()
1632
if accelerator_tree is not None:
1633
accelerator_tree.lock_read()
1635
return _build_tree(tree, wt, accelerator_tree, hardlink,
1638
if accelerator_tree is not None:
1639
accelerator_tree.unlock()
1646
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
1647
"""See build_tree."""
1648
for num, _unused in enumerate(wt.all_file_ids()):
1649
if num > 0: # more than just a root
1650
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1651
existing_files = set()
1652
for dir, files in wt.walkdirs():
1653
existing_files.update(f[0] for f in files)
907
1654
file_trans_id = {}
908
1655
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
1656
pp = ProgressPhase("Build phase", 2, top_pb)
1657
if tree.inventory.root is not None:
1658
# This is kind of a hack: we should be altering the root
1659
# as part of the regular tree shape diff logic.
1660
# The conditional test here is to avoid doing an
1661
# expensive operation (flush) every time the root id
1662
# is set within the tree, nor setting the root and thus
1663
# marking the tree as dirty, because we use two different
1664
# idioms here: tree interfaces and inventory interfaces.
1665
if wt.get_root_id() != tree.get_root_id():
1666
wt.set_root_id(tree.get_root_id())
910
1668
tt = TreeTransform(wt)
913
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
914
file_ids = topology_sorted_ids(tree)
1672
file_trans_id[wt.get_root_id()] = \
1673
tt.trans_id_tree_file_id(wt.get_root_id())
915
1674
pb = bzrlib.ui.ui_factory.nested_progress_bar()
917
for num, file_id in enumerate(file_ids):
918
pb.update("Building tree", num, len(file_ids))
919
entry = tree.inventory[file_id]
1676
deferred_contents = []
1678
total = len(tree.inventory)
1680
precomputed_delta = []
1682
precomputed_delta = None
1683
for num, (tree_path, entry) in \
1684
enumerate(tree.inventory.iter_entries_by_dir()):
1685
pb.update("Building tree", num - len(deferred_contents), total)
920
1686
if entry.parent_id is None:
922
if entry.parent_id not in file_trans_id:
923
raise repr(entry.parent_id)
1689
file_id = entry.file_id
1691
precomputed_delta.append((None, tree_path, file_id, entry))
1692
if tree_path in existing_files:
1693
target_path = wt.abspath(tree_path)
1694
kind = file_kind(target_path)
1695
if kind == "directory":
1697
bzrdir.BzrDir.open(target_path)
1698
except errors.NotBranchError:
1702
if (file_id not in divert and
1703
_content_match(tree, entry, file_id, kind,
1705
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1706
if kind == 'directory':
924
1708
parent_id = file_trans_id[entry.parent_id]
925
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1709
if entry.kind == 'file':
1710
# We *almost* replicate new_by_entry, so that we can defer
1711
# getting the file text, and get them all at once.
1712
trans_id = tt.create_path(entry.name, parent_id)
1713
file_trans_id[file_id] = trans_id
1714
tt.version_file(file_id, trans_id)
1715
executable = tree.is_executable(file_id, tree_path)
1717
tt.set_executability(executable, trans_id)
1718
deferred_contents.append((file_id, trans_id))
1720
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1723
new_trans_id = file_trans_id[file_id]
1724
old_parent = tt.trans_id_tree_path(tree_path)
1725
_reparent_children(tt, old_parent, new_trans_id)
1726
offset = num + 1 - len(deferred_contents)
1727
_create_files(tt, tree, deferred_contents, pb, offset,
1728
accelerator_tree, hardlink)
1732
divert_trans = set(file_trans_id[f] for f in divert)
1733
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1734
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1735
if len(raw_conflicts) > 0:
1736
precomputed_delta = None
1737
conflicts = cook_conflicts(raw_conflicts, tt)
1738
for conflict in conflicts:
1741
wt.add_conflicts(conflicts)
1742
except errors.UnsupportedOperation:
1744
result = tt.apply(no_conflicts=True,
1745
precomputed_delta=precomputed_delta)
933
1748
top_pb.finished()
1752
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
1754
total = len(desired_files) + offset
1755
if accelerator_tree is None:
1756
new_desired_files = desired_files
1758
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
1759
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
1760
in iter if not (c or e[0] != e[1]))
1761
new_desired_files = []
1763
for file_id, trans_id in desired_files:
1764
accelerator_path = unchanged.get(file_id)
1765
if accelerator_path is None:
1766
new_desired_files.append((file_id, trans_id))
1768
pb.update('Adding file contents', count + offset, total)
1770
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
1773
contents = accelerator_tree.get_file(file_id, accelerator_path)
1775
tt.create_file(contents, trans_id)
1780
for count, (trans_id, contents) in enumerate(tree.iter_files_bytes(
1781
new_desired_files)):
1782
tt.create_file(contents, trans_id)
1783
pb.update('Adding file contents', count + offset, total)
1786
def _reparent_children(tt, old_parent, new_parent):
1787
for child in tt.iter_tree_children(old_parent):
1788
tt.adjust_path(tt.final_name(child), new_parent, child)
1790
def _reparent_transform_children(tt, old_parent, new_parent):
1791
by_parent = tt.by_parent()
1792
for child in by_parent[old_parent]:
1793
tt.adjust_path(tt.final_name(child), new_parent, child)
1794
return by_parent[old_parent]
1796
def _content_match(tree, entry, file_id, kind, target_path):
1797
if entry.kind != kind:
1799
if entry.kind == "directory":
1801
if entry.kind == "file":
1802
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1804
elif entry.kind == "symlink":
1805
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1810
def resolve_checkout(tt, conflicts, divert):
1811
new_conflicts = set()
1812
for c_type, conflict in ((c[0], c) for c in conflicts):
1813
# Anything but a 'duplicate' would indicate programmer error
1814
if c_type != 'duplicate':
1815
raise AssertionError(c_type)
1816
# Now figure out which is new and which is old
1817
if tt.new_contents(conflict[1]):
1818
new_file = conflict[1]
1819
old_file = conflict[2]
1821
new_file = conflict[2]
1822
old_file = conflict[1]
1824
# We should only get here if the conflict wasn't completely
1826
final_parent = tt.final_parent(old_file)
1827
if new_file in divert:
1828
new_name = tt.final_name(old_file)+'.diverted'
1829
tt.adjust_path(new_name, final_parent, new_file)
1830
new_conflicts.add((c_type, 'Diverted to',
1831
new_file, old_file))
1833
new_name = tt.final_name(old_file)+'.moved'
1834
tt.adjust_path(new_name, final_parent, old_file)
1835
new_conflicts.add((c_type, 'Moved existing file to',
1836
old_file, new_file))
1837
return new_conflicts
935
1840
def new_by_entry(tt, entry, parent_id, tree):
936
1841
"""Create a new file according to its inventory entry"""
1065
1919
return has_contents, contents_mod, meta_mod
1068
def revert(working_tree, target_tree, filenames, backups=False,
1069
pb=DummyProgress()):
1922
def revert(working_tree, target_tree, filenames, backups=False,
1923
pb=DummyProgress(), change_reporter=None):
1070
1924
"""Revert a working tree's contents to those of a target tree."""
1071
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1072
def interesting(file_id):
1073
return interesting_ids is None or file_id in interesting_ids
1925
target_tree.lock_read()
1075
1926
tt = TreeTransform(working_tree, pb)
1077
merge_modified = working_tree.merge_modified()
1079
def trans_id_file_id(file_id):
1081
return trans_id[file_id]
1083
return tt.trans_id_tree_file_id(file_id)
1085
pp = ProgressPhase("Revert phase", 4, pb)
1087
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1089
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1091
by_parent = tt.by_parent()
1092
for id_num, file_id in enumerate(sorted_interesting):
1093
child_pb.update("Reverting file", id_num+1,
1094
len(sorted_interesting))
1095
if file_id not in working_tree.inventory:
1096
entry = target_tree.inventory[file_id]
1097
parent_id = trans_id_file_id(entry.parent_id)
1098
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1099
trans_id[file_id] = e_trans_id
1101
backup_this = backups
1102
if file_id in merge_modified:
1104
del merge_modified[file_id]
1105
change_entry(tt, file_id, working_tree, target_tree,
1106
trans_id_file_id, backup_this, trans_id,
1111
wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1112
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1114
for id_num, file_id in enumerate(wt_interesting):
1115
child_pb.update("New file check", id_num+1,
1116
len(sorted_interesting))
1117
if file_id not in target_tree:
1118
trans_id = tt.trans_id_tree_file_id(file_id)
1119
tt.unversion_file(trans_id)
1120
if file_id in merge_modified:
1928
pp = ProgressPhase("Revert phase", 3, pb)
1930
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1932
merge_modified = _alter_files(working_tree, target_tree, tt,
1933
child_pb, filenames, backups)
1937
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1939
raw_conflicts = resolve_conflicts(tt, child_pb,
1940
lambda t, c: conflict_pass(t, c, target_tree))
1943
conflicts = cook_conflicts(raw_conflicts, tt)
1945
change_reporter = delta._ChangeReporter(
1946
unversioned_filter=working_tree.is_ignored)
1947
delta.report_changes(tt.iter_changes(), change_reporter)
1948
for conflict in conflicts:
1952
working_tree.set_merge_modified(merge_modified)
1954
target_tree.unlock()
1960
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1962
merge_modified = working_tree.merge_modified()
1963
change_list = target_tree.iter_changes(working_tree,
1964
specific_files=specific_files, pb=pb)
1965
if target_tree.inventory.root is None:
1972
for id_num, (file_id, path, changed_content, versioned, parent, name,
1973
kind, executable) in enumerate(change_list):
1974
if skip_root and file_id[0] is not None and parent[0] is None:
1976
trans_id = tt.trans_id_file_id(file_id)
1979
keep_content = False
1980
if kind[0] == 'file' and (backups or kind[1] is None):
1981
wt_sha1 = working_tree.get_file_sha1(file_id)
1982
if merge_modified.get(file_id) != wt_sha1:
1983
# acquire the basis tree lazily to prevent the
1984
# expense of accessing it when it's not needed ?
1985
# (Guessing, RBC, 200702)
1986
if basis_tree is None:
1987
basis_tree = working_tree.basis_tree()
1988
basis_tree.lock_read()
1989
if file_id in basis_tree:
1990
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1992
elif kind[1] is None and not versioned[1]:
1994
if kind[0] is not None:
1995
if not keep_content:
1121
1996
tt.delete_contents(trans_id)
1122
del merge_modified[file_id]
1126
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1128
raw_conflicts = resolve_conflicts(tt, child_pb)
1131
conflicts = cook_conflicts(raw_conflicts, tt)
1132
for conflict in conflicts:
1136
working_tree.set_merge_modified({})
1997
elif kind[1] is not None:
1998
parent_trans_id = tt.trans_id_file_id(parent[0])
1999
by_parent = tt.by_parent()
2000
backup_name = _get_backup_name(name[0], by_parent,
2001
parent_trans_id, tt)
2002
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2003
new_trans_id = tt.create_path(name[0], parent_trans_id)
2004
if versioned == (True, True):
2005
tt.unversion_file(trans_id)
2006
tt.version_file(file_id, new_trans_id)
2007
# New contents should have the same unix perms as old
2010
trans_id = new_trans_id
2011
if kind[1] == 'directory':
2012
tt.create_directory(trans_id)
2013
elif kind[1] == 'symlink':
2014
tt.create_symlink(target_tree.get_symlink_target(file_id),
2016
elif kind[1] == 'file':
2017
deferred_files.append((file_id, (trans_id, mode_id)))
2018
if basis_tree is None:
2019
basis_tree = working_tree.basis_tree()
2020
basis_tree.lock_read()
2021
new_sha1 = target_tree.get_file_sha1(file_id)
2022
if (file_id in basis_tree and new_sha1 ==
2023
basis_tree.get_file_sha1(file_id)):
2024
if file_id in merge_modified:
2025
del merge_modified[file_id]
2027
merge_modified[file_id] = new_sha1
2029
# preserve the execute bit when backing up
2030
if keep_content and executable[0] == executable[1]:
2031
tt.set_executability(executable[1], trans_id)
2032
elif kind[1] is not None:
2033
raise AssertionError(kind[1])
2034
if versioned == (False, True):
2035
tt.version_file(file_id, trans_id)
2036
if versioned == (True, False):
2037
tt.unversion_file(trans_id)
2038
if (name[1] is not None and
2039
(name[0] != name[1] or parent[0] != parent[1])):
2041
name[1], tt.trans_id_file_id(parent[1]), trans_id)
2042
if executable[0] != executable[1] and kind[1] == "file":
2043
tt.set_executability(executable[1], trans_id)
2044
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2046
tt.create_file(bytes, trans_id, mode_id)
1143
def resolve_conflicts(tt, pb=DummyProgress()):
2048
if basis_tree is not None:
2050
return merge_modified
2053
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1144
2054
"""Make many conflict-resolution attempts, but die if they fail"""
2055
if pass_func is None:
2056
pass_func = conflict_pass
1145
2057
new_conflicts = set()
1147
2059
for n in range(10):