This file is indexed.

/usr/lib/python2.7/dist-packages/mercurial/obsutil.py is in mercurial-common 4.5.3-1ubuntu2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
# obsutil.py - utility functions for obsolescence
#
# Copyright 2017 Boris Feld <boris.feld@octobus.net>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

from __future__ import absolute_import

import re

from .i18n import _
from . import (
    node as nodemod,
    phases,
    util,
)

class marker(object):
    """Wrap obsolete marker raw data"""

    def __init__(self, repo, data):
        # the repo argument will be used to create changectx in later version
        self._repo = repo
        self._data = data
        self._decodedmeta = None

    def __hash__(self):
        return hash(self._data)

    def __eq__(self, other):
        if type(other) != type(self):
            return False
        return self._data == other._data

    def precnode(self):
        msg = ("'marker.precnode' is deprecated, "
               "use 'marker.prednode'")
        util.nouideprecwarn(msg, '4.4')
        return self.prednode()

    def prednode(self):
        """Predecessor changeset node identifier"""
        return self._data[0]

    def succnodes(self):
        """List of successor changesets node identifiers"""
        return self._data[1]

    def parentnodes(self):
        """Parents of the predecessors (None if not recorded)"""
        return self._data[5]

    def metadata(self):
        """Decoded metadata dictionary"""
        return dict(self._data[3])

    def date(self):
        """Creation date as (unixtime, offset)"""
        return self._data[4]

    def flags(self):
        """The flags field of the marker"""
        return self._data[2]

def getmarkers(repo, nodes=None, exclusive=False):
    """returns markers known in a repository

    If <nodes> is specified, only markers "relevant" to those nodes are are
    returned"""
    if nodes is None:
        rawmarkers = repo.obsstore
    elif exclusive:
        rawmarkers = exclusivemarkers(repo, nodes)
    else:
        rawmarkers = repo.obsstore.relevantmarkers(nodes)

    for markerdata in rawmarkers:
        yield marker(repo, markerdata)

def closestpredecessors(repo, nodeid):
    """yield the list of next predecessors pointing on visible changectx nodes

    This function respect the repoview filtering, filtered revision will be
    considered missing.
    """

    precursors = repo.obsstore.predecessors
    stack = [nodeid]
    seen = set(stack)

    while stack:
        current = stack.pop()
        currentpreccs = precursors.get(current, ())

        for prec in currentpreccs:
            precnodeid = prec[0]

            # Basic cycle protection
            if precnodeid in seen:
                continue
            seen.add(precnodeid)

            if precnodeid in repo:
                yield precnodeid
            else:
                stack.append(precnodeid)

def allprecursors(*args, **kwargs):
    """ (DEPRECATED)
    """
    msg = ("'obsutil.allprecursors' is deprecated, "
           "use 'obsutil.allpredecessors'")
    util.nouideprecwarn(msg, '4.4')

    return allpredecessors(*args, **kwargs)

def allpredecessors(obsstore, nodes, ignoreflags=0):
    """Yield node for every precursors of <nodes>.

    Some precursors may be unknown locally.

    This is a linear yield unsuited to detecting folded changesets. It includes
    initial nodes too."""

    remaining = set(nodes)
    seen = set(remaining)
    while remaining:
        current = remaining.pop()
        yield current
        for mark in obsstore.predecessors.get(current, ()):
            # ignore marker flagged with specified flag
            if mark[2] & ignoreflags:
                continue
            suc = mark[0]
            if suc not in seen:
                seen.add(suc)
                remaining.add(suc)

def allsuccessors(obsstore, nodes, ignoreflags=0):
    """Yield node for every successor of <nodes>.

    Some successors may be unknown locally.

    This is a linear yield unsuited to detecting split changesets. It includes
    initial nodes too."""
    remaining = set(nodes)
    seen = set(remaining)
    while remaining:
        current = remaining.pop()
        yield current
        for mark in obsstore.successors.get(current, ()):
            # ignore marker flagged with specified flag
            if mark[2] & ignoreflags:
                continue
            for suc in mark[1]:
                if suc not in seen:
                    seen.add(suc)
                    remaining.add(suc)

