Patchwork [RFC] status: add terse option flag (issue4119)

login
register
mail settings
Submitter Sean Farley
Date April 6, 2015, 6:22 p.m.
Message ID <c85c11535052d49e3653.1428344532@1.0.0.127.in-addr.arpa>
Download mbox | patch
Permalink /patch/8512/
State Changes Requested
Headers show

Comments

Sean Farley - April 6, 2015, 6:22 p.m.
# HG changeset patch
# User Sean Farley <sean@farley.io>
# Date 1427261050 25200
#      Tue Mar 24 22:24:10 2015 -0700
# Node ID c85c11535052d49e36533b10d2f07943b71c40b4
# Parent  7c6f9097e2e03be6630d782114ac312264f7333a
status: add terse option flag (issue4119)

Based on work by Martin Geisler, this patch adds the ability to abbreviate the
ouput of status by only listing the parent directory for multiple files. By
default, we only do this for unknown files but control this through the
experimental.terse option.

For example, imagine we have the following output of status:

$ hg st
A mercurial/adddir/a
A mercurial/adddir/b
A mercurial/adddir/c
? bar
? baz
? foo/subdir/a
? foo/subdir/b
? foo/subdir/c
? foo/x
? foo/y
? mercurial/subdir/a
? mercurial/subdir/b
? mercurial/subdir/c
? ugh/x

without any configuration, we get:

$ hg st -t
A mercurial/adddir/a
A mercurial/adddir/b
A mercurial/adddir/c
? bar
? baz
? foo/
? mercurial/subdir/
? ugh/x

and with some config knobs, we get:

$ hg st -t --config experimental.terse='a?'
A mercurial/adddir/
? bar
? baz
? foo/
? mercurial/subdir/
? ugh/x

And we can still see the files in 'foo' by:

$ hg st -t foo
? foo/subdir/
? foo/x
? foo/y

But, this has a downside of not working as desired with relative paths:

$ hg st -t re:
A mercurial/adddir/a
A mercurial/adddir/b
A mercurial/adddir/c
? bar
? baz
? foo/subdir/a
? foo/subdir/b
? foo/subdir/c
? foo/x
? foo/y
? mercurial/subdir/a
? mercurial/subdir/b
? mercurial/subdir/c
? ugh/x
Matt Mackall - April 7, 2015, 7:37 p.m.
On Mon, 2015-04-06 at 11:22 -0700, Sean Farley wrote:
> # HG changeset patch
> # User Sean Farley <sean@farley.io>
> # Date 1427261050 25200
> #      Tue Mar 24 22:24:10 2015 -0700
> # Node ID c85c11535052d49e36533b10d2f07943b71c40b4
> # Parent  7c6f9097e2e03be6630d782114ac312264f7333a
> status: add terse option flag (issue4119)
> 
> Based on work by Martin Geisler, this patch adds the ability to abbreviate the
> ouput of status by only listing the parent directory for multiple files. By
> default, we only do this for unknown files but control this through the
> experimental.terse option.

Once we take -t, it's going to be really hard to change. It stops being
an experiment in like 9 days. So I'd really like to see a discussion why
it's not terse for all states before I take this.

If the answer is "because people have lots of unknown files but not
other sorts of files", it might be useful to think about why we show
unknown files at all: it's to help people remember to add things and
thereby not make broken commits.

Thus, the proper tool to cut down on unknown clutter is hgignore, and
the proper number of unknown files is 0. People who can't be bothered to
be tidy can just use hg status -q (= hg status -mard). Adding -t doesn't
even save a keystroke and probably just reinforces ?-blindness.

So I can see the value in a general option that collapses paths to try
to get a sense of large statuses, but I don't find one that's ?-specific
very compelling.
Sean Farley - April 7, 2015, 8:40 p.m.
Matt Mackall <mpm@selenic.com> writes:

> On Mon, 2015-04-06 at 11:22 -0700, Sean Farley wrote:
>> # HG changeset patch
>> # User Sean Farley <sean@farley.io>
>> # Date 1427261050 25200
>> #      Tue Mar 24 22:24:10 2015 -0700
>> # Node ID c85c11535052d49e36533b10d2f07943b71c40b4
>> # Parent  7c6f9097e2e03be6630d782114ac312264f7333a
>> status: add terse option flag (issue4119)
>> 
>> Based on work by Martin Geisler, this patch adds the ability to abbreviate the
>> ouput of status by only listing the parent directory for multiple files. By
>> default, we only do this for unknown files but control this through the
>> experimental.terse option.
>
> Once we take -t, it's going to be really hard to change. It stops being
> an experiment in like 9 days. So I'd really like to see a discussion why
> it's not terse for all states before I take this.

Yep, I agree.

> If the answer is "because people have lots of unknown files but not
> other sorts of files", it might be useful to think about why we show
> unknown files at all: it's to help people remember to add things and
> thereby not make broken commits.
>
> Thus, the proper tool to cut down on unknown clutter is hgignore, and
> the proper number of unknown files is 0. People who can't be bothered to
> be tidy can just use hg status -q (= hg status -mard). Adding -t doesn't
> even save a keystroke and probably just reinforces ?-blindness.
>
> So I can see the value in a general option that collapses paths to try
> to get a sense of large statuses, but I don't find one that's ?-specific
> very compelling.

