Patchwork [V2] status: add a flag to terse the output (issue4119)

login
register
mail settings
Submitter Pulkit Goyal
Date June 17, 2017, 11:04 p.m.
Message ID <cb336b5ad077bee8e9cc.1497740673@workspace>
Download mbox | patch
Permalink /patch/21470/
State Superseded
Headers show

Comments

Pulkit Goyal - June 17, 2017, 11:04 p.m.
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1497710422 -19800
#      Sat Jun 17 20:10:22 2017 +0530
# Node ID cb336b5ad077bee8e9cc954f01889695da4089bc
# Parent  3abba5bc34546951b11b1bd3f5e5c77b90d950d1
status: add a flag to terse the output (issue4119)

This adds an experimental flag -t/--terse which will terse the output. The terse flag
will respect other flags which filters the output. The flag takes a string
whose value can be a subsequence of "marduic" (the order does not matter here.)

Ignored files are not considered while tersing unless -i flag is passed or 'i'
is there is the terse flag value.

The flag is experimental for testing as there may be cases which will produce
strange results with the flag. We can set the terse on by default by simply
passing 'u' to the cmdutil.tersestatus().

This patch also adds a test file with tests covering the new feature.
Danek Duvall - June 19, 2017, 9:15 p.m.
I'd like to see some text in the help output that talks about -t and its
arguments -- what are they, what do they mean, what it means to combine
them, how to disable them.

Pulkit Goyal wrote:

> @@ -4804,16 +4806,28 @@
>      show = [k for k in states if opts.get(k)]
>      if opts.get('all'):
>          show += ui.quiet and (states[:4] + ['clean']) or states
> +        if ui.quiet and terse:
> +            for st in ('ignored', 'unknown'):
> +                if st[0:1] in terse:
> +                    show.append(st)
> +
>      if not show:
>          if ui.quiet:
>              show = states[:4]
>          else:
>              show = states[:5]
> +        if terse:
> +            for st in ('ignored', 'unknown', 'clean'):
> +                if st[0:1] in terse:
> +                    show.append(st)

Is there a reason you're using [0:1] here instead of simply [0]?

> diff --git a/tests/test-terse-status.t b/tests/test-terse-status.t
> new file mode 100644
> --- /dev/null
> +++ b/tests/test-terse-status.t
> @@ -0,0 +1,106 @@
> +  $ mkdir folder
> +  $ cd folder
> +  $ hg init
> +  $ mkdir x
> +  $ touch a b x/aa.o x/bb.o
> +  $ hg status
> +  ? a
> +  ? b
> +  ? x/aa.o
> +  ? x/bb.o
> +
> +Show that only passed status are tersed
> +  $ hg status --terse m
> +  ? a
> +  ? b
> +  ? x/aa.o
> +  ? x/bb.o

I think you need some tests that actually end up tersing a directory that
has only modified (or added, or ...) files.

Danek
Pulkit Goyal - June 19, 2017, 9:39 p.m.
On Tue, Jun 20, 2017 at 2:45 AM, Danek Duvall <danek.duvall@oracle.com> wrote:
> I'd like to see some text in the help output that talks about -t and its
> arguments -- what are they, what do they mean, what it means to combine
> them, how to disable them.

Yeah that will be of great help to the users. Will add in V3 (or a followup).

>> +                if st[0:1] in terse:
>> +                    show.append(st)
>
> Is there a reason you're using [0:1] here instead of simply [0]?

Yes the reason is Python 3 because in that st[0] will return ascii value.
>>> a = b'abc'
>>> for i in range(len(a)):
...     print(a[i])
...
97
98
99


> I think you need some tests that actually end up tersing a directory that
> has only modified (or added, or ...) files.

Sure.
via Mercurial-devel - June 19, 2017, 9:44 p.m.
On Mon, Jun 19, 2017 at 2:39 PM, Pulkit Goyal <7895pulkit@gmail.com> wrote:
> On Tue, Jun 20, 2017 at 2:45 AM, Danek Duvall <danek.duvall@oracle.com> wrote:
>> I'd like to see some text in the help output that talks about -t and its
>> arguments -- what are they, what do they mean, what it means to combine
>> them, how to disable them.
>
> Yeah that will be of great help to the users. Will add in V3 (or a followup).
>
>>> +                if st[0:1] in terse:
>>> +                    show.append(st)
>>
>> Is there a reason you're using [0:1] here instead of simply [0]?
>
> Yes the reason is Python 3 because in that st[0] will return ascii value.
>>>> a = b'abc'
>>>> for i in range(len(a)):
> ...     print(a[i])
> ...
> 97
> 98
> 99