def _filterprunes(markers):
    """return a set with no prune markers"""
    return set(m for m in markers if m[1])

def exclusivemarkers(repo, nodes):
    """set of markers relevant to "nodes" but no other locally-known nodes

    This function compute the set of markers "exclusive" to a locally-known
    node. This means we walk the markers starting from <nodes> until we reach a
    locally-known precursors outside of <nodes>. Element of <nodes> with
    locally-known successors outside of <nodes> are ignored (since their
    precursors markers are also relevant to these successors).

    For example:

        # (A0 rewritten as A1)
        #
        # A0 <-1- A1 # Marker "1" is exclusive to A1

        or

        # (A0 rewritten as AX; AX rewritten as A1; AX is unkown locally)
        #
        # <-1- A0 <-2- AX <-3- A1 # Marker "2,3" are exclusive to A1

        or

        # (A0 has unknown precursors, A0 rewritten as A1 and A2 (divergence))
        #
        #          <-2- A1 # Marker "2" is exclusive to A0,A1
        #        /
        # <-1- A0
        #        \
        #         <-3- A2 # Marker "3" is exclusive to A0,A2
        #
        # in addition:
        #
        #  Markers "2,3" are exclusive to A1,A2
        #  Markers "1,2,3" are exclusive to A0,A1,A2

        See test/test-obsolete-bundle-strip.t for more examples.

    An example usage is strip. When stripping a changeset, we also want to
    strip the markers exclusive to this changeset. Otherwise we would have
    "dangling"" obsolescence markers from its precursors: Obsolescence markers
    marking a node as obsolete without any successors available locally.

    As for relevant markers, the prune markers for children will be followed.
    Of course, they will only be followed if the pruned children is
    locally-known. Since the prune markers are relevant to the pruned node.
    However, while prune markers are considered relevant to the parent of the
    pruned changesets, prune markers for locally-known changeset (with no
    successors) are considered exclusive to the pruned nodes. This allows
    to strip the prune markers (with the rest of the exclusive chain) alongside
    the pruned changesets.
    """
    # running on a filtered repository would be dangerous as markers could be
    # reported as exclusive when they are relevant for other filtered nodes.
    unfi = repo.unfiltered()

    # shortcut to various useful item
    nm = unfi.changelog.nodemap
    precursorsmarkers = unfi.obsstore.predecessors
    successormarkers = unfi.obsstore.successors
    childrenmarkers = unfi.obsstore.children

    # exclusive markers (return of the function)
    exclmarkers = set()
    # we need fast membership testing
    nodes = set(nodes)
    # looking for head in the obshistory
    #
    # XXX we are ignoring all issues in regard with cycle for now.
    stack = [n for n in nodes if not _filterprunes(successormarkers.get(n, ()))]
    stack.sort()
    # nodes already stacked
    seennodes = set(stack)
    while stack:
        current = stack.pop()
        # fetch precursors markers
        markers = list(precursorsmarkers.get(current, ()))
        # extend the list with prune markers
        for mark in successormarkers.get(current, ()):
            if not mark[1]:
                markers.append(mark)
        # and markers from children (looking for prune)
        for mark in childrenmarkers.get(current, ()):
            if not mark[1]:
                markers.append(mark)
        # traverse the markers
        for mark in markers:
            if mark in exclmarkers:
                # markers already selected
                continue

            # If the markers is about the current node, select it
            #
            # (this delay the addition of markers from children)
            if mark[1] or mark[0] == current:
                exclmarkers.add(mark)

            # should we keep traversing through the precursors?
            prec = mark[0]

            # nodes in the stack or already processed
            if prec in seennodes:
                continue

            # is this a locally known node ?
            known = prec in nm
            # if locally-known and not in the <nodes> set the traversal
            # stop here.
            if known and prec not in nodes:
                continue

            # do not keep going if there are unselected markers pointing to this
            # nodes. If we end up traversing these unselected markers later the
            # node will be taken care of at that point.
            precmarkers = _filterprunes(successormarkers.get(prec))
            if precmarkers.issubset(exclmarkers):
                seennodes.add(prec)
                stack.append(prec)

    return exclmarkers

def foreground(repo, nodes):
    """return all nodes in the "foreground" of other node

    The foreground of a revision is anything reachable using parent -> children
    or precursor -> successor relation. It is very similar to "descendant" but
    augmented with obsolescence information.

    Beware that possible obsolescence cycle may result if complex situation.
    """
    repo = repo.unfiltered()
    foreground = set(repo.set('%ln::', nodes))
    if repo.obsstore:
        # We only need this complicated logic if there is obsolescence
        # XXX will probably deserve an optimised revset.
        nm = repo.changelog.nodemap
        plen = -1
        # compute the whole set of successors or descendants
        while len(foreground) != plen:
            plen = len(foreground)
            succs = set(c.node() for c in foreground)
            mutable = [c.node() for c in foreground if c.mutable()]
            succs.update(allsuccessors(repo.obsstore, mutable))
            known = (n for n in succs if n in nm)
            foreground = set(repo.set('%ln::', known))
    return set(c.node() for c in foreground)

# effectflag field
#
# Effect-flag is a 1-byte bit field used to store what changed between a
# changeset and its successor(s).
#
# The effect flag is stored in obs-markers metadata while we iterate on the
# information design. That's why we have the EFFECTFLAGFIELD. If we come up
# with an incompatible design for effect flag, we can store a new design under
# another field name so we don't break readers. We plan to extend the existing
# obsmarkers bit-field when the effect flag design will be stabilized.
#
# The effect-flag is placed behind an experimental flag
# `effect-flags` set to off by default.
#

EFFECTFLAGFIELD = "ef1"

DESCCHANGED = 1 << 0 # action changed the description
METACHANGED = 1 << 1 # action change the meta
DIFFCHANGED = 1 << 3 # action change diff introduced by the changeset
PARENTCHANGED = 1 << 2 # action change the parent
USERCHANGED = 1 << 4 # the user changed
DATECHANGED = 1 << 5 # the date changed
BRANCHCHANGED = 1 << 6 # the branch changed

METABLACKLIST = [
    re.compile('^branch$'),
    re.compile('^.*-source$'),
    re.compile('^.*_source$'),
    re.compile('^source$'),
]

def metanotblacklisted(metaitem):
    """ Check that the key of a meta item (extrakey, extravalue) does not
    match at least one of the blacklist pattern
    """
    metakey = metaitem[0]

    return not any(pattern.match(metakey) for pattern in METABLACKLIST)

def _prepare_hunk(hunk):
    """Drop all information but the username and patch"""
    cleanhunk = []
    for line in hunk.splitlines():
        if line.startswith(b'# User') or not line.startswith(b'#'):
            if line.startswith(b'@@'):
                line = b'@@\n'
            cleanhunk.append(line)
    return cleanhunk

def _getdifflines(iterdiff):
    """return a cleaned up lines"""
    lines = next(iterdiff, None)

    if lines is None:
        return lines

    return _prepare_hunk(lines)

def _cmpdiff(leftctx, rightctx):
    """return True if both ctx introduce the "same diff"

    This is a first and basic implementation, with many shortcoming.
    """

    # Leftctx or right ctx might be filtered, so we need to use the contexts
    # with an unfiltered repository to safely compute the diff
    leftunfi = leftctx._repo.unfiltered()[leftctx.rev()]
    leftdiff = leftunfi.diff(git=1)
    rightunfi = rightctx._repo.unfiltered()[rightctx.rev()]
    rightdiff = rightunfi.diff(git=1)

    left, right = (0, 0)
    while None not in (left, right):
        left = _getdifflines(leftdiff)
        right = _getdifflines(rightdiff)

        if left != right:
            return False
    return True

