/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/graph.py

  • Committer: John Arbash Meinel
  • Date: 2008-05-28 23:20:33 UTC
  • mto: This revision was merged to the branch mainline in revision 3458.
  • Revision ID: john@arbash-meinel.com-20080528232033-cx3l3yg845udklps
Bring back always in the form of 'override'.
Change the functions so that they are compatible with the released
definition, and just allow callers to supply override=False
if they want the new behavior.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import time
18
18
 
19
19
from bzrlib import (
20
20
    debug,
21
21
    errors,
22
 
    osutils,
23
22
    revision,
 
23
    symbol_versioning,
24
24
    trace,
 
25
    tsort,
25
26
    )
26
 
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
 
27
from bzrlib.deprecated_graph import (node_distances, select_farthest)
27
28
 
28
29
STEP_UNIQUE_SEARCHER_EVERY = 5
29
30
 
60
61
        return 'DictParentsProvider(%r)' % self.ancestry
61
62
 
62
63
    def get_parent_map(self, keys):
63
 
        """See StackedParentsProvider.get_parent_map"""
 
64
        """See _StackedParentsProvider.get_parent_map"""
64
65
        ancestry = self.ancestry
65
66
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
66
67
 
67
 
@deprecated_function(deprecated_in((1, 16, 0)))
68
 
def _StackedParentsProvider(*args, **kwargs):
69
 
    return StackedParentsProvider(*args, **kwargs)
70
 
 
71
 
class StackedParentsProvider(object):
72
 
    """A parents provider which stacks (or unions) multiple providers.
73
 
    
74
 
    The providers are queries in the order of the provided parent_providers.
75
 
    """
76
 
    
 
68
 
 
69
class _StackedParentsProvider(object):
 
70
 
77
71
    def __init__(self, parent_providers):
78
72
        self._parent_providers = parent_providers
79
73
 
80
74
    def __repr__(self):
81
 
        return "%s(%r)" % (self.__class__.__name__, self._parent_providers)
 
75
        return "_StackedParentsProvider(%r)" % self._parent_providers
82
76
 
83
77
    def get_parent_map(self, keys):
84
78
        """Get a mapping of keys => parents
105
99
 
106
100
 
107
101
class CachingParentsProvider(object):
108
 
    """A parents provider which will cache the revision => parents as a dict.
109
 
 
110
 
    This is useful for providers which have an expensive look up.
111
 
 
112
 
    Either a ParentsProvider or a get_parent_map-like callback may be
113
 
    supplied.  If it provides extra un-asked-for parents, they will be cached,
114
 
    but filtered out of get_parent_map.
115
 
 
116
 
    The cache is enabled by default, but may be disabled and re-enabled.
 
102
    """A parents provider which will cache the revision => parents in a dict.
 
103
 
 
104
    This is useful for providers that have an expensive lookup.
117
105
    """
118
 
    def __init__(self, parent_provider=None, get_parent_map=None):
119
 
        """Constructor.
120
106
 
121
 
        :param parent_provider: The ParentProvider to use.  It or
122
 
            get_parent_map must be supplied.
123
 
        :param get_parent_map: The get_parent_map callback to use.  It or
124
 
            parent_provider must be supplied.
125
 
        """
 
107
    def __init__(self, parent_provider):
126
108
        self._real_provider = parent_provider
127
 
        if get_parent_map is None:
128
 
            self._get_parent_map = self._real_provider.get_parent_map
129
 
        else:
130
 
            self._get_parent_map = get_parent_map
131
 
        self._cache = None
132
 
        self.enable_cache(True)
 
109
        # Theoretically we could use an LRUCache here
 
110
        self._cache = {}
133
111
 
134
112
    def __repr__(self):
135
113
        return "%s(%r)" % (self.__class__.__name__, self._real_provider)
136
114
 
137
 
    def enable_cache(self, cache_misses=True):
138
 
        """Enable cache."""
139
 
        if self._cache is not None:
140
 
            raise AssertionError('Cache enabled when already enabled.')
141
 
        self._cache = {}
142
 
        self._cache_misses = cache_misses
