# HG changeset patch
# User Yuya Nishihara <y...@tcha.org>
# Date 1506843424 -3600
#      Sun Oct 01 08:37:04 2017 +0100
# Node ID 743f288540ff52670eb36e5728c4bf35fcca2a07
# Parent  8de6dc4ab3304251a0bce4154603b784d4d519a0
formatter: fix default list/dict generator to be evaluated more than once

Before, _hybrid.gen must be a generator which could be consumed only once.
It was okay in templatekw.py since template keywords are functions which
create temporary hybrid objects, but the formatter doesn't work in that way.

To work around the issue, this patch makes _hybrid.gen optionally be a
function returning a generator.

Thanks to Pulkit for finding this issue.

diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -348,15 +348,14 @@ class _templateconverter(object):
         data = util.sortdict(_iteritems(data))
         def f():
             yield _plainconverter.formatdict(data, key, value, fmt, sep)
-        return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
-                                     gen=f())
+        return templatekw.hybriddict(data, key=key, value=value, fmt=fmt, 
gen=f)
     @staticmethod
     def formatlist(data, name, fmt, sep):
         '''build object that can be evaluated as either plain string or list'''
         data = list(data)
         def f():
             yield _plainconverter.formatlist(data, name, fmt, sep)
-        return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
+        return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f)
 
 class templateformatter(baseformatter):
     def __init__(self, ui, out, topic, opts):
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -39,15 +39,12 @@ class _hybrid(object):
 
     def __init__(self, gen, values, makemap, joinfmt):
         if gen is not None:
-            self.gen = gen
+            self.gen = gen  # generator or function returning generator
         self._values = values
         self._makemap = makemap
         self.joinfmt = joinfmt
-    @util.propertycache
     def gen(self):
-        return self._defaultgen()
-    def _defaultgen(self):
-        """Generator to stringify this as {join(self, ' ')}"""
+        """Default generator to stringify this as {join(self, ' ')}"""
         for i, x in enumerate(self._values):
             if i > 0:
                 yield ' '
@@ -104,9 +101,12 @@ def hybridlist(data, name, fmt='%s', gen
 def unwraphybrid(thing):
     """Return an object which can be stringified possibly by using a legacy
     template"""
-    if not util.safehasattr(thing, 'gen'):
+    gen = getattr(thing, 'gen', None)
+    if gen is None:
         return thing
-    return thing.gen
+    if callable(gen):
+        return gen()
+    return gen
 
 def unwrapvalue(thing):
     """Move the inner value object out of the wrapper"""
@@ -685,7 +685,7 @@ def showsuccessorssets(repo, ctx, **args
     # Format the successorssets
     def render(d):
         t = []
-        for i in d.gen:
+        for i in d.gen():
             t.append(i)
         return "".join(t)
 
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -760,8 +760,12 @@ Template keywords
   3de5eca88c00 ????-??-?? (glob)
   $ hg debugobsolete -r6 -T '{join(metadata % "{key}={value}", " ")}\n'
   user=test <t...@example.net>
-  $ hg debugobsolete -r6 -T '{metadata}\n'
+  $ hg debugobsolete -r6 -T '{metadata}\n{metadata}\n'
+  'user': 'test <t...@example.net>'
   'user': 'test <t...@example.net>'
+  $ hg debugobsolete -r6 -T '{succnodes}\n{succnodes}\n'
+  3de5eca88c00aa039da7399a220f4a5221faa585
+  3de5eca88c00aa039da7399a220f4a5221faa585
   $ hg debugobsolete -r6 -T '{flag} {get(metadata, "user")}\n'
   0 test <t...@example.net>
 
_______________________________________________
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

Reply via email to