19
19
from stat import S_ISREG
21
from bzrlib import BZRDIR
21
22
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
22
ReusingTransform, NotVersionedError, CantMoveRoot,
23
ExistingLimbo, ImmortalLimbo)
23
ReusingTransform, NotVersionedError, CantMoveRoot)
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
28
from bzrlib.trace import mutter, warning
25
from bzrlib.osutils import file_kind, supports_executable, pathjoin
26
from bzrlib.trace import mutter
32
28
ROOT_PARENT = "root-parent"
35
30
def unique_add(map, key, value):
37
32
raise DuplicateKey(key=key)
41
class _TransformResults(object):
42
def __init__(self, modified_paths):
44
self.modified_paths = modified_paths
47
35
class TreeTransform(object):
48
"""Represent a tree transformation.
50
This object is designed to support incremental generation of the transform,
53
It is easy to produce malformed transforms, but they are generally
54
harmless. Attempting to apply a malformed transform will cause an
55
exception to be raised before any modifications are made to the tree.
57
Many kinds of malformed transforms can be corrected with the
58
resolve_conflicts function. The remaining ones indicate programming error,
59
such as trying to create a file with no path.
61
Two sets of file creation methods are supplied. Convenience methods are:
66
These are composed of the low-level methods:
68
* create_file or create_directory or create_symlink
72
def __init__(self, tree, pb=DummyProgress()):
36
"""Represent a tree transformation."""
37
def __init__(self, tree):
73
38
"""Note: a write lock is taken on the tree.
75
40
Use TreeTransform.finalize() to release the lock
714
637
tree_paths = list(self._tree_path_ids.iteritems())
715
638
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)
639
for path, trans_id in tree_paths:
640
full_path = self._tree.abspath(path)
641
if trans_id in self._removed_contents:
642
self.delete_any(full_path)
643
elif trans_id in self._new_name or trans_id in self._new_parent:
645
os.rename(full_path, self._limbo_name(trans_id))
647
if e.errno != errno.ENOENT:
649
if trans_id in self._removed_id:
650
if trans_id == self._new_root:
651
file_id = self._tree.inventory.root.file_id
653
file_id = self.get_tree_file_id(trans_id)
655
elif trans_id in self._new_name or trans_id in self._new_parent:
656
file_id = self.get_tree_file_id(trans_id)
657
if file_id is not None:
658
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
661
def _apply_insertions(self, inv, limbo_inv):
746
662
"""Perform tree operations that insert directory/inventory names.
749
665
limbo any files that needed renaming. This must be done in strict
750
666
parent-to-child order.
752
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))
668
for path, trans_id in self.new_paths():
670
kind = self._new_contents[trans_id]
672
kind = contents = None
673
if trans_id in self._new_contents or self.path_changed(trans_id):
674
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
676
os.rename(self._limbo_name(trans_id), full_path)
678
# We may be renaming a dangling inventory id
679
if e.errno != errno.ENOENT:
681
if trans_id in self._new_contents:
682
del self._new_contents[trans_id]
684
if trans_id in self._new_id:
686
kind = file_kind(self._tree.abspath(path))
687
inv.add_path(path, kind, self._new_id[trans_id])
688
elif trans_id in self._new_name or trans_id in self._new_parent:
689
entry = limbo_inv.get(trans_id)
690
if entry is not None:
691
entry.name = self.final_name(trans_id)
692
parent_path = os.path.dirname(path)
693
entry.parent_id = self._tree.inventory.path2id(parent_path)
696
# requires files and inventory entries to be in place
697
if trans_id in self._new_executability:
698
self._set_executability(path, inv, trans_id)
796
700
def _set_executability(self, path, inv, trans_id):
797
701
"""Set the executability of versioned files """
902
805
file_ids.sort(key=tree.id2path)
905
def build_tree(tree, wt):
808
def build_tree(branch, tree):
906
809
"""Create working tree for a branch, using a Transaction."""
907
810
file_trans_id = {}
908
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
pp = ProgressPhase("Build phase", 2, top_pb)
811
wt = branch.working_tree()
910
812
tt = TreeTransform(wt)
913
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
814
file_trans_id[wt.get_root_id()] = tt.get_id_tree(wt.get_root_id())
914
815
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,
816
for file_id in file_ids:
817
entry = tree.inventory[file_id]
818
if entry.parent_id is None:
820
if entry.parent_id not in file_trans_id:
821
raise repr(entry.parent_id)
822
parent_id = file_trans_id[entry.parent_id]
823
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, tree)
935
828
def new_by_entry(tt, entry, parent_id, tree):
936
829
"""Create a new file according to its inventory entry"""
1061
941
cur_entry._read_tree_state(working_tree.id2path(file_id),
1063
943
contents_mod, meta_mod = entry.detect_changes(cur_entry)
1064
cur_entry._forget_tree_state()
1065
944
return has_contents, contents_mod, meta_mod
1068
def revert(working_tree, target_tree, filenames, backups=False,
1069
pb=DummyProgress()):
947
def revert(working_tree, target_tree, filenames, backups=False):
1070
948
"""Revert a working tree's contents to those of a target tree."""
1071
949
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1072
950
def interesting(file_id):
1073
951
return interesting_ids is None or file_id in interesting_ids
1075
tt = TreeTransform(working_tree, pb)
953
tt = TreeTransform(working_tree)
1077
merge_modified = working_tree.merge_modified()
1079
def trans_id_file_id(file_id):
956
def get_trans_id(file_id):
1081
958
return trans_id[file_id]
1082
959
except KeyError:
1083
return tt.trans_id_tree_file_id(file_id)
960
return tt.get_id_tree(file_id)
1085
pp = ProgressPhase("Revert phase", 4, pb)
1087
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
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,
1111
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:
962
for file_id in topology_sorted_ids(target_tree):
963
if not interesting(file_id):
965
if file_id not in working_tree.inventory:
966
entry = target_tree.inventory[file_id]
967
parent_id = get_trans_id(entry.parent_id)
968
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
969
trans_id[file_id] = e_trans_id
971
change_entry(tt, file_id, working_tree, target_tree,
972
get_trans_id, backups, trans_id)
973
for file_id in working_tree:
974
if not interesting(file_id):
976
if file_id not in target_tree:
977
tt.unversion_file(tt.get_id_tree(file_id))
978
resolve_conflicts(tt)
1136
working_tree.set_merge_modified({})
1143
def resolve_conflicts(tt, pb=DummyProgress()):
984
def resolve_conflicts(tt):
1144
985
"""Make many conflict-resolution attempts, but die if they fail"""
1145
new_conflicts = set()
1148
pb.update('Resolution pass', n+1, 10)
1149
conflicts = tt.find_conflicts()
1150
if len(conflicts) == 0:
1151
return new_conflicts
1152
new_conflicts.update(conflict_pass(tt, conflicts))
1153
raise MalformedTransform(conflicts=conflicts)
987
conflicts = tt.find_conflicts()
988
if len(conflicts) == 0:
990
conflict_pass(tt, conflicts)
991
raise MalformedTransform(conflicts=conflicts)
1158
994
def conflict_pass(tt, conflicts):
1159
995
"""Resolve some classes of conflicts."""
1160
new_conflicts = set()
1161
996
for c_type, conflict in ((c[0], c) for c in conflicts):
1162
997
if c_type == 'duplicate id':
1163
998
tt.unversion_file(conflict[1])
1164
new_conflicts.add((c_type, 'Unversioned existing file',
1165
conflict[1], conflict[2], ))
1166
999
elif c_type == 'duplicate':
1167
1000
# files that were renamed take precedence
1168
1001
new_name = tt.final_name(conflict[1])+'.moved'
1169
1002
final_parent = tt.final_parent(conflict[1])
1170
1003
if tt.path_changed(conflict[1]):
1171
1004
tt.adjust_path(new_name, final_parent, conflict[2])
1172
new_conflicts.add((c_type, 'Moved existing file to',
1173
conflict[2], conflict[1]))
1175
1006
tt.adjust_path(new_name, final_parent, conflict[1])
1176
new_conflicts.add((c_type, 'Moved existing file to',
1177
conflict[1], conflict[2]))
1178
1007
elif c_type == 'parent loop':
1179
1008
# break the loop by undoing one of the ops that caused the loop
1180
1009
cur = conflict[1]
1181
1010
while not tt.path_changed(cur):
1182
1011
cur = tt.final_parent(cur)
1183
new_conflicts.add((c_type, 'Cancelled move', cur,
1184
tt.final_parent(cur),))
1185
1012
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1187
1013
elif c_type == 'missing parent':
1188
1014
trans_id = conflict[1]
1190
1016
tt.cancel_deletion(trans_id)
1191
new_conflicts.add((c_type, 'Not deleting', trans_id))
1192
1017
except KeyError:
1193
1018
tt.create_directory(trans_id)
1194
new_conflicts.add((c_type, 'Created directory.', trans_id))
1195
1019
elif c_type == 'unversioned parent':
1196
1020
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1197
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1198
return new_conflicts
1201
def cook_conflicts(raw_conflicts, tt):
1202
"""Generate a list of cooked conflicts, sorted by file path"""
1203
from bzrlib.conflicts import Conflict
1204
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1205
return sorted(conflict_iter, key=Conflict.sort_key)
1208
def iter_cook_conflicts(raw_conflicts, tt):
1209
from bzrlib.conflicts import Conflict
1211
for conflict in raw_conflicts:
1212
c_type = conflict[0]
1213
action = conflict[1]
1214
modified_path = fp.get_path(conflict[2])
1215
modified_id = tt.final_file_id(conflict[2])
1216
if len(conflict) == 3:
1217
yield Conflict.factory(c_type, action=action, path=modified_path,
1218
file_id=modified_id)
1221
conflicting_path = fp.get_path(conflict[3])
1222
conflicting_id = tt.final_file_id(conflict[3])
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)