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