143
 
        self.missing_keys = set()
144
 
 
145
 
    def disable_cache(self):
146
 
        """Disable and clear the cache."""
147
 
        self._cache = None
148
 
        self._cache_misses = None
149
 
        self.missing_keys = set()
150
 
 
151
 
    def get_cached_map(self):
152
 
        """Return any cached get_parent_map values."""
153
 
        if self._cache is None:
154
 
            return None
155
 
        return dict(self._cache)
156
 
 
157
115
    def get_parent_map(self, keys):
158
 
        """See StackedParentsProvider.get_parent_map."""
 
116
        """See _StackedParentsProvider.get_parent_map"""
 
117
        needed = set()
 
118
        # If the _real_provider doesn't have a key, we cache a value of None,
 
119
        # which we then later use to realize we cannot provide a value for that
 
120
        # key.
 
121
        parent_map = {}
159
122
        cache = self._cache
160
 
        if cache is None:
161
 
            cache = self._get_parent_map(keys)
162
 
        else:
163
 
            needed_revisions = set(key for key in keys if key not in cache)
164
 
            # Do not ask for negatively cached keys
165
 
            needed_revisions.difference_update(self.missing_keys)
166
 
            if needed_revisions:
167
 
                parent_map = self._get_parent_map(needed_revisions)
168
 
                cache.update(parent_map)
169
 
                if self._cache_misses:
170
 
                    for key in needed_revisions:
171
 
                        if key not in parent_map:
172
 
                            self.note_missing_key(key)
173
 
        result = {}
174
123
        for key in keys:
175
 
            value = cache.get(key)
176
 
            if value is not None:
177
 
                result[key] = value
178
 
        return result
 
124
            if key in cache:
 
125
                value = cache[key]
 
126
                if value is not None:
 
127
                    parent_map[key] = value
 
128
            else:
 
129
                needed.add(key)
179
130
 
180
 
    def note_missing_key(self, key):
181
 
        """Note that key is a missing key."""
182
 
        if self._cache_misses:
183
 
            self.missing_keys.add(key)
 
131
        if needed:
 
132
            new_parents = self._real_provider.get_parent_map(needed)
 
133
            cache.update(new_parents)
 
134
            parent_map.update(new_parents)
 
135
            needed.difference_update(new_parents)
 
136
            cache.update(dict.fromkeys(needed, None))
 
137
        return parent_map
184
138
 
185
139
 
186
140
class Graph(object):
258
212
        right = searchers[1].seen
259
213
        return (left.difference(right), right.difference(left))
260
214
 
261
 
    def find_distance_to_null(self, target_revision_id, known_revision_ids):
262
 
        """Find the left-hand distance to the NULL_REVISION.
263
 
 
264
 
        (This can also be considered the revno of a branch at
265
 
        target_revision_id.)
266
 
 
267
 
        :param target_revision_id: A revision_id which we would like to know
268
 
            the revno for.
269
 
        :param known_revision_ids: [(revision_id, revno)] A list of known
270
 
            revno, revision_id tuples. We'll use this to seed the search.
271
 
        """
272
 
        # Map from revision_ids to a known value for their revno
273
 
        known_revnos = dict(known_revision_ids)
274
 
        cur_tip = target_revision_id
275
 
        num_steps = 0
276
 
        NULL_REVISION = revision.NULL_REVISION
277
 
        known_revnos[NULL_REVISION] = 0
278
 
 
279
 
        searching_known_tips = list(known_revnos.keys())
280
 
 
281
 
        unknown_searched = {}
282
 
 
283
 
        while cur_tip not in known_revnos:
284
 
            unknown_searched[cur_tip] = num_steps
285
 
            num_steps += 1
286
 
            to_search = set([cur_tip])
287
 
            to_search.update(searching_known_tips)
288
 
            parent_map = self.get_parent_map(to_search)
289
 
            parents = parent_map.get(cur_tip, None)
290
 
            if not parents: # An empty list or None is a ghost
291
 
                raise errors.GhostRevisionsHaveNoRevno(target_revision_id,
292
 
                                                       cur_tip)
