154
203
"""Change the path that is assigned to a transaction id."""
155
204
if trans_id == self._new_root:
156
205
raise CantMoveRoot
206
previous_parent = self._new_parent.get(trans_id)
207
previous_name = self._new_name.get(trans_id)
157
208
self._new_name[trans_id] = name
158
209
self._new_parent[trans_id] = parent
210
if (trans_id in self._limbo_files and
211
trans_id not in self._needs_rename):
212
self._rename_in_limbo([trans_id])
213
self._limbo_children[previous_parent].remove(trans_id)
214
del self._limbo_children_names[previous_parent][previous_name]
216
def _rename_in_limbo(self, trans_ids):
217
"""Fix limbo names so that the right final path is produced.
219
This means we outsmarted ourselves-- we tried to avoid renaming
220
these files later by creating them with their final names in their
221
final parents. But now the previous name or parent is no longer
222
suitable, so we have to rename them.
224
Even for trans_ids that have no new contents, we must remove their
225
entries from _limbo_files, because they are now stale.
227
for trans_id in trans_ids:
228
old_path = self._limbo_files.pop(trans_id)
229
if trans_id not in self._new_contents:
231
new_path = self._limbo_name(trans_id)
232
os.rename(old_path, new_path)
160
234
def adjust_root_path(self, name, parent):
161
235
"""Emulate moving the root by moving all children, instead.
804
def apply(self, no_conflicts=False, _mover=None):
709
805
"""Apply all changes to the inventory and filesystem.
711
807
If filesystem or inventory conflicts are present, MalformedTransform
810
If apply succeeds, finalize is not necessary.
812
:param no_conflicts: if True, the caller guarantees there are no
813
conflicts, so no check is made.
814
:param _mover: Supply an alternate FileMover, for testing
714
conflicts = self.find_conflicts()
715
if len(conflicts) != 0:
716
raise MalformedTransform(conflicts=conflicts)
817
conflicts = self.find_conflicts()
818
if len(conflicts) != 0:
819
raise MalformedTransform(conflicts=conflicts)
718
820
inv = self._tree.inventory
719
822
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
721
child_pb.update('Apply phase', 0, 2)
722
self._apply_removals(inv, limbo_inv)
723
child_pb.update('Apply phase', 1, 2)
724
modified_paths = self._apply_insertions(inv, limbo_inv)
829
child_pb.update('Apply phase', 0, 2)
830
self._apply_removals(inv, inventory_delta, mover)
831
child_pb.update('Apply phase', 1, 2)
832
modified_paths = self._apply_insertions(inv, inventory_delta,
838
mover.apply_deletions()
726
840
child_pb.finished()
727
self._tree._write_inventory(inv)
841
self._tree.apply_inventory_delta(inventory_delta)
728
842
self.__done = True
730
return _TransformResults(modified_paths)
844
return _TransformResults(modified_paths, self.rename_count)
732
846
def _limbo_name(self, trans_id):
733
847
"""Generate the limbo name of a file"""
734
return pathjoin(self._limbodir, trans_id)
848
limbo_name = self._limbo_files.get(trans_id)
849
if limbo_name is not None:
851
parent = self._new_parent.get(trans_id)
852
# if the parent directory is already in limbo (e.g. when building a
853
# tree), choose a limbo name inside the parent, to reduce further
855
use_direct_path = False
856
if self._new_contents.get(parent) == 'directory':
857
filename = self._new_name.get(trans_id)
858
if filename is not None:
859
if parent not in self._limbo_children:
860
self._limbo_children[parent] = set()
861
self._limbo_children_names[parent] = {}
862
use_direct_path = True
863
# the direct path can only be used if no other file has
864
# already taken this pathname, i.e. if the name is unused, or
865
# if it is already associated with this trans_id.
866
elif (self._limbo_children_names[parent].get(filename)
867
in (trans_id, None)):
868
use_direct_path = True
870
limbo_name = pathjoin(self._limbo_files[parent], filename)
871
self._limbo_children[parent].add(trans_id)
872
self._limbo_children_names[parent][filename] = trans_id
874
limbo_name = pathjoin(self._limbodir, trans_id)
875
self._needs_rename.add(trans_id)
876
self._limbo_files[trans_id] = limbo_name
736
def _apply_removals(self, inv, limbo_inv):
879
def _apply_removals(self, inv, inventory_delta, mover):
737
880
"""Perform tree operations that remove directory/inventory names.
739
882
That is, delete files that are to be deleted, and put any files that
749
892
child_pb.update('removing file', num, len(tree_paths))
750
893
full_path = self._tree.abspath(path)
751
894
if trans_id in self._removed_contents:
752
delete_any(full_path)
895
mover.pre_delete(full_path, os.path.join(self._deletiondir,
753
897
elif trans_id in self._new_name or trans_id in \
754
898
self._new_parent:
756
os.rename(full_path, self._limbo_name(trans_id))
900
mover.rename(full_path, self._limbo_name(trans_id))
757
901
except OSError, e:
758
902
if e.errno != errno.ENOENT:
905
self.rename_count += 1
760
906
if trans_id in self._removed_id:
761
907
if trans_id == self._new_root:
762
908
file_id = self._tree.inventory.root.file_id
764
910
file_id = self.tree_file_id(trans_id)
766
elif trans_id in self._new_name or trans_id in self._new_parent:
767
file_id = self.tree_file_id(trans_id)
768
if file_id is not None:
769
limbo_inv[trans_id] = inv[file_id]
911
assert file_id is not None
912
inventory_delta.append((path, None, file_id, None))
772
914
child_pb.finished()
774
def _apply_insertions(self, inv, limbo_inv):
916
def _apply_insertions(self, inv, inventory_delta, mover):
775
917
"""Perform tree operations that insert directory/inventory names.
777
919
That is, create any files that need to be created, and restore from
804
950
if trans_id in self._new_id:
806
952
kind = file_kind(self._tree.abspath(path))
807
inv.add_path(path, kind, self._new_id[trans_id])
808
elif trans_id in self._new_name or trans_id in\
810
entry = limbo_inv.get(trans_id)
811
if entry is not None:
812
entry.name = self.final_name(trans_id)
813
parent_path = os.path.dirname(path)
815
self._tree.inventory.path2id(parent_path)
818
# requires files and inventory entries to be in place
953
if trans_id in self._new_reference_revision:
954
new_entry = inventory.TreeReference(
955
self._new_id[trans_id],
956
self._new_name[trans_id],
957
self.final_file_id(self._new_parent[trans_id]),
958
None, self._new_reference_revision[trans_id])
960
new_entry = inventory.make_entry(kind,
961
self.final_name(trans_id),
962
self.final_file_id(self.final_parent(trans_id)),
963
self._new_id[trans_id])
965
if trans_id in self._new_name or trans_id in\
967
trans_id in self._new_executability:
968
file_id = self.final_file_id(trans_id)
969
if file_id is not None:
971
new_entry = entry.copy()
973
if trans_id in self._new_name or trans_id in\
975
if new_entry is not None:
976
new_entry.name = self.final_name(trans_id)
977
parent = self.final_parent(trans_id)
978
parent_id = self.final_file_id(parent)
979
new_entry.parent_id = parent_id
819
981
if trans_id in self._new_executability:
820
self._set_executability(path, inv, trans_id)
982
self._set_executability(path, new_entry, trans_id)
983
if new_entry is not None:
984
if new_entry.file_id in inv:
985
old_path = inv.id2path(new_entry.file_id)
988
inventory_delta.append((old_path, path,
822
992
child_pb.finished()
823
993
return modified_paths
825
def _set_executability(self, path, inv, trans_id):
995
def _set_executability(self, path, entry, trans_id):
826
996
"""Set the executability of versioned files """
827
file_id = inv.path2id(path)
828
997
new_executability = self._new_executability[trans_id]
829
inv[file_id].executable = new_executability
998
entry.executable = new_executability
830
999
if supports_executable():
831
1000
abspath = self._tree.abspath(path)
832
1001
current_mode = os.stat(abspath).st_mode
893
1062
self.create_symlink(target, trans_id)
1065
def _affected_ids(self):
1066
"""Return the set of transform ids affected by the transform"""
1067
trans_ids = set(self._removed_id)
1068
trans_ids.update(self._new_id.keys())
1069
trans_ids.update(self._removed_contents)
1070
trans_ids.update(self._new_contents.keys())
1071
trans_ids.update(self._new_executability.keys())
1072
trans_ids.update(self._new_name.keys())
1073
trans_ids.update(self._new_parent.keys())
1076
def _get_file_id_maps(self):
1077
"""Return mapping of file_ids to trans_ids in the to and from states"""
1078
trans_ids = self._affected_ids()
1081
# Build up two dicts: trans_ids associated with file ids in the
1082
# FROM state, vs the TO state.
1083
for trans_id in trans_ids:
1084
from_file_id = self.tree_file_id(trans_id)
1085
if from_file_id is not None:
1086
from_trans_ids[from_file_id] = trans_id
1087
to_file_id = self.final_file_id(trans_id)
1088
if to_file_id is not None:
1089
to_trans_ids[to_file_id] = trans_id
1090
return from_trans_ids, to_trans_ids
1092
def _from_file_data(self, from_trans_id, from_versioned, file_id):
1093
"""Get data about a file in the from (tree) state
1095
Return a (name, parent, kind, executable) tuple
1097
from_path = self._tree_id_paths.get(from_trans_id)
1099
# get data from working tree if versioned
1100
from_entry = self._tree.inventory[file_id]
1101
from_name = from_entry.name
1102
from_parent = from_entry.parent_id
1105
if from_path is None:
1106
# File does not exist in FROM state
1110
# File exists, but is not versioned. Have to use path-
1112
from_name = os.path.basename(from_path)
1113
tree_parent = self.get_tree_parent(from_trans_id)
1114
from_parent = self.tree_file_id(tree_parent)
1115
if from_path is not None:
1116
from_kind, from_executable, from_stats = \
1117
self._tree._comparison_data(from_entry, from_path)
1120
from_executable = False
1121
return from_name, from_parent, from_kind, from_executable
1123
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1124
"""Get data about a file in the to (target) state
1126
Return a (name, parent, kind, executable) tuple
1128
to_name = self.final_name(to_trans_id)
1130
to_kind = self.final_kind(to_trans_id)
1133
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1134
if to_trans_id in self._new_executability:
1135
to_executable = self._new_executability[to_trans_id]
1136
elif to_trans_id == from_trans_id:
1137
to_executable = from_executable
1139
to_executable = False
1140
return to_name, to_parent, to_kind, to_executable
1142
def _iter_changes(self):
1143
"""Produce output in the same format as Tree._iter_changes.
1145
Will produce nonsensical results if invoked while inventory/filesystem
1146
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1148
This reads the Transform, but only reproduces changes involving a
1149
file_id. Files that are not versioned in either of the FROM or TO
1150
states are not reflected.
1152
final_paths = FinalPaths(self)
1153
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1155
# Now iterate through all active file_ids
1156
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1158
from_trans_id = from_trans_ids.get(file_id)
1159
# find file ids, and determine versioning state
1160
if from_trans_id is None:
1161
from_versioned = False
1162
from_trans_id = to_trans_ids[file_id]
1164
from_versioned = True
1165
to_trans_id = to_trans_ids.get(file_id)
1166
if to_trans_id is None:
1167
to_versioned = False
1168
to_trans_id = from_trans_id
1172
from_name, from_parent, from_kind, from_executable = \
1173
self._from_file_data(from_trans_id, from_versioned, file_id)
1175
to_name, to_parent, to_kind, to_executable = \
1176
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1178
if not from_versioned:
1181
from_path = self._tree_id_paths.get(from_trans_id)
1182
if not to_versioned:
1185
to_path = final_paths.get_path(to_trans_id)
1186
if from_kind != to_kind:
1188
elif to_kind in ('file', 'symlink') and (
1189
to_trans_id != from_trans_id or
1190
to_trans_id in self._new_contents):
1192
if (not modified and from_versioned == to_versioned and
1193
from_parent==to_parent and from_name == to_name and
1194
from_executable == to_executable):
1196
results.append((file_id, (from_path, to_path), modified,
1197
(from_versioned, to_versioned),
1198
(from_parent, to_parent),
1199
(from_name, to_name),
1200
(from_kind, to_kind),
1201
(from_executable, to_executable)))
1202
return iter(sorted(results, key=lambda x:x[1]))
896
1205
def joinpath(parent, child):
897
1206
"""Join tree-relative paths, handling the tree root specially"""
898
1207
if parent is None or parent == "":
934
1243
file_ids.sort(key=tree.id2path)
937
1247
def build_tree(tree, wt):
938
"""Create working tree for a branch, using a Transaction."""
1248
"""Create working tree for a branch, using a TreeTransform.
1250
This function should be used on empty trees, having a tree root at most.
1251
(see merge and revert functionality for working with existing trees)
1253
Existing files are handled like so:
1255
- Existing bzrdirs take precedence over creating new items. They are
1256
created as '%s.diverted' % name.
1257
- Otherwise, if the content on disk matches the content we are building,
1258
it is silently replaced.
1259
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1261
wt.lock_tree_write()
1265
return _build_tree(tree, wt)
1271
def _build_tree(tree, wt):
1272
"""See build_tree."""
1273
if len(wt.inventory) > 1: # more than just a root
1274
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
939
1275
file_trans_id = {}
940
1276
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
941
1277
pp = ProgressPhase("Build phase", 2, top_pb)
1278
if tree.inventory.root is not None:
1279
# This is kind of a hack: we should be altering the root
1280
# as part of the regular tree shape diff logic.
1281
# The conditional test here is to avoid doing an
1282
# expensive operation (flush) every time the root id
1283
# is set within the tree, nor setting the root and thus
1284
# marking the tree as dirty, because we use two different
1285
# idioms here: tree interfaces and inventory interfaces.
1286
if wt.path2id('') != tree.inventory.root.file_id:
1287
wt.set_root_id(tree.inventory.root.file_id)
942
1289
tt = TreeTransform(wt)
945
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
946
file_ids = topology_sorted_ids(tree)
1293
file_trans_id[wt.get_root_id()] = \
1294
tt.trans_id_tree_file_id(wt.get_root_id())
947
1295
pb = bzrlib.ui.ui_factory.nested_progress_bar()
949
for num, file_id in enumerate(file_ids):
950
pb.update("Building tree", num, len(file_ids))
951
entry = tree.inventory[file_id]
1297
deferred_contents = []
1298
for num, (tree_path, entry) in \
1299
enumerate(tree.inventory.iter_entries_by_dir()):
1300
pb.update("Building tree", num - len(deferred_contents),
1301
len(tree.inventory))
952
1302
if entry.parent_id is None:
1305
file_id = entry.file_id
1306
target_path = wt.abspath(tree_path)
1308
kind = file_kind(target_path)
1312
if kind == "directory":
1314
bzrdir.BzrDir.open(target_path)
1315
except errors.NotBranchError:
1319
if (file_id not in divert and
1320
_content_match(tree, entry, file_id, kind,
1322
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1323
if kind == 'directory':
954
1325
if entry.parent_id not in file_trans_id:
955
raise repr(entry.parent_id)
1326
raise AssertionError(
1327
'entry %s parent id %r is not in file_trans_id %r'
1328
% (entry, entry.parent_id, file_trans_id))
956
1329
parent_id = file_trans_id[entry.parent_id]
957
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1330
if entry.kind == 'file':
1331
# We *almost* replicate new_by_entry, so that we can defer
1332
# getting the file text, and get them all at once.
1333
trans_id = tt.create_path(entry.name, parent_id)
1334
file_trans_id[file_id] = trans_id
1335
tt.version_file(entry.file_id, trans_id)
1336
executable = tree.is_executable(entry.file_id, tree_path)
1337
if executable is not None:
1338
tt.set_executability(executable, trans_id)
1339
deferred_contents.append((entry.file_id, trans_id))
1341
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1344
new_trans_id = file_trans_id[file_id]
1345
old_parent = tt.trans_id_tree_path(tree_path)
1346
_reparent_children(tt, old_parent, new_trans_id)
1347
for num, (trans_id, bytes) in enumerate(
1348
tree.iter_files_bytes(deferred_contents)):
1349
tt.create_file(bytes, trans_id)
1350
pb.update('Adding file contents',
1351
(num + len(tree.inventory) - len(deferred_contents)),
1352
len(tree.inventory))
1356
divert_trans = set(file_trans_id[f] for f in divert)
1357
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1358
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1359
conflicts = cook_conflicts(raw_conflicts, tt)
1360
for conflict in conflicts:
1363
wt.add_conflicts(conflicts)
1364
except errors.UnsupportedOperation:
965
1369
top_pb.finished()
1373
def _reparent_children(tt, old_parent, new_parent):
1374
for child in tt.iter_tree_children(old_parent):
1375
tt.adjust_path(tt.final_name(child), new_parent, child)
1378
def _content_match(tree, entry, file_id, kind, target_path):
1379
if entry.kind != kind:
1381
if entry.kind == "directory":
1383
if entry.kind == "file":
1384
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1386
elif entry.kind == "symlink":
1387
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1392
def resolve_checkout(tt, conflicts, divert):
1393
new_conflicts = set()
1394
for c_type, conflict in ((c[0], c) for c in conflicts):
1395
# Anything but a 'duplicate' would indicate programmer error
1396
assert c_type == 'duplicate', c_type
1397
# Now figure out which is new and which is old
1398
if tt.new_contents(conflict[1]):
1399
new_file = conflict[1]
1400
old_file = conflict[2]
1402
new_file = conflict[2]
1403
old_file = conflict[1]
1405
# We should only get here if the conflict wasn't completely
1407
final_parent = tt.final_parent(old_file)
1408
if new_file in divert:
1409
new_name = tt.final_name(old_file)+'.diverted'
1410
tt.adjust_path(new_name, final_parent, new_file)
1411
new_conflicts.add((c_type, 'Diverted to',
1412
new_file, old_file))
1414
new_name = tt.final_name(old_file)+'.moved'
1415
tt.adjust_path(new_name, final_parent, old_file)
1416
new_conflicts.add((c_type, 'Moved existing file to',
1417
old_file, new_file))
1418
return new_conflicts
967
1421
def new_by_entry(tt, entry, parent_id, tree):
968
1422
"""Create a new file according to its inventory entry"""
973
1427
executable = tree.is_executable(entry.file_id)
974
1428
return tt.new_file(name, parent_id, contents, entry.file_id,
976
elif kind == 'directory':
977
return tt.new_directory(name, parent_id, entry.file_id)
1430
elif kind in ('directory', 'tree-reference'):
1431
trans_id = tt.new_directory(name, parent_id, entry.file_id)
1432
if kind == 'tree-reference':
1433
tt.set_tree_reference(entry.reference_revision, trans_id)
978
1435
elif kind == 'symlink':
979
1436
target = tree.get_symlink_target(entry.file_id)
980
1437
return tt.new_symlink(name, parent_id, target, entry.file_id)
1439
raise errors.BadFileKindError(name, kind)
982
1441
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
983
1442
"""Create new file contents according to an inventory entry."""
984
1443
if entry.kind == "file":
986
1445
lines = tree.get_file(entry.file_id).readlines()
987
1446
tt.create_file(lines, trans_id, mode_id=mode_id)
988
1447
elif entry.kind == "symlink":
996
1455
tt.set_executability(entry.executable, trans_id)
1458
@deprecated_function(zero_fifteen)
999
1459
def find_interesting(working_tree, target_tree, filenames):
1000
"""Find the ids corresponding to specified filenames."""
1001
trees = (working_tree, target_tree)
1002
return tree.find_ids_across_trees(filenames, trees)
1460
"""Find the ids corresponding to specified filenames.
1462
Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1464
working_tree.lock_read()
1466
target_tree.lock_read()
1468
return working_tree.paths2ids(filenames, [target_tree])
1470
target_tree.unlock()
1472
working_tree.unlock()
1475
@deprecated_function(zero_ninety)
1005
1476
def change_entry(tt, file_id, working_tree, target_tree,
1006
1477
trans_id_file_id, backups, trans_id, by_parent):
1007
1478
"""Replace a file_id's contents with those from a target tree."""
1479
if file_id is None and target_tree is None:
1480
# skip the logic altogether in the deprecation test
1008
1482
e_trans_id = trans_id_file_id(file_id)
1009
1483
entry = target_tree.inventory[file_id]
1010
1484
has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry,
1080
1559
return has_contents, contents_mod, meta_mod
1083
def revert(working_tree, target_tree, filenames, backups=False,
1084
pb=DummyProgress()):
1562
def revert(working_tree, target_tree, filenames, backups=False,
1563
pb=DummyProgress(), change_reporter=None):
1085
1564
"""Revert a working tree's contents to those of a target tree."""
1086
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1087
def interesting(file_id):
1088
return interesting_ids is None or file_id in interesting_ids
1565
target_tree.lock_read()
1090
1566
tt = TreeTransform(working_tree, pb)
1092
merge_modified = working_tree.merge_modified()
1094
def trans_id_file_id(file_id):
1096
return trans_id[file_id]
1098
return tt.trans_id_tree_file_id(file_id)
1100
pp = ProgressPhase("Revert phase", 4, pb)
1102
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1104
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1106
by_parent = tt.by_parent()
1107
for id_num, file_id in enumerate(sorted_interesting):
1108
child_pb.update("Reverting file", id_num+1,
1109
len(sorted_interesting))
1110
if file_id not in working_tree.inventory:
1111
entry = target_tree.inventory[file_id]
1112
parent_id = trans_id_file_id(entry.parent_id)
1113
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1114
trans_id[file_id] = e_trans_id
1116
backup_this = backups
1117
if file_id in merge_modified:
1119
del merge_modified[file_id]
1120
change_entry(tt, file_id, working_tree, target_tree,
1121
trans_id_file_id, backup_this, trans_id,
1126
wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1127
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1129
for id_num, file_id in enumerate(wt_interesting):
1130
child_pb.update("New file check", id_num+1,
1131
len(sorted_interesting))
1132
if file_id not in target_tree:
1133
trans_id = tt.trans_id_tree_file_id(file_id)
1134
tt.unversion_file(trans_id)
1135
if file_id in merge_modified:
1136
tt.delete_contents(trans_id)
1137
del merge_modified[file_id]
1568
pp = ProgressPhase("Revert phase", 3, pb)
1570
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1572
merge_modified = _alter_files(working_tree, target_tree, tt,
1573
child_pb, filenames, backups)
1139
1575
child_pb.finished()
1140
1576
pp.next_phase()
1145
1581
child_pb.finished()
1146
1582
conflicts = cook_conflicts(raw_conflicts, tt)
1584
change_reporter = delta._ChangeReporter(
1585
unversioned_filter=working_tree.is_ignored)
1586
delta.report_changes(tt._iter_changes(), change_reporter)
1147
1587
for conflict in conflicts:
1148
1588
warning(conflict)
1149
1589
pp.next_phase()
1151
working_tree.set_merge_modified({})
1591
working_tree.set_merge_modified(merge_modified)
1593
target_tree.unlock()
1155
1596
return conflicts
1158
def resolve_conflicts(tt, pb=DummyProgress()):
1599
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1601
merge_modified = working_tree.merge_modified()
1602
change_list = target_tree._iter_changes(working_tree,
1603
specific_files=specific_files, pb=pb)
1604
if target_tree.inventory.root is None:
1611
for id_num, (file_id, path, changed_content, versioned, parent, name,
1612
kind, executable) in enumerate(change_list):
1613
if skip_root and file_id[0] is not None and parent[0] is None:
1615
trans_id = tt.trans_id_file_id(file_id)
1618
keep_content = False
1619
if kind[0] == 'file' and (backups or kind[1] is None):
1620
wt_sha1 = working_tree.get_file_sha1(file_id)
1621
if merge_modified.get(file_id) != wt_sha1:
1622
# acquire the basis tree lazily to prevent the
1623
# expense of accessing it when it's not needed ?
1624
# (Guessing, RBC, 200702)
1625
if basis_tree is None:
1626
basis_tree = working_tree.basis_tree()
1627
basis_tree.lock_read()
1628
if file_id in basis_tree:
1629
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1631
elif kind[1] is None and not versioned[1]:
1633
if kind[0] is not None:
1634
if not keep_content:
1635
tt.delete_contents(trans_id)
1636
elif kind[1] is not None:
1637
parent_trans_id = tt.trans_id_file_id(parent[0])
1638
by_parent = tt.by_parent()
1639
backup_name = _get_backup_name(name[0], by_parent,
1640
parent_trans_id, tt)
1641
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1642
new_trans_id = tt.create_path(name[0], parent_trans_id)
1643
if versioned == (True, True):
1644
tt.unversion_file(trans_id)
1645
tt.version_file(file_id, new_trans_id)
1646
# New contents should have the same unix perms as old
1649
trans_id = new_trans_id
1650
if kind[1] == 'directory':
1651
tt.create_directory(trans_id)
1652
elif kind[1] == 'symlink':
1653
tt.create_symlink(target_tree.get_symlink_target(file_id),
1655
elif kind[1] == 'file':
1656
deferred_files.append((file_id, (trans_id, mode_id)))
1657
if basis_tree is None:
1658
basis_tree = working_tree.basis_tree()
1659
basis_tree.lock_read()
1660
new_sha1 = target_tree.get_file_sha1(file_id)
1661
if (file_id in basis_tree and new_sha1 ==
1662
basis_tree.get_file_sha1(file_id)):
1663
if file_id in merge_modified:
1664
del merge_modified[file_id]
1666
merge_modified[file_id] = new_sha1
1668
# preserve the execute bit when backing up
1669
if keep_content and executable[0] == executable[1]:
1670
tt.set_executability(executable[1], trans_id)
1672
assert kind[1] is None
1673
if versioned == (False, True):
1674
tt.version_file(file_id, trans_id)
1675
if versioned == (True, False):
1676
tt.unversion_file(trans_id)
1677
if (name[1] is not None and
1678
(name[0] != name[1] or parent[0] != parent[1])):
1680
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1681
if executable[0] != executable[1] and kind[1] == "file":
1682
tt.set_executability(executable[1], trans_id)
1683
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
1685
tt.create_file(bytes, trans_id, mode_id)
1687
if basis_tree is not None:
1689
return merge_modified
1692
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1159
1693
"""Make many conflict-resolution attempts, but die if they fail"""
1694
if pass_func is None:
1695
pass_func = conflict_pass
1160
1696
new_conflicts = set()
1162
1698
for n in range(10):
1239
1788
file_id=modified_id,
1240
1789
conflict_path=conflicting_path,
1241
1790
conflict_file_id=conflicting_id)
1793
class _FileMover(object):
1794
"""Moves and deletes files for TreeTransform, tracking operations"""
1797
self.past_renames = []
1798
self.pending_deletions = []
1800
def rename(self, from_, to):
1801
"""Rename a file from one path to another. Functions like os.rename"""
1802
os.rename(from_, to)
1803
self.past_renames.append((from_, to))
1805
def pre_delete(self, from_, to):
1806
"""Rename a file out of the way and mark it for deletion.
1808
Unlike os.unlink, this works equally well for files and directories.
1809
:param from_: The current file path
1810
:param to: A temporary path for the file
1812
self.rename(from_, to)
1813
self.pending_deletions.append(to)
1816
"""Reverse all renames that have been performed"""
1817
for from_, to in reversed(self.past_renames):
1818
os.rename(to, from_)
1819
# after rollback, don't reuse _FileMover
1821
pending_deletions = None
1823
def apply_deletions(self):
1824
"""Apply all marked deletions"""
1825
for path in self.pending_deletions:
1827
# after apply_deletions, don't reuse _FileMover
1829
pending_deletions = None