Skip to content

Commit

Permalink
adding json export for spack blame
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
vsoch committed May 4, 2021
1 parent 88b3009 commit ae031ea
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 15 deletions.
44 changes: 44 additions & 0 deletions lib/spack/docs/developer_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,50 @@ just like you would with the normal ``python`` command.

.. _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``
^^^^^^^^^^^^^
Expand Down
67 changes: 53 additions & 14 deletions lib/spack/spack/cmd/blame.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@

import os
import re
import sys

import llnl.util.tty as tty
from llnl.util.lang import pretty_date
from llnl.util.filesystem import working_dir
from llnl.util.tty.colify import colify_table
import spack.util.spack_json as sjson

import spack.paths
import spack.repo
Expand All @@ -33,12 +35,57 @@ def setup_parser(subparser):
view_group.add_argument(
'-g', '--git', dest='view', action='store_const', const='git',
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(
'package_or_file', help='name of package to show contributions for, '
'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):
# make sure this is a git repo
if not spack_is_git_repo():
Expand Down Expand Up @@ -96,18 +143,10 @@ def blame(parser, args):
else: # args.view == 'percent'
rows = sorted(counts.items(), key=lambda t: t[1], reverse=True)

# Print a nice table with authors and emails
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]
# Dump as json
if args.json:
dump_json(rows, last_mod, total_lines, emails)

colify_table(table)
# Print a nice table with authors and emails
else:
print_table(rows, last_mod, total_lines, emails)
24 changes: 24 additions & 0 deletions lib/spack/spack/test/cmd/blame.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest

from llnl.util.filesystem import working_dir
import spack.util.spack_json as sjson

import spack.paths
import spack.cmd
Expand Down Expand Up @@ -44,6 +45,29 @@ def test_blame_file(mock_packages):
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):
"""Sanity check the blame command to make sure it works."""
with capfd.disabled():
Expand Down
2 changes: 1 addition & 1 deletion share/spack/spack-completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ _spack_arch() {
_spack_blame() {
if $list_options
then
SPACK_COMPREPLY="-h --help -t --time -p --percent -g --git"
SPACK_COMPREPLY="-h --help -t --time -p --percent -g --git --json"
else
_all_packages
fi
Expand Down

0 comments on commit ae031ea

Please sign in to comment.