adding json export for spack blame (#23417)
I would like to be able to export (and save and then load programatically) spack blame metadata, so this commit adds a spack blame --json argument, along with developer docs for it Signed-off-by: vsoch <vsoch@users.noreply.github.com> Co-authored-by: vsoch <vsoch@users.noreply.github.com>
This commit is contained in:
parent
b44bb952eb
commit
3cef5663d8
@ -867,6 +867,50 @@ just like you would with the normal ``python`` command.
|
|||||||
|
|
||||||
.. _cmd-spack-url:
|
.. _cmd-spack-url:
|
||||||
|
|
||||||
|
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
``spack blame``
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Spack blame is a way to quickly see contributors to packages or files
|
||||||
|
in the spack repository. You should provide a target package name or
|
||||||
|
file name to the command. Here is an example asking to see contributions
|
||||||
|
for the package "python":
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ spack blame python
|
||||||
|
LAST_COMMIT LINES % AUTHOR EMAIL
|
||||||
|
2 weeks ago 3 0.3 Mickey Mouse <cheddar@gmouse.org>
|
||||||
|
a month ago 927 99.7 Minnie Mouse <swiss@mouse.org>
|
||||||
|
|
||||||
|
2 weeks ago 930 100.0
|
||||||
|
|
||||||
|
|
||||||
|
By default, you will get a table view (shown above) sorted by date of contribution,
|
||||||
|
with the most recent contribution at the top. If you want to sort instead
|
||||||
|
by percentage of code contribution, then add ``-p``:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ spack blame -p python
|
||||||
|
|
||||||
|
|
||||||
|
And to see the git blame view, add ``-g`` instead:
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ spack blame -g python
|
||||||
|
|
||||||
|
|
||||||
|
Finally, to get a json export of the data, add ``--json``:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ spack blame --json python
|
||||||
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
``spack url``
|
``spack url``
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
@ -5,11 +5,13 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
from llnl.util.lang import pretty_date
|
from llnl.util.lang import pretty_date
|
||||||
from llnl.util.filesystem import working_dir
|
from llnl.util.filesystem import working_dir
|
||||||
from llnl.util.tty.colify import colify_table
|
from llnl.util.tty.colify import colify_table
|
||||||
|
import spack.util.spack_json as sjson
|
||||||
|
|
||||||
import spack.paths
|
import spack.paths
|
||||||
import spack.repo
|
import spack.repo
|
||||||
@ -33,12 +35,57 @@ def setup_parser(subparser):
|
|||||||
view_group.add_argument(
|
view_group.add_argument(
|
||||||
'-g', '--git', dest='view', action='store_const', const='git',
|
'-g', '--git', dest='view', action='store_const', const='git',
|
||||||
help='show git blame output instead of summary')
|
help='show git blame output instead of summary')
|
||||||
|
subparser.add_argument(
|
||||||
|
"--json", action="store_true", default=False,
|
||||||
|
help="output blame as machine-readable json records")
|
||||||
|
|
||||||
subparser.add_argument(
|
subparser.add_argument(
|
||||||
'package_or_file', help='name of package to show contributions for, '
|
'package_or_file', help='name of package to show contributions for, '
|
||||||
'or path to a file in the spack repo')
|
'or path to a file in the spack repo')
|
||||||
|
|
||||||
|
|
||||||
|
def print_table(rows, last_mod, total_lines, emails):
|
||||||
|
"""
|
||||||
|
Given a set of rows with authors and lines, print a table.
|
||||||
|
"""
|
||||||
|
table = [['LAST_COMMIT', 'LINES', '%', 'AUTHOR', 'EMAIL']]
|
||||||
|
for author, nlines in rows:
|
||||||
|
table += [[
|
||||||
|
pretty_date(last_mod[author]),
|
||||||
|
nlines,
|
||||||
|
round(nlines / float(total_lines) * 100, 1),
|
||||||
|
author,
|
||||||
|
emails[author]]]
|
||||||
|
|
||||||
|
table += [[''] * 5]
|
||||||
|
table += [[pretty_date(max(last_mod.values())), total_lines, '100.0'] +
|
||||||
|
[''] * 3]
|
||||||
|
|
||||||
|
colify_table(table)
|
||||||
|
|
||||||
|
|
||||||
|
def dump_json(rows, last_mod, total_lines, emails):
|
||||||
|
"""
|
||||||
|
Dump the blame as a json object to the terminal.
|
||||||
|
"""
|
||||||
|
result = {}
|
||||||
|
authors = []
|
||||||
|
for author, nlines in rows:
|
||||||
|
authors.append({
|
||||||
|
"last_commit": pretty_date(last_mod[author]),
|
||||||
|
"lines": nlines,
|
||||||
|
"percentage": round(nlines / float(total_lines) * 100, 1),
|
||||||
|
"author": author,
|
||||||
|
"email": emails[author]
|
||||||
|
})
|
||||||
|
|
||||||
|
result['authors'] = authors
|
||||||
|
result["totals"] = {"last_commit": pretty_date(max(last_mod.values())),
|
||||||
|
"lines": total_lines, "percentage": "100.0"}
|
||||||
|
|
||||||
|
sjson.dump(result, sys.stdout)
|
||||||
|
|
||||||
|
|
||||||
def blame(parser, args):
|
def blame(parser, args):
|
||||||
# make sure this is a git repo
|
# make sure this is a git repo
|
||||||
if not spack_is_git_repo():
|
if not spack_is_git_repo():
|
||||||
@ -96,18 +143,10 @@ def blame(parser, args):
|
|||||||
else: # args.view == 'percent'
|
else: # args.view == 'percent'
|
||||||
rows = sorted(counts.items(), key=lambda t: t[1], reverse=True)
|
rows = sorted(counts.items(), key=lambda t: t[1], reverse=True)
|
||||||
|
|
||||||
|
# Dump as json
|
||||||
|
if args.json:
|
||||||
|
dump_json(rows, last_mod, total_lines, emails)
|
||||||
|
|
||||||
# Print a nice table with authors and emails
|
# Print a nice table with authors and emails
|
||||||
table = [['LAST_COMMIT', 'LINES', '%', 'AUTHOR', 'EMAIL']]
|
else:
|
||||||
for author, nlines in rows:
|
print_table(rows, last_mod, total_lines, emails)
|
||||||
table += [[
|
|
||||||
pretty_date(last_mod[author]),
|
|
||||||
nlines,
|
|
||||||
round(nlines / float(total_lines) * 100, 1),
|
|
||||||
author,
|
|
||||||
emails[author]]]
|
|
||||||
|
|
||||||
table += [[''] * 5]
|
|
||||||
table += [[pretty_date(max(last_mod.values())), total_lines, '100.0'] +
|
|
||||||
[''] * 3]
|
|
||||||
|
|
||||||
colify_table(table)
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from llnl.util.filesystem import working_dir
|
from llnl.util.filesystem import working_dir
|
||||||
|
import spack.util.spack_json as sjson
|
||||||
|
|
||||||
import spack.paths
|
import spack.paths
|
||||||
import spack.cmd
|
import spack.cmd
|
||||||
@ -44,6 +45,29 @@ def test_blame_file(mock_packages):
|
|||||||
assert 'EMAIL' in out
|
assert 'EMAIL' in out
|
||||||
|
|
||||||
|
|
||||||
|
def test_blame_json(mock_packages):
|
||||||
|
"""Ensure that we can output json as a blame."""
|
||||||
|
with working_dir(spack.paths.prefix):
|
||||||
|
out = blame('--json', 'mpich')
|
||||||
|
|
||||||
|
# Test loading the json, and top level keys
|
||||||
|
loaded = sjson.load(out)
|
||||||
|
assert "authors" in out
|
||||||
|
assert "totals" in out
|
||||||
|
|
||||||
|
# Authors should be a list
|
||||||
|
assert len(loaded['authors']) > 0
|
||||||
|
|
||||||
|
# Each of authors and totals has these shared keys
|
||||||
|
keys = ["last_commit", "lines", "percentage"]
|
||||||
|
for key in keys:
|
||||||
|
assert key in loaded['totals']
|
||||||
|
|
||||||
|
# But authors is a list of multiple
|
||||||
|
for key in keys + ["author", "email"]:
|
||||||
|
assert key in loaded['authors'][0]
|
||||||
|
|
||||||
|
|
||||||
def test_blame_by_git(mock_packages, capfd):
|
def test_blame_by_git(mock_packages, capfd):
|
||||||
"""Sanity check the blame command to make sure it works."""
|
"""Sanity check the blame command to make sure it works."""
|
||||||
with capfd.disabled():
|
with capfd.disabled():
|
||||||
|
@ -384,7 +384,7 @@ _spack_arch() {
|
|||||||
_spack_blame() {
|
_spack_blame() {
|
||||||
if $list_options
|
if $list_options
|
||||||
then
|
then
|
||||||
SPACK_COMPREPLY="-h --help -t --time -p --percent -g --git"
|
SPACK_COMPREPLY="-h --help -t --time -p --percent -g --git --json"
|
||||||
else
|
else
|
||||||
_all_packages
|
_all_packages
|
||||||
fi
|
fi
|
||||||
|
Loading…
Reference in New Issue
Block a user