293
 
            cur_tip = parents[0]
294
 
            next_known_tips = []
295
 
            for revision_id in searching_known_tips:
296
 
                parents = parent_map.get(revision_id, None)
297
 
                if not parents:
298
 
                    continue
299
 
                next = parents[0]
300
 
                next_revno = known_revnos[revision_id] - 1
301
 
                if next in unknown_searched:
302
 
                    # We have enough information to return a value right now
303
 
                    return next_revno + unknown_searched[next]
304
 
                if next in known_revnos:
305
 
                    continue
306
 
                known_revnos[next] = next_revno
307
 
                next_known_tips.append(next)
308
 
            searching_known_tips = next_known_tips
309
 
 
310
 
        # We reached a known revision, so just add in how many steps it took to
311
 
        # get there.
312
 
        return known_revnos[cur_tip] + num_steps
313
 
 
314
 
    def find_lefthand_distances(self, keys):
315
 
        """Find the distance to null for all the keys in keys.
316
 
 
317
 
        :param keys: keys to lookup.
318
 
        :return: A dict key->distance for all of keys.
319
 
        """
320
 
        # Optimisable by concurrent searching, but a random spread should get
321
 
        # some sort of hit rate.
322
 
        result = {}
323
 
        known_revnos = []
324
 
        ghosts = []
325
 
        for key in keys:
326
 
            try:
327
 
                known_revnos.append(
328
 
                    (key, self.find_distance_to_null(key, known_revnos)))
329
 
            except errors.GhostRevisionsHaveNoRevno:
330
 
                ghosts.append(key)
331
 
        for key in ghosts:
332
 
            known_revnos.append((key, -1))
333
 
        return dict(known_revnos)
334
 
 
335
215
    def find_unique_ancestors(self, unique_revision, common_revisions):
336
216
        """Find the unique ancestors for a revision versus others.
337
217
 
588
468
    def _refine_unique_nodes(self, unique_searcher, all_unique_searcher,
589
469
                             unique_tip_searchers, common_searcher):
590
470
        """Steps 5-8 of find_unique_ancestors.
591
 
 
 
471
        
592
472
        This function returns when common_searcher has stopped searching for
593
473
        more nodes.
594
474
        """
637
517
                                 all_unique_searcher._iterations)
638
518
            unique_tip_searchers = next_unique_searchers
639
519
 
 
520
    @symbol_versioning.deprecated_method(symbol_versioning.one_one)
 
521
    def get_parents(self, revisions):
 
522
        """Find revision ids of the parents of a list of revisions
 
523
 
 
524
        A list is returned of the same length as the input.  Each entry
 
525
        is a list of parent ids for the corresponding input revision.
 
526
 
 
527
        [NULL_REVISION] is used as the parent of the first user-committed
 
528
        revision.  Its parent list is empty.
 
529
 
 
530
        If the revision is not present (i.e. a ghost), None is used in place
 
531
        of the list of parents.
 
532
 
 
533
        Deprecated in bzr 1.2 - please see get_parent_map.
 
534
        """
 
535
        parents = self.get_parent_map(revisions)
 
536
        return [parents.get(r, None) for r in revisions]
 
537
 
640
538
    def get_parent_map(self, revisions):
641
539
        """Get a map of key:parent_list for revisions.
642
540
 
815
713
            common_walker.start_searching(new_common)
816
714
        return candidate_heads
817
715
 
818
 
    def find_merge_order(self, tip_revision_id, lca_revision_ids):
819
 
        """Find the order that each revision was merged into tip.
820
 
 
821
 
        This basically just walks backwards with a stack, and walks left-first
822
 
        until it finds a node to stop.
823
 
        """
824
 
        if len(lca_revision_ids) == 1:
825
 
            return list(lca_revision_ids)
826
 
        looking_for = set(lca_revision_ids)
827
 
        # TODO: Is there a way we could do this "faster" by batching up the
828
 
        # get_parent_map requests?
829
 
        # TODO: Should we also be culling the ancestry search right away? We
830
 
        # could add looking_for to the "stop" list, and walk their
