/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 bzrlib/merge.py

  • Committer: John Arbash Meinel
  • Date: 2008-07-17 12:55:11 UTC
  • mfrom: (3556 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3557.
  • Revision ID: john@arbash-meinel.com-20080717125511-rjpil183ctky8l84
Merge bzr.dev 3556

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
 
18
import errno
 
19
from itertools import chain
18
20
import os
19
 
import errno
20
21
import warnings
21
22
 
22
23
from bzrlib import (
23
24
    debug,
24
25
    errors,
 
26
    graph as _mod_graph,
25
27
    osutils,
26
28
    patiencediff,
27
29
    registry,
28
30
    revision as _mod_revision,
 
31
    tsort,
29
32
    )
30
33
from bzrlib.branch import Branch
31
34
from bzrlib.conflicts import ConflictList, Conflict
42
45
                           WorkingTreeNotRevision,
43
46
                           BinaryFile,
44
47
                           )
 
48
from bzrlib.graph import Graph
45
49
from bzrlib.merge3 import Merge3
46
50
from bzrlib.osutils import rename, pathjoin
47
51
from progress import DummyProgress, ProgressPhase
257
261
    def compare_basis(self):
258
262
        try:
259
263
            basis_tree = self.revision_tree(self.this_tree.last_revision())
260
 
        except errors.RevisionNotPresent:
 
264
        except errors.NoSuchRevision:
261
265
            basis_tree = self.this_tree.basis_tree()
262
266
        changes = self.this_tree.changes_from(basis_tree)
263
267
        if not changes.has_changed():
277
281
        for revision_id in new_parents:
278
282
            try:
279
283
                tree = self.revision_tree(revision_id)
280
 
            except errors.RevisionNotPresent:
 
284
            except errors.NoSuchRevision:
281
285
                tree = None
282
286
            else:
283
287
                tree.lock_read()
419
423
            merge = self.make_merger()
420
424
            merge.do_merge()
421
425
            if self.recurse == 'down':
422
 
                for path, file_id in self.this_tree.iter_references():
423
 
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
 
426
                for relpath, file_id in self.this_tree.iter_references():
 
427
                    sub_tree = self.this_tree.get_nested_tree(file_id, relpath)
424
428
                    other_revision = self.other_tree.get_reference_revision(
425
 
                        file_id, path)
 
429
                        file_id, relpath)
426
430
                    if  other_revision == sub_tree.last_revision():
427
431
                        continue
428
432
                    sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
429
433
                    sub_merge.merge_type = self.merge_type
430
 
                    relpath = self.this_tree.relpath(path)
431
434
                    other_branch = self.other_branch.reference_parent(file_id, relpath)
432
435
                    sub_merge.set_other_revision(other_revision, other_branch)
433
436
                    base_revision = self.base_tree.get_reference_revision(file_id)
1241
1244
 
1242
1245
class _PlanMergeBase(object):
1243
1246
 
1244
 
    def __init__(self, a_rev, b_rev, vf):
 
1247
    def __init__(self, a_rev, b_rev, vf, key_prefix):
1245
1248
        """Contructor.
1246
1249
 
1247
1250
        :param a_rev: Revision-id of one revision to merge
1248
1251
        :param b_rev: Revision-id of the other revision to merge
1249
 
        :param vf: A versionedfile containing both revisions
 
1252
        :param vf: A VersionedFiles containing both revisions
 
1253
        :param key_prefix: A prefix for accessing keys in vf, typically
 
1254
            (file_id,).
1250
1255
        """
1251
1256
        self.a_rev = a_rev
1252
1257
        self.b_rev = b_rev
1253
 
        self.lines_a = vf.get_lines(a_rev)
1254
 
        self.lines_b = vf.get_lines(b_rev)
1255
1258
        self.vf = vf
1256
1259
        self._last_lines = None
1257
1260
        self._last_lines_revision_id = None
1258
1261
        self._cached_matching_blocks = {}
 
1262
        self._key_prefix = key_prefix
 
1263
        self._precache_tip_lines()
 
1264
 
 
1265
    def _precache_tip_lines(self):
 
1266
        lines = self.get_lines([self.a_rev, self.b_rev])
 
1267
        self.lines_a = lines[self.a_rev]
 
1268
        self.lines_b = lines[self.b_rev]
 
1269
 
 
1270
    def get_lines(self, revisions):
 
