68
90
* create_file or create_directory or create_symlink
70
92
* set_executability
94
Transform/Transaction ids
95
-------------------------
96
trans_ids are temporary ids assigned to all files involved in a transform.
97
It's possible, even common, that not all files in the Tree have trans_ids.
99
trans_ids are used because filenames and file_ids are not good enough
100
identifiers; filenames change, and not all files have file_ids. File-ids
101
are also associated with trans-ids, so that moving a file moves its
104
trans_ids are only valid for the TreeTransform that generated them.
108
Limbo is a temporary directory use to hold new versions of files.
109
Files are added to limbo by create_file, create_directory, create_symlink,
110
and their convenience variants (new_*). Files may be removed from limbo
111
using cancel_creation. Files are renamed from limbo into their final
112
location as part of TreeTransform.apply
114
Limbo must be cleaned up, by either calling TreeTransform.apply or
115
calling TreeTransform.finalize.
117
Files are placed into limbo inside their parent directories, where
118
possible. This reduces subsequent renames, and makes operations involving
119
lots of files faster. This optimization is only possible if the parent
120
directory is created *before* creating any of its children, so avoid
121
creating children before parents, where possible.
125
This temporary directory is used by _FileMover for storing files that are
126
about to be deleted. In case of rollback, the files will be restored.
127
FileMover does not delete files until it is sure that a rollback will not
72
130
def __init__(self, tree, pb=DummyProgress()):
73
"""Note: a write lock is taken on the tree.
131
"""Note: a tree_write lock is taken on the tree.
75
Use TreeTransform.finalize() to release the lock
133
Use TreeTransform.finalize() to release the lock (can be omitted if
134
TreeTransform.apply() called).
77
136
object.__init__(self)
79
self._tree.lock_write()
138
self._tree.lock_tree_write()
81
140
control_files = self._tree._control_files
82
self._limbodir = control_files.controlfilename('limbo')
141
self._limbodir = urlutils.local_path_from_url(
142
control_files.controlfilename('limbo'))
84
144
os.mkdir(self._limbodir)
85
145
except OSError, e:
86
146
if e.errno == errno.EEXIST:
87
147
raise ExistingLimbo(self._limbodir)
148
self._deletiondir = urlutils.local_path_from_url(
149
control_files.controlfilename('pending-deletion'))
151
os.mkdir(self._deletiondir)
153
if e.errno == errno.EEXIST:
154
raise errors.ExistingPendingDeletion(self._deletiondir)
89
157
self._tree.unlock()
160
# counter used to generate trans-ids (which are locally unique)
92
161
self._id_number = 0
162
# mapping of trans_id -> new basename
93
163
self._new_name = {}
164
# mapping of trans_id -> new parent trans_id
94
165
self._new_parent = {}
166
# mapping of trans_id with new contents -> new file_kind
95
167
self._new_contents = {}
168
# A mapping of transform ids to their limbo filename
169
self._limbo_files = {}
170
# A mapping of transform ids to a set of the transform ids of children
171
# that their limbo directory has
172
self._limbo_children = {}
173
# Map transform ids to maps of child filename to child transform id
174
self._limbo_children_names = {}
175
# List of transform ids that need to be renamed from limbo into place
176
self._needs_rename = set()
177
# Set of trans_ids whose contents will be removed
96
178
self._removed_contents = set()
179
# Mapping of trans_id -> new execute-bit value
97
180
self._new_executability = {}
181
# Mapping of trans_id -> new tree-reference value
182
self._new_reference_revision = {}
183
# Mapping of trans_id -> new file_id
185
# Mapping of old file-id -> trans_id
99
186
self._non_present_ids = {}
187
# Mapping of new file_id -> trans_id
100
188
self._r_new_id = {}
189
# Set of file_ids that will be removed
101
190
self._removed_id = set()
191
# Mapping of path in old tree -> trans_id
102
192
self._tree_path_ids = {}
193
# Mapping trans_id -> path in old tree
103
194
self._tree_id_paths = {}
195
# Cache of realpath results, to speed up canonical_path
197
# Cache of relpath results, to speed up canonical_path
199
# The trans_id that will be used as the tree root
104
200
self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
201
# Indictor of whether the transform has been applied
105
202
self.__done = False
205
# A counter of how many files have been renamed
206
self.rename_count = 0
108
208
def __get_root(self):
109
209
return self._new_root
870
def apply(self, no_conflicts=False, _mover=None):
680
871
"""Apply all changes to the inventory and filesystem.
682
873
If filesystem or inventory conflicts are present, MalformedTransform
876
If apply succeeds, finalize is not necessary.
878
:param no_conflicts: if True, the caller guarantees there are no
879
conflicts, so no check is made.
880
:param _mover: Supply an alternate FileMover, for testing
685
conflicts = self.find_conflicts()
686
if len(conflicts) != 0:
687
raise MalformedTransform(conflicts=conflicts)
883
conflicts = self.find_conflicts()
884
if len(conflicts) != 0:
885
raise MalformedTransform(conflicts=conflicts)
689
886
inv = self._tree.inventory
690
888
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)
895
child_pb.update('Apply phase', 0, 2)
896
self._apply_removals(inv, inventory_delta, mover)
897
child_pb.update('Apply phase', 1, 2)
898
modified_paths = self._apply_insertions(inv, inventory_delta,
904
mover.apply_deletions()
697
906
child_pb.finished()
698
self._tree._write_inventory(inv)
907
self._tree.apply_inventory_delta(inventory_delta)
699
908
self.__done = True
701
return _TransformResults(modified_paths)
910
return _TransformResults(modified_paths, self.rename_count)
703
912
def _limbo_name(self, trans_id):
704
913
"""Generate the limbo name of a file"""
705
return pathjoin(self._limbodir, trans_id)
707
def _apply_removals(self, inv, limbo_inv):
914
limbo_name = self._limbo_files.get(trans_id)
915
if limbo_name is not None:
917
parent = self._new_parent.get(trans_id)
918
# if the parent directory is already in limbo (e.g. when building a
919
# tree), choose a limbo name inside the parent, to reduce further
921
use_direct_path = False
922
if self._new_contents.get(parent) == 'directory':
923
filename = self._new_name.get(trans_id)
924
if filename is not None:
925
if parent not in self._limbo_children:
926
self._limbo_children[parent] = set()
927
self._limbo_children_names[parent] = {}
928
use_direct_path = True
929
# the direct path can only be used if no other file has
930
# already taken this pathname, i.e. if the name is unused, or
931
# if it is already associated with this trans_id.
932
elif self._tree.case_sensitive:
933
if (self._limbo_children_names[parent].get(filename)
934
in (trans_id, None)):
935
use_direct_path = True
937
for l_filename, l_trans_id in\
938
self._limbo_children_names[parent].iteritems():
939
if l_trans_id == trans_id:
941
if l_filename.lower() == filename.lower():
944
use_direct_path = True
947
limbo_name = pathjoin(self._limbo_files[parent], filename)
948
self._limbo_children[parent].add(trans_id)
949
self._limbo_children_names[parent][filename] = trans_id
951
limbo_name = pathjoin(self._limbodir, trans_id)
952
self._needs_rename.add(trans_id)
953
self._limbo_files[trans_id] = limbo_name
956
def _apply_removals(self, inv, inventory_delta, mover):
708
957
"""Perform tree operations that remove directory/inventory names.
710
959
That is, delete files that are to be deleted, and put any files that
762
1012
if trans_id in self._new_contents or \
763
1013
self.path_changed(trans_id):
764
1014
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:
1015
if trans_id in self._needs_rename:
1017
mover.rename(self._limbo_name(trans_id), full_path)
1019
# We may be renaming a dangling inventory id
1020
if e.errno != errno.ENOENT:
1023
self.rename_count += 1
771
1024
if trans_id in self._new_contents:
772
1025
modified_paths.append(full_path)
773
del self._new_contents[trans_id]
1026
completed_new.append(trans_id)
775
1028
if trans_id in self._new_id:
776
1029
if kind is None:
777
1030
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
1031
if trans_id in self._new_reference_revision:
1032
new_entry = inventory.TreeReference(
1033
self._new_id[trans_id],
1034
self._new_name[trans_id],
1035
self.final_file_id(self._new_parent[trans_id]),
1036
None, self._new_reference_revision[trans_id])
1038
new_entry = inventory.make_entry(kind,
1039
self.final_name(trans_id),
1040
self.final_file_id(self.final_parent(trans_id)),
1041
self._new_id[trans_id])
1043
if trans_id in self._new_name or trans_id in\
1044
self._new_parent or\
1045
trans_id in self._new_executability:
1046
file_id = self.final_file_id(trans_id)
1047
if file_id is not None:
1048
entry = inv[file_id]
1049
new_entry = entry.copy()
1051
if trans_id in self._new_name or trans_id in\
1053
if new_entry is not None:
1054
new_entry.name = self.final_name(trans_id)
1055
parent = self.final_parent(trans_id)
1056
parent_id = self.final_file_id(parent)
1057
new_entry.parent_id = parent_id
790
1059
if trans_id in self._new_executability:
791
self._set_executability(path, inv, trans_id)
1060
self._set_executability(path, new_entry, trans_id)
1061
if new_entry is not None:
1062
if new_entry.file_id in inv:
1063
old_path = inv.id2path(new_entry.file_id)
1066
inventory_delta.append((old_path, path,
793
1070
child_pb.finished()
1071
for trans_id in completed_new:
1072
del self._new_contents[trans_id]
794
1073
return modified_paths
796
def _set_executability(self, path, inv, trans_id):
1075
def _set_executability(self, path, entry, trans_id):
797
1076
"""Set the executability of versioned files """
798
file_id = inv.path2id(path)
799
1077
new_executability = self._new_executability[trans_id]
800
inv[file_id].executable = new_executability
1078
entry.executable = new_executability
801
1079
if supports_executable():
802
1080
abspath = self._tree.abspath(path)
803
1081
current_mode = os.stat(abspath).st_mode
861
1142
self.create_symlink(target, trans_id)
1145
def _affected_ids(self):
1146
"""Return the set of transform ids affected by the transform"""
1147
trans_ids = set(self._removed_id)
1148
trans_ids.update(self._new_id.keys())
1149
trans_ids.update(self._removed_contents)
1150
trans_ids.update(self._new_contents.keys())
1151
trans_ids.update(self._new_executability.keys())
1152
trans_ids.update(self._new_name.keys())
1153
trans_ids.update(self._new_parent.keys())
1156
def _get_file_id_maps(self):
1157
"""Return mapping of file_ids to trans_ids in the to and from states"""
1158
trans_ids = self._affected_ids()
1161
# Build up two dicts: trans_ids associated with file ids in the
1162
# FROM state, vs the TO state.
1163
for trans_id in trans_ids:
1164
from_file_id = self.tree_file_id(trans_id)
1165
if from_file_id is not None:
1166
from_trans_ids[from_file_id] = trans_id
1167
to_file_id = self.final_file_id(trans_id)
1168
if to_file_id is not None:
1169
to_trans_ids[to_file_id] = trans_id
1170
return from_trans_ids, to_trans_ids
1172
def _from_file_data(self, from_trans_id, from_versioned, file_id):
1173
"""Get data about a file in the from (tree) state
1175
Return a (name, parent, kind, executable) tuple
1177
from_path = self._tree_id_paths.get(from_trans_id)
1179
# get data from working tree if versioned
1180
from_entry = self._tree.inventory[file_id]
1181
from_name = from_entry.name
1182
from_parent = from_entry.parent_id
1185
if from_path is None:
1186
# File does not exist in FROM state
1190
# File exists, but is not versioned. Have to use path-
1192
from_name = os.path.basename(from_path)
1193
tree_parent = self.get_tree_parent(from_trans_id)
1194
from_parent = self.tree_file_id(tree_parent)
1195
if from_path is not None:
1196
from_kind, from_executable, from_stats = \
1197
self._tree._comparison_data(from_entry, from_path)
1200
from_executable = False
1201
return from_name, from_parent, from_kind, from_executable
1203
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1204
"""Get data about a file in the to (target) state
1206
Return a (name, parent, kind, executable) tuple
1208
to_name = self.final_name(to_trans_id)
1210
to_kind = self.final_kind(to_trans_id)
1213
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1214
if to_trans_id in self._new_executability:
1215
to_executable = self._new_executability[to_trans_id]
1216
elif to_trans_id == from_trans_id:
1217
to_executable = from_executable
1219
to_executable = False
1220
return to_name, to_parent, to_kind, to_executable
1222
def _iter_changes(self):
1223
"""Produce output in the same format as Tree._iter_changes.
1225
Will produce nonsensical results if invoked while inventory/filesystem
1226
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1228
This reads the Transform, but only reproduces changes involving a
1229
file_id. Files that are not versioned in either of the FROM or TO
1230
states are not reflected.
1232
final_paths = FinalPaths(self)
1233
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1235
# Now iterate through all active file_ids
1236
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1238
from_trans_id = from_trans_ids.get(file_id)
1239
# find file ids, and determine versioning state
1240
if from_trans_id is None:
1241
from_versioned = False
1242
from_trans_id = to_trans_ids[file_id]
1244
from_versioned = True
1245
to_trans_id = to_trans_ids.get(file_id)
1246
if to_trans_id is None:
1247
to_versioned = False
1248
to_trans_id = from_trans_id
1252
from_name, from_parent, from_kind, from_executable = \
1253
self._from_file_data(from_trans_id, from_versioned, file_id)
1255
to_name, to_parent, to_kind, to_executable = \
1256
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1258
if not from_versioned:
1261
from_path = self._tree_id_paths.get(from_trans_id)
1262
if not to_versioned:
1265
to_path = final_paths.get_path(to_trans_id)
1266
if from_kind != to_kind:
1268
elif to_kind in ('file', 'symlink') and (
1269
to_trans_id != from_trans_id or
1270
to_trans_id in self._new_contents):
1272
if (not modified and from_versioned == to_versioned and
1273
from_parent==to_parent and from_name == to_name and
1274
from_executable == to_executable):
1276
results.append((file_id, (from_path, to_path), modified,
1277
(from_versioned, to_versioned),
1278
(from_parent, to_parent),
1279
(from_name, to_name),
1280
(from_kind, to_kind),
1281
(from_executable, to_executable)))
1282
return iter(sorted(results, key=lambda x:x[1]))
864
1285
def joinpath(parent, child):
865
1286
"""Join tree-relative paths, handling the tree root specially"""
866
1287
if parent is None or parent == "":
896
1317
self._known_paths[trans_id] = self._determine_path(trans_id)
897
1318
return self._known_paths[trans_id]
899
1321
def topology_sorted_ids(tree):
900
1322
"""Determine the topological order of the ids in a tree"""
901
1323
file_ids = list(tree)
902
1324
file_ids.sort(key=tree.id2path)
905
def build_tree(tree, wt):
906
"""Create working tree for a branch, using a Transaction."""
1328
def build_tree(tree, wt, accelerator_tree=None):
1329
"""Create working tree for a branch, using a TreeTransform.
1331
This function should be used on empty trees, having a tree root at most.
1332
(see merge and revert functionality for working with existing trees)
1334
Existing files are handled like so:
1336
- Existing bzrdirs take precedence over creating new items. They are
1337
created as '%s.diverted' % name.
1338
- Otherwise, if the content on disk matches the content we are building,
1339
it is silently replaced.
1340
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1342
:param tree: The tree to convert wt into a copy of
1343
:param wt: The working tree that files will be placed into
1344
:param accelerator_tree: A tree which can be used for retrieving file
1345
contents more quickly than tree itself, i.e. a workingtree. tree
1346
will be used for cases where accelerator_tree's content is different.
1348
wt.lock_tree_write()
1352
if accelerator_tree is not None:
1353
accelerator_tree.lock_read()
1355
return _build_tree(tree, wt, accelerator_tree)
1357
if accelerator_tree is not None:
1358
accelerator_tree.unlock()
1365
def _build_tree(tree, wt, accelerator_tree):
1366
"""See build_tree."""
1367
if len(wt.inventory) > 1: # more than just a root
1368
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
907
1369
file_trans_id = {}
908
1370
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
1371
pp = ProgressPhase("Build phase", 2, top_pb)
1372
if tree.inventory.root is not None:
1373
# This is kind of a hack: we should be altering the root
1374
# as part of the regular tree shape diff logic.
1375
# The conditional test here is to avoid doing an
1376
# expensive operation (flush) every time the root id
1377
# is set within the tree, nor setting the root and thus
1378
# marking the tree as dirty, because we use two different
1379
# idioms here: tree interfaces and inventory interfaces.
1380
if wt.get_root_id() != tree.get_root_id():
1381
wt.set_root_id(tree.get_root_id())
910
1383
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)
1387
file_trans_id[wt.get_root_id()] = \
1388
tt.trans_id_tree_file_id(wt.get_root_id())
915
1389
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]
1391
deferred_contents = []
1392
for num, (tree_path, entry) in \
1393
enumerate(tree.inventory.iter_entries_by_dir()):
1394
pb.update("Building tree", num - len(deferred_contents),
1395
len(tree.inventory))
920
1396
if entry.parent_id is None:
1399
file_id = entry.file_id
1400
target_path = wt.abspath(tree_path)
1402
kind = file_kind(target_path)
1406
if kind == "directory":
1408
bzrdir.BzrDir.open(target_path)
1409
except errors.NotBranchError:
1413
if (file_id not in divert and
1414
_content_match(tree, entry, file_id, kind,
1416
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1417
if kind == 'directory':
922
1419
if entry.parent_id not in file_trans_id:
923
raise repr(entry.parent_id)
1420
raise AssertionError(
1421
'entry %s parent id %r is not in file_trans_id %r'
1422
% (entry, entry.parent_id, file_trans_id))
924
1423
parent_id = file_trans_id[entry.parent_id]
925
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1424
if entry.kind == 'file':
1425
# We *almost* replicate new_by_entry, so that we can defer
1426
# getting the file text, and get them all at once.
1427
trans_id = tt.create_path(entry.name, parent_id)
1428
file_trans_id[file_id] = trans_id
1429
tt.version_file(entry.file_id, trans_id)
1430
executable = tree.is_executable(entry.file_id, tree_path)
1431
if executable is not None:
1432
tt.set_executability(executable, trans_id)
1433
deferred_contents.append((entry.file_id, trans_id))
1435
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1438
new_trans_id = file_trans_id[file_id]
1439
old_parent = tt.trans_id_tree_path(tree_path)
1440
_reparent_children(tt, old_parent, new_trans_id)
1441
for num, (trans_id, bytes) in enumerate(
1442
_iter_files_bytes_accelerated(tree, accelerator_tree,
1443
deferred_contents)):
1444
tt.create_file(bytes, trans_id)
1445
pb.update('Adding file contents',
1446
(num + len(tree.inventory) - len(deferred_contents)),
1447
len(tree.inventory))
1451
divert_trans = set(file_trans_id[f] for f in divert)
1452
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1453
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1454
conflicts = cook_conflicts(raw_conflicts, tt)
1455
for conflict in conflicts:
1458
wt.add_conflicts(conflicts)
1459
except errors.UnsupportedOperation:
1461
result = tt.apply(no_conflicts=True)
933
1464
top_pb.finished()
1468
def _iter_files_bytes_accelerated(tree, accelerator_tree, desired_files):
1469
if accelerator_tree is None:
1470
new_desired_files = desired_files
1472
iter = accelerator_tree._iter_changes(tree, include_unchanged=True)
1473
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
1475
new_desired_files = []
1476
for file_id, identifier in desired_files:
1477
accelerator_path = unchanged.get(file_id)
1478
if accelerator_path is None:
1479
new_desired_files.append((file_id, identifier))
1481
contents = accelerator_tree.get_file(file_id, accelerator_path)
1484
contents_bytes = (contents.read(),)
1487
yield identifier, contents_bytes
1488
for result in tree.iter_files_bytes(new_desired_files):
1492
def _reparent_children(tt, old_parent, new_parent):
1493
for child in tt.iter_tree_children(old_parent):
1494
tt.adjust_path(tt.final_name(child), new_parent, child)
1496
def _reparent_transform_children(tt, old_parent, new_parent):
1497
by_parent = tt.by_parent()
1498
for child in by_parent[old_parent]:
1499
tt.adjust_path(tt.final_name(child), new_parent, child)
1501
def _content_match(tree, entry, file_id, kind, target_path):
1502
if entry.kind != kind:
1504
if entry.kind == "directory":
1506
if entry.kind == "file":
1507
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1509
elif entry.kind == "symlink":
1510
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1515
def resolve_checkout(tt, conflicts, divert):
1516
new_conflicts = set()
1517
for c_type, conflict in ((c[0], c) for c in conflicts):
1518
# Anything but a 'duplicate' would indicate programmer error
1519
assert c_type == 'duplicate', c_type
1520
# Now figure out which is new and which is old
1521
if tt.new_contents(conflict[1]):
1522
new_file = conflict[1]
1523
old_file = conflict[2]
1525
new_file = conflict[2]
1526
old_file = conflict[1]
1528
# We should only get here if the conflict wasn't completely
1530
final_parent = tt.final_parent(old_file)
1531
if new_file in divert:
1532
new_name = tt.final_name(old_file)+'.diverted'
1533
tt.adjust_path(new_name, final_parent, new_file)
1534
new_conflicts.add((c_type, 'Diverted to',
1535
new_file, old_file))
1537
new_name = tt.final_name(old_file)+'.moved'
1538
tt.adjust_path(new_name, final_parent, old_file)
1539
new_conflicts.add((c_type, 'Moved existing file to',
1540
old_file, new_file))
1541
return new_conflicts
935
1544
def new_by_entry(tt, entry, parent_id, tree):
936
1545
"""Create a new file according to its inventory entry"""
958
1573
elif entry.kind == "directory":
959
1574
tt.create_directory(trans_id)
961
1577
def create_entry_executability(tt, entry, trans_id):
962
1578
"""Set the executability of a trans_id according to an inventory entry"""
963
1579
if entry.kind == "file":
964
1580
tt.set_executability(entry.executable, trans_id)
1583
@deprecated_function(zero_fifteen)
967
1584
def find_interesting(working_tree, target_tree, filenames):
968
"""Find the ids corresponding to specified filenames."""
970
interesting_ids = None
972
interesting_ids = set()
973
for tree_path in filenames:
975
for tree in (working_tree, target_tree):
976
file_id = tree.inventory.path2id(tree_path)
977
if file_id is not None:
978
interesting_ids.add(file_id)
981
raise NotVersionedError(path=tree_path)
982
return interesting_ids
1585
"""Find the ids corresponding to specified filenames.
1587
Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1589
working_tree.lock_read()
1591
target_tree.lock_read()
1593
return working_tree.paths2ids(filenames, [target_tree])
1595
target_tree.unlock()
1597
working_tree.unlock()
1600
@deprecated_function(zero_ninety)
985
1601
def change_entry(tt, file_id, working_tree, target_tree,
986
1602
trans_id_file_id, backups, trans_id, by_parent):
987
1603
"""Replace a file_id's contents with those from a target tree."""
1604
if file_id is None and target_tree is None:
1605
# skip the logic altogether in the deprecation test
988
1607
e_trans_id = trans_id_file_id(file_id)
989
1608
entry = target_tree.inventory[file_id]
990
1609
has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry,
1065
1684
return has_contents, contents_mod, meta_mod
1068
def revert(working_tree, target_tree, filenames, backups=False,
1069
pb=DummyProgress()):
1687
def revert(working_tree, target_tree, filenames, backups=False,
1688
pb=DummyProgress(), change_reporter=None):
1070
1689
"""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
1690
target_tree.lock_read()
1075
1691
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:
1693
pp = ProgressPhase("Revert phase", 3, pb)
1695
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1697
merge_modified = _alter_files(working_tree, target_tree, tt,
1698
child_pb, filenames, backups)
1702
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1704
raw_conflicts = resolve_conflicts(tt, child_pb,
1705
lambda t, c: conflict_pass(t, c, target_tree))
1708
conflicts = cook_conflicts(raw_conflicts, tt)
1710
change_reporter = delta._ChangeReporter(
1711
unversioned_filter=working_tree.is_ignored)
1712
delta.report_changes(tt._iter_changes(), change_reporter)
1713
for conflict in conflicts:
1717
working_tree.set_merge_modified(merge_modified)
1719
target_tree.unlock()
1725
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1727
merge_modified = working_tree.merge_modified()
1728
change_list = target_tree._iter_changes(working_tree,
1729
specific_files=specific_files, pb=pb)
1730
if target_tree.inventory.root is None:
1737
for id_num, (file_id, path, changed_content, versioned, parent, name,
1738
kind, executable) in enumerate(change_list):
1739
if skip_root and file_id[0] is not None and parent[0] is None:
1741
trans_id = tt.trans_id_file_id(file_id)
1744
keep_content = False
1745
if kind[0] == 'file' and (backups or kind[1] is None):
1746
wt_sha1 = working_tree.get_file_sha1(file_id)
1747
if merge_modified.get(file_id) != wt_sha1:
1748
# acquire the basis tree lazily to prevent the
1749
# expense of accessing it when it's not needed ?
1750
# (Guessing, RBC, 200702)
1751
if basis_tree is None:
1752
basis_tree = working_tree.basis_tree()
1753
basis_tree.lock_read()
1754
if file_id in basis_tree:
1755
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1757
elif kind[1] is None and not versioned[1]:
1759
if kind[0] is not None:
1760
if not keep_content:
1121
1761
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({})
1762
elif kind[1] is not None:
1763
parent_trans_id = tt.trans_id_file_id(parent[0])
1764
by_parent = tt.by_parent()
1765
backup_name = _get_backup_name(name[0], by_parent,
1766
parent_trans_id, tt)
1767
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1768
new_trans_id = tt.create_path(name[0], parent_trans_id)
1769
if versioned == (True, True):
1770
tt.unversion_file(trans_id)
1771
tt.version_file(file_id, new_trans_id)
1772
# New contents should have the same unix perms as old
1775
trans_id = new_trans_id
1776
if kind[1] == 'directory':
1777
tt.create_directory(trans_id)
1778
elif kind[1] == 'symlink':
1779
tt.create_symlink(target_tree.get_symlink_target(file_id),
1781
elif kind[1] == 'file':
1782
deferred_files.append((file_id, (trans_id, mode_id)))
1783
if basis_tree is None:
1784
basis_tree = working_tree.basis_tree()
1785
basis_tree.lock_read()
1786
new_sha1 = target_tree.get_file_sha1(file_id)
1787
if (file_id in basis_tree and new_sha1 ==
1788
basis_tree.get_file_sha1(file_id)):
1789
if file_id in merge_modified:
1790
del merge_modified[file_id]
1792
merge_modified[file_id] = new_sha1
1794
# preserve the execute bit when backing up
1795
if keep_content and executable[0] == executable[1]:
1796
tt.set_executability(executable[1], trans_id)
1798
assert kind[1] is None
1799
if versioned == (False, True):
1800
tt.version_file(file_id, trans_id)
1801
if versioned == (True, False):
1802
tt.unversion_file(trans_id)
1803
if (name[1] is not None and
1804
(name[0] != name[1] or parent[0] != parent[1])):
1806
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1807
if executable[0] != executable[1] and kind[1] == "file":
1808
tt.set_executability(executable[1], trans_id)
1809
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
1811
tt.create_file(bytes, trans_id, mode_id)
1143
def resolve_conflicts(tt, pb=DummyProgress()):
1813
if basis_tree is not None:
1815
return merge_modified
1818
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1144
1819
"""Make many conflict-resolution attempts, but die if they fail"""
1820
if pass_func is None:
1821
pass_func = conflict_pass
1145
1822
new_conflicts = set()
1147
1824
for n in range(10):
1188
1869
trans_id = conflict[1]
1190
1871
tt.cancel_deletion(trans_id)
1191
new_conflicts.add((c_type, 'Not deleting', trans_id))
1872
new_conflicts.add(('deleting parent', 'Not deleting',
1192
1874
except KeyError:
1193
1875
tt.create_directory(trans_id)
1194
new_conflicts.add((c_type, 'Created directory.', trans_id))
1876
new_conflicts.add((c_type, 'Created directory', trans_id))
1878
tt.final_name(trans_id)
1880
if path_tree is not None:
1881
file_id = tt.final_file_id(trans_id)
1882
entry = path_tree.inventory[file_id]
1883
parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1884
tt.adjust_path(entry.name, parent_trans_id, trans_id)
1195
1885
elif c_type == 'unversioned parent':
1196
1886
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1197
1887
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1888
elif c_type == 'non-directory parent':
1889
parent_id = conflict[1]
1890
parent_parent = tt.final_parent(parent_id)
1891
parent_name = tt.final_name(parent_id)
1892
parent_file_id = tt.final_file_id(parent_id)
1893
new_parent_id = tt.new_directory(parent_name + '.new',
1894
parent_parent, parent_file_id)
1895
_reparent_transform_children(tt, parent_id, new_parent_id)
1896
tt.unversion_file(parent_id)
1897
new_conflicts.add((c_type, 'Created directory', new_parent_id))
1198
1898
return new_conflicts
1200
1901
def cook_conflicts(raw_conflicts, tt):
1201
1902
"""Generate a list of cooked conflicts, sorted by file path"""
1203
if conflict.path is not None:
1204
return conflict.path, conflict.typestring
1205
elif getattr(conflict, "conflict_path", None) is not None:
1206
return conflict.conflict_path, conflict.typestring
1208
return None, conflict.typestring
1903
from bzrlib.conflicts import Conflict
1904
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1905
return sorted(conflict_iter, key=Conflict.sort_key)
1210
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1212
1908
def iter_cook_conflicts(raw_conflicts, tt):
1213
1909
from bzrlib.conflicts import Conflict