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
29
from bzrlib import tree
31
import bzrlib.urlutils as urlutils
34
30
ROOT_PARENT = "root-parent"
218
203
def canonical_path(self, path):
219
204
"""Get the canonical tree-relative path"""
220
205
# don't follow final symlinks
221
abs = self._tree.abspath(path)
222
if abs in self._relpaths:
223
return self._relpaths[abs]
224
dirname, basename = os.path.split(abs)
225
if dirname not in self._realpaths:
226
self._realpaths[dirname] = os.path.realpath(dirname)
227
dirname = self._realpaths[dirname]
228
abs = pathjoin(dirname, basename)
229
if dirname in self._relpaths:
230
relpath = pathjoin(self._relpaths[dirname], basename)
231
relpath = relpath.rstrip('/\\')
233
relpath = self._tree.relpath(abs)
234
self._relpaths[abs] = relpath
206
dirname, basename = os.path.split(self._tree.abspath(path))
207
dirname = os.path.realpath(dirname)
208
return self._tree.relpath(pathjoin(dirname, basename))
237
210
def trans_id_tree_path(self, path):
238
211
"""Determine (and maybe set) the transaction ID for a tree path."""
260
233
New file takes the permissions of any existing file with that id,
261
234
unless mode_id is specified.
263
name = self._limbo_name(trans_id)
267
unique_add(self._new_contents, trans_id, 'file')
269
# Clean up the file, it never got registered so
270
# TreeTransform.finalize() won't clean it up.
275
for segment in contents:
236
f = file(self._limbo_name(trans_id), 'wb')
237
unique_add(self._new_contents, trans_id, 'file')
238
for segment in contents:
279
241
self._set_mode(trans_id, mode_id, S_ISREG)
281
243
def _set_mode(self, trans_id, mode_id, typefunc):
317
279
os.symlink(target, self._limbo_name(trans_id))
318
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):
320
293
def cancel_creation(self, trans_id):
321
294
"""Cancel the creation of new file contents."""
322
295
del self._new_contents[trans_id]
323
delete_any(self._limbo_name(trans_id))
296
self.delete_any(self._limbo_name(trans_id))
325
298
def delete_contents(self, trans_id):
326
299
"""Schedule the contents of a path entry for deletion"""
535
508
yield self.trans_id_tree_path(childpath)
537
def has_named_child(self, by_parent, parent_id, name):
539
children = by_parent[parent_id]
542
for child in children:
543
if self.final_name(child) == name:
546
path = self._tree_id_paths[parent_id]
549
childpath = joinpath(path, name)
550
child_id = self._tree_path_ids.get(childpath)
552
return lexists(self._tree.abspath(childpath))
554
if self.final_parent(child_id) != parent_id:
556
if child_id in self._removed_contents:
557
# XXX What about dangling file-ids?
562
510
def _parent_loops(self):
563
511
"""No entry should be its own ancestor"""
717
659
raise MalformedTransform(conflicts=conflicts)
719
661
inv = self._tree.inventory
720
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
722
child_pb.update('Apply phase', 0, 2)
723
self._apply_removals(inv, limbo_inv)
724
child_pb.update('Apply phase', 1, 2)
725
modified_paths = self._apply_insertions(inv, limbo_inv)
662
self._apply_removals(inv, limbo_inv)
663
self._apply_insertions(inv, limbo_inv)
728
664
self._tree._write_inventory(inv)
729
665
self.__done = True
731
return _TransformResults(modified_paths)
733
668
def _limbo_name(self, trans_id):
734
669
"""Generate the limbo name of a file"""
744
679
tree_paths = list(self._tree_path_ids.iteritems())
745
680
tree_paths.sort(reverse=True)
746
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
748
for num, data in enumerate(tree_paths):
749
path, trans_id = data
750
child_pb.update('removing file', num, len(tree_paths))
751
full_path = self._tree.abspath(path)
752
if trans_id in self._removed_contents:
753
delete_any(full_path)
754
elif trans_id in self._new_name or trans_id in \
757
os.rename(full_path, self._limbo_name(trans_id))
759
if e.errno != errno.ENOENT:
761
if trans_id in self._removed_id:
762
if trans_id == self._new_root:
763
file_id = self._tree.inventory.root.file_id
765
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]
767
elif trans_id in self._new_name or trans_id in self._new_parent:
768
file_id = self.tree_file_id(trans_id)
769
if file_id is not None:
770
limbo_inv[trans_id] = inv[file_id]
775
706
def _apply_insertions(self, inv, limbo_inv):
776
707
"""Perform tree operations that insert directory/inventory names.
780
711
parent-to-child order.
782
713
new_paths = self.new_paths()
784
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
786
for num, (path, trans_id) in enumerate(new_paths):
787
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)
789
kind = self._new_contents[trans_id]
791
kind = contents = None
792
if trans_id in self._new_contents or \
793
self.path_changed(trans_id):
794
full_path = self._tree.abspath(path)
796
os.rename(self._limbo_name(trans_id), full_path)
798
# We may be renaming a dangling inventory id
799
if e.errno != errno.ENOENT:
801
if trans_id in self._new_contents:
802
modified_paths.append(full_path)
803
del self._new_contents[trans_id]
805
if trans_id in self._new_id:
807
kind = file_kind(self._tree.abspath(path))
808
inv.add_path(path, kind, self._new_id[trans_id])
809
elif trans_id in self._new_name or trans_id in\
811
entry = limbo_inv.get(trans_id)
812
if entry is not None:
813
entry.name = self.final_name(trans_id)
814
parent_path = os.path.dirname(path)
816
self._tree.inventory.path2id(parent_path)
819
# requires files and inventory entries to be in place
820
if trans_id in self._new_executability:
821
self._set_executability(path, inv, trans_id)
824
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)
826
748
def _set_executability(self, path, inv, trans_id):
827
749
"""Set the executability of versioned files """
859
781
parent_id is the transaction id of the parent directory of the file.
860
782
contents is an iterator of bytestrings, which will be used to produce
862
:param file_id: The inventory ID of the file, if it is to be versioned.
863
:param executable: Only valid when a file_id has been supplied.
784
file_id is the inventory ID of the file, if it is to be versioned.
865
786
trans_id = self._new_entry(name, parent_id, file_id)
866
# TODO: rather than scheduling a set_executable call,
867
# have create_file create the file with the right mode.
868
787
self.create_file(contents, trans_id)
869
788
if executable is not None:
870
789
self.set_executability(executable, trans_id)
938
857
def build_tree(tree, wt):
939
858
"""Create working tree for a branch, using a Transaction."""
940
859
file_trans_id = {}
941
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
942
pp = ProgressPhase("Build phase", 2, top_pb)
943
860
tt = TreeTransform(wt)
946
862
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
947
863
file_ids = topology_sorted_ids(tree)
948
pb = bzrlib.ui.ui_factory.nested_progress_bar()
950
for num, file_id in enumerate(file_ids):
951
pb.update("Building tree", num, len(file_ids))
952
entry = tree.inventory[file_id]
953
if entry.parent_id is None:
955
if entry.parent_id not in file_trans_id:
956
raise repr(entry.parent_id)
957
parent_id = file_trans_id[entry.parent_id]
958
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)
968
876
def new_by_entry(tt, entry, parent_id, tree):
969
877
"""Create a new file according to its inventory entry"""
1000
908
def find_interesting(working_tree, target_tree, filenames):
1001
909
"""Find the ids corresponding to specified filenames."""
1002
trees = (working_tree, target_tree)
1003
return tree.find_ids_across_trees(filenames, trees)
911
interesting_ids = None
913
interesting_ids = set()
914
for tree_path in filenames:
915
for tree in (working_tree, target_tree):
917
file_id = tree.inventory.path2id(tree_path)
918
if file_id is not None:
919
interesting_ids.add(file_id)
922
raise NotVersionedError(path=tree_path)
923
return interesting_ids
1006
926
def change_entry(tt, file_id, working_tree, target_tree,
1007
trans_id_file_id, backups, trans_id, by_parent):
927
trans_id_file_id, backups, trans_id):
1008
928
"""Replace a file_id's contents with those from a target tree."""
1009
929
e_trans_id = trans_id_file_id(file_id)
1010
930
entry = target_tree.inventory[file_id]
1017
937
tt.delete_contents(e_trans_id)
1019
939
parent_trans_id = trans_id_file_id(entry.parent_id)
1020
backup_name = get_backup_name(entry, by_parent,
1021
parent_trans_id, tt)
1022
tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
940
tt.adjust_path(entry.name+"~", parent_trans_id, e_trans_id)
1023
941
tt.unversion_file(e_trans_id)
1024
942
e_trans_id = tt.create_path(entry.name, parent_trans_id)
1025
943
tt.version_file(file_id, e_trans_id)
1043
961
tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1046
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1047
"""Produce a backup-style name that appears to be available"""
1051
yield "%s.~%d~" % (entry.name, counter)
1053
for name in name_gen():
1054
if not tt.has_named_child(by_parent, parent_trans_id, name):
1057
964
def _entry_changes(file_id, entry, working_tree):
1058
965
"""Determine in which ways the inventory entry has changed.
1101
1009
except KeyError:
1102
1010
return tt.trans_id_tree_file_id(file_id)
1104
pp = ProgressPhase("Revert phase", 4, pb)
1106
1012
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1107
1013
interesting(i)]
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,
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)
1130
1024
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]
1145
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1147
raw_conflicts = resolve_conflicts(tt, child_pb)
1150
conflicts = cook_conflicts(raw_conflicts, tt)
1151
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)):
1155
working_tree.set_merge_modified({})
1162
1038
def resolve_conflicts(tt, pb=DummyProgress()):
1216
1092
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1217
1093
return new_conflicts
1220
1095
def cook_conflicts(raw_conflicts, tt):
1221
1096
"""Generate a list of cooked conflicts, sorted by file path"""
1222
from bzrlib.conflicts import Conflict
1223
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1224
return sorted(conflict_iter, key=Conflict.sort_key)
1098
if conflict[2] is not None:
1099
return conflict[2], conflict[0]
1100
elif len(conflict) == 6:
1101
return conflict[4], conflict[0]
1103
return None, conflict[0]
1105
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1227
1107
def iter_cook_conflicts(raw_conflicts, tt):
1228
from bzrlib.conflicts import Conflict
1108
cooked_conflicts = []
1229
1109
fp = FinalPaths(tt)
1230
1110
for conflict in raw_conflicts:
1231
1111
c_type = conflict[0]
1233
1113
modified_path = fp.get_path(conflict[2])
1234
1114
modified_id = tt.final_file_id(conflict[2])
1235
1115
if len(conflict) == 3:
1236
yield Conflict.factory(c_type, action=action, path=modified_path,
1237
file_id=modified_id)
1116
yield c_type, action, modified_path, modified_id
1240
1118
conflicting_path = fp.get_path(conflict[3])
1241
1119
conflicting_id = tt.final_file_id(conflict[3])
1242
yield Conflict.factory(c_type, action=action, path=modified_path,
1243
file_id=modified_id,
1244
conflict_path=conflicting_path,
1245
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