For the sake of correctness, "st[0] in terse" would also have worked,
wouldn't it? st[0] would be a byte, but wouldn't terse be bytes too? I
don't mind using st[0:1] for consistency, though.

>
>
>> I think you need some tests that actually end up tersing a directory that
>> has only modified (or added, or ...) files.
>
> Sure.
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Pulkit Goyal - June 19, 2017, 9:51 p.m.
On Tue, Jun 20, 2017 at 3:14 AM, Martin von Zweigbergk
<martinvonz@google.com> wrote:
> On Mon, Jun 19, 2017 at 2:39 PM, Pulkit Goyal <7895pulkit@gmail.com> wrote:
>> On Tue, Jun 20, 2017 at 2:45 AM, Danek Duvall <danek.duvall@oracle.com> wrote:
>>> I'd like to see some text in the help output that talks about -t and its
>>> arguments -- what are they, what do they mean, what it means to combine
>>> them, how to disable them.
>>
>> Yeah that will be of great help to the users. Will add in V3 (or a followup).
>>
>>>> +                if st[0:1] in terse:
>>>> +                    show.append(st)
>>>
>>> Is there a reason you're using [0:1] here instead of simply [0]?
>>
>> Yes the reason is Python 3 because in that st[0] will return ascii value.
>>>>> a = b'abc'
>>>>> for i in range(len(a)):
>> ...     print(a[i])
>> ...
>> 97
>> 98
>> 99
>
> For the sake of correctness, "st[0] in terse" would also have worked,
> wouldn't it? st[0] would be a byte, but wouldn't terse be bytes too? I
> don't mind using st[0:1] for consistency, though.

Oops, sorry. I didn't know this before and lived in an illusion that
it won't work because of st[0] returning ascii value. I need to learn
about how this works. Thanks for correcting.

Patch

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -402,6 +402,116 @@ 
 
     return commit(ui, repo, recordinwlock, pats, opts)
 
