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

login
register
mail settings
Submitter Sean Farley
Date Oct. 15, 2015, 6:24 a.m.
Message ID <3dd7ff9879636256252d.1444890277@laptop.local>
Download mbox | patch
Permalink /patch/11098/
State Deferred
Headers show

Comments

Sean Farley - Oct. 15, 2015, 6:24 a.m.
# HG changeset patch
# User Sean Farley <sean@farley.io>
# Date 1427261050 25200
#      Tue Mar 24 22:24:10 2015 -0700
# Node ID 3dd7ff9879636256252dd87ddda204383fb5863e
# Parent  a38924f7680c6b7d95e14ade999c35748c9dcafd
status: add terse option flag (issue4119)

Based on an idea by Martin Geisler, this patch adds the ability to abbreviate
the ouput of status by only listing the parent directory for multiple files.
Directories that only contain one file are not collapsed, though. By default,
we do this for all status types but control this through the
experimental.tersestatuses option.

Future work could speed up status operations even further by skipping disk
operations based on this config.

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/
? bar
? baz
? foo/
? mercurial/subdir/
? ugh/x

and with some config knobs, we get:

$ hg st -t --config experimental.tersestatuses='?'
A mercurial/adddir/a
A mercurial/adddir/b
A mercurial/adddir/c
? 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
Augie Fackler - Oct. 15, 2015, 1:50 p.m.
On Wed, Oct 14, 2015 at 11:24:37PM -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 3dd7ff9879636256252dd87ddda204383fb5863e
> # Parent  a38924f7680c6b7d95e14ade999c35748c9dcafd
> status: add terse option flag (issue4119)

I'm +1 on this for this cycle, but would like others to chime in as I
know this has been contentious in the past.

(One thing I know this will need eventually is a way to disable it,
probably along the lines of --terse=never or something.)

