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

login
register
mail settings
Submitter Sean Farley
Date Oct. 13, 2015, 9:46 p.m.
Message ID <93173ffbe421307efe9c.1444772802@laptop.office.atlassian.com>
Download mbox | patch
Permalink /patch/11025/
State Deferred
Headers show

Comments

Sean Farley - Oct. 13, 2015, 9:46 p.m.
# HG changeset patch
# User Sean Farley <sean@farley.io>
# Date 1427261050 25200
#      Tue Mar 24 22:24:10 2015 -0700
# Node ID 93173ffbe421307efe9cae4404587a541a826ee7
# 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. By
default, we do this for all status types but control this through the
experimental.terse 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.terse='?'
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
Pierre-Yves David - Oct. 14, 2015, 2:20 p.m.
On 10/13/2015 02:46 PM, 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 93173ffbe421307efe9cae4404587a541a826ee7
> # Parent  a38924f7680c6b7d95e14ade999c35748c9dcafd
> status: add terse option flag (issue4119)

I really like the idea and think we should move forward on this topic. 
However there is likely some testing and discussion required. Can we 
flag the command switch as EXPERIMENTAL so that we have option to adjust 
this in the next cycle?

>
> 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. By
> default, we do this for all status types but control this through the
> experimental.terse 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.terse='?'
> 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')),
>       ] + 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', 'terse', '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-help.t b/tests/test-help.t
> --- a/tests/test-help.t
> +++ b/tests/test-help.t
> @@ -575,10 +575,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
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@selenic.com
> https://selenic.com/mailman/listinfo/mercurial-devel
>
Sean Farley - Oct. 14, 2015, 5:26 p.m.
Pierre-Yves David <pierre-yves.david@ens-lyon.org> writes:

> On 10/13/2015 02:46 PM, 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 93173ffbe421307efe9cae4404587a541a826ee7
>> # Parent  a38924f7680c6b7d95e14ade999c35748c9dcafd
>> status: add terse option flag (issue4119)
>
> I really like the idea and think we should move forward on this topic. 
> However there is likely some testing and discussion required. Can we 
> flag the command switch as EXPERIMENTAL so that we have option to adjust 
> this in the next cycle?

Sure. Any other feedback before I send a V3? Or is it easy enough for a
reviewer to add '(EXPERIMENTAL)'?
Ryan McElroy - Oct. 14, 2015, 10:40 p.m.
On 10/14/2015 7:20 AM, Pierre-Yves David wrote:
>
>
> On 10/13/2015 02:46 PM, 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 93173ffbe421307efe9cae4404587a541a826ee7
>> # Parent  a38924f7680c6b7d95e14ade999c35748c9dcafd
>> status: add terse option flag (issue4119)
>
> I really like the idea and think we should move forward on this topic. 
> However there is likely some testing and discussion required. Can we 
> flag the command switch as EXPERIMENTAL so that we have option to 
> adjust this in the next cycle?
>
>>
>> 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. By
>> default, we do this for all status types but control this through the
>> experimental.terse 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
Why do we get ugh/x and not just ugh/ ?

If not all files in  mercurial/adddir/a was not added (ie, only some 
files were added, what does the output look like?)

>>
>> and with some config knobs, we get:
>>
>> $ hg st -t --config experimental.terse='?'

This config option is poorly named -- I have no idea what to expect it 
to do (I had to read the code).

Perhaps better: 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

What about for the '' filematcher? (eg, hg status ''). Does that also 
fail to be tersified?

What would it take to make this work?

>>
>> 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')),
>>       ] + 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', 'terse', '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-help.t b/tests/test-help.t
>> --- a/tests/test-help.t
>> +++ b/tests/test-help.t
>> @@ -575,10 +575,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
>> _______________________________________________
>> Mercurial-devel mailing list
>> Mercurial-devel@selenic.com
>> https://selenic.com/mailman/listinfo/mercurial-devel
>>
>
Sean Farley - Oct. 14, 2015, 10:53 p.m.
Ryan McElroy <rm@fb.com> writes:

> On 10/14/2015 7:20 AM, Pierre-Yves David wrote:
>>
>>
>> On 10/13/2015 02:46 PM, 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 93173ffbe421307efe9cae4404587a541a826ee7
>>> # Parent  a38924f7680c6b7d95e14ade999c35748c9dcafd
>>> status: add terse option flag (issue4119)
>>
>> I really like the idea and think we should move forward on this topic. 
>> However there is likely some testing and discussion required. Can we 
>> flag the command switch as EXPERIMENTAL so that we have option to 
>> adjust this in the next cycle?
>>
>>>
>>> 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. By
>>> default, we do this for all status types but control this through the
>>> experimental.terse 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
> Why do we get ugh/x and not just ugh/ ?

Because there is only one file.

> If not all files in  mercurial/adddir/a was not added (ie, only some 
> files were added, what does the output look like?)

(let's assume there are four files: two added and two unknown) There
would be two entries in the output:

A mercurial/adddir/
? mercurial/adddir/

>>> and with some config knobs, we get:
>>>
>>> $ hg st -t --config experimental.terse='?'
>
> This config option is poorly named -- I have no idea what to expect it 
> to do (I had to read the code).
>
> Perhaps better: experimental.tersestatuses='?'

Sure.

>>> 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
>
> What about for the '' filematcher? (eg, hg status ''). Does that also 
> fail to be tersified?

Yep.

> What would it take to make this work?

Probably not too much work but this code has bit rotted for a long time
in my head. We could probably hammer it out at the sprint if there's
free time.
Ryan McElroy - Oct. 14, 2015, 11:52 p.m.
On 10/14/2015 3:53 PM, Sean Farley wrote:
> Ryan McElroy <rm@fb.com> writes:
>
>> On 10/14/2015 7:20 AM, Pierre-Yves David wrote:
>>>
>>> On 10/13/2015 02:46 PM, 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 93173ffbe421307efe9cae4404587a541a826ee7
>>>> # Parent  a38924f7680c6b7d95e14ade999c35748c9dcafd
>>>> status: add terse option flag (issue4119)
>>> I really like the idea and think we should move forward on this topic.
>>> However there is likely some testing and discussion required. Can we
>>> flag the command switch as EXPERIMENTAL so that we have option to
>>> adjust this in the next cycle?

Agree. See below for why.

>>>
>>>> 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. By
>>>> default, we do this for all status types but control this through the
>>>> experimental.terse 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
>> Why do we get ugh/x and not just ugh/ ?
> Because there is only one file.

Dislike this choice, but I'm open to discussion.

>> If not all files in  mercurial/adddir/a was not added (ie, only some
>> files were added, what does the output look like?)
> (let's assume there are four files: two added and two unknown) There
> would be two entries in the output:
>
> A mercurial/adddir/
> ? mercurial/adddir/

I find this surprising and unexpected. I think it makes sense to keep 
this experimental until we've hashed out some of these details, but I 
definitely like the overall direction.

>>>> and with some config knobs, we get:
>>>>
>>>> $ hg st -t --config experimental.terse='?'
>> This config option is poorly named -- I have no idea what to expect it
>> to do (I had to read the code).
>>
>> Perhaps better: experimental.tersestatuses='?'
> Sure.
>
>>>> 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
>> What about for the '' filematcher? (eg, hg status ''). Does that also
>> fail to be tersified?
> Yep.
>
>> What would it take to make this work?
> Probably not too much work but this code has bit rotted for a long time
> in my head. We could probably hammer it out at the sprint if there's
> free time.

Sounds good!
Augie Fackler - Oct. 15, 2015, 1:09 p.m.
On Wed, Oct 14, 2015 at 04:52:20PM -0700, Ryan McElroy wrote:
> On 10/14/2015 3:53 PM, Sean Farley wrote:
> >Ryan McElroy <rm@fb.com> writes:
> >
> >>On 10/14/2015 7:20 AM, Pierre-Yves David wrote:
> >>>
> >>>On 10/13/2015 02:46 PM, Sean Farley wrote:

[snip]

> >>>>? mercurial/subdir/
> >>>>? ugh/x
> >>Why do we get ugh/x and not just ugh/ ?
> >Because there is only one file.
>
> Dislike this choice, but I'm open to discussion.

I'm open to experimentation and disussion, but my gut says the
sensible default for this status-collapsing should probably match the
behavior of `svn status`.

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')),
     ] + 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', 'terse', '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-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -575,10 +575,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