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
944
file_ids.sort(key=tree.id2path)
905
948
def build_tree(tree, wt):
906
"""Create working tree for a branch, using a Transaction."""
949
"""Create working tree for a branch, using a TreeTransform.
951
This function should be used on empty trees, having a tree root at most.
952
(see merge and revert functionality for working with existing trees)
954
Existing files are handled like so:
956
- Existing bzrdirs take precedence over creating new items. They are
957
created as '%s.diverted' % name.
958
- Otherwise, if the content on disk matches the content we are building,
959
it is silently replaced.
960
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
962
if len(wt.inventory) > 1: # more than just a root
963
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
907
964
file_trans_id = {}
908
965
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
966
pp = ProgressPhase("Build phase", 2, top_pb)
967
if tree.inventory.root is not None:
968
wt.set_root_id(tree.inventory.root.file_id)
910
969
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)
973
file_trans_id[wt.get_root_id()] = \
974
tt.trans_id_tree_file_id(wt.get_root_id())
915
975
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]
977
for num, (tree_path, entry) in \
978
enumerate(tree.inventory.iter_entries_by_dir()):
979
pb.update("Building tree", num, len(tree.inventory))
920
980
if entry.parent_id is None:
983
file_id = entry.file_id
984
target_path = wt.abspath(tree_path)
986
kind = file_kind(target_path)
990
if kind == "directory":
992
bzrdir.BzrDir.open(target_path)
993
except errors.NotBranchError:
997
if (file_id not in divert and
998
_content_match(tree, entry, file_id, kind,
1000
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1001
if kind == 'directory':
922
1003
if entry.parent_id not in file_trans_id:
923
1004
raise repr(entry.parent_id)
924
1005
parent_id = file_trans_id[entry.parent_id]
925
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1006
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1009
new_trans_id = file_trans_id[file_id]
1010
old_parent = tt.trans_id_tree_path(tree_path)
1011
_reparent_children(tt, old_parent, new_trans_id)
1015
divert_trans = set(file_trans_id[f] for f in divert)
1016
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1017
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1018
conflicts = cook_conflicts(raw_conflicts, tt)
1019
for conflict in conflicts:
1022
wt.add_conflicts(conflicts)
1023
except errors.UnsupportedOperation:
933
1028
top_pb.finished()
1031
def _reparent_children(tt, old_parent, new_parent):
1032
for child in tt.iter_tree_children(old_parent):
1033
tt.adjust_path(tt.final_name(child), new_parent, child)
1036
def _content_match(tree, entry, file_id, kind, target_path):
1037
if entry.kind != kind:
1039
if entry.kind == "directory":
1041
if entry.kind == "file":
1042
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1044
elif entry.kind == "symlink":
1045
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1050
def resolve_checkout(tt, conflicts, divert):
1051
new_conflicts = set()
1052
for c_type, conflict in ((c[0], c) for c in conflicts):
1053
# Anything but a 'duplicate' would indicate programmer error
1054
assert c_type == 'duplicate', c_type
1055
# Now figure out which is new and which is old
1056
if tt.new_contents(conflict[1]):
1057
new_file = conflict[1]
1058
old_file = conflict[2]
1060
new_file = conflict[2]
1061
old_file = conflict[1]
1063
# We should only get here if the conflict wasn't completely
1065
final_parent = tt.final_parent(old_file)
1066
if new_file in divert:
1067
new_name = tt.final_name(old_file)+'.diverted'
1068
tt.adjust_path(new_name, final_parent, new_file)
1069
new_conflicts.add((c_type, 'Diverted to',
1070
new_file, old_file))
1072
new_name = tt.final_name(old_file)+'.moved'
1073
tt.adjust_path(new_name, final_parent, old_file)
1074
new_conflicts.add((c_type, 'Moved existing file to',
1075
old_file, new_file))
1076
return new_conflicts
935
1079
def new_by_entry(tt, entry, parent_id, tree):
936
1080
"""Create a new file according to its inventory entry"""
937
1081
name = entry.name
1025
1157
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1158
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1161
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1026
1162
"""Produce a backup-style name that appears to be available"""
1027
1163
def name_gen():
1030
yield "%s.~%d~" % (entry.name, counter)
1166
yield "%s.~%d~" % (name, counter)
1032
for name in name_gen():
1033
if not tt.has_named_child(by_parent, parent_trans_id, name):
1168
for new_name in name_gen():
1169
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1036
1173
def _entry_changes(file_id, entry, working_tree):
1037
1174
"""Determine in which ways the inventory entry has changed.
1068
1200
def revert(working_tree, target_tree, filenames, backups=False,
1069
pb=DummyProgress()):
1201
pb=DummyProgress(), change_reporter=None):
1070
1202
"""Revert a working tree's contents to those of a target tree."""
1071
1203
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
1204
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]
1206
pp = ProgressPhase("Revert phase", 3, pb)
1208
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1210
_alter_files(working_tree, target_tree, tt, child_pb,
1211
interesting_ids, backups, change_reporter)
1124
1213
child_pb.finished()
1125
1214
pp.next_phase()
1140
1229
return conflicts
1143
def resolve_conflicts(tt, pb=DummyProgress()):
1232
def _alter_files(working_tree, target_tree, tt, pb, interesting_ids, backups,
1234
from bzrlib import delta
1235
merge_modified = working_tree.merge_modified()
1236
change_list = list(target_tree._iter_changes(working_tree,
1237
specific_file_ids=interesting_ids, pb=pb))
1238
if target_tree.inventory.root is None:
1244
change_reporter = delta.ChangeReporter(working_tree.inventory)
1245
delta.report_changes(change_list, change_reporter)
1246
for id_num, (file_id, path, changed_content, versioned, parent, name, kind,
1247
executable) in enumerate(change_list):
1248
if skip_root and file_id[0] is not None and parent[0] is None:
1250
trans_id = tt.trans_id_file_id(file_id)
1253
keep_content = False
1254
if kind[0] == 'file' and (backups or kind[1] is None):
1255
wt_sha1 = working_tree.get_file_sha1(file_id)
1256
if merge_modified.get(file_id) != wt_sha1:
1257
if basis_tree is None:
1258
basis_tree = working_tree.basis_tree()
1259
if file_id in basis_tree:
1260
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1262
elif kind[1] is None and not versioned[1]:
1264
if kind[0] is not None:
1265
if not keep_content:
1266
tt.delete_contents(trans_id)
1267
elif kind[1] is not None:
1268
parent_trans_id = tt.trans_id_file_id(parent[0])
1269
by_parent = tt.by_parent()
1270
backup_name = _get_backup_name(name[0], by_parent,
1271
parent_trans_id, tt)
1272
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1273
new_trans_id = tt.create_path(name[0], parent_trans_id)
1274
if versioned == (True, True):
1275
tt.unversion_file(trans_id)
1276
tt.version_file(file_id, new_trans_id)
1277
# New contents should have the same unix perms as old
1280
trans_id = new_trans_id
1281
if kind[1] == 'directory':
1282
tt.create_directory(trans_id)
1283
elif kind[1] == 'symlink':
1284
tt.create_symlink(target_tree.get_symlink_target(file_id),
1286
elif kind[1] == 'file':
1287
tt.create_file(target_tree.get_file_lines(file_id),
1289
# preserve the execute bit when backing up
1290
if keep_content and executable[0] == executable[1]:
1291
tt.set_executability(executable[1], trans_id)
1293
assert kind[1] is None
1294
if versioned == (False, True):
1295
tt.version_file(file_id, trans_id)
1296
if versioned == (True, False):
1297
tt.unversion_file(trans_id)
1298
if (name[1] is not None and
1299
(name[0] != name[1] or parent[0] != parent[1])):
1300
tt.adjust_path(name[1], tt.trans_id_file_id(parent[1]), trans_id)
1301
if executable[0] != executable[1] and kind[1] == "file":
1302
tt.set_executability(executable[1], trans_id)
1305
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1144
1306
"""Make many conflict-resolution attempts, but die if they fail"""
1307
if pass_func is None:
1308
pass_func = conflict_pass
1145
1309
new_conflicts = set()
1147
1311
for n in range(10):
1188
1352
trans_id = conflict[1]
1190
1354
tt.cancel_deletion(trans_id)
1191
new_conflicts.add((c_type, 'Not deleting', trans_id))
1355
new_conflicts.add(('deleting parent', 'Not deleting',
1192
1357
except KeyError:
1193
1358
tt.create_directory(trans_id)
1194
new_conflicts.add((c_type, 'Created directory.', trans_id))
1359
new_conflicts.add((c_type, 'Created directory', trans_id))
1195
1360
elif c_type == 'unversioned parent':
1196
1361
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1197
1362
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1198
1363
return new_conflicts
1200
1366
def cook_conflicts(raw_conflicts, tt):
1201
1367
"""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
1368
from bzrlib.conflicts import Conflict
1369
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1370
return sorted(conflict_iter, key=Conflict.sort_key)
1210
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1212
1373
def iter_cook_conflicts(raw_conflicts, tt):
1213
1374
from bzrlib.conflicts import Conflict