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
97
112
self._new_name = {}
98
113
self._new_parent = {}
99
114
self._new_contents = {}
115
# A mapping of transform ids to their limbo filename
116
self._limbo_files = {}
117
# A mapping of transform ids to a set of the transform ids of children
118
# that their limbo directory has
119
self._limbo_children = {}
120
# Map transform ids to maps of child filename to child transform id
121
self._limbo_children_names = {}
122
# List of transform ids that need to be renamed from limbo into place
123
self._needs_rename = set()
100
124
self._removed_contents = set()
101
125
self._new_executability = {}
126
self._new_reference_revision = {}
102
127
self._new_id = {}
103
128
self._non_present_ids = {}
104
129
self._r_new_id = {}
105
130
self._removed_id = set()
106
131
self._tree_path_ids = {}
107
132
self._tree_id_paths = {}
133
# Cache of realpath results, to speed up canonical_path
108
134
self._realpaths = {}
109
# Cache of realpath results, to speed up canonical_path
135
# Cache of relpath results, to speed up canonical_path
110
136
self._relpaths = {}
111
# Cache of relpath results, to speed up canonical_path
112
137
self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
113
138
self.__done = False
140
self.rename_count = 0
116
142
def __get_root(self):
117
143
return self._new_root
155
187
"""Change the path that is assigned to a transaction id."""
156
188
if trans_id == self._new_root:
157
189
raise CantMoveRoot
190
previous_parent = self._new_parent.get(trans_id)
191
previous_name = self._new_name.get(trans_id)
158
192
self._new_name[trans_id] = name
159
193
self._new_parent[trans_id] = parent
194
if (trans_id in self._limbo_files and
195
trans_id not in self._needs_rename):
196
self._rename_in_limbo([trans_id])
197
self._limbo_children[previous_parent].remove(trans_id)
198
del self._limbo_children_names[previous_parent][previous_name]
200
def _rename_in_limbo(self, trans_ids):
201
"""Fix limbo names so that the right final path is produced.
203
This means we outsmarted ourselves-- we tried to avoid renaming
204
these files later by creating them with their final names in their
205
final parents. But now the previous name or parent is no longer
206
suitable, so we have to rename them.
208
Even for trans_ids that have no new contents, we must remove their
209
entries from _limbo_files, because they are now stale.
211
for trans_id in trans_ids:
212
old_path = self._limbo_files.pop(trans_id)
213
if trans_id not in self._new_contents:
215
new_path = self._limbo_name(trans_id)
216
os.rename(old_path, new_path)
161
218
def adjust_root_path(self, name, parent):
162
219
"""Emulate moving the root by moving all children, instead.
788
def apply(self, no_conflicts=False):
714
789
"""Apply all changes to the inventory and filesystem.
716
791
If filesystem or inventory conflicts are present, MalformedTransform
794
If apply succeeds, finalize is not necessary.
796
:param no_conflicts: if True, the caller guarantees there are no
797
conflicts, so no check is made.
719
conflicts = self.find_conflicts()
720
if len(conflicts) != 0:
721
raise MalformedTransform(conflicts=conflicts)
800
conflicts = self.find_conflicts()
801
if len(conflicts) != 0:
802
raise MalformedTransform(conflicts=conflicts)
723
803
inv = self._tree.inventory
724
805
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
726
807
child_pb.update('Apply phase', 0, 2)
727
self._apply_removals(inv, limbo_inv)
808
self._apply_removals(inv, inventory_delta)
728
809
child_pb.update('Apply phase', 1, 2)
729
modified_paths = self._apply_insertions(inv, limbo_inv)
810
modified_paths = self._apply_insertions(inv, inventory_delta)
731
812
child_pb.finished()
732
self._tree._write_inventory(inv)
813
self._tree.apply_inventory_delta(inventory_delta)
733
814
self.__done = True
735
return _TransformResults(modified_paths)
816
return _TransformResults(modified_paths, self.rename_count)
737
818
def _limbo_name(self, trans_id):
738
819
"""Generate the limbo name of a file"""
739
return pathjoin(self._limbodir, trans_id)
820
limbo_name = self._limbo_files.get(trans_id)
821
if limbo_name is not None:
823
parent = self._new_parent.get(trans_id)
824
# if the parent directory is already in limbo (e.g. when building a
825
# tree), choose a limbo name inside the parent, to reduce further
827
use_direct_path = False
828
if self._new_contents.get(parent) == 'directory':
829
filename = self._new_name.get(trans_id)
830
if filename is not None:
831
if parent not in self._limbo_children:
832
self._limbo_children[parent] = set()
833
self._limbo_children_names[parent] = {}
834
use_direct_path = True
835
# the direct path can only be used if no other file has
836
# already taken this pathname, i.e. if the name is unused, or
837
# if it is already associated with this trans_id.
838
elif (self._limbo_children_names[parent].get(filename)
839
in (trans_id, None)):
840
use_direct_path = True
842
limbo_name = pathjoin(self._limbo_files[parent], filename)
843
self._limbo_children[parent].add(trans_id)
844
self._limbo_children_names[parent][filename] = trans_id
846
limbo_name = pathjoin(self._limbodir, trans_id)
847
self._needs_rename.add(trans_id)
848
self._limbo_files[trans_id] = limbo_name
741
def _apply_removals(self, inv, limbo_inv):
851
def _apply_removals(self, inv, inventory_delta):
742
852
"""Perform tree operations that remove directory/inventory names.
744
854
That is, delete files that are to be deleted, and put any files that
809
921
if trans_id in self._new_id:
811
923
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
924
if trans_id in self._new_reference_revision:
925
new_entry = inventory.TreeReference(
926
self._new_id[trans_id],
927
self._new_name[trans_id],
928
self.final_file_id(self._new_parent[trans_id]),
929
None, self._new_reference_revision[trans_id])
931
new_entry = inventory.make_entry(kind,
932
self.final_name(trans_id),
933
self.final_file_id(self.final_parent(trans_id)),
934
self._new_id[trans_id])
936
if trans_id in self._new_name or trans_id in\
938
trans_id in self._new_executability:
939
file_id = self.final_file_id(trans_id)
940
if file_id is not None:
942
new_entry = entry.copy()
944
if trans_id in self._new_name or trans_id in\
946
if new_entry is not None:
947
new_entry.name = self.final_name(trans_id)
948
parent = self.final_parent(trans_id)
949
parent_id = self.final_file_id(parent)
950
new_entry.parent_id = parent_id
824
952
if trans_id in self._new_executability:
825
self._set_executability(path, inv, trans_id)
953
self._set_executability(path, new_entry, trans_id)
954
if new_entry is not None:
955
if new_entry.file_id in inv:
956
old_path = inv.id2path(new_entry.file_id)
959
inventory_delta.append((old_path, path,
827
963
child_pb.finished()
828
964
return modified_paths
830
def _set_executability(self, path, inv, trans_id):
966
def _set_executability(self, path, entry, trans_id):
831
967
"""Set the executability of versioned files """
832
file_id = inv.path2id(path)
833
968
new_executability = self._new_executability[trans_id]
834
inv[file_id].executable = new_executability
969
entry.executable = new_executability
835
970
if supports_executable():
836
971
abspath = self._tree.abspath(path)
837
972
current_mode = os.stat(abspath).st_mode
898
1033
self.create_symlink(target, trans_id)
1036
def _affected_ids(self):
1037
"""Return the set of transform ids affected by the transform"""
1038
trans_ids = set(self._removed_id)
1039
trans_ids.update(self._new_id.keys())
1040
trans_ids.update(self._removed_contents)
1041
trans_ids.update(self._new_contents.keys())
1042
trans_ids.update(self._new_executability.keys())
1043
trans_ids.update(self._new_name.keys())
1044
trans_ids.update(self._new_parent.keys())
1047
def _get_file_id_maps(self):
1048
"""Return mapping of file_ids to trans_ids in the to and from states"""
1049
trans_ids = self._affected_ids()
1052
# Build up two dicts: trans_ids associated with file ids in the
1053
# FROM state, vs the TO state.
1054
for trans_id in trans_ids:
1055
from_file_id = self.tree_file_id(trans_id)
1056
if from_file_id is not None:
1057
from_trans_ids[from_file_id] = trans_id
1058
to_file_id = self.final_file_id(trans_id)
1059
if to_file_id is not None:
1060
to_trans_ids[to_file_id] = trans_id
1061
return from_trans_ids, to_trans_ids
1063
def _from_file_data(self, from_trans_id, from_versioned, file_id):
1064
"""Get data about a file in the from (tree) state
1066
Return a (name, parent, kind, executable) tuple
1068
from_path = self._tree_id_paths.get(from_trans_id)
1070
# get data from working tree if versioned
1071
from_entry = self._tree.inventory[file_id]
1072
from_name = from_entry.name
1073
from_parent = from_entry.parent_id
1076
if from_path is None:
1077
# File does not exist in FROM state
1081
# File exists, but is not versioned. Have to use path-
1083
from_name = os.path.basename(from_path)
1084
tree_parent = self.get_tree_parent(from_trans_id)
1085
from_parent = self.tree_file_id(tree_parent)
1086
if from_path is not None:
1087
from_kind, from_executable, from_stats = \
1088
self._tree._comparison_data(from_entry, from_path)
1091
from_executable = False
1092
return from_name, from_parent, from_kind, from_executable
1094
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1095
"""Get data about a file in the to (target) state
1097
Return a (name, parent, kind, executable) tuple
1099
to_name = self.final_name(to_trans_id)
1101
to_kind = self.final_kind(to_trans_id)
1104
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1105
if to_trans_id in self._new_executability:
1106
to_executable = self._new_executability[to_trans_id]
1107
elif to_trans_id == from_trans_id:
1108
to_executable = from_executable
1110
to_executable = False
1111
return to_name, to_parent, to_kind, to_executable
1113
def _iter_changes(self):
1114
"""Produce output in the same format as Tree._iter_changes.
1116
Will produce nonsensical results if invoked while inventory/filesystem
1117
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1119
This reads the Transform, but only reproduces changes involving a
1120
file_id. Files that are not versioned in either of the FROM or TO
1121
states are not reflected.
1123
final_paths = FinalPaths(self)
1124
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1126
# Now iterate through all active file_ids
1127
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1129
from_trans_id = from_trans_ids.get(file_id)
1130
# find file ids, and determine versioning state
1131
if from_trans_id is None:
1132
from_versioned = False
1133
from_trans_id = to_trans_ids[file_id]
1135
from_versioned = True
1136
to_trans_id = to_trans_ids.get(file_id)
1137
if to_trans_id is None:
1138
to_versioned = False
1139
to_trans_id = from_trans_id
1143
from_name, from_parent, from_kind, from_executable = \
1144
self._from_file_data(from_trans_id, from_versioned, file_id)
1146
to_name, to_parent, to_kind, to_executable = \
1147
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1149
if not from_versioned:
1152
from_path = self._tree_id_paths.get(from_trans_id)
1153
if not to_versioned:
1156
to_path = final_paths.get_path(to_trans_id)
1157
if from_kind != to_kind:
1159
elif to_kind in ('file', 'symlink') and (
1160
to_trans_id != from_trans_id or
1161
to_trans_id in self._new_contents):
1163
if (not modified and from_versioned == to_versioned and
1164
from_parent==to_parent and from_name == to_name and
1165
from_executable == to_executable):
1167
results.append((file_id, (from_path, to_path), modified,
1168
(from_versioned, to_versioned),
1169
(from_parent, to_parent),
1170
(from_name, to_name),
1171
(from_kind, to_kind),
1172
(from_executable, to_executable)))
1173
return iter(sorted(results, key=lambda x:x[1]))
901
1176
def joinpath(parent, child):
902
1177
"""Join tree-relative paths, handling the tree root specially"""
903
1178
if parent is None or parent == "":
954
1229
it is silently replaced.
955
1230
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
957
assert 2 > len(wt.inventory)
1232
wt.lock_tree_write()
1236
return _build_tree(tree, wt)
1242
def _build_tree(tree, wt):
1243
"""See build_tree."""
1244
if len(wt.inventory) > 1: # more than just a root
1245
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
958
1246
file_trans_id = {}
959
1247
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
960
1248
pp = ProgressPhase("Build phase", 2, top_pb)
1249
if tree.inventory.root is not None:
1250
# This is kind of a hack: we should be altering the root
1251
# as part of the regular tree shape diff logic.
1252
# The conditional test here is to avoid doing an
1253
# expensive operation (flush) every time the root id
1254
# is set within the tree, nor setting the root and thus
1255
# marking the tree as dirty, because we use two different
1256
# idioms here: tree interfaces and inventory interfaces.
1257
if wt.path2id('') != tree.inventory.root.file_id:
1258
wt.set_root_id(tree.inventory.root.file_id)
961
1260
tt = TreeTransform(wt)
1077
1379
executable = tree.is_executable(entry.file_id)
1078
1380
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)
1382
elif kind in ('directory', 'tree-reference'):
1383
trans_id = tt.new_directory(name, parent_id, entry.file_id)
1384
if kind == 'tree-reference':
1385
tt.set_tree_reference(entry.reference_revision, trans_id)
1082
1387
elif kind == 'symlink':
1083
1388
target = tree.get_symlink_target(entry.file_id)
1084
1389
return tt.new_symlink(name, parent_id, target, entry.file_id)
1391
raise errors.BadFileKindError(name, kind)
1086
1393
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1087
1394
"""Create new file contents according to an inventory entry."""
1149
1467
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1468
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1471
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1150
1472
"""Produce a backup-style name that appears to be available"""
1151
1473
def name_gen():
1154
yield "%s.~%d~" % (entry.name, counter)
1476
yield "%s.~%d~" % (name, counter)
1156
for name in name_gen():
1157
if not tt.has_named_child(by_parent, parent_trans_id, name):
1478
for new_name in name_gen():
1479
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1160
1483
def _entry_changes(file_id, entry, working_tree):
1161
1484
"""Determine in which ways the inventory entry has changed.
1184
1507
return has_contents, contents_mod, meta_mod
1187
def revert(working_tree, target_tree, filenames, backups=False,
1188
pb=DummyProgress()):
1510
def revert(working_tree, target_tree, filenames, backups=False,
1511
pb=DummyProgress(), change_reporter=None):
1189
1512
"""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)
1513
target_tree.lock_read()
1194
1514
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]
1516
pp = ProgressPhase("Revert phase", 3, pb)
1518
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1520
merge_modified = _alter_files(working_tree, target_tree, tt,
1521
child_pb, filenames, backups)
1260
1523
child_pb.finished()
1261
1524
pp.next_phase()
1266
1529
child_pb.finished()
1267
1530
conflicts = cook_conflicts(raw_conflicts, tt)
1532
change_reporter = delta._ChangeReporter(
1533
unversioned_filter=working_tree.is_ignored)
1534
delta.report_changes(tt._iter_changes(), change_reporter)
1268
1535
for conflict in conflicts:
1269
1536
warning(conflict)
1270
1537
pp.next_phase()
1272
working_tree.set_merge_modified({})
1539
working_tree.set_merge_modified(merge_modified)
1541
target_tree.unlock()
1276
1544
return conflicts
1547
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1549
merge_modified = working_tree.merge_modified()
1550
change_list = target_tree._iter_changes(working_tree,
1551
specific_files=specific_files, pb=pb)
1552
if target_tree.inventory.root is None:
1558
for id_num, (file_id, path, changed_content, versioned, parent, name,
1559
kind, executable) in enumerate(change_list):
1560
if skip_root and file_id[0] is not None and parent[0] is None:
1562
trans_id = tt.trans_id_file_id(file_id)
1565
keep_content = False
1566
if kind[0] == 'file' and (backups or kind[1] is None):
1567
wt_sha1 = working_tree.get_file_sha1(file_id)
1568
if merge_modified.get(file_id) != wt_sha1:
1569
# acquire the basis tree lazily to prevent the
1570
# expense of accessing it when it's not needed ?
1571
# (Guessing, RBC, 200702)
1572
if basis_tree is None:
1573
basis_tree = working_tree.basis_tree()
1574
basis_tree.lock_read()
1575
if file_id in basis_tree:
1576
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1578
elif kind[1] is None and not versioned[1]:
1580
if kind[0] is not None:
1581
if not keep_content:
1582
tt.delete_contents(trans_id)
1583
elif kind[1] is not None:
1584
parent_trans_id = tt.trans_id_file_id(parent[0])
1585
by_parent = tt.by_parent()
1586
backup_name = _get_backup_name(name[0], by_parent,
1587
parent_trans_id, tt)
1588
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1589
new_trans_id = tt.create_path(name[0], parent_trans_id)
1590
if versioned == (True, True):
1591
tt.unversion_file(trans_id)
1592
tt.version_file(file_id, new_trans_id)
1593
# New contents should have the same unix perms as old
1596
trans_id = new_trans_id
1597
if kind[1] == 'directory':
1598
tt.create_directory(trans_id)
1599
elif kind[1] == 'symlink':
1600
tt.create_symlink(target_tree.get_symlink_target(file_id),
1602
elif kind[1] == 'file':
1603
tt.create_file(target_tree.get_file_lines(file_id),
1605
if basis_tree is None:
1606
basis_tree = working_tree.basis_tree()
1607
basis_tree.lock_read()
1608
new_sha1 = target_tree.get_file_sha1(file_id)
1609
if (file_id in basis_tree and new_sha1 ==
1610
basis_tree.get_file_sha1(file_id)):
1611
if file_id in merge_modified:
1612
del merge_modified[file_id]
1614
merge_modified[file_id] = new_sha1
1616
# preserve the execute bit when backing up
1617
if keep_content and executable[0] == executable[1]:
1618
tt.set_executability(executable[1], trans_id)
1620
assert kind[1] is None
1621
if versioned == (False, True):
1622
tt.version_file(file_id, trans_id)
1623
if versioned == (True, False):
1624
tt.unversion_file(trans_id)
1625
if (name[1] is not None and
1626
(name[0] != name[1] or parent[0] != parent[1])):
1628
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1629
if executable[0] != executable[1] and kind[1] == "file":
1630
tt.set_executability(executable[1], trans_id)
1632
if basis_tree is not None:
1634
return merge_modified
1279
1637
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1280
1638
"""Make many conflict-resolution attempts, but die if they fail"""
1281
1639
if pass_func is None: