~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tsort.py

  • Committer: Aaron Bentley
  • Date: 2006-06-14 19:45:57 UTC
  • mto: This revision was merged to the branch mainline in revision 1777.
  • Revision ID: abentley@panoramicfeedback.com-20060614194557-6b499aa1cf03f7e6
Use create_signature for signing policy, deprecate check_signatures for this

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
 
1
# (C) 2005, 2006 Canonical Limited.
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
18
18
"""Topological sorting routines."""
19
19
 
20
20
 
21
 
from bzrlib import errors
22
 
import bzrlib.revision as _mod_revision
 
21
import bzrlib.errors as errors
23
22
 
24
23
 
25
24
__all__ = ["topo_sort", "TopoSorter", "merge_sort", "MergeSorter"]
62
61
        """
63
62
        # a dict of the graph.
64
63
        self._graph = dict(graph)
65
 
        self._visitable = set(self._graph)
66
64
        ### if debugging:
67
65
        # self._original_graph = dict(graph)
68
66
        
122
120
                            # this parent was completed by a child on the
123
121
                            # call stack. skip it.
124
122
                            continue
125
 
                        if next_node_name not in self._visitable:
126
 
                            continue
127
123
                        # otherwise transfer it from the source graph into the
128
124
                        # top of the current depth first search stack.
129
125
                        try:
163
159
        return node_name
164
160
 
165
161
 
166
 
def merge_sort(graph, branch_tip, mainline_revisions=None, generate_revno=False):
 
162
def merge_sort(graph, branch_tip, mainline_revisions=None):
167
163
    """Topological sort a graph which groups merges.
168
164
 
169
165
    :param graph: sequence of pairs of node->parents_list.
178
174
                               old revision listed in the mainline revisions
179
175
                               list.
180
176
                               The order for this parameter is oldest-first.
181
 
    :param generate_revno: Optional parameter controlling the generation of
182
 
        revision number sequences in the output. See the output description of
183
 
        the MergeSorter docstring for details.
184
 
    :result: See the MergeSorter docstring for details.
 
177
 
 
178
    The result is a list of node names, such that all parents come before
 
179
    their children.
 
180
 
185
181
    node identifiers can be any hashable object, and are typically strings.
186
182
    """
187
 
    return MergeSorter(graph, branch_tip, mainline_revisions,
188
 
        generate_revno).sorted()
 
183
    return MergeSorter(graph, branch_tip, mainline_revisions).sorted()
189
184
 
190
185
 
191
186
class MergeSorter(object):
192
187
 
193
 
    __slots__ = ['_node_name_stack',
194
 
                 '_node_merge_depth_stack',
195
 
                 '_pending_parents_stack',
196
 
                 '_first_child_stack',
197
 
                 '_left_subtree_pushed_stack',
198
 
                 '_generate_revno',
199
 
                 '_graph',
200
 
                 '_mainline_revisions',
201
 
                 '_stop_revision',
202
 
                 '_original_graph',
203
 
                 '_revnos',
204
 
                 '_revno_to_branch_count',
205
 
                 '_completed_node_names',
206
 
                 '_scheduled_nodes',
207
 
                ]
208
 
 
209
 
    def __init__(self, graph, branch_tip, mainline_revisions=None,
210
 
        generate_revno=False):
 
188
    def __init__(self, graph, branch_tip, mainline_revisions=None):
211
189
        """Merge-aware topological sorting of a graph.
212
190
    
213
191
        :param graph: sequence of pairs of node_name->parent_names_list.
226
204
                               old revision listed in the mainline revisions
227
205
                               list.
228
206
                               The order for this parameter is oldest-first.
229
 
        :param generate_revno: Optional parameter controlling the generation of
230
 
            revision number sequences in the output. See the output description
231
 
            for more details.
232
 
 
233
 
        The result is a list sorted so that all parents come before
234
 
        their children. Each element of the list is a tuple containing:
235
 
        (sequence_number, node_name, merge_depth, end_of_merge)
236
 
         * sequence_number: The sequence of this row in the output. Useful for 
237
 
           GUIs.
238
 
         * node_name: The node name: opaque text to the merge routine.
239
 
         * merge_depth: How many levels of merging deep this node has been
240
 
           found.
