22
22
ReusingTransform, NotVersionedError, CantMoveRoot,
23
23
ExistingLimbo, ImmortalLimbo)
24
24
from bzrlib.inventory import InventoryEntry
25
from bzrlib.osutils import file_kind, supports_executable, pathjoin
25
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
26
27
from bzrlib.progress import DummyProgress, ProgressPhase
27
28
from bzrlib.trace import mutter, warning
286
287
os.symlink(target, self._limbo_name(trans_id))
287
288
unique_add(self._new_contents, trans_id, 'symlink')
290
def delete_any(full_path):
291
"""Delete a file or directory."""
295
# We may be renaming a dangling inventory id
296
if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
300
290
def cancel_creation(self, trans_id):
301
291
"""Cancel the creation of new file contents."""
302
292
del self._new_contents[trans_id]
303
self.delete_any(self._limbo_name(trans_id))
293
delete_any(self._limbo_name(trans_id))
305
295
def delete_contents(self, trans_id):
306
296
"""Schedule the contents of a path entry for deletion"""
465
455
# ensure all children of all existent parents are known
466
456
# all children of non-existent parents are known, by definition.
467
457
self._add_tree_children()
468
by_parent = self._by_parent()
458
by_parent = self.by_parent()
469
459
conflicts.extend(self._unversioned_parents(by_parent))
470
460
conflicts.extend(self._parent_loops())
471
461
conflicts.extend(self._duplicate_entries(by_parent))
482
472
Active parents are those which gain children, and those which are
483
473
removed. This is a necessary first step in detecting conflicts.
485
parents = self._by_parent().keys()
475
parents = self.by_parent().keys()
486
476
parents.extend([t for t in self._removed_contents if
487
477
self.tree_kind(t) == 'directory'])
488
478
for trans_id in self._removed_id:
515
505
yield self.trans_id_tree_path(childpath)
507
def has_named_child(self, by_parent, parent_id, name):
509
children = by_parent[parent_id]
512
for child in children:
513
if self.final_name(child) == name:
516
path = self._tree_id_paths[parent_id]
519
childpath = joinpath(path, name)
520
child_id = self._tree_path_ids.get(childpath)
522
return lexists(self._tree.abspath(childpath))
524
if tt.final_parent(child_id) != parent_id:
526
if child_id in tt._removed_contents:
527
# XXX What about dangling file-ids?
517
532
def _parent_loops(self):
518
533
"""No entry should be its own ancestor"""
605
620
if name == last_name:
606
621
conflicts.append(('duplicate', last_trans_id, trans_id,
609
last_trans_id = trans_id
624
kind = self.final_kind(trans_id)
627
file_id = self.final_file_id(trans_id)
628
if kind is not None or file_id is not None:
630
last_trans_id = trans_id
612
633
def _duplicate_ids(self):
884
905
def build_tree(tree, wt):
885
906
"""Create working tree for a branch, using a Transaction."""
886
907
file_trans_id = {}
908
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
pp = ProgressPhase("Build phase", 2, top_pb)
887
910
tt = TreeTransform(wt)
889
913
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
890
914
file_ids = topology_sorted_ids(tree)
891
for file_id in file_ids:
892
entry = tree.inventory[file_id]
893
if entry.parent_id is None:
895
if entry.parent_id not in file_trans_id:
896
raise repr(entry.parent_id)
897
parent_id = file_trans_id[entry.parent_id]
898
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, tree)
915
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]
920
if entry.parent_id is None:
922
if entry.parent_id not in file_trans_id:
923
raise repr(entry.parent_id)
924
parent_id = file_trans_id[entry.parent_id]
925
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
903
935
def new_by_entry(tt, entry, parent_id, tree):
904
936
"""Create a new file according to its inventory entry"""
940
972
interesting_ids = set()
941
973
for tree_path in filenames:
942
975
for tree in (working_tree, target_tree):
944
976
file_id = tree.inventory.path2id(tree_path)
945
977
if file_id is not None:
946
978
interesting_ids.add(file_id)
947
979
not_found = False
949
raise NotVersionedError(path=tree_path)
981
raise NotVersionedError(path=tree_path)
950
982
return interesting_ids
953
985
def change_entry(tt, file_id, working_tree, target_tree,
954
trans_id_file_id, backups, trans_id):
986
trans_id_file_id, backups, trans_id, by_parent):
955
987
"""Replace a file_id's contents with those from a target tree."""
956
988
e_trans_id = trans_id_file_id(file_id)
957
989
entry = target_tree.inventory[file_id]
964
996
tt.delete_contents(e_trans_id)
966
998
parent_trans_id = trans_id_file_id(entry.parent_id)
967
tt.adjust_path(entry.name+"~", parent_trans_id, e_trans_id)
999
backup_name = get_backup_name(entry, by_parent,
1000
parent_trans_id, tt)
1001
tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
968
1002
tt.unversion_file(e_trans_id)
969
1003
e_trans_id = tt.create_path(entry.name, parent_trans_id)
970
1004
tt.version_file(file_id, e_trans_id)
988
1022
tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1025
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1026
"""Produce a backup-style name that appears to be available"""
1030
yield "%s.~%d~" % (entry.name, counter)
1032
for name in name_gen():
1033
if not tt.has_named_child(by_parent, parent_trans_id, name):
991
1036
def _entry_changes(file_id, entry, working_tree):
992
1037
"""Determine in which ways the inventory entry has changed.
1057
1103
backup_this = False
1058
1104
del merge_modified[file_id]
1059
1105
change_entry(tt, file_id, working_tree, target_tree,
1060
trans_id_file_id, backup_this, trans_id)
1106
trans_id_file_id, backup_this, trans_id,
1062
1109
child_pb.finished()
1063
1110
pp.next_phase()
1081
1128
raw_conflicts = resolve_conflicts(tt, child_pb)
1083
1130
child_pb.finished()
1084
for line in conflicts_strings(cook_conflicts(raw_conflicts, tt)):
1131
conflicts = cook_conflicts(raw_conflicts, tt)
1132
for conflict in conflicts:
1086
1134
pp.next_phase()
1088
1136
working_tree.set_merge_modified({})
1094
1143
def resolve_conflicts(tt, pb=DummyProgress()):
1148
1197
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1149
1198
return new_conflicts
1151
1201
def cook_conflicts(raw_conflicts, tt):
1152
1202
"""Generate a list of cooked conflicts, sorted by file path"""
1154
if conflict[2] is not None:
1155
return conflict[2], conflict[0]
1156
elif len(conflict) == 6:
1157
return conflict[4], conflict[0]
1159
return None, conflict[0]
1203
from bzrlib.conflicts import Conflict
1204
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1205
return sorted(conflict_iter, key=Conflict.sort_key)
1161
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1163
1208
def iter_cook_conflicts(raw_conflicts, tt):
1164
cooked_conflicts = []
1209
from bzrlib.conflicts import Conflict
1165
1210
fp = FinalPaths(tt)
1166
1211
for conflict in raw_conflicts:
1167
1212
c_type = conflict[0]
1169
1214
modified_path = fp.get_path(conflict[2])
1170
1215
modified_id = tt.final_file_id(conflict[2])
1171
1216
if len(conflict) == 3:
1172
yield c_type, action, modified_path, modified_id
1217
yield Conflict.factory(c_type, action=action, path=modified_path,
1218
file_id=modified_id)
1174
1221
conflicting_path = fp.get_path(conflict[3])
1175
1222
conflicting_id = tt.final_file_id(conflict[3])
1176
yield (c_type, action, modified_path, modified_id,
1177
conflicting_path, conflicting_id)
1180
def conflicts_strings(conflicts):
1181
"""Generate strings for the provided conflicts"""
1182
for conflict in conflicts:
1183
conflict_type = conflict[0]
1184
if conflict_type == 'text conflict':
1185
yield 'Text conflict in %s' % conflict[2]
1186
elif conflict_type == 'contents conflict':
1187
yield 'Contents conflict in %s' % conflict[2]
1188
elif conflict_type == 'path conflict':
1189
yield 'Path conflict: %s / %s' % conflict[2:]
1190
elif conflict_type == 'duplicate id':
1191
vals = (conflict[4], conflict[1], conflict[2])
1192
yield 'Conflict adding id to %s. %s %s.' % vals
1193
elif conflict_type == 'duplicate':
1194
vals = (conflict[4], conflict[1], conflict[2])
1195
yield 'Conflict adding file %s. %s %s.' % vals
1196
elif conflict_type == 'parent loop':
1197
vals = (conflict[4], conflict[2], conflict[1])
1198
yield 'Conflict moving %s into %s. %s.' % vals
1199
elif conflict_type == 'unversioned parent':
1200
vals = (conflict[2], conflict[1])
1201
yield 'Conflict adding versioned files to %s. %s.' % vals
1202
elif conflict_type == 'missing parent':
1203
vals = (conflict[2], conflict[1])
1204
yield 'Conflict adding files to %s. %s.' % vals
1223
yield Conflict.factory(c_type, action=action, path=modified_path,
1224
file_id=modified_id,
1225
conflict_path=conflicting_path,
1226
conflict_file_id=conflicting_id)