Patchwork [9,of,9] template: use template-engine for obsfate

login
register
mail settings
Submitter Boris Feld
Date July 6, 2017, 9:50 p.m.
Message ID <e18d8e61b7260e246a82.1499377817@FB>
Download mbox | patch
Permalink /patch/22052/
State Changes Requested, archived
Headers show

Comments

Boris Feld - July 6, 2017, 9:50 p.m.
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499085172 -7200
#      Mon Jul 03 14:32:52 2017 +0200
# Node ID e18d8e61b7260e246a82727c8cde01f936692cff
# Parent  098585d4fbc88dc54513e12fa306d0e52ea2b323
# EXP-Topic obsfatetemplate
template: use template-engine for obsfate

Try to replace the obsfateprinter part by "sub-templates".

The code is hacky, I've tried to use at maximum the template engine but the
raw data-structure doesn't seems well supported:

    [{'markers': [{}, {}, ...], 'successors': [A, ...], 'verb': '', ...}, ...]

I've put this changeset at the end so the beginning of the serie might be
accepted without it. Even without this changeset, we already have good tests
and the right structure for the computation of obsfate data.
Sean Farley - July 7, 2017, 1:05 a.m.
Boris Feld <boris.feld@octobus.net> writes:

> # HG changeset patch
> # User Boris Feld <boris.feld@octobus.net>
> # Date 1499085172 -7200
> #      Mon Jul 03 14:32:52 2017 +0200
> # Node ID e18d8e61b7260e246a82727c8cde01f936692cff
> # Parent  098585d4fbc88dc54513e12fa306d0e52ea2b323
> # EXP-Topic obsfatetemplate
> template: use template-engine for obsfate
>
> Try to replace the obsfateprinter part by "sub-templates".
>
> The code is hacky, I've tried to use at maximum the template engine but the
> raw data-structure doesn't seems well supported:
>
>     [{'markers': [{}, {}, ...], 'successors': [A, ...], 'verb': '', ...}, ...]
>
> I've put this changeset at the end so the beginning of the serie might be
> accepted without it. Even without this changeset, we already have good tests
> and the right structure for the computation of obsfate data.

I like the general sentiment of this series, so thanks for that! But by
the end of reading the series I found myself want to customize (e.g. not
include some of the info; colorize differently, etc.) the string.

I think an easy approach for now (unless I'm missing something) would be
to make {obsfate} a meta-like template that is just an alias for finer
grain templates: {obsfate_user} {obsfate_succesors} (or whatever name
you prefer).

I have the same critique for 'hg show' as well (*cough* Greg *cough*).
Boris Feld - July 7, 2017, 10:24 a.m.
On Thu, 2017-07-06 at 18:05 -0700, Sean Farley wrote:
> Boris Feld <boris.feld@octobus.net> writes:
> 
> > # HG changeset patch
> > # User Boris Feld <boris.feld@octobus.net>
> > # Date 1499085172 -7200
> > #      Mon Jul 03 14:32:52 2017 +0200
> > # Node ID e18d8e61b7260e246a82727c8cde01f936692cff
> > # Parent  098585d4fbc88dc54513e12fa306d0e52ea2b323
> > # EXP-Topic obsfatetemplate
> > template: use template-engine for obsfate
> > 
> > Try to replace the obsfateprinter part by "sub-templates".
> > 
> > The code is hacky, I've tried to use at maximum the template engine
> > but the
> > raw data-structure doesn't seems well supported:
> > 
> >     [{'markers': [{}, {}, ...], 'successors': [A, ...], 'verb': '',
> > ...}, ...]
> > 
> > I've put this changeset at the end so the beginning of the serie
> > might be
> > accepted without it. Even without this changeset, we already have
> > good tests
> > and the right structure for the computation of obsfate data.
> 
> I like the general sentiment of this series, so thanks for that! But
> by
> the end of reading the series I found myself want to customize (e.g.
> not
> include some of the info; colorize differently, etc.) the string.
> 
> I think an easy approach for now (unless I'm missing something) would
> be
> to make {obsfate} a meta-like template that is just an alias for
> finer
> grain templates: {obsfate_user} {obsfate_succesors} (or whatever name
> you prefer).

Interesting, I didn't think about your use-cases much.

The main twist in obsfate is that the value is a list. The list could
contains 1 element in case of a simple evolution or several elements in
case of a divergence. We could have a top-level template
{obsfate_users} being a list of list, but it would be hard to combine
with another top-level template {obsfate_verb} for example.

Also, I just found out that it's possible to access the data with the
template engine.

For example if you only want the verb and the users:
    hg log -G -T '{obsfate % "obsolete: {get(obsfate, "verb")} by
{get(obsfate, "users")}\n"}'

Which would returns "obsolete: rewritten by test" in simple cases.
Unfortunately it fails with multiple users "obsolete: rewritten by
test1test2"

I tried changing the template into:

    hg log -G -T '{obsfate % "obsolete: {get(obsfate, "verb")} by
{join(get(obsfate, "users"), ', ')}\n"}'

Unfortunately I only get a blank output, maybe a template engine
experts could help me identify what am I doing wrong.

Apart from this problem, do you think is it customizable enough for
your needs?

We can even access the markers with:

    $ hg log -T '{obsfate % "obsolete: {ge
t(obsfate, "markers")|json}\n"}'
    obsolete: [["471f378eab4c5e25f6c77f785b27c936efb22874",
["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["user", "test"]],
[0.0, 0], null]]
    obsolete: [["471f378eab4c5e25f6c77f785b27c936efb22874",
["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["user", "test"]],
[0.0, 0], null], ["65b757b745b935093c87a2bccd877521cccffcbd",
["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["user", "test"]],
[0.0, 0], null]]

However, I'm not sure what would be the use-cases to accessing the
markers as obsfate is designed to show a summary of the evolution of a
changeset. There could be multiple successorsets in case of divergence
and several markers for each successorsets in case the changeset has
been rewritten several times.

Cheers,

> 
> I have the same critique for 'hg show' as well (*cough* Greg
> *cough*).
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Yuya Nishihara - July 7, 2017, 12:09 p.m.
On Thu, 06 Jul 2017 23:50:17 +0200, Boris Feld wrote:
> # HG changeset patch
> # User Boris Feld <boris.feld@octobus.net>
> # Date 1499085172 -7200
> #      Mon Jul 03 14:32:52 2017 +0200
> # Node ID e18d8e61b7260e246a82727c8cde01f936692cff
> # Parent  098585d4fbc88dc54513e12fa306d0e52ea2b323
> # EXP-Topic obsfatetemplate
> template: use template-engine for obsfate
> 
> Try to replace the obsfateprinter part by "sub-templates".
> 
> The code is hacky, I've tried to use at maximum the template engine but the
> raw data-structure doesn't seems well supported:
> 
>     [{'markers': [{}, {}, ...], 'successors': [A, ...], 'verb': '', ...}, ...]
> 
> I've put this changeset at the end so the beginning of the serie might be
> accepted without it. Even without this changeset, we already have good tests
> and the right structure for the computation of obsfate data.
> 
> diff -r 098585d4fbc8 -r e18d8e61b726 mercurial/templatekw.py
> --- a/mercurial/templatekw.py	Mon Jul 03 17:38:56 2017 +0200
> +++ b/mercurial/templatekw.py	Mon Jul 03 14:32:52 2017 +0200
> @@ -699,6 +699,32 @@
>  
>      return "; ".join(lines)
>  
> +def obsfatedefaulttempl(ui):
> +    """ Returns a dict with the default templates for obs fate
> +    """
> +    # Prepare templates
> +    verbtempl = '{verb}'
> +    usertempl = '{if(users, " by {join(users, ", ")}")}'
> +    succtempl = '{if(successors, " as ")}{successors}' # Bypass if limitation
> +    datetempleq = ' (at {min_date|isodate})'
> +    datetemplnoteq = ' (between {min_date|isodate} and {max_date|isodate})'
> +
> +    datetempl = '{if(max_date, "{ifeq(min_date, max_date, "%s", "%s")}")}'
> +    datetempl = datetempl % (datetempleq, datetemplnoteq)
> +
> +    optionalusertempl = usertempl
> +    username = _getusername(ui)
> +    if username is not None:
> +        optionalusertempl = ('{ifeq(join(users, "\0"), "%s", "", "%s")}'
> +                             % (username, usertempl))
> +
> +    # Assemble them
> +    return {
> +        'obsfate_quiet': verbtempl + succtempl,
> +        'obsfate': verbtempl + optionalusertempl + succtempl,
> +        'obsfate_verbose': verbtempl + usertempl + succtempl + datetempl,
> +    }

This makes me feel you're doing things in wrong layer. In principle, template
keywords provide primitive data, which are pretty-printed by using user/stock
templates (e.g. templates/map-cmdline.default.)
Boris Feld - July 11, 2017, 9:16 a.m.
On Fri, 2017-07-07 at 21:09 +0900, Yuya Nishihara wrote:
> On Thu, 06 Jul 2017 23:50:17 +0200, Boris Feld wrote:
> > # HG changeset patch
> > # User Boris Feld <boris.feld@octobus.net>
> > # Date 1499085172 -7200
> > #      Mon Jul 03 14:32:52 2017 +0200
> > # Node ID e18d8e61b7260e246a82727c8cde01f936692cff
> > # Parent  098585d4fbc88dc54513e12fa306d0e52ea2b323
> > # EXP-Topic obsfatetemplate
> > template: use template-engine for obsfate
> > 
> > Try to replace the obsfateprinter part by "sub-templates".
> > 
> > The code is hacky, I've tried to use at maximum the template engine
> > but the
> > raw data-structure doesn't seems well supported:
> > 
> >     [{'markers': [{}, {}, ...], 'successors': [A, ...], 'verb': '',
> > ...}, ...]
> > 
> > I've put this changeset at the end so the beginning of the serie
> > might be
> > accepted without it. Even without this changeset, we already have
> > good tests
> > and the right structure for the computation of obsfate data.
> > 
> > diff -r 098585d4fbc8 -r e18d8e61b726 mercurial/templatekw.py
> > --- a/mercurial/templatekw.py	Mon Jul 03 17:38:56 2017 +0200
> > +++ b/mercurial/templatekw.py	Mon Jul 03 14:32:52 2017 +0200
> > @@ -699,6 +699,32 @@
> >  
> >      return "; ".join(lines)
> >  
> > +def obsfatedefaulttempl(ui):
> > +    """ Returns a dict with the default templates for obs fate
> > +    """
> > +    # Prepare templates
> > +    verbtempl = '{verb}'
> > +    usertempl = '{if(users, " by {join(users, ", ")}")}'
> > +    succtempl = '{if(successors, " as ")}{successors}' # Bypass if
> > limitation
> > +    datetempleq = ' (at {min_date|isodate})'
> > +    datetemplnoteq = ' (between {min_date|isodate} and
> > {max_date|isodate})'
> > +
> > +    datetempl = '{if(max_date, "{ifeq(min_date, max_date, "%s",
> > "%s")}")}'
> > +    datetempl = datetempl % (datetempleq, datetemplnoteq)
> > +
> > +    optionalusertempl = usertempl
> > +    username = _getusername(ui)
> > +    if username is not None:
> > +        optionalusertempl = ('{ifeq(join(users, "\0"), "%s", "",
> > "%s")}'
> > +                             % (username, usertempl))
> > +
> > +    # Assemble them
> > +    return {
> > +        'obsfate_quiet': verbtempl + succtempl,
> > +        'obsfate': verbtempl + optionalusertempl + succtempl,
> > +        'obsfate_verbose': verbtempl + usertempl + succtempl +
> > datetempl,
> > +    }
> 
> This makes me feel you're doing things in wrong layer. In principle,
> template
> keywords provide primitive data, which are pretty-printed by using
> user/stock
> templates (e.g. templates/map-cmdline.default.)

Definitely, I should have made it more explicit that this one patch is
in RFC state.

Obsfate has a difficult task: summarize the obsolescence history
(potentially spanning multiple obs-markers), aggregating the different
values and all of this in multiple dimensions when there's a
divergence.

This template seems quite complex, it felt complex during
implementation using templates. I tried finding an existing template
that was close to this complexity, successorssets was close but obsfate
adds one more layer of nesting, so I didn't find a good example to
mimic.

I'm pretty sure that implementing obsfate cleanly with the template
engine can be done, but after spending several days, I'm afraid I won't
be able to do it on my own. For example, I wasn't able to successfully
format the users list using templates, I tried doing this:
    
    hg log -G -T '{obsfate % "obsolete: {get(obsfate, "verb")} by
{join(get(obsfate, "users"), ', ')}\n"}'

Lately, I was thinking about sending a V2 that, instead of returning
the formatted string, would returns an _hybrid object:
    
-    return _obsfateprinter(values, repo, repo.ui)
+    gen = _obsfateprinter(values, repo, repo.ui)
+    return _hybrid(gen, values, None, None)

This way people would be able to start customizing it (with template
function "get") and we would be able to improve the implementation with
potential syntactic sugar addition in the template engine.

What do you think? Could you provide me with some direction to move
forward?

Cheers,
Yuya Nishihara - July 11, 2017, 3:06 p.m.
On Tue, 11 Jul 2017 11:16:03 +0200, Boris Feld wrote:
> Obsfate has a difficult task: summarize the obsolescence history
> (potentially spanning multiple obs-markers), aggregating the different
> values and all of this in multiple dimensions when there's a
> divergence.
> 
> This template seems quite complex, it felt complex during
> implementation using templates. I tried finding an existing template
> that was close to this complexity, successorssets was close but obsfate
> adds one more layer of nesting, so I didn't find a good example to
> mimic.
> 
> I'm pretty sure that implementing obsfate cleanly with the template
> engine can be done, but after spending several days, I'm afraid I won't
> be able to do it on my own. For example, I wasn't able to successfully
> format the users list using templates, I tried doing this:
>     
>     hg log -G -T '{obsfate % "obsolete: {get(obsfate, "verb")} by
> {join(get(obsfate, "users"), ', ')}\n"}'
> 
> Lately, I was thinking about sending a V2 that, instead of returning
> the formatted string, would returns an _hybrid object:
>     
> -    return _obsfateprinter(values, repo, repo.ui)
> +    gen = _obsfateprinter(values, repo, repo.ui)
> +    return _hybrid(gen, values, None, None)
> 
> This way people would be able to start customizing it (with template
> function "get") and we would be able to improve the implementation with
> potential syntactic sugar addition in the template engine.

Well, I don't have expertise in the obsolete thingy, though I'm (unfortunately)
a template expert.

Guessing from the PATCH 4, which has the following functions,

  obsfatedata: ctx => [succs, ...] => [(succs, markers), ...]

maybe we'll want a template function which converts 'succs' to 'markers' ?
Let's call it 'relatedmarkers' here.

  relatedmarkers: succs => [marker, ...]  (where marker is a _hybrid dict)

Then, a part of {obsfate} could probably be written as:

  {successorsets % "{relatedmarkers(successorset)
                     % "{get(marker, "verb")} ..."}"}

I think that's similar to what Jun suggested.
Jun Wu - July 11, 2017, 7:44 p.m.
Excerpts from Yuya Nishihara's message of 2017-07-12 00:06:13 +0900:
> Well, I don't have expertise in the obsolete thingy, though I'm (unfortunately)
> a template expert.
> 
> Guessing from the PATCH 4, which has the following functions,
> 
>   obsfatedata: ctx => [succs, ...] => [(succs, markers), ...]
> 
> maybe we'll want a template function which converts 'succs' to 'markers' ?
> Let's call it 'relatedmarkers' here.
> 
>   relatedmarkers: succs => [marker, ...]  (where marker is a _hybrid dict)
> 
> Then, a part of {obsfate} could probably be written as:
> 
>   {successorsets % "{relatedmarkers(successorset)
>                      % "{get(marker, "verb")} ..."}"}

Is "get" necessary? It seems "%" is changing the "binding" here, so I wonder
if it's possible to do something like:

  "{successorgroups %
    "{markers %
      "{user} did {operation} on {predecessors} and got {successors}"}"}"

> I think that's similar to what Jun suggested.
Yuya Nishihara - July 12, 2017, 1:33 p.m.
On Tue, 11 Jul 2017 12:44:52 -0700, Jun Wu wrote:
> Excerpts from Yuya Nishihara's message of 2017-07-12 00:06:13 +0900:
> > Well, I don't have expertise in the obsolete thingy, though I'm (unfortunately)
> > a template expert.
> > 
> > Guessing from the PATCH 4, which has the following functions,
> > 
> >   obsfatedata: ctx => [succs, ...] => [(succs, markers), ...]
> > 
> > maybe we'll want a template function which converts 'succs' to 'markers' ?
> > Let's call it 'relatedmarkers' here.
> > 
> >   relatedmarkers: succs => [marker, ...]  (where marker is a _hybrid dict)
> > 
> > Then, a part of {obsfate} could probably be written as:
> > 
> >   {successorsets % "{relatedmarkers(successorset)
> >                      % "{get(marker, "verb")} ..."}"}
> 
> Is "get" necessary?

Maybe no. Good catch.

> It seems "%" is changing the "binding" here, so I wonder
> if it's possible to do something like:
> 
>   "{successorgroups %
>     "{markers %
>       "{user} did {operation} on {predecessors} and got {successors}"}"}"

I'm not sure if we need markers(node) or markers([node, ...]). markers(node)
can be a template keyword bound to the current ctx,

  {successorgroups % "{groups % "{markers % ...}"}"}

but markers([node, ...]) can't because there's no concept of the current set
of nodes.

  {successorgroups % "{markers(groups) % ...}"}