>
> Based on an idea by Martin Geisler, this patch adds the ability to abbreviate
> the ouput of status by only listing the parent directory for multiple files.
> Directories that only contain one file are not collapsed, though. By default,
> we do this for all status types but control this through the
> experimental.tersestatuses option.
>
> Future work could speed up status operations even further by skipping disk
> operations based on this config.
>
> 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/
> ? bar
> ? baz
> ? foo/
> ? mercurial/subdir/
> ? ugh/x
>
> and with some config knobs, we get:
>
> $ hg st -t --config experimental.tersestatuses='?'
> A mercurial/adddir/a
> A mercurial/adddir/b
> A mercurial/adddir/c
> ? 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
>
> 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, operator
>  import setdiscovery, treediscovery, dagutil, pvec, localrepo
>  import phases, obsolete, exchange, bundle2, repair, lock as lockmod
>  import ui as uimod
> +import match as matchmod
>
>  table = {}
>
>  command = cmdutil.command(table)
>
> @@ -5898,10 +5899,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 (EXPERIMENTAL)')),
>      ] + walkopts + subrepoopts + formatteropts,
>      _('[OPTION]... [FILE]...'),
>      inferrepo=True)
>  def status(ui, repo, *pats, **opts):
>      """show changed files in the working directory
> @@ -5998,10 +6000,65 @@ def status(ui, repo, *pats, **opts):
>
>      m = scmutil.match(repo[node2], pats, opts)
>      stat = repo.status(node1, node2, m,
>                         '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', 'tersestatuses', 'MAR!?IC').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')
>          or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
>          copy = copies.pathcopies(repo[node1], repo[node2], m)
> diff --git a/tests/test-completion.t b/tests/test-completion.t
> --- a/tests/test-completion.t
> +++ b/tests/test-completion.t
> @@ -214,11 +214,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-status.t b/tests/test-status.t
> --- a/tests/test-status.t
> +++ b/tests/test-status.t
> @@ -99,10 +99,40 @@ 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
> +  A a/
> +  ? b/
> +  ? in_root
> +
> +test config option to terse-ify just unknown files
> +
> +  $ hg status --terse --config experimental.tersestatuses='?'
> +  A a/1/in_a_1
> +  A a/in_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
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@selenic.com
> https://selenic.com/mailman/listinfo/mercurial-devel
Gilles Moris - Oct. 16, 2015, 5:54 a.m.
Le 15/10/2015 08:24, Sean Farley a écrit :
> # HG changeset patch
> # User Sean Farley <sean@farley.io>
> # Date 1427261050 25200
> #      Tue Mar 24 22:24:10 2015 -0700
> # Node ID 3dd7ff9879636256252dd87ddda204383fb5863e
> # Parent  a38924f7680c6b7d95e14ade999c35748c9dcafd
> status: add terse option flag (issue4119)
>
> Based on an idea by Martin Geisler, this patch adds the ability to abbreviate
> the ouput of status by only listing the parent directory for multiple files.
> Directories that only contain one file are not collapsed, though. By default,
> we do this for all status types but control this through the
> experimental.tersestatuses option.
I am wandering why we would not want this to be an argument of the 
option, with possibly a character like * (or another more  shell 
neutral) for everything. I would want sometimes reducing unknow, 
sometimes added, ...
Was there an argument for the config knob vs option with value?

Regards.
Gilles.
>
> Future work could speed up status operations even further by skipping disk
> operations based on this config.
>
>
Matt Mackall - Oct. 16, 2015, 8:32 p.m.
On Wed, 2015-10-14 at 23:24 -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 3dd7ff9879636256252dd87ddda204383fb5863e
> # Parent  a38924f7680c6b7d95e14ade999c35748c9dcafd
> status: add terse option flag (issue4119)

Doesn't appear to work for me:

$ hg st -t
M mercurial/commands.py
? a
? b
? c449ec4ff851
? c449ec4ff851.1
? contrib/docker/wine
? contrib/docker/wine-debian
? contrib/dumpdirstate.py
? contrib/import-graph.py
? defer.py
? f
? foo
? hgweb.config
? mercurial/graph.png
? mercurial/templates/static/style-coal.css
? mercurial/threads.py
? mercurial/timeunpacker.py
? new-chunkbuffer
? newdelta.txt
? p
? packman.py
? raw.txt
? similarity.py
? split-vpn.txt
? t4705
? threads.txt

I don't know what's expected at the root, but it's not collapsing any
directories.
Sean Farley - Oct. 16, 2015, 9:05 p.m.
Matt Mackall <mpm@selenic.com> writes:

> On Wed, 2015-10-14 at 23:24 -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 3dd7ff9879636256252dd87ddda204383fb5863e
>> # Parent  a38924f7680c6b7d95e14ade999c35748c9dcafd
>> status: add terse option flag (issue4119)
>
> Doesn't appear to work for me:
>
> $ hg st -t
> M mercurial/commands.py
> ? a
> ? b
> ? c449ec4ff851
> ? c449ec4ff851.1
> ? contrib/docker/wine
> ? contrib/docker/wine-debian
> ? contrib/dumpdirstate.py
> ? contrib/import-graph.py
> ? defer.py
> ? f
> ? foo
> ? hgweb.config
> ? mercurial/graph.png
> ? mercurial/templates/static/style-coal.css
> ? mercurial/threads.py
> ? mercurial/timeunpacker.py
> ? new-chunkbuffer
> ? newdelta.txt
> ? p
> ? packman.py
> ? raw.txt
> ? similarity.py
> ? split-vpn.txt
> ? t4705
> ? threads.txt
>
> I don't know what's expected at the root, but it's not collapsing any
> directories.

Interesting. This example exposes all the shortcuts I took. I'll try to
get a simple fix out today but if not, then might need to pick Wez's and
Sid's brain at the sprint for a fast way to do this.

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, operator
 import setdiscovery, treediscovery, dagutil, pvec, localrepo
 import phases, obsolete, exchange, bundle2, repair, lock as lockmod
 import ui as uimod
+import match as matchmod
 
 table = {}
 
 command = cmdutil.command(table)
 
@@ -5898,10 +5899,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 (EXPERIMENTAL)')),
     ] + walkopts + subrepoopts + formatteropts,
     _('[OPTION]... [FILE]...'),
     inferrepo=True)
 def status(ui, repo, *pats, **opts):
     """show changed files in the working directory
@@ -5998,10 +6000,65 @@  def status(ui, repo, *pats, **opts):
 
     m = scmutil.match(repo[node2], pats, opts)
     stat = repo.status(node1, node2, m,
                        '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', 'tersestatuses', 'MAR!?IC').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')
         or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
         copy = copies.pathcopies(repo[node1], repo[node2], m)
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -214,11 +214,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-status.t b/tests/test-status.t
--- a/tests/test-status.t
+++ b/tests/test-status.t
@@ -99,10 +99,40 @@  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
+  A a/
+  ? b/
+  ? in_root
+
+test config option to terse-ify just unknown files
+
+  $ hg status --terse --config experimental.tersestatuses='?'
+  A a/1/in_a_1
+  A a/in_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