831
 
        # ancestry in batched mode. The flip side is it might mean we walk a
832
 
        # lot of "stop" nodes, rather than only the minimum.
833
 
        # Then again, without it we may trace back into ancestry we could have
834
 
        # stopped early.
835
 
        stack = [tip_revision_id]
836
 
        found = []
837
 
        stop = set()
838
 
        while stack and looking_for:
839
 
            next = stack.pop()
840
 
            stop.add(next)
841
 
            if next in looking_for:
842
 
                found.append(next)
843
 
                looking_for.remove(next)
844
 
                if len(looking_for) == 1:
845
 
                    found.append(looking_for.pop())
846
 
                    break
847
 
                continue
848
 
            parent_ids = self.get_parent_map([next]).get(next, None)
849
 
            if not parent_ids: # Ghost, nothing to search here
850
 
                continue
851
 
            for parent_id in reversed(parent_ids):
852
 
                # TODO: (performance) We see the parent at this point, but we
853
 
                #       wait to mark it until later to make sure we get left
854
 
                #       parents before right parents. However, instead of
855
 
                #       waiting until we have traversed enough parents, we
856
 
                #       could instead note that we've found it, and once all
857
 
                #       parents are in the stack, just reverse iterate the
858
 
                #       stack for them.
859
 
                if parent_id not in stop:
860
 
                    # this will need to be searched
861
 
                    stack.append(parent_id)
862
 
                stop.add(parent_id)
863
 
        return found
864
 
 
865
716
    def find_unique_lca(self, left_revision, right_revision,
866
717
                        count_steps=False):
867
718
        """Find a unique LCA.
926
777
        An ancestor may sort after a descendant if the relationship is not
927
778
        visible in the supplied list of revisions.
928
779
        """
929
 
        from bzrlib import tsort
930
780
        sorter = tsort.TopoSorter(self.get_parent_map(revisions))
931
781
        return sorter.iter_topo_order()
932
782
 
940
790
        return set([candidate_descendant]) == self.heads(
941
791
            [candidate_ancestor, candidate_descendant])
942
792
 
943
 
    def is_between(self, revid, lower_bound_revid, upper_bound_revid):
944
 
        """Determine whether a revision is between two others.
945
 
 
946
 
        returns true if and only if:
947
 
        lower_bound_revid <= revid <= upper_bound_revid
948
 
        """
949
 
        return ((upper_bound_revid is None or
950
 
                    self.is_ancestor(revid, upper_bound_revid)) and
951
 
               (lower_bound_revid is None or
952
 
                    self.is_ancestor(lower_bound_revid, revid)))
953
 
 
954
793
    def _search_for_extra_common(self, common, searchers):
955
794
        """Make sure that unique nodes are genuinely unique.
956
795
 
1229
1068
 
1230
1069
    def get_result(self):
1231
1070
        """Get a SearchResult for the current state of this searcher.
1232
 
 
 
1071
        
1233
1072
        :return: A SearchResult for this search so far. The SearchResult is
1234
1073
            static - the search can be advanced and the search result will not
1235
1074
            be invalidated or altered.
1239
1078
            # exclude keys for them. However, while we could have a second
1240
1079
            # look-ahead result buffer and shuffle things around, this method
1241
1080
            # is typically only called once per search - when memoising the
1242
 
            # results of the search.
 
1081
            # results of the search. 
1243
1082
            found, ghosts, next, parents = self._do_query(self._next_query)
1244
1083
            # pretend we didn't query: perhaps we should tweak _do_query to be
1245
1084
            # entirely stateless?
1286
1125
 
1287
1126
    def next_with_ghosts(self):
