19
19
from stat import S_ISREG
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
21
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
22
31
ReusingTransform, NotVersionedError, CantMoveRoot,
23
ExistingLimbo, ImmortalLimbo)
32
ExistingLimbo, ImmortalLimbo, NoFinalPath)
24
33
from bzrlib.inventory import InventoryEntry
25
34
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
27
36
from bzrlib.progress import DummyProgress, ProgressPhase
37
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
28
38
from bzrlib.trace import mutter, warning
39
from bzrlib import tree
41
import bzrlib.urlutils as urlutils
32
44
ROOT_PARENT = "root-parent"
211
229
def canonical_path(self, path):
212
230
"""Get the canonical tree-relative path"""
213
231
# 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))
232
abs = self._tree.abspath(path)
233
if abs in self._relpaths:
234
return self._relpaths[abs]
235
dirname, basename = os.path.split(abs)
236
if dirname not in self._realpaths:
237
self._realpaths[dirname] = os.path.realpath(dirname)
238
dirname = self._realpaths[dirname]
239
abs = pathjoin(dirname, basename)
240
if dirname in self._relpaths:
241
relpath = pathjoin(self._relpaths[dirname], basename)
242
relpath = relpath.rstrip('/\\')
244
relpath = self._tree.relpath(abs)
245
self._relpaths[abs] = relpath
218
248
def trans_id_tree_path(self, path):
219
249
"""Determine (and maybe set) the transaction ID for a tree path."""
861
924
self.create_symlink(target, trans_id)
927
def _affected_ids(self):
928
"""Return the set of transform ids affected by the transform"""
929
trans_ids = set(self._removed_id)
930
trans_ids.update(self._new_id.keys())
931
trans_ids.update(self._removed_contents)
932
trans_ids.update(self._new_contents.keys())
933
trans_ids.update(self._new_executability.keys())
934
trans_ids.update(self._new_name.keys())
935
trans_ids.update(self._new_parent.keys())
938
def _get_file_id_maps(self):
939
"""Return mapping of file_ids to trans_ids in the to and from states"""
940
trans_ids = self._affected_ids()
943
# Build up two dicts: trans_ids associated with file ids in the
944
# FROM state, vs the TO state.
945
for trans_id in trans_ids:
946
from_file_id = self.tree_file_id(trans_id)
947
if from_file_id is not None:
948
from_trans_ids[from_file_id] = trans_id
949
to_file_id = self.final_file_id(trans_id)
950
if to_file_id is not None:
951
to_trans_ids[to_file_id] = trans_id
952
return from_trans_ids, to_trans_ids
954
def _from_file_data(self, from_trans_id, from_versioned, file_id):
955
"""Get data about a file in the from (tree) state
957
Return a (name, parent, kind, executable) tuple
959
from_path = self._tree_id_paths.get(from_trans_id)
961
# get data from working tree if versioned
962
from_entry = self._tree.inventory[file_id]
963
from_name = from_entry.name
964
from_parent = from_entry.parent_id
967
if from_path is None:
968
# File does not exist in FROM state
972
# File exists, but is not versioned. Have to use path-
974
from_name = os.path.basename(from_path)
975
tree_parent = self.get_tree_parent(from_trans_id)
976
from_parent = self.tree_file_id(tree_parent)
977
if from_path is not None:
978
from_kind, from_executable, from_stats = \
979
self._tree._comparison_data(from_entry, from_path)
982
from_executable = False
983
return from_name, from_parent, from_kind, from_executable
985
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
986
"""Get data about a file in the to (target) state
988
Return a (name, parent, kind, executable) tuple
990
to_name = self.final_name(to_trans_id)
992
to_kind = self.final_kind(to_trans_id)
995
to_parent = self.final_file_id(self.final_parent(to_trans_id))
996
if to_trans_id in self._new_executability:
997
to_executable = self._new_executability[to_trans_id]
998
elif to_trans_id == from_trans_id:
999
to_executable = from_executable
1001
to_executable = False
1002
return to_name, to_parent, to_kind, to_executable
1004
def _iter_changes(self):
1005
"""Produce output in the same format as Tree._iter_changes.
1007
Will produce nonsensical results if invoked while inventory/filesystem
1008
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1010
This reads the Transform, but only reproduces changes involving a
1011
file_id. Files that are not versioned in either of the FROM or TO
1012
states are not reflected.
1014
final_paths = FinalPaths(self)
1015
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1017
# Now iterate through all active file_ids
1018
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1020
from_trans_id = from_trans_ids.get(file_id)
1021
# find file ids, and determine versioning state
1022
if from_trans_id is None:
1023
from_versioned = False
1024
from_trans_id = to_trans_ids[file_id]
1026
from_versioned = True
1027
to_trans_id = to_trans_ids.get(file_id)
1028
if to_trans_id is None:
1029
to_versioned = False
1030
to_trans_id = from_trans_id
1034
from_name, from_parent, from_kind, from_executable = \
1035
self._from_file_data(from_trans_id, from_versioned, file_id)
1037
to_name, to_parent, to_kind, to_executable = \
1038
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1040
if not from_versioned:
1043
from_path = self._tree_id_paths.get(from_trans_id)
1044
if not to_versioned:
1047
to_path = final_paths.get_path(to_trans_id)
1048
if from_kind != to_kind:
1050
elif to_kind in ('file' or 'symlink') and (
1051
to_trans_id != from_trans_id or
1052
to_trans_id in self._new_contents):
1054
if (not modified and from_versioned == to_versioned and
1055
from_parent==to_parent and from_name == to_name and
1056
from_executable == to_executable):
1058
results.append((file_id, (from_path, to_path), modified,
1059
(from_versioned, to_versioned),
1060
(from_parent, to_parent),
1061
(from_name, to_name),
1062
(from_kind, to_kind),
1063
(from_executable, to_executable)))
1064
return iter(sorted(results, key=lambda x:x[1]))
864
1067
def joinpath(parent, child):
865
1068
"""Join tree-relative paths, handling the tree root specially"""
866
1069
if parent is None or parent == "":
902
1105
file_ids.sort(key=tree.id2path)
905
1109
def build_tree(tree, wt):
906
"""Create working tree for a branch, using a Transaction."""
1110
"""Create working tree for a branch, using a TreeTransform.
1112
This function should be used on empty trees, having a tree root at most.
1113
(see merge and revert functionality for working with existing trees)
1115
Existing files are handled like so:
1117
- Existing bzrdirs take precedence over creating new items. They are
1118
created as '%s.diverted' % name.
1119
- Otherwise, if the content on disk matches the content we are building,
1120
it is silently replaced.
1121
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1123
wt.lock_tree_write()
1127
return _build_tree(tree, wt)
1133
def _build_tree(tree, wt):
1134
"""See build_tree."""
1135
if len(wt.inventory) > 1: # more than just a root
1136
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
907
1137
file_trans_id = {}
908
1138
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
1139
pp = ProgressPhase("Build phase", 2, top_pb)
1140
if tree.inventory.root is not None:
1141
# this is kindof a hack: we should be altering the root
1142
# as partof the regular tree shape diff logic.
1143
# the conditional test hereis to avoid doing an
1144
# expensive operation (flush) every time the root id
1145
# is set within the tree, nor setting the root and thus
1146
# marking the tree as dirty, because we use two different
1147
# idioms here: tree interfaces and inventory interfaces.
1148
if wt.path2id('') != tree.inventory.root.file_id:
1149
wt.set_root_id(tree.inventory.root.file_id)
910
1151
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)
1155
file_trans_id[wt.get_root_id()] = \
1156
tt.trans_id_tree_file_id(wt.get_root_id())
915
1157
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]
1159
for num, (tree_path, entry) in \
1160
enumerate(tree.inventory.iter_entries_by_dir()):
1161
pb.update("Building tree", num, len(tree.inventory))
920
1162
if entry.parent_id is None:
1165
file_id = entry.file_id
1166
target_path = wt.abspath(tree_path)
1168
kind = file_kind(target_path)
1172
if kind == "directory":
1174
bzrdir.BzrDir.open(target_path)
1175
except errors.NotBranchError:
1179
if (file_id not in divert and
1180
_content_match(tree, entry, file_id, kind,
1182
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1183
if kind == 'directory':
922
1185
if entry.parent_id not in file_trans_id:
923
raise repr(entry.parent_id)
1186
raise AssertionError(
1187
'entry %s parent id %r is not in file_trans_id %r'
1188
% (entry, entry.parent_id, file_trans_id))
924
1189
parent_id = file_trans_id[entry.parent_id]
925
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1190
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1193
new_trans_id = file_trans_id[file_id]
1194
old_parent = tt.trans_id_tree_path(tree_path)
1195
_reparent_children(tt, old_parent, new_trans_id)
1199
divert_trans = set(file_trans_id[f] for f in divert)
1200
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1201
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1202
conflicts = cook_conflicts(raw_conflicts, tt)
1203
for conflict in conflicts:
1206
wt.add_conflicts(conflicts)
1207
except errors.UnsupportedOperation:
933
1212
top_pb.finished()
1215
def _reparent_children(tt, old_parent, new_parent):
1216
for child in tt.iter_tree_children(old_parent):
1217
tt.adjust_path(tt.final_name(child), new_parent, child)
1220
def _content_match(tree, entry, file_id, kind, target_path):
1221
if entry.kind != kind:
1223
if entry.kind == "directory":
1225
if entry.kind == "file":
1226
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1228
elif entry.kind == "symlink":
1229
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1234
def resolve_checkout(tt, conflicts, divert):
1235
new_conflicts = set()
1236
for c_type, conflict in ((c[0], c) for c in conflicts):
1237
# Anything but a 'duplicate' would indicate programmer error
1238
assert c_type == 'duplicate', c_type
1239
# Now figure out which is new and which is old
1240
if tt.new_contents(conflict[1]):
1241
new_file = conflict[1]
1242
old_file = conflict[2]
1244
new_file = conflict[2]
1245
old_file = conflict[1]
1247
# We should only get here if the conflict wasn't completely
1249
final_parent = tt.final_parent(old_file)
1250
if new_file in divert:
1251
new_name = tt.final_name(old_file)+'.diverted'
1252
tt.adjust_path(new_name, final_parent, new_file)
1253
new_conflicts.add((c_type, 'Diverted to',
1254
new_file, old_file))
1256
new_name = tt.final_name(old_file)+'.moved'
1257
tt.adjust_path(new_name, final_parent, old_file)
1258
new_conflicts.add((c_type, 'Moved existing file to',
1259
old_file, new_file))
1260
return new_conflicts
935
1263
def new_by_entry(tt, entry, parent_id, tree):
936
1264
"""Create a new file according to its inventory entry"""
937
1265
name = entry.name
941
1269
executable = tree.is_executable(entry.file_id)
942
1270
return tt.new_file(name, parent_id, contents, entry.file_id,
944
elif kind == 'directory':
945
return tt.new_directory(name, parent_id, entry.file_id)
1272
elif kind in ('directory', 'tree-reference'):
1273
trans_id = tt.new_directory(name, parent_id, entry.file_id)
1274
if kind == 'tree-reference':
1275
tt.set_tree_reference(entry.reference_revision, trans_id)
946
1277
elif kind == 'symlink':
947
1278
target = tree.get_symlink_target(entry.file_id)
948
1279
return tt.new_symlink(name, parent_id, target, entry.file_id)
1281
raise errors.BadFileKindError(name, kind)
950
1283
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
951
1284
"""Create new file contents according to an inventory entry."""
952
1285
if entry.kind == "file":
954
1287
lines = tree.get_file(entry.file_id).readlines()
955
1288
tt.create_file(lines, trans_id, mode_id=mode_id)
956
1289
elif entry.kind == "symlink":
1065
1397
return has_contents, contents_mod, meta_mod
1068
def revert(working_tree, target_tree, filenames, backups=False,
1069
pb=DummyProgress()):
1400
def revert(working_tree, target_tree, filenames, backups=False,
1401
pb=DummyProgress(), change_reporter=None):
1070
1402
"""Revert a working tree's contents to those of a target tree."""
1071
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
1403
target_tree.lock_read()
1075
1404
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]
1406
pp = ProgressPhase("Revert phase", 3, pb)
1408
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1410
_alter_files(working_tree, target_tree, tt, child_pb,
1124
1413
child_pb.finished()
1125
1414
pp.next_phase()
1130
1419
child_pb.finished()
1131
1420
conflicts = cook_conflicts(raw_conflicts, tt)
1422
change_reporter = delta.ChangeReporter(
1423
unversioned_filter=working_tree.is_ignored)
1424
delta.report_changes(tt._iter_changes(), change_reporter)
1132
1425
for conflict in conflicts:
1133
1426
warning(conflict)
1134
1427
pp.next_phase()
1136
1429
working_tree.set_merge_modified({})
1431
target_tree.unlock()
1140
1434
return conflicts
1143
def resolve_conflicts(tt, pb=DummyProgress()):
1437
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1439
merge_modified = working_tree.merge_modified()
1440
change_list = target_tree._iter_changes(working_tree,
1441
specific_files=specific_files, pb=pb)
1442
if target_tree.inventory.root is None:
1448
for id_num, (file_id, path, changed_content, versioned, parent, name,
1449
kind, executable) in enumerate(change_list):
1450
if skip_root and file_id[0] is not None and parent[0] is None:
1452
trans_id = tt.trans_id_file_id(file_id)
1455
keep_content = False
1456
if kind[0] == 'file' and (backups or kind[1] is None):
1457
wt_sha1 = working_tree.get_file_sha1(file_id)
1458
if merge_modified.get(file_id) != wt_sha1:
1459
# acquire the basis tree lazyily to prevent the expense
1460
# of accessing it when its not needed ? (Guessing, RBC,
1462
if basis_tree is None:
1463
basis_tree = working_tree.basis_tree()
1464
basis_tree.lock_read()
1465
if file_id in basis_tree:
1466
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1468
elif kind[1] is None and not versioned[1]:
1470
if kind[0] is not None:
1471
if not keep_content:
1472
tt.delete_contents(trans_id)
1473
elif kind[1] is not None:
1474
parent_trans_id = tt.trans_id_file_id(parent[0])
1475
by_parent = tt.by_parent()
1476
backup_name = _get_backup_name(name[0], by_parent,
1477
parent_trans_id, tt)
1478
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1479
new_trans_id = tt.create_path(name[0], parent_trans_id)
1480
if versioned == (True, True):
1481
tt.unversion_file(trans_id)
1482
tt.version_file(file_id, new_trans_id)
1483
# New contents should have the same unix perms as old
1486
trans_id = new_trans_id
1487
if kind[1] == 'directory':
1488
tt.create_directory(trans_id)
1489
elif kind[1] == 'symlink':
1490
tt.create_symlink(target_tree.get_symlink_target(file_id),
1492
elif kind[1] == 'file':
1493
tt.create_file(target_tree.get_file_lines(file_id),
1495
# preserve the execute bit when backing up
1496
if keep_content and executable[0] == executable[1]:
1497
tt.set_executability(executable[1], trans_id)
1499
assert kind[1] is None
1500
if versioned == (False, True):
1501
tt.version_file(file_id, trans_id)
1502
if versioned == (True, False):
1503
tt.unversion_file(trans_id)
1504
if (name[1] is not None and
1505
(name[0] != name[1] or parent[0] != parent[1])):
1507
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1508
if executable[0] != executable[1] and kind[1] == "file":
1509
tt.set_executability(executable[1], trans_id)
1511
if basis_tree is not None:
1515
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1144
1516
"""Make many conflict-resolution attempts, but die if they fail"""
1517
if pass_func is None:
1518
pass_func = conflict_pass
1145
1519
new_conflicts = set()
1147
1521
for n in range(10):