1
1
# Copyright (C) 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
211
219
def canonical_path(self, path):
212
220
"""Get the canonical tree-relative path"""
213
221
# don't follow final symlinks
214
dirname, basename = os.path.split(self._tree.abspath(path))
215
dirname = os.path.realpath(dirname)
216
return self._tree.relpath(pathjoin(dirname, basename))
222
abs = self._tree.abspath(path)
223
if abs in self._relpaths:
224
return self._relpaths[abs]
225
dirname, basename = os.path.split(abs)
226
if dirname not in self._realpaths:
227
self._realpaths[dirname] = os.path.realpath(dirname)
228
dirname = self._realpaths[dirname]
229
abs = pathjoin(dirname, basename)
230
if dirname in self._relpaths:
231
relpath = pathjoin(self._relpaths[dirname], basename)
232
relpath = relpath.rstrip('/\\')
234
relpath = self._tree.relpath(abs)
235
self._relpaths[abs] = relpath
218
238
def trans_id_tree_path(self, path):
219
239
"""Determine (and maybe set) the transaction ID for a tree path."""
902
939
file_ids.sort(key=tree.id2path)
905
943
def build_tree(tree, wt):
906
"""Create working tree for a branch, using a Transaction."""
944
"""Create working tree for a branch, using a TreeTransform.
946
This function should be used on empty trees, having a tree root at most.
947
(see merge and revert functionality for working with existing trees)
949
Existing files are handled like so:
951
- Existing bzrdirs take precedence over creating new items. They are
952
created as '%s.diverted' % name.
953
- Otherwise, if the content on disk matches the content we are building,
954
it is silently replaced.
955
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
957
assert 2 > len(wt.inventory)
907
958
file_trans_id = {}
908
959
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
960
pp = ProgressPhase("Build phase", 2, top_pb)
910
961
tt = TreeTransform(wt)
913
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
914
file_ids = topology_sorted_ids(tree)
965
file_trans_id[wt.get_root_id()] = \
966
tt.trans_id_tree_file_id(wt.get_root_id())
915
967
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]
969
for num, (tree_path, entry) in \
970
enumerate(tree.inventory.iter_entries_by_dir()):
971
pb.update("Building tree", num, len(tree.inventory))
920
972
if entry.parent_id is None:
975
file_id = entry.file_id
976
target_path = wt.abspath(tree_path)
978
kind = file_kind(target_path)
982
if kind == "directory":
984
bzrdir.BzrDir.open(target_path)
985
except errors.NotBranchError:
989
if (file_id not in divert and
990
_content_match(tree, entry, file_id, kind,
992
tt.delete_contents(tt.trans_id_tree_path(tree_path))
993
if kind == 'directory':
922
995
if entry.parent_id not in file_trans_id:
923
996
raise repr(entry.parent_id)
924
997
parent_id = file_trans_id[entry.parent_id]
925
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
998
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1001
new_trans_id = file_trans_id[file_id]
1002
old_parent = tt.trans_id_tree_path(tree_path)
1003
_reparent_children(tt, old_parent, new_trans_id)
1007
divert_trans = set(file_trans_id[f] for f in divert)
1008
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1009
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1010
conflicts = cook_conflicts(raw_conflicts, tt)
1011
for conflict in conflicts:
1014
wt.add_conflicts(conflicts)
1015
except errors.UnsupportedOperation:
933
1020
top_pb.finished()
1023
def _reparent_children(tt, old_parent, new_parent):
1024
for child in tt.iter_tree_children(old_parent):
1025
tt.adjust_path(tt.final_name(child), new_parent, child)
1028
def _content_match(tree, entry, file_id, kind, target_path):
1029
if entry.kind != kind:
1031
if entry.kind == "directory":
1033
if entry.kind == "file":
1034
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1036
elif entry.kind == "symlink":
1037
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1042
def resolve_checkout(tt, conflicts, divert):
1043
new_conflicts = set()
1044
for c_type, conflict in ((c[0], c) for c in conflicts):
1045
# Anything but a 'duplicate' would indicate programmer error
1046
assert c_type == 'duplicate', c_type
1047
# Now figure out which is new and which is old
1048
if tt.new_contents(conflict[1]):
1049
new_file = conflict[1]
1050
old_file = conflict[2]
1052
new_file = conflict[2]
1053
old_file = conflict[1]
1055
# We should only get here if the conflict wasn't completely
1057
final_parent = tt.final_parent(old_file)
1058
if new_file in divert:
1059
new_name = tt.final_name(old_file)+'.diverted'
1060
tt.adjust_path(new_name, final_parent, new_file)
1061
new_conflicts.add((c_type, 'Diverted to',
1062
new_file, old_file))
1064
new_name = tt.final_name(old_file)+'.moved'
1065
tt.adjust_path(new_name, final_parent, old_file)
1066
new_conflicts.add((c_type, 'Moved existing file to',
1067
old_file, new_file))
1068
return new_conflicts
935
1071
def new_by_entry(tt, entry, parent_id, tree):
936
1072
"""Create a new file according to its inventory entry"""
937
1073
name = entry.name
1188
1330
trans_id = conflict[1]
1190
1332
tt.cancel_deletion(trans_id)
1191
new_conflicts.add((c_type, 'Not deleting', trans_id))
1333
new_conflicts.add(('deleting parent', 'Not deleting',
1192
1335
except KeyError:
1193
1336
tt.create_directory(trans_id)
1194
new_conflicts.add((c_type, 'Created directory.', trans_id))
1337
new_conflicts.add((c_type, 'Created directory', trans_id))
1195
1338
elif c_type == 'unversioned parent':
1196
1339
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1197
1340
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1198
1341
return new_conflicts
1200
1344
def cook_conflicts(raw_conflicts, tt):
1201
1345
"""Generate a list of cooked conflicts, sorted by file path"""
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
1208
return None, conflict.typestring
1346
from bzrlib.conflicts import Conflict
1347
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1348
return sorted(conflict_iter, key=Conflict.sort_key)
1210
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1212
1351
def iter_cook_conflicts(raw_conflicts, tt):
1213
1352
from bzrlib.conflicts import Conflict