19
19
from stat import S_ISREG
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
30
21
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
31
22
ReusingTransform, NotVersionedError, CantMoveRoot,
32
ExistingLimbo, ImmortalLimbo, NoFinalPath)
23
ExistingLimbo, ImmortalLimbo)
33
24
from bzrlib.inventory import InventoryEntry
34
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
36
from bzrlib.progress import DummyProgress, ProgressPhase
37
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen, \
25
from bzrlib.osutils import file_kind, supports_executable, pathjoin
26
from bzrlib.progress import DummyProgress
39
27
from bzrlib.trace import mutter, warning
40
from bzrlib import tree
42
import bzrlib.urlutils as urlutils
45
30
ROOT_PARENT = "root-parent"
188
139
"""Change the path that is assigned to a transaction id."""
189
140
if trans_id == self._new_root:
190
141
raise CantMoveRoot
191
previous_parent = self._new_parent.get(trans_id)
192
previous_name = self._new_name.get(trans_id)
193
142
self._new_name[trans_id] = name
194
143
self._new_parent[trans_id] = parent
195
if (trans_id in self._limbo_files and
196
trans_id not in self._needs_rename):
197
self._rename_in_limbo([trans_id])
198
self._limbo_children[previous_parent].remove(trans_id)
199
del self._limbo_children_names[previous_parent][previous_name]
201
def _rename_in_limbo(self, trans_ids):
202
"""Fix limbo names so that the right final path is produced.
204
This means we outsmarted ourselves-- we tried to avoid renaming
205
these files later by creating them with their final names in their
206
final parents. But now the previous name or parent is no longer
207
suitable, so we have to rename them.
209
Even for trans_ids that have no new contents, we must remove their
210
entries from _limbo_files, because they are now stale.
212
for trans_id in trans_ids:
213
old_path = self._limbo_files.pop(trans_id)
214
if trans_id not in self._new_contents:
216
new_path = self._limbo_name(trans_id)
217
os.rename(old_path, new_path)
219
145
def adjust_root_path(self, name, parent):
220
146
"""Emulate moving the root by moving all children, instead.
277
203
def canonical_path(self, path):
278
204
"""Get the canonical tree-relative path"""
279
205
# don't follow final symlinks
280
abs = self._tree.abspath(path)
281
if abs in self._relpaths:
282
return self._relpaths[abs]
283
dirname, basename = os.path.split(abs)
284
if dirname not in self._realpaths:
285
self._realpaths[dirname] = os.path.realpath(dirname)
286
dirname = self._realpaths[dirname]
287
abs = pathjoin(dirname, basename)
288
if dirname in self._relpaths:
289
relpath = pathjoin(self._relpaths[dirname], basename)
290
relpath = relpath.rstrip('/\\')
292
relpath = self._tree.relpath(abs)
293
self._relpaths[abs] = relpath
206
dirname, basename = os.path.split(self._tree.abspath(path))
207
dirname = os.path.realpath(dirname)
208
return self._tree.relpath(pathjoin(dirname, basename))
296
210
def trans_id_tree_path(self, path):
297
211
"""Determine (and maybe set) the transaction ID for a tree path."""
789
def apply(self, no_conflicts=False):
790
652
"""Apply all changes to the inventory and filesystem.
792
654
If filesystem or inventory conflicts are present, MalformedTransform
795
If apply succeeds, finalize is not necessary.
797
:param no_conflicts: if True, the caller guarantees there are no
798
conflicts, so no check is made.
801
conflicts = self.find_conflicts()
802
if len(conflicts) != 0:
803
raise MalformedTransform(conflicts=conflicts)
657
conflicts = self.find_conflicts()
658
if len(conflicts) != 0:
659
raise MalformedTransform(conflicts=conflicts)
804
661
inv = self._tree.inventory
806
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
808
child_pb.update('Apply phase', 0, 2)
809
self._apply_removals(inv, inventory_delta)
810
child_pb.update('Apply phase', 1, 2)
811
modified_paths = self._apply_insertions(inv, inventory_delta)
814
self._tree.apply_inventory_delta(inventory_delta)
662
self._apply_removals(inv, limbo_inv)
663
self._apply_insertions(inv, limbo_inv)
664
self._tree._write_inventory(inv)
815
665
self.__done = True
817
return _TransformResults(modified_paths, self.rename_count)
819
668
def _limbo_name(self, trans_id):
820
669
"""Generate the limbo name of a file"""
821
limbo_name = self._limbo_files.get(trans_id)
822
if limbo_name is not None:
824
parent = self._new_parent.get(trans_id)
825
# if the parent directory is already in limbo (e.g. when building a
826
# tree), choose a limbo name inside the parent, to reduce further
828
use_direct_path = False
829
if self._new_contents.get(parent) == 'directory':
830
filename = self._new_name.get(trans_id)
831
if filename is not None:
832
if parent not in self._limbo_children:
833
self._limbo_children[parent] = set()
834
self._limbo_children_names[parent] = {}
835
use_direct_path = True
836
# the direct path can only be used if no other file has
837
# already taken this pathname, i.e. if the name is unused, or
838
# if it is already associated with this trans_id.
839
elif (self._limbo_children_names[parent].get(filename)
840
in (trans_id, None)):
841
use_direct_path = True
843
limbo_name = pathjoin(self._limbo_files[parent], filename)
844
self._limbo_children[parent].add(trans_id)
845
self._limbo_children_names[parent][filename] = trans_id
847
limbo_name = pathjoin(self._limbodir, trans_id)
848
self._needs_rename.add(trans_id)
849
self._limbo_files[trans_id] = limbo_name
670
return pathjoin(self._limbodir, trans_id)
852
def _apply_removals(self, inv, inventory_delta):
672
def _apply_removals(self, inv, limbo_inv):
853
673
"""Perform tree operations that remove directory/inventory names.
855
675
That is, delete files that are to be deleted, and put any files that
859
679
tree_paths = list(self._tree_path_ids.iteritems())
860
680
tree_paths.sort(reverse=True)
861
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
863
for num, data in enumerate(tree_paths):
864
path, trans_id = data
865
child_pb.update('removing file', num, len(tree_paths))
866
full_path = self._tree.abspath(path)
867
if trans_id in self._removed_contents:
868
delete_any(full_path)
869
elif trans_id in self._new_name or trans_id in \
872
os.rename(full_path, self._limbo_name(trans_id))
874
if e.errno != errno.ENOENT:
877
self.rename_count += 1
878
if trans_id in self._removed_id:
879
if trans_id == self._new_root:
880
file_id = self._tree.inventory.root.file_id
882
file_id = self.tree_file_id(trans_id)
883
assert file_id is not None
884
inventory_delta.append((path, None, file_id, None))
681
for num, data in enumerate(tree_paths):
682
path, trans_id = data
683
self._pb.update('removing file', num+1, len(tree_paths))
684
full_path = self._tree.abspath(path)
685
if trans_id in self._removed_contents:
686
self.delete_any(full_path)
687
elif trans_id in self._new_name or trans_id in self._new_parent:
689
os.rename(full_path, self._limbo_name(trans_id))
691
if e.errno != errno.ENOENT:
693
if trans_id in self._removed_id:
694
if trans_id == self._new_root:
695
file_id = self._tree.inventory.root.file_id
697
file_id = self.tree_file_id(trans_id)
699
elif trans_id in self._new_name or trans_id in self._new_parent:
700
file_id = self.tree_file_id(trans_id)
701
if file_id is not None:
702
limbo_inv[trans_id] = inv[file_id]
888
def _apply_insertions(self, inv, inventory_delta):
706
def _apply_insertions(self, inv, limbo_inv):
889
707
"""Perform tree operations that insert directory/inventory names.
891
709
That is, create any files that need to be created, and restore from
893
711
parent-to-child order.
895
713
new_paths = self.new_paths()
897
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
899
for num, (path, trans_id) in enumerate(new_paths):
901
child_pb.update('adding file', num, len(new_paths))
714
for num, (path, trans_id) in enumerate(new_paths):
715
self._pb.update('adding file', num+1, len(new_paths))
717
kind = self._new_contents[trans_id]
719
kind = contents = None
720
if trans_id in self._new_contents or self.path_changed(trans_id):
721
full_path = self._tree.abspath(path)
903
kind = self._new_contents[trans_id]
905
kind = contents = None
906
if trans_id in self._new_contents or \
907
self.path_changed(trans_id):
908
full_path = self._tree.abspath(path)
909
if trans_id in self._needs_rename:
911
os.rename(self._limbo_name(trans_id), full_path)
913
# We may be renaming a dangling inventory id
914
if e.errno != errno.ENOENT:
917
self.rename_count += 1
918
if trans_id in self._new_contents:
919
modified_paths.append(full_path)
920
del self._new_contents[trans_id]
922
if trans_id in self._new_id:
924
kind = file_kind(self._tree.abspath(path))
925
if trans_id in self._new_reference_revision:
926
new_entry = inventory.TreeReference(
927
self._new_id[trans_id],
928
self._new_name[trans_id],
929
self.final_file_id(self._new_parent[trans_id]),
930
None, self._new_reference_revision[trans_id])
932
new_entry = inventory.make_entry(kind,
933
self.final_name(trans_id),
934
self.final_file_id(self.final_parent(trans_id)),
935
self._new_id[trans_id])
937
if trans_id in self._new_name or trans_id in\
939
trans_id in self._new_executability:
940
file_id = self.final_file_id(trans_id)
941
if file_id is not None:
943
new_entry = entry.copy()
945
if trans_id in self._new_name or trans_id in\
947
if new_entry is not None:
948
new_entry.name = self.final_name(trans_id)
949
parent = self.final_parent(trans_id)
950
parent_id = self.final_file_id(parent)
951
new_entry.parent_id = parent_id
953
if trans_id in self._new_executability:
954
self._set_executability(path, new_entry, trans_id)
955
if new_entry is not None:
956
if new_entry.file_id in inv:
957
old_path = inv.id2path(new_entry.file_id)
960
inventory_delta.append((old_path, path,
965
return modified_paths
967
def _set_executability(self, path, entry, trans_id):
723
os.rename(self._limbo_name(trans_id), full_path)
725
# We may be renaming a dangling inventory id
726
if e.errno != errno.ENOENT:
728
if trans_id in self._new_contents:
729
del self._new_contents[trans_id]
731
if trans_id in self._new_id:
733
kind = file_kind(self._tree.abspath(path))
734
inv.add_path(path, kind, self._new_id[trans_id])
735
elif trans_id in self._new_name or trans_id in self._new_parent:
736
entry = limbo_inv.get(trans_id)
737
if entry is not None:
738
entry.name = self.final_name(trans_id)
739
parent_path = os.path.dirname(path)
740
entry.parent_id = self._tree.inventory.path2id(parent_path)
743
# requires files and inventory entries to be in place
744
if trans_id in self._new_executability:
745
self._set_executability(path, inv, trans_id)
748
def _set_executability(self, path, inv, trans_id):
968
749
"""Set the executability of versioned files """
750
file_id = inv.path2id(path)
969
751
new_executability = self._new_executability[trans_id]
970
entry.executable = new_executability
752
inv[file_id].executable = new_executability
971
753
if supports_executable():
972
754
abspath = self._tree.abspath(path)
973
755
current_mode = os.stat(abspath).st_mode
1034
813
self.create_symlink(target, trans_id)
1037
def _affected_ids(self):
1038
"""Return the set of transform ids affected by the transform"""
1039
trans_ids = set(self._removed_id)
1040
trans_ids.update(self._new_id.keys())
1041
trans_ids.update(self._removed_contents)
1042
trans_ids.update(self._new_contents.keys())
1043
trans_ids.update(self._new_executability.keys())
1044
trans_ids.update(self._new_name.keys())
1045
trans_ids.update(self._new_parent.keys())
1048
def _get_file_id_maps(self):
1049
"""Return mapping of file_ids to trans_ids in the to and from states"""
1050
trans_ids = self._affected_ids()
1053
# Build up two dicts: trans_ids associated with file ids in the
1054
# FROM state, vs the TO state.
1055
for trans_id in trans_ids:
1056
from_file_id = self.tree_file_id(trans_id)
1057
if from_file_id is not None:
1058
from_trans_ids[from_file_id] = trans_id
1059
to_file_id = self.final_file_id(trans_id)
1060
if to_file_id is not None:
1061
to_trans_ids[to_file_id] = trans_id
1062
return from_trans_ids, to_trans_ids
1064
def _from_file_data(self, from_trans_id, from_versioned, file_id):
1065
"""Get data about a file in the from (tree) state
1067
Return a (name, parent, kind, executable) tuple
1069
from_path = self._tree_id_paths.get(from_trans_id)
1071
# get data from working tree if versioned
1072
from_entry = self._tree.inventory[file_id]
1073
from_name = from_entry.name
1074
from_parent = from_entry.parent_id
1077
if from_path is None:
1078
# File does not exist in FROM state
1082
# File exists, but is not versioned. Have to use path-
1084
from_name = os.path.basename(from_path)
1085
tree_parent = self.get_tree_parent(from_trans_id)
1086
from_parent = self.tree_file_id(tree_parent)
1087
if from_path is not None:
1088
from_kind, from_executable, from_stats = \
1089
self._tree._comparison_data(from_entry, from_path)
1092
from_executable = False
1093
return from_name, from_parent, from_kind, from_executable
1095
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1096
"""Get data about a file in the to (target) state
1098
Return a (name, parent, kind, executable) tuple
1100
to_name = self.final_name(to_trans_id)
1102
to_kind = self.final_kind(to_trans_id)
1105
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1106
if to_trans_id in self._new_executability:
1107
to_executable = self._new_executability[to_trans_id]
1108
elif to_trans_id == from_trans_id:
1109
to_executable = from_executable
1111
to_executable = False
1112
return to_name, to_parent, to_kind, to_executable
1114
def _iter_changes(self):
1115
"""Produce output in the same format as Tree._iter_changes.
1117
Will produce nonsensical results if invoked while inventory/filesystem
1118
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1120
This reads the Transform, but only reproduces changes involving a
1121
file_id. Files that are not versioned in either of the FROM or TO
1122
states are not reflected.
1124
final_paths = FinalPaths(self)
1125
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1127
# Now iterate through all active file_ids
1128
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1130
from_trans_id = from_trans_ids.get(file_id)
1131
# find file ids, and determine versioning state
1132
if from_trans_id is None:
1133
from_versioned = False
1134
from_trans_id = to_trans_ids[file_id]
1136
from_versioned = True
1137
to_trans_id = to_trans_ids.get(file_id)
1138
if to_trans_id is None:
1139
to_versioned = False
1140
to_trans_id = from_trans_id
1144
from_name, from_parent, from_kind, from_executable = \
1145
self._from_file_data(from_trans_id, from_versioned, file_id)
1147
to_name, to_parent, to_kind, to_executable = \
1148
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1150
if not from_versioned:
1153
from_path = self._tree_id_paths.get(from_trans_id)
1154
if not to_versioned:
1157
to_path = final_paths.get_path(to_trans_id)
1158
if from_kind != to_kind:
1160
elif to_kind in ('file', 'symlink') and (
1161
to_trans_id != from_trans_id or
1162
to_trans_id in self._new_contents):
1164
if (not modified and from_versioned == to_versioned and
1165
from_parent==to_parent and from_name == to_name and
1166
from_executable == to_executable):
1168
results.append((file_id, (from_path, to_path), modified,
1169
(from_versioned, to_versioned),
1170
(from_parent, to_parent),
1171
(from_name, to_name),
1172
(from_kind, to_kind),
1173
(from_executable, to_executable)))
1174
return iter(sorted(results, key=lambda x:x[1]))
1177
816
def joinpath(parent, child):
1178
817
"""Join tree-relative paths, handling the tree root specially"""
1179
818
if parent is None or parent == "":
1215
854
file_ids.sort(key=tree.id2path)
1219
857
def build_tree(tree, wt):
1220
"""Create working tree for a branch, using a TreeTransform.
1222
This function should be used on empty trees, having a tree root at most.
1223
(see merge and revert functionality for working with existing trees)
1225
Existing files are handled like so:
1227
- Existing bzrdirs take precedence over creating new items. They are
1228
created as '%s.diverted' % name.
1229
- Otherwise, if the content on disk matches the content we are building,
1230
it is silently replaced.
1231
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1233
wt.lock_tree_write()
1237
return _build_tree(tree, wt)
1243
def _build_tree(tree, wt):
1244
"""See build_tree."""
1245
if len(wt.inventory) > 1: # more than just a root
1246
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
858
"""Create working tree for a branch, using a Transaction."""
1247
859
file_trans_id = {}
1248
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1249
pp = ProgressPhase("Build phase", 2, top_pb)
1250
if tree.inventory.root is not None:
1251
# This is kind of a hack: we should be altering the root
1252
# as part of the regular tree shape diff logic.
1253
# The conditional test here is to avoid doing an
1254
# expensive operation (flush) every time the root id
1255
# is set within the tree, nor setting the root and thus
1256
# marking the tree as dirty, because we use two different
1257
# idioms here: tree interfaces and inventory interfaces.
1258
if wt.path2id('') != tree.inventory.root.file_id:
1259
wt.set_root_id(tree.inventory.root.file_id)
1261
860
tt = TreeTransform(wt)
1265
file_trans_id[wt.get_root_id()] = \
1266
tt.trans_id_tree_file_id(wt.get_root_id())
1267
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1269
for num, (tree_path, entry) in \
1270
enumerate(tree.inventory.iter_entries_by_dir()):
1271
pb.update("Building tree", num, len(tree.inventory))
1272
if entry.parent_id is None:
1275
file_id = entry.file_id
1276
target_path = wt.abspath(tree_path)
1278
kind = file_kind(target_path)
1282
if kind == "directory":
1284
bzrdir.BzrDir.open(target_path)
1285
except errors.NotBranchError:
1289
if (file_id not in divert and
1290
_content_match(tree, entry, file_id, kind,
1292
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1293
if kind == 'directory':
1295
if entry.parent_id not in file_trans_id:
1296
raise AssertionError(
1297
'entry %s parent id %r is not in file_trans_id %r'
1298
% (entry, entry.parent_id, file_trans_id))
1299
parent_id = file_trans_id[entry.parent_id]
1300
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1303
new_trans_id = file_trans_id[file_id]
1304
old_parent = tt.trans_id_tree_path(tree_path)
1305
_reparent_children(tt, old_parent, new_trans_id)
1309
divert_trans = set(file_trans_id[f] for f in divert)
1310
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1311
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1312
conflicts = cook_conflicts(raw_conflicts, tt)
1313
for conflict in conflicts:
1316
wt.add_conflicts(conflicts)
1317
except errors.UnsupportedOperation:
862
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
863
file_ids = topology_sorted_ids(tree)
864
for file_id in file_ids:
865
entry = tree.inventory[file_id]
866
if entry.parent_id is None:
868
if entry.parent_id not in file_trans_id:
869
raise repr(entry.parent_id)
870
parent_id = file_trans_id[entry.parent_id]
871
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, tree)
1326
def _reparent_children(tt, old_parent, new_parent):
1327
for child in tt.iter_tree_children(old_parent):
1328
tt.adjust_path(tt.final_name(child), new_parent, child)
1331
def _content_match(tree, entry, file_id, kind, target_path):
1332
if entry.kind != kind:
1334
if entry.kind == "directory":
1336
if entry.kind == "file":
1337
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1339
elif entry.kind == "symlink":
1340
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1345
def resolve_checkout(tt, conflicts, divert):
1346
new_conflicts = set()
1347
for c_type, conflict in ((c[0], c) for c in conflicts):
1348
# Anything but a 'duplicate' would indicate programmer error
1349
assert c_type == 'duplicate', c_type
1350
# Now figure out which is new and which is old
1351
if tt.new_contents(conflict[1]):
1352
new_file = conflict[1]
1353
old_file = conflict[2]
1355
new_file = conflict[2]
1356
old_file = conflict[1]
1358
# We should only get here if the conflict wasn't completely
1360
final_parent = tt.final_parent(old_file)
1361
if new_file in divert:
1362
new_name = tt.final_name(old_file)+'.diverted'
1363
tt.adjust_path(new_name, final_parent, new_file)
1364
new_conflicts.add((c_type, 'Diverted to',
1365
new_file, old_file))
1367
new_name = tt.final_name(old_file)+'.moved'
1368
tt.adjust_path(new_name, final_parent, old_file)
1369
new_conflicts.add((c_type, 'Moved existing file to',
1370
old_file, new_file))
1371
return new_conflicts
1374
876
def new_by_entry(tt, entry, parent_id, tree):
1375
877
"""Create a new file according to its inventory entry"""
1380
882
executable = tree.is_executable(entry.file_id)
1381
883
return tt.new_file(name, parent_id, contents, entry.file_id,
1383
elif kind in ('directory', 'tree-reference'):
1384
trans_id = tt.new_directory(name, parent_id, entry.file_id)
1385
if kind == 'tree-reference':
1386
tt.set_tree_reference(entry.reference_revision, trans_id)
885
elif kind == 'directory':
886
return tt.new_directory(name, parent_id, entry.file_id)
1388
887
elif kind == 'symlink':
1389
888
target = tree.get_symlink_target(entry.file_id)
1390
889
return tt.new_symlink(name, parent_id, target, entry.file_id)
1392
raise errors.BadFileKindError(name, kind)
1394
891
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1395
892
"""Create new file contents according to an inventory entry."""
1396
893
if entry.kind == "file":
1398
895
lines = tree.get_file(entry.file_id).readlines()
1399
896
tt.create_file(lines, trans_id, mode_id=mode_id)
1400
897
elif entry.kind == "symlink":
1408
905
tt.set_executability(entry.executable, trans_id)
1411
@deprecated_function(zero_fifteen)
1412
908
def find_interesting(working_tree, target_tree, filenames):
1413
"""Find the ids corresponding to specified filenames.
1415
Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1417
working_tree.lock_read()
1419
target_tree.lock_read()
1421
return working_tree.paths2ids(filenames, [target_tree])
1423
target_tree.unlock()
1425
working_tree.unlock()
1428
@deprecated_function(zero_nineteen)
909
"""Find the ids corresponding to specified filenames."""
911
interesting_ids = None
913
interesting_ids = set()
914
for tree_path in filenames:
915
for tree in (working_tree, target_tree):
917
file_id = tree.inventory.path2id(tree_path)
918
if file_id is not None:
919
interesting_ids.add(file_id)
922
raise NotVersionedError(path=tree_path)
923
return interesting_ids
1429
926
def change_entry(tt, file_id, working_tree, target_tree,
1430
trans_id_file_id, backups, trans_id, by_parent):
927
trans_id_file_id, backups, trans_id):
1431
928
"""Replace a file_id's contents with those from a target tree."""
1432
if file_id is None and target_tree is None:
1433
# skip the logic altogether in the deprecation test
1435
929
e_trans_id = trans_id_file_id(file_id)
1436
930
entry = target_tree.inventory[file_id]
1437
931
has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry,
1512
993
return has_contents, contents_mod, meta_mod
1515
def revert(working_tree, target_tree, filenames, backups=False,
1516
pb=DummyProgress(), change_reporter=None):
996
def revert(working_tree, target_tree, filenames, backups=False,
1517
998
"""Revert a working tree's contents to those of a target tree."""
1518
target_tree.lock_read()
999
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1000
def interesting(file_id):
1001
return interesting_ids is None or file_id in interesting_ids
1519
1003
tt = TreeTransform(working_tree, pb)
1521
pp = ProgressPhase("Revert phase", 3, pb)
1523
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1525
merge_modified = _alter_files(working_tree, target_tree, tt,
1526
child_pb, filenames, backups)
1530
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1532
raw_conflicts = resolve_conflicts(tt, child_pb)
1535
conflicts = cook_conflicts(raw_conflicts, tt)
1537
change_reporter = delta._ChangeReporter(
1538
unversioned_filter=working_tree.is_ignored)
1539
delta.report_changes(tt._iter_changes(), change_reporter)
1540
for conflict in conflicts:
1006
def trans_id_file_id(file_id):
1008
return trans_id[file_id]
1010
return tt.trans_id_tree_file_id(file_id)
1012
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1014
for id_num, file_id in enumerate(sorted_interesting):
1015
pb.update("Reverting file", id_num+1, len(sorted_interesting))
1016
if file_id not in working_tree.inventory:
1017
entry = target_tree.inventory[file_id]
1018
parent_id = trans_id_file_id(entry.parent_id)
1019
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1020
trans_id[file_id] = e_trans_id
1022
change_entry(tt, file_id, working_tree, target_tree,
1023
trans_id_file_id, backups, trans_id)
1024
wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1025
for id_num, file_id in enumerate(wt_interesting):
1026
pb.update("New file check", id_num+1, len(sorted_interesting))
1027
if file_id not in target_tree:
1028
tt.unversion_file(tt.trans_id_tree_file_id(file_id))
1029
raw_conflicts = resolve_conflicts(tt, pb)
1030
for line in conflicts_strings(cook_conflicts(raw_conflicts, tt)):
1544
working_tree.set_merge_modified(merge_modified)
1546
target_tree.unlock()
1552
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1554
merge_modified = working_tree.merge_modified()
1555
change_list = target_tree._iter_changes(working_tree,
1556
specific_files=specific_files, pb=pb)
1557
if target_tree.inventory.root is None:
1563
for id_num, (file_id, path, changed_content, versioned, parent, name,
1564
kind, executable) in enumerate(change_list):
1565
if skip_root and file_id[0] is not None and parent[0] is None:
1567
trans_id = tt.trans_id_file_id(file_id)
1570
keep_content = False
1571
if kind[0] == 'file' and (backups or kind[1] is None):
1572
wt_sha1 = working_tree.get_file_sha1(file_id)
1573
if merge_modified.get(file_id) != wt_sha1:
1574
# acquire the basis tree lazily to prevent the
1575
# expense of accessing it when it's not needed ?
1576
# (Guessing, RBC, 200702)
1577
if basis_tree is None:
1578
basis_tree = working_tree.basis_tree()
1579
basis_tree.lock_read()
1580
if file_id in basis_tree:
1581
if wt_sha1 != basis_tree.get_file_sha1(file_id):
1583
elif kind[1] is None and not versioned[1]:
1585
if kind[0] is not None:
1586
if not keep_content:
1587
tt.delete_contents(trans_id)
1588
elif kind[1] is not None:
1589
parent_trans_id = tt.trans_id_file_id(parent[0])
1590
by_parent = tt.by_parent()
1591
backup_name = _get_backup_name(name[0], by_parent,
1592
parent_trans_id, tt)
1593
tt.adjust_path(backup_name, parent_trans_id, trans_id)
1594
new_trans_id = tt.create_path(name[0], parent_trans_id)
1595
if versioned == (True, True):
1596
tt.unversion_file(trans_id)
1597
tt.version_file(file_id, new_trans_id)
1598
# New contents should have the same unix perms as old
1601
trans_id = new_trans_id
1602
if kind[1] == 'directory':
1603
tt.create_directory(trans_id)
1604
elif kind[1] == 'symlink':
1605
tt.create_symlink(target_tree.get_symlink_target(file_id),
1607
elif kind[1] == 'file':
1608
tt.create_file(target_tree.get_file_lines(file_id),
1610
if basis_tree is None:
1611
basis_tree = working_tree.basis_tree()
1612
basis_tree.lock_read()
1613
new_sha1 = target_tree.get_file_sha1(file_id)
1614
if (file_id in basis_tree and new_sha1 ==
1615
basis_tree.get_file_sha1(file_id)):
1616
if file_id in merge_modified:
1617
del merge_modified[file_id]
1619
merge_modified[file_id] = new_sha1
1621
# preserve the execute bit when backing up
1622
if keep_content and executable[0] == executable[1]:
1623
tt.set_executability(executable[1], trans_id)
1625
assert kind[1] is None
1626
if versioned == (False, True):
1627
tt.version_file(file_id, trans_id)
1628
if versioned == (True, False):
1629
tt.unversion_file(trans_id)
1630
if (name[1] is not None and
1631
(name[0] != name[1] or parent[0] != parent[1])):
1633
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1634
if executable[0] != executable[1] and kind[1] == "file":
1635
tt.set_executability(executable[1], trans_id)
1637
if basis_tree is not None:
1639
return merge_modified
1642
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1038
def resolve_conflicts(tt, pb=DummyProgress()):
1643
1039
"""Make many conflict-resolution attempts, but die if they fail"""
1644
if pass_func is None:
1645
pass_func = conflict_pass
1646
1040
new_conflicts = set()
1648
1042
for n in range(10):
1694
1083
trans_id = conflict[1]
1696
1085
tt.cancel_deletion(trans_id)
1697
new_conflicts.add(('deleting parent', 'Not deleting',
1086
new_conflicts.add((c_type, 'Not deleting', trans_id))
1699
1087
except KeyError:
1700
1088
tt.create_directory(trans_id)
1701
new_conflicts.add((c_type, 'Created directory', trans_id))
1703
tt.final_name(trans_id)
1705
file_id = tt.final_file_id(trans_id)
1706
entry = path_tree.inventory[file_id]
1707
parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1708
tt.adjust_path(entry.name, parent_trans_id, trans_id)
1089
new_conflicts.add((c_type, 'Created directory.', trans_id))
1709
1090
elif c_type == 'unversioned parent':
1710
1091
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1711
1092
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1712
1093
return new_conflicts
1715
1095
def cook_conflicts(raw_conflicts, tt):
1716
1096
"""Generate a list of cooked conflicts, sorted by file path"""
1717
from bzrlib.conflicts import Conflict
1718
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1719
return sorted(conflict_iter, key=Conflict.sort_key)
1098
if conflict[2] is not None:
1099
return conflict[2], conflict[0]
1100
elif len(conflict) == 6:
1101
return conflict[4], conflict[0]
1103
return None, conflict[0]
1105
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1722
1107
def iter_cook_conflicts(raw_conflicts, tt):
1723
from bzrlib.conflicts import Conflict
1108
cooked_conflicts = []
1724
1109
fp = FinalPaths(tt)
1725
1110
for conflict in raw_conflicts:
1726
1111
c_type = conflict[0]
1728
1113
modified_path = fp.get_path(conflict[2])
1729
1114
modified_id = tt.final_file_id(conflict[2])
1730
1115
if len(conflict) == 3:
1731
yield Conflict.factory(c_type, action=action, path=modified_path,
1732
file_id=modified_id)
1116
yield c_type, action, modified_path, modified_id
1735
1118
conflicting_path = fp.get_path(conflict[3])
1736
1119
conflicting_id = tt.final_file_id(conflict[3])
1737
yield Conflict.factory(c_type, action=action, path=modified_path,
1738
file_id=modified_id,
1739
conflict_path=conflicting_path,
1740
conflict_file_id=conflicting_id)
1120
yield (c_type, action, modified_path, modified_id,
1121
conflicting_path, conflicting_id)
1124
def conflicts_strings(conflicts):
1125
"""Generate strings for the provided conflicts"""
1126
for conflict in conflicts:
1127
conflict_type = conflict[0]
1128
if conflict_type == 'text conflict':
1129
yield 'Text conflict in %s' % conflict[2]
1130
elif conflict_type == 'contents conflict':
1131
yield 'Contents conflict in %s' % conflict[2]
1132
elif conflict_type == 'path conflict':
1133
yield 'Path conflict: %s / %s' % conflict[2:]
1134
elif conflict_type == 'duplicate id':
1135
vals = (conflict[4], conflict[1], conflict[2])
1136
yield 'Conflict adding id to %s. %s %s.' % vals
1137
elif conflict_type == 'duplicate':
1138
vals = (conflict[4], conflict[1], conflict[2])
1139
yield 'Conflict adding file %s. %s %s.' % vals
1140
elif conflict_type == 'parent loop':
1141
vals = (conflict[4], conflict[2], conflict[1])
1142
yield 'Conflict moving %s into %s. %s.' % vals
1143
elif conflict_type == 'unversioned parent':
1144
vals = (conflict[2], conflict[1])
1145
yield 'Conflict adding versioned files to %s. %s.' % vals
1146
elif conflict_type == 'missing parent':
1147
vals = (conflict[2], conflict[1])
1148
yield 'Conflict adding files to %s. %s.' % vals