mercurial/obsutil.py
changeset 33142 4f49810a1011
parent 32879 1858fc2327ef
child 33143 d09ae850296d
equal deleted inserted replaced
33140:f458a6701983 33142:4f49810a1011
    32 
    32 
    33             if precnodeid in repo:
    33             if precnodeid in repo:
    34                 yield precnodeid
    34                 yield precnodeid
    35             else:
    35             else:
    36                 stack.append(precnodeid)
    36                 stack.append(precnodeid)
       
    37 
       
    38 def successorssets(repo, initialnode, cache=None):
       
    39     """Return set of all latest successors of initial nodes
       
    40 
       
    41     The successors set of a changeset A are the group of revisions that succeed
       
    42     A. It succeeds A as a consistent whole, each revision being only a partial
       
    43     replacement. The successors set contains non-obsolete changesets only.
       
    44 
       
    45     This function returns the full list of successor sets which is why it
       
    46     returns a list of tuples and not just a single tuple. Each tuple is a valid
       
    47     successors set. Note that (A,) may be a valid successors set for changeset A
       
    48     (see below).
       
    49 
       
    50     In most cases, a changeset A will have a single element (e.g. the changeset
       
    51     A is replaced by A') in its successors set. Though, it is also common for a
       
    52     changeset A to have no elements in its successor set (e.g. the changeset
       
    53     has been pruned). Therefore, the returned list of successors sets will be
       
    54     [(A',)] or [], respectively.
       
    55 
       
    56     When a changeset A is split into A' and B', however, it will result in a
       
    57     successors set containing more than a single element, i.e. [(A',B')].
       
    58     Divergent changesets will result in multiple successors sets, i.e. [(A',),
       
    59     (A'')].
       
    60 
       
    61     If a changeset A is not obsolete, then it will conceptually have no
       
    62     successors set. To distinguish this from a pruned changeset, the successor
       
    63     set will contain itself only, i.e. [(A,)].
       
    64 
       
    65     Finally, successors unknown locally are considered to be pruned (obsoleted
       
    66     without any successors).
       
    67 
       
    68     The optional `cache` parameter is a dictionary that may contain precomputed
       
    69     successors sets. It is meant to reuse the computation of a previous call to
       
    70     `successorssets` when multiple calls are made at the same time. The cache
       
    71     dictionary is updated in place. The caller is responsible for its life
       
    72     span. Code that makes multiple calls to `successorssets` *must* use this
       
    73     cache mechanism or suffer terrible performance.
       
    74     """
       
    75 
       
    76     succmarkers = repo.obsstore.successors
       
    77 
       
    78     # Stack of nodes we search successors sets for
       
    79     toproceed = [initialnode]
       
    80     # set version of above list for fast loop detection
       
    81     # element added to "toproceed" must be added here
       
    82     stackedset = set(toproceed)
       
    83     if cache is None:
       
    84         cache = {}
       
    85 
       
    86     # This while loop is the flattened version of a recursive search for
       
    87     # successors sets
       
    88     #
       
    89     # def successorssets(x):
       
    90     #    successors = directsuccessors(x)
       
    91     #    ss = [[]]
       
    92     #    for succ in directsuccessors(x):
       
    93     #        # product as in itertools cartesian product
       
    94     #        ss = product(ss, successorssets(succ))
       
    95     #    return ss
       
    96     #
       
    97     # But we can not use plain recursive calls here:
       
    98     # - that would blow the python call stack
       
    99     # - obsolescence markers may have cycles, we need to handle them.
       
   100     #
       
   101     # The `toproceed` list act as our call stack. Every node we search
       
   102     # successors set for are stacked there.
       
   103     #
       
   104     # The `stackedset` is set version of this stack used to check if a node is
       
   105     # already stacked. This check is used to detect cycles and prevent infinite
       
   106     # loop.
       
   107     #
       
   108     # successors set of all nodes are stored in the `cache` dictionary.
       
   109     #
       
   110     # After this while loop ends we use the cache to return the successors sets
       
   111     # for the node requested by the caller.
       
   112     while toproceed:
       
   113         # Every iteration tries to compute the successors sets of the topmost
       
   114         # node of the stack: CURRENT.
       
   115         #
       
   116         # There are four possible outcomes:
       
   117         #
       
   118         # 1) We already know the successors sets of CURRENT:
       
   119         #    -> mission accomplished, pop it from the stack.
       
   120         # 2) Node is not obsolete:
       
   121         #    -> the node is its own successors sets. Add it to the cache.
       
   122         # 3) We do not know successors set of direct successors of CURRENT:
       
   123         #    -> We add those successors to the stack.
       
   124         # 4) We know successors sets of all direct successors of CURRENT:
       
   125         #    -> We can compute CURRENT successors set and add it to the
       
   126         #       cache.
       
   127         #
       
   128         current = toproceed[-1]
       
   129         if current in cache:
       
   130             # case (1): We already know the successors sets
       
   131             stackedset.remove(toproceed.pop())
       
   132         elif current not in succmarkers:
       
   133             # case (2): The node is not obsolete.
       
   134             if current in repo:
       
   135                 # We have a valid last successors.
       
   136                 cache[current] = [(current,)]
       
   137             else:
       
   138                 # Final obsolete version is unknown locally.
       
   139                 # Do not count that as a valid successors
       
   140                 cache[current] = []
       
   141         else:
       
   142             # cases (3) and (4)
       
   143             #
       
   144             # We proceed in two phases. Phase 1 aims to distinguish case (3)
       
   145             # from case (4):
       
   146             #
       
   147             #     For each direct successors of CURRENT, we check whether its
       
   148             #     successors sets are known. If they are not, we stack the
       
   149             #     unknown node and proceed to the next iteration of the while
       
   150             #     loop. (case 3)
       
   151             #
       
   152             #     During this step, we may detect obsolescence cycles: a node
       
   153             #     with unknown successors sets but already in the call stack.
       
   154             #     In such a situation, we arbitrary set the successors sets of
       
   155             #     the node to nothing (node pruned) to break the cycle.
       
   156             #
       
   157             #     If no break was encountered we proceed to phase 2.
       
   158             #
       
   159             # Phase 2 computes successors sets of CURRENT (case 4); see details
       
   160             # in phase 2 itself.
       
   161             #
       
   162             # Note the two levels of iteration in each phase.
       
   163             # - The first one handles obsolescence markers using CURRENT as
       
   164             #   precursor (successors markers of CURRENT).
       
   165             #
       
   166             #   Having multiple entry here means divergence.
       
   167             #
       
   168             # - The second one handles successors defined in each marker.
       
   169             #
       
   170             #   Having none means pruned node, multiple successors means split,
       
   171             #   single successors are standard replacement.
       
   172             #
       
   173             for mark in sorted(succmarkers[current]):
       
   174                 for suc in mark[1]:
       
   175                     if suc not in cache:
       
   176                         if suc in stackedset:
       
   177                             # cycle breaking
       
   178                             cache[suc] = []
       
   179                         else:
       
   180                             # case (3) If we have not computed successors sets
       
   181                             # of one of those successors we add it to the
       
   182                             # `toproceed` stack and stop all work for this
       
   183                             # iteration.
       
   184                             toproceed.append(suc)
       
   185                             stackedset.add(suc)
       
   186                             break
       
   187                 else:
       
   188                     continue
       
   189                 break
       
   190             else:
       
   191                 # case (4): we know all successors sets of all direct
       
   192                 # successors
       
   193                 #
       
   194                 # Successors set contributed by each marker depends on the
       
   195                 # successors sets of all its "successors" node.
       
   196                 #
       
   197                 # Each different marker is a divergence in the obsolescence
       
   198                 # history. It contributes successors sets distinct from other
       
   199                 # markers.
       
   200                 #
       
   201                 # Within a marker, a successor may have divergent successors
       
   202                 # sets. In such a case, the marker will contribute multiple
       
   203                 # divergent successors sets. If multiple successors have
       
   204                 # divergent successors sets, a Cartesian product is used.
       
   205                 #
       
   206                 # At the end we post-process successors sets to remove
       
   207                 # duplicated entry and successors set that are strict subset of
       
   208                 # another one.
       
   209                 succssets = []
       
   210                 for mark in sorted(succmarkers[current]):
       
   211                     # successors sets contributed by this marker
       
   212                     markss = [[]]
       
   213                     for suc in mark[1]:
       
   214                         # cardinal product with previous successors
       
   215                         productresult = []
       
   216                         for prefix in markss:
       
   217                             for suffix in cache[suc]:
       
   218                                 newss = list(prefix)
       
   219                                 for part in suffix:
       
   220                                     # do not duplicated entry in successors set
       
   221                                     # first entry wins.
       
   222                                     if part not in newss:
       
   223                                         newss.append(part)
       
   224                                 productresult.append(newss)
       
   225                         markss = productresult
       
   226                     succssets.extend(markss)
       
   227                 # remove duplicated and subset
       
   228                 seen = []
       
   229                 final = []
       
   230                 candidate = sorted(((set(s), s) for s in succssets if s),
       
   231                                    key=lambda x: len(x[1]), reverse=True)
       
   232                 for setversion, listversion in candidate:
       
   233                     for seenset in seen:
       
   234                         if setversion.issubset(seenset):
       
   235                             break
       
   236                     else:
       
   237                         final.append(listversion)
       
   238                         seen.append(setversion)
       
   239                 final.reverse() # put small successors set first
       
   240                 cache[current] = final
       
   241     return cache[initialnode]