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,
25
from bzrlib.osutils import file_kind, supports_executable, pathjoin
36
26
from bzrlib.progress import DummyProgress, ProgressPhase
37
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
38
27
from bzrlib.trace import mutter, warning
39
from bzrlib import tree
41
import bzrlib.urlutils as urlutils
44
31
ROOT_PARENT = "root-parent"
187
146
"""Change the path that is assigned to a transaction id."""
188
147
if trans_id == self._new_root:
189
148
raise CantMoveRoot
190
previous_parent = self._new_parent.get(trans_id)
191
previous_name = self._new_name.get(trans_id)
192
149
self._new_name[trans_id] = name
193
150
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
152
def adjust_root_path(self, name, parent):
219
153
"""Emulate moving the root by moving all children, instead.
276
210
def canonical_path(self, path):
277
211
"""Get the canonical tree-relative path"""
278
212
# don't follow final symlinks
279
abs = self._tree.abspath(path)
280
if abs in self._relpaths:
281
return self._relpaths[abs]
282
dirname, basename = os.path.split(abs)
283
if dirname not in self._realpaths:
284
self._realpaths[dirname] = os.path.realpath(dirname)
285
dirname = self._realpaths[dirname]
286
abs = pathjoin(dirname, basename)
287
if dirname in self._relpaths:
288
relpath = pathjoin(self._relpaths[dirname], basename)
289
relpath = relpath.rstrip('/\\')
291
relpath = self._tree.relpath(abs)
292
self._relpaths[abs] = relpath
213
dirname, basename = os.path.split(self._tree.abspath(path))
214
dirname = os.path.realpath(dirname)
215
return self._tree.relpath(pathjoin(dirname, basename))
295
217
def trans_id_tree_path(self, path):
296
218
"""Determine (and maybe set) the transaction ID for a tree path."""
374
286
os.symlink(target, self._limbo_name(trans_id))
375
287
unique_add(self._new_contents, trans_id, 'symlink')
290
def delete_any(full_path):
291
"""Delete a file or directory."""
295
# We may be renaming a dangling inventory id
296
if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
377
300
def cancel_creation(self, trans_id):
378
301
"""Cancel the creation of new file contents."""
379
302
del self._new_contents[trans_id]
380
children = self._limbo_children.get(trans_id)
381
# if this is a limbo directory with children, move them before removing
383
if children is not None:
384
self._rename_in_limbo(children)
385
del self._limbo_children[trans_id]
386
del self._limbo_children_names[trans_id]
387
delete_any(self._limbo_name(trans_id))
303
self.delete_any(self._limbo_name(trans_id))
389
305
def delete_contents(self, trans_id):
390
306
"""Schedule the contents of a path entry for deletion"""
788
def apply(self, no_conflicts=False):
789
659
"""Apply all changes to the inventory and filesystem.
791
661
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.
800
conflicts = self.find_conflicts()
801
if len(conflicts) != 0:
802
raise MalformedTransform(conflicts=conflicts)
664
conflicts = self.find_conflicts()
665
if len(conflicts) != 0:
666
raise MalformedTransform(conflicts=conflicts)
803
668
inv = self._tree.inventory
805
669
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
807
671
child_pb.update('Apply phase', 0, 2)
808
self._apply_removals(inv, inventory_delta)
672
self._apply_removals(inv, limbo_inv)
809
673
child_pb.update('Apply phase', 1, 2)
810
modified_paths = self._apply_insertions(inv, inventory_delta)
674
modified_paths = self._apply_insertions(inv, limbo_inv)
812
676
child_pb.finished()
813
self._tree.apply_inventory_delta(inventory_delta)
677
self._tree._write_inventory(inv)
814
678
self.__done = True
816
return _TransformResults(modified_paths, self.rename_count)
680
return _TransformResults(modified_paths)
818
682
def _limbo_name(self, trans_id):
819
683
"""Generate the limbo name of a file"""
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
684
return pathjoin(self._limbodir, trans_id)
851
def _apply_removals(self, inv, inventory_delta):
686
def _apply_removals(self, inv, limbo_inv):
852
687
"""Perform tree operations that remove directory/inventory names.
854
689
That is, delete files that are to be deleted, and put any files that
921
754
if trans_id in self._new_id:
923
756
kind = file_kind(self._tree.abspath(path))
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
757
inv.add_path(path, kind, self._new_id[trans_id])
758
elif trans_id in self._new_name or trans_id in\
760
entry = limbo_inv.get(trans_id)
761
if entry is not None:
762
entry.name = self.final_name(trans_id)
763
parent_path = os.path.dirname(path)
765
self._tree.inventory.path2id(parent_path)
768
# requires files and inventory entries to be in place
952
769
if trans_id in self._new_executability:
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,
770
self._set_executability(path, inv, trans_id)
963
772
child_pb.finished()
964
773
return modified_paths
966
def _set_executability(self, path, entry, trans_id):
775
def _set_executability(self, path, inv, trans_id):
967
776
"""Set the executability of versioned files """
777
file_id = inv.path2id(path)
968
778
new_executability = self._new_executability[trans_id]
969
entry.executable = new_executability
779
inv[file_id].executable = new_executability
970
780
if supports_executable():
971
781
abspath = self._tree.abspath(path)
972
782
current_mode = os.stat(abspath).st_mode
1033
840
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]))
1176
843
def joinpath(parent, child):
1177
844
"""Join tree-relative paths, handling the tree root specially"""
1178
845
if parent is None or parent == "":
1214
881
file_ids.sort(key=tree.id2path)
1218
884
def build_tree(tree, wt):
1219
"""Create working tree for a branch, using a TreeTransform.
1221
This function should be used on empty trees, having a tree root at most.
1222
(see merge and revert functionality for working with existing trees)
1224
Existing files are handled like so:
1226
- Existing bzrdirs take precedence over creating new items. They are
1227
created as '%s.diverted' % name.
1228
- Otherwise, if the content on disk matches the content we are building,
1229
it is silently replaced.
1230
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
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)
885
"""Create working tree for a branch, using a Transaction."""
1246
886
file_trans_id = {}
1247
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
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)
1260
887
tt = TreeTransform(wt)
1264
file_trans_id[wt.get_root_id()] = \
1265
tt.trans_id_tree_file_id(wt.get_root_id())
1266
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1268
for num, (tree_path, entry) in \
1269
enumerate(tree.inventory.iter_entries_by_dir()):
1270
pb.update("Building tree", num, len(tree.inventory))
1271
if entry.parent_id is None:
1274
file_id = entry.file_id
1275
target_path = wt.abspath(tree_path)
1277
kind = file_kind(target_path)
1281
if kind == "directory":
1283
bzrdir.BzrDir.open(target_path)
1284
except errors.NotBranchError:
1288
if (file_id not in divert and
1289
_content_match(tree, entry, file_id, kind,
1291
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1292
if kind == 'directory':
1294
if entry.parent_id not in file_trans_id:
1295
raise AssertionError(
1296
'entry %s parent id %r is not in file_trans_id %r'
1297
% (entry, entry.parent_id, file_trans_id))
1298
parent_id = file_trans_id[entry.parent_id]
1299
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1302
new_trans_id = file_trans_id[file_id]
1303
old_parent = tt.trans_id_tree_path(tree_path)
1304
_reparent_children(tt, old_parent, new_trans_id)
1308
divert_trans = set(file_trans_id[f] for f in divert)
1309
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1310
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1311
conflicts = cook_conflicts(raw_conflicts, tt)
1312
for conflict in conflicts:
1315
wt.add_conflicts(conflicts)
1316
except errors.UnsupportedOperation:
889
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
890
file_ids = topology_sorted_ids(tree)
891
for file_id in file_ids:
892
entry = tree.inventory[file_id]
893
if entry.parent_id is None:
895
if entry.parent_id not in file_trans_id:
896
raise repr(entry.parent_id)
897
parent_id = file_trans_id[entry.parent_id]
898
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, tree)
1325
def _reparent_children(tt, old_parent, new_parent):
1326
for child in tt.iter_tree_children(old_parent):
1327
tt.adjust_path(tt.final_name(child), new_parent, child)
1330
def _content_match(tree, entry, file_id, kind, target_path):
1331
if entry.kind != kind:
1333
if entry.kind == "directory":
1335
if entry.kind == "file":
1336
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1338
elif entry.kind == "symlink":
1339
if tree.get_symlink_target(file_id) == os.readlink(target_path):
1344
def resolve_checkout(tt, conflicts, divert):
1345
new_conflicts = set()
1346
for c_type, conflict in ((c[0], c) for c in conflicts):
1347
# Anything but a 'duplicate' would indicate programmer error
1348
assert c_type == 'duplicate', c_type
1349
# Now figure out which is new and which is old
1350
if tt.new_contents(conflict[1]):
1351
new_file = conflict[1]
1352
old_file = conflict[2]
1354
new_file = conflict[2]
1355
old_file = conflict[1]
1357
# We should only get here if the conflict wasn't completely
1359
final_parent = tt.final_parent(old_file)
1360
if new_file in divert:
1361
new_name = tt.final_name(old_file)+'.diverted'
1362
tt.adjust_path(new_name, final_parent, new_file)
1363
new_conflicts.add((c_type, 'Diverted to',
1364
new_file, old_file))
1366
new_name = tt.final_name(old_file)+'.moved'
1367
tt.adjust_path(new_name, final_parent, old_file)
1368
new_conflicts.add((c_type, 'Moved existing file to',
1369
old_file, new_file))
1370
return new_conflicts
1373
903
def new_by_entry(tt, entry, parent_id, tree):
1374
904
"""Create a new file according to its inventory entry"""
1379
909
executable = tree.is_executable(entry.file_id)
1380
910
return tt.new_file(name, parent_id, contents, 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)
912
elif kind == 'directory':
913
return tt.new_directory(name, parent_id, entry.file_id)
1387
914
elif kind == 'symlink':
1388
915
target = tree.get_symlink_target(entry.file_id)
1389
916
return tt.new_symlink(name, parent_id, target, entry.file_id)
1391
raise errors.BadFileKindError(name, kind)
1393
918
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1394
919
"""Create new file contents according to an inventory entry."""
1395
920
if entry.kind == "file":
1397
922
lines = tree.get_file(entry.file_id).readlines()
1398
923
tt.create_file(lines, trans_id, mode_id=mode_id)
1399
924
elif entry.kind == "symlink":
1407
932
tt.set_executability(entry.executable, trans_id)
1410
@deprecated_function(zero_fifteen)
1411
935
def find_interesting(working_tree, target_tree, filenames):
1412
"""Find the ids corresponding to specified filenames.
1414
Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1416
working_tree.lock_read()
1418
target_tree.lock_read()
1420
return working_tree.paths2ids(filenames, [target_tree])
1422
target_tree.unlock()
1424
working_tree.unlock()
936
"""Find the ids corresponding to specified filenames."""
938
interesting_ids = None
940
interesting_ids = set()
941
for tree_path in filenames:
942
for tree in (working_tree, target_tree):
944
file_id = tree.inventory.path2id(tree_path)
945
if file_id is not None:
946
interesting_ids.add(file_id)
949
raise NotVersionedError(path=tree_path)
950
return interesting_ids
1427
953
def change_entry(tt, file_id, working_tree, target_tree,
1428
trans_id_file_id, backups, trans_id, by_parent):
954
trans_id_file_id, backups, trans_id):
1429
955
"""Replace a file_id's contents with those from a target tree."""
1430
956
e_trans_id = trans_id_file_id(file_id)
1431
957
entry = target_tree.inventory[file_id]
1507
1020
return has_contents, contents_mod, meta_mod
1510
def revert(working_tree, target_tree, filenames, backups=False,
1511
pb=DummyProgress(), change_reporter=None):
1023
def revert(working_tree, target_tree, filenames, backups=False,
1024
pb=DummyProgress()):
1512
1025
"""Revert a working tree's contents to those of a target tree."""
1513
target_tree.lock_read()
1026
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1027
def interesting(file_id):
1028
return interesting_ids is None or file_id in interesting_ids
1514
1030
tt = TreeTransform(working_tree, pb)
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)
1032
merge_modified = working_tree.merge_modified()
1034
def trans_id_file_id(file_id):
1036
return trans_id[file_id]
1038
return tt.trans_id_tree_file_id(file_id)
1040
pp = ProgressPhase("Revert phase", 4, pb)
1042
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1044
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1046
for id_num, file_id in enumerate(sorted_interesting):
1047
child_pb.update("Reverting file", id_num+1,
1048
len(sorted_interesting))
1049
if file_id not in working_tree.inventory:
1050
entry = target_tree.inventory[file_id]
1051
parent_id = trans_id_file_id(entry.parent_id)
1052
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1053
trans_id[file_id] = e_trans_id
1055
backup_this = backups
1056
if file_id in merge_modified:
1058
del merge_modified[file_id]
1059
change_entry(tt, file_id, working_tree, target_tree,
1060
trans_id_file_id, backup_this, trans_id)
1064
wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1065
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1067
for id_num, file_id in enumerate(wt_interesting):
1068
child_pb.update("New file check", id_num+1,
1069
len(sorted_interesting))
1070
if file_id not in target_tree:
1071
trans_id = tt.trans_id_tree_file_id(file_id)
1072
tt.unversion_file(trans_id)
1073
if file_id in merge_modified:
1074
tt.delete_contents(trans_id)
1075
del merge_modified[file_id]
1523
1077
child_pb.finished()
1524
1078
pp.next_phase()
1527
1081
raw_conflicts = resolve_conflicts(tt, child_pb)
1529
1083
child_pb.finished()
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)
1535
for conflict in conflicts:
1084
for line in conflicts_strings(cook_conflicts(raw_conflicts, tt)):
1537
1086
pp.next_phase()
1539
working_tree.set_merge_modified(merge_modified)
1088
working_tree.set_merge_modified({})
1541
target_tree.unlock()
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
1637
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1094
def resolve_conflicts(tt, pb=DummyProgress()):
1638
1095
"""Make many conflict-resolution attempts, but die if they fail"""
1639
if pass_func is None:
1640
pass_func = conflict_pass
1641
1096
new_conflicts = set()
1643
1098
for n in range(10):
1689
1139
trans_id = conflict[1]
1691
1141
tt.cancel_deletion(trans_id)
1692
new_conflicts.add(('deleting parent', 'Not deleting',
1142
new_conflicts.add((c_type, 'Not deleting', trans_id))
1694
1143
except KeyError:
1695
1144
tt.create_directory(trans_id)
1696
new_conflicts.add((c_type, 'Created directory', trans_id))
1698
tt.final_name(trans_id)
1700
file_id = tt.final_file_id(trans_id)
1701
entry = path_tree.inventory[file_id]
1702
parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1703
tt.adjust_path(entry.name, parent_trans_id, trans_id)
1145
new_conflicts.add((c_type, 'Created directory.', trans_id))
1704
1146
elif c_type == 'unversioned parent':
1705
1147
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1706
1148
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1707
1149
return new_conflicts
1710
1151
def cook_conflicts(raw_conflicts, tt):
1711
1152
"""Generate a list of cooked conflicts, sorted by file path"""
1712
from bzrlib.conflicts import Conflict
1713
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1714
return sorted(conflict_iter, key=Conflict.sort_key)
1154
if conflict[2] is not None:
1155
return conflict[2], conflict[0]
1156
elif len(conflict) == 6:
1157
return conflict[4], conflict[0]
1159
return None, conflict[0]
1161
return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1717
1163
def iter_cook_conflicts(raw_conflicts, tt):
1718
from bzrlib.conflicts import Conflict
1164
cooked_conflicts = []
1719
1165
fp = FinalPaths(tt)
1720
1166
for conflict in raw_conflicts:
1721
1167
c_type = conflict[0]
1723
1169
modified_path = fp.get_path(conflict[2])
1724
1170
modified_id = tt.final_file_id(conflict[2])
1725
1171
if len(conflict) == 3:
1726
yield Conflict.factory(c_type, action=action, path=modified_path,
1727
file_id=modified_id)
1172
yield c_type, action, modified_path, modified_id
1730
1174
conflicting_path = fp.get_path(conflict[3])
1731
1175
conflicting_id = tt.final_file_id(conflict[3])
1732
yield Conflict.factory(c_type, action=action, path=modified_path,
1733
file_id=modified_id,
1734
conflict_path=conflicting_path,
1735
conflict_file_id=conflicting_id)
1176
yield (c_type, action, modified_path, modified_id,
1177
conflicting_path, conflicting_id)
1180
def conflicts_strings(conflicts):
1181
"""Generate strings for the provided conflicts"""
1182
for conflict in conflicts:
1183
conflict_type = conflict[0]
1184
if conflict_type == 'text conflict':
1185
yield 'Text conflict in %s' % conflict[2]
1186
elif conflict_type == 'contents conflict':
1187
yield 'Contents conflict in %s' % conflict[2]
1188
elif conflict_type == 'path conflict':
1189
yield 'Path conflict: %s / %s' % conflict[2:]
1190
elif conflict_type == 'duplicate id':
1191
vals = (conflict[4], conflict[1], conflict[2])
1192
yield 'Conflict adding id to %s. %s %s.' % vals
1193
elif conflict_type == 'duplicate':
1194
vals = (conflict[4], conflict[1], conflict[2])
1195
yield 'Conflict adding file %s. %s %s.' % vals
1196
elif conflict_type == 'parent loop':
1197
vals = (conflict[4], conflict[2], conflict[1])
1198
yield 'Conflict moving %s into %s. %s.' % vals
1199
elif conflict_type == 'unversioned parent':
1200
vals = (conflict[2], conflict[1])
1201
yield 'Conflict adding versioned files to %s. %s.' % vals
1202
elif conflict_type == 'missing parent':
1203
vals = (conflict[2], conflict[1])
1204
yield 'Conflict adding files to %s. %s.' % vals