241
 
         * revno_sequence: When requested this field provides a sequence of
242
 
             revision numbers for all revisions. The format is:
243
 
             (REVNO, BRANCHNUM, BRANCHREVNO). BRANCHNUM is the number of the
244
 
             branch that the revno is on. From left to right the REVNO numbers
245
 
             are the sequence numbers within that branch of the revision.
246
 
             For instance, the graph {A:[], B:['A'], C:['A', 'B']} will get
247
 
             the following revno_sequences assigned: A:(1,), B:(1,1,1), C:(2,).
248
 
             This should be read as 'A is the first commit in the trunk',
249
 
             'B is the first commit on the first branch made from A', 'C is the
250
 
             second commit in the trunk'.
251
 
         * end_of_merge: When True the next node is part of a different merge.
252
207
 
253
208
        
254
209
        node identifiers can be any hashable object, and are typically strings.
330
285
             XXXX revisit when awake. ddaa asks about the relevance of each one
331
286
             - maybe more than one parent is relevant
332
287
        """
333
 
        self._generate_revno = generate_revno
334
288
        # a dict of the graph.
335
289
        self._graph = dict(graph)
336
290
        # if there is an explicit mainline, alter the graph to match. This is
351
305
            if parent is None:
352
306
                # end of mainline_revisions history
353
307
                continue
354
 
            graph_parent_ids = self._graph[revision]
355
 
            if not graph_parent_ids:
356
 
                # We ran into a ghost, skip over it, this is a workaround for
357
 
                # bug #243536, the _graph has had ghosts stripped, but the
358
 
                # mainline_revisions have not
359
 
                continue
360
 
            if graph_parent_ids[0] == parent:
 
308
            if self._graph[revision][0] == parent:
361
309
                continue
362
310
            # remove it from its prior spot
363
311
            self._graph[revision].remove(parent)
367
315
        # which requires the parents to be accessible: its easier for now
368
316
        # to just keep the original graph around.
369
317
        self._original_graph = dict(self._graph.items())
370
 
        # we need to know the revision numbers of revisions to determine
371
 
        # the revision numbers of their descendants
372
 
        # this is a graph from node to [revno_tuple, first_child]
373
 
        # where first_child is True if no other children have seen this node
374
 
        # and revno_tuple is the tuple that was assigned to the node.
375
 
        # we dont know revnos to start with, so we start it seeded with
376
 
        # [None, True]
377
 
        self._revnos = dict((revision, [None, True])
378
 
                            for revision in self._graph)
379
 
        # Each mainline revision counts how many child branches have spawned from it.
380
 
        self._revno_to_branch_count = {}
381
318
        
382
319
        # this is a stack storing the depth first search into the graph.
383
320
        self._node_name_stack = []
387
324
        # stack stores the parents we have not yet checked for the node at the 
388
325
        # matching depth in _node_name_stack
389
326
        self._pending_parents_stack = []
390
 
        # When we first look at a node we assign it a seqence number from its
391
 
        # leftmost parent.
392
 
        self._first_child_stack = []
393
327
        # this is a set of the nodes who have been completely analysed for fast
394
328
        # membership checking
395
329
        self._completed_node_names = set()
409
343
        # This records for each node when we have processed its left most 
410
344
        # unmerged subtree. After this subtree is scheduled, all other subtrees
411
345
        # have their merge depth increased by one from this nodes merge depth.
412
 
        # it contains tuples - name, merge_depth
413
 
        self._left_subtree_pushed_stack = []
 
346
        self._left_subtree_done_stack = []
414
347
 
415
348
        # seed the search with the tip of the branch
416
 
        if (branch_tip is not None and
417
 
            branch_tip != _mod_revision.NULL_REVISION):
 
349
        if branch_tip is not None:
418
350
            parents = self._graph.pop(branch_tip)
419
351
            self._push_node(branch_tip, 0, parents)
420
352
 
431
363
        After finishing iteration the sorter is empty and you cannot continue
432
364
        iteration.
433
365
        """
434
 
        # These are safe to offload to local variables, because they are used
435
 
        # as a stack and modified in place, never assigned to.
436
 
        node_name_stack = self._node_name_stack
437
 
        node_merge_depth_stack = self._node_merge_depth_stack