def geteffectflag(relation):
    """ From an obs-marker relation, compute what changed between the
    predecessor and the successor.
    """
    effects = 0

    source = relation[0]

    for changectx in relation[1]:
        # Check if description has changed
        if changectx.description() != source.description():
            effects |= DESCCHANGED

        # Check if user has changed
        if changectx.user() != source.user():
            effects |= USERCHANGED

        # Check if date has changed
        if changectx.date() != source.date():
            effects |= DATECHANGED

        # Check if branch has changed
        if changectx.branch() != source.branch():
            effects |= BRANCHCHANGED

        # Check if at least one of the parent has changed
        if changectx.parents() != source.parents():
            effects |= PARENTCHANGED

        # Check if other meta has changed
        changeextra = changectx.extra().items()
        ctxmeta = filter(metanotblacklisted, changeextra)

        sourceextra = source.extra().items()
        srcmeta = filter(metanotblacklisted, sourceextra)

        if ctxmeta != srcmeta:
            effects |= METACHANGED

        # Check if the diff has changed
        if not _cmpdiff(source, changectx):
            effects |= DIFFCHANGED

    return effects

def getobsoleted(repo, tr):
    """return the set of pre-existing revisions obsoleted by a transaction"""
    torev = repo.unfiltered().changelog.nodemap.get
    phase = repo._phasecache.phase
    succsmarkers = repo.obsstore.successors.get
    public = phases.public
    addedmarkers = tr.changes.get('obsmarkers')
    addedrevs = tr.changes.get('revs')
    seenrevs = set()
    obsoleted = set()
    for mark in addedmarkers:
        node = mark[0]
        rev = torev(node)
        if rev is None or rev in seenrevs or rev in addedrevs:
            continue
        seenrevs.add(rev)
        if phase(repo, rev) == public:
            continue
        if set(succsmarkers(node) or []).issubset(addedmarkers):
            obsoleted.add(rev)
    return obsoleted

class _succs(list):
    """small class to represent a successors with some metadata about it"""

    def __init__(self, *args, **kwargs):
        super(_succs, self).__init__(*args, **kwargs)
        self.markers = set()

    def copy(self):
        new = _succs(self)
        new.markers = self.markers.copy()
        return new

    @util.propertycache
    def _set(self):
        # immutable
        return set(self)

    def canmerge(self, other):
        return self._set.issubset(other._set)

