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, lexists,
27
from bzrlib.progress import DummyProgress, ProgressPhase
25
from bzrlib.osutils import file_kind, supports_executable, pathjoin
26
from bzrlib.progress import DummyProgress
28
27
from bzrlib.trace import mutter, warning
32
30
ROOT_PARENT = "root-parent"
287
279
os.symlink(target, self._limbo_name(trans_id))
288
280
unique_add(self._new_contents, trans_id, 'symlink')
283
def delete_any(full_path):
284
"""Delete a file or directory."""
288
# We may be renaming a dangling inventory id
289
if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
290
293
def cancel_creation(self, trans_id):
291
294
"""Cancel the creation of new file contents."""
292
295
del self._new_contents[trans_id]
293
delete_any(self._limbo_name(trans_id))
296
self.delete_any(self._limbo_name(trans_id))
295
298
def delete_contents(self, trans_id):
296
299
"""Schedule the contents of a path entry for deletion"""
505
508
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?
532
510
def _parent_loops(self):
533
511
"""No entry should be its own ancestor"""
687
659
raise MalformedTransform(conflicts=conflicts)
689
661
inv = self._tree.inventory
690
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)
662
self._apply_removals(inv, limbo_inv)
663
self._apply_insertions(inv, limbo_inv)
698
664
self._tree._write_inventory(inv)
699
665
self.__done = True
701
return _TransformResults(modified_paths)
703
668
def _limbo_name(self, trans_id):
704
669
"""Generate the limbo name of a file"""
714
679
tree_paths = list(self._tree_path_ids.iteritems())
715
680
tree_paths.sort(reverse=True)
716
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
718
for num, data in enumerate(tree_paths):
719
path, trans_id = data
720
child_pb.update('removing file', num, len(tree_paths))
721
full_path = self._tree.abspath(path)
722
if trans_id in self._removed_contents:
723
delete_any(full_path)
724
elif trans_id in self._new_name or trans_id in \
727
os.rename(full_path, self._limbo_name(trans_id))
729
if e.errno != errno.ENOENT:
731
if trans_id in self._removed_id:
732
if trans_id == self._new_root:
733
file_id = self._tree.inventory.root.file_id
735
file_id = self.tree_file_id(trans_id)
681
for num, data in enumerate(tree_paths):
682
path, trans_id = data
683
self._pb.update('removing file', num+1, len(tree_paths))
684
full_path = self._tree.abspath(path)
685
if trans_id in self._removed_contents:
686
self.delete_any(full_path)
687
elif trans_id in self._new_name or trans_id in self._new_parent:
689
os.rename(full_path, self._limbo_name(trans_id))
691
if e.errno != errno.ENOENT:
693
if trans_id in self._removed_id:
694
if trans_id == self._new_root:
695
file_id = self._tree.inventory.root.file_id
697
file_id = self.tree_file_id(trans_id)
699
elif trans_id in self._new_name or trans_id in self._new_parent:
700
file_id = self.tree_file_id(trans_id)
701
if file_id is not None:
702
limbo_inv[trans_id] = inv[file_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]
745
706
def _apply_insertions(self, inv, limbo_inv):
746
707
"""Perform tree operations that insert directory/inventory names.
750
711
parent-to-child order.
752
713
new_paths = self.new_paths()
754
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
756
for num, (path, trans_id) in enumerate(new_paths):
757
child_pb.update('adding file', num, len(new_paths))
714
for num, (path, trans_id) in enumerate(new_paths):
715
self._pb.update('adding file', num+1, len(new_paths))
717
kind = self._new_contents[trans_id]
719
kind = contents = None
720
if trans_id in self._new_contents or self.path_changed(trans_id):
721
full_path = self._tree.abspath(path)
759
kind = self._new_contents[trans_id]
761
kind = contents = None
762
if trans_id in self._new_contents or \
763
self.path_changed(trans_id):
764
full_path = self._tree.abspath(path)
766
os.rename(self._limbo_name(trans_id), full_path)
768
# We may be renaming a dangling inventory id
769
if e.errno != errno.ENOENT:
771
if trans_id in self._new_contents:
772
modified_paths.append(full_path)
773
del self._new_contents[trans_id]
775
if trans_id in self._new_id:
777
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
790
if trans_id in self._new_executability:
791
self._set_executability(path, inv, trans_id)
794
return modified_paths
723
os.rename(self._limbo_name(trans_id), full_path)
725
# We may be renaming a dangling inventory id
726
if e.errno != errno.ENOENT:
728
if trans_id in self._new_contents:
729
del self._new_contents[trans_id]
731
if trans_id in self._new_id:
733
kind = file_kind(self._tree.abspath(path))
734
inv.add_path(path, kind, self._new_id[trans_id])
735
elif trans_id in self._new_name or trans_id in self._new_parent:
736
entry = limbo_inv.get(trans_id)
737
if entry is not None:
738
entry.name = self.final_name(trans_id)
739
parent_path = os.path.dirname(path)
740
entry.parent_id = self._tree.inventory.path2id(parent_path)
743
# requires files and inventory entries to be in place
744
if trans_id in self._new_executability:
745
self._set_executability(path, inv, trans_id)
796
748
def _set_executability(self, path, inv, trans_id):
797
749
"""Set the executability of versioned files """
905
857
def build_tree(tree, wt):
906
858
"""Create working tree for a branch, using a Transaction."""
907
859
file_trans_id = {}
908
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
pp = ProgressPhase("Build phase", 2, top_pb)
910
860
tt = TreeTransform(wt)
913
862
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
914
863
file_ids = topology_sorted_ids(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,
864
for file_id in file_ids:
865
entry = tree.inventory[file_id]
866
if entry.parent_id is None:
868
if entry.parent_id not in file_trans_id:
869
raise repr(entry.parent_id)
870
parent_id = file_trans_id[entry.parent_id]
871
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, tree)
935
876
def new_by_entry(tt, entry, parent_id, tree):
936
877
"""Create a new file according to its inventory entry"""
972
913
interesting_ids = set()
973
914
for tree_path in filenames:
975
915
for tree in (working_tree, target_tree):
976
917
file_id = tree.inventory.path2id(tree_path)
977
918
if file_id is not None:
978
919
interesting_ids.add(file_id)
979
920
not_found = False
981
raise NotVersionedError(path=tree_path)
922
raise NotVersionedError(path=tree_path)
982
923
return interesting_ids
985
926
def change_entry(tt, file_id, working_tree, target_tree,
986
trans_id_file_id, backups, trans_id, by_parent):
927
trans_id_file_id, backups, trans_id):
987
928
"""Replace a file_id's contents with those from a target tree."""
988
929
e_trans_id = trans_id_file_id(file_id)
989
930
entry = target_tree.inventory[file_id]
996
937
tt.delete_contents(e_trans_id)
998
939
parent_trans_id = trans_id_file_id(entry.parent_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)
940
tt.adjust_path(entry.name+"~", parent_trans_id, e_trans_id)
1002
941
tt.unversion_file(e_trans_id)
1003
942
e_trans_id = tt.create_path(entry.name, parent_trans_id)
1004
943
tt.version_file(file_id, e_trans_id)
1022
961
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):
1036
964
def _entry_changes(file_id, entry, working_tree):
1037
965
"""Determine in which ways the inventory entry has changed.
1082
1009
except KeyError:
1083
1010
return tt.trans_id_tree_file_id(file_id)
1085
pp = ProgressPhase("Revert phase", 4, pb)
1087
1012
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1088
1013
interesting(i)]
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,
1014
for id_num, file_id in enumerate(sorted_interesting):
1015
pb.update("Reverting file", id_num+1, len(sorted_interesting))
1016
if file_id not in working_tree.inventory:
1017
entry = target_tree.inventory[file_id]
1018
parent_id = trans_id_file_id(entry.parent_id)
1019
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1020
trans_id[file_id] = e_trans_id
1022
change_entry(tt, file_id, working_tree, target_tree,
1023
trans_id_file_id, backups, trans_id)
1111
1024
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:
1121
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:
1025
for id_num, file_id in enumerate(wt_interesting):
1026
pb.update("New file check", id_num+1, len(sorted_interesting))
1027
if file_id not in target_tree:
1028
tt.unversion_file(tt.trans_id_tree_file_id(file_id))
1029
raw_conflicts = resolve_conflicts(tt, pb)
1030
for line in conflicts_strings(cook_conflicts(raw_conflicts, tt)):
1136
working_tree.set_merge_modified({})
1143
1038
def resolve_conflicts(tt, pb=DummyProgress()):
1200
1095
def cook_conflicts(raw_conflicts, tt):
1201
1096
"""Generate a list of cooked conflicts, sorted by file path"""
1202
1097
def key(conflict):
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
1098
if conflict[2] is not None:
1099
return conflict[2], conflict[0]
1100
elif len(conflict) == 6:
1101
return conflict[4], conflict[0]
1208
return None, conflict.typestring
1103
return None, conflict[0]
1210
1105
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1212
1107
def iter_cook_conflicts(raw_conflicts, tt):
1213
from bzrlib.conflicts import Conflict
1108
cooked_conflicts = []
1214
1109
fp = FinalPaths(tt)
1215
1110
for conflict in raw_conflicts:
1216
1111
c_type = conflict[0]
1218
1113
modified_path = fp.get_path(conflict[2])
1219
1114
modified_id = tt.final_file_id(conflict[2])
1220
1115
if len(conflict) == 3:
1221
yield Conflict.factory(c_type, action=action, path=modified_path,
1222
file_id=modified_id)
1116
yield c_type, action, modified_path, modified_id
1225
1118
conflicting_path = fp.get_path(conflict[3])
1226
1119
conflicting_id = tt.final_file_id(conflict[3])
1227
yield Conflict.factory(c_type, action=action, path=modified_path,
1228
file_id=modified_id,
1229
conflict_path=conflicting_path,
1230
conflict_file_id=conflicting_id)
1120
yield (c_type, action, modified_path, modified_id,
1121
conflicting_path, conflicting_id)
1124
def conflicts_strings(conflicts):
1125
"""Generate strings for the provided conflicts"""
1126
for conflict in conflicts:
1127
conflict_type = conflict[0]
1128
if conflict_type == 'text conflict':
1129
yield 'Text conflict in %s' % conflict[2]
1130
elif conflict_type == 'contents conflict':
1131
yield 'Contents conflict in %s' % conflict[2]
1132
elif conflict_type == 'path conflict':
1133
yield 'Path conflict: %s / %s' % conflict[2:]
1134
elif conflict_type == 'duplicate id':
1135
vals = (conflict[4], conflict[1], conflict[2])
1136
yield 'Conflict adding id to %s. %s %s.' % vals
1137
elif conflict_type == 'duplicate':
1138
vals = (conflict[4], conflict[1], conflict[2])
1139
yield 'Conflict adding file %s. %s %s.' % vals
1140
elif conflict_type == 'parent loop':
1141
vals = (conflict[4], conflict[2], conflict[1])
1142
yield 'Conflict moving %s into %s. %s.' % vals
1143
elif conflict_type == 'unversioned parent':
1144
vals = (conflict[2], conflict[1])
1145
yield 'Conflict adding versioned files to %s. %s.' % vals
1146
elif conflict_type == 'missing parent':
1147
vals = (conflict[2], conflict[1])
1148
yield 'Conflict adding files to %s. %s.' % vals