~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: Robert Collins
  • Date: 2007-10-22 01:23:51 UTC
  • mfrom: (2921.3.1 graph)
  • mto: This revision was merged to the branch mainline in revision 2933.
  • Revision ID: robertc@robertcollins.net-20071022012351-16lm27an2lbzk038
Merge graph history-access bugfix.

Show diffs side-by-side

added added

removed removed

Lines of Context:
239
239
        # skip over the actual candidate for each searcher
240
240
        for searcher in active_searchers.itervalues():
241
241
            searcher.next()
 
242
        # The common walker finds nodes that are common to two or more of the
 
243
        # input keys, so that we don't access all history when a currently
 
244
        # uncommon search point actually meets up with something behind a
 
245
        # common search point. Common search points do not keep searches
 
246
        # active; they just allow us to make searches inactive without
 
247
        # accessing all history.
 
248
        common_walker = self._make_breadth_first_searcher([])
242
249
        while len(active_searchers) > 0:
 
250
            ancestors = set()
 
251
            # advance searches
 
252
            try:
 
253
                common_walker.next()
 
254
            except StopIteration:
 
255
                pass
243
256
            for candidate in active_searchers.keys():
244
257
                try:
245
258
                    searcher = active_searchers[candidate]
249
262
                    # a descendant of another candidate.
250
263
                    continue
251
264
                try:
252
 
                    ancestors = searcher.next()
 
265
                    ancestors.update(searcher.next())
253
266
                except StopIteration:
254
267
                    del active_searchers[candidate]
255
268
                    continue
256
 
                for ancestor in ancestors:
257
 
                    if ancestor in candidate_heads:
258
 
                        candidate_heads.remove(ancestor)
259
 
                        del searchers[ancestor]
260
 
                        if ancestor in active_searchers:
261
 
                            del active_searchers[ancestor]
 
269
            # process found nodes
 
270
            new_common = set()
 
271
            for ancestor in ancestors:
 
272
                if ancestor in candidate_heads:
 
273
                    candidate_heads.remove(ancestor)
 
274
                    del searchers[ancestor]
 
275
                    if ancestor in active_searchers:
 
276
                        del active_searchers[ancestor]
 
277
                # it may meet up with a known common node
 
278
                already_common = ancestor in common_walker.seen
 
279
                if not already_common:
 
280
                    # or it may have been just reached by all the searchers:
262
281
                    for searcher in searchers.itervalues():
263
282
                        if ancestor not in searcher.seen:
 
283
                            common = False
264
284
                            break
265
285
                    else:
266
 
                        # if this revision was seen by all searchers, then it
267
 
                        # is a descendant of all candidates, so we can stop
268
 
                        # searching it, and any seen ancestors
269
 
                        for searcher in searchers.itervalues():
270
 
                            seen_ancestors =\
271
 
                                searcher.find_seen_ancestors(ancestor)
272
 
                            searcher.stop_searching_any(seen_ancestors)
 
286
                        common = True
 
287
                if already_common:
 
288
                    # some searcher has encountered our known common nodes:
 
289
                    # just stop it
 
290
                    ancestor_set = set([ancestor])
 
291
                    for searcher in searchers.itervalues():
 
292
                        searcher.stop_searching_any(ancestor_set)
 
293
                if common:
 
294
                    # The final active searcher has just reached this node,
 
295
                    # making it be known as a descendant of all candidates, so
 
296
                    # we can stop searching it, and any seen ancestors
 
297
                    new_common.add(ancestor)
 
298
                    for searcher in searchers.itervalues():
 
299
                        seen_ancestors =\
 
300
                            searcher.find_seen_ancestors(ancestor)
 
301
                        searcher.stop_searching_any(seen_ancestors)
 
302
            common_walker.start_searching(new_common)
273
303
        return candidate_heads
274
304
 
275
305
    def find_unique_lca(self, left_revision, right_revision):
306
336
    def is_ancestor(self, candidate_ancestor, candidate_descendant):