def successorssets(repo, initialnode, closest=False, cache=None):
    """Return set of all latest successors of initial nodes

    The successors set of a changeset A are the group of revisions that succeed
    A. It succeeds A as a consistent whole, each revision being only a partial
    replacement. By default, the successors set contains non-obsolete
    changesets only, walking the obsolescence graph until reaching a leaf. If
    'closest' is set to True, closest successors-sets are return (the
    obsolescence walk stops on known changesets).

    This function returns the full list of successor sets which is why it
    returns a list of tuples and not just a single tuple. Each tuple is a valid
    successors set. Note that (A,) may be a valid successors set for changeset A
    (see below).

    In most cases, a changeset A will have a single element (e.g. the changeset
    A is replaced by A') in its successors set. Though, it is also common for a
    changeset A to have no elements in its successor set (e.g. the changeset
    has been pruned). Therefore, the returned list of successors sets will be
    [(A',)] or [], respectively.

    When a changeset A is split into A' and B', however, it will result in a
    successors set containing more than a single element, i.e. [(A',B')].
    Divergent changesets will result in multiple successors sets, i.e. [(A',),
    (A'')].

    If a changeset A is not obsolete, then it will conceptually have no
    successors set. To distinguish this from a pruned changeset, the successor
    set will contain itself only, i.e. [(A,)].

    Finally, final successors unknown locally are considered to be pruned
    (pruned: obsoleted without any successors). (Final: successors not affected
    by markers).

    The 'closest' mode respect the repoview filtering. For example, without
    filter it will stop at the first locally known changeset, with 'visible'
    filter it will stop on visible changesets).

    The optional `cache` parameter is a dictionary that may contains
    precomputed successors sets. It is meant to reuse the computation of a
    previous call to `successorssets` when multiple calls are made at the same
    time. The cache dictionary is updated in place. The caller is responsible
    for its life span. Code that makes multiple calls to `successorssets`
    *should* use this cache mechanism or risk a performance hit.

    Since results are different depending of the 'closest' most, the same cache
    cannot be reused for both mode.
    """

    succmarkers = repo.obsstore.successors

    # Stack of nodes we search successors sets for
    toproceed = [initialnode]
    # set version of above list for fast loop detection
    # element added to "toproceed" must be added here
    stackedset = set(toproceed)
    if cache is None:
        cache = {}

    # This while loop is the flattened version of a recursive search for
    # successors sets
    #
    # def successorssets(x):
    #    successors = directsuccessors(x)
    #    ss = [[]]
    #    for succ in directsuccessors(x):
    #        # product as in itertools cartesian product
    #        ss = product(ss, successorssets(succ))
    #    return ss
    #
    # But we can not use plain recursive calls here:
    # - that would blow the python call stack
    # - obsolescence markers may have cycles, we need to handle them.
    #
    # The `toproceed` list act as our call stack. Every node we search
    # successors set for are stacked there.
    #
    # The `stackedset` is set version of this stack used to check if a node is
    # already stacked. This check is used to detect cycles and prevent infinite
    # loop.
    #
    # successors set of all nodes are stored in the `cache` dictionary.
    #
    # After this while loop ends we use the cache to return the successors sets
    # for the node requested by the caller.
    while toproceed:
        # Every iteration tries to compute the successors sets of the topmost
        # node of the stack: CURRENT.
        #
        # There are four possible outcomes:
        #
        # 1) We already know the successors sets of CURRENT:
        #    -> mission accomplished, pop it from the stack.
        # 2) Stop the walk:
        #    default case: Node is not obsolete
        #    closest case: Node is known at this repo filter level
        #      -> the node is its own successors sets. Add it to the cache.
        # 3) We do not know successors set of direct successors of CURRENT:
        #    -> We add those successors to the stack.
        # 4) We know successors sets of all direct successors of CURRENT:
        #    -> We can compute CURRENT successors set and add it to the
        #       cache.
        #
        current = toproceed[-1]

        # case 2 condition is a bit hairy because of closest,
        # we compute it on its own
        case2condition =  ((current not in succmarkers)
                           or (closest and current != initialnode
                               and current in repo))

        if current in cache:
            # case (1): We already know the successors sets
            stackedset.remove(toproceed.pop())
        elif case2condition:
            # case (2): end of walk.
            if current in repo:
                # We have a valid successors.
                cache[current] = [_succs((current,))]
            else:
                # Final obsolete version is unknown locally.
                # Do not count that as a valid successors
                cache[current] = []
        else:
            # cases (3) and (4)
            #
            # We proceed in two phases. Phase 1 aims to distinguish case (3)
            # from case (4):
            #
            #     For each direct successors of CURRENT, we check whether its
            #     successors sets are known. If they are not, we stack the
            #     unknown node and proceed to the next iteration of the while
            #     loop. (case 3)
            #
            #     During this step, we may detect obsolescence cycles: a node
            #     with unknown successors sets but already in the call stack.
            #     In such a situation, we arbitrary set the successors sets of
            #     the node to nothing (node pruned) to break the cycle.
            #
            #     If no break was encountered we proceed to phase 2.
            #
            # Phase 2 computes successors sets of CURRENT (case 4); see details
            # in phase 2 itself.
            #
            # Note the two levels of iteration in each phase.
            # - The first one handles obsolescence markers using CURRENT as
            #   precursor (successors markers of CURRENT).
            #
            #   Having multiple entry here means divergence.
            #
            # - The second one handles successors defined in each marker.
            #
            #   Having none means pruned node, multiple successors means split,
            #   single successors are standard replacement.
            #
            for mark in sorted(succmarkers[current]):
                for suc in mark[1]:
                    if suc not in cache:
                        if suc in stackedset:
                            # cycle breaking
                            cache[suc] = []
                        else:
                            # case (3) If we have not computed successors sets
                            # of one of those successors we add it to the
                            # `toproceed` stack and stop all work for this
                            # iteration.
                            toproceed.append(suc)
                            stackedset.add(suc)
                            break
                else:
                    continue
                break
            else:
                # case (4): we know all successors sets of all direct
                # successors
                #
                # Successors set contributed by each marker depends on the
                # successors sets of all its "successors" node.
                #
                # Each different marker is a divergence in the obsolescence
                # history. It contributes successors sets distinct from other
                # markers.
                #
                # Within a marker, a successor may have divergent successors
                # sets. In such a case, the marker will contribute multiple
                # divergent successors sets. If multiple successors have
                # divergent successors sets, a Cartesian product is used.
                #
                # At the end we post-process successors sets to remove
                # duplicated entry and successors set that are strict subset of
                # another one.
                succssets = []
                for mark in sorted(succmarkers[current]):
                    # successors sets contributed by this marker
                    base = _succs()
                    base.markers.add(mark)
                    markss = [base]
                    for suc in mark[1]:
                        # cardinal product with previous successors
                        productresult = []
                        for prefix in markss:
                            for suffix in cache[suc]:
                                newss = prefix.copy()
                                newss.markers.update(suffix.markers)
                                for part in suffix:
                                    # do not duplicated entry in successors set
                                    # first entry wins.
                                    if part not in newss:
                                        newss.append(part)
                                productresult.append(newss)
                        markss = productresult
                    succssets.extend(markss)
                # remove duplicated and subset
                seen = []
                final = []
                candidates = sorted((s for s in succssets if s),
                                    key=len, reverse=True)
                for cand in candidates:
                    for seensuccs in seen:
                        if cand.canmerge(seensuccs):
                            seensuccs.markers.update(cand.markers)
                            break
                    else:
                        final.append(cand)
                        seen.append(cand)
                final.reverse() # put small successors set first
                cache[current] = final
    return cache[initialnode]

