19
19
from stat import S_ISREG
21
from bzrlib import bzrdir, errors
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
22
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
23
31
ReusingTransform, NotVersionedError, CantMoveRoot,
24
ExistingLimbo, ImmortalLimbo)
32
ExistingLimbo, ImmortalLimbo, NoFinalPath)
25
33
from bzrlib.inventory import InventoryEntry
26
34
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
28
36
from bzrlib.progress import DummyProgress, ProgressPhase
37
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
29
38
from bzrlib.trace import mutter, warning
30
39
from bzrlib import tree
32
41
import bzrlib.urlutils as urlutils
809
825
if trans_id in self._new_id:
811
827
kind = file_kind(self._tree.abspath(path))
812
inv.add_path(path, kind, self._new_id[trans_id])
813
elif trans_id in self._new_name or trans_id in\
815
entry = limbo_inv.get(trans_id)
816
if entry is not None:
817
entry.name = self.final_name(trans_id)
818
parent_path = os.path.dirname(path)
820
self._tree.inventory.path2id(parent_path)
823
# requires files and inventory entries to be in place
828
if trans_id in self._new_reference_revision:
829
new_entry = inventory.TreeReference(
830
self._new_id[trans_id],
831
self._new_name[trans_id],
832
self.final_file_id(self._new_parent[trans_id]),
833
None, self._new_reference_revision[trans_id])
835
new_entry = inventory.make_entry(kind,
836
self.final_name(trans_id),
837
self.final_file_id(self.final_parent(trans_id)),
838
self._new_id[trans_id])
840
if trans_id in self._new_name or trans_id in\
842
trans_id in self._new_executability:
843
file_id = self.final_file_id(trans_id)
844
if file_id is not None:
846
new_entry = entry.copy()
848
if trans_id in self._new_name or trans_id in\
850
if new_entry is not None:
851
new_entry.name = self.final_name(trans_id)
852
parent = self.final_parent(trans_id)
853
parent_id = self.final_file_id(parent)
854
new_entry.parent_id = parent_id
824
856
if trans_id in self._new_executability:
825
self._set_executability(path, inv, trans_id)
857
self._set_executability(path, new_entry, trans_id)
858
if new_entry is not None:
859
if new_entry.file_id in inv:
860
old_path = inv.id2path(new_entry.file_id)
863
inventory_delta.append((old_path, path,
827
867
child_pb.finished()
828
868
return modified_paths
830
def _set_executability(self, path, inv, trans_id):
870
def _set_executability(self, path, entry, trans_id):
831
871
"""Set the executability of versioned files """
832
file_id = inv.path2id(path)
833
872
new_executability = self._new_executability[trans_id]
834
inv[file_id].executable = new_executability
873
entry.executable = new_executability
835
874
if supports_executable():
836
875
abspath = self._tree.abspath(path)
837
876
current_mode = os.stat(abspath).st_mode
898
937
self.create_symlink(target, trans_id)
940
def _affected_ids(self):
941
"""Return the set of transform ids affected by the transform"""
942
trans_ids = set(self._removed_id)
943
trans_ids.update(self._new_id.keys())
944
trans_ids.update(self._removed_contents)
945
trans_ids.update(self._new_contents.keys())
946
trans_ids.update(self._new_executability.keys())
947
trans_ids.update(self._new_name.keys())
948
trans_ids.update(self._new_parent.keys())
951
def _get_file_id_maps(self):
952
"""Return mapping of file_ids to trans_ids in the to and from states"""
953
trans_ids = self._affected_ids()
956
# Build up two dicts: trans_ids associated with file ids in the
957
# FROM state, vs the TO state.
958
for trans_id in trans_ids:
959
from_file_id = self.tree_file_id(trans_id)
960
if from_file_id is not None:
961
from_trans_ids[from_file_id] = trans_id
962
to_file_id = self.final_file_id(trans_id)
963
if to_file_id is not None:
964
to_trans_ids[to_file_id] = trans_id
965
return from_trans_ids, to_trans_ids
967
def _from_file_data(self, from_trans_id, from_versioned, file_id):
968
"""Get data about a file in the from (tree) state
970
Return a (name, parent, kind, executable) tuple
972
from_path = self._tree_id_paths.get(from_trans_id)
974
# get data from working tree if versioned
975
from_entry = self._tree.inventory[file_id]
976
from_name = from_entry.name
977
from_parent = from_entry.parent_id
980
if from_path is None:
981
# File does not exist in FROM state
985
# File exists, but is not versioned. Have to use path-
987
from_name = os.path.basename(from_path)
988
tree_parent = self.get_tree_parent(from_trans_id)
989
from_parent = self.tree_file_id(tree_parent)
990
if from_path is not None:
991
from_kind, from_executable, from_stats = \
992
self._tree._comparison_data(from_entry, from_path)
995
from_executable = False
996
return from_name, from_parent, from_kind, from_executable
998
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
999
"""Get data about a file in the to (target) state
1001
Return a (name, parent, kind, executable) tuple
1003
to_name = self.final_name(to_trans_id)
1005
to_kind = self.final_kind(to_trans_id)
1008
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1009
if to_trans_id in self._new_executability:
1010
to_executable = self._new_executability[to_trans_id]
1011
elif to_trans_id == from_trans_id:
1012
to_executable = from_executable
1014
to_executable = False
1015
return to_name, to_parent, to_kind, to_executable
1017
def _iter_changes(self):
1018
"""Produce output in the same format as Tree._iter_changes.
1020
Will produce nonsensical results if invoked while inventory/filesystem
1021
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1023
This reads the Transform, but only reproduces changes involving a
1024
file_id. Files that are not versioned in either of the FROM or TO
1025
states are not reflected.
1027
final_paths = FinalPaths(self)
1028
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1030
# Now iterate through all active file_ids
1031
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1033
from_trans_id = from_trans_ids.get(file_id)
1034
# find file ids, and determine versioning state
1035
if from_trans_id is None:
1036
from_versioned = False
1037
from_trans_id = to_trans_ids[file_id]
1039
from_versioned = True
1040
to_trans_id = to_trans_ids.get(file_id)
1041
if to_trans_id is None:
1042
to_versioned = False
1043
to_trans_id = from_trans_id
1047
from_name, from_parent, from_kind, from_executable = \
1048
self._from_file_data(from_trans_id, from_versioned, file_id)
1050
to_name, to_parent, to_kind, to_executable = \
1051
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1053
if not from_versioned:
1056
from_path = self._tree_id_paths.get(from_trans_id)
1057
if not to_versioned:
1060
to_path = final_paths.get_path(to_trans_id)
1061
if from_kind != to_kind:
1063
elif to_kind in ('file', 'symlink') and (
1064
to_trans_id != from_trans_id or
1065
to_trans_id in self._new_contents):
1067
if (not modified and from_versioned == to_versioned and
1068
from_parent==to_parent and from_name == to_name and
1069
from_executable == to_executable):
1071
results.append((file_id, (from_path, to_path), modified,
1072
(from_versioned, to_versioned),
1073
(from_parent, to_parent),
1074
(from_name, to_name),
1075
(from_kind, to_kind),
1076
(from_executable, to_executable)))
1077
return iter(sorted(results, key=lambda x:x[1]))
901
1080
def joinpath(parent, child):
902
1081
"""Join tree-relative paths, handling the tree root specially"""
903
1082
if parent is None or parent == "":
954
1133
it is silently replaced.
955
1134
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
957
assert 2 > len(wt.inventory)
1136
wt.lock_tree_write()
1140
return _build_tree(tree, wt)
1146
def _build_tree(tree, wt):
1147
"""See build_tree."""
1148
if len(wt.inventory) > 1: # more than just a root
1149
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
958
1150
file_trans_id = {}
959
1151
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
960
1152
pp = ProgressPhase("Build phase", 2, top_pb)
1153
if tree.inventory.root is not None:
1154
# this is kindof a hack: we should be altering the root
1155
# as partof the regular tree shape diff logic.
1156
# the conditional test hereis to avoid doing an
1157
# expensive operation (flush) every time the root id
1158
# is set within the tree, nor setting the root and thus
1159
# marking the tree as dirty, because we use two different
1160
# idioms here: tree interfaces and inventory interfaces.
1161
if wt.path2id('') != tree.inventory.root.file_id:
1162
wt.set_root_id(tree.inventory.root.file_id)
961
1164
tt = TreeTransform(wt)
1077
1282
executable = tree.is_executable(entry.file_id)
1078
1283
return tt.new_file(name, parent_id, contents, entry.file_id,
1080
elif kind == 'directory':
1081
return tt.new_directory(name, parent_id, entry.file_id)
1285
elif kind in ('directory', 'tree-reference'):
1286
trans_id = tt.new_directory(name, parent_id, entry.file_id)
1287
if kind == 'tree-reference':
1288
tt.set_tree_reference(entry.reference_revision, trans_id)
1082
1290
elif kind == 'symlink':
1083
1291
target = tree.get_symlink_target(entry.file_id)
1084
1292
return tt.new_symlink(name, parent_id, target, entry.file_id)
1294
raise errors.BadFileKindError(name, kind)
1086
1296
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1087
1297
"""Create new file contents according to an inventory entry."""
1149
1370
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1371
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1374
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1150
1375
"""Produce a backup-style name that appears to be available"""
1151
1376
def name_gen():
1154
yield "%s.~%d~" % (entry.name, counter)
1379
yield "%s.~%d~" % (name, counter)
1156
for name in name_gen():
1157
if not tt.has_named_child(by_parent, parent_trans_id, name):
1381
for new_name in name_gen():
1382
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1160
1386
def _entry_changes(file_id, entry, working_tree):
1161
1387
"""Determine in which ways the inventory entry has changed.
1184
1410
return has_contents, contents_mod, meta_mod
1187
def revert(working_tree, target_tree, filenames, backups=False,
1188
pb=DummyProgress()):
1413
def revert(working_tree, target_tree, filenames, backups=False,
1414
pb=DummyProgress(), change_reporter=None):
1189
1415
"""Revert a working tree's contents to those of a target tree."""
1190
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1191
def interesting(file_id):
1192
return interesting_ids is None or (file_id in interesting_ids)
1416
target_tree.lock_read()
1194
1417
tt = TreeTransform(working_tree, pb)
1196
merge_modified = working_tree.merge_modified()
1198
def trans_id_file_id(file_id):
1200
return trans_id[file_id]
1202
return tt.trans_id_tree_file_id(file_id)
1204
pp = ProgressPhase("Revert phase", 4, pb)
1206
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1208
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1210
by_parent = tt.by_parent()
1211
for id_num, file_id in enumerate(sorted_interesting):
1212
child_pb.update("Reverting file", id_num+1,
1213
len(sorted_interesting))
1214
if file_id not in working_tree.inventory:
1215
entry = target_tree.inventory[file_id]
1216
parent_id = trans_id_file_id(entry.parent_id)
1217
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1218
trans_id[file_id] = e_trans_id
1220
backup_this = backups
1221
if file_id in merge_modified:
1223
del merge_modified[file_id]
1224
change_entry(tt, file_id, working_tree, target_tree,
1225
trans_id_file_id, backup_this, trans_id,
1230
wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1231
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1233
for id_num, file_id in enumerate(wt_interesting):
1234
child_pb.update("New file check", id_num+1,
1235
len(sorted_interesting))
1236
if file_id not in target_tree:
1237
trans_id = tt.trans_id_tree_file_id(file_id)
1238
tt.unversion_file(trans_id)
1240
file_kind = working_tree.kind(file_id)
1243
if file_kind != 'file' and file_kind is not None:
1244
keep_contents = False
1245
delete_merge_modified = False
1247
if (file_id in merge_modified and
1248
merge_modified[file_id] ==
1249
working_tree.get_file_sha1(file_id)):
1250
keep_contents = False
1251
delete_merge_modified = True
1253
keep_contents = True
1254
delete_merge_modified = False
1255
if not keep_contents:
1256
tt.delete_contents(trans_id)
1257
if delete_merge_modified:
1258
del merge_modified[file_id]
1419
pp = ProgressPhase("Revert phase", 3, pb)
1421
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1423
merge_modified = _alter_files(working_tree, target_tree, tt,
1424
child_pb, filenames, backups)
1260
1426
child_pb.finished()
1261
1427
pp.next_phase()
1266
1432
child_pb.finished()
1267
1433
conflicts = cook_conflicts(raw_conflicts, tt)
1435
change_reporter = delta._ChangeReporter(
1436
unversioned_filter=working_tree.is_ignored)
1437
delta.report_changes(tt._iter_changes(), change_reporter)
1268
1438
for conflict in conflicts:
1269
1439
warning(conflict)
1270
1440
pp.next_phase()
1272
working_tree.set_merge_modified({})
1442
working_tree.set_merge_modified(merge_modified)
1444
target_tree.unlock()
1276
1447
return conflicts
1450
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1452
merge_modified = working_tree.merge_modified()
1453
change_list = target_tree._iter_changes(working_tree,
1454
specific_files=specific_files, pb=pb)
1455
if target_tree.inventory.root is None:
1461
for id_num, (file_id, path, changed_content, versioned, parent, name,
1462
kind, executable) in enumerate(change_list):
1463
if skip_root and file_id[0] is not None and parent[0] is None:
1465
trans_id = tt.trans_id_file_id(file_id)
1468
keep_content = False
1469
if kind[0] == 'file' and (backups or kind[1] is None):
1470
wt_sha1 = working_tree.get_file_sha1(file_id)
1471
if merge_modified.get(file_id) != wt_sha1:
1472
# acquire the basis tree lazyily to prevent the expense
1473
# of accessing it when its not needed ? (Guessing, RBC,
1475
if basis_tree is None:
1476
basis_tree = working_tree.basis_tree()
1477
basis_tree.lock_read()
1478
if file_id in basis_tree:
1479
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1481
elif kind[1] is None and not versioned[1]:
1483
if kind[0] is not None:
1484
if not keep_content:
1485
tt.delete_contents(trans_id)
1486
elif kind[1] is not None:
1487
parent_trans_id = tt.trans_id_file_id(parent[0])
1488
by_parent = tt.by_parent()
1489
backup_name = _get_backup_name(name[0], by_parent,
1490
parent_trans_id, tt)
1491
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1492
new_trans_id = tt.create_path(name[0], parent_trans_id)
1493
if versioned == (True, True):
1494
tt.unversion_file(trans_id)
1495
tt.version_file(file_id, new_trans_id)
1496
# New contents should have the same unix perms as old
1499
trans_id = new_trans_id
1500
if kind[1] == 'directory':
1501
tt.create_directory(trans_id)
1502
elif kind[1] == 'symlink':
1503
tt.create_symlink(target_tree.get_symlink_target(file_id),
1505
elif kind[1] == 'file':
1506
tt.create_file(target_tree.get_file_lines(file_id),
1508
if basis_tree is None:
1509
basis_tree = working_tree.basis_tree()
1510
basis_tree.lock_read()
1511
new_sha1 = target_tree.get_file_sha1(file_id)
1512
if (file_id in basis_tree and new_sha1 ==
1513
basis_tree.get_file_sha1(file_id)):
1514
if file_id in merge_modified:
1515
del merge_modified[file_id]
1517
merge_modified[file_id] = new_sha1
1519
# preserve the execute bit when backing up
1520
if keep_content and executable[0] == executable[1]:
1521
tt.set_executability(executable[1], trans_id)
1523
assert kind[1] is None
1524
if versioned == (False, True):
1525
tt.version_file(file_id, trans_id)
1526
if versioned == (True, False):
1527
tt.unversion_file(trans_id)
1528
if (name[1] is not None and
1529
(name[0] != name[1] or parent[0] != parent[1])):
1531
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1532
if executable[0] != executable[1] and kind[1] == "file":
1533
tt.set_executability(executable[1], trans_id)
1535
if basis_tree is not None:
1537
return merge_modified
1279
1540
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1280
1541
"""Make many conflict-resolution attempts, but die if they fail"""
1281
1542
if pass_func is None: