From 3cef5663d8c04228b1da4e890fe023b58b97548f Mon Sep 17 00:00:00 2001 From: Vanessasaurus <814322+vsoch@users.noreply.github.com> Date: Tue, 25 May 2021 12:40:08 -0600 Subject: 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 Co-authored-by: vsoch --- lib/spack/docs/developer_guide.rst | 44 +++++++++++++++++++++++++ lib/spack/spack/cmd/blame.py | 67 ++++++++++++++++++++++++++++++-------- lib/spack/spack/test/cmd/blame.py | 24 ++++++++++++++ 3 files changed, 121 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/spack/docs/developer_guide.rst b/lib/spack/docs/developer_guide.rst index 7ae6938eda..0ceb5d84a1 100644 --- a/lib/spack/docs/developer_guide.rst +++ b/lib/spack/docs/developer_guide.rst @@ -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 + a month ago 927 99.7 Minnie Mouse + + 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`` ^^^^^^^^^^^^^ diff --git a/lib/spack/spack/cmd/blame.py b/lib/spack/spack/cmd/blame.py index f61ed03b4a..0f11e73919 100644 --- a/lib/spack/spack/cmd/blame.py +++ b/lib/spack/spack/cmd/blame.py @@ -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 @@ -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(): @@ -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) diff --git a/lib/spack/spack/test/cmd/blame.py b/lib/spack/spack/test/cmd/blame.py index 5ca69bb7b4..40d3e92ea1 100644 --- a/lib/spack/spack/test/cmd/blame.py +++ b/lib/spack/spack/test/cmd/blame.py @@ -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 @@ -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(): -- cgit v1.2.3-60-g2f50