# HG changeset patch
# User Gregory Szorc <gregory.sz...@gmail.com>
# Date 1476260708 -7200
#      Wed Oct 12 10:25:08 2016 +0200
# Node ID 5e9412f7dbc979f9b0f7c7a7e31c1315631d61da
# Parent  30b9f29d5ec393e36d03f762faa323ba4cff7f68
commands: introduce `hg display`


* Tests
* Template bikeshedding
* Formatter tweaks

If you are reviewing this RFC, please consider:

* We have an opportunity for a fresh start without BC concerns in this
  command. What all should we change? I've proposed eliminating revision
  numbers and using shortest() in the template to limit the minimum node
  length to 5.

Currently, Mercurial has a number of commands to show information. And,
there are features coming down the pipe that will introduce more
commands for showing information.

Currently, when introducing a new class of data or a view that we
wish to expose to the user, the strategy is to introduce a new command
or overload an existing command, sometimes both. For example, there is
a desire to formalize the wip/smartlog/underway/mine functionality that
many have devised. There is also a desire to introduce a "topics"
concept. In the current model, we'd need a new command for
wip/smartlog/etc (that behaves a lot like a pre-defined alias of `hg
log`). For topics, we'd likely overload `hg topic[s]` to both display
and manipulate topics.

Adding new commands for every pre-defined query doesn't scale well
and pollutes `hg help`. Overloading commands to perform read-only and
write operations is arguably an UX anti-pattern: while having all
functionality for a given concept in one command is nice, having a
single command doing multiple discrete operations is not. Furthermore,
a user may be surprised that a command they thought was read-only
actually changes something.

We discussed this at the Mercurial 4.0 Sprint in Paris and decided that
having a single command where we could hang pre-defined views of
various data would be a good idea. Having such a command would:

* Help prevent an explosion of new query-related commands
* Create a clear separation between read and write operations
  (mitigates footguns)
* Avoids overloading the meaning of commands that manipulate data
  (bookmark, tag, branch, etc) (while we can't take away the
  existing behavior for BC reasons, we now won't introduce this
  behavior on new commands)
* Allows users to discover informational views more easily by
  aggregating them in a single location
* Lowers the barrier to creating the new views (since the barrier
  to creating a top-level command is relatively high)

So, this commit introduces the `hg display` command. This command
accepts a positional argument of the "view" to show. New views
can be registered with a decorator.

At the aforementioned Sprint, we discussed and discarded various

We considered making `hg log <view>` perform this behavior. The main
reason we can't do this is because a positional argument to `hg log`
can be a file path and if there is a conflict between a path name and
a view name, behavior is ambiguous. We could have introduced
`hg log --view` or similar, but we felt that required too much typing
(we don't want to require a command flag to show a view) and wasn't
very discoverable. Furthermore, `hg log` is optimized for showing
changelog data and there are things that `hg display` will view that
aren't changelog centric.

For the command name, we would have preferred `hg show` because it is
shorter and not ambigious with any other core command. However, a
number of people have created `hg show` as effectively an alias to
`hg export`. And, some were concerned that Git users used to `git show`
being equivalent to `hg export` would be confused by a `hg show` doing
something different.

We also considered `hg view`, but that is already used by the "hgk"

"display" is an accurate description of what the command does and
the biggest concern was "di" is a prefix match with "diff", so
"display" was chosen as the command name.

This commit introduces `hg display` and a mechanism to register views
with it. A "bookmarks" view has been implemented to demonstrate how
the functionality works.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -60,8 +60,9 @@ from . import (
+    registrar,
@@ -3860,8 +3861,74 @@ def diff(ui, repo, *pats, **opts):
     cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
+displayview = registrar.displaycmdfunc()
+@command('^display', formatteropts, _('[VIEW]'))
+def display(ui, repo, view=None, template=None):
+    """show various repository information
+    A requested view of repository data is displayed.
+    .. note::
+       The default output from this command is not covered under Mercurial's
+       default backwards-compatible mechanism (which puts an emphasis on
+       not changing behavior). This means output from this command may change
+       in any version. However, the values fed to the formatter are covered
+       under the default backwards-compatible mechanism.
+       What this means is that automated consumers of this command should
+       specify an explicit template via ``-T/--template`` (possibly one of
+       the built-in machine-readable styles such as ``-Tjson``) if they
+       wish to parse output.
+    """
+    views = displayview._table
+    if not view:
+        ui.warn(_('no view requested\n'))
+        ui.write('hint: use `hg display <view>`\n')
+        ui.write('\n')
+        ui.write('the following views are available:\n')
+        ui.write('\n')
+        for name, func in sorted(views.items()):
+            ui.write(_('%s\n' % func.__doc__))
+        raise error.Abort(_('no view requested'),
+                          hint=_('use `hg display <view>` to choose a view'))
+    if view not in views:
+        raise error.Abort(_('unknown view %s') % view)
+    template = template or 'display'
+    fmtopic = views[view]._topic
+    formatter = ui.formatter(fmtopic, {'template': template})
+    return views[view](ui, repo, formatter)
+@displayview('bookmarks', 'bookmarks')
+def displaybookmarks(ui, repo, fm):
+    """active bookmarks and their associated changeset"""
+    marks = repo._bookmarks
+    if not len(marks):
+        ui.write_err('(no bookmarks set)\n')
+        return 0
+    active = repo._activebookmark
+    longest = max(len(b) for b in marks)
+    for bm, node in sorted(marks.items()):
+        fm.startitem()
+        fm.write('bookmark', '%s', bm)
+        fm.write('node', fm.hexfunc(node), fm.hexfunc(node))
+        fm.data(ctx=repo[node],
+                active=bm == active,
+                longestlen=longest)
+    fm.end()
     [('o', 'output', '',
      _('print output to file with formatted name'), _('FORMAT')),
     ('', 'switch-parent', None, _('diff against the second parent')),
diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -246,4 +246,11 @@ class templatefunc(_templateregistrarbas
     Otherwise, explicit 'templater.loadfunction()' is needed.
     _getname = _funcregistrarbase._parsefuncdecl
+class displaycmdfunc(_funcregistrarbase):
+    """Register a function to be invoked for an `hg display <thing>`."""
+    _docformat = pycompat.sysstr("%s: %s")
+    def _extrasetup(self, name, func, topic=None):
+        func._topic = topic
diff --git a/mercurial/templates/map-cmdline.display 
new file mode 100644
--- /dev/null
+++ b/mercurial/templates/map-cmdline.display
@@ -0,0 +1,1 @@
+bookmarks = '{pad(shortest(node, 5), 20)} {bookmark}\n'
Mercurial-devel mailing list

Reply via email to