438
 
        pending_parents_stack = self._pending_parents_stack
439
 
        left_subtree_pushed_stack = self._left_subtree_pushed_stack
440
 
        completed_node_names = self._completed_node_names
441
 
        scheduled_nodes = self._scheduled_nodes
442
 
 
443
 
        graph_pop = self._graph.pop
444
 
 
445
 
        def push_node(node_name, merge_depth, parents,
446
 
                      node_name_stack_append=node_name_stack.append,
447
 
                      node_merge_depth_stack_append=node_merge_depth_stack.append,
448
 
                      left_subtree_pushed_stack_append=left_subtree_pushed_stack.append,
449
 
                      pending_parents_stack_append=pending_parents_stack.append,
450
 
                      first_child_stack_append=self._first_child_stack.append,
451
 
                      revnos=self._revnos,
452
 
                      ):
453
 
            """Add node_name to the pending node stack.
454
 
 
455
 
            Names in this stack will get emitted into the output as they are popped
456
 
            off the stack.
457
 
 
458
 
            This inlines a lot of self._variable.append functions as local
459
 
            variables.
460
 
            """
461
 
            node_name_stack_append(node_name)
462
 
            node_merge_depth_stack_append(merge_depth)
463
 
            left_subtree_pushed_stack_append(False)
464
 
            pending_parents_stack_append(list(parents))
465
 
            # as we push it, check if it is the first child
466
 
            if parents:
467
 
                # node has parents, assign from the left most parent.
468
 
                parent_info = revnos[parents[0]]
469
 
                first_child = parent_info[1]
470
 
                parent_info[1] = False
471
 
            else:
472
 
                # We don't use the same algorithm here, but we need to keep the
473
 
                # stack in line
474
 
                first_child = None
475
 
            first_child_stack_append(first_child)
476
 
 
477
 
        def pop_node(node_name_stack_pop=node_name_stack.pop,
478
 
                     node_merge_depth_stack_pop=node_merge_depth_stack.pop,
479
 
                     first_child_stack_pop=self._first_child_stack.pop,
480
 
                     left_subtree_pushed_stack_pop=left_subtree_pushed_stack.pop,
481
 
                     pending_parents_stack_pop=pending_parents_stack.pop,
482
 
                     original_graph=self._original_graph,
483
 
                     revnos=self._revnos,
484
 
                     completed_node_names_add=self._completed_node_names.add,
485
 
                     scheduled_nodes_append=scheduled_nodes.append,
486
 
                     revno_to_branch_count=self._revno_to_branch_count,
487
 
                    ):
488
 
            """Pop the top node off the stack
489
 
 
490
 
            The node is appended to the sorted output.
491
 
            """
492
 
            # we are returning from the flattened call frame:
493
 
            # pop off the local variables
494
 
            node_name = node_name_stack_pop()
495
 
            merge_depth = node_merge_depth_stack_pop()
496
 
            first_child = first_child_stack_pop()
497
 
            # remove this node from the pending lists:
498
 
            left_subtree_pushed_stack_pop()
499
 
            pending_parents_stack_pop()
500
 
 
501
 
            parents = original_graph[node_name]
502
 
            if parents:
503
 
                # node has parents, assign from the left most parent.
504
 
                parent_revno = revnos[parents[0]][0]
505
 
                if not first_child:
506
 
                    # not the first child, make a new branch
507
 
                    base_revno = parent_revno[0]
508
 
                    branch_count = revno_to_branch_count.get(base_revno, 0)
509
 
                    branch_count += 1
510
 
                    revno_to_branch_count[base_revno] = branch_count
511
 
                    revno = (parent_revno[0], branch_count, 1)
512
 
                    # revno = (parent_revno[0], branch_count, parent_revno[-1]+1)
513
 
                else:
514
 
                    # as the first child, we just increase the final revision
515
 
                    # number
516
 
                    revno = parent_revno[:-1] + (parent_revno[-1] + 1,)
517
 
            else:
518
 
                # no parents, use the root sequence
519
 
                root_count = revno_to_branch_count.get(0, -1)
520
 
                root_count += 1
521
 
                if root_count:
522
 
                    revno = (0, root_count, 1)
523
 
                else:
524
 
                    revno = (1,)
525
 
                revno_to_branch_count[0] = root_count
526
 
 
527
 
            # store the revno for this node for future reference
528
 
            revnos[node_name][0] = revno
529
 
            completed_node_names_add(node_name)
530
 
            scheduled_nodes_append((node_name, merge_depth, revno))
531
 
            return node_name
532
 
 
533
 
 
534
 
        while node_name_stack:
 
366
        while self._node_name_stack:
535
367
            # loop until this call completes.
536
 
            parents_to_visit = pending_parents_stack[-1]
 
368
            parents_to_visit = self._pending_parents_stack[-1]
537
369
            # if all parents are done, the revision is done
538
370
            if not parents_to_visit:
539
371
                # append the revision to the topo sorted scheduled list:
540
372
                # all the nodes parents have been scheduled added, now
541
373
                # we can add it to the output.
542
 
                pop_node()
 
374
                self._pop_node()
543
375
            else:
544
 
                while pending_parents_stack[-1]:
545
 
                    if not left_subtree_pushed_stack[-1]:
 
376
                while self._pending_parents_stack[-1]:
 
377
                    if not self._left_subtree_done_stack[-1]:
546
378
                        # recurse depth first into the primary parent
547
 
                        next_node_name = pending_parents_stack[-1].pop(0)
 
379
                        next_node_name = self._pending_parents_stack[-1].pop(0)
548
380
                    else:
549
381
                        # place any merges in right-to-left order for scheduling
550
382
                        # which gives us left-to-right order after we reverse
553
385
                        # subtree rather than the left most, which will 
554
386
                        # display nicely (you get smaller trees at the top
555
387
                        # of the combined merge).
556
 
                        next_node_name = pending_parents_stack[-1].pop()
557
 
                    if next_node_name in completed_node_names:
 
388
                        next_node_name = self._pending_parents_stack[-1].pop()
 
389
                    if next_node_name in self._completed_node_names:
558
390
                        # this parent was completed by a child on the
559
391
                        # call stack. skip it.
560
392
                        continue
561
393
                    # otherwise transfer it from the source graph into the
562
394
                    # top of the current depth first search stack.
563
395
                    try:
564
 
                        parents = graph_pop(next_node_name)
 
396
                        parents = self._graph.pop(next_node_name)
565
397
                    except KeyError:
566
398
                        # if the next node is not in the source graph it has
567
399
                        # already been popped from it and placed into the
568
400
                        # current search stack (but not completed or we would
569
401
                        # have hit the continue 4 lines up.
570
402
                        # this indicates a cycle.
571
 
                        raise errors.GraphCycleError(node_name_stack)
 
403
                        raise errors.GraphCycleError(self._node_name_stack)
572
404
                    next_merge_depth = 0
573
 
                    if left_subtree_pushed_stack[-1]:
574
 
                        # a new child branch from name_stack[-1]
 
405
                    if self._left_subtree_done_stack[-1]:
575
406
                        next_merge_depth = 1
576
407
                    else:
577
408
                        next_merge_depth = 0
578
 
                        left_subtree_pushed_stack[-1] = True
 
409
                        self._left_subtree_done_stack[-1] = True
579
410
                    next_merge_depth = (
580
 
                        node_merge_depth_stack[-1] + next_merge_depth)
581
 
                    push_node(
 
411
                        self._node_merge_depth_stack[-1] + next_merge_depth)
 
412
                    self._push_node(
582
413
                        next_node_name,
583
414
                        next_merge_depth,
584
415
                        parents)
585
416
                    # and do not continue processing parents until this 'call' 
586
417
                    # has recursed.
587
418
                    break
588
 
 
589
419
        # We have scheduled the graph. Now deliver the ordered output:
590
420
        sequence_number = 0
591
 
        stop_revision = self._stop_revision
592
 
        generate_revno = self._generate_revno
593
 
        original_graph = self._original_graph
594
 
 
595
 
        while scheduled_nodes:
596
 
            node_name, merge_depth, revno = scheduled_nodes.pop()
597
 
            if node_name == stop_revision:
 
421
        while self._scheduled_nodes:
 
422
            node_name, merge_depth = self._scheduled_nodes.pop()
 
423
            if node_name == self._stop_revision:
598
424
                return
599
 
            if not len(scheduled_nodes):
