790
792
self.active_hooks = [hook for hook in hooks if hook is not None]
791
793
with ui.ui_factory.nested_progress_bar() as child_pb:
792
794
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)
795
executable3) in enumerate(entries):
802
796
trans_id = self.tt.trans_id_file_id(file_id)
803
797
# Try merging each entry
804
798
child_pb.update(gettext('Preparing file merge'),
869
863
executable3 = change.executable + (this_executable,)
871
865
(change.file_id, change.changed_content, paths3,
872
parents3, names3, executable3, change.copied))
866
parents3, names3, executable3))
874
868
def _entries_lca(self):
875
869
"""Gather data about files modified between multiple trees.
880
874
For the multi-valued entries, the format will be (BASE, [lca1, lca2])
882
:return: [(file_id, changed, paths, parents, names, executable, copied)], where:
876
:return: [(file_id, changed, paths, parents, names, executable)], where:
884
878
* file_id: Simple file_id of the entry
885
879
* changed: Boolean, True if the kind or contents changed else False
1049
1043
((base_ie.name, lca_names),
1050
1044
other_ie.name, this_ie.name),
1051
1045
((base_ie.executable, lca_executable),
1052
other_ie.executable, this_ie.executable),
1053
# Copy detection is not yet supported, so nothing is
1046
other_ie.executable, this_ie.executable)
1058
1049
def write_modified(self, results):
1315
1306
# This is a contents conflict, because none of the available
1316
1307
# functions could merge it.
1317
1308
file_group = self._dump_conflicts(
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)
1309
name, (base_path, other_path, this_path), parent_id,
1310
file_id, set_version=True)
1322
1311
self._raw_conflicts.append(('contents conflict', file_group))
1323
1312
elif hook_status == 'success':
1324
1313
self.tt.create_file(lines, trans_id)
1330
1319
name = self.tt.final_name(trans_id)
1331
1320
parent_id = self.tt.final_parent(trans_id)
1332
1321
self._dump_conflicts(
1333
name, (base_path, other_path, this_path), parent_id)
1322
name, (base_path, other_path, this_path), parent_id, file_id)
1334
1323
elif hook_status == 'delete':
1335
1324
self.tt.unversion_file(trans_id)
1336
1325
result = "deleted"
1436
1425
self._raw_conflicts.append(('text conflict', trans_id))
1437
1426
name = self.tt.final_name(trans_id)
1438
1427
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))
1428
file_id = self.tt.final_file_id(trans_id)
1429
file_group = self._dump_conflicts(name, paths, parent_id, file_id,
1430
this_lines, base_lines,
1442
1432
file_group.append(trans_id)
1444
1434
def _get_filter_tree_path(self, path):
1455
1445
# Skip the lookup for older formats
1458
def _dump_conflicts(self, name, paths, parent_id, lines=None,
1448
def _dump_conflicts(self, name, paths, parent_id, file_id, this_lines=None,
1449
base_lines=None, other_lines=None, set_version=False,
1459
1450
no_base=False):
1460
1451
"""Emit conflict files.
1461
1452
If this_lines, base_lines, or other_lines are omitted, they will be
1463
1454
or .BASE (in that order) will be created as versioned files.
1465
1456
base_path, other_path, this_path = paths
1467
base_lines, other_lines, this_lines = lines
1469
base_lines = other_lines = this_lines = None
1470
1457
data = [('OTHER', self.other_tree, other_path, other_lines),
1471
1458
('THIS', self.this_tree, this_path, this_lines)]
1472
1459
if not no_base:
1473
1460
data.append(('BASE', self.base_tree, base_path, base_lines))
1475
1462
# 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
1463
# ignoring the conflict suffixes
1465
if wt.supports_content_filtering():
1467
filter_tree_path = wt.id2path(file_id)
1468
except errors.NoSuchId:
1469
# file has been deleted
1470
filter_tree_path = None
1472
# Skip the id2path lookup for older formats
1479
1473
filter_tree_path = None
1481
1476
file_group = []
1482
1477
for suffix, tree, path, lines in data:
1483
1478
if path is not None:
1485
1480
name, parent_id, path, tree, suffix, lines,
1486
1481
filter_tree_path)
1487
1482
file_group.append(trans_id)
1483
if set_version and not versioned:
1484
self.tt.version_file(trans_id, file_id=file_id)
1488
1486
return file_group
1490
1488
def _conflict_file(self, name, parent_id, path, tree, suffix,
1532
1530
def cook_conflicts(self, fs_conflicts):
1533
1531
"""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))
1532
content_conflict_file_ids = set()
1533
cooked_conflicts = transform.cook_conflicts(fs_conflicts, self.tt)
1534
fp = transform.FinalPaths(self.tt)
1535
for conflict in self._raw_conflicts:
1536
conflict_type = conflict[0]
1537
if conflict_type == 'path conflict':
1539
this_parent, this_name,
1540
other_parent, other_name) = conflict[1:]
1541
if this_parent is None or this_name is None:
1542
this_path = '<deleted>'
1544
parent_path = fp.get_path(
1545
self.tt.trans_id_file_id(this_parent))
1546
this_path = osutils.pathjoin(parent_path, this_name)
1547
if other_parent is None or other_name is None:
1548
other_path = '<deleted>'
1550
if other_parent == self.other_tree.path2id(''):
1551
# The tree transform doesn't know about the other root,
1552
# so we special case here to avoid a NoFinalPath
1556
parent_path = fp.get_path(
1557
self.tt.trans_id_file_id(other_parent))
1558
other_path = osutils.pathjoin(parent_path, other_name)
1559
c = _mod_conflicts.Conflict.factory(
1560
'path conflict', path=this_path,
1561
conflict_path=other_path,
1563
elif conflict_type == 'contents conflict':
1564
for trans_id in conflict[1]:
1565
file_id = self.tt.final_file_id(trans_id)
1566
if file_id is not None:
1567
# Ok we found the relevant file-id
1569
path = fp.get_path(trans_id)
1570
for suffix in ('.BASE', '.THIS', '.OTHER'):
1571
if path.endswith(suffix):
1572
# Here is the raw path
1573
path = path[:-len(suffix)]
1575
c = _mod_conflicts.Conflict.factory(conflict_type,
1576
path=path, file_id=file_id)
1577
content_conflict_file_ids.add(file_id)
1578
elif conflict_type == 'text conflict':
1579
trans_id = conflict[1]
1580
path = fp.get_path(trans_id)
1581
file_id = self.tt.final_file_id(trans_id)
1582
c = _mod_conflicts.Conflict.factory(conflict_type,
1583
path=path, file_id=file_id)
1585
raise AssertionError('bad conflict type: %r' % (conflict,))
1586
cooked_conflicts.append(c)
1588
self.cooked_conflicts = []
1589
# We want to get rid of path conflicts when a corresponding contents
1590
# conflict exists. This can occur when one branch deletes a file while
1591
# the other renames *and* modifies it. In this case, the content
1592
# conflict is enough.
1593
for c in cooked_conflicts:
1594
if (c.typestring == 'path conflict'
1595
and c.file_id in content_conflict_file_ids):
1597
self.cooked_conflicts.append(c)
1598
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1538
1601
class WeaveMerger(Merge3Merger):
1589
1652
self._raw_conflicts.append(('text conflict', trans_id))
1590
1653
name = self.tt.final_name(trans_id)
1591
1654
parent_id = self.tt.final_parent(trans_id)
1592
file_group = self._dump_conflicts(name, paths, parent_id,
1593
(base_lines, None, None),
1655
file_id = self.tt.final_file_id(trans_id)
1656
file_group = self._dump_conflicts(name, paths, parent_id, file_id,
1658
base_lines=base_lines)
1595
1659
file_group.append(trans_id)