757
780
def do_merge(self):
758
with contextlib.ExitStack() as stack:
759
stack.enter_context(self.working_tree.lock_tree_write())
760
stack.enter_context(self.this_tree.lock_read())
761
stack.enter_context(self.base_tree.lock_read())
762
stack.enter_context(self.other_tree.lock_read())
763
self.tt = self.working_tree.transform()
764
stack.enter_context(self.tt)
765
self._compute_transform()
766
results = self.tt.apply(no_conflicts=True)
767
self.write_modified(results)
769
self.working_tree.add_conflicts(self.cooked_conflicts)
770
except errors.UnsupportedOperation:
781
operation = cleanup.OperationWithCleanups(self._do_merge)
782
self.working_tree.lock_tree_write()
783
operation.add_cleanup(self.working_tree.unlock)
784
self.this_tree.lock_read()
785
operation.add_cleanup(self.this_tree.unlock)
786
self.base_tree.lock_read()
787
operation.add_cleanup(self.base_tree.unlock)
788
self.other_tree.lock_read()
789
operation.add_cleanup(self.other_tree.unlock)
792
def _do_merge(self, operation):
793
self.tt = transform.TreeTransform(self.working_tree, None)
794
operation.add_cleanup(self.tt.finalize)
795
self._compute_transform()
796
results = self.tt.apply(no_conflicts=True)
797
self.write_modified(results)
799
self.working_tree.add_conflicts(self.cooked_conflicts)
800
except errors.UnsupportedOperation:
773
803
def make_preview_transform(self):
774
with self.base_tree.lock_read(), self.other_tree.lock_read():
775
self.tt = self.working_tree.preview_transform()
776
self._compute_transform()
804
operation = cleanup.OperationWithCleanups(self._make_preview_transform)
805
self.base_tree.lock_read()
806
operation.add_cleanup(self.base_tree.unlock)
807
self.other_tree.lock_read()
808
operation.add_cleanup(self.other_tree.unlock)
809
return operation.run_simple()
811
def _make_preview_transform(self):
812
self.tt = transform.TransformPreview(self.working_tree)
813
self._compute_transform()
779
816
def _compute_transform(self):
780
817
if self._lca_trees is None:
781
entries = list(self._entries3())
818
entries = self._entries3()
782
819
resolver = self._three_way
784
entries = list(self._entries_lca())
821
entries = self._entries_lca()
785
822
resolver = self._lca_multi_way
786
823
# Prepare merge hooks
787
824
factories = Merger.hooks['merge_file_content']
788
825
# One hook for each registered one plus our default merger
789
826
hooks = [factory(self) for factory in factories] + [self]
790
827
self.active_hooks = [hook for hook in hooks if hook is not None]
791
with ui.ui_factory.nested_progress_bar() as child_pb:
792
for num, (file_id, changed, paths3, parents3, names3,
793
executable3, copied) in enumerate(entries):
795
# Treat copies as simple adds for now
796
paths3 = (None, paths3[1], None)
797
parents3 = (None, parents3[1], None)
798
names3 = (None, names3[1], None)
799
executable3 = (None, executable3[1], None)
802
trans_id = self.tt.trans_id_file_id(file_id)
828
child_pb = ui.ui_factory.nested_progress_bar()
830
for num, (file_id, changed, parents3, names3,
831
executable3) in enumerate(entries):
803
832
# Try merging each entry
804
833
child_pb.update(gettext('Preparing file merge'),
805
834
num, len(entries))
806
self._merge_names(trans_id, file_id, paths3, parents3,
807
names3, resolver=resolver)
835
self._merge_names(file_id, parents3, names3, resolver=resolver)
809
file_status = self._do_merge_contents(paths3, trans_id, file_id)
837
file_status = self._do_merge_contents(file_id)
811
839
file_status = 'unmodified'
812
self._merge_executable(paths3, trans_id, executable3,
813
file_status, resolver=resolver)
840
self._merge_executable(file_id,
841
executable3, file_status, resolver=resolver)
814
844
self.tt.fixup_new_roots()
815
845
self._finish_computing_transform()
840
872
other and this. names3 is a tuple of names for base, other and this.
841
873
executable3 is a tuple of execute-bit values for base, other and this.
843
876
iterator = self.other_tree.iter_changes(self.base_tree,
844
specific_files=self.interesting_files,
845
extra_trees=[self.this_tree])
846
this_interesting_files = self.this_tree.find_related_paths_across_trees(
847
self.interesting_files, trees=[self.other_tree])
848
this_entries = dict(self.this_tree.iter_entries_by_dir(
849
specific_files=this_interesting_files))
850
for change in iterator:
851
if change.path[0] is not None:
852
this_path = _mod_tree.find_previous_path(
853
self.base_tree, self.this_tree, change.path[0])
855
this_path = _mod_tree.find_previous_path(
856
self.other_tree, self.this_tree, change.path[1])
857
this_entry = this_entries.get(this_path)
858
if this_entry is not None:
859
this_name = this_entry.name
860
this_parent = this_entry.parent_id
861
this_executable = this_entry.executable
877
specific_files=self.interesting_files,
878
extra_trees=[self.this_tree])
879
this_entries = dict((e.file_id, e) for p, e in
880
self.this_tree.iter_entries_by_dir(
881
self.interesting_ids))
882
for (file_id, paths, changed, versioned, parents, names, kind,
883
executable) in iterator:
884
if (self.interesting_ids is not None and
885
file_id not in self.interesting_ids):
887
entry = this_entries.get(file_id)
888
if entry is not None:
889
this_name = entry.name
890
this_parent = entry.parent_id
891
this_executable = entry.executable
864
894
this_parent = None
865
895
this_executable = None
866
parents3 = change.parent_id + (this_parent,)
867
names3 = change.name + (this_name,)
868
paths3 = change.path + (this_path, )
869
executable3 = change.executable + (this_executable,)
871
(change.file_id, change.changed_content, paths3,
872
parents3, names3, executable3, change.copied))
896
parents3 = parents + (this_parent,)
897
names3 = names + (this_name,)
898
executable3 = executable + (this_executable,)
899
result.append((file_id, changed, parents3, names3, executable3))
874
902
def _entries_lca(self):
875
903
"""Gather data about files modified between multiple trees.
1001
1018
(base_ie.executable, lca_executable),
1002
1019
other_ie.executable, this_ie.executable)
1003
1020
if (parent_id_winner == 'this' and name_winner == 'this'
1004
and sha1_winner == 'this' and exec_winner == 'this'):
1021
and sha1_winner == 'this' and exec_winner == 'this'):
1005
1022
# No kind, parent, name, exec, or content change for
1006
1023
# OTHER, so this node is not considered interesting
1008
1025
if sha1_winner == 'this':
1009
1026
content_changed = False
1010
1027
elif other_ie.kind == 'symlink':
1011
def get_target(ie, tree, path):
1028
def get_target(ie, tree):
1012
1029
if ie.kind != 'symlink':
1014
return tree.get_symlink_target(path)
1015
base_target = get_target(base_ie, self.base_tree, base_path)
1016
lca_targets = [get_target(ie, tree, lca_path) for ie, tree, lca_path
1017
in zip(lca_entries, self._lca_trees, lca_paths)]
1018
this_target = get_target(
1019
this_ie, self.this_tree, this_path)
1020
other_target = get_target(
1021
other_ie, self.other_tree, other_path)
1031
return tree.get_symlink_target(file_id)
1032
base_target = get_target(base_ie, self.base_tree)
1033
lca_targets = [get_target(ie, tree) for ie, tree
1034
in zip(lca_entries, self._lca_trees)]
1035
this_target = get_target(this_ie, self.this_tree)
1036
other_target = get_target(other_ie, self.other_tree)
1022
1037
target_winner = self._lca_multi_way(
1023
1038
(base_target, lca_targets),
1024
1039
other_target, this_target)
1025
1040
if (parent_id_winner == 'this' and name_winner == 'this'
1026
and target_winner == 'this'):
1041
and target_winner == 'this'):
1027
1042
# No kind, parent, name, or symlink target change
1028
1043
# not interesting
1041
1056
raise AssertionError('unhandled kind: %s' % other_ie.kind)
1043
1058
# If we have gotten this far, that means something has changed
1044
yield (file_id, content_changed,
1045
((base_path, lca_paths),
1046
other_path, this_path),
1059
result.append((file_id, content_changed,
1047
1060
((base_ie.parent_id, lca_parent_ids),
1048
1061
other_ie.parent_id, this_ie.parent_id),
1049
1062
((base_ie.name, lca_names),
1050
1063
other_ie.name, this_ie.name),
1051
1064
((base_ie.executable, lca_executable),
1052
other_ie.executable, this_ie.executable),
1053
# Copy detection is not yet supported, so nothing is
1065
other_ie.executable, this_ie.executable)
1058
1069
def write_modified(self, results):
1059
if not self.working_tree.supports_merge_modified():
1061
1070
modified_hashes = {}
1062
1071
for path in results.modified_paths:
1063
wt_relpath = self.working_tree.relpath(path)
1064
if not self.working_tree.is_versioned(wt_relpath):
1072
file_id = self.working_tree.path2id(self.working_tree.relpath(path))
1066
hash = self.working_tree.get_file_sha1(wt_relpath)
1075
hash = self.working_tree.get_file_sha1(file_id)
1067
1076
if hash is None:
1069
modified_hashes[wt_relpath] = hash
1078
modified_hashes[file_id] = hash
1070
1079
self.working_tree.set_merge_modified(modified_hashes)
1082
def parent(entry, file_id):
1074
1083
"""Determine the parent for a file_id (used as a key method)"""
1075
1084
if entry is None:
1077
1086
return entry.parent_id
1089
def name(entry, file_id):
1081
1090
"""Determine the name for a file_id (used as a key method)"""
1082
1091
if entry is None:
1084
1093
return entry.name
1087
def contents_sha1(tree, path):
1096
def contents_sha1(tree, file_id):
1088
1097
"""Determine the sha1 of the file contents (used as a key method)."""
1090
return tree.get_file_sha1(path)
1091
except errors.NoSuchFile:
1098
if not tree.has_id(file_id):
1100
return tree.get_file_sha1(file_id)
1095
def executable(tree, path):
1103
def executable(tree, file_id):
1096
1104
"""Determine the executability of a file-id (used as a key method)."""
1098
if tree.kind(path) != "file":
1100
except errors.NoSuchFile:
1105
if not tree.has_id(file_id):
1102
return tree.is_executable(path)
1107
if tree.kind(file_id) != "file":
1109
return tree.is_executable(file_id)
1105
def kind(tree, path):
1112
def kind(tree, file_id):
1106
1113
"""Determine the kind of a file-id (used as a key method)."""
1108
return tree.kind(path)
1109
except errors.NoSuchFile:
1114
if not tree.has_id(file_id):
1116
return tree.kind(file_id)
1113
1119
def _three_way(base, other, this):
1217
1245
parent_trans_id = transform.ROOT_PARENT
1219
1247
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1220
self.tt.adjust_path(name, parent_trans_id, trans_id)
1248
self.tt.adjust_path(name, parent_trans_id,
1249
self.tt.trans_id_file_id(file_id))
1222
def _do_merge_contents(self, paths, trans_id, file_id):
1251
def _do_merge_contents(self, file_id):
1223
1252
"""Performs a merge on file_id contents."""
1224
def contents_pair(tree, path):
1228
kind = tree.kind(path)
1229
except errors.NoSuchFile:
1253
def contents_pair(tree):
1254
if not tree.has_id(file_id):
1256
kind = tree.kind(file_id)
1231
1257
if kind == "file":
1232
contents = tree.get_file_sha1(path)
1258
contents = tree.get_file_sha1(file_id)
1233
1259
elif kind == "symlink":
1234
contents = tree.get_symlink_target(path)
1260
contents = tree.get_symlink_target(file_id)
1236
1262
contents = None
1237
1263
return kind, contents
1239
base_path, other_path, this_path = paths
1240
1265
# See SPOT run. run, SPOT, run.
1241
1266
# So we're not QUITE repeating ourselves; we do tricky things with
1243
other_pair = contents_pair(self.other_tree, other_path)
1244
this_pair = contents_pair(self.this_tree, this_path)
1268
base_pair = contents_pair(self.base_tree)
1269
other_pair = contents_pair(self.other_tree)
1245
1270
if self._lca_trees:
1246
(base_path, lca_paths) = base_path
1247
base_pair = contents_pair(self.base_tree, base_path)
1248
lca_pairs = [contents_pair(tree, path)
1249
for tree, path in zip(self._lca_trees, lca_paths)]
1271
this_pair = contents_pair(self.this_tree)
1272
lca_pairs = [contents_pair(tree) for tree in self._lca_trees]
1250
1273
winner = self._lca_multi_way((base_pair, lca_pairs), other_pair,
1251
1274
this_pair, allow_overriding_lca=False)
1253
base_pair = contents_pair(self.base_tree, base_path)
1254
1276
if base_pair == other_pair:
1255
1277
winner = 'this'
1257
1279
# We delayed evaluating this_pair as long as we can to avoid
1258
1280
# unnecessary sha1 calculation
1259
this_pair = contents_pair(self.this_tree, this_path)
1281
this_pair = contents_pair(self.this_tree)
1260
1282
winner = self._three_way(base_pair, other_pair, this_pair)
1261
1283
if winner == 'this':
1262
1284
# No interesting changes introduced by OTHER
1263
1285
return "unmodified"
1264
1286
# We have a hypothetical conflict, but if we have files, then we
1265
1287
# can try to merge the content
1266
params = MergeFileHookParams(
1267
self, (base_path, other_path, this_path), trans_id, this_pair[0],
1288
trans_id = self.tt.trans_id_file_id(file_id)
1289
params = MergeFileHookParams(self, file_id, trans_id, this_pair[0],
1268
1290
other_pair[0], winner)
1269
1291
hooks = self.active_hooks
1270
1292
hook_status = 'not_applicable'
1379
1401
# BASE is a file, or both converted to files, so at least we
1380
1402
# have agreement that output should be a file.
1382
self.text_merge(merge_hook_params.trans_id,
1383
merge_hook_params.paths)
1404
self.text_merge(merge_hook_params.file_id,
1405
merge_hook_params.trans_id)
1384
1406
except errors.BinaryFile:
1385
1407
return 'not_applicable', None
1386
1408
return 'done', None
1388
1410
return 'not_applicable', None
1390
def get_lines(self, tree, path):
1412
def get_lines(self, tree, file_id):
1391
1413
"""Return the lines in a file, or an empty list."""
1395
kind = tree.kind(path)
1396
except errors.NoSuchFile:
1414
if tree.has_id(file_id):
1415
return tree.get_file_lines(file_id)
1401
return tree.get_file_lines(path)
1403
def text_merge(self, trans_id, paths):
1404
"""Perform a three-way text merge on a file"""
1419
def text_merge(self, file_id, trans_id):
1420
"""Perform a three-way text merge on a file_id"""
1405
1421
# it's possible that we got here with base as a different type.
1406
1422
# if so, we just want two-way text conflicts.
1407
base_path, other_path, this_path = paths
1408
base_lines = self.get_lines(self.base_tree, base_path)
1409
other_lines = self.get_lines(self.other_tree, other_path)
1410
this_lines = self.get_lines(self.this_tree, this_path)
1423
if self.base_tree.has_id(file_id) and \
1424
self.base_tree.kind(file_id) == "file":
1425
base_lines = self.get_lines(self.base_tree, file_id)
1428
other_lines = self.get_lines(self.other_tree, file_id)
1429
this_lines = self.get_lines(self.this_tree, file_id)
1411
1430
m3 = merge3.Merge3(base_lines, this_lines, other_lines,
1412
1431
is_cherrypick=self.cherrypick)
1413
start_marker = b"!START OF MERGE CONFLICT!" + b"I HOPE THIS IS UNIQUE"
1432
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1414
1433
if self.show_base is True:
1415
base_marker = b'|' * 7
1434
base_marker = '|' * 7
1417
1436
base_marker = None
1419
1438
def iter_merge3(retval):
1420
1439
retval["text_conflicts"] = False
1421
for line in m3.merge_lines(name_a=b"TREE",
1422
name_b=b"MERGE-SOURCE",
1423
name_base=b"BASE-REVISION",
1440
for line in m3.merge_lines(name_a = "TREE",
1441
name_b = "MERGE-SOURCE",
1442
name_base = "BASE-REVISION",
1424
1443
start_marker=start_marker,
1425
1444
base_marker=base_marker,
1426
1445
reprocess=self.reprocess):
1427
1446
if line.startswith(start_marker):
1428
1447
retval["text_conflicts"] = True
1429
yield line.replace(start_marker, b'<' * 7)
1448
yield line.replace(start_marker, '<' * 7)
1436
1455
self._raw_conflicts.append(('text conflict', trans_id))
1437
1456
name = self.tt.final_name(trans_id)
1438
1457
parent_id = self.tt.final_parent(trans_id)
1439
file_group = self._dump_conflicts(
1440
name, paths, parent_id,
1441
lines=(base_lines, other_lines, this_lines))
1458
file_group = self._dump_conflicts(name, parent_id, file_id,
1459
this_lines, base_lines,
1442
1461
file_group.append(trans_id)
1444
def _get_filter_tree_path(self, path):
1464
def _get_filter_tree_path(self, file_id):
1445
1465
if self.this_tree.supports_content_filtering():
1446
1466
# We get the path from the working tree if it exists.
1447
1467
# That fails though when OTHER is adding a file, so
1448
1468
# we fall back to the other tree to find the path if
1449
1469
# it doesn't exist locally.
1450
filter_path = _mod_tree.find_previous_path(
1451
self.other_tree, self.working_tree, path)
1452
if filter_path is None:
1455
# Skip the lookup for older formats
1471
return self.this_tree.id2path(file_id)
1472
except errors.NoSuchId:
1473
return self.other_tree.id2path(file_id)
1474
# Skip the id2path lookup for older formats
1458
def _dump_conflicts(self, name, paths, parent_id, lines=None,
1477
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
1478
base_lines=None, other_lines=None, set_version=False,
1459
1479
no_base=False):
1460
1480
"""Emit conflict files.
1461
1481
If this_lines, base_lines, or other_lines are omitted, they will be
1462
1482
determined automatically. If set_version is true, the .OTHER, .THIS
1463
1483
or .BASE (in that order) will be created as versioned files.
1465
base_path, other_path, this_path = paths
1467
base_lines, other_lines, this_lines = lines
1469
base_lines = other_lines = this_lines = None
1470
data = [('OTHER', self.other_tree, other_path, other_lines),
1471
('THIS', self.this_tree, this_path, this_lines)]
1485
data = [('OTHER', self.other_tree, other_lines),
1486
('THIS', self.this_tree, this_lines)]
1472
1487
if not no_base:
1473
data.append(('BASE', self.base_tree, base_path, base_lines))
1488
data.append(('BASE', self.base_tree, base_lines))
1475
1490
# We need to use the actual path in the working tree of the file here,
1476
if self.this_tree.supports_content_filtering():
1477
filter_tree_path = this_path
1491
# ignoring the conflict suffixes
1493
if wt.supports_content_filtering():
1495
filter_tree_path = wt.id2path(file_id)
1496
except errors.NoSuchId:
1497
# file has been deleted
1498
filter_tree_path = None
1500
# Skip the id2path lookup for older formats
1479
1501
filter_tree_path = None
1481
1504
file_group = []
1482
for suffix, tree, path, lines in data:
1483
if path is not None:
1484
trans_id = self._conflict_file(
1485
name, parent_id, path, tree, suffix, lines,
1505
for suffix, tree, lines in data:
1506
if tree.has_id(file_id):
1507
trans_id = self._conflict_file(name, parent_id, tree, file_id,
1508
suffix, lines, filter_tree_path)
1487
1509
file_group.append(trans_id)
1510
if set_version and not versioned:
1511
self.tt.version_file(file_id, trans_id)
1488
1513
return file_group
1490
def _conflict_file(self, name, parent_id, path, tree, suffix,
1515
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1491
1516
lines=None, filter_tree_path=None):
1492
1517
"""Emit a single conflict file."""
1493
1518
name = name + '.' + suffix
1494
1519
trans_id = self.tt.create_path(name, parent_id)
1495
transform.create_from_tree(
1496
self.tt, trans_id, tree, path,
1498
filter_tree_path=filter_tree_path)
1520
transform.create_from_tree(self.tt, trans_id, tree, file_id, lines,
1499
1522
return trans_id
1501
def _merge_executable(self, paths, trans_id, executable, file_status,
1524
def merge_executable(self, file_id, file_status):
1525
"""Perform a merge on the execute bit."""
1526
executable = [self.executable(t, file_id) for t in (self.base_tree,
1527
self.other_tree, self.this_tree)]
1528
self._merge_executable(file_id, executable, file_status,
1529
resolver=self._three_way)
1531
def _merge_executable(self, file_id, executable, file_status,
1503
1533
"""Perform a merge on the execute bit."""
1504
1534
base_executable, other_executable, this_executable = executable
1505
base_path, other_path, this_path = paths
1506
1535
if file_status == "deleted":
1508
1537
winner = resolver(*executable)
1509
1538
if winner == "conflict":
1510
# There must be a None in here, if we have a conflict, but we
1511
# need executability since file status was not deleted.
1512
if other_path is None:
1539
# There must be a None in here, if we have a conflict, but we
1540
# need executability since file status was not deleted.
1541
if self.executable(self.other_tree, file_id) is None:
1513
1542
winner = "this"
1515
1544
winner = "other"
1516
1545
if winner == 'this' and file_status != "modified":
1547
trans_id = self.tt.trans_id_file_id(file_id)
1518
1548
if self.tt.final_kind(trans_id) != "file":
1520
1550
if winner == "this":
1521
1551
executability = this_executable
1523
if other_path is not None:
1553
if self.other_tree.has_id(file_id):
1524
1554
executability = other_executable
1525
elif this_path is not None:
1555
elif self.this_tree.has_id(file_id):
1526
1556
executability = this_executable
1527
elif base_path is not None:
1557
elif self.base_tree_has_id(file_id):
1528
1558
executability = base_executable
1529
1559
if executability is not None:
1560
trans_id = self.tt.trans_id_file_id(file_id)
1530
1561
self.tt.set_executability(executability, trans_id)
1532
1563
def cook_conflicts(self, fs_conflicts):
1533
1564
"""Convert all conflicts into a form that doesn't depend on trans_id"""
1534
self.cooked_conflicts = list(self.tt.cook_conflicts(
1535
list(fs_conflicts) + self._raw_conflicts))
1565
content_conflict_file_ids = set()
1566
cooked_conflicts = transform.cook_conflicts(fs_conflicts, self.tt)
1567
fp = transform.FinalPaths(self.tt)
1568
for conflict in self._raw_conflicts:
1569
conflict_type = conflict[0]
1570
if conflict_type == 'path conflict':
1572
this_parent, this_name,
1573
other_parent, other_name) = conflict[1:]
1574
if this_parent is None or this_name is None:
1575
this_path = '<deleted>'
1577
parent_path = fp.get_path(
1578
self.tt.trans_id_file_id(this_parent))
1579
this_path = osutils.pathjoin(parent_path, this_name)
1580
if other_parent is None or other_name is None:
1581
other_path = '<deleted>'
1583
if other_parent == self.other_tree.get_root_id():
1584
# The tree transform doesn't know about the other root,
1585
# so we special case here to avoid a NoFinalPath
1589
parent_path = fp.get_path(
1590
self.tt.trans_id_file_id(other_parent))
1591
other_path = osutils.pathjoin(parent_path, other_name)
1592
c = _mod_conflicts.Conflict.factory(
1593
'path conflict', path=this_path,
1594
conflict_path=other_path,
1596
elif conflict_type == 'contents conflict':
1597
for trans_id in conflict[1]:
1598
file_id = self.tt.final_file_id(trans_id)
1599
if file_id is not None:
1600
# Ok we found the relevant file-id
1602
path = fp.get_path(trans_id)
1603
for suffix in ('.BASE', '.THIS', '.OTHER'):
1604
if path.endswith(suffix):
1605
# Here is the raw path
1606
path = path[:-len(suffix)]
1608
c = _mod_conflicts.Conflict.factory(conflict_type,
1609
path=path, file_id=file_id)
1610
content_conflict_file_ids.add(file_id)
1611
elif conflict_type == 'text conflict':
1612
trans_id = conflict[1]
1613
path = fp.get_path(trans_id)
1614
file_id = self.tt.final_file_id(trans_id)
1615
c = _mod_conflicts.Conflict.factory(conflict_type,
1616
path=path, file_id=file_id)
1618
raise AssertionError('bad conflict type: %r' % (conflict,))
1619
cooked_conflicts.append(c)
1621
self.cooked_conflicts = []
1622
# We want to get rid of path conflicts when a corresponding contents
1623
# conflict exists. This can occur when one branch deletes a file while
1624
# the other renames *and* modifies it. In this case, the content
1625
# conflict is enough.
1626
for c in cooked_conflicts:
1627
if (c.typestring == 'path conflict'
1628
and c.file_id in content_conflict_file_ids):
1630
self.cooked_conflicts.append(c)
1631
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1538
1634
class WeaveMerger(Merge3Merger):
1589
1683
self._raw_conflicts.append(('text conflict', trans_id))
1590
1684
name = self.tt.final_name(trans_id)
1591
1685
parent_id = self.tt.final_parent(trans_id)
1592
file_group = self._dump_conflicts(name, paths, parent_id,
1593
(base_lines, None, None),
1686
file_group = self._dump_conflicts(name, parent_id, file_id,
1688
base_lines=base_lines)
1595
1689
file_group.append(trans_id)
1598
1692
class LCAMerger(WeaveMerger):
1600
requires_file_merge_plan = True
1602
def _generate_merge_plan(self, this_path, base):
1603
return self.this_tree.plan_file_lca_merge(this_path, self.other_tree,
1694
def _generate_merge_plan(self, file_id, base):
1695
return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1607
1698
class Diff3Merger(Merge3Merger):
1608
1699
"""Three-way merger using external diff3 for text merging"""
1610
requires_file_merge_plan = False
1612
def dump_file(self, temp_dir, name, tree, path):
1701
def dump_file(self, temp_dir, name, tree, file_id):
1613
1702
out_path = osutils.pathjoin(temp_dir, name)
1614
with open(out_path, "wb") as out_file:
1615
in_file = tree.get_file(path)
1703
out_file = open(out_path, "wb")
1705
in_file = tree.get_file(file_id)
1616
1706
for line in in_file:
1617
1707
out_file.write(line)
1618
1710
return out_path
1620
def text_merge(self, trans_id, paths):
1712
def text_merge(self, file_id, trans_id):
1621
1713
"""Perform a diff3 merge using a specified file-id and trans-id.
1622
1714
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1623
1715
will be dumped, and a will be conflict noted.
1625
1717
import breezy.patch
1626
base_path, other_path, this_path = paths
1627
1718
temp_dir = osutils.mkdtemp(prefix="bzr-")
1629
1720
new_file = osutils.pathjoin(temp_dir, "new")
1630
this = self.dump_file(
1631
temp_dir, "this", self.this_tree, this_path)
1632
base = self.dump_file(
1633
temp_dir, "base", self.base_tree, base_path)
1634
other = self.dump_file(
1635
temp_dir, "other", self.other_tree, other_path)
1721
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1722
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1723
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1636
1724
status = breezy.patch.diff3(new_file, this, base, other)
1637
1725
if status not in (0, 1):
1638
1726
raise errors.BzrError("Unhandled diff3 exit code")
1639
with open(new_file, 'rb') as f:
1727
f = open(new_file, 'rb')
1640
1729
self.tt.create_file(f, trans_id)
1641
1732
if status == 1:
1642
1733
name = self.tt.final_name(trans_id)
1643
1734
parent_id = self.tt.final_parent(trans_id)
1644
self._dump_conflicts(name, paths, parent_id)
1735
self._dump_conflicts(name, parent_id, file_id)
1645
1736
self._raw_conflicts.append(('text conflict', trans_id))
1647
1738
osutils.rmtree(temp_dir)
1757
1848
super(MergeIntoMergeType, self).__init__(*args, **kwargs)
1759
1850
def _compute_transform(self):
1760
with ui.ui_factory.nested_progress_bar() as child_pb:
1851
child_pb = ui.ui_factory.nested_progress_bar()
1761
1853
entries = self._entries_to_incorporate()
1762
1854
entries = list(entries)
1763
for num, (entry, parent_id, relpath) in enumerate(entries):
1764
child_pb.update(gettext('Preparing file merge'),
1855
for num, (entry, parent_id) in enumerate(entries):
1856
child_pb.update(gettext('Preparing file merge'), num, len(entries))
1766
1857
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1767
path = osutils.pathjoin(self._source_subpath, relpath)
1768
trans_id = transform.new_by_entry(path, self.tt, entry,
1769
parent_trans_id, self.other_tree)
1858
trans_id = transform.new_by_entry(self.tt, entry,
1859
parent_trans_id, self.other_tree)
1770
1862
self._finish_computing_transform()
1772
1864
def _entries_to_incorporate(self):
1773
1865
"""Yields pairs of (inventory_entry, new_parent)."""
1774
subdir_id = self.other_tree.path2id(self._source_subpath)
1866
other_inv = self.other_tree.root_inventory
1867
subdir_id = other_inv.path2id(self._source_subpath)
1775
1868
if subdir_id is None:
1776
1869
# XXX: The error would be clearer if it gave the URL of the source
1777
1870
# branch, but we don't have a reference to that here.
1778
1871
raise PathNotInTree(self._source_subpath, "Source tree")
1779
subdir = next(self.other_tree.iter_entries_by_dir(
1780
specific_files=[self._source_subpath]))[1]
1872
subdir = other_inv[subdir_id]
1781
1873
parent_in_target = osutils.dirname(self._target_subdir)
1782
1874
target_id = self.this_tree.path2id(parent_in_target)
1783
1875
if target_id is None: