~bzr-pqm/bzr/bzr.dev

4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
1
# Copyright (C) 2009 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
"""Implementation of Graph algorithms when we have already loaded everything.
18
"""
19
20
from bzrlib import (
21
    revision,
22
    )
23
24
25
class _KnownGraphNode(object):
26
    """Represents a single object in the known graph."""
27
4371.4.10 by Vincent Ladeuil
Cleanup.
28
    __slots__ = ('key', 'parent_keys', 'child_keys', 'gdfo')
4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
29
30
    def __init__(self, key, parent_keys):
31
        self.key = key
32
        self.parent_keys = parent_keys
33
        self.child_keys = []
34
        # Greatest distance from origin
35
        self.gdfo = None
36
37
    def __repr__(self):
4371.4.10 by Vincent Ladeuil
Cleanup.
38
        return '%s(%s  gdfo:%s par:%s child:%s)' % (
4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
39
            self.__class__.__name__, self.key, self.gdfo,
4371.4.10 by Vincent Ladeuil
Cleanup.
40
            self.parent_keys, self.child_keys)
4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
41
42
43
class KnownGraph(object):
44
    """This is a class which assumes we already know the full graph."""
45
46
    def __init__(self, parent_map, do_cache=True):
47
        """Create a new KnownGraph instance.
48
49
        :param parent_map: A dictionary mapping key => parent_keys
50
        """
51
        self._nodes = {}
52
        # Maps {sorted(revision_id, revision_id): heads}
53
        self._known_heads = {}
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
54
        self.do_cache = do_cache
4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
55
        self._initialize_nodes(parent_map)
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
56
        self._find_gdfo()
4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
57
58
    def _initialize_nodes(self, parent_map):
59
        """Populate self._nodes.
60
4371.4.9 by Vincent Ladeuil
Simplify gdfo computing by finding tails when at graph build time.
61
        After this has finished:
62
        - self._nodes will have an entry for every entry in parent_map.
63
        - ghosts will have a parent_keys = None,
64
        - all nodes found will also have .child_keys populated with all known
65
          child_keys,
66
        - self._tails will list all the nodes without parents.
4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
67
        """
4371.4.9 by Vincent Ladeuil
Simplify gdfo computing by finding tails when at graph build time.
68
        tails = self._tails = set()
4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
69
        nodes = self._nodes
70
        for key, parent_keys in parent_map.iteritems():
71
            if key in nodes:
72
                node = nodes[key]
73
                node.parent_keys = parent_keys
4371.4.9 by Vincent Ladeuil
Simplify gdfo computing by finding tails when at graph build time.
74
                if parent_keys:
75
                    # This node has been added before being seen in parent_map
76
                    # (see below)
77
                    tails.remove(node)
4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
78
            else:
79
                node = _KnownGraphNode(key, parent_keys)
80
                nodes[key] = node
81
            for parent_key in parent_keys:
82
                try:
83
                    parent_node = nodes[parent_key]
84
                except KeyError:
85
                    parent_node = _KnownGraphNode(parent_key, None)
86
                    nodes[parent_key] = parent_node
4371.4.9 by Vincent Ladeuil
Simplify gdfo computing by finding tails when at graph build time.
87
                    # Potentially a tail, if we're wrong we'll remove it later
88
                    # (see above)
89
                    tails.add(parent_node)
4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
90
                parent_node.child_keys.append(key)
91
92
    def _find_gdfo(self):
4371.4.1 by Vincent Ladeuil
A new recursive gdfo processing method.
93
        nodes = self._nodes
4371.4.10 by Vincent Ladeuil
Cleanup.
94
        known_parent_gdfos = {}
4371.4.1 by Vincent Ladeuil
A new recursive gdfo processing method.
95
        pending = []
96
4371.4.9 by Vincent Ladeuil
Simplify gdfo computing by finding tails when at graph build time.
97
        for node in self._tails:
98
            node.gdfo = 1
99
            pending.append(node)
100
4371.4.8 by Vincent Ladeuil
Replace the existing KnownGraph._find_gdfo.
101
        while pending:
102
            node = pending.pop()
4371.4.1 by Vincent Ladeuil
A new recursive gdfo processing method.
103
            for child_key in node.child_keys:
104
                child = nodes[child_key]
4371.4.9 by Vincent Ladeuil
Simplify gdfo computing by finding tails when at graph build time.
105
                try:
106
                    known_parent_gdfos[child_key] += 1
107
                except KeyError:
108
                    known_parent_gdfos[child_key] = 1
4371.4.1 by Vincent Ladeuil
A new recursive gdfo processing method.
109
                if child.gdfo is None or node.gdfo + 1 > child.gdfo:
110
                    child.gdfo = node.gdfo + 1
111
                if known_parent_gdfos[child_key] == len(child.parent_keys):
112
                    # We are the last parent updating that node, we can
113
                    # continue from there
4371.4.2 by Vincent Ladeuil
Same gdfo processing, without recursion.
114
                    pending.append(child)
4371.4.1 by Vincent Ladeuil
A new recursive gdfo processing method.
115
4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
116
    def heads(self, keys):
117
        """Return the heads from amongst keys.
118
119
        This is done by searching the ancestries of each key.  Any key that is
120
        reachable from another key is not returned; all the others are.
121
4371.4.3 by Vincent Ladeuil
A new heads() method based on gdfo.
122
        This operation scales with the relative depth between any two keys. It
123
        uses gdfo to avoid walking all ancestry.
124
125
        :param keys: An iterable of keys.
126
        :return: A set of the heads. Note that as a set there is no ordering
127
            information. Callers will need to filter their input to create
128
            order if they need it.
129
        """
130
        candidate_nodes = dict((key, self._nodes[key]) for key in keys)
131
        if revision.NULL_REVISION in candidate_nodes:
132
            # NULL_REVISION is only a head if it is the only entry
133
            candidate_nodes.pop(revision.NULL_REVISION)
134
            if not candidate_nodes:
135
                return frozenset([revision.NULL_REVISION])
136
        if len(candidate_nodes) < 2:
137
            # No or only one candidate
138
            return frozenset(candidate_nodes)
139
        heads_key = frozenset(candidate_nodes)
140
        if heads_key != frozenset(keys):
141
            # Mention duplicates
142
            note('%s != %s', heads_key, frozenset(keys))
143
        # Do we have a cached result ?
144
        try:
145
            heads = self._known_heads[heads_key]
146
            return heads
147
        except KeyError:
148
            pass
149
        # Let's compute the heads
4371.4.4 by Vincent Ladeuil
Feedback from John, 'seen' is a set().
150
        seen = set()
4371.4.3 by Vincent Ladeuil
A new heads() method based on gdfo.
151
        pending = []
152
        min_gdfo = None
153
        for node in candidate_nodes.values():
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
154
            if node.parent_keys:
4371.4.3 by Vincent Ladeuil
A new heads() method based on gdfo.
155
                pending.extend(node.parent_keys)
156
            if min_gdfo is None or node.gdfo < min_gdfo:
157
                min_gdfo = node.gdfo
158
        nodes = self._nodes
159
        while pending:
160
            node_key = pending.pop()
161
            if node_key in seen:
162
                # node already appears in some ancestry
163
                continue
4371.4.4 by Vincent Ladeuil
Feedback from John, 'seen' is a set().
164
            seen.add(node_key)
4371.4.3 by Vincent Ladeuil
A new heads() method based on gdfo.
165
            node = nodes[node_key]
166
            if node.gdfo <= min_gdfo:
167
                continue
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
168
            if node.parent_keys:
4371.4.3 by Vincent Ladeuil
A new heads() method based on gdfo.
169
                pending.extend(node.parent_keys)
4371.4.4 by Vincent Ladeuil
Feedback from John, 'seen' is a set().
170
        heads = heads_key.difference(seen)
4371.4.3 by Vincent Ladeuil
A new heads() method based on gdfo.
171
        if self.do_cache:
172
            self._known_heads[heads_key] = heads
173
        return heads
174