1288
1127
        """Return the next found ancestors, with ghosts split out.
1289
 
 
 
1128
        
1290
1129
        Ancestors are returned in the order they are seen in a breadth-first
1291
1130
        traversal.  No ancestor will be returned more than once. Ancestors are
1292
1131
        returned only after asking for their parents, which allows us to detect
1336
1175
        parent_map = self._parents_provider.get_parent_map(revisions)
1337
1176
        found_revisions.update(parent_map)
1338
1177
        for rev_id, parents in parent_map.iteritems():
1339
 
            if parents is None:
1340
 
                continue
1341
1178
            new_found_parents = [p for p in parents if p not in self.seen]
1342
1179
            if new_found_parents:
1343
1180
                # Calling set.update() with an empty generator is actually
1351
1188
 
1352
1189
    def find_seen_ancestors(self, revisions):
1353
1190
        """Find ancestors of these revisions that have already been seen.
1354
 
 
 
1191
        
1355
1192
        This function generally makes the assumption that querying for the
1356
1193
        parents of a node that has already been queried is reasonably cheap.
1357
1194
        (eg, not a round trip to a remote host).
1392
1229
        Remove any of the specified revisions from the search list.
1393
1230
 
1394
1231
        None of the specified revisions are required to be present in the
1395
 
        search list.
1396
 
 
1397
 
        It is okay to call stop_searching_any() for revisions which were seen
1398
 
        in previous iterations. It is the callers responsibility to call
1399
 
        find_seen_ancestors() to make sure that current search tips that are
1400
 
        ancestors of those revisions are also stopped.  All explicitly stopped
1401
 
        revisions will be excluded from the search result's get_keys(), though.
 
1232
        search list.  In this case, the call is a no-op.
1402
1233
        """
1403
1234
        # TODO: does this help performance?
1404
1235
        # if not revisions:
1413
1244
                self._current_ghosts.intersection(revisions))
1414
1245
            self._current_present.difference_update(stopped)
1415
1246
            self._current_ghosts.difference_update(stopped)
1416
 
            # stopping 'x' should stop returning parents of 'x', but
 
1247
            # stopping 'x' should stop returning parents of 'x', but 
1417
1248
            # not if 'y' always references those same parents
1418
1249
            stop_rev_references = {}
1419
1250
            for rev in stopped_present:
1435
1266
                    stop_parents.add(rev_id)
1436
1267
            self._next_query.difference_update(stop_parents)
1437
1268
        self._stopped_keys.update(stopped)
1438
 
        self._stopped_keys.update(revisions)
1439
1269
        return stopped
1440
1270
 
1441
1271
    def start_searching(self, revisions):
1481
1311
            a SearchResult from a smart server, in which case the keys list is
1482
1312
            not necessarily immediately available.
1483
1313
        """
1484
 
        self._recipe = ('search', start_keys, exclude_keys, key_count)
 
1314
        self._recipe = (start_keys, exclude_keys, key_count)
1485
1315
        self._keys = frozenset(keys)
1486
1316
 
1487
1317
    def get_recipe(self):
1488
1318
        """Return a recipe that can be used to replay this search.
1489
 
 
 
1319
        
1490
1320
        The recipe allows reconstruction of the same results at a later date
1491
1321
        without knowing all the found keys. The essential elements are a list
1492
 
        of keys to start and to stop at. In order to give reproducible
 
1322
        of keys to start and and to stop at. In order to give reproducible
1493
1323
        results when ghosts are encountered by a search they are automatically
1494
1324
        added to the exclude list (or else ghost filling may alter the
1495
1325
        results).
1496
1326
 
1497
 
        :return: A tuple ('search', start_keys_set, exclude_keys_set,
1498
 
            revision_count). To recreate the results of this search, create a
1499
 
            breadth first searcher on the same graph starting at start_keys.
1500
 
            Then call next() (or next_with_ghosts()) repeatedly, and on every
1501
 
            result, call stop_searching_any on any keys from the exclude_keys
1502
 
            set. The revision_count value acts as a trivial cross-check - the
1503
 
            found revisions of the new search should have as many elements as
 
1327
        :return: A tuple (start_keys_set, exclude_keys_set, revision_count). To
 
1328
            recreate the results of this search, create a breadth first
 
1329
            searcher on the same graph starting at start_keys. Then call next()
 
1330
            (or next_with_ghosts()) repeatedly, and on every result, call
 
1331
            stop_searching_any on any keys from the exclude_keys set. The
 
1332
            revision_count value acts as a trivial cross-check - the found
 
1333
            revisions of the new search should have as many elements as
1504
1334
            revision_count. If it does not, then additional revisions have been
1505
1335
            ghosted since the search was executed the first time and the second
1506
1336
            time.
1514
1344
        """
