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:
Vanessasaurus 2021-05-25 12:40:08 -06:00 committed by GitHub
parent b44bb952eb
commit 3cef5663d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 122 additions and 15 deletions

View File

@ -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``
^^^^^^^^^^^^^ ^^^^^^^^^^^^^

View File

@ -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)

View File

@ -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():

View File

@ -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