##// END OF EJS Templates
effectflag: detect when parents changed...
Boris Feld -
r34420:fa26f589 default
parent child Browse files
Show More
@@ -1,692 +1,697 b''
1 1 # obsutil.py - utility functions for obsolescence
2 2 #
3 3 # Copyright 2017 Boris Feld <boris.feld@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from . import (
11 11 phases,
12 12 util
13 13 )
14 14
15 15 class marker(object):
16 16 """Wrap obsolete marker raw data"""
17 17
18 18 def __init__(self, repo, data):
19 19 # the repo argument will be used to create changectx in later version
20 20 self._repo = repo
21 21 self._data = data
22 22 self._decodedmeta = None
23 23
24 24 def __hash__(self):
25 25 return hash(self._data)
26 26
27 27 def __eq__(self, other):
28 28 if type(other) != type(self):
29 29 return False
30 30 return self._data == other._data
31 31
32 32 def precnode(self):
33 33 msg = ("'marker.precnode' is deprecated, "
34 34 "use 'marker.prednode'")
35 35 util.nouideprecwarn(msg, '4.4')
36 36 return self.prednode()
37 37
38 38 def prednode(self):
39 39 """Predecessor changeset node identifier"""
40 40 return self._data[0]
41 41
42 42 def succnodes(self):
43 43 """List of successor changesets node identifiers"""
44 44 return self._data[1]
45 45
46 46 def parentnodes(self):
47 47 """Parents of the predecessors (None if not recorded)"""
48 48 return self._data[5]
49 49
50 50 def metadata(self):
51 51 """Decoded metadata dictionary"""
52 52 return dict(self._data[3])
53 53
54 54 def date(self):
55 55 """Creation date as (unixtime, offset)"""
56 56 return self._data[4]
57 57
58 58 def flags(self):
59 59 """The flags field of the marker"""
60 60 return self._data[2]
61 61
62 62 def getmarkers(repo, nodes=None, exclusive=False):
63 63 """returns markers known in a repository
64 64
65 65 If <nodes> is specified, only markers "relevant" to those nodes are are
66 66 returned"""
67 67 if nodes is None:
68 68 rawmarkers = repo.obsstore
69 69 elif exclusive:
70 70 rawmarkers = exclusivemarkers(repo, nodes)
71 71 else:
72 72 rawmarkers = repo.obsstore.relevantmarkers(nodes)
73 73
74 74 for markerdata in rawmarkers:
75 75 yield marker(repo, markerdata)
76 76
77 77 def closestpredecessors(repo, nodeid):
78 78 """yield the list of next predecessors pointing on visible changectx nodes
79 79
80 80 This function respect the repoview filtering, filtered revision will be
81 81 considered missing.
82 82 """
83 83
84 84 precursors = repo.obsstore.predecessors
85 85 stack = [nodeid]
86 86 seen = set(stack)
87 87
88 88 while stack:
89 89 current = stack.pop()
90 90 currentpreccs = precursors.get(current, ())
91 91
92 92 for prec in currentpreccs:
93 93 precnodeid = prec[0]
94 94
95 95 # Basic cycle protection
96 96 if precnodeid in seen:
97 97 continue
98 98 seen.add(precnodeid)
99 99
100 100 if precnodeid in repo:
101 101 yield precnodeid
102 102 else:
103 103 stack.append(precnodeid)
104 104
105 105 def allprecursors(*args, **kwargs):
106 106 """ (DEPRECATED)
107 107 """
108 108 msg = ("'obsutil.allprecursors' is deprecated, "
109 109 "use 'obsutil.allpredecessors'")
110 110 util.nouideprecwarn(msg, '4.4')
111 111
112 112 return allpredecessors(*args, **kwargs)
113 113
114 114 def allpredecessors(obsstore, nodes, ignoreflags=0):
115 115 """Yield node for every precursors of <nodes>.
116 116
117 117 Some precursors may be unknown locally.
118 118
119 119 This is a linear yield unsuited to detecting folded changesets. It includes
120 120 initial nodes too."""
121 121
122 122 remaining = set(nodes)
123 123 seen = set(remaining)
124 124 while remaining:
125 125 current = remaining.pop()
126 126 yield current
127 127 for mark in obsstore.predecessors.get(current, ()):
128 128 # ignore marker flagged with specified flag
129 129 if mark[2] & ignoreflags:
130 130 continue
131 131 suc = mark[0]
132 132 if suc not in seen:
133 133 seen.add(suc)
134 134 remaining.add(suc)
135 135
136 136 def allsuccessors(obsstore, nodes, ignoreflags=0):
137 137 """Yield node for every successor of <nodes>.
138 138
139 139 Some successors may be unknown locally.
140 140
141 141 This is a linear yield unsuited to detecting split changesets. It includes
142 142 initial nodes too."""
143 143 remaining = set(nodes)
144 144 seen = set(remaining)
145 145 while remaining:
146 146 current = remaining.pop()
147 147 yield current
148 148 for mark in obsstore.successors.get(current, ()):
149 149 # ignore marker flagged with specified flag
150 150 if mark[2] & ignoreflags:
151 151 continue
152 152 for suc in mark[1]:
153 153 if suc not in seen:
154 154 seen.add(suc)
155 155 remaining.add(suc)
156 156
157 157 def _filterprunes(markers):
158 158 """return a set with no prune markers"""
159 159 return set(m for m in markers if m[1])
160 160
161 161 def exclusivemarkers(repo, nodes):
162 162 """set of markers relevant to "nodes" but no other locally-known nodes
163 163
164 164 This function compute the set of markers "exclusive" to a locally-known
165 165 node. This means we walk the markers starting from <nodes> until we reach a
166 166 locally-known precursors outside of <nodes>. Element of <nodes> with
167 167 locally-known successors outside of <nodes> are ignored (since their
168 168 precursors markers are also relevant to these successors).
169 169
170 170 For example:
171 171
172 172 # (A0 rewritten as A1)
173 173 #
174 174 # A0 <-1- A1 # Marker "1" is exclusive to A1
175 175
176 176 or
177 177
178 178 # (A0 rewritten as AX; AX rewritten as A1; AX is unkown locally)
179 179 #
180 180 # <-1- A0 <-2- AX <-3- A1 # Marker "2,3" are exclusive to A1
181 181
182 182 or
183 183
184 184 # (A0 has unknown precursors, A0 rewritten as A1 and A2 (divergence))
185 185 #
186 186 # <-2- A1 # Marker "2" is exclusive to A0,A1
187 187 # /
188 188 # <-1- A0
189 189 # \
190 190 # <-3- A2 # Marker "3" is exclusive to A0,A2
191 191 #
192 192 # in addition:
193 193 #
194 194 # Markers "2,3" are exclusive to A1,A2
195 195 # Markers "1,2,3" are exclusive to A0,A1,A2
196 196
197 197 See test/test-obsolete-bundle-strip.t for more examples.
198 198
199 199 An example usage is strip. When stripping a changeset, we also want to
200 200 strip the markers exclusive to this changeset. Otherwise we would have
201 201 "dangling"" obsolescence markers from its precursors: Obsolescence markers
202 202 marking a node as obsolete without any successors available locally.
203 203
204 204 As for relevant markers, the prune markers for children will be followed.
205 205 Of course, they will only be followed if the pruned children is
206 206 locally-known. Since the prune markers are relevant to the pruned node.
207 207 However, while prune markers are considered relevant to the parent of the
208 208 pruned changesets, prune markers for locally-known changeset (with no
209 209 successors) are considered exclusive to the pruned nodes. This allows
210 210 to strip the prune markers (with the rest of the exclusive chain) alongside
211 211 the pruned changesets.
212 212 """
213 213 # running on a filtered repository would be dangerous as markers could be
214 214 # reported as exclusive when they are relevant for other filtered nodes.
215 215 unfi = repo.unfiltered()
216 216
217 217 # shortcut to various useful item
218 218 nm = unfi.changelog.nodemap
219 219 precursorsmarkers = unfi.obsstore.predecessors
220 220 successormarkers = unfi.obsstore.successors
221 221 childrenmarkers = unfi.obsstore.children
222 222
223 223 # exclusive markers (return of the function)
224 224 exclmarkers = set()
225 225 # we need fast membership testing
226 226 nodes = set(nodes)
227 227 # looking for head in the obshistory
228 228 #
229 229 # XXX we are ignoring all issues in regard with cycle for now.
230 230 stack = [n for n in nodes if not _filterprunes(successormarkers.get(n, ()))]
231 231 stack.sort()
232 232 # nodes already stacked
233 233 seennodes = set(stack)
234 234 while stack:
235 235 current = stack.pop()
236 236 # fetch precursors markers
237 237 markers = list(precursorsmarkers.get(current, ()))
238 238 # extend the list with prune markers
239 239 for mark in successormarkers.get(current, ()):
240 240 if not mark[1]:
241 241 markers.append(mark)
242 242 # and markers from children (looking for prune)
243 243 for mark in childrenmarkers.get(current, ()):
244 244 if not mark[1]:
245 245 markers.append(mark)
246 246 # traverse the markers
247 247 for mark in markers:
248 248 if mark in exclmarkers:
249 249 # markers already selected
250 250 continue
251 251
252 252 # If the markers is about the current node, select it
253 253 #
254 254 # (this delay the addition of markers from children)
255 255 if mark[1] or mark[0] == current:
256 256 exclmarkers.add(mark)
257 257
258 258 # should we keep traversing through the precursors?
259 259 prec = mark[0]
260 260
261 261 # nodes in the stack or already processed
262 262 if prec in seennodes:
263 263 continue
264 264
265 265 # is this a locally known node ?
266 266 known = prec in nm
267 267 # if locally-known and not in the <nodes> set the traversal
268 268 # stop here.
269 269 if known and prec not in nodes:
270 270 continue
271 271
272 272 # do not keep going if there are unselected markers pointing to this
273 273 # nodes. If we end up traversing these unselected markers later the
274 274 # node will be taken care of at that point.
275 275 precmarkers = _filterprunes(successormarkers.get(prec))
276 276 if precmarkers.issubset(exclmarkers):
277 277 seennodes.add(prec)
278 278 stack.append(prec)
279 279
280 280 return exclmarkers
281 281
282 282 def foreground(repo, nodes):
283 283 """return all nodes in the "foreground" of other node
284 284
285 285 The foreground of a revision is anything reachable using parent -> children
286 286 or precursor -> successor relation. It is very similar to "descendant" but
287 287 augmented with obsolescence information.
288 288
289 289 Beware that possible obsolescence cycle may result if complex situation.
290 290 """
291 291 repo = repo.unfiltered()
292 292 foreground = set(repo.set('%ln::', nodes))
293 293 if repo.obsstore:
294 294 # We only need this complicated logic if there is obsolescence
295 295 # XXX will probably deserve an optimised revset.
296 296 nm = repo.changelog.nodemap
297 297 plen = -1
298 298 # compute the whole set of successors or descendants
299 299 while len(foreground) != plen:
300 300 plen = len(foreground)
301 301 succs = set(c.node() for c in foreground)
302 302 mutable = [c.node() for c in foreground if c.mutable()]
303 303 succs.update(allsuccessors(repo.obsstore, mutable))
304 304 known = (n for n in succs if n in nm)
305 305 foreground = set(repo.set('%ln::', known))
306 306 return set(c.node() for c in foreground)
307 307
308 308 # logic around storing and using effect flags
309 309 EFFECTFLAGFIELD = "ef1"
310 310
311 311 DESCCHANGED = 1 << 0 # action changed the description
312 PARENTCHANGED = 1 << 2 # action change the parent
312 313 USERCHANGED = 1 << 4 # the user changed
313 314 DATECHANGED = 1 << 5 # the date changed
314 315 BRANCHCHANGED = 1 << 6 # the branch changed
315 316
316 317 def geteffectflag(relation):
317 318 """ From an obs-marker relation, compute what changed between the
318 319 predecessor and the successor.
319 320 """
320 321 effects = 0
321 322
322 323 source = relation[0]
323 324
324 325 for changectx in relation[1]:
325 326 # Check if description has changed
326 327 if changectx.description() != source.description():
327 328 effects |= DESCCHANGED
328 329
329 330 # Check if user has changed
330 331 if changectx.user() != source.user():
331 332 effects |= USERCHANGED
332 333
333 334 # Check if date has changed
334 335 if changectx.date() != source.date():
335 336 effects |= DATECHANGED
336 337
337 338 # Check if branch has changed
338 339 if changectx.branch() != source.branch():
339 340 effects |= BRANCHCHANGED
340 341
342 # Check if at least one of the parent has changed
343 if changectx.parents() != source.parents():
344 effects |= PARENTCHANGED
345
341 346 return effects
342 347
343 348 def getobsoleted(repo, tr):
344 349 """return the set of pre-existing revisions obsoleted by a transaction"""
345 350 torev = repo.unfiltered().changelog.nodemap.get
346 351 phase = repo._phasecache.phase
347 352 succsmarkers = repo.obsstore.successors.get
348 353 public = phases.public
349 354 addedmarkers = tr.changes.get('obsmarkers')
350 355 addedrevs = tr.changes.get('revs')
351 356 seenrevs = set(addedrevs)
352 357 obsoleted = set()
353 358 for mark in addedmarkers:
354 359 node = mark[0]
355 360 rev = torev(node)
356 361 if rev is None or rev in seenrevs:
357 362 continue
358 363 seenrevs.add(rev)
359 364 if phase(repo, rev) == public:
360 365 continue
361 366 if set(succsmarkers(node) or []).issubset(addedmarkers):
362 367 obsoleted.add(rev)
363 368 return obsoleted
364 369
365 370 class _succs(list):
366 371 """small class to represent a successors with some metadata about it"""
367 372
368 373 def __init__(self, *args, **kwargs):
369 374 super(_succs, self).__init__(*args, **kwargs)
370 375 self.markers = set()
371 376
372 377 def copy(self):
373 378 new = _succs(self)
374 379 new.markers = self.markers.copy()
375 380 return new
376 381
377 382 @util.propertycache
378 383 def _set(self):
379 384 # immutable
380 385 return set(self)
381 386
382 387 def canmerge(self, other):
383 388 return self._set.issubset(other._set)
384 389
385 390 def successorssets(repo, initialnode, closest=False, cache=None):
386 391 """Return set of all latest successors of initial nodes
387 392
388 393 The successors set of a changeset A are the group of revisions that succeed
389 394 A. It succeeds A as a consistent whole, each revision being only a partial
390 395 replacement. By default, the successors set contains non-obsolete
391 396 changesets only, walking the obsolescence graph until reaching a leaf. If
392 397 'closest' is set to True, closest successors-sets are return (the
393 398 obsolescence walk stops on known changesets).
394 399
395 400 This function returns the full list of successor sets which is why it
396 401 returns a list of tuples and not just a single tuple. Each tuple is a valid
397 402 successors set. Note that (A,) may be a valid successors set for changeset A
398 403 (see below).
399 404
400 405 In most cases, a changeset A will have a single element (e.g. the changeset
401 406 A is replaced by A') in its successors set. Though, it is also common for a
402 407 changeset A to have no elements in its successor set (e.g. the changeset
403 408 has been pruned). Therefore, the returned list of successors sets will be
404 409 [(A',)] or [], respectively.
405 410
406 411 When a changeset A is split into A' and B', however, it will result in a
407 412 successors set containing more than a single element, i.e. [(A',B')].
408 413 Divergent changesets will result in multiple successors sets, i.e. [(A',),
409 414 (A'')].
410 415
411 416 If a changeset A is not obsolete, then it will conceptually have no
412 417 successors set. To distinguish this from a pruned changeset, the successor
413 418 set will contain itself only, i.e. [(A,)].
414 419
415 420 Finally, final successors unknown locally are considered to be pruned
416 421 (pruned: obsoleted without any successors). (Final: successors not affected
417 422 by markers).
418 423
419 424 The 'closest' mode respect the repoview filtering. For example, without
420 425 filter it will stop at the first locally known changeset, with 'visible'
421 426 filter it will stop on visible changesets).
422 427
423 428 The optional `cache` parameter is a dictionary that may contains
424 429 precomputed successors sets. It is meant to reuse the computation of a
425 430 previous call to `successorssets` when multiple calls are made at the same
426 431 time. The cache dictionary is updated in place. The caller is responsible
427 432 for its life span. Code that makes multiple calls to `successorssets`
428 433 *should* use this cache mechanism or risk a performance hit.
429 434
430 435 Since results are different depending of the 'closest' most, the same cache
431 436 cannot be reused for both mode.
432 437 """
433 438
434 439 succmarkers = repo.obsstore.successors
435 440
436 441 # Stack of nodes we search successors sets for
437 442 toproceed = [initialnode]
438 443 # set version of above list for fast loop detection
439 444 # element added to "toproceed" must be added here
440 445 stackedset = set(toproceed)
441 446 if cache is None:
442 447 cache = {}
443 448
444 449 # This while loop is the flattened version of a recursive search for
445 450 # successors sets
446 451 #
447 452 # def successorssets(x):
448 453 # successors = directsuccessors(x)
449 454 # ss = [[]]
450 455 # for succ in directsuccessors(x):
451 456 # # product as in itertools cartesian product
452 457 # ss = product(ss, successorssets(succ))
453 458 # return ss
454 459 #
455 460 # But we can not use plain recursive calls here:
456 461 # - that would blow the python call stack
457 462 # - obsolescence markers may have cycles, we need to handle them.
458 463 #
459 464 # The `toproceed` list act as our call stack. Every node we search
460 465 # successors set for are stacked there.
461 466 #
462 467 # The `stackedset` is set version of this stack used to check if a node is
463 468 # already stacked. This check is used to detect cycles and prevent infinite
464 469 # loop.
465 470 #
466 471 # successors set of all nodes are stored in the `cache` dictionary.
467 472 #
468 473 # After this while loop ends we use the cache to return the successors sets
469 474 # for the node requested by the caller.
470 475 while toproceed:
471 476 # Every iteration tries to compute the successors sets of the topmost
472 477 # node of the stack: CURRENT.
473 478 #
474 479 # There are four possible outcomes:
475 480 #
476 481 # 1) We already know the successors sets of CURRENT:
477 482 # -> mission accomplished, pop it from the stack.
478 483 # 2) Stop the walk:
479 484 # default case: Node is not obsolete
480 485 # closest case: Node is known at this repo filter level
481 486 # -> the node is its own successors sets. Add it to the cache.
482 487 # 3) We do not know successors set of direct successors of CURRENT:
483 488 # -> We add those successors to the stack.
484 489 # 4) We know successors sets of all direct successors of CURRENT:
485 490 # -> We can compute CURRENT successors set and add it to the
486 491 # cache.
487 492 #
488 493 current = toproceed[-1]
489 494
490 495 # case 2 condition is a bit hairy because of closest,
491 496 # we compute it on its own
492 497 case2condition = ((current not in succmarkers)
493 498 or (closest and current != initialnode
494 499 and current in repo))
495 500
496 501 if current in cache:
497 502 # case (1): We already know the successors sets
498 503 stackedset.remove(toproceed.pop())
499 504 elif case2condition:
500 505 # case (2): end of walk.
501 506 if current in repo:
502 507 # We have a valid successors.
503 508 cache[current] = [_succs((current,))]
504 509 else:
505 510 # Final obsolete version is unknown locally.
506 511 # Do not count that as a valid successors
507 512 cache[current] = []
508 513 else:
509 514 # cases (3) and (4)
510 515 #
511 516 # We proceed in two phases. Phase 1 aims to distinguish case (3)
512 517 # from case (4):
513 518 #
514 519 # For each direct successors of CURRENT, we check whether its
515 520 # successors sets are known. If they are not, we stack the
516 521 # unknown node and proceed to the next iteration of the while
517 522 # loop. (case 3)
518 523 #
519 524 # During this step, we may detect obsolescence cycles: a node
520 525 # with unknown successors sets but already in the call stack.
521 526 # In such a situation, we arbitrary set the successors sets of
522 527 # the node to nothing (node pruned) to break the cycle.
523 528 #
524 529 # If no break was encountered we proceed to phase 2.
525 530 #
526 531 # Phase 2 computes successors sets of CURRENT (case 4); see details
527 532 # in phase 2 itself.
528 533 #
529 534 # Note the two levels of iteration in each phase.
530 535 # - The first one handles obsolescence markers using CURRENT as
531 536 # precursor (successors markers of CURRENT).
532 537 #
533 538 # Having multiple entry here means divergence.
534 539 #
535 540 # - The second one handles successors defined in each marker.
536 541 #
537 542 # Having none means pruned node, multiple successors means split,
538 543 # single successors are standard replacement.
539 544 #
540 545 for mark in sorted(succmarkers[current]):
541 546 for suc in mark[1]:
542 547 if suc not in cache:
543 548 if suc in stackedset:
544 549 # cycle breaking
545 550 cache[suc] = []
546 551 else:
547 552 # case (3) If we have not computed successors sets
548 553 # of one of those successors we add it to the
549 554 # `toproceed` stack and stop all work for this
550 555 # iteration.
551 556 toproceed.append(suc)
552 557 stackedset.add(suc)
553 558 break
554 559 else:
555 560 continue
556 561 break
557 562 else:
558 563 # case (4): we know all successors sets of all direct
559 564 # successors
560 565 #
561 566 # Successors set contributed by each marker depends on the
562 567 # successors sets of all its "successors" node.
563 568 #
564 569 # Each different marker is a divergence in the obsolescence
565 570 # history. It contributes successors sets distinct from other
566 571 # markers.
567 572 #
568 573 # Within a marker, a successor may have divergent successors
569 574 # sets. In such a case, the marker will contribute multiple
570 575 # divergent successors sets. If multiple successors have
571 576 # divergent successors sets, a Cartesian product is used.
572 577 #
573 578 # At the end we post-process successors sets to remove
574 579 # duplicated entry and successors set that are strict subset of
575 580 # another one.
576 581 succssets = []
577 582 for mark in sorted(succmarkers[current]):
578 583 # successors sets contributed by this marker
579 584 base = _succs()
580 585 base.markers.add(mark)
581 586 markss = [base]
582 587 for suc in mark[1]:
583 588 # cardinal product with previous successors
584 589 productresult = []
585 590 for prefix in markss:
586 591 for suffix in cache[suc]:
587 592 newss = prefix.copy()
588 593 newss.markers.update(suffix.markers)
589 594 for part in suffix:
590 595 # do not duplicated entry in successors set
591 596 # first entry wins.
592 597 if part not in newss:
593 598 newss.append(part)
594 599 productresult.append(newss)
595 600 markss = productresult
596 601 succssets.extend(markss)
597 602 # remove duplicated and subset
598 603 seen = []
599 604 final = []
600 605 candidates = sorted((s for s in succssets if s),
601 606 key=len, reverse=True)
602 607 for cand in candidates:
603 608 for seensuccs in seen:
604 609 if cand.canmerge(seensuccs):
605 610 seensuccs.markers.update(cand.markers)
606 611 break
607 612 else:
608 613 final.append(cand)
609 614 seen.append(cand)
610 615 final.reverse() # put small successors set first
611 616 cache[current] = final
612 617 return cache[initialnode]
613 618
614 619 def successorsandmarkers(repo, ctx):
615 620 """compute the raw data needed for computing obsfate
616 621 Returns a list of dict, one dict per successors set
617 622 """
618 623 if not ctx.obsolete():
619 624 return None
620 625
621 626 ssets = successorssets(repo, ctx.node(), closest=True)
622 627
623 628 # closestsuccessors returns an empty list for pruned revisions, remap it
624 629 # into a list containing an empty list for future processing
625 630 if ssets == []:
626 631 ssets = [[]]
627 632
628 633 # Try to recover pruned markers
629 634 succsmap = repo.obsstore.successors
630 635 fullsuccessorsets = [] # successor set + markers
631 636 for sset in ssets:
632 637 if sset:
633 638 fullsuccessorsets.append(sset)
634 639 else:
635 640 # successorsset return an empty set() when ctx or one of its
636 641 # successors is pruned.
637 642 # In this case, walk the obs-markers tree again starting with ctx
638 643 # and find the relevant pruning obs-makers, the ones without
639 644 # successors.
640 645 # Having these markers allow us to compute some information about
641 646 # its fate, like who pruned this changeset and when.
642 647
643 648 # XXX we do not catch all prune markers (eg rewritten then pruned)
644 649 # (fix me later)
645 650 foundany = False
646 651 for mark in succsmap.get(ctx.node(), ()):
647 652 if not mark[1]:
648 653 foundany = True
649 654 sset = _succs()
650 655 sset.markers.add(mark)
651 656 fullsuccessorsets.append(sset)
652 657 if not foundany:
653 658 fullsuccessorsets.append(_succs())
654 659
655 660 values = []
656 661 for sset in fullsuccessorsets:
657 662 values.append({'successors': sset, 'markers': sset.markers})
658 663
659 664 return values
660 665
661 666 def successorsetverb(successorset):
662 667 """ Return the verb summarizing the successorset
663 668 """
664 669 if not successorset:
665 670 verb = 'pruned'
666 671 elif len(successorset) == 1:
667 672 verb = 'rewritten'
668 673 else:
669 674 verb = 'split'
670 675 return verb
671 676
672 677 def markersdates(markers):
673 678 """returns the list of dates for a list of markers
674 679 """
675 680 return [m[4] for m in markers]
676 681
677 682 def markersusers(markers):
678 683 """ Returns a sorted list of markers users without duplicates
679 684 """
680 685 markersmeta = [dict(m[3]) for m in markers]
681 686 users = set(meta.get('user') for meta in markersmeta if meta.get('user'))
682 687
683 688 return sorted(users)
684 689
685 690 def markersoperations(markers):
686 691 """ Returns a sorted list of markers operations without duplicates
687 692 """
688 693 markersmeta = [dict(m[3]) for m in markers]
689 694 operations = set(meta.get('operation') for meta in markersmeta
690 695 if meta.get('operation'))
691 696
692 697 return sorted(operations)
@@ -1,167 +1,167 b''
1 1 Test the 'effect-flags' feature
2 2
3 3 Global setup
4 4 ============
5 5
6 6 $ . $TESTDIR/testlib/obsmarker-common.sh
7 7 $ cat >> $HGRCPATH <<EOF
8 8 > [ui]
9 9 > interactive = true
10 10 > [phases]
11 11 > publish=False
12 12 > [extensions]
13 13 > rebase =
14 14 > [experimental]
15 15 > evolution = all
16 16 > effect-flags = 1
17 17 > EOF
18 18
19 19 $ hg init $TESTTMP/effect-flags
20 20 $ cd $TESTTMP/effect-flags
21 21 $ mkcommit ROOT
22 22
23 23 amend touching the description only
24 24 -----------------------------------
25 25
26 26 $ mkcommit A0
27 27 $ hg commit --amend -m "A1"
28 28
29 29 check result
30 30
31 31 $ hg debugobsolete --rev .
32 32 471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
33 33
34 34 amend touching the user only
35 35 ----------------------------
36 36
37 37 $ mkcommit B0
38 38 $ hg commit --amend -u "bob <bob@bob.com>"
39 39
40 40 check result
41 41
42 42 $ hg debugobsolete --rev .
43 43 ef4a313b1e0ade55718395d80e6b88c5ccd875eb 5485c92d34330dac9d7a63dc07e1e3373835b964 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
44 44
45 45 amend touching the date only
46 46 ----------------------------
47 47
48 48 $ mkcommit B1
49 49 $ hg commit --amend -d "42 0"
50 50
51 51 check result
52 52
53 53 $ hg debugobsolete --rev .
54 54 2ef0680ff45038ac28c9f1ff3644341f54487280 4dd84345082e9e5291c2e6b3f335bbf8bf389378 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '32', 'operation': 'amend', 'user': 'test'}
55 55
56 56 amend touching the branch only
57 57 ----------------------------
58 58
59 59 $ mkcommit B2
60 60 $ hg branch my-branch
61 61 marked working directory as branch my-branch
62 62 (branches are permanent and global, did you want a bookmark?)
63 63 $ hg commit --amend
64 64
65 65 check result
66 66
67 67 $ hg debugobsolete --rev .
68 68 bd3db8264ceebf1966319f5df3be7aac6acd1a8e 14a01456e0574f0e0a0b15b2345486a6364a8d79 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '64', 'operation': 'amend', 'user': 'test'}
69 69
70 70 $ hg up default
71 71 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
72 72
73 73 rebase (parents change)
74 74 -----------------------
75 75
76 76 $ mkcommit C0
77 77 $ mkcommit D0
78 78 $ hg rebase -r . -d 'desc(B0)'
79 79 rebasing 10:c85eff83a034 "D0" (tip)
80 80
81 81 check result
82 82
83 83 $ hg debugobsolete --rev .
84 c85eff83a0340efd9da52b806a94c350222f3371 da86aa2f19a30d6686b15cae15c7b6c908ec9699 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
84 c85eff83a0340efd9da52b806a94c350222f3371 da86aa2f19a30d6686b15cae15c7b6c908ec9699 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
85 85
86 86 amend touching the diff
87 87 -----------------------
88 88
89 89 $ mkcommit E0
90 90 $ echo 42 >> E0
91 91 $ hg commit --amend
92 92
93 93 check result
94 94
95 95 $ hg debugobsolete --rev .
96 96 ebfe0333e0d96f68a917afd97c0a0af87f1c3b5f 75781fdbdbf58a987516b00c980bccda1e9ae588 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
97 97
98 98 amend with multiple effect (desc and meta)
99 99 -------------------------------------------
100 100
101 101 $ mkcommit F0
102 102 $ hg branch my-other-branch
103 103 marked working directory as branch my-other-branch
104 104 $ hg commit --amend -m F1 -u "bob <bob@bob.com>" -d "42 0"
105 105
106 106 check result
107 107
108 108 $ hg debugobsolete --rev .
109 109 fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '113', 'operation': 'amend', 'user': 'test'}
110 110
111 111 rebase not touching the diff
112 112 ----------------------------
113 113
114 114 $ cat << EOF > H0
115 115 > 0
116 116 > 1
117 117 > 2
118 118 > 3
119 119 > 4
120 120 > 5
121 121 > 6
122 122 > 7
123 123 > 8
124 124 > 9
125 125 > 10
126 126 > EOF
127 127 $ hg add H0
128 128 $ hg commit -m 'H0'
129 129 $ echo "H1" >> H0
130 130 $ hg commit -m "H1"
131 131 $ hg up -r "desc(H0)"
132 132 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
133 133 $ cat << EOF > H0
134 134 > H2
135 135 > 0
136 136 > 1
137 137 > 2
138 138 > 3
139 139 > 4
140 140 > 5
141 141 > 6
142 142 > 7
143 143 > 8
144 144 > 9
145 145 > 10
146 146 > EOF
147 147 $ hg commit -m "H2"
148 148 created new head
149 149 $ hg rebase -s "desc(H1)" -d "desc(H2)" -t :merge3
150 150 rebasing 17:b57fed8d8322 "H1"
151 151 merging H0
152 152 $ hg debugobsolete -r tip
153 b57fed8d83228a8ae3748d8c3760a77638dd4f8c e509e2eb3df5d131ff7c02350bf2a9edd0c09478 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
153 b57fed8d83228a8ae3748d8c3760a77638dd4f8c e509e2eb3df5d131ff7c02350bf2a9edd0c09478 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
154 154
155 155 amend closing the branch should be detected as meta change
156 156 ----------------------------------------------------------
157 157
158 158 $ hg branch closedbranch
159 159 marked working directory as branch closedbranch
160 160 $ mkcommit G0
161 161 $ mkcommit I0
162 162 $ hg commit --amend --close-branch
163 163
164 164 check result
165 165
166 166 $ hg debugobsolete -r .
167 167 2f599e54c1c6974299065cdf54e1ad640bfb7b5d 12c6238b5e371eea00fd2013b12edce3f070928b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
General Comments 0
You need to be logged in to leave comments. Login now