1515
1345
        return self._keys
1516
1346
 
1517
 
    def is_empty(self):
1518
 
        """Return false if the search lists 1 or more revisions."""
1519
 
        return self._recipe[3] == 0
1520
 
 
1521
 
    def refine(self, seen, referenced):
1522
 
        """Create a new search by refining this search.
1523
 
 
1524
 
        :param seen: Revisions that have been satisfied.
1525
 
        :param referenced: Revision references observed while satisfying some
1526
 
            of this search.
1527
 
        """
1528
 
        start = self._recipe[1]
1529
 
        exclude = self._recipe[2]
1530
 
        count = self._recipe[3]
1531
 
        keys = self.get_keys()
1532
 
        # New heads = referenced + old heads - seen things - exclude
1533
 
        pending_refs = set(referenced)
1534
 
        pending_refs.update(start)
1535
 
        pending_refs.difference_update(seen)
1536
 
        pending_refs.difference_update(exclude)
1537
 
        # New exclude = old exclude + satisfied heads
1538
 
        seen_heads = start.intersection(seen)
1539
 
        exclude.update(seen_heads)
1540
 
        # keys gets seen removed
1541
 
        keys = keys - seen
1542
 
        # length is reduced by len(seen)
1543
 
        count -= len(seen)
1544
 
        return SearchResult(pending_refs, exclude, count, keys)
1545
 
 
1546
 
 
1547
 
class PendingAncestryResult(object):
1548
 
    """A search result that will reconstruct the ancestry for some graph heads.
1549
 
 
1550
 
    Unlike SearchResult, this doesn't hold the complete search result in
1551
 
    memory, it just holds a description of how to generate it.
1552
 
    """
1553
 
 
1554
 
    def __init__(self, heads, repo):
1555
 
        """Constructor.
1556
 
 
1557
 
        :param heads: an iterable of graph heads.
1558
 
        :param repo: a repository to use to generate the ancestry for the given
1559
 
            heads.
1560
 
        """
1561
 
        self.heads = frozenset(heads)
1562
 
        self.repo = repo
1563
 
 
1564
 
    def get_recipe(self):
1565
 
        """Return a recipe that can be used to replay this search.
1566
 
 
1567
 
        The recipe allows reconstruction of the same results at a later date.
1568
 
 
1569
 
        :seealso SearchResult.get_recipe:
1570
 
 
1571
 
        :return: A tuple ('proxy-search', start_keys_set, set(), -1)
1572
 
            To recreate this result, create a PendingAncestryResult with the
1573
 
            start_keys_set.
1574
 
        """
1575
 
        return ('proxy-search', self.heads, set(), -1)
1576
 
 
1577
 
    def get_keys(self):
1578
 
        """See SearchResult.get_keys.
1579
 
 
1580
 
        Returns all the keys for the ancestry of the heads, excluding
1581
 
        NULL_REVISION.
1582
 
        """
1583
 
        return self._get_keys(self.repo.get_graph())
1584
 
 
1585
 
    def _get_keys(self, graph):
1586
 
        NULL_REVISION = revision.NULL_REVISION
1587
 
        keys = [key for (key, parents) in graph.iter_ancestry(self.heads)
1588
 
                if key != NULL_REVISION and parents is not None]
1589
 
        return keys
1590
 
 
1591
 
    def is_empty(self):
1592
 
        """Return false if the search lists 1 or more revisions."""
1593
 
        if revision.NULL_REVISION in self.heads:
1594
 
            return len(self.heads) == 1
1595
 
        else:
1596
 
            return len(self.heads) == 0
1597
 
 
1598
 
    def refine(self, seen, referenced):
1599
 
        """Create a new search by refining this search.
1600
 
 
1601
 
        :param seen: Revisions that have been satisfied.
1602
 
        :param referenced: Revision references observed while satisfying some
1603
 
            of this search.
1604
 
        """
