19
19
from stat import S_ISREG
21
from bzrlib import bzrdir, errors
22
from bzrlib.lazy_import import lazy_import
23
lazy_import(globals(), """
24
from bzrlib import delta
21
26
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
22
27
ReusingTransform, NotVersionedError, CantMoveRoot,
23
ExistingLimbo, ImmortalLimbo)
28
ExistingLimbo, ImmortalLimbo, NoFinalPath)
24
29
from bzrlib.inventory import InventoryEntry
25
30
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
27
32
from bzrlib.progress import DummyProgress, ProgressPhase
28
33
from bzrlib.trace import mutter, warning
34
from bzrlib import tree
36
import bzrlib.urlutils as urlutils
32
39
ROOT_PARENT = "root-parent"
211
223
def canonical_path(self, path):
212
224
"""Get the canonical tree-relative path"""
213
225
# 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))
226
abs = self._tree.abspath(path)
227
if abs in self._relpaths:
228
return self._relpaths[abs]
229
dirname, basename = os.path.split(abs)
230
if dirname not in self._realpaths:
231
self._realpaths[dirname] = os.path.realpath(dirname)
232
dirname = self._realpaths[dirname]
233
abs = pathjoin(dirname, basename)
234
if dirname in self._relpaths:
235
relpath = pathjoin(self._relpaths[dirname], basename)
236
relpath = relpath.rstrip('/\\')
238
relpath = self._tree.relpath(abs)
239
self._relpaths[abs] = relpath
218
242
def trans_id_tree_path(self, path):
219
243
"""Determine (and maybe set) the transaction ID for a tree path."""
861
907
self.create_symlink(target, trans_id)
910
def _affected_ids(self):
911
"""Return the set of transform ids affected by the transform"""
912
trans_ids = set(self._removed_id)
913
trans_ids.update(self._new_id.keys())
914
trans_ids.update(self._removed_contents)
915
trans_ids.update(self._new_contents.keys())
916
trans_ids.update(self._new_executability.keys())
917
trans_ids.update(self._new_name.keys())
918
trans_ids.update(self._new_parent.keys())
921
def _get_file_id_maps(self):
922
"""Return mapping of file_ids to trans_ids in the to and from states"""
923
trans_ids = self._affected_ids()
926
# Build up two dicts: trans_ids associated with file ids in the
927
# FROM state, vs the TO state.
928
for trans_id in trans_ids:
929
from_file_id = self.tree_file_id(trans_id)
930
if from_file_id is not None:
931
from_trans_ids[from_file_id] = trans_id
932
to_file_id = self.final_file_id(trans_id)
933
if to_file_id is not None:
934
to_trans_ids[to_file_id] = trans_id
935
return from_trans_ids, to_trans_ids
937
def _from_file_data(self, from_trans_id, from_versioned, file_id):
938
"""Get data about a file in the from (tree) state
940
Return a (name, parent, kind, executable) tuple
942
from_path = self._tree_id_paths.get(from_trans_id)
944
# get data from working tree if versioned
945
from_entry = self._tree.inventory[file_id]
946
from_name = from_entry.name
947
from_parent = from_entry.parent_id
950
if from_path is None:
951
# File does not exist in FROM state
955
# File exists, but is not versioned. Have to use path-
957
from_name = os.path.basename(from_path)
958
tree_parent = self.get_tree_parent(from_trans_id)
959
from_parent = self.tree_file_id(tree_parent)
960
if from_path is not None:
961
from_kind, from_executable, from_stats = \
962
self._tree._comparison_data(from_entry, from_path)
965
from_executable = False
966
return from_name, from_parent, from_kind, from_executable
968
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
969
"""Get data about a file in the to (target) state
971
Return a (name, parent, kind, executable) tuple
973
to_name = self.final_name(to_trans_id)
975
to_kind = self.final_kind(to_trans_id)
978
to_parent = self.final_file_id(self.final_parent(to_trans_id))
979
if to_trans_id in self._new_executability:
980
to_executable = self._new_executability[to_trans_id]
981
elif to_trans_id == from_trans_id:
982
to_executable = from_executable
984
to_executable = False
985
return to_name, to_parent, to_kind, to_executable
987
def _iter_changes(self):
988
"""Produce output in the same format as Tree._iter_changes.
990
Will produce nonsensical results if invoked while inventory/filesystem
991
conflicts (as reported by TreeTransform.find_conflicts()) are present.
993
This reads the Transform, but only reproduces changes involving a
994
file_id. Files that are not versioned in either of the FROM or TO
995
states are not reflected.
997
final_paths = FinalPaths(self)
998
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1000
# Now iterate through all active file_ids
1001
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1003
from_trans_id = from_trans_ids.get(file_id)
1004
# find file ids, and determine versioning state
1005
if from_trans_id is None:
1006
from_versioned = False
1007
from_trans_id = to_trans_ids[file_id]
1009
from_versioned = True
1010
to_trans_id = to_trans_ids.get(file_id)
1011
if to_trans_id is None:
1012
to_versioned = False
1013
to_trans_id = from_trans_id
1017
from_name, from_parent, from_kind, from_executable = \
1018
self._from_file_data(from_trans_id, from_versioned, file_id)
1020
to_name, to_parent, to_kind, to_executable = \
1021
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1023
to_path = final_paths.get_path(to_trans_id)
1024
if from_kind != to_kind:
1026
elif to_kind in ('file' or 'symlink') and (
1027
to_trans_id != from_trans_id or
1028
to_trans_id in self._new_contents):
1030
if (not modified and from_versioned == to_versioned and
1031
from_parent==to_parent and from_name == to_name and
1032
from_executable == to_executable):
1034
results.append((file_id, to_path, modified,
1035
(from_versioned, to_versioned),
1036
(from_parent, to_parent),
1037
(from_name, to_name),
1038
(from_kind, to_kind),
1039
(from_executable, to_executable)))
1040
return iter(sorted(results, key=lambda x:x[1]))
864
1043
def joinpath(parent, child):
865
1044
"""Join tree-relative paths, handling the tree root specially"""
866
1045
if parent is None or parent == "":
902
1081
file_ids.sort(key=tree.id2path)
905
1085
def build_tree(tree, wt):
906
"""Create working tree for a branch, using a Transaction."""
1086
"""Create working tree for a branch, using a TreeTransform.
1088
This function should be used on empty trees, having a tree root at most.
1089
(see merge and revert functionality for working with existing trees)
1091
Existing files are handled like so:
1093
- Existing bzrdirs take precedence over creating new items. They are
1094
created as '%s.diverted' % name.
1095
- Otherwise, if the content on disk matches the content we are building,
1096
it is silently replaced.
1097
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1099
if len(wt.inventory) > 1: # more than just a root
1100
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
907
1101
file_trans_id = {}
908
1102
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
1103
pp = ProgressPhase("Build phase", 2, top_pb)
1104
if tree.inventory.root is not None:
1105
wt.set_root_id(tree.inventory.root.file_id)
910
1106
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)
1110
file_trans_id[wt.get_root_id()] = \
1111
tt.trans_id_tree_file_id(wt.get_root_id())
915
1112
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]
1114
for num, (tree_path, entry) in \
1115
enumerate(tree.inventory.iter_entries_by_dir()):
1116
pb.update("Building tree", num, len(tree.inventory))
920
1117
if entry.parent_id is None:
1120
file_id = entry.file_id
1121
target_path = wt.abspath(tree_path)
1123
kind = file_kind(target_path)
1127
if kind == "directory":
1129
bzrdir.BzrDir.open(target_path)
1130
except errors.NotBranchError:
1134
if (file_id not in divert and
1135
_content_match(tree, entry, file_id, kind,
1137
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1138
if kind == 'directory':
922
1140
if entry.parent_id not in file_trans_id:
923
1141
raise repr(entry.parent_id)
924
1142
parent_id = file_trans_id[entry.parent_id]
925
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1143
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1146
new_trans_id = file_trans_id[file_id]
1147
old_parent = tt.trans_id_tree_path(tree_path)
1148
_reparent_children(tt, old_parent, new_trans_id)
1152
divert_trans = set(file_trans_id[f] for f in divert)
1153
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1154
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1155
conflicts = cook_conflicts(raw_conflicts, tt)
1156
for conflict in conflicts:
1159
wt.add_conflicts(conflicts)
1160
except errors.UnsupportedOperation:
933
1165
top_pb.finished()
1168
def _reparent_children(tt, old_parent, new_parent):
1169
for child in tt.iter_tree_children(old_parent):
1170
tt.adjust_path(tt.final_name(child), new_parent, child)
1173
def _content_match(tree, entry, file_id, kind, target_path):
1174
if entry.kind != kind:
1176
if entry.kind == "directory":
1178
if entry.kind == "file":
1179
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1181
elif entry.kind == "symlink":
1182
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1187
def resolve_checkout(tt, conflicts, divert):
1188
new_conflicts = set()
1189
for c_type, conflict in ((c[0], c) for c in conflicts):
1190
# Anything but a 'duplicate' would indicate programmer error
1191
assert c_type == 'duplicate', c_type
1192
# Now figure out which is new and which is old
1193
if tt.new_contents(conflict[1]):
1194
new_file = conflict[1]
1195
old_file = conflict[2]
1197
new_file = conflict[2]
1198
old_file = conflict[1]
1200
# We should only get here if the conflict wasn't completely
1202
final_parent = tt.final_parent(old_file)
1203
if new_file in divert:
1204
new_name = tt.final_name(old_file)+'.diverted'
1205
tt.adjust_path(new_name, final_parent, new_file)
1206
new_conflicts.add((c_type, 'Diverted to',
1207
new_file, old_file))
1209
new_name = tt.final_name(old_file)+'.moved'
1210
tt.adjust_path(new_name, final_parent, old_file)
1211
new_conflicts.add((c_type, 'Moved existing file to',
1212
old_file, new_file))
1213
return new_conflicts
935
1216
def new_by_entry(tt, entry, parent_id, tree):
936
1217
"""Create a new file according to its inventory entry"""
937
1218
name = entry.name
1025
1294
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1295
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1298
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1026
1299
"""Produce a backup-style name that appears to be available"""
1027
1300
def name_gen():
1030
yield "%s.~%d~" % (entry.name, counter)
1303
yield "%s.~%d~" % (name, counter)
1032
for name in name_gen():
1033
if not tt.has_named_child(by_parent, parent_trans_id, name):
1305
for new_name in name_gen():
1306
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1036
1310
def _entry_changes(file_id, entry, working_tree):
1037
1311
"""Determine in which ways the inventory entry has changed.
1068
1337
def revert(working_tree, target_tree, filenames, backups=False,
1069
pb=DummyProgress()):
1338
pb=DummyProgress(), change_reporter=None):
1070
1339
"""Revert a working tree's contents to those of a target tree."""
1071
1340
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
1341
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]
1343
pp = ProgressPhase("Revert phase", 3, pb)
1345
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1347
_alter_files(working_tree, target_tree, tt, child_pb,
1348
interesting_ids, backups)
1124
1350
child_pb.finished()
1125
1351
pp.next_phase()
1140
1369
return conflicts
1143
def resolve_conflicts(tt, pb=DummyProgress()):
1372
def _alter_files(working_tree, target_tree, tt, pb, interesting_ids,
1374
merge_modified = working_tree.merge_modified()
1375
change_list = target_tree._iter_changes(working_tree,
1376
specific_file_ids=interesting_ids, pb=pb)
1377
if target_tree.inventory.root is None:
1382
for id_num, (file_id, path, changed_content, versioned, parent, name,
1383
kind, executable) in enumerate(change_list):
1384
if skip_root and file_id[0] is not None and parent[0] is None:
1386
trans_id = tt.trans_id_file_id(file_id)
1389
keep_content = False
1390
if kind[0] == 'file' and (backups or kind[1] is None):
1391
wt_sha1 = working_tree.get_file_sha1(file_id)
1392
if merge_modified.get(file_id) != wt_sha1:
1393
if basis_tree is None:
1394
basis_tree = working_tree.basis_tree()
1395
if file_id in basis_tree:
1396
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1398
elif kind[1] is None and not versioned[1]:
1400
if kind[0] is not None:
1401
if not keep_content:
1402
tt.delete_contents(trans_id)
1403
elif kind[1] is not None:
1404
parent_trans_id = tt.trans_id_file_id(parent[0])
1405
by_parent = tt.by_parent()
1406
backup_name = _get_backup_name(name[0], by_parent,
1407
parent_trans_id, tt)
1408
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1409
new_trans_id = tt.create_path(name[0], parent_trans_id)
1410
if versioned == (True, True):
1411
tt.unversion_file(trans_id)
1412
tt.version_file(file_id, new_trans_id)
1413
# New contents should have the same unix perms as old
1416
trans_id = new_trans_id
1417
if kind[1] == 'directory':
1418
tt.create_directory(trans_id)
1419
elif kind[1] == 'symlink':
1420
tt.create_symlink(target_tree.get_symlink_target(file_id),
1422
elif kind[1] == 'file':
1423
tt.create_file(target_tree.get_file_lines(file_id),
1425
# preserve the execute bit when backing up
1426
if keep_content and executable[0] == executable[1]:
1427
tt.set_executability(executable[1], trans_id)
1429
assert kind[1] is None
1430
if versioned == (False, True):
1431
tt.version_file(file_id, trans_id)
1432
if versioned == (True, False):
1433
tt.unversion_file(trans_id)
1434
if (name[1] is not None and
1435
(name[0] != name[1] or parent[0] != parent[1])):
1436
tt.adjust_path(name[1], tt.trans_id_file_id(parent[1]), trans_id)
1437
if executable[0] != executable[1] and kind[1] == "file":
1438
tt.set_executability(executable[1], trans_id)
1441
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1144
1442
"""Make many conflict-resolution attempts, but die if they fail"""
1443
if pass_func is None:
1444
pass_func = conflict_pass
1145
1445
new_conflicts = set()
1147
1447
for n in range(10):
1188
1488
trans_id = conflict[1]
1190
1490
tt.cancel_deletion(trans_id)
1191
new_conflicts.add((c_type, 'Not deleting', trans_id))
1491
new_conflicts.add(('deleting parent', 'Not deleting',
1192
1493
except KeyError:
1193
1494
tt.create_directory(trans_id)
1194
new_conflicts.add((c_type, 'Created directory.', trans_id))
1495
new_conflicts.add((c_type, 'Created directory', trans_id))
1195
1496
elif c_type == 'unversioned parent':
1196
1497
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1197
1498
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1198
1499
return new_conflicts
1200
1502
def cook_conflicts(raw_conflicts, tt):
1201
1503
"""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
1504
from bzrlib.conflicts import Conflict
1505
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1506
return sorted(conflict_iter, key=Conflict.sort_key)
1210
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1212
1509
def iter_cook_conflicts(raw_conflicts, tt):
1213
1510
from bzrlib.conflicts import Conflict