Mercurial > public > mercurial-scm > hg
comparison mercurial/obsutil.py @ 33142:4f49810a1011
obsutil: move 'successorssets' to the new modules
We have a new 'obsutil' module now. We move this high level utility there to bring
'obsolete.py' back to a more reasonable size.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Tue, 27 Jun 2017 01:03:01 +0200 |
parents | 1858fc2327ef |
children | d09ae850296d |
comparison
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] |