793
825
if trans_id in self._new_id:
795
827
kind = file_kind(self._tree.abspath(path))
796
inv.add_path(path, kind, self._new_id[trans_id])
797
elif trans_id in self._new_name or trans_id in\
799
entry = limbo_inv.get(trans_id)
800
if entry is not None:
801
entry.name = self.final_name(trans_id)
802
parent_path = os.path.dirname(path)
804
self._tree.inventory.path2id(parent_path)
807
# requires files and inventory entries to be in place
828
if trans_id in self._new_reference_revision:
829
new_entry = inventory.TreeReference(
830
self._new_id[trans_id],
831
self._new_name[trans_id],
832
self.final_file_id(self._new_parent[trans_id]),
833
None, self._new_reference_revision[trans_id])
835
new_entry = inventory.make_entry(kind,
836
self.final_name(trans_id),
837
self.final_file_id(self.final_parent(trans_id)),
838
self._new_id[trans_id])
840
if trans_id in self._new_name or trans_id in\
842
trans_id in self._new_executability:
843
file_id = self.final_file_id(trans_id)
844
if file_id is not None:
846
new_entry = entry.copy()
848
if trans_id in self._new_name or trans_id in\
850
if new_entry is not None:
851
new_entry.name = self.final_name(trans_id)
852
parent = self.final_parent(trans_id)
853
parent_id = self.final_file_id(parent)
854
new_entry.parent_id = parent_id
808
856
if trans_id in self._new_executability:
809
self._set_executability(path, inv, trans_id)
857
self._set_executability(path, new_entry, trans_id)
858
if new_entry is not None:
859
if new_entry.file_id in inv:
860
old_path = inv.id2path(new_entry.file_id)
863
inventory_delta.append((old_path, path,
811
867
child_pb.finished()
812
868
return modified_paths
814
def _set_executability(self, path, inv, trans_id):
870
def _set_executability(self, path, entry, trans_id):
815
871
"""Set the executability of versioned files """
816
file_id = inv.path2id(path)
817
872
new_executability = self._new_executability[trans_id]
818
inv[file_id].executable = new_executability
873
entry.executable = new_executability
819
874
if supports_executable():
820
875
abspath = self._tree.abspath(path)
821
876
current_mode = os.stat(abspath).st_mode
882
937
self.create_symlink(target, trans_id)
940
def _affected_ids(self):
941
"""Return the set of transform ids affected by the transform"""
942
trans_ids = set(self._removed_id)
943
trans_ids.update(self._new_id.keys())
944
trans_ids.update(self._removed_contents)
945
trans_ids.update(self._new_contents.keys())
946
trans_ids.update(self._new_executability.keys())
947
trans_ids.update(self._new_name.keys())
948
trans_ids.update(self._new_parent.keys())
951
def _get_file_id_maps(self):
952
"""Return mapping of file_ids to trans_ids in the to and from states"""
953
trans_ids = self._affected_ids()
956
# Build up two dicts: trans_ids associated with file ids in the
957
# FROM state, vs the TO state.
958
for trans_id in trans_ids:
959
from_file_id = self.tree_file_id(trans_id)
960
if from_file_id is not None:
961
from_trans_ids[from_file_id] = trans_id
962
to_file_id = self.final_file_id(trans_id)
963
if to_file_id is not None:
964
to_trans_ids[to_file_id] = trans_id
965
return from_trans_ids, to_trans_ids
967
def _from_file_data(self, from_trans_id, from_versioned, file_id):
968
"""Get data about a file in the from (tree) state
970
Return a (name, parent, kind, executable) tuple
972
from_path = self._tree_id_paths.get(from_trans_id)
974
# get data from working tree if versioned
975
from_entry = self._tree.inventory[file_id]
976
from_name = from_entry.name
977
from_parent = from_entry.parent_id
980
if from_path is None:
981
# File does not exist in FROM state
985
# File exists, but is not versioned. Have to use path-
987
from_name = os.path.basename(from_path)
988
tree_parent = self.get_tree_parent(from_trans_id)
989
from_parent = self.tree_file_id(tree_parent)
990
if from_path is not None:
991
from_kind, from_executable, from_stats = \
992
self._tree._comparison_data(from_entry, from_path)
995
from_executable = False
996
return from_name, from_parent, from_kind, from_executable
998
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
999
"""Get data about a file in the to (target) state
1001
Return a (name, parent, kind, executable) tuple
1003
to_name = self.final_name(to_trans_id)
1005
to_kind = self.final_kind(to_trans_id)
1008
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1009
if to_trans_id in self._new_executability:
1010
to_executable = self._new_executability[to_trans_id]
1011
elif to_trans_id == from_trans_id:
1012
to_executable = from_executable
1014
to_executable = False
1015
return to_name, to_parent, to_kind, to_executable
1017
def _iter_changes(self):
1018
"""Produce output in the same format as Tree._iter_changes.
1020
Will produce nonsensical results if invoked while inventory/filesystem
1021
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1023
This reads the Transform, but only reproduces changes involving a
1024
file_id. Files that are not versioned in either of the FROM or TO
1025
states are not reflected.
1027
final_paths = FinalPaths(self)
1028
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1030
# Now iterate through all active file_ids
1031
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1033
from_trans_id = from_trans_ids.get(file_id)
1034
# find file ids, and determine versioning state
1035
if from_trans_id is None:
1036
from_versioned = False
1037
from_trans_id = to_trans_ids[file_id]
1039
from_versioned = True
1040
to_trans_id = to_trans_ids.get(file_id)
1041
if to_trans_id is None:
1042
to_versioned = False
1043
to_trans_id = from_trans_id
1047
from_name, from_parent, from_kind, from_executable = \
1048
self._from_file_data(from_trans_id, from_versioned, file_id)
1050
to_name, to_parent, to_kind, to_executable = \
1051
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1053
if not from_versioned:
1056
from_path = self._tree_id_paths.get(from_trans_id)
1057
if not to_versioned:
1060
to_path = final_paths.get_path(to_trans_id)
1061
if from_kind != to_kind:
1063
elif to_kind in ('file', 'symlink') and (
1064
to_trans_id != from_trans_id or
1065
to_trans_id in self._new_contents):
1067
if (not modified and from_versioned == to_versioned and
1068
from_parent==to_parent and from_name == to_name and
1069
from_executable == to_executable):
1071
results.append((file_id, (from_path, to_path), modified,
1072
(from_versioned, to_versioned),
1073
(from_parent, to_parent),
1074
(from_name, to_name),
1075
(from_kind, to_kind),
1076
(from_executable, to_executable)))
1077
return iter(sorted(results, key=lambda x:x[1]))
885
1080
def joinpath(parent, child):
886
1081
"""Join tree-relative paths, handling the tree root specially"""
887
1082
if parent is None or parent == "":
923
1118
file_ids.sort(key=tree.id2path)
926
1122
def build_tree(tree, wt):
927
"""Create working tree for a branch, using a Transaction."""
1123
"""Create working tree for a branch, using a TreeTransform.
1125
This function should be used on empty trees, having a tree root at most.
1126
(see merge and revert functionality for working with existing trees)
1128
Existing files are handled like so:
1130
- Existing bzrdirs take precedence over creating new items. They are
1131
created as '%s.diverted' % name.
1132
- Otherwise, if the content on disk matches the content we are building,
1133
it is silently replaced.
1134
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1136
wt.lock_tree_write()
1140
return _build_tree(tree, wt)
1146
def _build_tree(tree, wt):
1147
"""See build_tree."""
1148
if len(wt.inventory) > 1: # more than just a root
1149
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
928
1150
file_trans_id = {}
929
1151
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
930
1152
pp = ProgressPhase("Build phase", 2, top_pb)
1153
if tree.inventory.root is not None:
1154
# this is kindof a hack: we should be altering the root
1155
# as partof the regular tree shape diff logic.
1156
# the conditional test hereis to avoid doing an
1157
# expensive operation (flush) every time the root id
1158
# is set within the tree, nor setting the root and thus
1159
# marking the tree as dirty, because we use two different
1160
# idioms here: tree interfaces and inventory interfaces.
1161
if wt.path2id('') != tree.inventory.root.file_id:
1162
wt.set_root_id(tree.inventory.root.file_id)
931
1164
tt = TreeTransform(wt)
934
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
935
file_ids = topology_sorted_ids(tree)
1168
file_trans_id[wt.get_root_id()] = \
1169
tt.trans_id_tree_file_id(wt.get_root_id())
936
1170
pb = bzrlib.ui.ui_factory.nested_progress_bar()
938
for num, file_id in enumerate(file_ids):
939
pb.update("Building tree", num, len(file_ids))
940
entry = tree.inventory[file_id]
1172
for num, (tree_path, entry) in \
1173
enumerate(tree.inventory.iter_entries_by_dir()):
1174
pb.update("Building tree", num, len(tree.inventory))
941
1175
if entry.parent_id is None:
1178
file_id = entry.file_id
1179
target_path = wt.abspath(tree_path)
1181
kind = file_kind(target_path)
1185
if kind == "directory":
1187
bzrdir.BzrDir.open(target_path)
1188
except errors.NotBranchError:
1192
if (file_id not in divert and
1193
_content_match(tree, entry, file_id, kind,
1195
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1196
if kind == 'directory':
943
1198
if entry.parent_id not in file_trans_id:
944
raise repr(entry.parent_id)
1199
raise AssertionError(
1200
'entry %s parent id %r is not in file_trans_id %r'
1201
% (entry, entry.parent_id, file_trans_id))
945
1202
parent_id = file_trans_id[entry.parent_id]
946
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1203
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1206
new_trans_id = file_trans_id[file_id]
1207
old_parent = tt.trans_id_tree_path(tree_path)
1208
_reparent_children(tt, old_parent, new_trans_id)
1212
divert_trans = set(file_trans_id[f] for f in divert)
1213
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1214
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1215
conflicts = cook_conflicts(raw_conflicts, tt)
1216
for conflict in conflicts:
1219
wt.add_conflicts(conflicts)
1220
except errors.UnsupportedOperation:
954
1225
top_pb.finished()
1228
def _reparent_children(tt, old_parent, new_parent):
1229
for child in tt.iter_tree_children(old_parent):
1230
tt.adjust_path(tt.final_name(child), new_parent, child)
1233
def _content_match(tree, entry, file_id, kind, target_path):
1234
if entry.kind != kind:
1236
if entry.kind == "directory":
1238
if entry.kind == "file":
1239
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1241
elif entry.kind == "symlink":
1242
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1247
def resolve_checkout(tt, conflicts, divert):
1248
new_conflicts = set()
1249
for c_type, conflict in ((c[0], c) for c in conflicts):
1250
# Anything but a 'duplicate' would indicate programmer error
1251
assert c_type == 'duplicate', c_type
1252
# Now figure out which is new and which is old
1253
if tt.new_contents(conflict[1]):
1254
new_file = conflict[1]
1255
old_file = conflict[2]
1257
new_file = conflict[2]
1258
old_file = conflict[1]
1260
# We should only get here if the conflict wasn't completely
1262
final_parent = tt.final_parent(old_file)
1263
if new_file in divert:
1264
new_name = tt.final_name(old_file)+'.diverted'
1265
tt.adjust_path(new_name, final_parent, new_file)
1266
new_conflicts.add((c_type, 'Diverted to',
1267
new_file, old_file))
1269
new_name = tt.final_name(old_file)+'.moved'
1270
tt.adjust_path(new_name, final_parent, old_file)
1271
new_conflicts.add((c_type, 'Moved existing file to',
1272
old_file, new_file))
1273
return new_conflicts
956
1276
def new_by_entry(tt, entry, parent_id, tree):
957
1277
"""Create a new file according to its inventory entry"""
958
1278
name = entry.name
962
1282
executable = tree.is_executable(entry.file_id)
963
1283
return tt.new_file(name, parent_id, contents, entry.file_id,
965
elif kind == 'directory':
966
return tt.new_directory(name, parent_id, entry.file_id)
1285
elif kind in ('directory', 'tree-reference'):
1286
trans_id = tt.new_directory(name, parent_id, entry.file_id)
1287
if kind == 'tree-reference':
1288
tt.set_tree_reference(entry.reference_revision, trans_id)
967
1290
elif kind == 'symlink':
968
1291
target = tree.get_symlink_target(entry.file_id)
969
1292
return tt.new_symlink(name, parent_id, target, entry.file_id)
1294
raise errors.BadFileKindError(name, kind)
971
1296
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
972
1297
"""Create new file contents according to an inventory entry."""
973
1298
if entry.kind == "file":
975
1300
lines = tree.get_file(entry.file_id).readlines()
976
1301
tt.create_file(lines, trans_id, mode_id=mode_id)
977
1302
elif entry.kind == "symlink":
1084
1410
return has_contents, contents_mod, meta_mod
1087
def revert(working_tree, target_tree, filenames, backups=False,
1088
pb=DummyProgress()):
1413
def revert(working_tree, target_tree, filenames, backups=False,
1414
pb=DummyProgress(), change_reporter=None):
1089
1415
"""Revert a working tree's contents to those of a target tree."""
1090
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1091
def interesting(file_id):
1092
return interesting_ids is None or file_id in interesting_ids
1416
target_tree.lock_read()
1094
1417
tt = TreeTransform(working_tree, pb)
1096
merge_modified = working_tree.merge_modified()
1098
def trans_id_file_id(file_id):
1100
return trans_id[file_id]
1102
return tt.trans_id_tree_file_id(file_id)
1104
pp = ProgressPhase("Revert phase", 4, pb)
1106
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1108
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1110
by_parent = tt.by_parent()
1111
for id_num, file_id in enumerate(sorted_interesting):
1112
child_pb.update("Reverting file", id_num+1,
1113
len(sorted_interesting))
1114
if file_id not in working_tree.inventory:
1115
entry = target_tree.inventory[file_id]
1116
parent_id = trans_id_file_id(entry.parent_id)
1117
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1118
trans_id[file_id] = e_trans_id
1120
backup_this = backups
1121
if file_id in merge_modified:
1123
del merge_modified[file_id]
1124
change_entry(tt, file_id, working_tree, target_tree,
1125
trans_id_file_id, backup_this, trans_id,
1130
wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1131
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1133
for id_num, file_id in enumerate(wt_interesting):
1134
child_pb.update("New file check", id_num+1,
1135
len(sorted_interesting))
1136
if file_id not in target_tree:
1137
trans_id = tt.trans_id_tree_file_id(file_id)
1138
tt.unversion_file(trans_id)
1139
if file_id in merge_modified:
1140
tt.delete_contents(trans_id)
1141
del merge_modified[file_id]
1419
pp = ProgressPhase("Revert phase", 3, pb)
1421
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1423
_alter_files(working_tree, target_tree, tt, child_pb,
1143
1426
child_pb.finished()
1144
1427
pp.next_phase()
1149
1432
child_pb.finished()
1150
1433
conflicts = cook_conflicts(raw_conflicts, tt)
1435
change_reporter = delta._ChangeReporter(
1436
unversioned_filter=working_tree.is_ignored)
1437
delta.report_changes(tt._iter_changes(), change_reporter)
1151
1438
for conflict in conflicts:
1152
1439
warning(conflict)
1153
1440
pp.next_phase()
1155
1442
working_tree.set_merge_modified({})
1444
target_tree.unlock()
1159
1447
return conflicts
1162
def resolve_conflicts(tt, pb=DummyProgress()):
1450
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1452
merge_modified = working_tree.merge_modified()
1453
change_list = target_tree._iter_changes(working_tree,
1454
specific_files=specific_files, pb=pb)
1455
if target_tree.inventory.root is None:
1461
for id_num, (file_id, path, changed_content, versioned, parent, name,
1462
kind, executable) in enumerate(change_list):
1463
if skip_root and file_id[0] is not None and parent[0] is None:
1465
trans_id = tt.trans_id_file_id(file_id)
1468
keep_content = False
1469
if kind[0] == 'file' and (backups or kind[1] is None):
1470
wt_sha1 = working_tree.get_file_sha1(file_id)
1471
if merge_modified.get(file_id) != wt_sha1:
1472
# acquire the basis tree lazyily to prevent the expense
1473
# of accessing it when its not needed ? (Guessing, RBC,
1475
if basis_tree is None:
1476
basis_tree = working_tree.basis_tree()
1477
basis_tree.lock_read()
1478
if file_id in basis_tree:
1479
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1481
elif kind[1] is None and not versioned[1]:
1483
if kind[0] is not None:
1484
if not keep_content:
1485
tt.delete_contents(trans_id)
1486
elif kind[1] is not None:
1487
parent_trans_id = tt.trans_id_file_id(parent[0])
1488
by_parent = tt.by_parent()
1489
backup_name = _get_backup_name(name[0], by_parent,
1490
parent_trans_id, tt)
1491
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1492
new_trans_id = tt.create_path(name[0], parent_trans_id)
1493
if versioned == (True, True):
1494
tt.unversion_file(trans_id)
1495
tt.version_file(file_id, new_trans_id)
1496
# New contents should have the same unix perms as old
1499
trans_id = new_trans_id
1500
if kind[1] == 'directory':
1501
tt.create_directory(trans_id)
1502
elif kind[1] == 'symlink':
1503
tt.create_symlink(target_tree.get_symlink_target(file_id),
1505
elif kind[1] == 'file':
1506
tt.create_file(target_tree.get_file_lines(file_id),
1508
# preserve the execute bit when backing up
1509
if keep_content and executable[0] == executable[1]:
1510
tt.set_executability(executable[1], trans_id)
1512
assert kind[1] is None
1513
if versioned == (False, True):
1514
tt.version_file(file_id, trans_id)
1515
if versioned == (True, False):
1516
tt.unversion_file(trans_id)
1517
if (name[1] is not None and
1518
(name[0] != name[1] or parent[0] != parent[1])):
1520
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1521
if executable[0] != executable[1] and kind[1] == "file":
1522
tt.set_executability(executable[1], trans_id)
1524
if basis_tree is not None:
1528
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1163
1529
"""Make many conflict-resolution attempts, but die if they fail"""
1530
if pass_func is None:
1531
pass_func = conflict_pass
1164
1532
new_conflicts = set()
1166
1534
for n in range(10):