19
19
from stat import S_ISREG
21
from bzrlib import bzrdir, errors
21
22
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
22
23
ReusingTransform, NotVersionedError, CantMoveRoot,
23
ExistingLimbo, ImmortalLimbo)
24
ExistingLimbo, ImmortalLimbo, NoFinalPath)
24
25
from bzrlib.inventory import InventoryEntry
25
26
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
27
28
from bzrlib.progress import DummyProgress, ProgressPhase
28
29
from bzrlib.trace import mutter, warning
30
from bzrlib import tree
32
import bzrlib.urlutils as urlutils
32
35
ROOT_PARENT = "root-parent"
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
945
file_ids.sort(key=tree.id2path)
905
949
def build_tree(tree, wt):
906
"""Create working tree for a branch, using a Transaction."""
950
"""Create working tree for a branch, using a TreeTransform.
952
This function should be used on empty trees, having a tree root at most.
953
(see merge and revert functionality for working with existing trees)
955
Existing files are handled like so:
957
- Existing bzrdirs take precedence over creating new items. They are
958
created as '%s.diverted' % name.
959
- Otherwise, if the content on disk matches the content we are building,
960
it is silently replaced.
961
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
963
if len(wt.inventory) > 1: # more than just a root
964
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
907
965
file_trans_id = {}
908
966
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
967
pp = ProgressPhase("Build phase", 2, top_pb)
968
if tree.inventory.root is not None:
969
wt.set_root_id(tree.inventory.root.file_id)
910
970
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)
974
file_trans_id[wt.get_root_id()] = \
975
tt.trans_id_tree_file_id(wt.get_root_id())
915
976
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]
978
for num, (tree_path, entry) in \
979
enumerate(tree.inventory.iter_entries_by_dir()):
980
pb.update("Building tree", num, len(tree.inventory))
920
981
if entry.parent_id is None:
984
file_id = entry.file_id
985
target_path = wt.abspath(tree_path)
987
kind = file_kind(target_path)
991
if kind == "directory":
993
bzrdir.BzrDir.open(target_path)
994
except errors.NotBranchError:
998
if (file_id not in divert and
999
_content_match(tree, entry, file_id, kind,
1001
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1002
if kind == 'directory':
922
1004
if entry.parent_id not in file_trans_id:
923
1005
raise repr(entry.parent_id)
924
1006
parent_id = file_trans_id[entry.parent_id]
925
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1007
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1010
new_trans_id = file_trans_id[file_id]
1011
old_parent = tt.trans_id_tree_path(tree_path)
1012
_reparent_children(tt, old_parent, new_trans_id)
1016
divert_trans = set(file_trans_id[f] for f in divert)
1017
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1018
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1019
conflicts = cook_conflicts(raw_conflicts, tt)
1020
for conflict in conflicts:
1023
wt.add_conflicts(conflicts)
1024
except errors.UnsupportedOperation:
933
1029
top_pb.finished()
1032
def _reparent_children(tt, old_parent, new_parent):
1033
for child in tt.iter_tree_children(old_parent):
1034
tt.adjust_path(tt.final_name(child), new_parent, child)
1037
def _content_match(tree, entry, file_id, kind, target_path):
1038
if entry.kind != kind:
1040
if entry.kind == "directory":
1042
if entry.kind == "file":
1043
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1045
elif entry.kind == "symlink":
1046
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1051
def resolve_checkout(tt, conflicts, divert):
1052
new_conflicts = set()
1053
for c_type, conflict in ((c[0], c) for c in conflicts):
1054
# Anything but a 'duplicate' would indicate programmer error
1055
assert c_type == 'duplicate', c_type
1056
# Now figure out which is new and which is old
1057
if tt.new_contents(conflict[1]):
1058
new_file = conflict[1]
1059
old_file = conflict[2]
1061
new_file = conflict[2]
1062
old_file = conflict[1]
1064
# We should only get here if the conflict wasn't completely
1066
final_parent = tt.final_parent(old_file)
1067
if new_file in divert:
1068
new_name = tt.final_name(old_file)+'.diverted'
1069
tt.adjust_path(new_name, final_parent, new_file)
1070
new_conflicts.add((c_type, 'Diverted to',
1071
new_file, old_file))
1073
new_name = tt.final_name(old_file)+'.moved'
1074
tt.adjust_path(new_name, final_parent, old_file)
1075
new_conflicts.add((c_type, 'Moved existing file to',
1076
old_file, new_file))
1077
return new_conflicts
935
1080
def new_by_entry(tt, entry, parent_id, tree):
936
1081
"""Create a new file according to its inventory entry"""
937
1082
name = entry.name
1025
1158
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1159
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1162
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1026
1163
"""Produce a backup-style name that appears to be available"""
1027
1164
def name_gen():
1030
yield "%s.~%d~" % (entry.name, counter)
1167
yield "%s.~%d~" % (name, counter)
1032
for name in name_gen():
1033
if not tt.has_named_child(by_parent, parent_trans_id, name):
1169
for new_name in name_gen():
1170
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1036
1174
def _entry_changes(file_id, entry, working_tree):
1037
1175
"""Determine in which ways the inventory entry has changed.
1069
1202
pb=DummyProgress()):
1070
1203
"""Revert a working tree's contents to those of a target tree."""
1071
1204
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1072
def interesting(file_id):
1073
return interesting_ids is None or file_id in interesting_ids
1075
1205
tt = TreeTransform(working_tree, pb)
1077
merge_modified = working_tree.merge_modified()
1079
def trans_id_file_id(file_id):
1081
return trans_id[file_id]
1083
return tt.trans_id_tree_file_id(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]
1207
pp = ProgressPhase("Revert phase", 3, pb)
1209
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1211
_alter_files(working_tree, target_tree, tt, child_pb,
1212
interesting_ids, backups)
1124
1214
child_pb.finished()
1125
1215
pp.next_phase()
1140
1230
return conflicts
1143
def resolve_conflicts(tt, pb=DummyProgress()):
1233
def _alter_files(working_tree, target_tree, tt, pb, interesting_ids, backups):
1234
merge_modified = working_tree.merge_modified()
1235
iterator = target_tree._iter_changes(working_tree,
1236
specific_file_ids=interesting_ids,
1238
if target_tree.inventory.root is None:
1243
for id_num, (file_id, path, changed_content, versioned, parent, name, kind,
1244
executable) in enumerate(iterator):
1245
if skip_root and file_id[0] is not None and parent[0] is None:
1247
trans_id = tt.trans_id_file_id(file_id)
1250
keep_content = False
1251
if kind[0] == 'file' and (backups or kind[1] is None):
1252
wt_sha1 = working_tree.get_file_sha1(file_id)
1253
if merge_modified.get(file_id) != wt_sha1:
1254
if basis_tree is None:
1255
basis_tree = working_tree.basis_tree()
1256
if file_id in basis_tree:
1257
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1259
elif kind[1] is None and not versioned[1]:
1261
if kind[0] is not None:
1262
if not keep_content:
1263
tt.delete_contents(trans_id)
1264
elif kind[1] is not None:
1265
parent_trans_id = tt.trans_id_file_id(parent[0])
1266
by_parent = tt.by_parent()
1267
backup_name = _get_backup_name(name[0], by_parent,
1268
parent_trans_id, tt)
1269
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1270
new_trans_id = tt.create_path(name[0], parent_trans_id)
1271
if versioned == (True, True):
1272
tt.unversion_file(trans_id)
1273
tt.version_file(file_id, new_trans_id)
1274
# New contents should have the same unix perms as old
1277
trans_id = new_trans_id
1278
if kind[1] == 'directory':
1279
tt.create_directory(trans_id)
1280
elif kind[1] == 'symlink':
1281
tt.create_symlink(target_tree.get_symlink_target(file_id),
1283
elif kind[1] == 'file':
1284
tt.create_file(target_tree.get_file_lines(file_id),
1286
# preserve the execute bit when backing up
1287
if keep_content and executable[0] == executable[1]:
1288
tt.set_executability(executable[1], trans_id)
1290
assert kind[1] is None
1291
if versioned == (False, True):
1292
tt.version_file(file_id, trans_id)
1293
if versioned == (True, False):
1294
tt.unversion_file(trans_id)
1295
if (name[1] is not None and
1296
(name[0] != name[1] or parent[0] != parent[1])):
1297
tt.adjust_path(name[1], tt.trans_id_file_id(parent[1]), trans_id)
1298
if executable[0] != executable[1] and kind[1] == "file":
1299
tt.set_executability(executable[1], trans_id)
1302
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1144
1303
"""Make many conflict-resolution attempts, but die if they fail"""
1304
if pass_func is None:
1305
pass_func = conflict_pass
1145
1306
new_conflicts = set()
1147
1308
for n in range(10):
1188
1349
trans_id = conflict[1]
1190
1351
tt.cancel_deletion(trans_id)
1191
new_conflicts.add((c_type, 'Not deleting', trans_id))
1352
new_conflicts.add(('deleting parent', 'Not deleting',
1192
1354
except KeyError:
1193
1355
tt.create_directory(trans_id)
1194
new_conflicts.add((c_type, 'Created directory.', trans_id))
1356
new_conflicts.add((c_type, 'Created directory', trans_id))
1195
1357
elif c_type == 'unversioned parent':
1196
1358
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1197
1359
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1198
1360
return new_conflicts
1200
1363
def cook_conflicts(raw_conflicts, tt):
1201
1364
"""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
1365
from bzrlib.conflicts import Conflict
1366
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1367
return sorted(conflict_iter, key=Conflict.sort_key)
1210
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1212
1370
def iter_cook_conflicts(raw_conflicts, tt):
1213
1371
from bzrlib.conflicts import Conflict