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
147
257
"""Change the path that is assigned to a transaction id."""
148
258
if trans_id == self._new_root:
149
259
raise CantMoveRoot
260
previous_parent = self._new_parent.get(trans_id)
261
previous_name = self._new_name.get(trans_id)
150
262
self._new_name[trans_id] = name
151
263
self._new_parent[trans_id] = parent
264
if (trans_id in self._limbo_files and
265
trans_id not in self._needs_rename):
266
self._rename_in_limbo([trans_id])
267
self._limbo_children[previous_parent].remove(trans_id)
268
del self._limbo_children_names[previous_parent][previous_name]
270
def _rename_in_limbo(self, trans_ids):
271
"""Fix limbo names so that the right final path is produced.
273
This means we outsmarted ourselves-- we tried to avoid renaming
274
these files later by creating them with their final names in their
275
final parents. But now the previous name or parent is no longer
276
suitable, so we have to rename them.
278
Even for trans_ids that have no new contents, we must remove their
279
entries from _limbo_files, because they are now stale.
281
for trans_id in trans_ids:
282
old_path = self._limbo_files.pop(trans_id)
283
if trans_id not in self._new_contents:
285
new_path = self._limbo_name(trans_id)
286
os.rename(old_path, new_path)
153
288
def adjust_root_path(self, name, parent):
154
289
"""Emulate moving the root by moving all children, instead.
867
def apply(self, no_conflicts=False, _mover=None):
680
868
"""Apply all changes to the inventory and filesystem.
682
870
If filesystem or inventory conflicts are present, MalformedTransform
873
If apply succeeds, finalize is not necessary.
875
:param no_conflicts: if True, the caller guarantees there are no
876
conflicts, so no check is made.
877
:param _mover: Supply an alternate FileMover, for testing
685
conflicts = self.find_conflicts()
686
if len(conflicts) != 0:
687
raise MalformedTransform(conflicts=conflicts)
880
conflicts = self.find_conflicts()
881
if len(conflicts) != 0:
882
raise MalformedTransform(conflicts=conflicts)
689
883
inv = self._tree.inventory
690
885
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)
892
child_pb.update('Apply phase', 0, 2)
893
self._apply_removals(inv, inventory_delta, mover)
894
child_pb.update('Apply phase', 1, 2)
895
modified_paths = self._apply_insertions(inv, inventory_delta,
901
mover.apply_deletions()
697
903
child_pb.finished()
698
self._tree._write_inventory(inv)
904
self._tree.apply_inventory_delta(inventory_delta)
699
905
self.__done = True
701
return _TransformResults(modified_paths)
907
return _TransformResults(modified_paths, self.rename_count)
703
909
def _limbo_name(self, trans_id):
704
910
"""Generate the limbo name of a file"""
705
return pathjoin(self._limbodir, trans_id)
707
def _apply_removals(self, inv, limbo_inv):
911
limbo_name = self._limbo_files.get(trans_id)
912
if limbo_name is not None:
914
parent = self._new_parent.get(trans_id)
915
# if the parent directory is already in limbo (e.g. when building a
916
# tree), choose a limbo name inside the parent, to reduce further
918
use_direct_path = False
919
if self._new_contents.get(parent) == 'directory':
920
filename = self._new_name.get(trans_id)
921
if filename is not None:
922
if parent not in self._limbo_children:
923
self._limbo_children[parent] = set()
924
self._limbo_children_names[parent] = {}
925
use_direct_path = True
926
# the direct path can only be used if no other file has
927
# already taken this pathname, i.e. if the name is unused, or
928
# if it is already associated with this trans_id.
929
elif self._tree.case_sensitive:
930
if (self._limbo_children_names[parent].get(filename)
931
in (trans_id, None)):
932
use_direct_path = True
934
for l_filename, l_trans_id in\
935
self._limbo_children_names[parent].iteritems():
936
if l_trans_id == trans_id:
938
if l_filename.lower() == filename.lower():
941
use_direct_path = True
944
limbo_name = pathjoin(self._limbo_files[parent], filename)
945
self._limbo_children[parent].add(trans_id)
946
self._limbo_children_names[parent][filename] = trans_id
948
limbo_name = pathjoin(self._limbodir, trans_id)
949
self._needs_rename.add(trans_id)
950
self._limbo_files[trans_id] = limbo_name
953
def _apply_removals(self, inv, inventory_delta, mover):
708
954
"""Perform tree operations that remove directory/inventory names.
710
956
That is, delete files that are to be deleted, and put any files that
720
966
child_pb.update('removing file', num, len(tree_paths))
721
967
full_path = self._tree.abspath(path)
722
968
if trans_id in self._removed_contents:
723
delete_any(full_path)
969
mover.pre_delete(full_path, os.path.join(self._deletiondir,
724
971
elif trans_id in self._new_name or trans_id in \
725
972
self._new_parent:
727
os.rename(full_path, self._limbo_name(trans_id))
974
mover.rename(full_path, self._limbo_name(trans_id))
728
975
except OSError, e:
729
976
if e.errno != errno.ENOENT:
979
self.rename_count += 1
731
980
if trans_id in self._removed_id:
732
981
if trans_id == self._new_root:
733
file_id = self._tree.inventory.root.file_id
982
file_id = self._tree.get_root_id()
735
984
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]
985
assert file_id is not None
986
inventory_delta.append((path, None, file_id, None))
743
988
child_pb.finished()
745
def _apply_insertions(self, inv, limbo_inv):
990
def _apply_insertions(self, inv, inventory_delta, mover):
746
991
"""Perform tree operations that insert directory/inventory names.
748
993
That is, create any files that need to be created, and restore from
775
1024
if trans_id in self._new_id:
776
1025
if kind is None:
777
1026
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
1027
if trans_id in self._new_reference_revision:
1028
new_entry = inventory.TreeReference(
1029
self._new_id[trans_id],
1030
self._new_name[trans_id],
1031
self.final_file_id(self._new_parent[trans_id]),
1032
None, self._new_reference_revision[trans_id])
1034
new_entry = inventory.make_entry(kind,
1035
self.final_name(trans_id),
1036
self.final_file_id(self.final_parent(trans_id)),
1037
self._new_id[trans_id])
1039
if trans_id in self._new_name or trans_id in\
1040
self._new_parent or\
1041
trans_id in self._new_executability:
1042
file_id = self.final_file_id(trans_id)
1043
if file_id is not None:
1044
entry = inv[file_id]
1045
new_entry = entry.copy()
1047
if trans_id in self._new_name or trans_id in\
1049
if new_entry is not None:
1050
new_entry.name = self.final_name(trans_id)
1051
parent = self.final_parent(trans_id)
1052
parent_id = self.final_file_id(parent)
1053
new_entry.parent_id = parent_id
790
1055
if trans_id in self._new_executability:
791
self._set_executability(path, inv, trans_id)
1056
self._set_executability(path, new_entry, trans_id)
1057
if new_entry is not None:
1058
if new_entry.file_id in inv:
1059
old_path = inv.id2path(new_entry.file_id)
1062
inventory_delta.append((old_path, path,
793
1066
child_pb.finished()
794
1067
return modified_paths
796
def _set_executability(self, path, inv, trans_id):
1069
def _set_executability(self, path, entry, trans_id):
797
1070
"""Set the executability of versioned files """
798
file_id = inv.path2id(path)
799
1071
new_executability = self._new_executability[trans_id]
800
inv[file_id].executable = new_executability
1072
entry.executable = new_executability
801
1073
if supports_executable():
802
1074
abspath = self._tree.abspath(path)
803
1075
current_mode = os.stat(abspath).st_mode
861
1136
self.create_symlink(target, trans_id)
1139
def _affected_ids(self):
1140
"""Return the set of transform ids affected by the transform"""
1141
trans_ids = set(self._removed_id)
1142
trans_ids.update(self._new_id.keys())
1143
trans_ids.update(self._removed_contents)
1144
trans_ids.update(self._new_contents.keys())
1145
trans_ids.update(self._new_executability.keys())
1146
trans_ids.update(self._new_name.keys())
1147
trans_ids.update(self._new_parent.keys())
1150
def _get_file_id_maps(self):
1151
"""Return mapping of file_ids to trans_ids in the to and from states"""
1152
trans_ids = self._affected_ids()
1155
# Build up two dicts: trans_ids associated with file ids in the
1156
# FROM state, vs the TO state.
1157
for trans_id in trans_ids:
1158
from_file_id = self.tree_file_id(trans_id)
1159
if from_file_id is not None:
1160
from_trans_ids[from_file_id] = trans_id
1161
to_file_id = self.final_file_id(trans_id)
1162
if to_file_id is not None:
1163
to_trans_ids[to_file_id] = trans_id
1164
return from_trans_ids, to_trans_ids
1166
def _from_file_data(self, from_trans_id, from_versioned, file_id):
1167
"""Get data about a file in the from (tree) state
1169
Return a (name, parent, kind, executable) tuple
1171
from_path = self._tree_id_paths.get(from_trans_id)
1173
# get data from working tree if versioned
1174
from_entry = self._tree.inventory[file_id]
1175
from_name = from_entry.name
1176
from_parent = from_entry.parent_id
1179
if from_path is None:
1180
# File does not exist in FROM state
1184
# File exists, but is not versioned. Have to use path-
1186
from_name = os.path.basename(from_path)
1187
tree_parent = self.get_tree_parent(from_trans_id)
1188
from_parent = self.tree_file_id(tree_parent)
1189
if from_path is not None:
1190
from_kind, from_executable, from_stats = \
1191
self._tree._comparison_data(from_entry, from_path)
1194
from_executable = False
1195
return from_name, from_parent, from_kind, from_executable
1197
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1198
"""Get data about a file in the to (target) state
1200
Return a (name, parent, kind, executable) tuple
1202
to_name = self.final_name(to_trans_id)
1204
to_kind = self.final_kind(to_trans_id)
1207
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1208
if to_trans_id in self._new_executability:
1209
to_executable = self._new_executability[to_trans_id]
1210
elif to_trans_id == from_trans_id:
1211
to_executable = from_executable
1213
to_executable = False
1214
return to_name, to_parent, to_kind, to_executable
1216
def _iter_changes(self):
1217
"""Produce output in the same format as Tree._iter_changes.
1219
Will produce nonsensical results if invoked while inventory/filesystem
1220
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1222
This reads the Transform, but only reproduces changes involving a
1223
file_id. Files that are not versioned in either of the FROM or TO
1224
states are not reflected.
1226
final_paths = FinalPaths(self)
1227
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1229
# Now iterate through all active file_ids
1230
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1232
from_trans_id = from_trans_ids.get(file_id)
1233
# find file ids, and determine versioning state
1234
if from_trans_id is None:
1235
from_versioned = False
1236
from_trans_id = to_trans_ids[file_id]
1238
from_versioned = True
1239
to_trans_id = to_trans_ids.get(file_id)
1240
if to_trans_id is None:
1241
to_versioned = False
1242
to_trans_id = from_trans_id
1246
from_name, from_parent, from_kind, from_executable = \
1247
self._from_file_data(from_trans_id, from_versioned, file_id)
1249
to_name, to_parent, to_kind, to_executable = \
1250
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1252
if not from_versioned:
1255
from_path = self._tree_id_paths.get(from_trans_id)
1256
if not to_versioned:
1259
to_path = final_paths.get_path(to_trans_id)
1260
if from_kind != to_kind:
1262
elif to_kind in ('file', 'symlink') and (
1263
to_trans_id != from_trans_id or
1264
to_trans_id in self._new_contents):
1266
if (not modified and from_versioned == to_versioned and
1267
from_parent==to_parent and from_name == to_name and
1268
from_executable == to_executable):
1270
results.append((file_id, (from_path, to_path), modified,
1271
(from_versioned, to_versioned),
1272
(from_parent, to_parent),
1273
(from_name, to_name),
1274
(from_kind, to_kind),
1275
(from_executable, to_executable)))
1276
return iter(sorted(results, key=lambda x:x[1]))
864
1279
def joinpath(parent, child):
865
1280
"""Join tree-relative paths, handling the tree root specially"""
866
1281
if parent is None or parent == "":
896
1311
self._known_paths[trans_id] = self._determine_path(trans_id)
897
1312
return self._known_paths[trans_id]
899
1315
def topology_sorted_ids(tree):
900
1316
"""Determine the topological order of the ids in a tree"""
901
1317
file_ids = list(tree)
902
1318
file_ids.sort(key=tree.id2path)
905
1322
def build_tree(tree, wt):
906
"""Create working tree for a branch, using a Transaction."""
1323
"""Create working tree for a branch, using a TreeTransform.
1325
This function should be used on empty trees, having a tree root at most.
1326
(see merge and revert functionality for working with existing trees)
1328
Existing files are handled like so:
1330
- Existing bzrdirs take precedence over creating new items. They are
1331
created as '%s.diverted' % name.
1332
- Otherwise, if the content on disk matches the content we are building,
1333
it is silently replaced.
1334
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1336
wt.lock_tree_write()
1340
return _build_tree(tree, wt)
1347
def _build_tree(tree, wt):
1348
"""See build_tree."""
1349
if len(wt.inventory) > 1: # more than just a root
1350
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
907
1351
file_trans_id = {}
908
1352
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
1353
pp = ProgressPhase("Build phase", 2, top_pb)
1354
if tree.inventory.root is not None:
1355
# This is kind of a hack: we should be altering the root
1356
# as part of the regular tree shape diff logic.
1357
# The conditional test here is to avoid doing an
1358
# expensive operation (flush) every time the root id
1359
# is set within the tree, nor setting the root and thus
1360
# marking the tree as dirty, because we use two different
1361
# idioms here: tree interfaces and inventory interfaces.
1362
if wt.get_root_id() != tree.get_root_id():
1363
wt.set_root_id(tree.get_root_id())
910
1365
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)
1369
file_trans_id[wt.get_root_id()] = \
1370
tt.trans_id_tree_file_id(wt.get_root_id())
915
1371
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]
1373
deferred_contents = []
1374
for num, (tree_path, entry) in \
1375
enumerate(tree.inventory.iter_entries_by_dir()):
1376
pb.update("Building tree", num - len(deferred_contents),
1377
len(tree.inventory))
920
1378
if entry.parent_id is None:
1381
file_id = entry.file_id
1382
target_path = wt.abspath(tree_path)
1384
kind = file_kind(target_path)
1388
if kind == "directory":
1390
bzrdir.BzrDir.open(target_path)
1391
except errors.NotBranchError:
1395
if (file_id not in divert and
1396
_content_match(tree, entry, file_id, kind,
1398
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1399
if kind == 'directory':
922
1401
if entry.parent_id not in file_trans_id:
923
raise repr(entry.parent_id)
1402
raise AssertionError(
1403
'entry %s parent id %r is not in file_trans_id %r'
1404
% (entry, entry.parent_id, file_trans_id))
924
1405
parent_id = file_trans_id[entry.parent_id]
925
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1406
if entry.kind == 'file':
1407
# We *almost* replicate new_by_entry, so that we can defer
1408
# getting the file text, and get them all at once.
1409
trans_id = tt.create_path(entry.name, parent_id)
1410
file_trans_id[file_id] = trans_id
1411
tt.version_file(entry.file_id, trans_id)
1412
executable = tree.is_executable(entry.file_id, tree_path)
1413
if executable is not None:
1414
tt.set_executability(executable, trans_id)
1415
deferred_contents.append((entry.file_id, trans_id))
1417
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1420
new_trans_id = file_trans_id[file_id]
1421
old_parent = tt.trans_id_tree_path(tree_path)
1422
_reparent_children(tt, old_parent, new_trans_id)
1423
for num, (trans_id, bytes) in enumerate(
1424
tree.iter_files_bytes(deferred_contents)):
1425
tt.create_file(bytes, trans_id)
1426
pb.update('Adding file contents',
1427
(num + len(tree.inventory) - len(deferred_contents)),
1428
len(tree.inventory))
1432
divert_trans = set(file_trans_id[f] for f in divert)
1433
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1434
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1435
conflicts = cook_conflicts(raw_conflicts, tt)
1436
for conflict in conflicts:
1439
wt.add_conflicts(conflicts)
1440
except errors.UnsupportedOperation:
933
1445
top_pb.finished()
1449
def _reparent_children(tt, old_parent, new_parent):
1450
for child in tt.iter_tree_children(old_parent):
1451
tt.adjust_path(tt.final_name(child), new_parent, child)
1454
def _content_match(tree, entry, file_id, kind, target_path):
1455
if entry.kind != kind:
1457
if entry.kind == "directory":
1459
if entry.kind == "file":
1460
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1462
elif entry.kind == "symlink":
1463
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1468
def resolve_checkout(tt, conflicts, divert):
1469
new_conflicts = set()
1470
for c_type, conflict in ((c[0], c) for c in conflicts):
1471
# Anything but a 'duplicate' would indicate programmer error
1472
assert c_type == 'duplicate', c_type
1473
# Now figure out which is new and which is old
1474
if tt.new_contents(conflict[1]):
1475
new_file = conflict[1]
1476
old_file = conflict[2]
1478
new_file = conflict[2]
1479
old_file = conflict[1]
1481
# We should only get here if the conflict wasn't completely
1483
final_parent = tt.final_parent(old_file)
1484
if new_file in divert:
1485
new_name = tt.final_name(old_file)+'.diverted'
1486
tt.adjust_path(new_name, final_parent, new_file)
1487
new_conflicts.add((c_type, 'Diverted to',
1488
new_file, old_file))
1490
new_name = tt.final_name(old_file)+'.moved'
1491
tt.adjust_path(new_name, final_parent, old_file)
1492
new_conflicts.add((c_type, 'Moved existing file to',
1493
old_file, new_file))
1494
return new_conflicts
935
1497
def new_by_entry(tt, entry, parent_id, tree):
936
1498
"""Create a new file according to its inventory entry"""
958
1526
elif entry.kind == "directory":
959
1527
tt.create_directory(trans_id)
961
1530
def create_entry_executability(tt, entry, trans_id):
962
1531
"""Set the executability of a trans_id according to an inventory entry"""
963
1532
if entry.kind == "file":
964
1533
tt.set_executability(entry.executable, trans_id)
1536
@deprecated_function(zero_fifteen)
967
1537
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
1538
"""Find the ids corresponding to specified filenames.
1540
Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1542
working_tree.lock_read()
1544
target_tree.lock_read()
1546
return working_tree.paths2ids(filenames, [target_tree])
1548
target_tree.unlock()
1550
working_tree.unlock()
1553
@deprecated_function(zero_ninety)
985
1554
def change_entry(tt, file_id, working_tree, target_tree,
986
1555
trans_id_file_id, backups, trans_id, by_parent):
987
1556
"""Replace a file_id's contents with those from a target tree."""
1557
if file_id is None and target_tree is None:
1558
# skip the logic altogether in the deprecation test
988
1560
e_trans_id = trans_id_file_id(file_id)
989
1561
entry = target_tree.inventory[file_id]
990
1562
has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry,
1065
1637
return has_contents, contents_mod, meta_mod
1068
def revert(working_tree, target_tree, filenames, backups=False,
1069
pb=DummyProgress()):
1640
def revert(working_tree, target_tree, filenames, backups=False,
1641
pb=DummyProgress(), change_reporter=None):
1070
1642
"""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
1643
target_tree.lock_read()
1075
1644
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:
1646
pp = ProgressPhase("Revert phase", 3, pb)
1648
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1650
merge_modified = _alter_files(working_tree, target_tree, tt,
1651
child_pb, filenames, backups)
1655
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1657
raw_conflicts = resolve_conflicts(tt, child_pb,
1658
lambda t, c: conflict_pass(t, c, target_tree))
1661
conflicts = cook_conflicts(raw_conflicts, tt)
1663
change_reporter = delta._ChangeReporter(
1664
unversioned_filter=working_tree.is_ignored)
1665
delta.report_changes(tt._iter_changes(), change_reporter)
1666
for conflict in conflicts:
1670
working_tree.set_merge_modified(merge_modified)
1672
target_tree.unlock()
1678
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1680
merge_modified = working_tree.merge_modified()
1681
change_list = target_tree._iter_changes(working_tree,
1682
specific_files=specific_files, pb=pb)
1683
if target_tree.inventory.root is None:
1690
for id_num, (file_id, path, changed_content, versioned, parent, name,
1691
kind, executable) in enumerate(change_list):
1692
if skip_root and file_id[0] is not None and parent[0] is None:
1694
trans_id = tt.trans_id_file_id(file_id)
1697
keep_content = False
1698
if kind[0] == 'file' and (backups or kind[1] is None):
1699
wt_sha1 = working_tree.get_file_sha1(file_id)
1700
if merge_modified.get(file_id) != wt_sha1:
1701
# acquire the basis tree lazily to prevent the
1702
# expense of accessing it when it's not needed ?
1703
# (Guessing, RBC, 200702)
1704
if basis_tree is None:
1705
basis_tree = working_tree.basis_tree()
1706
basis_tree.lock_read()
1707
if file_id in basis_tree:
1708
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1710
elif kind[1] is None and not versioned[1]:
1712
if kind[0] is not None:
1713
if not keep_content:
1121
1714
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({})
1715
elif kind[1] is not None:
1716
parent_trans_id = tt.trans_id_file_id(parent[0])
1717
by_parent = tt.by_parent()
1718
backup_name = _get_backup_name(name[0], by_parent,
1719
parent_trans_id, tt)
1720
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1721
new_trans_id = tt.create_path(name[0], parent_trans_id)
1722
if versioned == (True, True):
1723
tt.unversion_file(trans_id)
1724
tt.version_file(file_id, new_trans_id)
1725
# New contents should have the same unix perms as old
1728
trans_id = new_trans_id
1729
if kind[1] == 'directory':
1730
tt.create_directory(trans_id)
1731
elif kind[1] == 'symlink':
1732
tt.create_symlink(target_tree.get_symlink_target(file_id),
1734
elif kind[1] == 'file':
1735
deferred_files.append((file_id, (trans_id, mode_id)))
1736
if basis_tree is None:
1737
basis_tree = working_tree.basis_tree()
1738
basis_tree.lock_read()
1739
new_sha1 = target_tree.get_file_sha1(file_id)
1740
if (file_id in basis_tree and new_sha1 ==
1741
basis_tree.get_file_sha1(file_id)):
1742
if file_id in merge_modified:
1743
del merge_modified[file_id]
1745
merge_modified[file_id] = new_sha1
1747
# preserve the execute bit when backing up
1748
if keep_content and executable[0] == executable[1]:
1749
tt.set_executability(executable[1], trans_id)
1751
assert kind[1] is None
1752
if versioned == (False, True):
1753
tt.version_file(file_id, trans_id)
1754
if versioned == (True, False):
1755
tt.unversion_file(trans_id)
1756
if (name[1] is not None and
1757
(name[0] != name[1] or parent[0] != parent[1])):
1759
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1760
if executable[0] != executable[1] and kind[1] == "file":
1761
tt.set_executability(executable[1], trans_id)
1762
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
1764
tt.create_file(bytes, trans_id, mode_id)
1143
def resolve_conflicts(tt, pb=DummyProgress()):
1766
if basis_tree is not None:
1768
return merge_modified
1771
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1144
1772
"""Make many conflict-resolution attempts, but die if they fail"""
1773
if pass_func is None:
1774
pass_func = conflict_pass
1145
1775
new_conflicts = set()
1147
1777
for n in range(10):
1165
1800
conflict[1], conflict[2], ))
1166
1801
elif c_type == 'duplicate':
1167
1802
# files that were renamed take precedence
1168
new_name = tt.final_name(conflict[1])+'.moved'
1169
1803
final_parent = tt.final_parent(conflict[1])
1170
1804
if tt.path_changed(conflict[1]):
1171
tt.adjust_path(new_name, final_parent, conflict[2])
1172
new_conflicts.add((c_type, 'Moved existing file to',
1173
conflict[2], conflict[1]))
1805
existing_file, new_file = conflict[2], conflict[1]
1175
tt.adjust_path(new_name, final_parent, conflict[1])
1176
new_conflicts.add((c_type, 'Moved existing file to',
1177
conflict[1], conflict[2]))
1807
existing_file, new_file = conflict[1], conflict[2]
1808
new_name = tt.final_name(existing_file)+'.moved'
1809
tt.adjust_path(new_name, final_parent, existing_file)
1810
new_conflicts.add((c_type, 'Moved existing file to',
1811
existing_file, new_file))
1178
1812
elif c_type == 'parent loop':
1179
1813
# break the loop by undoing one of the ops that caused the loop
1180
1814
cur = conflict[1]
1188
1822
trans_id = conflict[1]
1190
1824
tt.cancel_deletion(trans_id)
1191
new_conflicts.add((c_type, 'Not deleting', trans_id))
1825
new_conflicts.add(('deleting parent', 'Not deleting',
1192
1827
except KeyError:
1193
1828
tt.create_directory(trans_id)
1194
new_conflicts.add((c_type, 'Created directory.', trans_id))
1829
new_conflicts.add((c_type, 'Created directory', trans_id))
1831
tt.final_name(trans_id)
1833
if path_tree is not None:
1834
file_id = tt.final_file_id(trans_id)
1835
entry = path_tree.inventory[file_id]
1836
parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1837
tt.adjust_path(entry.name, parent_trans_id, trans_id)
1195
1838
elif c_type == 'unversioned parent':
1196
1839
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1197
1840
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1198
1841
return new_conflicts
1200
1844
def cook_conflicts(raw_conflicts, tt):
1201
1845
"""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
1846
from bzrlib.conflicts import Conflict
1847
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1848
return sorted(conflict_iter, key=Conflict.sort_key)
1210
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1212
1851
def iter_cook_conflicts(raw_conflicts, tt):
1213
1852
from bzrlib.conflicts import Conflict