D360: log: add a "graphwidth" template variable

hooper (Danny Hooper) phabricator at mercurial-scm.org
Sat Aug 12 00:26:02 UTC 2017


hooper created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Wrapping text in templates for 'hg log --graph' can't be done very well,
  because the template doesn't know how wide the graph drawing is. The edge
  drawing function needs to know the number of lines in the template output, so
  we need to also determine how wide that drawing would be before we call the
  edgefn or evaluate the template.
  
  This patch adds an optional widthfn callback alongside edgefn so that callers
  to displaygraph() can enable it to pass the computed graph width into the
  template. The new argument is added so as to avoid breaking any extensions
  calling the current displaygraph(). The stock asciiedges() gets a stock
  asciiwidth() so that we can do something like this:
  
  COLUMNS=10 hg log --graph --template "{fill(desc, termwidth - graphwidth)}"
  @  a a a a
  
  | a a a a |
  | a a a a |
  |
  
  o    a a a
  
  | \   a a a |
  |           | a a a |
  |           | a a a |
  |
  
  Using extensions to do this would be relatively complicated due to a lack of
  hooks in this area of the code.
  
  In the future it may make sense to have a more generic "textwidth" that tells
  you how many columns you can expect to fill without causing the terminal to
  wrap your output. I'm not sure there are other situations to motivate this yet,
  or if it is entirely feasible.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D360

AFFECTED FILES
  hgext/show.py
  mercurial/cmdutil.py
  mercurial/commands.py
  mercurial/graphmod.py
  mercurial/templatekw.py
  tests/test-command-template.t

CHANGE DETAILS

diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -4319,3 +4319,155 @@
   custom
 
   $ cd ..
+
+Test 'graphwidth' in 'hg log' on various topologies. The key here is that the
+printed graphwidths 3, 5, 7, etc. should all line up in their respective
+columns. We don't care about other aspects of the graph rendering here.
+
+  $ hg init graphwidth
+  $ cd graphwidth
+
+  $ wrappabletext="a a a a a a a a a a a a"
+
+  $ printf "first\n" > file
+  $ hg add file
+  $ hg commit -m "$wrappabletext"
+
+  $ printf "first\nsecond\n" > file
+  $ hg commit -m "$wrappabletext"
+
+  $ hg checkout 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ printf "third\nfirst\n" > file
+  $ hg commit -m "$wrappabletext"
+  created new head
+
+  $ hg merge
+  merging file
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+  $ hg log --graph -T "{graphwidth}"
+  @  3
+  |
+  | @  5
+  |/
+  o  3
+  
+  $ hg commit -m "$wrappabletext"
+
+  $ hg log --graph -T "{graphwidth}"
+  @    5
+  |\
+  | o  5
+  | |
+  o |  5
+  |/
+  o  3
+  
+
+  $ hg checkout 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ printf "third\nfirst\nsecond\n" > file
+  $ hg commit -m "$wrappabletext"
+  created new head
+
+  $ hg log --graph -T "{graphwidth}"
+  @  3
+  |
+  | o    7
+  | |\
+  +---o  7
+  | |
+  | o  5
+  |/
+  o  3
+  
+
+  $ hg log --graph -T "{graphwidth}" -r 3
+  o    5
+  |\
+  ~ ~
+
+  $ hg log --graph -T "{graphwidth}" -r 1
+  o  3
+  |
+  ~
+
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg commit -m "$wrappabletext"
+
+  $ printf "seventh\n" >> file
+  $ hg commit -m "$wrappabletext"
+
+  $ hg log --graph -T "{graphwidth}"
+  @  3
+  |
+  o    5
+  |\
+  | o  5
+  | |
+  o |    7
+  |\ \
+  | o |  7
+  | |/
+  o /  5
+  |/
+  o  3
+  
+
+The point of graphwidth is to allow wrapping that accounts for the space taken
+by the graph.
+
+  $ COLUMNS=10 hg log --graph -T "{fill(desc, termwidth - graphwidth)}"
+  @  a a a a
+  |  a a a a
+  |  a a a a
+  o    a a a
+  |\   a a a
+  | |  a a a
+  | |  a a a
+  | o  a a a
+  | |  a a a
+  | |  a a a
+  | |  a a a
+  o |    a a
+  |\ \   a a
+  | | |  a a
+  | | |  a a
+  | | |  a a
+  | | |  a a
+  | o |  a a
+  | |/   a a
+  | |    a a
+  | |    a a
+  | |    a a
+  | |    a a
+  o |  a a a
+  |/   a a a
+  |    a a a
+  |    a a a
+  o  a a a a
+     a a a a
+     a a a a
+
+Something tricky happens when there are elided nodes; the next drawn row of
+edges can be more than one column wider, but the graph width only increases by
+one column. The remaining columns are added in between the nodes.
+
+  $ hg log --graph -T "{graphwidth}" -r "0|2|4|5"
+  o    5
+  |\
+  | \
+  | :\
+  o : :  7
+  :/ /
+  : o  5
+  :/
+  o  3
+  
+
+  $ cd ..
+
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -764,6 +764,13 @@
     """Integer. The width of the current terminal."""
     return repo.ui.termwidth()
 