307
337
        """Determine whether a revision is an ancestor of another.
308
338
 
309
 
        There are two possible outcomes: True and False, but there are three
310
 
        possible relationships:
311
 
 
312
 
        a) candidate_ancestor is an ancestor of candidate_descendant
313
 
        b) candidate_ancestor is an descendant of candidate_descendant
314
 
        c) candidate_ancestor is an sibling of candidate_descendant
315
 
 
316
 
        To check for a, we walk from candidate_descendant, looking for
317
 
        candidate_ancestor.
318
 
 
319
 
        To check for b, we walk from candidate_ancestor, looking for
320
 
        candidate_descendant.
321
 
 
322
 
        To make a and b more efficient, we can stop any searches that hit
323
 
        common ancestors.
324
 
 
325
 
        If we exhaust our searches, but neither a or b is true, then c is true.
326
 
 
327
 
        In order to find c efficiently, we must avoid searching from
328
 
        candidate_descendant or candidate_ancestor into common ancestors.  But
329
 
        if we don't search common ancestors at all, we won't know if we hit
330
 
        common ancestors.  So we have a walker for common ancestors.  Note that
331
 
        its searches are not required to terminate in order to determine c to
332
 
        be true.
333
 
        """
334
 
        ancestor_walker = self._make_breadth_first_searcher(
335
 
            [candidate_ancestor])
336
 
        descendant_walker = self._make_breadth_first_searcher(
337
 
            [candidate_descendant])
338
 
        common_walker = self._make_breadth_first_searcher([])
339
 
        active_ancestor = True
340
 
        active_descendant = True
341
 
        while (active_ancestor or active_descendant):
342
 
            new_common = set()
343
 
            if active_descendant:
344
 
                try:
345
 
                    nodes = descendant_walker.next()
346
 
                except StopIteration:
347
 
                    active_descendant = False
348
 
                else:
349
 
                    if candidate_ancestor in nodes:
350
 
                        return True
351
 
                    new_common.update(nodes.intersection(ancestor_walker.seen))
352
 
            if active_ancestor:
353
 
                try:
354
 
                    nodes = ancestor_walker.next()
355
 
                except StopIteration:
356
 
                    active_ancestor = False
357
 
                else:
358
 
                    if candidate_descendant in nodes:
359
 
                        return False
360
 
                    new_common.update(nodes.intersection(
361
 
                        descendant_walker.seen))
362
 
            try:
363
 
                new_common.update(common_walker.next())
364
 
            except StopIteration:
365
 
                pass
366
 
            for walker in (ancestor_walker, descendant_walker):
367
 
                for node in new_common:
368
 
                    c_ancestors = walker.find_seen_ancestors(node)
369
 
                    walker.stop_searching_any(c_ancestors)
370
 
                common_walker.start_searching(new_common)
371
 
        return False
 
339
        We answer this using heads() as heads() has the logic to perform the
 
340
        smallest number of parent looksup to determine the ancestral
 
341
        relationship between N revisions.
 
342
        """
 
343
        return set([candidate_descendant]) == self.heads(
 
344
            [candidate_ancestor, candidate_descendant])
 
345
 
 
346
 
 
347
class HeadsCache(object):
 
348
    """A cache of results for graph heads calls."""
 
349
 
 
350
    def __init__(self, graph):
 
351
        self.graph = graph
 
352
        self._heads = {}
 
353
 
 
354
    def heads(self, keys):
 
355
        """Return the heads of keys.
 
356
 
 
357
        This matches the API of Graph.heads(), specifically the return value is
 
358
        a set which can be mutated, and ordering of the input is not preserved
 
359
        in the output.
 
360
 
 
361
        :see also: Graph.heads.
 
362
        :param keys: The keys to calculate heads for.
 
363
        :return: A set containing the heads, which may be mutated without
 
364
            affecting future lookups.
 
365
        """
 
366
        keys = frozenset(keys)
 
367
        try:
 
368
            return set(self._heads[keys])
 
369
        except KeyError:
 
370
            heads = self.graph.heads(keys)
 
371
            self._heads[keys] = heads
 
372
            return set(heads)
372
373
 
373
374
 
374
375
class HeadsCache(object):