1271
        """Get lines for revisions from the backing VersionedFiles.
 
1272
        
 
1273
        :raises RevisionNotPresent: on absent texts.
 
1274
        """
 
1275
        keys = [(self._key_prefix + (rev,)) for rev in revisions]
 
1276
        result = {}
 
1277
        for record in self.vf.get_record_stream(keys, 'unordered', True):
 
1278
            if record.storage_kind == 'absent':
 
1279
                raise errors.RevisionNotPresent(record.key, self.vf)
 
1280
            result[record.key[-1]] = osutils.split_lines(
 
1281
                record.get_bytes_as('fulltext'))
 
1282
        return result
1259
1283
 
1260
1284
    def plan_merge(self):
1261
1285
        """Generate a 'plan' for merging the two revisions.
1309
1333
            return cached
1310
1334
        if self._last_lines_revision_id == left_revision:
1311
1335
            left_lines = self._last_lines
 
1336
            right_lines = self.get_lines([right_revision])[right_revision]
1312
1337
        else:
1313
 
            left_lines = self.vf.get_lines(left_revision)
1314
 
        right_lines = self.vf.get_lines(right_revision)
 
1338
            lines = self.get_lines([left_revision, right_revision])
 
1339
            left_lines = lines[left_revision]
 
1340
            right_lines = lines[right_revision]
1315
1341
        self._last_lines = right_lines
1316
1342
        self._last_lines_revision_id = right_revision
1317
1343
        matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1370
1396
class _PlanMerge(_PlanMergeBase):
1371
1397
    """Plan an annotate merge using on-the-fly annotation"""
1372
1398
 
1373
 
    def __init__(self, a_rev, b_rev, vf):
1374
 
       _PlanMergeBase.__init__(self, a_rev, b_rev, vf)
1375
 
       a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1376
 
       b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1377
 
       self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1378
 
 
1379
 
    def _determine_status(self, revision_id, unique_line_numbers):
1380
 
        """Determines the status unique lines versus all lcas.
1381
 
 
1382
 
        Basically, determines why the line is unique to this revision.
1383
 
 
1384
 
        A line may be determined new or killed, but not both.
1385
 
 
1386
 
        :param revision_id: The id of the revision in which the lines are
1387
 
            unique
1388
 
        :param unique_line_numbers: The line numbers of unique lines.
1389
 
        :return a tuple of (new_this, killed_other):
1390
 
        """
1391
 
        new = self._find_new(revision_id)
1392
 
        killed = set(unique_line_numbers).difference(new)
1393
 
        return new, killed
1394
 
 
1395
 
    def _find_new(self, version_id):
1396
 
        """Determine which lines are new in the ancestry of this version.
1397
 
 
1398
 
        If a lines is present in this version, and not present in any
1399
 
        common ancestor, it is considered new.
1400
 
        """
1401
 
        if version_id not in self.uncommon:
1402
 
            return set()
1403
 
        parents = self.vf.get_parent_map([version_id])[version_id]
1404
 
        if len(parents) == 0:
1405
 
            return set(range(len(self.vf.get_lines(version_id))))
1406
 
        new = None
1407
 
        for parent in parents:
1408
 
            blocks = self._get_matching_blocks(version_id, parent)
1409
 
            result, unused = self._unique_lines(blocks)
1410
 
            parent_new = self._find_new(parent)
1411
 
            for i, j, n in blocks:
1412
 
                for ii, jj in [(i+r, j+r) for r in range(n)]:
1413
 
                    if jj in parent_new:
1414
 
                        result.append(ii)
1415
 
            if new is None:
1416
 
                new = set(result)
1417
 
            else:
1418
 
                new.intersection_update(result)
1419
 
        return new
 
1399
    def __init__(self, a_rev, b_rev, vf, key_prefix):
 
1400
        super(_PlanMerge, self).__init__(a_rev, b_rev, vf, key_prefix)
 
1401
        self.a_key = self._key_prefix + (self.a_rev,)
 
1402
        self.b_key = self._key_prefix + (self.b_rev,)
 
1403
        self.graph = Graph(self.vf)
 
1404
        heads = self.graph.heads((self.a_key, self.b_key))
 
1405
        if len(heads) == 1:
 
1406
            # one side dominates, so we can just return its values, yay for
 
1407
            # per-file graphs
 
1408
            # Ideally we would know that before we get this far
 
1409
            self._head_key = heads.pop()
 
1410
            if self._head_key == self.a_key:
 
1411
                other = b_rev
 
1412
            else:
 
1413
                other = a_rev
 
1414
            mutter('found dominating revision for %s\n%s > %s', self.vf,
 
1415
                   self._head_key[-1], other)
 
1416
            self._weave = None
 
1417
        else:
 
1418
            self._head_key = None
 
1419
            self._build_weave()
 
1420
 
 
1421
    def _precache_tip_lines(self):
 
1422
        # Turn this into a no-op, because we will do this later
 
1423
        pass
 
1424
 
 
1425
    def _find_recursive_lcas(self):
 
1426
        """Find all the ancestors back to a unique lca"""
 
1427
        cur_ancestors = (self.a_key, self.b_key)
 
1428
        # graph.find_lca(uncommon, keys) now returns plain NULL_REVISION,
 
1429
        # rather than a key tuple. We will just map that directly to no common
 
1430
        # ancestors.
 
1431
        parent_map = {}
 
1432
        while True:
 
1433
            next_lcas = self.graph.find_lca(*cur_ancestors)
 
1434
            # Map a plain NULL_REVISION to a simple no-ancestors
 
1435
            if next_lcas == set([NULL_REVISION]):
 
1436
                next_lcas = ()
 
1437
            # Order the lca's based on when they were merged into the tip
 
1438
            # While the actual merge portion of weave merge uses a set() of
 
1439
            # active revisions, the order of insertion *does* effect the
 
1440
            # implicit ordering of the texts.
 
1441
            for rev_key in cur_ancestors:
 
1442
                ordered_parents = tuple(self.graph.find_merge_order(rev_key,
 
1443
                                                                    next_lcas))
 
1444
                parent_map[rev_key] = ordered_parents
 
1445
            if len(next_lcas) == 0:
 
1446
                break
 
1447
            elif len(next_lcas) == 1:
 
1448
                parent_map[list(next_lcas)[0]] = ()
 
1449
                break
 
1450
            elif len(next_lcas) > 2:
 
1451
                # More than 2 lca's, fall back to grabbing all nodes between
 
1452
                # this and the unique lca.
 
1453
                mutter('More than 2 LCAs, falling back to all nodes for:'
 
1454
                       ' %s, %s\n=> %s', self.a_key, self.b_key, cur_ancestors)
 
1455
                cur_lcas = next_lcas
 
1456
                while len(cur_lcas) > 1:
 
1457
                    cur_lcas = self.graph.find_lca(*cur_lcas)
 
1458
                if len(cur_lcas) == 0:
 
1459
                    # No common base to find, use the full ancestry
 
1460
                    unique_lca = None
 
1461
                else:
 
1462
                    unique_lca = list(cur_lcas)[0]
 
1463
                    if unique_lca == NULL_REVISION:
 
1464
                        # find_lca will return a plain 'NULL_REVISION' rather
 
1465
                        # than a key tuple when there is no common ancestor, we
 
1466
                        # prefer to just use None, because it doesn't confuse
 
1467
                        # _get_interesting_texts()
 
1468
                        unique_lca = None
 
1469
                parent_map.update(self._find_unique_parents(next_lcas,
 
1470
                                                            unique_lca))
 
1471
                break
 
1472
            cur_ancestors = next_lcas
 
1473
        return parent_map
 
1474
 
 
1475
    def _find_unique_parents(self, tip_keys, base_key):
 
1476
        """Find ancestors of tip that aren't ancestors of base.
 
1477
        
 
1478
        :param tip_keys: Nodes that are interesting
 
1479
        :param base_key: Cull all ancestors of this node
 
1480
        :return: The parent map for all revisions between tip_keys and
 
1481
            base_key. base_key will be included. References to nodes outside of
 
1482
            the ancestor set will also be removed.
 
1483
        """
 
1484
        # TODO: this would be simpler if find_unique_ancestors took a list
 
1485
        #       instead of a single tip, internally it supports it, but it
 
1486
        #       isn't a "backwards compatible" api change.
 
1487
        if base_key is None:
 
1488
            parent_map = dict(self.graph.iter_ancestry(tip_keys))
 
1489
            # We remove NULL_REVISION because it isn't a proper tuple key, and
 
1490
            # thus confuses things like _get_interesting_texts, and our logic
 
1491
            # to add the texts into the memory weave.
 
1492
            if NULL_REVISION in parent_map:
 
1493
                parent_map.pop(NULL_REVISION)
 
1494
        else:
 
1495
            interesting = set()
 
1496
            for tip in tip_keys:
 
1497
                interesting.update(
 
1498
                    self.graph.find_unique_ancestors(tip, [base_key]))
 
1499
            parent_map = self.graph.get_parent_map(interesting)
 
1500
            parent_map[base_key] = ()
 
1501
        culled_parent_map, child_map, tails = self._remove_external_references(
 
1502
            parent_map)
 
1503
        # Remove all the tails but base_key
 
1504
        if base_key is not None:
 
1505
            tails.remove(base_key)
 
1506
            self._prune_tails(culled_parent_map, child_map, tails)
 
1507
        # Now remove all the uninteresting 'linear' regions
 
1508
        simple_map = _mod_graph.collapse_linear_regions(culled_parent_map)
 
1509
        return simple_map
 
1510
 
 
1511
    @staticmethod
 
1512
    def _remove_external_references(parent_map):
 
1513
        """Remove references that go outside of the parent map.
 
1514
 
 
1515
        :param parent_map: Something returned from Graph.get_parent_map(keys)
 
1516
        :return: (filtered_parent_map, child_map, tails)
 
1517
            filtered_parent_map is parent_map without external references
 
1518
            child_map is the {parent_key: [child_keys]} mapping
 
1519
            tails is a list of nodes that do not have any parents in the map
 
1520
        """
 
1521
        # TODO: The basic effect of this function seems more generic than
 
1522
        #       _PlanMerge. But the specific details of building a child_map,
 
1523
        #       and computing tails seems very specific to _PlanMerge.
 
1524
        #       Still, should this be in Graph land?
 
1525
        filtered_parent_map = {}
 
1526
        child_map = {}
 
1527
        tails = []
 
1528
        for key, parent_keys in parent_map.iteritems():
 
1529
            culled_parent_keys = [p for p in parent_keys if p in parent_map]
 
1530
            if not culled_parent_keys:
 
1531
                tails.append(key)
 
1532
            for parent_key in culled_parent_keys:
 
1533
                child_map.setdefault(parent_key, []).append(key)
 
1534
            # TODO: Do we want to do this, it adds overhead for every node,
 
1535
            #       just to say that the node has no children
 
1536
            child_map.setdefault(key, [])
 
1537
            filtered_parent_map[key] = culled_parent_keys
 
1538
        return filtered_parent_map, child_map, tails
 
1539
 
 
1540
    @staticmethod
 
1541
    def _prune_tails(parent_map, child_map, tails_to_remove):
 
1542
        """Remove tails from the parent map.
 
1543
        
 
1544
        This will remove the supplied revisions until no more children have 0
 
1545
        parents.
 
1546
 
 
1547
        :param parent_map: A dict of {child: [parents]}, this dictionary will
 
1548
            be modified in place.
 
1549
        :param tails_to_remove: A list of tips that should be removed,
 
1550
            this list will be consumed
 
1551
        :param child_map: The reverse dict of parent_map ({parent: [children]})
 
1552
            this dict will be modified
 
1553
        :return: None, parent_map will be modified in place.
 
1554
        """
 
1555
        while tails_to_remove:
 
1556
            next = tails_to_remove.pop()
 
1557
            parent_map.pop(next)
 
1558
            children = child_map.pop(next)
 
1559
            for child in children:
 
1560
                child_parents = parent_map[child]
 
1561
                child_parents.remove(next)
 
1562
                if len(child_parents) == 0:
 
1563
                    tails_to_remove.append(child)
 
1564
 
 
1565
    def _get_interesting_texts(self, parent_map):
 
1566
        """Return a dict of texts we are interested in.
 
1567
 
 
1568
        Note that the input is in key tuples, but the output is in plain
 
1569
        revision ids.
 
1570
 
 
1571
        :param parent_map: The output from _find_recursive_lcas
 
1572
        :return: A dict of {'revision_id':lines} as returned by
 
1573
            _PlanMergeBase.get_lines()
 