def successorsandmarkers(repo, ctx):
    """compute the raw data needed for computing obsfate
    Returns a list of dict, one dict per successors set
    """
    if not ctx.obsolete():
        return None

    ssets = successorssets(repo, ctx.node(), closest=True)

    # closestsuccessors returns an empty list for pruned revisions, remap it
    # into a list containing an empty list for future processing
    if ssets == []:
        ssets = [[]]

    # Try to recover pruned markers
    succsmap = repo.obsstore.successors
    fullsuccessorsets = [] # successor set + markers
    for sset in ssets:
        if sset:
            fullsuccessorsets.append(sset)
        else:
            # successorsset return an empty set() when ctx or one of its
            # successors is pruned.
            # In this case, walk the obs-markers tree again starting with ctx
            # and find the relevant pruning obs-makers, the ones without
            # successors.
            # Having these markers allow us to compute some information about
            # its fate, like who pruned this changeset and when.

            # XXX we do not catch all prune markers (eg rewritten then pruned)
            # (fix me later)
            foundany = False
            for mark in succsmap.get(ctx.node(), ()):
                if not mark[1]:
                    foundany = True
                    sset = _succs()
                    sset.markers.add(mark)
                    fullsuccessorsets.append(sset)
            if not foundany:
                fullsuccessorsets.append(_succs())

    values = []
    for sset in fullsuccessorsets:
        values.append({'successors': sset, 'markers': sset.markers})

    return values

def _getobsfate(successorssets):
    """ Compute a changeset obsolescence fate based on its successorssets.
    Successors can be the tipmost ones or the immediate ones. This function
    return values are not meant to be shown directly to users, it is meant to
    be used by internal functions only.
    Returns one fate from the following values:
    - pruned
    - diverged
    - superseded
    - superseded_split
    """

    if len(successorssets) == 0:
        # The commit has been pruned
        return 'pruned'
    elif len(successorssets) > 1:
        return 'diverged'
    else:
        # No divergence, only one set of successors
        successors = successorssets[0]

        if len(successors) == 1:
            return 'superseded'
        else:
            return 'superseded_split'