600
 
                # last revision is the end of a merge
 
425
            if not len(self._scheduled_nodes):
601
426
                end_of_merge = True
602
 
            elif scheduled_nodes[-1][1] < merge_depth:
 
427
            elif self._scheduled_nodes[-1][1] < merge_depth:
603
428
                # the next node is to our left
604
429
                end_of_merge = True
605
 
            elif (scheduled_nodes[-1][1] == merge_depth and
606
 
                  (scheduled_nodes[-1][0] not in
607
 
                   original_graph[node_name])):
 
430
            elif (self._scheduled_nodes[-1][1] == merge_depth and
 
431
                  (self._scheduled_nodes[-1][0] not in
 
432
                   self._original_graph[node_name])):
608
433
                # the next node was part of a multiple-merge.
609
434
                end_of_merge = True
610
435
            else:
611
436
                end_of_merge = False
612
 
            if generate_revno:
613
 
                yield (sequence_number, node_name, merge_depth, revno, end_of_merge)
614
 
            else:
615
 
                yield (sequence_number, node_name, merge_depth, end_of_merge)
 
437
            yield (sequence_number, node_name, merge_depth, end_of_merge)
616
438
            sequence_number += 1
617
439
 
618
440
    def _push_node(self, node_name, merge_depth, parents):
623
445
        """
624
446
        self._node_name_stack.append(node_name)
625
447
        self._node_merge_depth_stack.append(merge_depth)
626
 
        self._left_subtree_pushed_stack.append(False)
 
448
        self._left_subtree_done_stack.append(False)
627
449
        self._pending_parents_stack.append(list(parents))
628
 
        # as we push it, figure out if this is the first child
629
 
        parents = self._original_graph[node_name]
630
 
        if parents:
631
 
            # node has parents, assign from the left most parent.
632
 
            parent_info = self._revnos[parents[0]]
633
 
            first_child = parent_info[1]
634
 
            parent_info[1] = False
635
 
        else:
636
 
            # We don't use the same algorithm here, but we need to keep the
637
 
            # stack in line
638
 
            first_child = None
639
 
        self._first_child_stack.append(first_child)
640
450
 
641
451
    def _pop_node(self):
642
452
        """Pop the top node off the stack 
647
457
        # pop off the local variables
648
458
        node_name = self._node_name_stack.pop()
649
459
        merge_depth = self._node_merge_depth_stack.pop()
650
 
        first_child = self._first_child_stack.pop()
651
 
        # remove this node from the pending lists:
652
 
        self._left_subtree_pushed_stack.pop()
 
460
        self._left_subtree_done_stack.pop()
653
461
        self._pending_parents_stack.pop()
654
462
 
655
 
        parents = self._original_graph[node_name]
656
 
        if parents:
657
 
            # node has parents, assign from the left most parent.
658
 
            parent_revno = self._revnos[parents[0]][0]
659
 
            if not first_child:
660
 
                # not the first child, make a new branch
661
 
                base_revno = parent_revno[0]
662
 
                branch_count = self._revno_to_branch_count.get(base_revno, 0)
663
 
                branch_count += 1
664
 
                self._revno_to_branch_count[base_revno] = branch_count
665
 
                revno = (parent_revno[0], branch_count, 1)
666
 
                # revno = (parent_revno[0], branch_count, parent_revno[-1]+1)
667
 
            else:
668
 
                # as the first child, we just increase the final revision
669
 
                # number
670
 
                revno = parent_revno[:-1] + (parent_revno[-1] + 1,)
671
 
        else:
672
 
            # no parents, use the root sequence
673
 
            root_count = self._revno_to_branch_count.get(0, 0)
674
 
            if root_count:
675
 
                revno = (0, root_count, 1)
676
 
            else:
677
 
                revno = (1,)
678
 
            root_count += 1
679
 
            self._revno_to_branch_count[0] = root_count
680
 
 
681
 
        # store the revno for this node for future reference
682
 
        self._revnos[node_name][0] = revno
683
463
        self._completed_node_names.add(node_name)
684
 
        self._scheduled_nodes.append((node_name, merge_depth, self._revnos[node_name][0]))
 
464
        self._scheduled_nodes.append((node_name, merge_depth))
685
465
        return node_name