187
153
"""Change the path that is assigned to a transaction id."""
188
154
if trans_id == self._new_root:
189
155
raise CantMoveRoot
190
previous_parent = self._new_parent.get(trans_id)
191
previous_name = self._new_name.get(trans_id)
192
156
self._new_name[trans_id] = name
193
157
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)
218
159
def adjust_root_path(self, name, parent):
219
160
"""Emulate moving the root by moving all children, instead.
789
700
If filesystem or inventory conflicts are present, MalformedTransform
792
If apply succeeds, finalize is not necessary.
794
703
conflicts = self.find_conflicts()
795
704
if len(conflicts) != 0:
796
705
raise MalformedTransform(conflicts=conflicts)
797
707
inv = self._tree.inventory
799
708
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
801
710
child_pb.update('Apply phase', 0, 2)
802
self._apply_removals(inv, inventory_delta)
711
self._apply_removals(inv, limbo_inv)
803
712
child_pb.update('Apply phase', 1, 2)
804
modified_paths = self._apply_insertions(inv, inventory_delta)
713
modified_paths = self._apply_insertions(inv, limbo_inv)
806
715
child_pb.finished()
807
self._tree.apply_inventory_delta(inventory_delta)
716
self._tree._write_inventory(inv)
808
717
self.__done = True
810
return _TransformResults(modified_paths, self.rename_count)
719
return _TransformResults(modified_paths)
812
721
def _limbo_name(self, trans_id):
813
722
"""Generate the limbo name of a file"""
814
limbo_name = self._limbo_files.get(trans_id)
815
if limbo_name is not None:
817
parent = self._new_parent.get(trans_id)
818
# if the parent directory is already in limbo (e.g. when building a
819
# tree), choose a limbo name inside the parent, to reduce further
821
use_direct_path = False
822
if self._new_contents.get(parent) == 'directory':
823
filename = self._new_name.get(trans_id)
824
if filename is not None:
825
if parent not in self._limbo_children:
826
self._limbo_children[parent] = set()
827
self._limbo_children_names[parent] = {}
828
use_direct_path = True
829
# the direct path can only be used if no other file has
830
# already taken this pathname, i.e. if the name is unused, or
831
# if it is already associated with this trans_id.
832
elif (self._limbo_children_names[parent].get(filename)
833
in (trans_id, None)):
834
use_direct_path = True
836
limbo_name = pathjoin(self._limbo_files[parent], filename)
837
self._limbo_children[parent].add(trans_id)
838
self._limbo_children_names[parent][filename] = trans_id
840
limbo_name = pathjoin(self._limbodir, trans_id)
841
self._needs_rename.add(trans_id)
842
self._limbo_files[trans_id] = limbo_name
723
return pathjoin(self._limbodir, trans_id)
845
def _apply_removals(self, inv, inventory_delta):
725
def _apply_removals(self, inv, limbo_inv):
846
726
"""Perform tree operations that remove directory/inventory names.
848
728
That is, delete files that are to be deleted, and put any files that
915
793
if trans_id in self._new_id:
917
795
kind = file_kind(self._tree.abspath(path))
918
if trans_id in self._new_reference_revision:
919
new_entry = inventory.TreeReference(
920
self._new_id[trans_id],
921
self._new_name[trans_id],
922
self.final_file_id(self._new_parent[trans_id]),
923
None, self._new_reference_revision[trans_id])
925
new_entry = inventory.make_entry(kind,
926
self.final_name(trans_id),
927
self.final_file_id(self.final_parent(trans_id)),
928
self._new_id[trans_id])
930
if trans_id in self._new_name or trans_id in\
932
trans_id in self._new_executability:
933
file_id = self.final_file_id(trans_id)
934
if file_id is not None:
936
new_entry = entry.copy()
938
if trans_id in self._new_name or trans_id in\
940
if new_entry is not None:
941
new_entry.name = self.final_name(trans_id)
942
parent = self.final_parent(trans_id)
943
parent_id = self.final_file_id(parent)
944
new_entry.parent_id = parent_id
796
inv.add_path(path, kind, self._new_id[trans_id])
797
elif trans_id in self._new_name or trans_id in\
799
entry = limbo_inv.get(trans_id)
800
if entry is not None:
801
entry.name = self.final_name(trans_id)
802
parent_path = os.path.dirname(path)
804
self._tree.inventory.path2id(parent_path)
807
# requires files and inventory entries to be in place
946
808
if trans_id in self._new_executability:
947
self._set_executability(path, new_entry, trans_id)
948
if new_entry is not None:
949
if new_entry.file_id in inv:
950
old_path = inv.id2path(new_entry.file_id)
953
inventory_delta.append((old_path, path,
809
self._set_executability(path, inv, trans_id)
957
811
child_pb.finished()
958
812
return modified_paths
960
def _set_executability(self, path, entry, trans_id):
814
def _set_executability(self, path, inv, trans_id):
961
815
"""Set the executability of versioned files """
816
file_id = inv.path2id(path)
962
817
new_executability = self._new_executability[trans_id]
963
entry.executable = new_executability
818
inv[file_id].executable = new_executability
964
819
if supports_executable():
965
820
abspath = self._tree.abspath(path)
966
821
current_mode = os.stat(abspath).st_mode
1027
882
self.create_symlink(target, trans_id)
1030
def _affected_ids(self):
1031
"""Return the set of transform ids affected by the transform"""
1032
trans_ids = set(self._removed_id)
1033
trans_ids.update(self._new_id.keys())
1034
trans_ids.update(self._removed_contents)
1035
trans_ids.update(self._new_contents.keys())
1036
trans_ids.update(self._new_executability.keys())
1037
trans_ids.update(self._new_name.keys())
1038
trans_ids.update(self._new_parent.keys())
1041
def _get_file_id_maps(self):
1042
"""Return mapping of file_ids to trans_ids in the to and from states"""
1043
trans_ids = self._affected_ids()
1046
# Build up two dicts: trans_ids associated with file ids in the
1047
# FROM state, vs the TO state.
1048
for trans_id in trans_ids:
1049
from_file_id = self.tree_file_id(trans_id)
1050
if from_file_id is not None:
1051
from_trans_ids[from_file_id] = trans_id
1052
to_file_id = self.final_file_id(trans_id)
1053
if to_file_id is not None:
1054
to_trans_ids[to_file_id] = trans_id
1055
return from_trans_ids, to_trans_ids
1057
def _from_file_data(self, from_trans_id, from_versioned, file_id):
1058
"""Get data about a file in the from (tree) state
1060
Return a (name, parent, kind, executable) tuple
1062
from_path = self._tree_id_paths.get(from_trans_id)
1064
# get data from working tree if versioned
1065
from_entry = self._tree.inventory[file_id]
1066
from_name = from_entry.name
1067
from_parent = from_entry.parent_id
1070
if from_path is None:
1071
# File does not exist in FROM state
1075
# File exists, but is not versioned. Have to use path-
1077
from_name = os.path.basename(from_path)
1078
tree_parent = self.get_tree_parent(from_trans_id)
1079
from_parent = self.tree_file_id(tree_parent)
1080
if from_path is not None:
1081
from_kind, from_executable, from_stats = \
1082
self._tree._comparison_data(from_entry, from_path)
1085
from_executable = False
1086
return from_name, from_parent, from_kind, from_executable
1088
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1089
"""Get data about a file in the to (target) state
1091
Return a (name, parent, kind, executable) tuple
1093
to_name = self.final_name(to_trans_id)
1095
to_kind = self.final_kind(to_trans_id)
1098
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1099
if to_trans_id in self._new_executability:
1100
to_executable = self._new_executability[to_trans_id]
1101
elif to_trans_id == from_trans_id:
1102
to_executable = from_executable
1104
to_executable = False
1105
return to_name, to_parent, to_kind, to_executable
1107
def _iter_changes(self):
1108
"""Produce output in the same format as Tree._iter_changes.
1110
Will produce nonsensical results if invoked while inventory/filesystem
1111
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1113
This reads the Transform, but only reproduces changes involving a
1114
file_id. Files that are not versioned in either of the FROM or TO
1115
states are not reflected.
1117
final_paths = FinalPaths(self)
1118
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1120
# Now iterate through all active file_ids
1121
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1123
from_trans_id = from_trans_ids.get(file_id)
1124
# find file ids, and determine versioning state
1125
if from_trans_id is None:
1126
from_versioned = False
1127
from_trans_id = to_trans_ids[file_id]
1129
from_versioned = True
1130
to_trans_id = to_trans_ids.get(file_id)
1131
if to_trans_id is None:
1132
to_versioned = False
1133
to_trans_id = from_trans_id
1137
from_name, from_parent, from_kind, from_executable = \
1138
self._from_file_data(from_trans_id, from_versioned, file_id)
1140
to_name, to_parent, to_kind, to_executable = \
1141
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1143
if not from_versioned:
1146
from_path = self._tree_id_paths.get(from_trans_id)
1147
if not to_versioned:
1150
to_path = final_paths.get_path(to_trans_id)
1151
if from_kind != to_kind:
1153
elif to_kind in ('file', 'symlink') and (
1154
to_trans_id != from_trans_id or
1155
to_trans_id in self._new_contents):
1157
if (not modified and from_versioned == to_versioned and
1158
from_parent==to_parent and from_name == to_name and
1159
from_executable == to_executable):
1161
results.append((file_id, (from_path, to_path), modified,
1162
(from_versioned, to_versioned),
1163
(from_parent, to_parent),
1164
(from_name, to_name),
1165
(from_kind, to_kind),
1166
(from_executable, to_executable)))
1167
return iter(sorted(results, key=lambda x:x[1]))
1170
885
def joinpath(parent, child):
1171
886
"""Join tree-relative paths, handling the tree root specially"""
1172
887
if parent is None or parent == "":
1208
923
file_ids.sort(key=tree.id2path)
1212
926
def build_tree(tree, wt):
1213
"""Create working tree for a branch, using a TreeTransform.
1215
This function should be used on empty trees, having a tree root at most.
1216
(see merge and revert functionality for working with existing trees)
1218
Existing files are handled like so:
1220
- Existing bzrdirs take precedence over creating new items. They are
1221
created as '%s.diverted' % name.
1222
- Otherwise, if the content on disk matches the content we are building,
1223
it is silently replaced.
1224
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1226
wt.lock_tree_write()
1230
return _build_tree(tree, wt)
1236
def _build_tree(tree, wt):
1237
"""See build_tree."""
1238
if len(wt.inventory) > 1: # more than just a root
1239
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
927
"""Create working tree for a branch, using a Transaction."""
1240
928
file_trans_id = {}
1241
929
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1242
930
pp = ProgressPhase("Build phase", 2, top_pb)
1243
if tree.inventory.root is not None:
1244
# This is kind of a hack: we should be altering the root
1245
# as part of the regular tree shape diff logic.
1246
# The conditional test here is to avoid doing an
1247
# expensive operation (flush) every time the root id
1248
# is set within the tree, nor setting the root and thus
1249
# marking the tree as dirty, because we use two different
1250
# idioms here: tree interfaces and inventory interfaces.
1251
if wt.path2id('') != tree.inventory.root.file_id:
1252
wt.set_root_id(tree.inventory.root.file_id)
1254
931
tt = TreeTransform(wt)
1258
file_trans_id[wt.get_root_id()] = \
1259
tt.trans_id_tree_file_id(wt.get_root_id())
934
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
935
file_ids = topology_sorted_ids(tree)
1260
936
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1262
for num, (tree_path, entry) in \
1263
enumerate(tree.inventory.iter_entries_by_dir()):
1264
pb.update("Building tree", num, len(tree.inventory))
938
for num, file_id in enumerate(file_ids):
939
pb.update("Building tree", num, len(file_ids))
940
entry = tree.inventory[file_id]
1265
941
if entry.parent_id is None:
1268
file_id = entry.file_id
1269
target_path = wt.abspath(tree_path)
1271
kind = file_kind(target_path)
1275
if kind == "directory":
1277
bzrdir.BzrDir.open(target_path)
1278
except errors.NotBranchError:
1282
if (file_id not in divert and
1283
_content_match(tree, entry, file_id, kind,
1285
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1286
if kind == 'directory':
1288
943
if entry.parent_id not in file_trans_id:
1289
raise AssertionError(
1290
'entry %s parent id %r is not in file_trans_id %r'
1291
% (entry, entry.parent_id, file_trans_id))
944
raise repr(entry.parent_id)
1292
945
parent_id = file_trans_id[entry.parent_id]
1293
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
946
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1296
new_trans_id = file_trans_id[file_id]
1297
old_parent = tt.trans_id_tree_path(tree_path)
1298
_reparent_children(tt, old_parent, new_trans_id)
1302
divert_trans = set(file_trans_id[f] for f in divert)
1303
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1304
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1305
conflicts = cook_conflicts(raw_conflicts, tt)
1306
for conflict in conflicts:
1309
wt.add_conflicts(conflicts)
1310
except errors.UnsupportedOperation:
1315
954
top_pb.finished()
1319
def _reparent_children(tt, old_parent, new_parent):
1320
for child in tt.iter_tree_children(old_parent):
1321
tt.adjust_path(tt.final_name(child), new_parent, child)
1324
def _content_match(tree, entry, file_id, kind, target_path):
1325
if entry.kind != kind:
1327
if entry.kind == "directory":
1329
if entry.kind == "file":
1330
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1332
elif entry.kind == "symlink":
1333
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1338
def resolve_checkout(tt, conflicts, divert):
1339
new_conflicts = set()
1340
for c_type, conflict in ((c[0], c) for c in conflicts):
1341
# Anything but a 'duplicate' would indicate programmer error
1342
assert c_type == 'duplicate', c_type
1343
# Now figure out which is new and which is old
1344
if tt.new_contents(conflict[1]):
1345
new_file = conflict[1]
1346
old_file = conflict[2]
1348
new_file = conflict[2]
1349
old_file = conflict[1]
1351
# We should only get here if the conflict wasn't completely
1353
final_parent = tt.final_parent(old_file)
1354
if new_file in divert:
1355
new_name = tt.final_name(old_file)+'.diverted'
1356
tt.adjust_path(new_name, final_parent, new_file)
1357
new_conflicts.add((c_type, 'Diverted to',
1358
new_file, old_file))
1360
new_name = tt.final_name(old_file)+'.moved'
1361
tt.adjust_path(new_name, final_parent, old_file)
1362
new_conflicts.add((c_type, 'Moved existing file to',
1363
old_file, new_file))
1364
return new_conflicts
1367
956
def new_by_entry(tt, entry, parent_id, tree):
1368
957
"""Create a new file according to its inventory entry"""
1373
962
executable = tree.is_executable(entry.file_id)
1374
963
return tt.new_file(name, parent_id, contents, entry.file_id,
1376
elif kind in ('directory', 'tree-reference'):
1377
trans_id = tt.new_directory(name, parent_id, entry.file_id)
1378
if kind == 'tree-reference':
1379
tt.set_tree_reference(entry.reference_revision, trans_id)
965
elif kind == 'directory':
966
return tt.new_directory(name, parent_id, entry.file_id)
1381
967
elif kind == 'symlink':
1382
968
target = tree.get_symlink_target(entry.file_id)
1383
969
return tt.new_symlink(name, parent_id, target, entry.file_id)
1385
raise errors.BadFileKindError(name, kind)
1387
971
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1388
972
"""Create new file contents according to an inventory entry."""
1389
973
if entry.kind == "file":
1391
975
lines = tree.get_file(entry.file_id).readlines()
1392
976
tt.create_file(lines, trans_id, mode_id=mode_id)
1393
977
elif entry.kind == "symlink":
1501
1086
return has_contents, contents_mod, meta_mod
1504
def revert(working_tree, target_tree, filenames, backups=False,
1505
pb=DummyProgress(), change_reporter=None):
1089
def revert(working_tree, target_tree, filenames, backups=False,
1090
pb=DummyProgress()):
1506
1091
"""Revert a working tree's contents to those of a target tree."""
1507
target_tree.lock_read()
1092
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1093
def interesting(file_id):
1094
return interesting_ids is None or file_id in interesting_ids
1508
1096
tt = TreeTransform(working_tree, pb)
1510
pp = ProgressPhase("Revert phase", 3, pb)
1512
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1514
merge_modified = _alter_files(working_tree, target_tree, tt,
1515
child_pb, filenames, backups)
1098
merge_modified = working_tree.merge_modified()
1100
def trans_id_file_id(file_id):
1102
return trans_id[file_id]
1104
return tt.trans_id_tree_file_id(file_id)
1106
pp = ProgressPhase("Revert phase", 4, pb)
1108
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1110
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1112
by_parent = tt.by_parent()
1113
for id_num, file_id in enumerate(sorted_interesting):
1114
child_pb.update("Reverting file", id_num+1,
1115
len(sorted_interesting))
1116
if file_id not in working_tree.inventory:
1117
entry = target_tree.inventory[file_id]
1118
parent_id = trans_id_file_id(entry.parent_id)
1119
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1120
trans_id[file_id] = e_trans_id
1122
backup_this = backups
1123
if file_id in merge_modified:
1125
del merge_modified[file_id]
1126
change_entry(tt, file_id, working_tree, target_tree,
1127
trans_id_file_id, backup_this, trans_id,
1132
wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1133
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1135
for id_num, file_id in enumerate(wt_interesting):
1136
child_pb.update("New file check", id_num+1,
1137
len(sorted_interesting))
1138
if file_id not in target_tree:
1139
trans_id = tt.trans_id_tree_file_id(file_id)
1140
tt.unversion_file(trans_id)
1141
if file_id in merge_modified:
1142
tt.delete_contents(trans_id)
1143
del merge_modified[file_id]
1517
1145
child_pb.finished()
1518
1146
pp.next_phase()
1523
1151
child_pb.finished()
1524
1152
conflicts = cook_conflicts(raw_conflicts, tt)
1526
change_reporter = delta._ChangeReporter(
1527
unversioned_filter=working_tree.is_ignored)
1528
delta.report_changes(tt._iter_changes(), change_reporter)
1529
1153
for conflict in conflicts:
1530
1154
warning(conflict)
1531
1155
pp.next_phase()
1533
working_tree.set_merge_modified(merge_modified)
1157
working_tree.set_merge_modified({})
1535
target_tree.unlock()
1538
1161
return conflicts
1541
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1543
merge_modified = working_tree.merge_modified()
1544
change_list = target_tree._iter_changes(working_tree,
1545
specific_files=specific_files, pb=pb)
1546
if target_tree.inventory.root is None:
1552
for id_num, (file_id, path, changed_content, versioned, parent, name,
1553
kind, executable) in enumerate(change_list):
1554
if skip_root and file_id[0] is not None and parent[0] is None:
1556
trans_id = tt.trans_id_file_id(file_id)
1559
keep_content = False
1560
if kind[0] == 'file' and (backups or kind[1] is None):
1561
wt_sha1 = working_tree.get_file_sha1(file_id)
1562
if merge_modified.get(file_id) != wt_sha1:
1563
# acquire the basis tree lazily to prevent the
1564
# expense of accessing it when it's not needed ?
1565
# (Guessing, RBC, 200702)
1566
if basis_tree is None:
1567
basis_tree = working_tree.basis_tree()
1568
basis_tree.lock_read()
1569
if file_id in basis_tree:
1570
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1572
elif kind[1] is None and not versioned[1]:
1574
if kind[0] is not None:
1575
if not keep_content:
1576
tt.delete_contents(trans_id)
1577
elif kind[1] is not None:
1578
parent_trans_id = tt.trans_id_file_id(parent[0])
1579
by_parent = tt.by_parent()
1580
backup_name = _get_backup_name(name[0], by_parent,
1581
parent_trans_id, tt)
1582
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1583
new_trans_id = tt.create_path(name[0], parent_trans_id)
1584
if versioned == (True, True):
1585
tt.unversion_file(trans_id)
1586
tt.version_file(file_id, new_trans_id)
1587
# New contents should have the same unix perms as old
1590
trans_id = new_trans_id
1591
if kind[1] == 'directory':
1592
tt.create_directory(trans_id)
1593
elif kind[1] == 'symlink':
1594
tt.create_symlink(target_tree.get_symlink_target(file_id),
1596
elif kind[1] == 'file':
1597
tt.create_file(target_tree.get_file_lines(file_id),
1599
if basis_tree is None:
1600
basis_tree = working_tree.basis_tree()
1601
basis_tree.lock_read()
1602
new_sha1 = target_tree.get_file_sha1(file_id)
1603
if (file_id in basis_tree and new_sha1 ==
1604
basis_tree.get_file_sha1(file_id)):
1605
if file_id in merge_modified:
1606
del merge_modified[file_id]
1608
merge_modified[file_id] = new_sha1
1610
# preserve the execute bit when backing up
1611
if keep_content and executable[0] == executable[1]:
1612
tt.set_executability(executable[1], trans_id)
1614
assert kind[1] is None
1615
if versioned == (False, True):
1616
tt.version_file(file_id, trans_id)
1617
if versioned == (True, False):
1618
tt.unversion_file(trans_id)
1619
if (name[1] is not None and
1620
(name[0] != name[1] or parent[0] != parent[1])):
1622
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1623
if executable[0] != executable[1] and kind[1] == "file":
1624
tt.set_executability(executable[1], trans_id)
1626
if basis_tree is not None:
1628
return merge_modified
1631
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1164
def resolve_conflicts(tt, pb=DummyProgress()):
1632
1165
"""Make many conflict-resolution attempts, but die if they fail"""
1633
if pass_func is None:
1634
pass_func = conflict_pass
1635
1166
new_conflicts = set()
1637
1168
for n in range(10):