714
"""Apply all changes to the inventory and filesystem.
716
If filesystem or inventory conflicts are present, MalformedTransform
719
conflicts = self.find_conflicts()
720
if len(conflicts) != 0:
721
raise MalformedTransform(conflicts=conflicts)
723
inv = self._tree.inventory
724
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
726
child_pb.update('Apply phase', 0, 2)
727
self._apply_removals(inv, limbo_inv)
728
child_pb.update('Apply phase', 1, 2)
729
modified_paths = self._apply_insertions(inv, limbo_inv)
732
self._tree._write_inventory(inv)
735
return _TransformResults(modified_paths)
737
850
def _limbo_name(self, trans_id):
738
851
"""Generate the limbo name of a file"""
739
return pathjoin(self._limbodir, trans_id)
741
def _apply_removals(self, inv, limbo_inv):
742
"""Perform tree operations that remove directory/inventory names.
744
That is, delete files that are to be deleted, and put any files that
745
need renaming into limbo. This must be done in strict child-to-parent
748
tree_paths = list(self._tree_path_ids.iteritems())
749
tree_paths.sort(reverse=True)
750
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
752
for num, data in enumerate(tree_paths):
753
path, trans_id = data
754
child_pb.update('removing file', num, len(tree_paths))
755
full_path = self._tree.abspath(path)
756
if trans_id in self._removed_contents:
757
delete_any(full_path)
758
elif trans_id in self._new_name or trans_id in \
761
os.rename(full_path, self._limbo_name(trans_id))
763
if e.errno != errno.ENOENT:
765
if trans_id in self._removed_id:
766
if trans_id == self._new_root:
767
file_id = self._tree.inventory.root.file_id
852
limbo_name = self._limbo_files.get(trans_id)
853
if limbo_name is not None:
855
parent = self._new_parent.get(trans_id)
856
# if the parent directory is already in limbo (e.g. when building a
857
# tree), choose a limbo name inside the parent, to reduce further
859
use_direct_path = False
860
if self._new_contents.get(parent) == 'directory':
861
filename = self._new_name.get(trans_id)
862
if filename is not None:
863
if parent not in self._limbo_children:
864
self._limbo_children[parent] = set()
865
self._limbo_children_names[parent] = {}
866
use_direct_path = True
867
# the direct path can only be used if no other file has
868
# already taken this pathname, i.e. if the name is unused, or
869
# if it is already associated with this trans_id.
870
elif self._case_sensitive_target:
871
if (self._limbo_children_names[parent].get(filename)
872
in (trans_id, None)):
873
use_direct_path = True
875
for l_filename, l_trans_id in\
876
self._limbo_children_names[parent].iteritems():
877
if l_trans_id == trans_id:
879
if l_filename.lower() == filename.lower():
769
file_id = self.tree_file_id(trans_id)
771
elif trans_id in self._new_name or trans_id in self._new_parent:
772
file_id = self.tree_file_id(trans_id)
773
if file_id is not None:
774
limbo_inv[trans_id] = inv[file_id]
779
def _apply_insertions(self, inv, limbo_inv):
780
"""Perform tree operations that insert directory/inventory names.
782
That is, create any files that need to be created, and restore from
783
limbo any files that needed renaming. This must be done in strict
784
parent-to-child order.
786
new_paths = self.new_paths()
788
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
790
for num, (path, trans_id) in enumerate(new_paths):
791
child_pb.update('adding file', num, len(new_paths))
793
kind = self._new_contents[trans_id]
795
kind = contents = None
796
if trans_id in self._new_contents or \
797
self.path_changed(trans_id):
798
full_path = self._tree.abspath(path)
800
os.rename(self._limbo_name(trans_id), full_path)
802
# We may be renaming a dangling inventory id
803
if e.errno != errno.ENOENT:
805
if trans_id in self._new_contents:
806
modified_paths.append(full_path)
807
del self._new_contents[trans_id]
809
if trans_id in self._new_id:
811
kind = file_kind(self._tree.abspath(path))
812
inv.add_path(path, kind, self._new_id[trans_id])
813
elif trans_id in self._new_name or trans_id in\
815
entry = limbo_inv.get(trans_id)
816
if entry is not None:
817
entry.name = self.final_name(trans_id)
818
parent_path = os.path.dirname(path)
820
self._tree.inventory.path2id(parent_path)
823
# requires files and inventory entries to be in place
824
if trans_id in self._new_executability:
825
self._set_executability(path, inv, trans_id)
828
return modified_paths
830
def _set_executability(self, path, inv, trans_id):
882
use_direct_path = True
885
limbo_name = pathjoin(self._limbo_files[parent], filename)
886
self._limbo_children[parent].add(trans_id)
887
self._limbo_children_names[parent][filename] = trans_id
889
limbo_name = pathjoin(self._limbodir, trans_id)
890
self._needs_rename.add(trans_id)
891
self._limbo_files[trans_id] = limbo_name
894
def _set_executability(self, path, trans_id):
831
895
"""Set the executability of versioned files """
832
file_id = inv.path2id(path)
833
new_executability = self._new_executability[trans_id]
834
inv[file_id].executable = new_executability
835
896
if supports_executable():
897
new_executability = self._new_executability[trans_id]
836
898
abspath = self._tree.abspath(path)
837
899
current_mode = os.stat(abspath).st_mode
838
900
if new_executability:
898
960
self.create_symlink(target, trans_id)
963
def _affected_ids(self):
964
"""Return the set of transform ids affected by the transform"""
965
trans_ids = set(self._removed_id)
966
trans_ids.update(self._new_id.keys())
967
trans_ids.update(self._removed_contents)
968
trans_ids.update(self._new_contents.keys())
969
trans_ids.update(self._new_executability.keys())
970
trans_ids.update(self._new_name.keys())
971
trans_ids.update(self._new_parent.keys())
974
def _get_file_id_maps(self):
975
"""Return mapping of file_ids to trans_ids in the to and from states"""
976
trans_ids = self._affected_ids()
979
# Build up two dicts: trans_ids associated with file ids in the
980
# FROM state, vs the TO state.
981
for trans_id in trans_ids:
982
from_file_id = self.tree_file_id(trans_id)
983
if from_file_id is not None:
984
from_trans_ids[from_file_id] = trans_id
985
to_file_id = self.final_file_id(trans_id)
986
if to_file_id is not None:
987
to_trans_ids[to_file_id] = trans_id
988
return from_trans_ids, to_trans_ids
990
def _from_file_data(self, from_trans_id, from_versioned, file_id):
991
"""Get data about a file in the from (tree) state
993
Return a (name, parent, kind, executable) tuple
995
from_path = self._tree_id_paths.get(from_trans_id)
997
# get data from working tree if versioned
998
from_entry = self._tree.inventory[file_id]
999
from_name = from_entry.name
1000
from_parent = from_entry.parent_id
1003
if from_path is None:
1004
# File does not exist in FROM state
1008
# File exists, but is not versioned. Have to use path-
1010
from_name = os.path.basename(from_path)
1011
tree_parent = self.get_tree_parent(from_trans_id)
1012
from_parent = self.tree_file_id(tree_parent)
1013
if from_path is not None:
1014
from_kind, from_executable, from_stats = \
1015
self._tree._comparison_data(from_entry, from_path)
1018
from_executable = False
1019
return from_name, from_parent, from_kind, from_executable
1021
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1022
"""Get data about a file in the to (target) state
1024
Return a (name, parent, kind, executable) tuple
1026
to_name = self.final_name(to_trans_id)
1028
to_kind = self.final_kind(to_trans_id)
1031
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1032
if to_trans_id in self._new_executability:
1033
to_executable = self._new_executability[to_trans_id]
1034
elif to_trans_id == from_trans_id:
1035
to_executable = from_executable
1037
to_executable = False
1038
return to_name, to_parent, to_kind, to_executable
1040
def iter_changes(self):
1041
"""Produce output in the same format as Tree.iter_changes.
1043
Will produce nonsensical results if invoked while inventory/filesystem
1044
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1046
This reads the Transform, but only reproduces changes involving a
1047
file_id. Files that are not versioned in either of the FROM or TO
1048
states are not reflected.
1050
final_paths = FinalPaths(self)
1051
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1053
# Now iterate through all active file_ids
1054
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1056
from_trans_id = from_trans_ids.get(file_id)
1057
# find file ids, and determine versioning state
1058
if from_trans_id is None:
1059
from_versioned = False
1060
from_trans_id = to_trans_ids[file_id]
1062
from_versioned = True
1063
to_trans_id = to_trans_ids.get(file_id)
1064
if to_trans_id is None:
1065
to_versioned = False
1066
to_trans_id = from_trans_id
1070
from_name, from_parent, from_kind, from_executable = \
1071
self._from_file_data(from_trans_id, from_versioned, file_id)
1073
to_name, to_parent, to_kind, to_executable = \
1074
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1076
if not from_versioned:
1079
from_path = self._tree_id_paths.get(from_trans_id)
1080
if not to_versioned:
1083
to_path = final_paths.get_path(to_trans_id)
1084
if from_kind != to_kind:
1086
elif to_kind in ('file', 'symlink') and (
1087
to_trans_id != from_trans_id or
1088
to_trans_id in self._new_contents):
1090
if (not modified and from_versioned == to_versioned and
1091
from_parent==to_parent and from_name == to_name and
1092
from_executable == to_executable):
1094
results.append((file_id, (from_path, to_path), modified,
1095
(from_versioned, to_versioned),
1096
(from_parent, to_parent),
1097
(from_name, to_name),
1098
(from_kind, to_kind),
1099
(from_executable, to_executable)))
1100
return iter(sorted(results, key=lambda x:x[1]))
1102
def get_preview_tree(self):
1103
"""Return a tree representing the result of the transform.
1105
This tree only supports the subset of Tree functionality required
1106
by show_diff_trees. It must only be compared to tt._tree.
1108
return _PreviewTree(self)
1111
class TreeTransform(TreeTransformBase):
1112
"""Represent a tree transformation.
1114
This object is designed to support incremental generation of the transform,
1117
However, it gives optimum performance when parent directories are created
1118
before their contents. The transform is then able to put child files
1119
directly in their parent directory, avoiding later renames.
1121
It is easy to produce malformed transforms, but they are generally
1122
harmless. Attempting to apply a malformed transform will cause an
1123
exception to be raised before any modifications are made to the tree.
1125
Many kinds of malformed transforms can be corrected with the
1126
resolve_conflicts function. The remaining ones indicate programming error,
1127
such as trying to create a file with no path.
1129
Two sets of file creation methods are supplied. Convenience methods are:
1134
These are composed of the low-level methods:
1136
* create_file or create_directory or create_symlink
1140
Transform/Transaction ids
1141
-------------------------
1142
trans_ids are temporary ids assigned to all files involved in a transform.
1143
It's possible, even common, that not all files in the Tree have trans_ids.
1145
trans_ids are used because filenames and file_ids are not good enough
1146
identifiers; filenames change, and not all files have file_ids. File-ids
1147
are also associated with trans-ids, so that moving a file moves its
1150
trans_ids are only valid for the TreeTransform that generated them.
1154
Limbo is a temporary directory use to hold new versions of files.
1155
Files are added to limbo by create_file, create_directory, create_symlink,
1156
and their convenience variants (new_*). Files may be removed from limbo
1157
using cancel_creation. Files are renamed from limbo into their final
1158
location as part of TreeTransform.apply
1160
Limbo must be cleaned up, by either calling TreeTransform.apply or
1161
calling TreeTransform.finalize.
1163
Files are placed into limbo inside their parent directories, where
1164
possible. This reduces subsequent renames, and makes operations involving
1165
lots of files faster. This optimization is only possible if the parent
1166
directory is created *before* creating any of its children, so avoid
1167
creating children before parents, where possible.
1171
This temporary directory is used by _FileMover for storing files that are
1172
about to be deleted. In case of rollback, the files will be restored.
1173
FileMover does not delete files until it is sure that a rollback will not
1176
def __init__(self, tree, pb=DummyProgress()):
1177
"""Note: a tree_write lock is taken on the tree.
1179
Use TreeTransform.finalize() to release the lock (can be omitted if
1180
TreeTransform.apply() called).
1182
tree.lock_tree_write()
1185
limbodir = urlutils.local_path_from_url(
1186
tree._transport.abspath('limbo'))
1190
if e.errno == errno.EEXIST:
1191
raise ExistingLimbo(limbodir)
1192
deletiondir = urlutils.local_path_from_url(
1193
tree._transport.abspath('pending-deletion'))
1195
os.mkdir(deletiondir)
1197
if e.errno == errno.EEXIST:
1198
raise errors.ExistingPendingDeletion(deletiondir)
1203
TreeTransformBase.__init__(self, tree, limbodir, pb,
1204
tree.case_sensitive)
1205
self._deletiondir = deletiondir
1207
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1208
"""Apply all changes to the inventory and filesystem.
1210
If filesystem or inventory conflicts are present, MalformedTransform
1213
If apply succeeds, finalize is not necessary.
1215
:param no_conflicts: if True, the caller guarantees there are no
1216
conflicts, so no check is made.
1217
:param precomputed_delta: An inventory delta to use instead of
1219
:param _mover: Supply an alternate FileMover, for testing
1221
if not no_conflicts:
1222
conflicts = self.find_conflicts()
1223
if len(conflicts) != 0:
1224
raise MalformedTransform(conflicts=conflicts)
1225
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1227
if precomputed_delta is None:
1228
child_pb.update('Apply phase', 0, 2)
1229
inventory_delta = self._generate_inventory_delta()
1232
inventory_delta = precomputed_delta
1235
mover = _FileMover()
1239
child_pb.update('Apply phase', 0 + offset, 2 + offset)
1240
self._apply_removals(mover)
1241
child_pb.update('Apply phase', 1 + offset, 2 + offset)
1242
modified_paths = self._apply_insertions(mover)
1247
mover.apply_deletions()
1250
self._tree.apply_inventory_delta(inventory_delta)
1253
return _TransformResults(modified_paths, self.rename_count)
1255
def _generate_inventory_delta(self):
1256
"""Generate an inventory delta for the current transform."""
1257
inventory_delta = []
1258
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1259
new_paths = self._inventory_altered()
1260
total_entries = len(new_paths) + len(self._removed_id)
1262
for num, trans_id in enumerate(self._removed_id):
1264
child_pb.update('removing file', num, total_entries)
1265
if trans_id == self._new_root:
1266
file_id = self._tree.get_root_id()
1268
file_id = self.tree_file_id(trans_id)
1269
# File-id isn't really being deleted, just moved
1270
if file_id in self._r_new_id:
1272
path = self._tree_id_paths[trans_id]
1273
inventory_delta.append((path, None, file_id, None))
1274
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1276
entries = self._tree.iter_entries_by_dir(
1277
new_path_file_ids.values())
1278
old_paths = dict((e.file_id, p) for p, e in entries)
1280
for num, (path, trans_id) in enumerate(new_paths):
1282
child_pb.update('adding file',
1283
num + len(self._removed_id), total_entries)
1284
file_id = new_path_file_ids[trans_id]
1289
kind = self.final_kind(trans_id)
1291
kind = self._tree.stored_kind(file_id)
1292
parent_trans_id = self.final_parent(trans_id)
1293
parent_file_id = new_path_file_ids.get(parent_trans_id)
1294
if parent_file_id is None:
1295
parent_file_id = self.final_file_id(parent_trans_id)
1296
if trans_id in self._new_reference_revision:
1297
new_entry = inventory.TreeReference(
1299
self._new_name[trans_id],
1300
self.final_file_id(self._new_parent[trans_id]),
1301
None, self._new_reference_revision[trans_id])
1303
new_entry = inventory.make_entry(kind,
1304
self.final_name(trans_id),
1305
parent_file_id, file_id)
1306
old_path = old_paths.get(new_entry.file_id)
1307
new_executability = self._new_executability.get(trans_id)
1308
if new_executability is not None:
1309
new_entry.executable = new_executability
1310
inventory_delta.append(
1311
(old_path, path, new_entry.file_id, new_entry))
1314
return inventory_delta
1316
def _apply_removals(self, mover):
1317
"""Perform tree operations that remove directory/inventory names.
1319
That is, delete files that are to be deleted, and put any files that
1320
need renaming into limbo. This must be done in strict child-to-parent
1323
If inventory_delta is None, no inventory delta generation is performed.
1325
tree_paths = list(self._tree_path_ids.iteritems())
1326
tree_paths.sort(reverse=True)
1327
kind_changes = set()
1328
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1330
for num, data in enumerate(tree_paths):
1331
path, trans_id = data
1332
child_pb.update('removing file', num, len(tree_paths))
1333
full_path = self._tree.abspath(path)
1334
if trans_id in self._removed_contents:
1335
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1337
elif trans_id in self._new_name or trans_id in \
1340
mover.rename(full_path, self._limbo_name(trans_id))
1342
if e.errno != errno.ENOENT:
1345
self.rename_count += 1
1350
def _apply_insertions(self, mover):
1351
"""Perform tree operations that insert directory/inventory names.
1353
That is, create any files that need to be created, and restore from
1354
limbo any files that needed renaming. This must be done in strict
1355
parent-to-child order.
1357
If inventory_delta is None, no inventory delta is calculated, and
1358
no list of modified paths is returned.
1360
kind_changes is a set of trans ids where the entry has changed
1361
kind, and so an inventory delta entry should be created for them.
1363
new_paths = self.new_paths(filesystem_only=True)
1365
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1367
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1369
for num, (path, trans_id) in enumerate(new_paths):
1371
child_pb.update('adding file', num, len(new_paths))
1372
full_path = self._tree.abspath(path)
1373
if trans_id in self._needs_rename:
1375
mover.rename(self._limbo_name(trans_id), full_path)
1377
# We may be renaming a dangling inventory id
1378
if e.errno != errno.ENOENT:
1381
self.rename_count += 1
1382
if (trans_id in self._new_contents or
1383
self.path_changed(trans_id)):
1384
if trans_id in self._new_contents:
1385
modified_paths.append(full_path)
1386
if trans_id in self._new_executability:
1387
self._set_executability(path, trans_id)
1390
self._new_contents.clear()
1391
return modified_paths
1394
class TransformPreview(TreeTransformBase):
1395
"""A TreeTransform for generating preview trees.
1397
Unlike TreeTransform, this version works when the input tree is a
1398
RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
1399
unversioned files in the input tree.
1402
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1404
limbodir = tempfile.mkdtemp(prefix='bzr-limbo-')
1405
TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1407
def canonical_path(self, path):
1410
def tree_kind(self, trans_id):
1411
path = self._tree_id_paths.get(trans_id)
1413
raise NoSuchFile(None)
1414
file_id = self._tree.path2id(path)
1415
return self._tree.kind(file_id)
1417
def _set_mode(self, trans_id, mode_id, typefunc):
1418
"""Set the mode of new file contents.
1419
The mode_id is the existing file to get the mode from (often the same
1420
as trans_id). The operation is only performed if there's a mode match
1421
according to typefunc.
1423
# is it ok to ignore this? probably
1426
def iter_tree_children(self, parent_id):
1427
"""Iterate through the entry's tree children, if any"""
1429
path = self._tree_id_paths[parent_id]
1432
file_id = self.tree_file_id(parent_id)
1435
children = getattr(self._tree.inventory[file_id], 'children', {})
1436
for child in children:
1437
childpath = joinpath(path, child)
1438
yield self.trans_id_tree_path(childpath)
1441
class _PreviewTree(tree.Tree):
1442
"""Partial implementation of Tree to support show_diff_trees"""
1444
def __init__(self, transform):
1445
self._transform = transform
1446
self._final_paths = FinalPaths(transform)
1447
self.__by_parent = None
1448
self._parent_ids = []
1450
def _changes(self, file_id):
1451
for changes in self._transform.iter_changes():
1452
if changes[0] == file_id:
1455
def _content_change(self, file_id):
1456
"""Return True if the content of this file changed"""
1457
changes = self._changes(file_id)
1458
# changes[2] is true if the file content changed. See
1459
# InterTree.iter_changes.
1460
return (changes is not None and changes[2])
1462
def _get_repository(self):
1463
repo = getattr(self._transform._tree, '_repository', None)
1465
repo = self._transform._tree.branch.repository
1468
def _iter_parent_trees(self):
1469
for revision_id in self.get_parent_ids():
1471
yield self.revision_tree(revision_id)
1472
except errors.NoSuchRevisionInTree:
1473
yield self._get_repository().revision_tree(revision_id)
1475
def _get_file_revision(self, file_id, vf, tree_revision):
1476
parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1477
self._iter_parent_trees()]
1478
vf.add_lines((file_id, tree_revision), parent_keys,
1479
self.get_file(file_id).readlines())
1480
repo = self._get_repository()
1481
base_vf = repo.texts
1482
if base_vf not in vf.fallback_versionedfiles:
1483
vf.fallback_versionedfiles.append(base_vf)
1484
return tree_revision
1486
def _stat_limbo_file(self, file_id):
1487
trans_id = self._transform.trans_id_file_id(file_id)
1488
name = self._transform._limbo_name(trans_id)
1489
return os.lstat(name)
1492
def _by_parent(self):
1493
if self.__by_parent is None:
1494
self.__by_parent = self._transform.by_parent()
1495
return self.__by_parent
1497
def lock_read(self):
1498
# Perhaps in theory, this should lock the TreeTransform?
1505
def inventory(self):
1506
"""This Tree does not use inventory as its backing data."""
1507
raise NotImplementedError(_PreviewTree.inventory)
1509
def get_root_id(self):
1510
return self._transform.final_file_id(self._transform.root)
1512
def all_file_ids(self):
1513
tree_ids = set(self._transform._tree.all_file_ids())
1514
tree_ids.difference_update(self._transform.tree_file_id(t)
1515
for t in self._transform._removed_id)
1516
tree_ids.update(self._transform._new_id.values())
1520
return iter(self.all_file_ids())
1522
def paths2ids(self, specific_files, trees=None, require_versioned=False):
1523
"""See Tree.paths2ids"""
1524
to_find = set(specific_files)
1526
for (file_id, paths, changed, versioned, parent, name, kind,
1527
executable) in self._transform.iter_changes():
1528
if paths[1] in to_find:
1529
result.append(file_id)
1530
to_find.remove(paths[1])
1531
result.update(self._transform._tree.paths2ids(to_find,
1532
trees=[], require_versioned=require_versioned))
1535
def _path2trans_id(self, path):
1536
segments = splitpath(path)
1537
cur_parent = self._transform.root
1538
for cur_segment in segments:
1539
for child in self._all_children(cur_parent):
1540
if self._transform.final_name(child) == cur_segment:
1547
def path2id(self, path):
1548
return self._transform.final_file_id(self._path2trans_id(path))
1550
def id2path(self, file_id):
1551
trans_id = self._transform.trans_id_file_id(file_id)
1553
return self._final_paths._determine_path(trans_id)
1555
raise errors.NoSuchId(self, file_id)
1557
def _all_children(self, trans_id):
1558
children = set(self._transform.iter_tree_children(trans_id))
1559
# children in the _new_parent set are provided by _by_parent.
1560
children.difference_update(self._transform._new_parent.keys())
1561
children.update(self._by_parent.get(trans_id, []))
1564
def _make_inv_entries(self, ordered_entries, specific_file_ids):
1565
for trans_id, parent_file_id in ordered_entries:
1566
file_id = self._transform.final_file_id(trans_id)
1569
if (specific_file_ids is not None
1570
and file_id not in specific_file_ids):
1573
kind = self._transform.final_kind(trans_id)
1575
kind = self._transform._tree.stored_kind(file_id)
1576
new_entry = inventory.make_entry(
1578
self._transform.final_name(trans_id),
1579
parent_file_id, file_id)
1580
yield new_entry, trans_id
1582
def iter_entries_by_dir(self, specific_file_ids=None):
1583
# This may not be a maximally efficient implementation, but it is
1584
# reasonably straightforward. An implementation that grafts the
1585
# TreeTransform changes onto the tree's iter_entries_by_dir results
1586
# might be more efficient, but requires tricky inferences about stack
1588
todo = [ROOT_PARENT]
1590
while len(todo) > 0:
1592
parent_file_id = self._transform.final_file_id(parent)
1593
children = list(self._all_children(parent))
1594
paths = dict(zip(children, self._final_paths.get_paths(children)))
1595
children.sort(key=paths.get)
1596
todo.extend(reversed(children))
1597
for trans_id in children:
1598
ordered_ids.append((trans_id, parent_file_id))
1599
for entry, trans_id in self._make_inv_entries(ordered_ids,
1601
yield unicode(self._final_paths.get_path(trans_id)), entry
1603
def kind(self, file_id):
1604
trans_id = self._transform.trans_id_file_id(file_id)
1605
return self._transform.final_kind(trans_id)
1607
def stored_kind(self, file_id):
1608
trans_id = self._transform.trans_id_file_id(file_id)
1610
return self._transform._new_contents[trans_id]
1612
return self._transform._tree.stored_kind(file_id)
1614
def get_file_mtime(self, file_id, path=None):
1615
"""See Tree.get_file_mtime"""
1616
if not self._content_change(file_id):
1617
return self._transform._tree.get_file_mtime(file_id, path)
1618
return self._stat_limbo_file(file_id).st_mtime
1620
def get_file_size(self, file_id):
1621
"""See Tree.get_file_size"""
1622
if self.kind(file_id) == 'file':
1623
return self._transform._tree.get_file_size(file_id)
1627
def get_file_sha1(self, file_id, path=None, stat_value=None):
1628
return self._transform._tree.get_file_sha1(file_id)
1630
def is_executable(self, file_id, path=None):
1631
trans_id = self._transform.trans_id_file_id(file_id)
1633
return self._transform._new_executability[trans_id]
1635
return self._transform._tree.is_executable(file_id, path)
1637
def path_content_summary(self, path):
1638
trans_id = self._path2trans_id(path)
1639
tt = self._transform
1640
tree_path = tt._tree_id_paths.get(trans_id)
1641
kind = tt._new_contents.get(trans_id)
1643
if tree_path is None or trans_id in tt._removed_contents:
1644
return 'missing', None, None, None
1645
summary = tt._tree.path_content_summary(tree_path)
1646
kind, size, executable, link_or_sha1 = summary
1649
limbo_name = tt._limbo_name(trans_id)
1650
if trans_id in tt._new_reference_revision:
1651
kind = 'tree-reference'
1653
statval = os.lstat(limbo_name)
1654
size = statval.st_size
1655
if not supports_executable():
1658
executable = statval.st_mode & S_IEXEC
1662
if kind == 'symlink':
1663
link_or_sha1 = os.readlink(limbo_name)
1664
if supports_executable():
1665
executable = tt._new_executability.get(trans_id, executable)
1666
return kind, size, executable, link_or_sha1
1668
def iter_changes(self, from_tree, include_unchanged=False,
1669
specific_files=None, pb=None, extra_trees=None,
1670
require_versioned=True, want_unversioned=False):
1671
"""See InterTree.iter_changes.
1673
This implementation does not support include_unchanged, specific_files,
1674
or want_unversioned. extra_trees, require_versioned, and pb are
1677
if from_tree is not self._transform._tree:
1678
raise ValueError('from_tree must be transform source tree.')
1679
if include_unchanged:
1680
raise ValueError('include_unchanged is not supported')
1681
if specific_files is not None:
1682
raise ValueError('specific_files is not supported')
1683
if want_unversioned:
1684
raise ValueError('want_unversioned is not supported')
1685
return self._transform.iter_changes()
1687
def get_file(self, file_id, path=None):
1688
"""See Tree.get_file"""
1689
if not self._content_change(file_id):
1690
return self._transform._tree.get_file(file_id, path)
1691
trans_id = self._transform.trans_id_file_id(file_id)
1692
name = self._transform._limbo_name(trans_id)
1693
return open(name, 'rb')
1695
def get_file_text(self, file_id):
1696
text_file = self.get_file(file_id)
1698
return text_file.read()
1702
def annotate_iter(self, file_id,
1703
default_revision=_mod_revision.CURRENT_REVISION):
1704
changes = self._changes(file_id)
1708
changed_content, versioned, kind = (changes[2], changes[3],
1712
get_old = (kind[0] == 'file' and versioned[0])
1714
old_annotation = self._transform._tree.annotate_iter(file_id,
1715
default_revision=default_revision)
1719
return old_annotation
1720
if not changed_content:
1721
return old_annotation
1722
return annotate.reannotate([old_annotation],
1723
self.get_file(file_id).readlines(),
1726
def get_symlink_target(self, file_id):
1727
"""See Tree.get_symlink_target"""
1728
if not self._content_change(file_id):
1729
return self._transform._tree.get_symlink_target(file_id)
1730
trans_id = self._transform.trans_id_file_id(file_id)
1731
name = self._transform._limbo_name(trans_id)
1732
return os.readlink(name)
1734
def list_files(self, include_root=False):
1735
return self._transform._tree.list_files(include_root)
1737
def walkdirs(self, prefix=""):
1738
return self._transform._tree.walkdirs(prefix)
1740
def get_parent_ids(self):
1741
return self._parent_ids
1743
def set_parent_ids(self, parent_ids):
1744
self._parent_ids = parent_ids
1746
def get_revision_tree(self, revision_id):
1747
return self._transform._tree.get_revision_tree(revision_id)
901
1750
def joinpath(parent, child):
902
1751
"""Join tree-relative paths, handling the tree root specially"""
903
1752
if parent is None or parent == "":
1184
2114
return has_contents, contents_mod, meta_mod
1187
def revert(working_tree, target_tree, filenames, backups=False,
1188
pb=DummyProgress()):
2117
def revert(working_tree, target_tree, filenames, backups=False,
2118
pb=DummyProgress(), change_reporter=None):
1189
2119
"""Revert a working tree's contents to those of a target tree."""
1190
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1191
def interesting(file_id):
1192
return interesting_ids is None or (file_id in interesting_ids)
2120
target_tree.lock_read()
1194
2121
tt = TreeTransform(working_tree, pb)
1196
merge_modified = working_tree.merge_modified()
1198
def trans_id_file_id(file_id):
1200
return trans_id[file_id]
1202
return tt.trans_id_tree_file_id(file_id)
1204
pp = ProgressPhase("Revert phase", 4, pb)
1206
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1208
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1210
by_parent = tt.by_parent()
1211
for id_num, file_id in enumerate(sorted_interesting):
1212
child_pb.update("Reverting file", id_num+1,
1213
len(sorted_interesting))
1214
if file_id not in working_tree.inventory:
1215
entry = target_tree.inventory[file_id]
1216
parent_id = trans_id_file_id(entry.parent_id)
1217
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1218
trans_id[file_id] = e_trans_id
1220
backup_this = backups
1221
if file_id in merge_modified:
1223
del merge_modified[file_id]
1224
change_entry(tt, file_id, working_tree, target_tree,
1225
trans_id_file_id, backup_this, trans_id,
1230
wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1231
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1233
for id_num, file_id in enumerate(wt_interesting):
1234
child_pb.update("New file check", id_num+1,
1235
len(sorted_interesting))
1236
if file_id not in target_tree:
1237
trans_id = tt.trans_id_tree_file_id(file_id)
1238
tt.unversion_file(trans_id)
1240
file_kind = working_tree.kind(file_id)
1243
if file_kind != 'file' and file_kind is not None:
1244
keep_contents = False
1245
delete_merge_modified = False
1247
if (file_id in merge_modified and
1248
merge_modified[file_id] ==
1249
working_tree.get_file_sha1(file_id)):
1250
keep_contents = False
1251
delete_merge_modified = True
1253
keep_contents = True
1254
delete_merge_modified = False
1255
if not keep_contents:
2123
pp = ProgressPhase("Revert phase", 3, pb)
2125
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2127
merge_modified = _alter_files(working_tree, target_tree, tt,
2128
child_pb, filenames, backups)
2132
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2134
raw_conflicts = resolve_conflicts(tt, child_pb,
2135
lambda t, c: conflict_pass(t, c, target_tree))
2138
conflicts = cook_conflicts(raw_conflicts, tt)
2140
change_reporter = delta._ChangeReporter(
2141
unversioned_filter=working_tree.is_ignored)
2142
delta.report_changes(tt.iter_changes(), change_reporter)
2143
for conflict in conflicts:
2147
working_tree.set_merge_modified(merge_modified)
2149
target_tree.unlock()
2155
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
2157
merge_modified = working_tree.merge_modified()
2158
change_list = target_tree.iter_changes(working_tree,
2159
specific_files=specific_files, pb=pb)
2160
if target_tree.inventory.root is None:
2167
for id_num, (file_id, path, changed_content, versioned, parent, name,
2168
kind, executable) in enumerate(change_list):
2169
if skip_root and file_id[0] is not None and parent[0] is None:
2171
trans_id = tt.trans_id_file_id(file_id)
2174
keep_content = False
2175
if kind[0] == 'file' and (backups or kind[1] is None):
2176
wt_sha1 = working_tree.get_file_sha1(file_id)
2177
if merge_modified.get(file_id) != wt_sha1:
2178
# acquire the basis tree lazily to prevent the
2179
# expense of accessing it when it's not needed ?
2180
# (Guessing, RBC, 200702)
2181
if basis_tree is None:
2182
basis_tree = working_tree.basis_tree()
2183
basis_tree.lock_read()
2184
if file_id in basis_tree:
2185
if wt_sha1 != basis_tree.get_file_sha1(file_id):
2187
elif kind[1] is None and not versioned[1]:
2189
if kind[0] is not None:
2190
if not keep_content:
1256
2191
tt.delete_contents(trans_id)
1257
if delete_merge_modified:
1258
del merge_modified[file_id]
1262
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1264
raw_conflicts = resolve_conflicts(tt, child_pb)
1267
conflicts = cook_conflicts(raw_conflicts, tt)
1268
for conflict in conflicts:
1272
working_tree.set_merge_modified({})
2192
elif kind[1] is not None:
2193
parent_trans_id = tt.trans_id_file_id(parent[0])
2194
by_parent = tt.by_parent()
2195
backup_name = _get_backup_name(name[0], by_parent,
2196
parent_trans_id, tt)
2197
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2198
new_trans_id = tt.create_path(name[0], parent_trans_id)
2199
if versioned == (True, True):
2200
tt.unversion_file(trans_id)
2201
tt.version_file(file_id, new_trans_id)
2202
# New contents should have the same unix perms as old
2205
trans_id = new_trans_id
2206
if kind[1] == 'directory':
2207
tt.create_directory(trans_id)
2208
elif kind[1] == 'symlink':
2209
tt.create_symlink(target_tree.get_symlink_target(file_id),
2211
elif kind[1] == 'file':
2212
deferred_files.append((file_id, (trans_id, mode_id)))
2213
if basis_tree is None:
2214
basis_tree = working_tree.basis_tree()
2215
basis_tree.lock_read()
2216
new_sha1 = target_tree.get_file_sha1(file_id)
2217
if (file_id in basis_tree and new_sha1 ==
2218
basis_tree.get_file_sha1(file_id)):
2219
if file_id in merge_modified:
2220
del merge_modified[file_id]
2222
merge_modified[file_id] = new_sha1
2224
# preserve the execute bit when backing up
2225
if keep_content and executable[0] == executable[1]:
2226
tt.set_executability(executable[1], trans_id)
2227
elif kind[1] is not None:
2228
raise AssertionError(kind[1])
2229
if versioned == (False, True):
2230
tt.version_file(file_id, trans_id)
2231
if versioned == (True, False):
2232
tt.unversion_file(trans_id)
2233
if (name[1] is not None and
2234
(name[0] != name[1] or parent[0] != parent[1])):
2236
name[1], tt.trans_id_file_id(parent[1]), trans_id)
2237
if executable[0] != executable[1] and kind[1] == "file":
2238
tt.set_executability(executable[1], trans_id)
2239
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2241
tt.create_file(bytes, trans_id, mode_id)
2243
if basis_tree is not None:
2245
return merge_modified
1279
2248
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):