##// END OF EJS Templates
obsolete: compute successors set...
Pierre-Yves David -
r18068:4bec77e6 default
parent child Browse files
Show More
@@ -0,0 +1,398 b''
1 Test file dedicated to testing the divergent troubles from obsolete changeset.
2
3 This is the most complexe troubles from far so we isolate it in a dedicated
4 file.
5
6 Enable obsolete
7
8 $ cat > obs.py << EOF
9 > import mercurial.obsolete
10 > mercurial.obsolete._enabled = True
11 > EOF
12 $ cat >> $HGRCPATH << EOF
13 > [ui]
14 > logtemplate = {rev}:{node|short} {desc}\n
15 > [extensions]
16 > obs=${TESTTMP}/obs.py
17 > [alias]
18 > debugobsolete = debugobsolete -d '0 0'
19 > [phases]
20 > publish=False
21 > EOF
22
23
24 $ mkcommit() {
25 > echo "$1" > "$1"
26 > hg add "$1"
27 > hg ci -m "$1"
28 > }
29 $ getid() {
30 > hg id --debug -ir "desc('$1')"
31 > }
32
33 setup repo
34
35 $ hg init reference
36 $ cd reference
37 $ mkcommit base
38 $ mkcommit A_0
39 $ hg up 0
40 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
41 $ mkcommit A_1
42 created new head
43 $ hg up 0
44 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
45 $ mkcommit A_2
46 created new head
47 $ hg up 0
48 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
49 $ cd ..
50
51
52 $ newcase() {
53 > hg clone -u 0 -q reference $1
54 > cd $1
55 > }
56
57 direct divergence
58 -----------------
59
60 A_1 have two direct and divergent successors A_1 and A_1
61
62 $ newcase direct
63 $ hg debugobsolete `getid A_0` `getid A_1`
64 $ hg debugobsolete `getid A_0` `getid A_2`
65 $ hg log -G --hidden
66 o 3:392fd25390da A_2
67 |
68 | o 2:82623d38b9ba A_1
69 |/
70 | x 1:007dc284c1f8 A_0
71 |/
72 @ 0:d20a80d4def3 base
73
74 $ hg debugsuccessorssets 'all()'
75 d20a80d4def3
76 d20a80d4def3
77 007dc284c1f8
78 82623d38b9ba
79 392fd25390da
80 82623d38b9ba
81 82623d38b9ba
82 392fd25390da
83 392fd25390da
84 $ cd ..
85
86
87 indirect divergence with known changeset
88 -------------------------------------------
89
90 $ newcase indirect_known
91 $ hg debugobsolete `getid A_0` `getid A_1`
92 $ hg debugobsolete `getid A_0` `getid A_2`
93 $ mkcommit A_3
94 created new head
95 $ hg debugobsolete `getid A_2` `getid A_3`
96 $ hg log -G --hidden
97 @ 4:01f36c5a8fda A_3
98 |
99 | x 3:392fd25390da A_2
100 |/
101 | o 2:82623d38b9ba A_1
102 |/
103 | x 1:007dc284c1f8 A_0
104 |/
105 o 0:d20a80d4def3 base
106
107 $ hg debugsuccessorssets 'all()'
108 d20a80d4def3
109 d20a80d4def3
110 007dc284c1f8
111 01f36c5a8fda
112 82623d38b9ba
113 82623d38b9ba
114 82623d38b9ba
115 392fd25390da
116 01f36c5a8fda
117 01f36c5a8fda
118 01f36c5a8fda
119 $ cd ..
120
121
122 indirect divergence with known changeset
123 -------------------------------------------
124
125 $ newcase indirect_unknown
126 $ hg debugobsolete `getid A_0` aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
127 $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa `getid A_1`
128 $ hg debugobsolete `getid A_0` `getid A_2`
129 $ hg log -G --hidden
130 o 3:392fd25390da A_2
131 |
132 | o 2:82623d38b9ba A_1
133 |/
134 | x 1:007dc284c1f8 A_0
135 |/
136 @ 0:d20a80d4def3 base
137
138 $ hg debugsuccessorssets 'all()'
139 d20a80d4def3
140 d20a80d4def3
141 007dc284c1f8
142 82623d38b9ba
143 392fd25390da
144 82623d38b9ba
145 82623d38b9ba
146 392fd25390da
147 392fd25390da
148 $ cd ..
149
150 do not take unknown node in account if they are final
151 -----------------------------------------------------
152
153 $ newcase final-unknown
154 $ hg debugobsolete `getid A_0` `getid A_1`
155 $ hg debugobsolete `getid A_1` `getid A_2`
156 $ hg debugobsolete `getid A_0` bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
157 $ hg debugobsolete bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccccccccccccccccccc
158 $ hg debugobsolete `getid A_1` dddddddddddddddddddddddddddddddddddddddd
159
160 $ hg debugsuccessorssets 'desc('A_0')'
161 007dc284c1f8
162 392fd25390da
163
164 $ cd ..
165
166 divergence that converge again is not divergence anymore
167 -----------------------------------------------------
168
169 $ newcase converged_divergence
170 $ hg debugobsolete `getid A_0` `getid A_1`
171 $ hg debugobsolete `getid A_0` `getid A_2`
172 $ mkcommit A_3
173 created new head
174 $ hg debugobsolete `getid A_1` `getid A_3`
175 $ hg debugobsolete `getid A_2` `getid A_3`
176 $ hg log -G --hidden
177 @ 4:01f36c5a8fda A_3
178 |
179 | x 3:392fd25390da A_2
180 |/
181 | x 2:82623d38b9ba A_1
182 |/
183 | x 1:007dc284c1f8 A_0
184 |/
185 o 0:d20a80d4def3 base
186
187 $ hg debugsuccessorssets 'all()'
188 d20a80d4def3
189 d20a80d4def3
190 007dc284c1f8
191 01f36c5a8fda
192 82623d38b9ba
193 01f36c5a8fda
194 392fd25390da
195 01f36c5a8fda
196 01f36c5a8fda
197 01f36c5a8fda
198 $ cd ..
199
200 split is not divergences
201 -----------------------------
202
203 $ newcase split
204 $ hg debugobsolete `getid A_0` `getid A_1` `getid A_2`
205 $ hg log -G --hidden
206 o 3:392fd25390da A_2
207 |
208 | o 2:82623d38b9ba A_1
209 |/
210 | x 1:007dc284c1f8 A_0
211 |/
212 @ 0:d20a80d4def3 base
213
214 $ hg debugsuccessorssets 'all()'
215 d20a80d4def3
216 d20a80d4def3
217 007dc284c1f8
218 82623d38b9ba 392fd25390da
219 82623d38b9ba
220 82623d38b9ba
221 392fd25390da
222 392fd25390da
223
224 Even when subsequente rewriting happen
225
226 $ mkcommit A_3
227 created new head
228 $ hg debugobsolete `getid A_1` `getid A_3`
229 $ hg up 0
230 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
231 $ mkcommit A_4
232 created new head
233 $ hg debugobsolete `getid A_2` `getid A_4`
234 $ hg up 0
235 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
236 $ mkcommit A_5
237 created new head
238 $ hg debugobsolete `getid A_4` `getid A_5`
239 $ hg log -G --hidden
240 @ 6:e442cfc57690 A_5
241 |
242 | x 5:6a411f0d7a0a A_4
243 |/
244 | o 4:01f36c5a8fda A_3
245 |/
246 | x 3:392fd25390da A_2
247 |/
248 | x 2:82623d38b9ba A_1
249 |/
250 | x 1:007dc284c1f8 A_0
251 |/
252 o 0:d20a80d4def3 base
253
254 $ hg debugsuccessorssets 'all()'
255 d20a80d4def3
256 d20a80d4def3
257 007dc284c1f8
258 01f36c5a8fda e442cfc57690
259 82623d38b9ba
260 01f36c5a8fda
261 392fd25390da
262 e442cfc57690
263 01f36c5a8fda
264 01f36c5a8fda
265 6a411f0d7a0a
266 e442cfc57690
267 e442cfc57690
268 e442cfc57690
269
270 Check more complexe obsolescence graft (with divergence)
271
272 $ mkcommit B_0; hg up 0
273 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
274 $ hg debugobsolete `getid B_0` `getid A_2`
275 $ mkcommit A_7; hg up 0
276 created new head
277 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
278 $ mkcommit A_8; hg up 0
279 created new head
280 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
281 $ hg debugobsolete `getid A_5` `getid A_7` `getid A_8`
282 $ mkcommit A_9; hg up 0
283 created new head
284 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
285 $ hg debugobsolete `getid A_5` `getid A_9`
286 $ hg log -G --hidden
287 o 10:bed64f5d2f5a A_9
288 |
289 | o 9:14608b260df8 A_8
290 |/
291 | o 8:7ae126973a96 A_7
292 |/
293 | x 7:3750ebee865d B_0
294 | |
295 | x 6:e442cfc57690 A_5
296 |/
297 | x 5:6a411f0d7a0a A_4
298 |/
299 | o 4:01f36c5a8fda A_3
300 |/
301 | x 3:392fd25390da A_2
302 |/
303 | x 2:82623d38b9ba A_1
304 |/
305 | x 1:007dc284c1f8 A_0
306 |/
307 @ 0:d20a80d4def3 base
308
309 $ hg debugsuccessorssets 'all()'
310 d20a80d4def3
311 d20a80d4def3
312 007dc284c1f8
313 01f36c5a8fda bed64f5d2f5a
314 01f36c5a8fda 7ae126973a96 14608b260df8
315 82623d38b9ba
316 01f36c5a8fda
317 392fd25390da
318 bed64f5d2f5a
319 7ae126973a96 14608b260df8
320 01f36c5a8fda
321 01f36c5a8fda
322 6a411f0d7a0a
323 bed64f5d2f5a
324 7ae126973a96 14608b260df8
325 e442cfc57690
326 bed64f5d2f5a
327 7ae126973a96 14608b260df8
328 3750ebee865d
329 bed64f5d2f5a
330 7ae126973a96 14608b260df8
331 7ae126973a96
332 7ae126973a96
333 14608b260df8
334 14608b260df8
335 bed64f5d2f5a
336 bed64f5d2f5a
337
338 fix the divergence
339
340 $ mkcommit A_A; hg up 0
341 created new head
342 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
343 $ hg debugobsolete `getid A_9` `getid A_A`
344 $ hg debugobsolete `getid A_7` `getid A_A`
345 $ hg debugobsolete `getid A_8` `getid A_A`
346 $ hg log -G --hidden
347 o 11:a139f71be9da A_A
348 |
349 | x 10:bed64f5d2f5a A_9
350 |/
351 | x 9:14608b260df8 A_8
352 |/
353 | x 8:7ae126973a96 A_7
354 |/
355 | x 7:3750ebee865d B_0
356 | |
357 | x 6:e442cfc57690 A_5
358 |/
359 | x 5:6a411f0d7a0a A_4
360 |/
361 | o 4:01f36c5a8fda A_3
362 |/
363 | x 3:392fd25390da A_2
364 |/
365 | x 2:82623d38b9ba A_1
366 |/
367 | x 1:007dc284c1f8 A_0
368 |/
369 @ 0:d20a80d4def3 base
370
371 $ hg debugsuccessorssets 'all()'
372 d20a80d4def3
373 d20a80d4def3
374 007dc284c1f8
375 01f36c5a8fda a139f71be9da
376 82623d38b9ba
377 01f36c5a8fda
378 392fd25390da
379 a139f71be9da
380 01f36c5a8fda
381 01f36c5a8fda
382 6a411f0d7a0a
383 a139f71be9da
384 e442cfc57690
385 a139f71be9da
386 3750ebee865d
387 a139f71be9da
388 7ae126973a96
389 a139f71be9da
390 14608b260df8
391 a139f71be9da
392 bed64f5d2f5a
393 a139f71be9da
394 a139f71be9da
395 a139f71be9da
396
397 $ cd ..
398
@@ -2459,6 +2459,60 b' def debugsub(ui, repo, rev=None):'
2459 ui.write((' source %s\n') % v[0])
2459 ui.write((' source %s\n') % v[0])
2460 ui.write((' revision %s\n') % v[1])
2460 ui.write((' revision %s\n') % v[1])
2461
2461
2462 @command('debugsuccessorssets',
2463 [],
2464 _('[REV]'))
2465 def debugsuccessorssets(ui, repo, *revs):
2466 """show set of successors for revision
2467
2468 A successors set of changeset A is a consistent group of revisions that
2469 succeed A. It contains non-obsolete changesets only.
2470
2471 In most cases a changeset A has a single successors set containing a single
2472 successors (changeset A replaced by A').
2473
2474 A changeset that is made obsolete with no successors are called "pruned".
2475 Such changesets have no successors sets at all.
2476
2477 A changeset that has been "split" will have a successors set containing
2478 more than one successors.
2479
2480 A changeset that has been rewritten in multiple different ways is called
2481 "divergent". Such changesets have multiple successor sets (each of which
2482 may also be split, i.e. have multiple successors).
2483
2484 Results are displayed as follows::
2485
2486 <rev1>
2487 <successors-1A>
2488 <rev2>
2489 <successors-2A>
2490 <successors-2B1> <successors-2B2> <successors-2B3>
2491
2492 Here rev2 has two possible (i.e. divergent) successors sets. The first
2493 holds one element, whereas the second holds three (i.e. the changeset has
2494 been split).
2495 """
2496 # passed to successorssets caching computation from one call to another
2497 cache = {}
2498 ctx2str = str
2499 node2str = short
2500 if ui.debug():
2501 def ctx2str(ctx):
2502 return ctx.hex()
2503 node2str = hex
2504 for rev in scmutil.revrange(repo, revs):
2505 ctx = repo[rev]
2506 ui.write('%s\n'% ctx2str(ctx))
2507 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
2508 if succsset:
2509 ui.write(' ')
2510 ui.write(node2str(succsset[0]))
2511 for node in succsset[1:]:
2512 ui.write(' ')
2513 ui.write(node2str(node))
2514 ui.write('\n')
2515
2462 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2516 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2463 def debugwalk(ui, repo, *pats, **opts):
2517 def debugwalk(ui, repo, *pats, **opts):
2464 """show how files match on given patterns"""
2518 """show how files match on given patterns"""
@@ -402,6 +402,182 b' def allsuccessors(obsstore, nodes, ignor'
402 seen.add(suc)
402 seen.add(suc)
403 remaining.add(suc)
403 remaining.add(suc)
404
404
405 def successorssets(repo, initialnode, cache=None):
406 """Return all set of successors of initial nodes
407
408 Successors set of changeset A are a group of revision that succeed A. It
409 succeed A as a consistent whole, each revision being only partial
410 replacement. Successors set contains non-obsolete changeset only.
411
412 In most cases a changeset A have zero (changeset pruned) or a single
413 successors set that contains a single successor (changeset A replaced by
414 A')
415
416 When changeset is split, it results successors set containing more than
417 a single element. Divergent rewriting will result in multiple successors
418 sets.
419
420 They are returned as a list of tuples containing all valid successors sets.
421
422 Final successors unknown locally are considered plain prune (obsoleted
423 without successors).
424
425 The optional `cache` parameter is a dictionary that may contains
426 precomputed successors sets. It is meant to reuse the computation of
427 previous call to `successorssets` when multiple calls are made at the same
428 time. The cache dictionary is updated in place. The caller is responsible
429 for its live spawn. Code that makes multiple calls to `successorssets`
430 *must* use this cache mechanism or suffer terrible performances."""
431
432 succmarkers = repo.obsstore.successors
433
434 # Stack of nodes we search successors sets for
435 toproceed = [initialnode]
436 # set version of above list for fast loop detection
437 # element added to "toproceed" must be added here
438 stackedset = set(toproceed)
439 if cache is None:
440 cache = {}
441
442 # This while loop is the flattened version of a recursive search for
443 # successors sets
444 #
445 # def successorssets(x):
446 # successors = directsuccessors(x)
447 # ss = [[]]
448 # for succ in directsuccessors(x):
449 # # product as in itertools cartesian product
450 # ss = product(ss, successorssets(succ))
451 # return ss
452 #
453 # But we can not use plain recursive calls here:
454 # - that would blow the python call stack
455 # - obsolescence markers may have cycles, we need to handle them.
456 #
457 # The `toproceed` list act as our call stack. Every node we search
458 # successors set for are stacked there.
459 #
460 # The `stackedset` is set version of this stack used to check if a node is
461 # already stacked. This check is used to detect cycles and prevent infinite
462 # loop.
463 #
464 # successors set of all nodes are stored in the `cache` dictionary.
465 #
466 # After this while loop ends we use the cache to return the successors sets
467 # for the node requested by the caller.
468 while toproceed:
469 # Every iteration tries to compute the successors sets of the topmost
470 # node of the stack: CURRENT.
471 #
472 # There are four possible outcomes:
473 #
474 # 1) We already know the successors sets of CURRENT:
475 # -> mission accomplished, pop it from the stack.
476 # 2) Node is not obsolete:
477 # -> the node is its own successors sets. Add it to the cache.
478 # 3) We do not know successors set of direct successors of CURRENT:
479 # -> We add those successors to the stack.
480 # 4) We know successors sets of all direct successors of CURRENT:
481 # -> We can compute CURRENT successors set and add it to the
482 # cache.
483 #
484 current = toproceed[-1]
485 if current in cache:
486 # case (1): We already know the successors sets
487 stackedset.remove(toproceed.pop())
488 elif current not in succmarkers:
489 # case (2): The node is not obsolete.
490 if current in repo:
491 # We have a valid last successors.
492 cache[current] = [(current,)]
493 else:
494 # Final obsolete version is unknown locally.
495 # Do not count that as a valid successors
496 cache[current] = []
497 else:
498 # cases (3) and (4)
499 #
500 # We proceed in two phases. Phase 1 aims to distinguish case (3)
501 # from case (4):
502 #
503 # For each direct successors of CURRENT, we check whether its
504 # successors sets are known. If they are not, we stack the
505 # unknown node and proceed to the next iteration of the while
506 # loop. (case 3)
507 #
508 # During this step, we may detect obsolescence cycles: a node
509 # with unknown successors sets but already in the call stack.
510 # In such a situation, we arbitrary set the successors sets of
511 # the node to nothing (node pruned) to break the cycle.
512 #
513 # If no break was encountered we proceeed to phase 2.
514 #
515 # Phase 2 computes successors sets of CURRENT (case 4); see details
516 # in phase 2 itself.
517 #
518 # Note the two levels of iteration in each phase.
519 # - The first one handles obsolescence markers using CURRENT as
520 # precursor (successors markers of CURRENT).
521 #
522 # Having multiple entry here means divergence.
523 #
524 # - The second one handles successors defined in each marker.
525 #
526 # Having none means pruned node, multiple successors means split,
527 # single successors are standard replacement.
528 #
529 for mark in succmarkers[current]:
530 for suc in mark[1]:
531 if suc not in cache:
532 if suc in stackedset:
533 # cycle breaking
534 cache[suc] = []
535 else:
536 # case (3) If we have not computed successors sets
537 # of one of those successors we add it to the
538 # `toproceed` stack and stop all work for this
539 # iteration.
540 toproceed.append(suc)
541 stackedset.add(suc)
542 break
543 else:
544 continue
545 break
546 else:
547 # case (4): we know all successors sets of all direct
548 # successors
549 #
550 # Successors set contributed by each marker depends on the
551 # successors sets of all its "successors" node.
552 #
553 # Each different marker is a divergence in the obsolescence
554 # history. It contributes successors sets dictinct from other
555 # markers.
556 #
557 # Within a marker, a successor may have divergent successors
558 # sets. In such a case, the marker will contribute multiple
559 # divergent successors sets. If multiple successors have
560 # divergents successors sets, a cartesian product is used.
561 succssets = []
562 for mark in succmarkers[current]:
563 # successors sets contributed by this marker
564 markss = [[]]
565 for suc in mark[1]:
566 productresult = []
567 for prefix in markss:
568 for suffix in cache[suc]:
569 newss = list(prefix)
570 for part in suffix:
571 # do not duplicated entry in successors set
572 # first entry wins.
573 if part not in newss:
574 newss.append(part)
575 productresult.append(newss)
576 markss = productresult
577 succssets.extend(markss)
578 cache[current] = list(set(tuple(r) for r in succssets if r))
579 return cache[initialnode]
580
405 def _knownrevs(repo, nodes):
581 def _knownrevs(repo, nodes):
406 """yield revision numbers of known nodes passed in parameters
582 """yield revision numbers of known nodes passed in parameters
407
583
@@ -96,6 +96,7 b' Show debug commands if there are no othe'
96 debugsetparents
96 debugsetparents
97 debugstate
97 debugstate
98 debugsub
98 debugsub
99 debugsuccessorssets
99 debugwalk
100 debugwalk
100 debugwireargs
101 debugwireargs
101
102
@@ -246,6 +247,7 b' Show all commands + options'
246 debugsetparents:
247 debugsetparents:
247 debugstate: nodates, datesort
248 debugstate: nodates, datesort
248 debugsub: rev
249 debugsub: rev
250 debugsuccessorssets:
249 debugwalk: include, exclude
251 debugwalk: include, exclude
250 debugwireargs: three, four, five, ssh, remotecmd, insecure
252 debugwireargs: three, four, five, ssh, remotecmd, insecure
251 graft: rev, continue, edit, log, currentdate, currentuser, date, user, tool, dry-run
253 graft: rev, continue, edit, log, currentdate, currentuser, date, user, tool, dry-run
General Comments 0
You need to be logged in to leave comments. Login now