I'm fine to enable it for all modes. I bring it to the mailing list now
for discussion since it's been talked about before.

I'm also fine to delay this until the sprint. One of my concerns if I
alias 'st = st -t re:' then I have no way to get that behavior without
some kind of '--relative' flag.

Patch

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -21,10 +21,11 @@  import minirst, revset, fileset
 import dagparser, context, simplemerge, graphmod, copies
 import random
 import setdiscovery, treediscovery, dagutil, pvec, localrepo
 import phases, obsolete, exchange, bundle2
 import ui as uimod
+import match as matchmod
 
 table = {}
 
 command = cmdutil.command(table)
 
@@ -5691,10 +5692,11 @@  class httpservice(object):
     ('n', 'no-status', None, _('hide status prefix')),
     ('C', 'copies', None, _('show source of copied files')),
     ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
     ('', 'rev', [], _('show difference from revision'), _('REV')),
     ('', 'change', '', _('list the changed files of a revision'), _('REV')),
+    ('t', 'terse', None, _('show only directory of unknown files')),
     ] + walkopts + subrepoopts + formatteropts,
     _('[OPTION]... [FILE]...'),
     inferrepo=True)
 def status(ui, repo, *pats, **opts):
     """show changed files in the working directory
@@ -5790,10 +5792,65 @@  def status(ui, repo, *pats, **opts):
             show = states[:5]
 
     stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
                        'ignored' in show, 'clean' in show, 'unknown' in show,
                        opts.get('subrepos'))
+
+    if opts.get('terse'):
+        stat = list(stat)
+        statmap = dict(zip('MAR!?IC', range(len('MAR!?IC'))))
+        knowndirs = set([''])
+
+        # can we avoid walking everything?
+        for path in repo.unfiltered()['.']:
+            d = os.path.dirname(path)
+            while d not in knowndirs:
+                knowndirs.add(d)
+                d = os.path.dirname(d)
+
+        match = None
+        if pats:
+            match = matchmod.match(repo.root, cwd, pats, opts.get('include'),
+                                   opts.get('exclude'))
+
+        # this method exists to override hiding a subdirectory if it matches
+        # one of the *pats, e.g.:
+        # hg st -t foo
+        # foo/a
+        # foo/b
+        def _match(path):
+            ret = path not in knowndirs
+            if match is not None:
+                ret = ret and not match(path)
+            return ret
+
+        for s in ui.config('experimental', 'terse', '?').upper():
+            results = {}
+            for path in stat[statmap[s]]:
+                prev = path
+                d = os.path.dirname(prev)
+                while _match(d):
+                    prev = d
+                    d = os.path.dirname(prev)
+
+                # to get the behavior of displaying the full path if there is
+                # only one file, we add the resulting path to a dictionary and
+                # overwrite that value if it already existed.
+                if prev != path:
+                    prev += '/'
+                    res = results.get(prev)
+                    if res is None:
+                        results[prev] = path
+                    else:
+                        results[prev] = prev
+                else:
+                    results[prev] = path
+
+            # replace the previous list
+            stat[statmap[s]] = sorted(results.values())
+        stat = scmutil.status(*stat)
+
     changestates = zip(states, 'MAR!?IC', stat)
 
     if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
         copy = copies.pathcopies(repo[node1], repo[node2])
 
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -211,11 +211,11 @@  Show all commands + options
   merge: force, rev, preview, tool
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
   push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
   remove: after, force, subrepos, include, exclude
   serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
-  status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos, template
+  status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, terse, include, exclude, subrepos, template
   summary: remote
   update: clean, check, date, rev, tool
   addremove: similarity, subrepos, include, exclude, dry-run
   archive: no-decode, prefix, rev, type, subrepos, include, exclude
   backout: merge, commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -569,10 +569,11 @@  Test command without options
    -n --no-status           hide status prefix
    -C --copies              show source of copied files
    -0 --print0              end filenames with NUL, for use with xargs
       --rev REV [+]         show difference from revision
       --change REV          list the changed files of a revision
+   -t --terse               show only directory of unknown files
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -S --subrepos            recurse into subrepositories
   
   (some details hidden, use --verbose to show complete help)
diff --git a/tests/test-status.t b/tests/test-status.t
--- a/tests/test-status.t
+++ b/tests/test-status.t
@@ -99,10 +99,32 @@  hg status . in repo root:
   $ hg status --cwd b/2 ..
   ? ../1/in_b_1
   ? in_b_2
   ? ../in_b
 
+test status with terse
+
+  $ hg status --terse
+  ? a/
+  ? b/
+  ? in_root
+
+make sure we can still list the individual files by specifying the directory
+
+  $ hg status --terse a
+  ? a/1/in_a_1
+  ? a/in_a
+
+test all status modes, not just unknown files, with terse
+
+  $ hg add -q a
+  $ hg status --terse --config experimental.terse='MAR!?IC'
+  A a/
+  ? b/
+  ? in_root
+  $ hg forget -q a
+
 combining patterns with root and patterns without a root works
 
   $ hg st a/in_a re:.*b$
   ? a/in_a
   ? b/in_b