Fixed reportage matrix.
Now it doesn't print things the user doesn't care about, like empty lint passes, or broken linters that aren't relevant to the current project. Added a test pass to ensure it doesn't blow up when running against an empty stage.
This commit is contained in:
parent
aa4b5aa978
commit
13b0378b67
57
README.rst
57
README.rst
|
@ -1,23 +1,49 @@
|
||||||
===============================
|
===============================
|
||||||
Git Lint
|
Git Lint: README
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
A git command that automatically runs identifiable linters against
|
A git command that automatically runs identifiable linters against
|
||||||
changed files in current repository or staging.
|
changed files in your current git repository or staging area.
|
||||||
|
|
||||||
* Free software: MIT license
|
* Free software: MIT license
|
||||||
|
|
||||||
**Git Lint** runs a configurable set of syntax, style, and complexity
|
**Git Lint** runs a configurable set of syntax, style, and complexity
|
||||||
checkers against changed files in your current working directory or
|
checkers against changed files in your current working directory or
|
||||||
staging area. It can be configured to work with any `lint` command.
|
staging area. It can be configured to work with any `lint`-like
|
||||||
Some commands may require shell wrappers.
|
command. Some commands may require shell wrappers.
|
||||||
|
|
||||||
While it may be possible to create a custom lint command in your npm,
|
While it may be possible to create a custom lint command in your npm,
|
||||||
grunt, Make, CMake, or whatever, the fact is we all use a VCS, and
|
grunt, Make, CMake, or whatever, the fact is we all use a VCS, and most
|
||||||
most of us use git. Having a centralized repository for what we want
|
of us use git. Having a centralized repository for what we want checked
|
||||||
checked and how we want it checked, associated with git (and by
|
and how we want it checked, associated with git (and by extension, as a
|
||||||
extension, as a pre-commit hook), that can be run at any time for any
|
pre-commit hook), that can be run at any time for any reason.
|
||||||
reason.
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
To lint only what's changed recently in your current working directory:
|
||||||
|
`git lint`
|
||||||
|
|
||||||
|
To lint everything, changed or otherwise, from the current directory down:
|
||||||
|
`git lint -a`
|
||||||
|
|
||||||
|
To lint what's changed from the repo's base:
|
||||||
|
`git lint -b`
|
||||||
|
|
||||||
|
To lint what's in your staging directory:
|
||||||
|
`git lint -s`
|
||||||
|
|
||||||
|
|
||||||
|
Install
|
||||||
|
-------
|
||||||
|
|
||||||
|
`pip install git-linter`
|
||||||
|
|
||||||
|
You will need to copy the .git-lint configuration file to either your
|
||||||
|
home directory or the repo`s base directory. Edit the configuration
|
||||||
|
file as needed. You will also need any linters that you plan on
|
||||||
|
running.
|
||||||
|
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
@ -37,11 +63,16 @@ Features
|
||||||
restoration of your workspace, it ensures the timestamps are the
|
restoration of your workspace, it ensures the timestamps are the
|
||||||
same, so as not to confuse your build system or IDE.
|
same, so as not to confuse your build system or IDE.
|
||||||
|
|
||||||
Credits
|
|
||||||
-------
|
|
||||||
|
|
||||||
The completion of this project was graciously sponsored by my employer,
|
Acknowledgements
|
||||||
Splunk <http://splunk.com>.
|
----------------
|
||||||
|
`Git lint` started life as a simple pre-commit hook. Most of the
|
||||||
|
changes since were inspired by Steve Pulec's Why You Need a Git
|
||||||
|
Pre-Commit Hook and Why Most Are Wrong_, as well as just my own needs as
|
||||||
|
a software developer.
|
||||||
|
|
||||||
|
.. _Why You Need a Git Pre-Commit Hook and Why Most Are Wrong: https://dzone.com/articles/why-your-need-git-pre-commit
|
||||||
|
|
||||||
|
|
||||||
Disclaimer
|
Disclaimer
|
||||||
----------
|
----------
|
||||||
|
|
|
@ -318,33 +318,33 @@ def get_filelist(options, extras):
|
||||||
# |___/ |___/ |_| |_|
|
# |___/ |___/ |_| |_|
|
||||||
|
|
||||||
|
|
||||||
class Runner:
|
class StagingRunner:
|
||||||
def __init__(self, options):
|
def __init__(self, filenames):
|
||||||
self.runner = ('staging' in options and Runner.staging_wrapper) or Runner.workspace_wrapper
|
self.filenames = filenames
|
||||||
|
|
||||||
def __call__(self, run_linters, filenames):
|
def __enter__(self):
|
||||||
return self.runner(run_linters, filenames)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def staging_wrapper(run_linters, filenames):
|
|
||||||
def time_gather(f):
|
def time_gather(f):
|
||||||
stats = os.stat(f)
|
stats = os.stat(f)
|
||||||
return (f, (stats.st_atime, stats.st_mtime))
|
return (f, (stats.st_atime, stats.st_mtime))
|
||||||
|
self.times = [time_gather(filename) for filename in self.filenames]
|
||||||
times = [time_gather(file) for file in filenames]
|
|
||||||
run_git_command(['stash', '--keep-index'])
|
run_git_command(['stash', '--keep-index'])
|
||||||
|
|
||||||
results = run_linters()
|
def __exit__(self, type, value, traceback):
|
||||||
run_git_command(['reset', '--hard'])
|
run_git_command(['reset', '--hard'])
|
||||||
run_git_command(['stash', 'pop', '--quiet', '--index'])
|
run_git_command(['stash', 'pop', '--quiet', '--index'])
|
||||||
|
for (filename, timepair) in self.times:
|
||||||
for (filename, timepair) in times:
|
|
||||||
os.utime(filename, timepair)
|
os.utime(filename, timepair)
|
||||||
return results
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def workspace_wrapper(run_linters, filenames):
|
class WorkspaceRunner(object):
|
||||||
return run_linters()
|
def __init__(self, filenames):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# ___ _ _ _
|
# ___ _ _ _
|
||||||
|
@ -368,7 +368,7 @@ class Linters:
|
||||||
"""Run one linter against one file.
|
"""Run one linter against one file.
|
||||||
|
|
||||||
If the result matches the error condition specified in the configuration file,
|
If the result matches the error condition specified in the configuration file,
|
||||||
return the error code and messages, either return nothing.
|
return the error code and messages, otherwise return nothing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cmd = linter['command'] + ' "' + filename + '"'
|
cmd = linter['command'] + ' "' + filename + '"'
|
||||||
|
@ -444,7 +444,9 @@ def run_linters(options, config, extras=[]):
|
||||||
cant_lint_filenames = [filename for filename in lintable_filenames
|
cant_lint_filenames = [filename for filename in lintable_filenames
|
||||||
if cant_lint_filter(filename)]
|
if cant_lint_filter(filename)]
|
||||||
|
|
||||||
runner = Runner(options)
|
runner = WorkspaceRunner
|
||||||
|
if 'staging' in options:
|
||||||
|
runner = StagingRunner
|
||||||
|
|
||||||
linters = Linters(build_config_subset(working_linter_names),
|
linters = Linters(build_config_subset(working_linter_names),
|
||||||
sorted(lintable_filenames))
|
sorted(lintable_filenames))
|
||||||
|
@ -454,7 +456,8 @@ def run_linters(options, config, extras=[]):
|
||||||
return (dryrun_results, unlintable_filenames, cant_lint_filenames,
|
return (dryrun_results, unlintable_filenames, cant_lint_filenames,
|
||||||
broken_linter_names, unfindable_filenames)
|
broken_linter_names, unfindable_filenames)
|
||||||
|
|
||||||
results = runner(linters, lintable_filenames)
|
with runner(lintable_filenames):
|
||||||
|
results = linters()
|
||||||
|
|
||||||
return (results, unlintable_filenames, cant_lint_filenames,
|
return (results, unlintable_filenames, cant_lint_filenames,
|
||||||
broken_linter_names, unfindable_filenames)
|
broken_linter_names, unfindable_filenames)
|
||||||
|
|
|
@ -32,6 +32,8 @@ OPTIONS = [
|
||||||
_('Path to config file'), []),
|
_('Path to config file'), []),
|
||||||
('h', 'help', False,
|
('h', 'help', False,
|
||||||
_('This help message'), []),
|
_('This help message'), []),
|
||||||
|
('V', 'verbose', False,
|
||||||
|
_('A slightly more verbose output'), []),
|
||||||
('v', 'version', False,
|
('v', 'version', False,
|
||||||
_('Version information'), [])
|
_('Version information'), [])
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from .git_lint import load_config, run_linters, git_base
|
from .git_lint import load_config, run_linters, git_base
|
||||||
|
import operator
|
||||||
import gettext
|
import gettext
|
||||||
_ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
|
||||||
|
@ -30,22 +31,34 @@ def print_report(results, unlintable_filenames, cant_lint_filenames,
|
||||||
sort_position = 0
|
sort_position = 0
|
||||||
grouping = _('Filename: {}')
|
grouping = _('Filename: {}')
|
||||||
grouped_results = group_by(results, sort_position)
|
grouped_results = group_by(results, sort_position)
|
||||||
|
|
||||||
for group in grouped_results:
|
for group in grouped_results:
|
||||||
|
messages = reduce(operator.add, [item[3] for item in group[1]], [])
|
||||||
|
if len(messages) == 0:
|
||||||
|
continue
|
||||||
print(grouping.format(group[0]))
|
print(grouping.format(group[0]))
|
||||||
for (filename, lintername, returncode, text) in group[1]:
|
for (filename, lintername, returncode, text) in group[1]:
|
||||||
print('\n'.join(base_file_cleaner(text)))
|
if text:
|
||||||
print('')
|
print('\n'.join(base_file_cleaner(text)))
|
||||||
if len(broken_linter_names):
|
print('')
|
||||||
|
print ('')
|
||||||
|
|
||||||
|
if len(broken_linter_names) and (len(cant_lint_filenames) or ('verbose' in options)):
|
||||||
print(_('These linters could not be run:'), ','.join(broken_linter_names))
|
print(_('These linters could not be run:'), ','.join(broken_linter_names))
|
||||||
if len(cant_lint_filenames):
|
if len(cant_lint_filenames):
|
||||||
print(_('As a result, these files were not linted:'))
|
print(_('As a result, these files were not linted:'))
|
||||||
print('\n'.join([' {}'.format(f) for f in cant_lint_filenames]))
|
print('\n'.join([' {}'.format(f) for f in cant_lint_filenames]))
|
||||||
if len(unlintable_filenames):
|
print('')
|
||||||
|
|
||||||
|
if len(unlintable_filenames) and ('verbose' in options):
|
||||||
print(_('The following files had no recognizeable linters:'))
|
print(_('The following files had no recognizeable linters:'))
|
||||||
print('\n'.join([' {}'.format(f) for f in unlintable_filenames]))
|
print('\n'.join([' {}'.format(f) for f in unlintable_filenames]))
|
||||||
|
print('')
|
||||||
|
|
||||||
if len(unfindable_filenames):
|
if len(unfindable_filenames):
|
||||||
print(_('The following files could not be found:'))
|
print(_('The following files could not be found:'))
|
||||||
print('\n'.join([' {}'.format(f) for f in unfindable_filenames]))
|
print('\n'.join([' {}'.format(f) for f in unfindable_filenames]))
|
||||||
|
print('')
|
||||||
|
|
||||||
|
|
||||||
def print_help(options, name):
|
def print_help(options, name):
|
||||||
|
|
|
@ -102,6 +102,17 @@ def test_02_empty_repository():
|
||||||
assert stderr.startswith('No configuration file found,')
|
assert stderr.startswith('No configuration file found,')
|
||||||
|
|
||||||
|
|
||||||
|
# It should behave well when the repository is empty and we're
|
||||||
|
# running against staging.
|
||||||
|
|
||||||
|
def test_02b_empty_repository():
|
||||||
|
with gittemp() as path:
|
||||||
|
os.chdir(path)
|
||||||
|
shell('git init')
|
||||||
|
(stdout, stderr, rc) = fullshell('git lint -s')
|
||||||
|
assert stderr.startswith('No configuration file found,')
|
||||||
|
|
||||||
|
|
||||||
def test_03_simple_repository():
|
def test_03_simple_repository():
|
||||||
with gittemp() as path:
|
with gittemp() as path:
|
||||||
os.chdir(path)
|
os.chdir(path)
|
||||||
|
|
Loading…
Reference in New Issue