def obsfateverb(successorset, markers):
    """ Return the verb summarizing the successorset and potentially using
    information from the markers
    """
    if not successorset:
        verb = 'pruned'
    elif len(successorset) == 1:
        verb = 'rewritten'
    else:
        verb = 'split'
    return verb

def markersdates(markers):
    """returns the list of dates for a list of markers
    """
    return [m[4] for m in markers]

def markersusers(markers):
    """ Returns a sorted list of markers users without duplicates
    """
    markersmeta = [dict(m[3]) for m in markers]
    users = set(meta.get('user') for meta in markersmeta if meta.get('user'))

    return sorted(users)

def markersoperations(markers):
    """ Returns a sorted list of markers operations without duplicates
    """
    markersmeta = [dict(m[3]) for m in markers]
    operations = set(meta.get('operation') for meta in markersmeta
                     if meta.get('operation'))

    return sorted(operations)

def obsfateprinter(successors, markers, ui):
    """ Build a obsfate string for a single successorset using all obsfate
    related function defined in obsutil
    """
    quiet = ui.quiet
    verbose = ui.verbose
    normal = not verbose and not quiet

    line = []

    # Verb
    line.append(obsfateverb(successors, markers))

    # Operations
    operations = markersoperations(markers)
    if operations:
        line.append(" using %s" % ", ".join(operations))

    # Successors
    if successors:
        fmtsuccessors = [successors.joinfmt(succ) for succ in successors]
        line.append(" as %s" % ", ".join(fmtsuccessors))

    # Users
    users = markersusers(markers)
    # Filter out current user in not verbose mode to reduce amount of
    # information
    if not verbose:
        currentuser = ui.username(acceptempty=True)
        if len(users) == 1 and currentuser in users:
            users = None

    if (verbose or normal) and users:
        line.append(" by %s" % ", ".join(users))

    # Date
    dates = markersdates(markers)

    if dates and verbose:
        min_date = min(dates)
        max_date = max(dates)

        if min_date == max_date:
            fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
            line.append(" (at %s)" % fmtmin_date)
        else:
            fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
            fmtmax_date = util.datestr(max_date, '%Y-%m-%d %H:%M %1%2')
            line.append(" (between %s and %s)" % (fmtmin_date, fmtmax_date))

    return "".join(line)


filteredmsgtable = {
    "pruned": _("hidden revision '%s' is pruned"),
    "diverged": _("hidden revision '%s' has diverged"),
    "superseded": _("hidden revision '%s' was rewritten as: %s"),
    "superseded_split": _("hidden revision '%s' was split as: %s"),
    "superseded_split_several": _("hidden revision '%s' was split as: %s and "
                                  "%d more"),
}

def _getfilteredreason(repo, changeid, ctx):
    """return a human-friendly string on why a obsolete changeset is hidden
    """
    successors = successorssets(repo, ctx.node())
    fate = _getobsfate(successors)

    # Be more precise in case the revision is superseded
    if fate == 'pruned':
        return filteredmsgtable['pruned'] % changeid
    elif fate == 'diverged':
        return filteredmsgtable['diverged'] % changeid
    elif fate == 'superseded':
        single_successor = nodemod.short(successors[0][0])
        return filteredmsgtable['superseded'] % (changeid, single_successor)
    elif fate == 'superseded_split':

        succs = []
        for node_id in successors[0]:
            succs.append(nodemod.short(node_id))

        if len(succs) <= 2:
            fmtsuccs = ', '.join(succs)
            return filteredmsgtable['superseded_split'] % (changeid, fmtsuccs)
        else:
            firstsuccessors = ', '.join(succs[:2])
            remainingnumber = len(succs) - 2

            args = (changeid, firstsuccessors, remainingnumber)
            return filteredmsgtable['superseded_split_several'] % args