Boris Feld - July 12, 2017, 1:44 p.m.
On Wed, 2017-07-12 at 00:06 +0900, Yuya Nishihara wrote:
> On Tue, 11 Jul 2017 11:16:03 +0200, Boris Feld wrote:
> > Obsfate has a difficult task: summarize the obsolescence history
> > (potentially spanning multiple obs-markers), aggregating the
> > different
> > values and all of this in multiple dimensions when there's a
> > divergence.
> > 
> > This template seems quite complex, it felt complex during
> > implementation using templates. I tried finding an existing
> > template
> > that was close to this complexity, successorssets was close but
> > obsfate
> > adds one more layer of nesting, so I didn't find a good example to
> > mimic.
> > 
> > I'm pretty sure that implementing obsfate cleanly with the template
> > engine can be done, but after spending several days, I'm afraid I
> > won't
> > be able to do it on my own. For example, I wasn't able to
> > successfully
> > format the users list using templates, I tried doing this:
> >     
> >     hg log -G -T '{obsfate % "obsolete: {get(obsfate, "verb")} by
> > {join(get(obsfate, "users"), ', ')}\n"}'
> > 
> > Lately, I was thinking about sending a V2 that, instead of
> > returning
> > the formatted string, would returns an _hybrid object:
> >     
> > -    return _obsfateprinter(values, repo, repo.ui)
> > +    gen = _obsfateprinter(values, repo, repo.ui)
> > +    return _hybrid(gen, values, None, None)
> > 
> > This way people would be able to start customizing it (with
> > template
> > function "get") and we would be able to improve the implementation
> > with
> > potential syntactic sugar addition in the template engine.
> 
> Well, I don't have expertise in the obsolete thingy, though I'm
> (unfortunately)
> a template expert.
> 
> Guessing from the PATCH 4, which has the following functions,
> 
>   obsfatedata: ctx => [succs, ...] => [(succs, markers), ...]
> 
> maybe we'll want a template function which converts 'succs' to
> 'markers' ?
> Let's call it 'relatedmarkers' here.
> 
>   relatedmarkers: succs => [marker, ...]  (where marker is a _hybrid
> dict)
> 
> Then, a part of {obsfate} could probably be written as:
> 
>   {successorsets % "{relatedmarkers(successorset)
>                      % "{get(marker, "verb")} ..."}"}

I hadn't thought about splitting the template into several templates
functions, it's a good idea!.