1574
        """
 
1575
        all_revision_keys = set(parent_map)
 
1576
        all_revision_keys.add(self.a_key)
 
1577
        all_revision_keys.add(self.b_key)
 
1578
 
 
1579
        # Everything else is in 'keys' but get_lines is in 'revision_ids'
 
1580
        all_texts = self.get_lines([k[-1] for k in all_revision_keys])
 
1581
        return all_texts
 
1582
 
 
1583
    def _build_weave(self):
 
1584
        from bzrlib import weave
 
1585
        self._weave = weave.Weave(weave_name='in_memory_weave',
 
1586
                                  allow_reserved=True)
 
1587
        parent_map = self._find_recursive_lcas()
 
1588
 
 
1589
        all_texts = self._get_interesting_texts(parent_map)
 
1590
 
 
1591
        # Note: Unfortunately, the order given by topo_sort will effect the
 
1592
        # ordering resolution in the output. Specifically, if you add A then B,
 
1593
        # then in the output text A lines will show up before B lines. And, of
 
1594
        # course, topo_sort doesn't guarantee any real ordering.
 
1595
        # So we use merge_sort, and add a fake node on the tip.
 
1596
        # This ensures that left-hand parents will always be inserted into the
 
1597
        # weave before right-hand parents.
 
1598
        tip_key = self._key_prefix + (_mod_revision.CURRENT_REVISION,)
 
1599
        parent_map[tip_key] = (self.a_key, self.b_key)
 
1600
 
 
1601
        for seq_num, key, depth, eom in reversed(tsort.merge_sort(parent_map,
 
1602
                                                                  tip_key)):
 
1603
            if key == tip_key:
 
1604
                continue
 
1605
        # for key in tsort.topo_sort(parent_map):
 
1606
            parent_keys = parent_map[key]
 
1607
            revision_id = key[-1]
 
1608
            parent_ids = [k[-1] for k in parent_keys]
 
1609
            self._weave.add_lines(revision_id, parent_ids,
 
1610
                                  all_texts[revision_id])
 
1611
 
 
1612
    def plan_merge(self):
 
1613
        """Generate a 'plan' for merging the two revisions.
 
1614
 
 
1615
        This involves comparing their texts and determining the cause of
 
1616
        differences.  If text A has a line and text B does not, then either the
 
1617
        line was added to text A, or it was deleted from B.  Once the causes
 
1618
        are combined, they are written out in the format described in
 
1619
        VersionedFile.plan_merge
 
1620
        """
 
1621
        if self._head_key is not None: # There was a single head
 
1622
            if self._head_key == self.a_key:
 
1623
                plan = 'new-a'
 
1624
            else:
 
1625
                if self._head_key != self.b_key:
 
1626
                    raise AssertionError('There was an invalid head: %s != %s'
 
1627
                                         % (self.b_key, self._head_key))
 
1628
                plan = 'new-b'
 
1629
            head_rev = self._head_key[-1]
 
1630
            lines = self.get_lines([head_rev])[head_rev]
 
1631
            return ((plan, line) for line in lines)
 
1632
        return self._weave.plan_merge(self.a_rev, self.b_rev)
1420
1633
 
1421
1634
 
1422
1635
class _PlanLCAMerge(_PlanMergeBase):
1430
1643
    This is faster, and hopefully produces more useful output.
1431
1644
    """
1432
1645
 
1433
 
    def __init__(self, a_rev, b_rev, vf, graph):
1434
 
        _PlanMergeBase.__init__(self, a_rev, b_rev, vf)
1435
 
        self.lcas = graph.find_lca(a_rev, b_rev)
 
1646
    def __init__(self, a_rev, b_rev, vf, key_prefix, graph):
 
1647
        _PlanMergeBase.__init__(self, a_rev, b_rev, vf, key_prefix)
 
1648
        lcas = graph.find_lca(key_prefix + (a_rev,), key_prefix + (b_rev,))
 
1649
        self.lcas = set()
 
1650
        for lca in lcas:
 
1651
            if lca == NULL_REVISION:
 
1652
                self.lcas.add(lca)
 
1653
            else:
 
1654
                self.lcas.add(lca[-1])
1436
1655
        for lca in self.lcas:
1437
1656
            if _mod_revision.is_null(lca):
1438
1657
                lca_lines = []
1439
1658
            else:
1440
 
                lca_lines = self.vf.get_lines(lca)
 
1659
                lca_lines = self.get_lines([lca])[lca]
1441
1660
            matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_a,
1442
1661
                                                           lca_lines)
1443
1662
            blocks = list(matcher.get_matching_blocks())