203
154
"""Change the path that is assigned to a transaction id."""
204
155
if trans_id == self._new_root:
205
156
raise CantMoveRoot
206
previous_parent = self._new_parent.get(trans_id)
207
previous_name = self._new_name.get(trans_id)
208
157
self._new_name[trans_id] = name
209
158
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)
234
160
def adjust_root_path(self, name, parent):
235
161
"""Emulate moving the root by moving all children, instead.
804
def apply(self, no_conflicts=False, _mover=None):
805
709
"""Apply all changes to the inventory and filesystem.
807
711
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
817
conflicts = self.find_conflicts()
818
if len(conflicts) != 0:
819
raise MalformedTransform(conflicts=conflicts)
714
conflicts = self.find_conflicts()
715
if len(conflicts) != 0:
716
raise MalformedTransform(conflicts=conflicts)
820
718
inv = self._tree.inventory
822
719
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
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()
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)
840
726
child_pb.finished()
841
self._tree.apply_inventory_delta(inventory_delta)
727
self._tree._write_inventory(inv)
842
728
self.__done = True
844
return _TransformResults(modified_paths, self.rename_count)
730
return _TransformResults(modified_paths)
846
732
def _limbo_name(self, trans_id):
847
733
"""Generate the limbo name of a file"""
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
734
return pathjoin(self._limbodir, trans_id)
879
def _apply_removals(self, inv, inventory_delta, mover):
736
def _apply_removals(self, inv, limbo_inv):
880
737
"""Perform tree operations that remove directory/inventory names.
882
739
That is, delete files that are to be deleted, and put any files that
892
749
child_pb.update('removing file', num, len(tree_paths))
893
750
full_path = self._tree.abspath(path)
894
751
if trans_id in self._removed_contents:
895
mover.pre_delete(full_path, os.path.join(self._deletiondir,
752
delete_any(full_path)
897
753
elif trans_id in self._new_name or trans_id in \
898
754
self._new_parent:
900
mover.rename(full_path, self._limbo_name(trans_id))
756
os.rename(full_path, self._limbo_name(trans_id))
901
757
except OSError, e:
902
758
if e.errno != errno.ENOENT:
905
self.rename_count += 1
906
760
if trans_id in self._removed_id:
907
761
if trans_id == self._new_root:
908
762
file_id = self._tree.inventory.root.file_id
910
764
file_id = self.tree_file_id(trans_id)
911
assert file_id is not None
912
inventory_delta.append((path, None, file_id, None))
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]
914
772
child_pb.finished()
916
def _apply_insertions(self, inv, inventory_delta, mover):
774
def _apply_insertions(self, inv, limbo_inv):
917
775
"""Perform tree operations that insert directory/inventory names.
919
777
That is, create any files that need to be created, and restore from
950
804
if trans_id in self._new_id:
952
806
kind = file_kind(self._tree.abspath(path))
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
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
981
819
if trans_id in self._new_executability:
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,
820
self._set_executability(path, inv, trans_id)
992
822
child_pb.finished()
993
823
return modified_paths
995
def _set_executability(self, path, entry, trans_id):
825
def _set_executability(self, path, inv, trans_id):
996
826
"""Set the executability of versioned files """
827
file_id = inv.path2id(path)
997
828
new_executability = self._new_executability[trans_id]
998
entry.executable = new_executability
829
inv[file_id].executable = new_executability
999
830
if supports_executable():
1000
831
abspath = self._tree.abspath(path)
1001
832
current_mode = os.stat(abspath).st_mode
1062
893
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]))
1205
896
def joinpath(parent, child):
1206
897
"""Join tree-relative paths, handling the tree root specially"""
1207
898
if parent is None or parent == "":
1243
934
file_ids.sort(key=tree.id2path)
1247
937
def build_tree(tree, wt):
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)
938
"""Create working tree for a branch, using a Transaction."""
1275
939
file_trans_id = {}
1276
940
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1277
941
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)
1289
942
tt = TreeTransform(wt)
1293
file_trans_id[wt.get_root_id()] = \
1294
tt.trans_id_tree_file_id(wt.get_root_id())
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)
1295
947
pb = bzrlib.ui.ui_factory.nested_progress_bar()
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))
949
for num, file_id in enumerate(file_ids):
950
pb.update("Building tree", num, len(file_ids))
951
entry = tree.inventory[file_id]
1302
952
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':
1325
954
if entry.parent_id not in file_trans_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))
955
raise repr(entry.parent_id)
1329
956
parent_id = file_trans_id[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))
957
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
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:
1369
965
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
1421
967
def new_by_entry(tt, entry, parent_id, tree):
1422
968
"""Create a new file according to its inventory entry"""
1427
973
executable = tree.is_executable(entry.file_id)
1428
974
return tt.new_file(name, parent_id, contents, 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)
976
elif kind == 'directory':
977
return tt.new_directory(name, parent_id, entry.file_id)
1435
978
elif kind == 'symlink':
1436
979
target = tree.get_symlink_target(entry.file_id)
1437
980
return tt.new_symlink(name, parent_id, target, entry.file_id)
1439
raise errors.BadFileKindError(name, kind)
1441
982
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1442
983
"""Create new file contents according to an inventory entry."""
1443
984
if entry.kind == "file":
1445
986
lines = tree.get_file(entry.file_id).readlines()
1446
987
tt.create_file(lines, trans_id, mode_id=mode_id)
1447
988
elif entry.kind == "symlink":
1455
996
tt.set_executability(entry.executable, trans_id)
1458
@deprecated_function(zero_fifteen)
1459
999
def find_interesting(working_tree, target_tree, filenames):
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)
1000
"""Find the ids corresponding to specified filenames."""
1001
trees = (working_tree, target_tree)
1002
return tree.find_ids_across_trees(filenames, trees)
1476
1005
def change_entry(tt, file_id, working_tree, target_tree,
1477
1006
trans_id_file_id, backups, trans_id, by_parent):
1478
1007
"""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
1482
1008
e_trans_id = trans_id_file_id(file_id)
1483
1009
entry = target_tree.inventory[file_id]
1484
1010
has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry,
1559
1080
return has_contents, contents_mod, meta_mod
1562
def revert(working_tree, target_tree, filenames, backups=False,
1563
pb=DummyProgress(), change_reporter=None):
1083
def revert(working_tree, target_tree, filenames, backups=False,
1084
pb=DummyProgress()):
1564
1085
"""Revert a working tree's contents to those of a target tree."""
1565
target_tree.lock_read()
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
1566
1090
tt = TreeTransform(working_tree, pb)
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)
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]
1575
1139
child_pb.finished()
1576
1140
pp.next_phase()
1581
1145
child_pb.finished()
1582
1146
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)
1587
1147
for conflict in conflicts:
1588
1148
warning(conflict)
1589
1149
pp.next_phase()
1591
working_tree.set_merge_modified(merge_modified)
1151
working_tree.set_merge_modified({})
1593
target_tree.unlock()
1596
1155
return conflicts
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):
1158
def resolve_conflicts(tt, pb=DummyProgress()):
1693
1159
"""Make many conflict-resolution attempts, but die if they fail"""
1694
if pass_func is None:
1695
pass_func = conflict_pass
1696
1160
new_conflicts = set()
1698
1162
for n in range(10):
1788
1239
file_id=modified_id,
1789
1240
conflict_path=conflicting_path,
1790
1241
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