/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/merge.py

  • Committer: Jelmer Vernooij
  • Date: 2020-07-05 12:50:01 UTC
  • mfrom: (7490.40.46 work)
  • mto: (7490.40.48 work)
  • mto: This revision was merged to the branch mainline in revision 7519.
  • Revision ID: jelmer@jelmer.uk-20200705125001-7s3vo0p55szbbws7
Merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
import contextlib
 
17
from __future__ import absolute_import
18
18
 
19
19
from .lazy_import import lazy_import
20
20
lazy_import(globals(), """
22
22
 
23
23
from breezy import (
24
24
    branch as _mod_branch,
 
25
    cleanup,
25
26
    conflicts as _mod_conflicts,
26
27
    debug,
27
28
    graph as _mod_graph,
36
37
    workingtree,
37
38
    )
38
39
from breezy.bzr import (
39
 
    conflicts as _mod_bzr_conflicts,
40
40
    generate_ids,
41
41
    versionedfile,
42
42
    )
43
43
from breezy.i18n import gettext
44
44
""")
45
 
from breezy.bzr.conflicts import Conflict as BzrConflict
46
45
from . import (
47
46
    decorators,
48
47
    errors,
50
49
    registry,
51
50
    transform,
52
51
    )
 
52
from .sixish import (
 
53
    viewitems,
 
54
    )
53
55
# TODO: Report back as changes are merged in
54
56
 
55
57
 
445
447
    def _add_parent(self):
446
448
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
447
449
        new_parent_trees = []
448
 
        with contextlib.ExitStack() as stack:
 
450
        with cleanup.ExitStack() as stack:
449
451
            for revision_id in new_parents:
450
452
                try:
451
453
                    tree = self.revision_tree(revision_id)
652
654
        return merge
653
655
 
654
656
    def do_merge(self):
655
 
        with contextlib.ExitStack() as stack:
 
657
        with cleanup.ExitStack() as stack:
656
658
            stack.enter_context(self.this_tree.lock_tree_write())
657
659
            if self.base_tree is not None:
658
660
                stack.enter_context(self.base_tree.lock_read())
755
757
            self.do_merge()
756
758
 
757
759
    def do_merge(self):
758
 
        with contextlib.ExitStack() as stack:
 
760
        with cleanup.ExitStack() as stack:
759
761
            stack.enter_context(self.working_tree.lock_tree_write())
760
762
            stack.enter_context(self.this_tree.lock_read())
761
763
            stack.enter_context(self.base_tree.lock_read())
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):
794
 
                if copied:
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)
800
 
                    changed = True
801
 
                    copied = False
 
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,)
870
864
            yield (
871
865
                (change.file_id, change.changed_content, paths3,
872
 
                 parents3, names3, executable3, change.copied))
 
866
                 parents3, names3, executable3))
873
867
 
874
868
    def _entries_lca(self):
875
869
        """Gather data about files modified between multiple trees.
879
873
 
880
874
        For the multi-valued entries, the format will be (BASE, [lca1, lca2])
881
875
 
882
 
        :return: [(file_id, changed, paths, parents, names, executable, copied)], where:
 
876
        :return: [(file_id, changed, paths, parents, names, executable)], where:
883
877
 
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
1054
 
                           # a copy:
1055
 
                           False
 
1046
                            other_ie.executable, this_ie.executable)
1056
1047
                           )
1057
1048
 
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)
1321
 
                    break
 
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,
 
1431
                                              other_lines)
1442
1432
            file_group.append(trans_id)
1443
1433
 
1444
1434
    def _get_filter_tree_path(self, path):
1455
1445
        # Skip the lookup for older formats
1456
1446
        return None
1457
1447
 
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.
1464
1455
        """
1465
1456
        base_path, other_path, this_path = paths
1466
 
        if lines:
1467
 
            base_lines, other_lines, this_lines = lines
1468
 
        else:
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))
1474
1461
 
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
 
1464
        wt = self.this_tree
 
1465
        if wt.supports_content_filtering():
 
1466
            try:
 
1467
                filter_tree_path = wt.id2path(file_id)
 
1468
            except errors.NoSuchId:
 
1469
                # file has been deleted
 
1470
                filter_tree_path = None
1478
1471
        else:
 
1472
            # Skip the id2path lookup for older formats
1479
1473
            filter_tree_path = None
1480
1474
 
 
1475
        versioned = False
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)
 
1485
                    versioned = True
1488
1486
        return file_group
1489
1487
 
1490
1488
    def _conflict_file(self, name, parent_id, path, tree, suffix,
1531
1529
 
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':
 
1538
                (trans_id, file_id,
 
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>'
 
1543
                else:
 
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>'
 
1549
                else:
 
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
 
1553
                        # exception
 
1554
                        parent_path = ''
 
1555
                    else:
 
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,
 
1562
                    file_id=file_id)
 
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
 
1568
                        break
 
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)]
 
1574
                        break
 
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)
 
1584
            else:
 
1585
                raise AssertionError('bad conflict type: %r' % (conflict,))
 
1586
            cooked_conflicts.append(c)
 
1587
 
 
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):
 
1596
                continue
 
1597
            self.cooked_conflicts.append(c)
 
1598
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1536
1599
 
1537
1600
 
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),
1594
 
                                              no_base=False)
 
1655
            file_id = self.tt.final_file_id(trans_id)
 
1656
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
 
1657
                                              no_base=False,
 
1658
                                              base_lines=base_lines)
1595
1659
            file_group.append(trans_id)
1596
1660
 
1597
1661
 
1641
1705
            if status == 1:
1642
1706
                name = self.tt.final_name(trans_id)
1643
1707
                parent_id = self.tt.final_parent(trans_id)
1644
 
                self._dump_conflicts(name, paths, parent_id)
 
1708
                file_id = self.tt.final_file_id(trans_id)
 
1709
                self._dump_conflicts(name, paths, parent_id, file_id)
1645
1710
                self._raw_conflicts.append(('text conflict', trans_id))
1646
1711
        finally:
1647
1712
            osutils.rmtree(temp_dir)
2185
2250
        filtered_parent_map = {}
2186
2251
        child_map = {}
2187
2252
        tails = []
2188
 
        for key, parent_keys in parent_map.items():
 
2253
        for key, parent_keys in viewitems(parent_map):
2189
2254
            culled_parent_keys = [p for p in parent_keys if p in parent_map]
2190
2255
            if not culled_parent_keys:
2191
2256
                tails.append(key)