One small, but important detail: the verb, users list and dates are
computed from the markers list. Something like this might work, what do
you think?

    {successorsets % "{obsfateverb(successorset)} by
{obsfateusers(successorset)} as {join(get(successorset, 'successors'),
', ')}"}

Would it be possible to keep the current {obsfate} template? It is easy
to use for users who are OK with the default obsfate output format
(which could be updated of course).

Also I've almost successfully reproduce the obsfate output "by hand"
with:

$ hg log -r 34177 --hidden -v -T "{obsfate}"
rewritten by Boris Feld <boris.feld@octobus.net> as e18d8e61b726 (at
2017-07-06 23:47 +0200)

$ hg log -r 34177 --hidden -T '{obsfate % "{get(obsfate, "verb")} by
{join(get(obsfate, "users"), ", ")} as {get(obsfate, "successors")} (at
{get(obsfate, "min_date")|isodate})\n"}'
rewritten by Boris Feld <boris.feld@octobus.net> as e18d8e61b726 (at
2017-07-06 23:47 +0200)
> 
> I think that's similar to what Jun suggested.
Yuya Nishihara - July 12, 2017, 4:08 p.m.
On Wed, 12 Jul 2017 15:44:13 +0200, Boris Feld wrote:
> On Wed, 2017-07-12 at 00:06 +0900, Yuya Nishihara wrote:
> > Guessing from the PATCH 4, which has the following functions,
> > 
> >   obsfatedata: ctx => [succs, ...] => [(succs, markers), ...]
> > 
> > maybe we'll want a template function which converts 'succs' to
> > 'markers' ?
> > Let's call it 'relatedmarkers' here.
> > 
> >   relatedmarkers: succs => [marker, ...]  (where marker is a _hybrid
> > dict)
> > 
> > Then, a part of {obsfate} could probably be written as:
> > 
> >   {successorsets % "{relatedmarkers(successorset)
> >                      % "{get(marker, "verb")} ..."}"}
> 
> I hadn't thought about splitting the template into several templates
> functions, it's a good idea!.
> 
> One small, but important detail: the verb, users list and dates are
> computed from the markers list. Something like this might work, what do
> you think?
> 
>     {successorsets % "{obsfateverb(successorset)} by
> {obsfateusers(successorset)} as {join(get(successorset, 'successors'),
> ', ')}"}

That will work. Maybe obsfatexxx() could be a single function which
summarizes markers.

  {successorsets % "{obsxxx(markers(successorset))} % "{verb} {user}...

or

  {successorsets % "{obsxxx(successorset)} % "{verb} {user}...

> Would it be possible to keep the current {obsfate} template? It is easy
> to use for users who are OK with the default obsfate output format
> (which could be updated of course).
> 
> Also I've almost successfully reproduce the obsfate output "by hand"
> with:
> 
> $ hg log -r 34177 --hidden -v -T "{obsfate}"
> rewritten by Boris Feld <boris.feld@octobus.net> as e18d8e61b726 (at
> 2017-07-06 23:47 +0200)
> 
> $ hg log -r 34177 --hidden -T '{obsfate % "{get(obsfate, "verb")} by
> {join(get(obsfate, "users"), ", ")} as {get(obsfate, "successors")} (at
> {get(obsfate, "min_date")|isodate})\n"}'
> rewritten by Boris Feld <boris.feld@octobus.net> as e18d8e61b726 (at
> 2017-07-06 23:47 +0200)

I don't think a template keyword should generate a long descriptive output.
You could instead add it to map-cmdline.default. I know it isn't always
useful, but that is another issue.
Jun Wu - July 12, 2017, 4:28 p.m.
As previously discussed in the "operation" thread [1]. I think we want to
use "operation" metadata instead of introducing flags, "obsfate" and/or
"verb" as new concepts. Practically "rebased as X" / "histedited as X" is
more friendly to end-user than just "rewritten as X".

With "operation" concept available, template could be just raw fields of an
obsmarker like user, data, successors, operation etc without needing another
layer of indirection (obsfate). I think that's simpler for users to
understand.

[1]: https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-May/097678.html

Excerpts from Boris Feld's message of 2017-07-12 15:44:13 +0200:
> On Wed, 2017-07-12 at 00:06 +0900, Yuya Nishihara wrote:
> > On Tue, 11 Jul 2017 11:16:03 +0200, Boris Feld wrote:
> > > Obsfate has a difficult task: summarize the obsolescence history
> > > (potentially spanning multiple obs-markers), aggregating the
> > > different
> > > values and all of this in multiple dimensions when there's a
> > > divergence.
> > > 
> > > This template seems quite complex, it felt complex during
> > > implementation using templates. I tried finding an existing
> > > template
> > > that was close to this complexity, successorssets was close but
> > > obsfate
> > > adds one more layer of nesting, so I didn't find a good example to
> > > mimic.
> > > 
> > > I'm pretty sure that implementing obsfate cleanly with the template
> > > engine can be done, but after spending several days, I'm afraid I
> > > won't
> > > be able to do it on my own. For example, I wasn't able to
> > > successfully
> > > format the users list using templates, I tried doing this:
> > >     
> > >     hg log -G -T '{obsfate % "obsolete: {get(obsfate, "verb")} by
> > > {join(get(obsfate, "users"), ', ')}\n"}'
> > > 
> > > Lately, I was thinking about sending a V2 that, instead of
> > > returning
> > > the formatted string, would returns an _hybrid object:
> > >     
> > > -    return _obsfateprinter(values, repo, repo.ui)
> > > +    gen = _obsfateprinter(values, repo, repo.ui)
> > > +    return _hybrid(gen, values, None, None)
> > > 
> > > This way people would be able to start customizing it (with
> > > template
> > > function "get") and we would be able to improve the implementation
> > > with
> > > potential syntactic sugar addition in the template engine.
> > 
> > Well, I don't have expertise in the obsolete thingy, though I'm
> > (unfortunately)
> > a template expert.
> > 
> > Guessing from the PATCH 4, which has the following functions,
> > 
> >   obsfatedata: ctx => [succs, ...] => [(succs, markers), ...]
> > 
> > maybe we'll want a template function which converts 'succs' to
> > 'markers' ?
> > Let's call it 'relatedmarkers' here.
> > 
> >   relatedmarkers: succs => [marker, ...]  (where marker is a _hybrid
> > dict)
> > 
> > Then, a part of {obsfate} could probably be written as:
> > 
> >   {successorsets % "{relatedmarkers(successorset)
> >                      % "{get(marker, "verb")} ..."}"}
> 
> I hadn't thought about splitting the template into several templates
> functions, it's a good idea!.
> 
> One small, but important detail: the verb, users list and dates are
> computed from the markers list. Something like this might work, what do
> you think?
> 
>     {successorsets % "{obsfateverb(successorset)} by
> {obsfateusers(successorset)} as {join(get(successorset, 'successors'),
> ', ')}"}
> 
> Would it be possible to keep the current {obsfate} template? It is easy
> to use for users who are OK with the default obsfate output format
> (which could be updated of course).
> 
> Also I've almost successfully reproduce the obsfate output "by hand"
> with:
> 
> $ hg log -r 34177 --hidden -v -T "{obsfate}"
> rewritten by Boris Feld <boris.feld@octobus.net> as e18d8e61b726 (at
> 2017-07-06 23:47 +0200)
> 
> $ hg log -r 34177 --hidden -T '{obsfate % "{get(obsfate, "verb")} by
> {join(get(obsfate, "users"), ", ")} as {get(obsfate, "successors")} (at
> {get(obsfate, "min_date")|isodate})\n"}'
> rewritten by Boris Feld <boris.feld@octobus.net> as e18d8e61b726 (at
> 2017-07-06 23:47 +0200)
> > 
> > I think that's similar to what Jun suggested.
Boris Feld - July 13, 2017, 10:32 a.m.
I think there was a misunderstanding about obsfate that I will try to
clarify, this email is a bit long, sorry.

TL;DR:
- hg log is a changeset-centric command
- obsfate template summarize the obs-history between a changeset and
its successors, history that can span several obs-markers
- hg obslog from evolve extensions is a obs-marker centric command that
could gain support for operation

Given this repository:

  @  changeset:   2:256782ab8caa
  |  tag:         tip
  |  parent:      0:ea207398892e
  |  user:        test
  |  date:        Thu Jan 01 00:00:00 1970 +0000
  |  summary:     B
  |
  | o  changeset:   1:2a34000d3544
  |/   user:        test
  |    date:        Thu Jan 01 00:00:00 1970 +0000
  |    summary:     A
  |
  o  changeset:   0:ea207398892e
     user:        test
     date:        Thu Jan 01 00:00:00 1970 +0000
     summary:     ROOT

If you rebase 1 on top of 2, amend it a first time, then amend it a
second time, you will get:


  @  changeset:   5:022e174ded42
  |  tag:         tip
  |  parent:      2:256782ab8caa
  |  user:        test2
  |  date:        Thu Jan 01 00:00:00 1970 +0000
  |  summary:     A"
  |
  o  changeset:   2:256782ab8caa
  |  parent:      0:ea207398892e
  |  user:        test
  |  date:        Thu Jan 01 00:00:00 1970 +0000
  |  summary:     B
  |
  o  changeset:   0:ea207398892e
     user:        test
     date:        Thu Jan 01 00:00:00 1970 +0000
     summary:     ROOT

Now for any reason if changeset 1 should be shown (if it's the current
repository parent for example), the output would be:


  o  changeset:   5:022e174ded42
  |  tag:         tip
  |  parent:      2:256782ab8caa
  |  user:        test2
  |  date:        Thu Jan 01 00:00:00 1970 +0000
  |  files:       A
  |  description:
  |  A"
  |
  |
  o  changeset:   2:256782ab8caa
  |  parent:      0:ea207398892e
  |  user:        test
  |  date:        Thu Jan 01 00:00:00 1970 +0000
  |  files:       B
  |  description:
  |  B
  |
  |
  | @  changeset:   1:2a34000d3544
  |/   user:        test
  |    date:        Thu Jan 01 00:00:00 1970 +0000
  |    obsolete:    rewritten by test as 022e174ded42 (at 1970-01-01
00:00 +0000)
  |    files:       A
  |    description:
  |    A
  |
  |
  o  changeset:   0:ea207398892e
     user:        test
     date:        Thu Jan 01 00:00:00 1970 +0000
     files:       ROOT
     description:
     ROOT

Obsfate was designed to summarize the obsolescence history for users.
As 'hg log' is a changeset centric command, obsfate tries to output
informations about visible changesets only. That's why obsfate for
2a34000d3544 is "rewritten as 022e174ded42". We have intermediary
changesets but they are not visible here so it would be disturbing to
say to the user "rewritten as 1737d8285b4d".

One side-effect of this summarization is that obsfate needs to
aggregate all the obs-markers from 2a34000d3544 to 022e174ded42, which
in this case is "1->3" (from the rebase), "3->4" (from the first amend)
and "4->5" (from the second amend). Here the range of obs-markers is
short but it could be much more big. That's why obsfate aggregate a
couple of fields to show a human-redable summary of the obs-history:

- Remove users duplicate.
- Compute the range of date, the smaller date of the obs-marker range
and the bigger one.
- Try to compute a verb based on the markers.

All theses computations are quite naive for the moment but the code has
been designed to be easily updated, wrapped by a command or extended.

This example is also quite simple but a real-life example:

  obsolete:    split by Boris Feld <boris.feld@octobus.net>,Matthieu
Laneuville <matthieu.laneuville@octobus.net> as 008f7cd1fcbe,
b6e50897b94e (between 2017-06-26 17:17 +0200 and 2017-07-02 15:08
+0200)

In this case, obsfate is summarizing 19 obs-markers.


Correct me if I'm wrong, but it looks like you're thinking about a more
obs-marker centric way of displaying history. We have a command like
that in the evolve extension: "obslog": while hg log shows changesets
and parents/descendants, hg obslog shows changesets and
successors/predecessors.

For example, this would be the output of "hg obslog -r 5":

  @  022e174ded42 (5) A"
  |
  x  15900262089a (4) A"
  |    rewritten(user) by test (Thu Jan 01 00:00:00 1970 +0000) as
022e174ded42
  |
  x  1737d8285b4d (3) A
  |    rewritten(description) by test (Thu Jan 01 00:00:00 1970 +0000)
as 15900262089a
  |
  x  2a34000d3544 (1) A
       rewritten(parent) by test (Thu Jan 01 00:00:00 1970 +0000) as
1737d8285b4d

With this command, one marker is displayed by line, not an aggregate so
it would make sense to add support for operation there. It would be a
nice improvement to use the operation metadata to display a more
accurate verb in this function.

On Wed, 2017-07-12 at 09:28 -0700, Jun Wu wrote:
> As previously discussed in the "operation" thread [1]. I think we
> want to
> use "operation" metadata instead of introducing flags, "obsfate"
> and/or
> "verb" as new concepts. Practically "rebased as X" / "histedited as
> X" is
> more friendly to end-user than just "rewritten as X".
> 
> With "operation" concept available, template could be just raw fields
> of an
> obsmarker like user, data, successors, operation etc without needing
> another
> layer of indirection (obsfate). I think that's simpler for users to
> understand.
> 
> [1]: https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-May
> /097678.html
> 
> Excerpts from Boris Feld's message of 2017-07-12 15:44:13 +0200:
> > On Wed, 2017-07-12 at 00:06 +0900, Yuya Nishihara wrote:
> > > On Tue, 11 Jul 2017 11:16:03 +0200, Boris Feld wrote:
> > > > Obsfate has a difficult task: summarize the obsolescence
> > > > history
> > > > (potentially spanning multiple obs-markers), aggregating the
> > > > different
> > > > values and all of this in multiple dimensions when there's a
> > > > divergence.
> > > > 
> > > > This template seems quite complex, it felt complex during
> > > > implementation using templates. I tried finding an existing
> > > > template
> > > > that was close to this complexity, successorssets was close but
> > > > obsfate
> > > > adds one more layer of nesting, so I didn't find a good example
> > > > to
> > > > mimic.
> > > > 
> > > > I'm pretty sure that implementing obsfate cleanly with the
> > > > template
> > > > engine can be done, but after spending several days, I'm afraid
> > > > I
> > > > won't
> > > > be able to do it on my own. For example, I wasn't able to
> > > > successfully
> > > > format the users list using templates, I tried doing this:
> > > >     
> > > >     hg log -G -T '{obsfate % "obsolete: {get(obsfate, "verb")}
> > > > by
> > > > {join(get(obsfate, "users"), ', ')}\n"}'
> > > > 
> > > > Lately, I was thinking about sending a V2 that, instead of
> > > > returning
> > > > the formatted string, would returns an _hybrid object:
> > > >     
> > > > -    return _obsfateprinter(values, repo, repo.ui)
> > > > +    gen = _obsfateprinter(values, repo, repo.ui)
> > > > +    return _hybrid(gen, values, None, None)
> > > > 
> > > > This way people would be able to start customizing it (with
> > > > template
> > > > function "get") and we would be able to improve the
> > > > implementation
> > > > with
> > > > potential syntactic sugar addition in the template engine.
> > > 
> > > Well, I don't have expertise in the obsolete thingy, though I'm
> > > (unfortunately)
> > > a template expert.
> > > 
> > > Guessing from the PATCH 4, which has the following functions,
> > > 
> > >   obsfatedata: ctx => [succs, ...] => [(succs, markers), ...]
> > > 
> > > maybe we'll want a template function which converts 'succs' to
> > > 'markers' ?
> > > Let's call it 'relatedmarkers' here.
> > > 
> > >   relatedmarkers: succs => [marker, ...]  (where marker is a
> > > _hybrid
> > > dict)
> > > 
> > > Then, a part of {obsfate} could probably be written as:
> > > 
> > >   {successorsets % "{relatedmarkers(successorset)
> > >                      % "{get(marker, "verb")} ..."}"}
> > 
> > I hadn't thought about splitting the template into several
> > templates
> > functions, it's a good idea!.
> > 
> > One small, but important detail: the verb, users list and dates are
> > computed from the markers list. Something like this might work,
> > what do
> > you think?
> > 
> >     {successorsets % "{obsfateverb(successorset)} by
> > {obsfateusers(successorset)} as {join(get(successorset,
> > 'successors'),
> > ', ')}"}
> > 
> > Would it be possible to keep the current {obsfate} template? It is
> > easy
> > to use for users who are OK with the default obsfate output format
> > (which could be updated of course).
> > 
> > Also I've almost successfully reproduce the obsfate output "by
> > hand"
> > with:
> > 
> > $ hg log -r 34177 --hidden -v -T "{obsfate}"
> > rewritten by Boris Feld <boris.feld@octobus.net> as e18d8e61b726
> > (at
> > 2017-07-06 23:47 +0200)
> > 
> > $ hg log -r 34177 --hidden -T '{obsfate % "{get(obsfate, "verb")}
> > by
> > {join(get(obsfate, "users"), ", ")} as {get(obsfate, "successors")}
> > (at
> > {get(obsfate, "min_date")|isodate})\n"}'
> > rewritten by Boris Feld <boris.feld@octobus.net> as e18d8e61b726
> > (at
> > 2017-07-06 23:47 +0200)
> > > 
> > > I think that's similar to what Jun suggested.
Jun Wu - July 13, 2017, 3:42 p.m.
Excerpts from Boris Feld's message of 2017-07-13 12:32:39 +0200:
> I think there was a misunderstanding about obsfate that I will try to
> clarify, this email is a bit long, sorry.
> 
> TL;DR:
> - hg log is a changeset-centric command
> - obsfate template summarize the obs-history between a changeset and
> its successors, history that can span several obs-markers

I agree a function is needed to work with multiple obsmarkers. But I don't
think the current obsfate implementation is a good design.

I think that function's input is (src, [dest=(not hidden())]), output is
multiple obsmarkers. The user could choose what to do with a list of ordered
obsmarkers. Like, if they want to display the entire chain of markers, they
should be able to do it. If they want to summarize things, we could provide
"dedup", "if_unique" helper function to allow them to do so.

> - hg obslog from evolve extensions is a obs-marker centric command that
> could gain support for operation

It still prints changesets, but uses a different DAG. It could be a --dag
flag of "hg log" command. We might also want to add other DAG types, like a
linear (ignore p2) graph [1].

[1]: https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-July/101148.html

> [...]
Jun Wu - July 13, 2017, 4:11 p.m.
Excerpts from Jun Wu's message of 2017-07-13 08:42:11 -0700:
> Excerpts from Boris Feld's message of 2017-07-13 12:32:39 +0200:
> > I think there was a misunderstanding about obsfate that I will try to
> > clarify, this email is a bit long, sorry.
> > 
> > TL;DR:
> > - hg log is a changeset-centric command
> > - obsfate template summarize the obs-history between a changeset and
> > its successors, history that can span several obs-markers
> 
> I agree a function is needed to work with multiple obsmarkers. But I don't
> think the current obsfate implementation is a good design.
> 
> I think that function's input is (src, [dest=(not hidden())]), output is
> multiple obsmarkers. The user could choose what to do with a list of ordered
> obsmarkers. Like, if they want to display the entire chain of markers, they
> should be able to do it. If they want to summarize things, we could provide
> "dedup", "if_unique" helper function to allow them to do so.

To minimize the work needed, I think we can:

  1. Add something to return a list of markers that can be used in templates
     directly. (Maybe a list of list if we want to want to deal with the
     divergence case well)
  2. Make obsfate accepts a list of markers (the output of the above
     function), and returns summarized results that can also be used in
     templates.

So "obsfate" is the "helper function" that summarize things, while the user
still have the flexibility of rendering markers in details.

> [...]

Patch

diff -r 098585d4fbc8 -r e18d8e61b726 mercurial/templatekw.py
--- a/mercurial/templatekw.py	Mon Jul 03 17:38:56 2017 +0200
+++ b/mercurial/templatekw.py	Mon Jul 03 14:32:52 2017 +0200
@@ -699,6 +699,32 @@ 
 
     return "; ".join(lines)
 
+def obsfatedefaulttempl(ui):
+    """ Returns a dict with the default templates for obs fate
+    """
+    # Prepare templates
+    verbtempl = '{verb}'
+    usertempl = '{if(users, " by {join(users, ", ")}")}'
+    succtempl = '{if(successors, " as ")}{successors}' # Bypass if limitation
+    datetempleq = ' (at {min_date|isodate})'
+    datetemplnoteq = ' (between {min_date|isodate} and {max_date|isodate})'
+
+    datetempl = '{if(max_date, "{ifeq(min_date, max_date, "%s", "%s")}")}'
+    datetempl = datetempl % (datetempleq, datetemplnoteq)
+
+    optionalusertempl = usertempl
+    username = _getusername(ui)
+    if username is not None:
+        optionalusertempl = ('{ifeq(join(users, "\0"), "%s", "", "%s")}'
+                             % (username, usertempl))
+
+    # Assemble them
+    return {
+        'obsfate_quiet': verbtempl + succtempl,
+        'obsfate': verbtempl + optionalusertempl + succtempl,
+        'obsfate_verbose': verbtempl + usertempl + succtempl + datetempl,
+    }
+
 @templatekeyword("obsfate")
 def showobsfate(repo, ctx, **args):
     """Returns a string describing how an obsolete changeset has evolved in a
@@ -717,7 +743,38 @@ 
     if values is None:
         return ''
 
-    return _obsfateprinter(values, repo, repo.ui)
+    # Format each successorset successors list
+    for raw in values:
+        # As we can't do something like
+        # "{join(map(nodeshort, successors), ', '}" in template, manually
+        # create a correct textual representation
+        gen = ', '.join(_formatrevnode(repo[n]) for n in raw['successors'])
+
+        makemap = lambda x: {'successor': x}
+        joinfmt = lambda d: "%s" % d['successor']
+        raw['successors'] = _hybrid(gen, raw['successors'], makemap,
+                                    joinfmt)
+
+    # And then format them
+    # Insert default obsfate templates
+    args['templ'].cache.update(obsfatedefaulttempl(repo.ui))
+
+    if repo.ui.quiet:
+        name = "obsfate_quiet"
+    elif repo.ui.verbose:
+        name = "obsfate_verbose"
+    elif repo.ui.debugflag:
+        name = "obsfate_debug"
+    else:
+        name = "obsfate"
+
+    # Format a single value using template
+    def fmt(d):
+        nargs = args.copy()
+        nargs.update(d[name])
+        return args['templ'](name, **nargs)
+
+    return _hybrid(None, values, lambda x: {name: x}, fmt)
 
 @templatekeyword('p1rev')
 def showp1rev(repo, ctx, templ, **args):
diff -r 098585d4fbc8 -r e18d8e61b726 tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t	Mon Jul 03 17:38:56 2017 +0200
+++ b/tests/test-obsmarker-template.t	Mon Jul 03 14:32:52 2017 +0200
@@ -20,7 +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(obsfate, "  Obsfate: {obsfate}\n")}'
+  > fatelog = log -G -T '{node|short}\n{if(obsfate, "  Obsfate: {join(obsfate, "; ")}\n")}'
+  > fatelogjson = log -G -T '{node|short} {obsfate|json}\n'
   > EOF
 
 Test templates on amended commit
@@ -212,6 +213,19 @@ 
   |/     Obsfate: rewritten by test1 as 3:a468dc9b3633 (at 2009-02-13 23:31 +0000)
   o  ea207398892e
   
+
+  $ hg fatelogjson --hidden
+  @  d004c8f274b9 ""
+  |
+  | x  a468dc9b3633 [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["user", "test2"]], [987654321.0, 0], null]], "max_date": [987654321.0, 0], "min_date": [987654321.0, 0], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], "users": ["test2"], "verb": "rewritten"}]
+  |/
+  | x  f137d23bb3e1 [{"markers": [["f137d23bb3e11dc1daeb6264fac9cb2433782e15", [], 0, [["user", "test1"]], [1234567890.0, 0], ["471f378eab4c5e25f6c77f785b27c936efb22874"]]], "max_date": [1234567890.0, 0], "min_date": [1234567890.0, 0], "successors": [], "users": ["test1"], "verb": "pruned"}]
+  | |
+  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["user", "test1"]], [1234567890.0, 0], null]], "max_date": [1234567890.0, 0], "min_date": [1234567890.0, 0], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], "users": ["test1"], "verb": "rewritten"}]
+  |/
+  o  ea207398892e ""
+  
+
 Test templates with splitted commit
 ===================================
 
@@ -345,6 +359,15 @@ 
   |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a
   o  ea207398892e
   
+  $ hg fatelogjson --hidden
+  @  f257fde29c7a ""
+  |
+  o  337fec4d2edc ""
+  |
+  | x  471597cad322 [{"markers": [["471597cad322d1f659bb169751be9133dad92ef3", ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"], "users": ["test"], "verb": "split"}]
+  |/
+  o  ea207398892e ""
+  
 Test templates with folded commit
 =================================
 
@@ -504,6 +527,17 @@ 
   |/     Obsfate: rewritten as 3:eb5a0daa2192
   o  ea207398892e
   
+
+  $ hg fatelogjson --hidden
+  @  eb5a0daa2192 ""
+  |
+  | x  0dec01379d3b [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}]
+  | |
+  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}]
+  |/
+  o  ea207398892e ""
+  
+
 Test templates with divergence
 ==============================
 
@@ -690,6 +724,19 @@ 
   |/     Obsfate: rewritten as 2:fdf9bde5129a; rewritten as 3:65b757b745b9
   o  ea207398892e
   
+
+  $ hg fatelogjson --hidden
+  o  019fadeab383 ""
+  |
+  | x  65b757b745b9 [{"markers": [["65b757b745b935093c87a2bccd877521cccffcbd", ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], "users": ["test"], "verb": "rewritten"}]
+  |/
+  | @  fdf9bde5129a ""
+  |/
+  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], "users": ["test"], "verb": "rewritten"}, {"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["65b757b745b935093c87a2bccd877521cccffcbd"], "users": ["test"], "verb": "rewritten"}]
+  |/
+  o  ea207398892e ""
+  
+
 Test templates with amended + folded commit
 ===========================================
 
@@ -906,6 +953,19 @@ 
   |/     Obsfate: rewritten as 4:eb5a0daa2192
   o  ea207398892e
   
+
+  $ hg fatelogjson --hidden
+  @  eb5a0daa2192 ""
+  |
+  | x  b7ea6d14e664 [{"markers": [["b7ea6d14e664bdc8922221f7992631b50da3fb07", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}]
+  | |
+  | | x  0dec01379d3b [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], "users": ["test"], "verb": "rewritten"}]
+  | |/
+  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}]
+  |/
+  o  ea207398892e ""
+  
+
 Test template with pushed and pulled obs markers
 ================================================
 
@@ -1467,6 +1527,27 @@ 
   |/     Obsfate: rewritten as 2:0dec01379d3b
   o  ea207398892e
   
+  $ hg fatelogjson --hidden
+  @  0b997eb7ceee ""
+  |
+  | o  b18bc8331526 ""
+  |/
+  | o  ba2ed02b0c9a ""
+  | |
+  | x  4a004186e638 [{"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["b18bc8331526a22cbb1801022bd1555bf291c48b"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["b18bc8331526a22cbb1801022bd1555bf291c48b"], "users": ["test"], "verb": "rewritten"}, {"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["0b997eb7ceeee06200a02f8aab185979092d514e"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["0b997eb7ceeee06200a02f8aab185979092d514e"], "users": ["test"], "verb": "rewritten"}]
+  |/
+  o  dd800401bd8c ""
+  |
+  | x  9bd10a0775e4 [{"markers": [["9bd10a0775e478708cada5f176ec6de654359ce7", ["dd800401bd8c79d815329277739e433e883f784e", "4a004186e63889f20cb16434fcbd72220bd1eace", "ba2ed02b0c9a56b9fdbc4e79c7e57866984d8a1f"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["4a004186e63889f20cb16434fcbd72220bd1eace", "ba2ed02b0c9a56b9fdbc4e79c7e57866984d8a1f", "dd800401bd8c79d815329277739e433e883f784e"], "users": ["test"], "verb": "split"}]
+  |/
+  o  f897c6137566 ""
+  |
+  | x  0dec01379d3b [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["f897c6137566320b081514b4c7227ecc3d384b39"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["f897c6137566320b081514b4c7227ecc3d384b39"], "users": ["test"], "verb": "rewritten"}, {"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["471f378eab4c5e25f6c77f785b27c936efb22874"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["471f378eab4c5e25f6c77f785b27c936efb22874"], "users": ["test"], "verb": "rewritten"}]
+  | |
+  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"], 0, [["user", "test"]], [0.0, 0], null]], "max_date": [0.0, 0], "min_date": [0.0, 0], "successors": ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"], "users": ["test"], "verb": "rewritten"}]
+  |/
+  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=