+def tersestatus(root, statlist, status, ignorefn, ignore):
+    """
+    Returns a list of statuses with directory collapsed if all the files in the
+    directory has the same status.
+    """
+
+    def numfiles(dirname):
+        """
+        Calculates the number of tracked files in a given directory which also
+        includes files which were removed or deleted. Considers ignored files
+        if ignore argument is True or 'i' is present in status argument.
+        """
+        if 'i' in status or ignore:
+            def match(fname):
+                return False
+        else:
+            match = ignorefn
+        lendir = 0
+        abspath = os.path.join(root, dirname)
+        # There might be cases when a directory does not exists as the whole
+        # directory can be removed and/or deleted.
+        try:
+            for f in os.listdir(abspath):
+                if not match(f):
+                    lendir += 1
+        except OSError:
+            pass
+        lendir += absentdir.get(dirname, 0)
+        return lendir
+
+    def absentones(removedfiles, missingfiles):
+        """
+        Returns a dictionary of directories and number of files which are either
+        removed or missing (deleted) in them.
+        """
+        absentdir = {}
+        absentfiles = removedfiles + missingfiles
+        while absentfiles:
+            f = absentfiles.pop()
+            par = os.path.dirname(f)
+            if par == '':
+                continue
+            try:
+                absentdir[par] += 1
+            except KeyError:
+                absentdir[par] = 1
+            if par not in removedfiles:
+                absentfiles.append(par)
+        return absentdir
+
+    indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
+    absentdir = absentones(statlist[2], statlist[3])
+    finalrs = [[]] * len(indexes)
+    didsomethingchanged = False
+
+    for st in pycompat.bytestr(status):
+
+        try:
+            ind = indexes[st]
+        except KeyError:
+            # TODO: Need a better error message here
+            raise error.Abort("'%s' not recognized" % st)
+
+        sfiles = statlist[ind]
+        if not sfiles:
+            continue
+        pardict = {}
+        for a in sfiles:
+            par = os.path.dirname(a)
+            pardict.setdefault(par, []).append(a)
+
+        rs = []
+        newls = []
+        for par, files in pardict.iteritems():
+            lenpar = numfiles(par)
+            if lenpar == len(files):
+                newls.append(par)
+
+        if not newls:
+            continue
+
+        while newls:
+            newel = newls.pop()
+            if newel == '':
+                continue
+            parn = os.path.dirname(newel)
+            pardict[newel] = []
+            # Adding pycompat.ossep as newel is a directory.
+            pardict.setdefault(parn, []).append(newel + pycompat.ossep)
+            lenpar = numfiles(parn)
+            if lenpar == len(pardict[parn]):
+                newls.append(parn)
+
+        # dict.values() for Py3 compatibility
+        for files in pardict.values():
+            rs.extend(files)
+
+        finalrs[ind] = rs
+        didsomethingchanged = True
+
+    # If nothing is changed, make sure the order of files is preserved.
+    if not didsomethingchanged:
+        return statlist
+
+    for x in xrange(len(indexes)):
+        if not finalrs[x]:
+            finalrs[x] = statlist[x]
+
+    return finalrs
+
 def findpossible(cmd, table, strict=False):
     """
     Return cmd -> (aliases, command table entry)
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4709,6 +4709,7 @@ 
     ('u', 'unknown', None, _('show only unknown (not tracked) files')),
     ('i', 'ignored', None, _('show only ignored files')),
     ('n', 'no-status', None, _('hide status prefix')),
+    ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
     ('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')),
@@ -4780,6 +4781,7 @@ 
     opts = pycompat.byteskwargs(opts)
     revs = opts.get('rev')
     change = opts.get('change')
+    terse = opts.get('terse')
 
     if revs and change:
         msg = _('cannot specify --rev and --change at the same time')
@@ -4804,16 +4806,28 @@ 
     show = [k for k in states if opts.get(k)]
     if opts.get('all'):
         show += ui.quiet and (states[:4] + ['clean']) or states
+        if ui.quiet and terse:
+            for st in ('ignored', 'unknown'):
+                if st[0:1] in terse:
+                    show.append(st)
+
     if not show:
         if ui.quiet:
             show = states[:4]
         else:
             show = states[:5]
+        if terse:
+            for st in ('ignored', 'unknown', 'clean'):
+                if st[0:1] in terse:
+                    show.append(st)
 
     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 terse:
+        stat = cmdutil.tersestatus(repo.root, stat, terse,
+                                    repo.dirstate._ignore, opts.get('ignore'))
     changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
 
     if (opts.get('all') or opts.get('copies')
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -230,7 +230,7 @@ 
   push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
   remove: after, force, subrepos, include, exclude
   serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, subrepos
-  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, terse, copies, print0, rev, change, include, exclude, subrepos, template
   summary: remote
   update: clean, check, merge, date, rev, tool
   addremove: similarity, subrepos, include, exclude, dry-run
diff --git a/tests/test-terse-status.t b/tests/test-terse-status.t
new file mode 100644
--- /dev/null
+++ b/tests/test-terse-status.t
@@ -0,0 +1,106 @@ 
+  $ mkdir folder
+  $ cd folder
+  $ hg init
+  $ mkdir x
+  $ touch a b x/aa.o x/bb.o
+  $ hg status
+  ? a
+  ? b
+  ? x/aa.o
+  ? x/bb.o
+
+Show that only passed status are tersed
+  $ hg status --terse m
+  ? a
+  ? b
+  ? x/aa.o
+  ? x/bb.o
+  $ hg status --terse a
+  ? a
+  ? b
+  ? x/aa.o
+  ? x/bb.o
+  $ hg status --terse u
+  ? a
+  ? b
+  ? x/
+  $ hg status --terse i
+  ? a
+  ? b
+  ? x/aa.o
+  ? x/bb.o
+  $ hg status --terse r
+  ? a
+  ? b
+  ? x/aa.o
+  ? x/bb.o
+  $ hg status --terse d
+  ? a
+  ? b
+  ? x/aa.o
+  ? x/bb.o
+  $ hg status --terse c
+  ? a
+  ? b
+  ? x/aa.o
+  ? x/bb.o
+  $ hg status --terse maudric
+  ? a
+  ? b
+  ? x/
+  $ hg status --terse f
+  abort: 'f' not recognized
+  [255]
+
+Have a .hgignore so that we can also have ignored files
+
+  $ echo ".*\.o" > .hgignore
+  $ hg status
+  ? .hgignore
+  ? a
+  ? b
+  $ hg status -i
+  I x/aa.o
+  I x/bb.o
+
+  $ hg status -t i
+  ? .hgignore
+  ? a
+  ? b
+  I x/
+
+Test interaction of ignore with other statuses while tersing
+
+  $ hg status -t maudric
+  ? .hgignore
+  ? a
+  ? b
+  I x/
+
+  $ touch x/aa x/bb
+  $ hg status -t marduc
+  ? .hgignore
+  ? a
+  ? b
+  ? x/
+  $ hg status -t mardiuc
+  ? .hgignore
+  ? a
+  ? b
+  ? x/aa
+  ? x/bb
+  I x/aa.o
+  I x/bb.o
+  $ hg add x/
+  adding x/aa
+  adding x/bb
+  $ hg status -t marduc
+  A x/
+  ? .hgignore
+  ? a
+  ? b
+  $ hg status -t marduc -a
+  A x/
+  $ hg status -t marduic -a
+  A x/aa
+  A x/bb