+ at templatekeyword('graphwidth')
+def graphwidth(repo, ctx, templ, **args):
+    """Integer. The width of the graph drawn by 'log --graph' or zero."""
+    # The value args['graphwidth'] will be this function, so we use an internal
+    # name to pass the value through props into this function.
+    return args.get('_graphwidth', 0)
+
 @templatekeyword('troubles')
 def showtroubles(**args):
     """List of strings. Evolution troubles affecting the changeset.
diff --git a/mercurial/graphmod.py b/mercurial/graphmod.py
--- a/mercurial/graphmod.py
+++ b/mercurial/graphmod.py
@@ -222,6 +222,24 @@
     state['edges'].pop(rev, None)
     yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
 
+def asciiwidth(state, rev, parents):
+    """returns the width of the graph drawn by ascii() based on asciiedges()"""
+    seen = state['seen'][:]
+    if rev not in seen:
+        seen.append(rev)
+    columns = len(seen)
+
+    # We might be adding columns to handle previously unseen parents, but only
+    # one of them goes into the graph width for this rev.
+    newparents = 0
+    for ptype, parent in parents:
+        if parent != rev and parent not in seen:
+            newparents += 1
+    columns = max(columns, columns + min(2, newparents) - 1)
+
+    # Each column is 2 characters, and there's another character of padding.
+    return 1 + 2 * columns
+
 def _fixlongrightedges(edges):
     for (i, (start, end)) in enumerate(edges):
         if end > start:
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -3123,7 +3123,8 @@
         def display(other, chlist, displayer):
             revdag = cmdutil.graphrevs(other, chlist, opts)
             cmdutil.displaygraph(ui, repo, revdag, displayer,
-                                 graphmod.asciiedges)
+                                 graphmod.asciiedges,
+                                 widthfn=graphmod.asciiwidth)
 
         hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
         return 0
@@ -3572,7 +3573,8 @@
         revdag = cmdutil.graphrevs(repo, o, opts)
         ui.pager('outgoing')
         displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
-        cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
+        cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges,
+                             widthfn=graphmod.asciiwidth)
         cmdutil.outgoinghooks(ui, repo, other, opts, o)
         return 0
 
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2508,7 +2508,7 @@
     return formatnode
 
 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
-                 filematcher=None):
+                 filematcher=None, widthfn=None):
     formatnode = _graphnodeformatter(ui, displayer)
     state = graphmod.asciistate()
     styles = state['styles']
@@ -2545,7 +2545,11 @@
         revmatchfn = None
         if filematcher is not None:
             revmatchfn = filematcher(ctx.rev())
-        displayer.show(ctx, copies=copies, matchfn=revmatchfn)
+        graphwidth = None
+        if widthfn:
+            graphwidth = widthfn(state, rev, parents)
+        displayer.show(ctx, copies=copies, matchfn=revmatchfn,
+                       _graphwidth=graphwidth)
         lines = displayer.hunk.pop(rev).split('\n')
         if not lines[-1]:
             del lines[-1]
@@ -2569,8 +2573,8 @@
 
     ui.pager('log')
     displayer = show_changeset(ui, repo, opts, buffered=True)
-    displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
-                 filematcher)
+    displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges,
+                 getrenamed, filematcher, widthfn=graphmod.asciiwidth)
 
 def checkunsupportedgraphflags(pats, opts):
     for op in ["newest_first"]:
diff --git a/hgext/show.py b/hgext/show.py
--- a/hgext/show.py
+++ b/hgext/show.py
@@ -397,7 +397,8 @@
     revdag = graphmod.dagwalker(repo, revs)
 
     ui.setconfig('experimental', 'graphshorten', True)
-    cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
+    cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges,
+                         widthfn=graphmod.asciiwidth)
 
 def extsetup(ui):
     # Alias `hg <prefix><view>` to `hg show <view>`.



To: hooper, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list