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
33
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
28
34
from bzrlib.trace import mutter, warning
35
from bzrlib import tree
37
import bzrlib.urlutils as urlutils
32
40
ROOT_PARENT = "root-parent"
211
224
def canonical_path(self, path):
212
225
"""Get the canonical tree-relative path"""
213
226
# 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))
227
abs = self._tree.abspath(path)
228
if abs in self._relpaths:
229
return self._relpaths[abs]
230
dirname, basename = os.path.split(abs)
231
if dirname not in self._realpaths:
232
self._realpaths[dirname] = os.path.realpath(dirname)
233
dirname = self._realpaths[dirname]
234
abs = pathjoin(dirname, basename)
235
if dirname in self._relpaths:
236
relpath = pathjoin(self._relpaths[dirname], basename)
237
relpath = relpath.rstrip('/\\')
239
relpath = self._tree.relpath(abs)
240
self._relpaths[abs] = relpath
218
243
def trans_id_tree_path(self, path):
219
244
"""Determine (and maybe set) the transaction ID for a tree path."""
861
908
self.create_symlink(target, trans_id)
911
def _affected_ids(self):
912
"""Return the set of transform ids affected by the transform"""
913
trans_ids = set(self._removed_id)
914
trans_ids.update(self._new_id.keys())
915
trans_ids.update(self._removed_contents)
916
trans_ids.update(self._new_contents.keys())
917
trans_ids.update(self._new_executability.keys())
918
trans_ids.update(self._new_name.keys())
919
trans_ids.update(self._new_parent.keys())
922
def _get_file_id_maps(self):
923
"""Return mapping of file_ids to trans_ids in the to and from states"""
924
trans_ids = self._affected_ids()
927
# Build up two dicts: trans_ids associated with file ids in the
928
# FROM state, vs the TO state.
929
for trans_id in trans_ids:
930
from_file_id = self.tree_file_id(trans_id)
931
if from_file_id is not None:
932
from_trans_ids[from_file_id] = trans_id
933
to_file_id = self.final_file_id(trans_id)
934
if to_file_id is not None:
935
to_trans_ids[to_file_id] = trans_id
936
return from_trans_ids, to_trans_ids
938
def _from_file_data(self, from_trans_id, from_versioned, file_id):
939
"""Get data about a file in the from (tree) state
941
Return a (name, parent, kind, executable) tuple
943
from_path = self._tree_id_paths.get(from_trans_id)
945
# get data from working tree if versioned
946
from_entry = self._tree.inventory[file_id]
947
from_name = from_entry.name
948
from_parent = from_entry.parent_id
951
if from_path is None:
952
# File does not exist in FROM state
956
# File exists, but is not versioned. Have to use path-
958
from_name = os.path.basename(from_path)
959
tree_parent = self.get_tree_parent(from_trans_id)
960
from_parent = self.tree_file_id(tree_parent)
961
if from_path is not None:
962
from_kind, from_executable, from_stats = \
963
self._tree._comparison_data(from_entry, from_path)
966
from_executable = False
967
return from_name, from_parent, from_kind, from_executable
969
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
970
"""Get data about a file in the to (target) state
972
Return a (name, parent, kind, executable) tuple
974
to_name = self.final_name(to_trans_id)
976
to_kind = self.final_kind(to_trans_id)
979
to_parent = self.final_file_id(self.final_parent(to_trans_id))
980
if to_trans_id in self._new_executability:
981
to_executable = self._new_executability[to_trans_id]
982
elif to_trans_id == from_trans_id:
983
to_executable = from_executable
985
to_executable = False
986
return to_name, to_parent, to_kind, to_executable
988
def _iter_changes(self):
989
"""Produce output in the same format as Tree._iter_changes.
991
Will produce nonsensical results if invoked while inventory/filesystem
992
conflicts (as reported by TreeTransform.find_conflicts()) are present.
994
This reads the Transform, but only reproduces changes involving a
995
file_id. Files that are not versioned in either of the FROM or TO
996
states are not reflected.
998
final_paths = FinalPaths(self)
999
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1001
# Now iterate through all active file_ids
1002
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1004
from_trans_id = from_trans_ids.get(file_id)
1005
# find file ids, and determine versioning state
1006
if from_trans_id is None:
1007
from_versioned = False
1008
from_trans_id = to_trans_ids[file_id]
1010
from_versioned = True
1011
to_trans_id = to_trans_ids.get(file_id)
1012
if to_trans_id is None:
1013
to_versioned = False
1014
to_trans_id = from_trans_id
1018
from_name, from_parent, from_kind, from_executable = \
1019
self._from_file_data(from_trans_id, from_versioned, file_id)
1021
to_name, to_parent, to_kind, to_executable = \
1022
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1024
to_path = final_paths.get_path(to_trans_id)
1025
if from_kind != to_kind:
1027
elif to_kind in ('file' or 'symlink') and (
1028
to_trans_id != from_trans_id or
1029
to_trans_id in self._new_contents):
1031
if (not modified and from_versioned == to_versioned and
1032
from_parent==to_parent and from_name == to_name and
1033
from_executable == to_executable):
1035
results.append((file_id, to_path, modified,
1036
(from_versioned, to_versioned),
1037
(from_parent, to_parent),
1038
(from_name, to_name),
1039
(from_kind, to_kind),
1040
(from_executable, to_executable)))
1041
return iter(sorted(results, key=lambda x:x[1]))
864
1044
def joinpath(parent, child):
865
1045
"""Join tree-relative paths, handling the tree root specially"""
866
1046
if parent is None or parent == "":
902
1082
file_ids.sort(key=tree.id2path)
905
1086
def build_tree(tree, wt):
906
"""Create working tree for a branch, using a Transaction."""
1087
"""Create working tree for a branch, using a TreeTransform.
1089
This function should be used on empty trees, having a tree root at most.
1090
(see merge and revert functionality for working with existing trees)
1092
Existing files are handled like so:
1094
- Existing bzrdirs take precedence over creating new items. They are
1095
created as '%s.diverted' % name.
1096
- Otherwise, if the content on disk matches the content we are building,
1097
it is silently replaced.
1098
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1100
wt.lock_tree_write()
1104
return _build_tree(tree, wt)
1110
def _build_tree(tree, wt):
1111
"""See build_tree."""
1112
if len(wt.inventory) > 1: # more than just a root
1113
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
907
1114
file_trans_id = {}
908
1115
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
909
1116
pp = ProgressPhase("Build phase", 2, top_pb)
1117
# if tree.inventory.root is not None:
1118
# wt.set_root_id(tree.inventory.root.file_id)
910
1119
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)
1123
file_trans_id[wt.get_root_id()] = \
1124
tt.trans_id_tree_file_id(wt.get_root_id())
915
1125
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]
1127
for num, (tree_path, entry) in \
1128
enumerate(tree.inventory.iter_entries_by_dir()):
1129
pb.update("Building tree", num, len(tree.inventory))
920
1130
if entry.parent_id is None:
1133
file_id = entry.file_id
1134
target_path = wt.abspath(tree_path)
1136
kind = file_kind(target_path)
1140
if kind == "directory":
1142
bzrdir.BzrDir.open(target_path)
1143
except errors.NotBranchError:
1147
if (file_id not in divert and
1148
_content_match(tree, entry, file_id, kind,
1150
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1151
if kind == 'directory':
922
1153
if entry.parent_id not in file_trans_id:
923
1154
raise repr(entry.parent_id)
924
1155
parent_id = file_trans_id[entry.parent_id]
925
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1156
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1159
new_trans_id = file_trans_id[file_id]
1160
old_parent = tt.trans_id_tree_path(tree_path)
1161
_reparent_children(tt, old_parent, new_trans_id)
1165
divert_trans = set(file_trans_id[f] for f in divert)
1166
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1167
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1168
conflicts = cook_conflicts(raw_conflicts, tt)
1169
for conflict in conflicts:
1172
wt.add_conflicts(conflicts)
1173
except errors.UnsupportedOperation:
933
1178
top_pb.finished()
1181
def _reparent_children(tt, old_parent, new_parent):
1182
for child in tt.iter_tree_children(old_parent):
1183
tt.adjust_path(tt.final_name(child), new_parent, child)
1186
def _content_match(tree, entry, file_id, kind, target_path):
1187
if entry.kind != kind:
1189
if entry.kind == "directory":
1191
if entry.kind == "file":
1192
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1194
elif entry.kind == "symlink":
1195
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1200
def resolve_checkout(tt, conflicts, divert):
1201
new_conflicts = set()
1202
for c_type, conflict in ((c[0], c) for c in conflicts):
1203
# Anything but a 'duplicate' would indicate programmer error
1204
assert c_type == 'duplicate', c_type
1205
# Now figure out which is new and which is old
1206
if tt.new_contents(conflict[1]):
1207
new_file = conflict[1]
1208
old_file = conflict[2]
1210
new_file = conflict[2]
1211
old_file = conflict[1]
1213
# We should only get here if the conflict wasn't completely
1215
final_parent = tt.final_parent(old_file)
1216
if new_file in divert:
1217
new_name = tt.final_name(old_file)+'.diverted'
1218
tt.adjust_path(new_name, final_parent, new_file)
1219
new_conflicts.add((c_type, 'Diverted to',
1220
new_file, old_file))
1222
new_name = tt.final_name(old_file)+'.moved'
1223
tt.adjust_path(new_name, final_parent, old_file)
1224
new_conflicts.add((c_type, 'Moved existing file to',
1225
old_file, new_file))
1226
return new_conflicts
935
1229
def new_by_entry(tt, entry, parent_id, tree):
936
1230
"""Create a new file according to its inventory entry"""
937
1231
name = entry.name
1025
1318
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1319
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1322
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1026
1323
"""Produce a backup-style name that appears to be available"""
1027
1324
def name_gen():
1030
yield "%s.~%d~" % (entry.name, counter)
1327
yield "%s.~%d~" % (name, counter)
1032
for name in name_gen():
1033
if not tt.has_named_child(by_parent, parent_trans_id, name):
1329
for new_name in name_gen():
1330
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1036
1334
def _entry_changes(file_id, entry, working_tree):
1037
1335
"""Determine in which ways the inventory entry has changed.
1065
1358
return has_contents, contents_mod, meta_mod
1068
def revert(working_tree, target_tree, filenames, backups=False,
1069
pb=DummyProgress()):
1361
def revert(working_tree, target_tree, filenames, backups=False,
1362
pb=DummyProgress(), change_reporter=None):
1070
1363
"""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
1364
target_tree.lock_read()
1075
1365
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]
1367
pp = ProgressPhase("Revert phase", 3, pb)
1369
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1371
_alter_files(working_tree, target_tree, tt, child_pb,
1124
1374
child_pb.finished()
1125
1375
pp.next_phase()
1130
1380
child_pb.finished()
1131
1381
conflicts = cook_conflicts(raw_conflicts, tt)
1383
change_reporter = delta.ChangeReporter(working_tree.inventory)
1384
delta.report_changes(tt._iter_changes(), change_reporter)
1132
1385
for conflict in conflicts:
1133
1386
warning(conflict)
1134
1387
pp.next_phase()
1136
1389
working_tree.set_merge_modified({})
1391
target_tree.unlock()
1140
1394
return conflicts
1143
def resolve_conflicts(tt, pb=DummyProgress()):
1397
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1399
merge_modified = working_tree.merge_modified()
1400
change_list = target_tree._iter_changes(working_tree,
1401
specific_files=specific_files, pb=pb)
1402
if target_tree.inventory.root is None:
1408
for id_num, (file_id, path, changed_content, versioned, parent, name,
1409
kind, executable) in enumerate(change_list):
1410
if skip_root and file_id[0] is not None and parent[0] is None:
1412
trans_id = tt.trans_id_file_id(file_id)
1415
keep_content = False
1416
if kind[0] == 'file' and (backups or kind[1] is None):
1417
wt_sha1 = working_tree.get_file_sha1(file_id)
1418
if merge_modified.get(file_id) != wt_sha1:
1419
# acquire the basis tree lazyily to prevent the expense
1420
# of accessing it when its not needed ? (Guessing, RBC,
1422
if basis_tree is None:
1423
basis_tree = working_tree.basis_tree()
1424
basis_tree.lock_read()
1425
if file_id in basis_tree:
1426
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1428
elif kind[1] is None and not versioned[1]:
1430
if kind[0] is not None:
1431
if not keep_content:
1432
tt.delete_contents(trans_id)
1433
elif kind[1] is not None:
1434
parent_trans_id = tt.trans_id_file_id(parent[0])
1435
by_parent = tt.by_parent()
1436
backup_name = _get_backup_name(name[0], by_parent,
1437
parent_trans_id, tt)
1438
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1439
new_trans_id = tt.create_path(name[0], parent_trans_id)
1440
if versioned == (True, True):
1441
tt.unversion_file(trans_id)
1442
tt.version_file(file_id, new_trans_id)
1443
# New contents should have the same unix perms as old
1446
trans_id = new_trans_id
1447
if kind[1] == 'directory':
1448
tt.create_directory(trans_id)
1449
elif kind[1] == 'symlink':
1450
tt.create_symlink(target_tree.get_symlink_target(file_id),
1452
elif kind[1] == 'file':
1453
tt.create_file(target_tree.get_file_lines(file_id),
1455
# preserve the execute bit when backing up
1456
if keep_content and executable[0] == executable[1]:
1457
tt.set_executability(executable[1], trans_id)
1459
assert kind[1] is None
1460
if versioned == (False, True):
1461
tt.version_file(file_id, trans_id)
1462
if versioned == (True, False):
1463
tt.unversion_file(trans_id)
1464
if (name[1] is not None and
1465
(name[0] != name[1] or parent[0] != parent[1])):
1467
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1468
if executable[0] != executable[1] and kind[1] == "file":
1469
tt.set_executability(executable[1], trans_id)
1471
if basis_tree is not None:
1475
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1144
1476
"""Make many conflict-resolution attempts, but die if they fail"""
1477
if pass_func is None:
1478
pass_func = conflict_pass
1145
1479
new_conflicts = set()
1147
1481
for n in range(10):
1188
1522
trans_id = conflict[1]
1190
1524
tt.cancel_deletion(trans_id)
1191
new_conflicts.add((c_type, 'Not deleting', trans_id))
1525
new_conflicts.add(('deleting parent', 'Not deleting',
1192
1527
except KeyError:
1193
1528
tt.create_directory(trans_id)
1194
new_conflicts.add((c_type, 'Created directory.', trans_id))
1529
new_conflicts.add((c_type, 'Created directory', trans_id))
1195
1530
elif c_type == 'unversioned parent':
1196
1531
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1197
1532
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1198
1533
return new_conflicts
1200
1536
def cook_conflicts(raw_conflicts, tt):
1201
1537
"""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
1538
from bzrlib.conflicts import Conflict
1539
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1540
return sorted(conflict_iter, key=Conflict.sort_key)
1210
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1212
1543
def iter_cook_conflicts(raw_conflicts, tt):
1213
1544
from bzrlib.conflicts import Conflict