Patchwork [4,of,8] template: add minimal obsfate template function

login
register
mail settings
Submitter Boris Feld
Date Aug. 7, 2017, 2:56 p.m.
Message ID <a96edc5bcdc8790719e0.1502117783@FB>
Download mbox | patch
Permalink /patch/22728/
State Changes Requested
Headers show

Comments

Boris Feld - Aug. 7, 2017, 2:56 p.m.
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501850856 -7200
#      Fri Aug 04 14:47:36 2017 +0200
# Node ID a96edc5bcdc8790719e003eefff91a4f656cc559
# Parent  e3f0339b83553039dcd87b62dc1dfbdf98548792
# EXP-Topic obsfatetemplate
template: add minimal obsfate template function

The goal of this series is to have templates capable of displaying the
evolution of each changeset in a clean and human-readable way.

Add the succsandmarkers template return successors and markers so it can be
used isolated like:

> {succsandmarkers % "{get(succsandmarkers, "markers")|json};"}

Also add a template function obsfate that takes succsandmarkers as input and
compute various fields from the list of markers and successors. Additional
fields will be added in following patches.
Yuya Nishihara - Aug. 8, 2017, 3:19 p.m.
On Mon, 07 Aug 2017 16:56:23 +0200, Boris Feld wrote:
> # HG changeset patch
> # User Boris Feld <boris.feld@octobus.net>
> # Date 1501850856 -7200
> #      Fri Aug 04 14:47:36 2017 +0200
> # Node ID a96edc5bcdc8790719e003eefff91a4f656cc559
> # Parent  e3f0339b83553039dcd87b62dc1dfbdf98548792
> # EXP-Topic obsfatetemplate
> template: add minimal obsfate template function

(Only reviewed the template stuff, since I haven't got so involved in obsolete
naming, the operation metadata, etc.)

> +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)
> +
> +    values = []
> +    for sset in ssets:
> +        values.append({'successors': sset, 'markers': sset.markers})
> +
> +    return values
> diff -r e3f0339b8355 -r a96edc5bcdc8 mercurial/templatekw.py
> --- a/mercurial/templatekw.py	Mon Jul 03 03:27:58 2017 +0200
> +++ b/mercurial/templatekw.py	Fri Aug 04 14:47:36 2017 +0200
> @@ -655,6 +655,21 @@
>      return _hybrid(gen(data), data, lambda x: {'successorset': x},
>                     lambda d: d["successorset"])
>  
> +@templatekeyword("succsandmarkers")
> +def showsuccsandmarkers(repo, ctx, **args):
> +    """Returns a list of dict for each final successor of ctx.
> +
> +    The dict contains successors node id in "successors" keys and the list of
> +    obs-markers from ctx to the set of successors in "markers"
> +    """
> +
> +    values = obsutil.successorsandmarkers(repo, ctx)
> +
> +    if values is None:
> +        values = []
> +
> +    return showlist('succsandmarkers', values, args)

I think returning a list of successor nodes is more natural.

Can we theoretically gather the relevant markers from successor nodes?
If the "markers" field serves just as a cache, it could be stored in
revcache[] and passed to obsfate() under the hood.

> +@templatefunc('obsfate(succsandmarkers)')
> +def obsfate(context, mapping, args):
> +    """ Compute obsfate related information based on successors and markers
> +    """
> +    data = args[0][0](context, mapping, args[0][1])
> +    data = obsutil.computeobsfate(data['successors'], data['markers'])

It has to check the number and types of the arguments. And please use
evalfuncarg().

> +    _hybrid = templatekw._hybrid
> +
> +    # Format the successors for displaying
> +    succs = _hybrid(None, data['successors'],
> +                    lambda x: {'ctx': mapping['repo'][x], 'revcache': {}},
> +                    lambda d: templatekw._formatrevnode(d['ctx']))
> +    data['successors'] = succs
> +
> +    return _hybrid(None, [data], lambda x: x, ', ')

[...]