1605
 
        referenced = self.heads.union(referenced)
1606
 
        return PendingAncestryResult(referenced - seen, self.repo)
1607
 
 
1608
 
 
1609
 
def collapse_linear_regions(parent_map):
1610
 
    """Collapse regions of the graph that are 'linear'.
1611
 
 
1612
 
    For example::
1613
 
 
1614
 
      A:[B], B:[C]
1615
 
 
1616
 
    can be collapsed by removing B and getting::
1617
 
 
1618
 
      A:[C]
1619
 
 
1620
 
    :param parent_map: A dictionary mapping children to their parents
1621
 
    :return: Another dictionary with 'linear' chains collapsed
1622
 
    """
1623
 
    # Note: this isn't a strictly minimal collapse. For example:
1624
 
    #   A
1625
 
    #  / \
1626
 
    # B   C
1627
 
    #  \ /
1628
 
    #   D
1629
 
    #   |
1630
 
    #   E
1631
 
    # Will not have 'D' removed, even though 'E' could fit. Also:
1632
 
    #   A
1633
 
    #   |    A
1634
 
    #   B => |
1635
 
    #   |    C
1636
 
    #   C
1637
 
    # A and C are both kept because they are edges of the graph. We *could* get
1638
 
    # rid of A if we wanted.
1639
 
    #   A
1640
 
    #  / \
1641
 
    # B   C
1642
 
    # |   |
1643
 
    # D   E
1644
 
    #  \ /
1645
 
    #   F
1646
 
    # Will not have any nodes removed, even though you do have an
1647
 
    # 'uninteresting' linear D->B and E->C
1648
 
    children = {}
1649
 
    for child, parents in parent_map.iteritems():
1650
 
        children.setdefault(child, [])
1651
 
        for p in parents:
1652
 
            children.setdefault(p, []).append(child)
1653
 
 
1654
 
    orig_children = dict(children)
1655
 
    removed = set()
1656
 
    result = dict(parent_map)
1657
 
    for node in parent_map:
1658
 
        parents = result[node]
1659
 
        if len(parents) == 1:
1660
 
            parent_children = children[parents[0]]
1661
 
            if len(parent_children) != 1:
1662
 
                # This is not the only child
1663
 
                continue
1664
 
            node_children = children[node]
1665
 
            if len(node_children) != 1:
1666
 
                continue
1667
 
            child_parents = result.get(node_children[0], None)
1668
 
            if len(child_parents) != 1:
1669
 
                # This is not its only parent
1670
 
                continue
1671
 
            # The child of this node only points at it, and the parent only has
1672
 
            # this as a child. remove this node, and join the others together
1673
 
            result[node_children[0]] = parents
1674
 
            children[parents[0]] = node_children
1675
 
            del result[node]
1676
 
            del children[node]
1677
 
            removed.add(node)
1678
 
 
1679
 
    return result
1680
 
 
1681
 
 
1682
 
class GraphThunkIdsToKeys(object):
1683
 
    """Forwards calls about 'ids' to be about keys internally."""
1684
 
 
1685
 
    def __init__(self, graph):
1686
 
        self._graph = graph
1687
 
 
1688
 
    def topo_sort(self):
1689
 
        return [r for (r,) in self._graph.topo_sort()]
1690
 
 
1691
 
    def heads(self, ids):
1692
 
        """See Graph.heads()"""
1693
 
        as_keys = [(i,) for i in ids]
1694
 
        head_keys = self._graph.heads(as_keys)
1695
 
        return set([h[0] for h in head_keys])
1696
 
 
1697
 
    def merge_sort(self, tip_revision):
1698
 
        return self._graph.merge_sort((tip_revision,))
1699
 
 
1700
 
 
1701
 
_counters = [0,0,0,0,0,0,0]
1702
 
try:
1703
 
    from bzrlib._known_graph_pyx import KnownGraph
1704
 
except ImportError, e:
1705
 
    osutils.failed_to_load_extension(e)
1706
 
    from bzrlib._known_graph_py import KnownGraph