759
757
def do_merge(self):
760
with cleanup.ExitStack() as stack:
758
with contextlib.ExitStack() as stack:
761
759
stack.enter_context(self.working_tree.lock_tree_write())
762
760
stack.enter_context(self.this_tree.lock_read())
763
761
stack.enter_context(self.base_tree.lock_read())
764
762
stack.enter_context(self.other_tree.lock_read())
765
self.tt = self.working_tree.get_transform()
763
self.tt = self.working_tree.transform()
766
764
stack.enter_context(self.tt)
767
765
self._compute_transform()
768
766
results = self.tt.apply(no_conflicts=True)
775
773
def make_preview_transform(self):
776
774
with self.base_tree.lock_read(), self.other_tree.lock_read():
777
self.tt = transform.TransformPreview(self.working_tree)
775
self.tt = self.working_tree.preview_transform()
778
776
self._compute_transform()
781
779
def _compute_transform(self):
782
780
if self._lca_trees is None:
783
entries = self._entries3()
781
entries = list(self._entries3())
784
782
resolver = self._three_way
786
entries = self._entries_lca()
784
entries = list(self._entries_lca())
787
785
resolver = self._lca_multi_way
788
786
# Prepare merge hooks
789
787
factories = Merger.hooks['merge_file_content']
792
790
self.active_hooks = [hook for hook in hooks if hook is not None]
793
791
with ui.ui_factory.nested_progress_bar() as child_pb:
794
792
for num, (file_id, changed, paths3, parents3, names3,
795
executable3) in enumerate(entries):
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)
796
802
trans_id = self.tt.trans_id_file_id(file_id)
798
803
# Try merging each entry
799
804
child_pb.update(gettext('Preparing file merge'),
800
805
num, len(entries))
863
867
names3 = change.name + (this_name,)
864
868
paths3 = change.path + (this_path, )
865
869
executable3 = change.executable + (this_executable,)
867
871
(change.file_id, change.changed_content, paths3,
868
parents3, names3, executable3))
872
parents3, names3, executable3, change.copied))
871
874
def _entries_lca(self):
872
875
"""Gather data about files modified between multiple trees.
877
880
For the multi-valued entries, the format will be (BASE, [lca1, lca2])
879
:return: [(file_id, changed, paths, parents, names, executable)], where:
882
:return: [(file_id, changed, paths, parents, names, executable, copied)], where:
881
884
* file_id: Simple file_id of the entry
882
885
* changed: Boolean, True if the kind or contents changed else False
1047
1049
((base_ie.name, lca_names),
1048
1050
other_ie.name, this_ie.name),
1049
1051
((base_ie.executable, lca_executable),
1050
other_ie.executable, this_ie.executable)
1052
other_ie.executable, this_ie.executable),
1053
# Copy detection is not yet supported, so nothing is
1054
1058
def write_modified(self, results):
1055
1059
if not self.working_tree.supports_merge_modified():
1311
1315
# This is a contents conflict, because none of the available
1312
1316
# functions could merge it.
1313
1317
file_group = self._dump_conflicts(
1314
name, (base_path, other_path, this_path), parent_id,
1315
file_id, set_version=True)
1318
name, (base_path, other_path, this_path), parent_id)
1319
for tid in file_group:
1320
self.tt.version_file(tid, file_id=file_id)
1316
1322
self._raw_conflicts.append(('contents conflict', file_group))
1317
1323
elif hook_status == 'success':
1318
1324
self.tt.create_file(lines, trans_id)
1324
1330
name = self.tt.final_name(trans_id)
1325
1331
parent_id = self.tt.final_parent(trans_id)
1326
1332
self._dump_conflicts(
1327
name, (base_path, other_path, this_path), parent_id, file_id)
1333
name, (base_path, other_path, this_path), parent_id)
1328
1334
elif hook_status == 'delete':
1329
1335
self.tt.unversion_file(trans_id)
1330
1336
result = "deleted"
1430
1436
self._raw_conflicts.append(('text conflict', trans_id))
1431
1437
name = self.tt.final_name(trans_id)
1432
1438
parent_id = self.tt.final_parent(trans_id)
1433
file_id = self.tt.final_file_id(trans_id)
1434
file_group = self._dump_conflicts(name, paths, parent_id, file_id,
1435
this_lines, base_lines,
1439
file_group = self._dump_conflicts(
1440
name, paths, parent_id,
1441
lines=(base_lines, other_lines, this_lines))
1437
1442
file_group.append(trans_id)
1439
1444
def _get_filter_tree_path(self, path):
1450
1455
# Skip the lookup for older formats
1453
def _dump_conflicts(self, name, paths, parent_id, file_id, this_lines=None,
1454
base_lines=None, other_lines=None, set_version=False,
1458
def _dump_conflicts(self, name, paths, parent_id, lines=None,
1455
1459
no_base=False):
1456
1460
"""Emit conflict files.
1457
1461
If this_lines, base_lines, or other_lines are omitted, they will be
1459
1463
or .BASE (in that order) will be created as versioned files.
1461
1465
base_path, other_path, this_path = paths
1467
base_lines, other_lines, this_lines = lines
1469
base_lines = other_lines = this_lines = None
1462
1470
data = [('OTHER', self.other_tree, other_path, other_lines),
1463
1471
('THIS', self.this_tree, this_path, this_lines)]
1464
1472
if not no_base:
1465
1473
data.append(('BASE', self.base_tree, base_path, base_lines))
1467
1475
# We need to use the actual path in the working tree of the file here,
1468
# ignoring the conflict suffixes
1470
if wt.supports_content_filtering():
1472
filter_tree_path = wt.id2path(file_id)
1473
except errors.NoSuchId:
1474
# file has been deleted
1475
filter_tree_path = None
1476
if self.this_tree.supports_content_filtering():
1477
filter_tree_path = this_path
1477
# Skip the id2path lookup for older formats
1478
1479
filter_tree_path = None
1481
1481
file_group = []
1482
1482
for suffix, tree, path, lines in data:
1483
1483
if path is not None:
1485
1485
name, parent_id, path, tree, suffix, lines,
1486
1486
filter_tree_path)
1487
1487
file_group.append(trans_id)
1488
if set_version and not versioned:
1489
self.tt.version_file(file_id, trans_id)
1491
1488
return file_group
1493
1490
def _conflict_file(self, name, parent_id, path, tree, suffix,
1535
1532
def cook_conflicts(self, fs_conflicts):
1536
1533
"""Convert all conflicts into a form that doesn't depend on trans_id"""
1537
content_conflict_file_ids = set()
1538
cooked_conflicts = transform.cook_conflicts(fs_conflicts, self.tt)
1539
fp = transform.FinalPaths(self.tt)
1540
for conflict in self._raw_conflicts:
1541
conflict_type = conflict[0]
1542
if conflict_type == 'path conflict':
1544
this_parent, this_name,
1545
other_parent, other_name) = conflict[1:]
1546
if this_parent is None or this_name is None:
1547
this_path = '<deleted>'
1549
parent_path = fp.get_path(
1550
self.tt.trans_id_file_id(this_parent))
1551
this_path = osutils.pathjoin(parent_path, this_name)
1552
if other_parent is None or other_name is None:
1553
other_path = '<deleted>'
1555
if other_parent == self.other_tree.path2id(''):
1556
# The tree transform doesn't know about the other root,
1557
# so we special case here to avoid a NoFinalPath
1561
parent_path = fp.get_path(
1562
self.tt.trans_id_file_id(other_parent))
1563
other_path = osutils.pathjoin(parent_path, other_name)
1564
c = _mod_conflicts.Conflict.factory(
1565
'path conflict', path=this_path,
1566
conflict_path=other_path,
1568
elif conflict_type == 'contents conflict':
1569
for trans_id in conflict[1]:
1570
file_id = self.tt.final_file_id(trans_id)
1571
if file_id is not None:
1572
# Ok we found the relevant file-id
1574
path = fp.get_path(trans_id)
1575
for suffix in ('.BASE', '.THIS', '.OTHER'):
1576
if path.endswith(suffix):
1577
# Here is the raw path
1578
path = path[:-len(suffix)]
1580
c = _mod_conflicts.Conflict.factory(conflict_type,
1581
path=path, file_id=file_id)
1582
content_conflict_file_ids.add(file_id)
1583
elif conflict_type == 'text conflict':
1584
trans_id = conflict[1]
1585
path = fp.get_path(trans_id)
1586
file_id = self.tt.final_file_id(trans_id)
1587
c = _mod_conflicts.Conflict.factory(conflict_type,
1588
path=path, file_id=file_id)
1590
raise AssertionError('bad conflict type: %r' % (conflict,))
1591
cooked_conflicts.append(c)
1593
self.cooked_conflicts = []
1594
# We want to get rid of path conflicts when a corresponding contents
1595
# conflict exists. This can occur when one branch deletes a file while
1596
# the other renames *and* modifies it. In this case, the content
1597
# conflict is enough.
1598
for c in cooked_conflicts:
1599
if (c.typestring == 'path conflict'
1600
and c.file_id in content_conflict_file_ids):
1602
self.cooked_conflicts.append(c)
1603
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1534
self.cooked_conflicts = list(self.tt.cook_conflicts(
1535
list(fs_conflicts) + self._raw_conflicts))
1606
1538
class WeaveMerger(Merge3Merger):
1657
1589
self._raw_conflicts.append(('text conflict', trans_id))
1658
1590
name = self.tt.final_name(trans_id)
1659
1591
parent_id = self.tt.final_parent(trans_id)
1660
file_id = self.tt.final_file_id(trans_id)
1661
file_group = self._dump_conflicts(name, paths, parent_id, file_id,
1663
base_lines=base_lines)
1592
file_group = self._dump_conflicts(name, paths, parent_id,
1593
(base_lines, None, None),
1664
1595
file_group.append(trans_id)