> +  |/     Obsfate: [{"markers": [["\udca4h\u071b63\udc8b\u0014\udcfd\udcb7\udc82_U\udcce=\udcf4\udce7\u0015\u0017\udcad"

Ugh, binary in JSON. This is one reason why I think raw "markers" shouldn't
be exposed.
Boris Feld - Aug. 8, 2017, 4:48 p.m.
On Wed, 2017-08-09 at 00:19 +0900, Yuya Nishihara wrote:
> On Mon, 07 Aug 2017 16:56:23 +0200, Boris Feld wrote:
> > # HG changeset patch
> > # User Boris Feld <boris.feld@octobus.net>
> > # Date 1501850856 -7200
> > #      Fri Aug 04 14:47:36 2017 +0200
> > # Node ID a96edc5bcdc8790719e003eefff91a4f656cc559
> > # Parent  e3f0339b83553039dcd87b62dc1dfbdf98548792
> > # EXP-Topic obsfatetemplate
> > template: add minimal obsfate template function
> 
> (Only reviewed the template stuff, since I haven't got so involved in
> obsolete
> naming, the operation metadata, etc.)
> 
> > +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)
> > +
> > +    values = []
> > +    for sset in ssets:
> > +        values.append({'successors': sset, 'markers':
> > sset.markers})
> > +
> > +    return values
> > diff -r e3f0339b8355 -r a96edc5bcdc8 mercurial/templatekw.py
> > --- a/mercurial/templatekw.py	Mon Jul 03 03:27:58 2017 +0200
> > +++ b/mercurial/templatekw.py	Fri Aug 04 14:47:36 2017 +0200
> > @@ -655,6 +655,21 @@
> >      return _hybrid(gen(data), data, lambda x: {'successorset': x},
> >                     lambda d: d["successorset"])
> >  
> > +@templatekeyword("succsandmarkers")
> > +def showsuccsandmarkers(repo, ctx, **args):
> > +    """Returns a list of dict for each final successor of ctx.
> > +
> > +    The dict contains successors node id in "successors" keys and
> > the list of
> > +    obs-markers from ctx to the set of successors in "markers"
> > +    """
> > +
> > +    values = obsutil.successorsandmarkers(repo, ctx)
> > +
> > +    if values is None:
> > +        values = []
> > +
> > +    return showlist('succsandmarkers', values, args)
> 
> I think returning a list of successor nodes is more natural.
> 
> Can we theoretically gather the relevant markers from successor
> nodes?
> If the "markers" field serves just as a cache, it could be stored in
> revcache[] and passed to obsfate() under the hood.

The current algorithm for computing the successors sets of a changeset
is this one:

Walk the obs-marker graph starting at a given revision. Follow
successors of each revisions walked until revisions has no more
successors, they are stable and they are the tip-most successor of the
initial revision.

Having the sets of successors doesn't helps because a successor could
be successor for more than one obsolete revision (fold for example).

The list of markers between a revisions and its successors could be
cached, I'm not sure how to use revcache for that. Did you meant
returning a hybrid object like this one, with a non-null revcache
parameter: https://www.mercurial-scm.org/repo/hg/file/tip/mercurial/tem
platekw.py#l641?

> 
> > +@templatefunc('obsfate(succsandmarkers)')
> > +def obsfate(context, mapping, args):
> > +    """ Compute obsfate related information based on successors
> > and markers
> > +    """
> > +    data = args[0][0](context, mapping, args[0][1])
> > +    data = obsutil.computeobsfate(data['successors'],
> > data['markers'])
> 
> It has to check the number and types of the arguments. And please use
> evalfuncarg().

Thank you, I was looking for a cleaner way to do it. I will send a V2
after I fixed the return value of showsuccsandmarkers. 

I will send a separate series to clean join, I used it as example.

> 
> > +    _hybrid = templatekw._hybrid
> > +
> > +    # Format the successors for displaying
> > +    succs = _hybrid(None, data['successors'],
> > +                    lambda x: {'ctx': mapping['repo'][x],
> > 'revcache': {}},
> > +                    lambda d: templatekw._formatrevnode(d['ctx']))
> > +    data['successors'] = succs
> > +
> > +    return _hybrid(None, [data], lambda x: x, ', ')
> 
> [...]
> 
> > +  |/     Obsfate: [{"markers":
> > [["\udca4h\u071b63\udc8b\u0014\udcfd\udcb7\udc82_U\udcce=\udcf4\udc
> > e7\u0015\u0017\udcad"
> 
> Ugh, binary in JSON. This is one reason why I think raw "markers"
> shouldn't
> be exposed.
Yuya Nishihara - Aug. 9, 2017, 1:42 p.m.
On Tue, 08 Aug 2017 18:48:08 +0200, Boris Feld wrote:
> On Wed, 2017-08-09 at 00:19 +0900, Yuya Nishihara wrote:
> > On Mon, 07 Aug 2017 16:56:23 +0200, Boris Feld wrote:
> > > +@templatekeyword("succsandmarkers")
> > > +def showsuccsandmarkers(repo, ctx, **args):
> > > +    """Returns a list of dict for each final successor of ctx.
> > > +
> > > +    The dict contains successors node id in "successors" keys and
> > > the list of
> > > +    obs-markers from ctx to the set of successors in "markers"
> > > +    """
> > > +
> > > +    values = obsutil.successorsandmarkers(repo, ctx)
> > > +
> > > +    if values is None:
> > > +        values = []
> > > +
> > > +    return showlist('succsandmarkers', values, args)
> > 
> > I think returning a list of successor nodes is more natural.
> > 
> > Can we theoretically gather the relevant markers from successor
> > nodes?
> > If the "markers" field serves just as a cache, it could be stored in
> > revcache[] and passed to obsfate() under the hood.
> 
> The current algorithm for computing the successors sets of a changeset
> is this one:
> 
> Walk the obs-marker graph starting at a given revision. Follow
> successors of each revisions walked until revisions has no more
> successors, they are stable and they are the tip-most successor of the
> initial revision.
> 
> Having the sets of successors doesn't helps because a successor could
> be successor for more than one obsolete revision (fold for example).

Thanks for clarifying that. So markers are tied to each successorset?

  successorssets(rev) -> [(succs0, markers0), (succs1, markers1), ...]

In which case, {successorsset} could populate markers by makemap(),

  successorssets % "{successorset} {obsmarkers}"
                                   ^^^^^^^^^^^^
                               no idea how this should be rendered, but
                               let's revisit it later.

and perhaps obsfate could take the pair explicitly or implicitly.

  successorsets % "{obsfate(successorset, obsmarkers)}"  # a function
  successorsets % "{obsfate}"  # a keyword available only in successorsets

obsfate could be even split to verb/users/mindate/maxdate.

  successorsets % "{obsverb} {obsusers} ..."
                   ^^^^^^^^^^^^^^^^^^^^
                 these keyword functions may be populated by makemap(), too.

> The list of markers between a revisions and its successors could be
> cached, I'm not sure how to use revcache for that. Did you meant
> returning a hybrid object like this one, with a non-null revcache
> parameter: https://www.mercurial-scm.org/repo/hg/file/tip/mercurial/tem
> platekw.py#l641?

Something like showlatesttag() or showfile*() was in mind, but "revcache"
seems not the right tool as the markers aren't bound to the current rev.
Boris Feld - Aug. 17, 2017, 2:43 p.m.
On Wed, 2017-08-09 at 22:42 +0900, Yuya Nishihara wrote:
> On Tue, 08 Aug 2017 18:48:08 +0200, Boris Feld wrote:
> > On Wed, 2017-08-09 at 00:19 +0900, Yuya Nishihara wrote:
> > > On Mon, 07 Aug 2017 16:56:23 +0200, Boris Feld wrote:
> > > > +@templatekeyword("succsandmarkers")
> > > > +def showsuccsandmarkers(repo, ctx, **args):
> > > > +    """Returns a list of dict for each final successor of ctx.
> > > > +
> > > > +    The dict contains successors node id in "successors" keys
> > > > and
> > > > the list of
> > > > +    obs-markers from ctx to the set of successors in "markers"
> > > > +    """
> > > > +
> > > > +    values = obsutil.successorsandmarkers(repo, ctx)
> > > > +
> > > > +    if values is None:
> > > > +        values = []
> > > > +
> > > > +    return showlist('succsandmarkers', values, args)
> > > 
> > > I think returning a list of successor nodes is more natural.
> > > 
> > > Can we theoretically gather the relevant markers from successor
> > > nodes?
> > > If the "markers" field serves just as a cache, it could be stored
> > > in
> > > revcache[] and passed to obsfate() under the hood.
> > 
> > The current algorithm for computing the successors sets of a
> > changeset
> > is this one:
> > 
> > Walk the obs-marker graph starting at a given revision. Follow
> > successors of each revisions walked until revisions has no more
> > successors, they are stable and they are the tip-most successor of
> > the
> > initial revision.
> > 
> > Having the sets of successors doesn't helps because a successor
> > could
> > be successor for more than one obsolete revision (fold for
> > example).
> 
> Thanks for clarifying that. So markers are tied to each successorset?
> 
>   successorssets(rev) -> [(succs0, markers0), (succs1, markers1),
> ...]

Yes markers are tied to each successorset.

> 
> In which case, {successorsset} could populate markers by makemap(),
> 
>   successorssets % "{successorset} {obsmarkers}"
>                                    ^^^^^^^^^^^^
>                                no idea how this should be rendered,
> but
>                                let's revisit it later.

I tried modifying successorssets to smuggle the markers via makemap but
it has one limitation.

In the case of pruned commits, we would have an empty successorsset but
at least one marker (the marker for pruning). Adding markers via
makemap would make the following condition: if(succsandmarkers, ...)
false for pruned markers.

I propose to keep succsandmarkers but hex all node ids so we would
never see binary information.

> 
> and perhaps obsfate could take the pair explicitly or implicitly.
> 
>   successorsets % "{obsfate(successorset, obsmarkers)}"  # a function
>   successorsets % "{obsfate}"  # a keyword available only in
> successorsets
> 
> obsfate could be even split to verb/users/mindate/maxdate.
> 
>   successorsets % "{obsverb} {obsusers} ..."
>                    ^^^^^^^^^^^^^^^^^^^^
>                  these keyword functions may be populated by
> makemap(), too.

I tried splitting obsfate into several functions and it looks nicer. I
prefer functions instead of keywords because keywords feels too magic,
do we have already some keywords that are similar (not taking context
as inputs) in core?

I have a rough series splitting obsfate(succsandmarkers) into
obsfateverb(successorset, markers), obsfateusers(successorset,
markers), ... Should I finish it and send a V2?
> 
> > The list of markers between a revisions and its successors could be
> > cached, I'm not sure how to use revcache for that. Did you meant
> > returning a hybrid object like this one, with a non-null revcache
> > parameter: https://www.mercurial-scm.org/repo/hg/file/tip/mercurial
> > /tem
> > platekw.py#l641?
> 
> Something like showlatesttag() or showfile*() was in mind, but
> "revcache"
> seems not the right tool as the markers aren't bound to the current
> rev.
Yuya Nishihara - Aug. 18, 2017, 1:44 p.m.
On Thu, 17 Aug 2017 16:43:11 +0200, Boris Feld wrote:
> On Wed, 2017-08-09 at 22:42 +0900, Yuya Nishihara wrote:
> > In which case, {successorsset} could populate markers by makemap(),
> > 
> >   successorssets % "{successorset} {obsmarkers}"
> >                                    ^^^^^^^^^^^^
> >                                no idea how this should be rendered,
> > but
> >                                let's revisit it later.
> 
> I tried modifying successorssets to smuggle the markers via makemap but
> it has one limitation.
> 
> In the case of pruned commits, we would have an empty successorsset but
> at least one marker (the marker for pruning). Adding markers via
> makemap would make the following condition: if(succsandmarkers, ...)
> false for pruned markers.
> 
> I propose to keep succsandmarkers but hex all node ids so we would
> never see binary information.

Good point. I have no better idea right now then, though I think
{succsandmarkers} exposes the implementation detail which a typical user
shouldn't have to understand.

> > and perhaps obsfate could take the pair explicitly or implicitly.
> > 
> >   successorsets % "{obsfate(successorset, obsmarkers)}"  # a function
> >   successorsets % "{obsfate}"  # a keyword available only in
> > successorsets
> > 
> > obsfate could be even split to verb/users/mindate/maxdate.
> > 
> >   successorsets % "{obsverb} {obsusers} ..."
> >                    ^^^^^^^^^^^^^^^^^^^^
> >                  these keyword functions may be populated by
> > makemap(), too.
> 
> I tried splitting obsfate into several functions and it looks nicer. I
> prefer functions instead of keywords because keywords feels too magic,

Perhaps that depends on how useful the function interface will be. Can we
pass another pair of successorset and obsmarkers which isn't generated by
{succsandmarkers} ?

> do we have already some keywords that are similar (not taking context
> as inputs) in core?

There are a few examples of not depending on the current context (e.g.
termwidth), but they aren't similar to the obsfate stuff.

> I have a rough series splitting obsfate(succsandmarkers) into
> obsfateverb(successorset, markers), obsfateusers(successorset,
> markers), ... Should I finish it and send a V2?

Well, since we don't seem to have any better idea, and these features are
experimental, it's probably a good move to start with that.

Patch

diff -r e3f0339b8355 -r a96edc5bcdc8 mercurial/obsutil.py
--- a/mercurial/obsutil.py	Mon Jul 03 03:27:58 2017 +0200
+++ b/mercurial/obsutil.py	Fri Aug 04 14:47:36 2017 +0200
@@ -8,6 +8,7 @@ 
 from __future__ import absolute_import
 
 from . import (
+    node as nodemod,
     phases,
 )
 
@@ -551,3 +552,45 @@ 
                 final.reverse() # put small successors set first
                 cache[current] = final
     return cache[initialnode]
+
+def computeobsfate(successorset, rawmarkers):
+    """ For a successor set, get all related markers and convert every nodeid
+    into its hexadecimal form.
+    """
+    hex = nodemod.hex
+
+    successorset = [hex(n) for n in successorset]
+
+    # hex the binary nodes in the markers
+    markers = []
+    for m in rawmarkers:
+        hexprec = hex(m[0])
+        hexsucs = tuple(hex(n) for n in m[1])
+        hexparents = None
+        if m[5] is not None:
+            hexparents = tuple(hex(n) for n in m[5])
+        newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
+        markers.append(newmarker)
+
+    # Format basic data
+    data = {
+        "successors": sorted(successorset),
+        "markers": sorted(markers)
+    }
+
+    return data
+
+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)
+
+    values = []
+    for sset in ssets:
+        values.append({'successors': sset, 'markers': sset.markers})
+
+    return values
diff -r e3f0339b8355 -r a96edc5bcdc8 mercurial/templatekw.py
--- a/mercurial/templatekw.py	Mon Jul 03 03:27:58 2017 +0200
+++ b/mercurial/templatekw.py	Fri Aug 04 14:47:36 2017 +0200
@@ -655,6 +655,21 @@ 
     return _hybrid(gen(data), data, lambda x: {'successorset': x},
                    lambda d: d["successorset"])
 
+@templatekeyword("succsandmarkers")
+def showsuccsandmarkers(repo, ctx, **args):
+    """Returns a list of dict for each final successor of ctx.
+
+    The dict contains successors node id in "successors" keys and the list of
+    obs-markers from ctx to the set of successors in "markers"
+    """
+
+    values = obsutil.successorsandmarkers(repo, ctx)
+
+    if values is None:
+        values = []
+
+    return showlist('succsandmarkers', values, args)
+
 @templatekeyword('p1rev')
 def showp1rev(repo, ctx, templ, **args):
     """Integer. The repository-local revision number of the changeset's
diff -r e3f0339b8355 -r a96edc5bcdc8 mercurial/templater.py
--- a/mercurial/templater.py	Mon Jul 03 03:27:58 2017 +0200
+++ b/mercurial/templater.py	Fri Aug 04 14:47:36 2017 +0200
@@ -18,6 +18,7 @@ 
     encoding,
     error,
     minirst,
+    obsutil,
     parser,
     pycompat,
     registrar,
@@ -849,6 +850,23 @@ 
     func = lambda a, b: a % b
     return runarithmetic(context, mapping, (func, args[0], args[1]))
 
+@templatefunc('obsfate(succsandmarkers)')
+def obsfate(context, mapping, args):
+    """ Compute obsfate related information based on successors and markers
+    """
+    data = args[0][0](context, mapping, args[0][1])
+    data = obsutil.computeobsfate(data['successors'], data['markers'])
+
+    _hybrid = templatekw._hybrid
+
+    # Format the successors for displaying
+    succs = _hybrid(None, data['successors'],
+                    lambda x: {'ctx': mapping['repo'][x], 'revcache': {}},
+                    lambda d: templatekw._formatrevnode(d['ctx']))
+    data['successors'] = succs
+
+    return _hybrid(None, [data], lambda x: x, ', ')
+
 @templatefunc('relpath(path)')
 def relpath(context, mapping, args):
     """Convert a repository-absolute path into a filesystem path relative to
diff -r e3f0339b8355 -r a96edc5bcdc8 tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t	Mon Jul 03 03:27:58 2017 +0200
+++ b/tests/test-obsmarker-template.t	Fri Aug 04 14:47:36 2017 +0200
@@ -20,6 +20,8 @@ 
   >     {if(successorssets, "\n  Successors: {successorssets}")}\
   >     {if(successorssets, "\n  multi-line: {join(successorssets, "\n  multi-line: ")}")}\
   >     {if(successorssets, "\n  json: {successorssets|json}")}\n'
+  > fatelog = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers % "{obsfate(succsandmarkers) % "rewritten as {join(successors, ", ")}; "}"} \n" )}'
+  > fatelogjson = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers|json}\n")}'
   > EOF
 
 Test templates on amended commit
@@ -33,8 +35,8 @@ 
   $ mkcommit ROOT
   $ mkcommit A0
   $ echo 42 >> A0
-  $ hg commit --amend -m "A1"
-  $ hg commit --amend -m "A2"
+  $ HGUSER=test1 hg commit --amend -m "A1" --config devel.default-date="1234567890 0"
+  $ HGUSER=test2 hg commit --amend -m "A2" --config devel.default-date="987654321 0"
 
   $ hg log --hidden -G
   @  changeset:   4:d004c8f274b9
@@ -83,6 +85,27 @@ 
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
+  $ hg fatelog -q
+  o  d004c8f274b9
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  o  ea207398892e
+  
+  $ hg fatelog
+  o  d004c8f274b9
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  o  ea207398892e
+  
+  $ hg fatelog -v
+  o  d004c8f274b9
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  o  ea207398892e
+  
   $ hg up 'desc(A1)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -99,6 +122,13 @@ 
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
+  $ hg fatelog -v
+  o  d004c8f274b9
+  |
+  | @  a468dc9b3633
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  o  ea207398892e
+  
 Predecessors template should show all the predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
@@ -123,6 +153,17 @@ 
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
+  $ hg fatelog --hidden -q
+  o  d004c8f274b9
+  |
+  | @  a468dc9b3633
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  | x  f137d23bb3e1
+  | |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:a468dc9b3633;
+  o  ea207398892e
+  
 
 Predecessors template shouldn't show anything as all obsolete commit are not
 visible.
@@ -155,7 +196,35 @@ 
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
+  $ hg fatelog -v
+  @  d004c8f274b9
+  |
+  o  ea207398892e
+  
 
+  $ hg fatelog -v --hidden
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  | x  f137d23bb3e1
+  | |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:a468dc9b3633;
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: [{"markers": [["\udca4h\u071b63\udc8b\u0014\udcfd\udcb7\udc82_U\udcce=\udcf4\udce7\u0015\u0017\udcad", ["\udcd0\u0004\udcc8\udcf2t\udcb9\udcecH\nG\udca9\u003c\u0010\udcda\udcc5\udcee\udce6:\udcdbx"], 0, [["user", "test2"]], [987654321.0, 0], null]], "successors": ["\udcd0\u0004\udcc8\udcf2t\udcb9\udcecH\nG\udca9\u003c\u0010\udcda\udcc5\udcee\udce6:\udcdbx"]}]
+  | x  f137d23bb3e1
+  | |
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["G\u001f7\udc8e\udcabL^%\udcf6\udcc7\u007fx['\udcc96\udcef\udcb2(t", ["\udca4h\u071b63\udc8b\u0014\udcfd\udcb7\udc82_U\udcce=\udcf4\udce7\u0015\u0017\udcad"], 0, [["user", "test1"]], [1234567890.0, 0], null]], "successors": ["\udca4h\u071b63\udc8b\u0014\udcfd\udcb7\udc82_U\udcce=\udcf4\udce7\u0015\u0017\udcad"]}]
+  o  ea207398892e
+  
 Test templates with splitted commit
 ===================================
 
@@ -239,6 +308,16 @@ 
   |      json: [["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | @  471597cad322
+  |/     Obsfate: rewritten as 2:337fec4d2edc, 3:f257fde29c7a;
+  o  ea207398892e
+  
   $ hg up f257fde29c7a
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -270,6 +349,25 @@ 
   |      json: [["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]]
   o  ea207398892e
   
+
+  $ hg fatelog --hidden
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: rewritten as 2:337fec4d2edc, 3:f257fde29c7a;
+  o  ea207398892e
+  
+  $ hg fatelogjson --hidden
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: [{"markers": [["G\u0015\udc97\udcca\udcd3\"\udcd1\udcf6Y\udcbb\u0016\udc97Q\udcbe\udc913\udcda\udcd9.\udcf3", ["3\u007f\udcecM.\udcdc\udcf0\udce7\udca4g\udce3_\udc81\udc824\udcbcb\u0000h\udcb5", "\udcf2W\udcfd\udce2\udc9cz\udc84|\udc9b`\u007fn\udc95\udc86V\udcd0\udcdf\u000f\udcb1\\"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["3\u007f\udcecM.\udcdc\udcf0\udce7\udca4g\udce3_\udc81\udc824\udcbcb\u0000h\udcb5", "\udcf2W\udcfd\udce2\udc9cz\udc84|\udc9b`\u007fn\udc95\udc86V\udcd0\udcdf\u000f\udcb1\\"]}]
+  o  ea207398892e
+  
 Test templates with folded commit
 =================================
 
@@ -354,6 +452,14 @@ 
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192;
+  o  ea207398892e
+  
   $ hg up 'desc(B0)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -375,6 +481,16 @@ 
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192;
+  o  ea207398892e
+  
   $ hg up 'desc(C0)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -404,6 +520,26 @@ 
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192;
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: [{"markers": [["\r\udcec\u00017\udc9d;\udce61\udc8cG\u000e\udcad1\udcb1\udcfez\udce7\udccbS\udcd5", ["\udcebZ\r\udcaa!\udc92;\udcbf\udc8c\udcae\udcb2\udcc4 \udc85\udcb9\udce4c\udc86\u001f\udcd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\udcebZ\r\udcaa!\udc92;\udcbf\udc8c\udcae\udcb2\udcc4 \udc85\udcb9\udce4c\udc86\u001f\udcd0"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["G\u001f7\udc8e\udcabL^%\udcf6\udcc7\u007fx['\udcc96\udcef\udcb2(t", ["\udcebZ\r\udcaa!\udc92;\udcbf\udc8c\udcae\udcb2\udcc4 \udc85\udcb9\udce4c\udc86\u001f\udcd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\udcebZ\r\udcaa!\udc92;\udcbf\udc8c\udcae\udcb2\udcc4 \udc85\udcb9\udce4c\udc86\u001f\udcd0"]}]
+  o  ea207398892e
+  
+
 Test templates with divergence
 ==============================
 
@@ -521,6 +657,15 @@ 
   |      json: [["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]]
   o  ea207398892e
   
+  $ hg fatelog
+  o  019fadeab383
+  |
+  | o  fdf9bde5129a
+  |/
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 2:fdf9bde5129a; rewritten as 4:019fadeab383;
+  o  ea207398892e
+  
   $ hg up 'desc(A1)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -533,6 +678,14 @@ 
   |/
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  019fadeab383
+  |
+  | @  fdf9bde5129a
+  |/
+  o  ea207398892e
+  
 Predecessors template should the predecessors as we force their display with
 --hidden
   $ hg tlog --hidden
@@ -562,6 +715,30 @@ 
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten as 4:019fadeab383;
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:fdf9bde5129a; rewritten as 3:65b757b745b9;
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: [{"markers": [["e\udcb7W\udcb7E\udcb95\t\u003c\udc87\udca2\udcbc\u0347u!\udccc\udccf\udcfc\udcbd", ["\u0001\udc9f\udcad\uacc3\udcf6i\udc9f\udca8:\u05fd\udcb4\udcd8.\udcd2\udcc0\udce5\udcab"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\u0001\udc9f\udcad\uacc3\udcf6i\udc9f\udca8:\u05fd\udcb4\udcd8.\udcd2\udcc0\udce5\udcab"]}]
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["G\u001f7\udc8e\udcabL^%\udcf6\udcc7\u007fx['\udcc96\udcef\udcb2(t", ["\udcfd\udcf9\udcbd\udce5\u0012\udc9a(\udcd4T\udc8f\udcad\udcd3\udcf6+&\\\udcdd;z."], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\udcfd\udcf9\udcbd\udce5\u0012\udc9a(\udcd4T\udc8f\udcad\udcd3\udcf6+&\\\udcdd;z."]}, {"markers": [["G\u001f7\udc8e\udcabL^%\udcf6\udcc7\u007fx['\udcc96\udcef\udcb2(t", ["e\udcb7W\udcb7E\udcb95\t\u003c\udc87\udca2\udcbc\u0347u!\udccc\udccf\udcfc\udcbd"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["e\udcb7W\udcb7E\udcb95\t\u003c\udc87\udca2\udcbc\u0347u!\udccc\udccf\udcfc\udcbd"]}]
+  o  ea207398892e
+  
+
 Test templates with amended + folded commit
 ===========================================
 
@@ -659,6 +836,14 @@ 
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  o  ea207398892e
+  
   $ hg up 'desc(B0)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -679,6 +864,16 @@ 
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten as 4:eb5a0daa2192;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  o  ea207398892e
+  
   $ hg up 'desc(B1)' --hidden
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -699,6 +894,16 @@ 
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  o  ea207398892e
+  
   $ hg up 'desc(C0)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -708,6 +913,12 @@ 
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  @  eb5a0daa2192
+  |
+  o  ea207398892e
+  
 Predecessors template should show all predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
@@ -735,6 +946,30 @@ 
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192;
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten as 3:b7ea6d14e664;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: [{"markers": [["\udcb7\udceam\u0014\udce6d\udcbd\u0212\"!\udcf7\udc99&1\udcb5\r\udca3\udcfb\u0007", ["\udcebZ\r\udcaa!\udc92;\udcbf\udc8c\udcae\udcb2\udcc4 \udc85\udcb9\udce4c\udc86\u001f\udcd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\udcebZ\r\udcaa!\udc92;\udcbf\udc8c\udcae\udcb2\udcc4 \udc85\udcb9\udce4c\udc86\u001f\udcd0"]}]
+  | | x  0dec01379d3b
+  | |/     Obsfate: [{"markers": [["\r\udcec\u00017\udc9d;\udce61\udc8cG\u000e\udcad1\udcb1\udcfez\udce7\udccbS\udcd5", ["\udcb7\udceam\u0014\udce6d\udcbd\u0212\"!\udcf7\udc99&1\udcb5\r\udca3\udcfb\u0007"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\udcb7\udceam\u0014\udce6d\udcbd\u0212\"!\udcf7\udc99&1\udcb5\r\udca3\udcfb\u0007"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["G\u001f7\udc8e\udcabL^%\udcf6\udcc7\u007fx['\udcc96\udcef\udcb2(t", ["\udcebZ\r\udcaa!\udc92;\udcbf\udc8c\udcae\udcb2\udcc4 \udc85\udcb9\udce4c\udc86\u001f\udcd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\udcebZ\r\udcaa!\udc92;\udcbf\udc8c\udcae\udcb2\udcc4 \udc85\udcb9\udce4c\udc86\u001f\udcd0"]}]
+  o  ea207398892e
+  
+
 Test template with pushed and pulled obs markers
 ================================================
 
@@ -838,6 +1073,14 @@ 
   |      json: [["7a230b46bf61e50b30308c6cfd7bd1269ef54702"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  7a230b46bf61
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 2:7a230b46bf61;
+  o  ea207398892e
+  
   $ hg up 'desc(A2)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -847,6 +1090,12 @@ 
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  @  7a230b46bf61
+  |
+  o  ea207398892e
+  
 Predecessors template should show all predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
@@ -862,6 +1111,14 @@ 
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:7a230b46bf61;
+  o  ea207398892e
+  
+
 Test template with obsmarkers cycle
 ===================================
 
@@ -895,6 +1152,12 @@ 
   o  ea207398892e
   
 
+  $ hg fatelog
+  @  f897c6137566
+  |
+  o  ea207398892e
+  
+
   $ hg up -r "desc(B0)" --hidden
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -923,6 +1186,16 @@ 
   o  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566; rewritten as 1:471f378eab4c;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b;
+  o  ea207398892e
+  
+
   $ hg up -r "desc(A0)" --hidden
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -936,6 +1209,14 @@ 
   o  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  | @  471f378eab4c
+  |/
+  o  ea207398892e
+  
+
   $ hg up -r "desc(ROOT)" --hidden
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -944,6 +1225,12 @@ 
   @  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  @  ea207398892e
+  
+
   $ hg tlog --hidden
   o  f897c6137566
   |    Predecessors: 2:0dec01379d3b
@@ -1153,6 +1440,21 @@ 
   |
   o  ea207398892e
   
+  $ hg fatelog
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: rewritten as 8:b18bc8331526; rewritten as 9:0b997eb7ceee;
+  o  dd800401bd8c
+  |
+  o  f897c6137566
+  |
+  o  ea207398892e
+  
   $ hg tlog --hidden
   @  0b997eb7ceee
   |    Predecessors: 6:4a004186e638
@@ -1211,6 +1513,48 @@ 
   |      json: [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]]
   o  ea207398892e
   
+  $ hg fatelog --hidden
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: rewritten as 8:b18bc8331526; rewritten as 9:0b997eb7ceee;
+  o  dd800401bd8c
+  |
+  | x  9bd10a0775e4
+  |/     Obsfate: rewritten as 6:4a004186e638, 7:ba2ed02b0c9a, 5:dd800401bd8c;
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566; rewritten as 1:471f378eab4c;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b;
+  o  ea207398892e
+  
+  $ hg fatelogjson --hidden
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: [{"markers": [["J\u0000A\udc86\udce68\udc89\udcf2\f\udcb1d4\udcfc\udcbdr\"\u000b\udcd1\udcea\udcce", ["\udcb1\udc8b\udcc83\u0015&\udca2,\udcbb\u0018\u0001\u0002+\udcd1U[\udcf2\udc91\u010b"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\udcb1\udc8b\udcc83\u0015&\udca2,\udcbb\u0018\u0001\u0002+\udcd1U[\udcf2\udc91\u010b"]}, {"markers": [["J\u0000A\udc86\udce68\udc89\udcf2\f\udcb1d4\udcfc\udcbdr\"\u000b\udcd1\udcea\udcce", ["\u000b\udc99~\udcb7\udcce\udcee\udce0b\u0000\udca0/\udc8a\udcab\u0018Yy\t-QN"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\u000b\udc99~\udcb7\udcce\udcee\udce0b\u0000\udca0/\udc8a\udcab\u0018Yy\t-QN"]}]
+  o  dd800401bd8c
+  |
+  | x  9bd10a0775e4
+  |/     Obsfate: [{"markers": [["\udc9b\udcd1\n\u0007u\udce4xp\udc8c\udcad\udca5\udcf1v\udcecm\udce6T5\udc9c\udce7", ["\u0740\u0004\u0001\udcbd\udc8cy\udcd8\u00152\udc92ws\udc9eC\u003e\udc88?xN", "J\u0000A\udc86\udce68\udc89\udcf2\f\udcb1d4\udcfc\udcbdr\"\u000b\udcd1\udcea\udcce", "\udcba.\udcd0+\f\udc9aV\udcb9\udcfd\udcbcNy\udcc7\udce5xf\udc98M\udc8a\u001f"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\u0740\u0004\u0001\udcbd\udc8cy\udcd8\u00152\udc92ws\udc9eC\u003e\udc88?xN", "J\u0000A\udc86\udce68\udc89\udcf2\f\udcb1d4\udcfc\udcbdr\"\u000b\udcd1\udcea\udcce", "\udcba.\udcd0+\f\udc9aV\udcb9\udcfd\udcbcNy\udcc7\udce5xf\udc98M\udc8a\u001f"]}]
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: [{"markers": [["\r\udcec\u00017\udc9d;\udce61\udc8cG\u000e\udcad1\udcb1\udcfez\udce7\udccbS\udcd5", ["\udcf8\udc97\udcc6\u0013uf2\u000b\b\u0015\u0014\udcb4\udcc7\"~\udccc=8K9"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\udcf8\udc97\udcc6\u0013uf2\u000b\b\u0015\u0014\udcb4\udcc7\"~\udccc=8K9"]}, {"markers": [["\r\udcec\u00017\udc9d;\udce61\udc8cG\u000e\udcad1\udcb1\udcfez\udce7\udccbS\udcd5", ["G\u001f7\udc8e\udcabL^%\udcf6\udcc7\u007fx['\udcc96\udcef\udcb2(t"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["G\u001f7\udc8e\udcabL^%\udcf6\udcc7\u007fx['\udcc96\udcef\udcb2(t"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["G\u001f7\udc8e\udcabL^%\udcf6\udcc7\u007fx['\udcc96\udcef\udcb2(t", ["\r\udcec\u00017\udc9d;\udce61\udc8cG\u000e\udcad1\udcb1\udcfez\udce7\udccbS\udcd5"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["\r\udcec\u00017\udc9d;\udce61\udc8cG\u000e\udcad1\udcb1\udcfez\udce7\udccbS\udcd5"]}]
+  o  ea207398892e
+  
   $ hg up --hidden 4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg rebase -r 7 -d 8 --config extensions.rebase=
@@ -1245,6 +1589,22 @@ 
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: rewritten as 9:0b997eb7ceee, 5:dd800401bd8c, 10:eceed8f98ffc; rewritten as 8:b18bc8331526, 5:dd800401bd8c, 10:eceed8f98ffc;
+  o  f897c6137566
+  |
+  o  ea207398892e
+  
 Test templates with pruned commits
 ==================================
 
@@ -1268,3 +1628,13 @@ 
   |
   o  ea207398892e
   
+  $ hg fatelog
+  @  471f378eab4c
+  |
+  o  ea207398892e
+  
+  $ hg fatelog -v
+  @  471f378eab4c
+  |
+  o  ea207398892e
+