Skip to content

Commit

Permalink
Fixed reportage matrix.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
elfsternberg committed Oct 3, 2016
1 parent aa4b5aa commit 13b0378
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 37 deletions.
57 changes: 44 additions & 13 deletions README.rst
@@ -1,23 +1,49 @@
===============================
Git Lint
Git Lint: README
===============================

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

**Git Lint** runs a configurable set of syntax, style, and complexity
checkers against changed files in your current working directory or
staging area. It can be configured to work with any `lint` command.
Some commands may require shell wrappers.
staging area. It can be configured to work with any `lint`-like
command. Some commands may require shell wrappers.

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
most of us use git. Having a centralized repository for what we want
checked and how we want it checked, associated with git (and by
extension, as a pre-commit hook), that can be run at any time for any
reason.
grunt, Make, CMake, or whatever, the fact is we all use a VCS, and most
of us use git. Having a centralized repository for what we want checked
and how we want it checked, associated with git (and by extension, as a
pre-commit hook), that can be run at any time for any 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
--------
Expand All @@ -37,11 +63,16 @@ Features
restoration of your workspace, it ensures the timestamps are the
same, so as not to confuse your build system or IDE.

Credits
-------

The completion of this project was graciously sponsored by my employer,
Splunk <http://splunk.com>.
Acknowledgements
----------------
`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
----------
Expand Down
43 changes: 23 additions & 20 deletions git_lint/git_lint.py
Expand Up @@ -318,33 +318,33 @@ def all_list():
# |___/ |___/ |_| |_|


class Runner:
def __init__(self, options):
self.runner = ('staging' in options and Runner.staging_wrapper) or Runner.workspace_wrapper

def __call__(self, run_linters, filenames):
return self.runner(run_linters, filenames)
class StagingRunner:
def __init__(self, filenames):
self.filenames = filenames

@staticmethod
def staging_wrapper(run_linters, filenames):
def __enter__(self):
def time_gather(f):
stats = os.stat(f)
return (f, (stats.st_atime, stats.st_mtime))

times = [time_gather(file) for file in filenames]
self.times = [time_gather(filename) for filename in self.filenames]
run_git_command(['stash', '--keep-index'])

results = run_linters()
def __exit__(self, type, value, traceback):
run_git_command(['reset', '--hard'])
run_git_command(['stash', 'pop', '--quiet', '--index'])

for (filename, timepair) in times:
for (filename, timepair) in self.times:
os.utime(filename, timepair)
return results

@staticmethod
def workspace_wrapper(run_linters, filenames):
return run_linters()

class WorkspaceRunner(object):
def __init__(self, filenames):
pass

def __enter__(self):
pass

def __exit__(self, type, value, traceback):
pass


# ___ _ _ _
Expand All @@ -368,7 +368,7 @@ def run_external_linter(filename, linter, linter_name):
"""Run one linter against one 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 + '"'
Expand Down Expand Up @@ -444,7 +444,9 @@ def build_config_subset(keys):
cant_lint_filenames = [filename for filename in lintable_filenames
if cant_lint_filter(filename)]

runner = Runner(options)
runner = WorkspaceRunner
if 'staging' in options:
runner = StagingRunner

linters = Linters(build_config_subset(working_linter_names),
sorted(lintable_filenames))
Expand All @@ -454,7 +456,8 @@ def build_config_subset(keys):
return (dryrun_results, unlintable_filenames, cant_lint_filenames,
broken_linter_names, unfindable_filenames)

results = runner(linters, lintable_filenames)
with runner(lintable_filenames):
results = linters()

return (results, unlintable_filenames, cant_lint_filenames,
broken_linter_names, unfindable_filenames)
2 changes: 2 additions & 0 deletions git_lint/options.py
Expand Up @@ -32,6 +32,8 @@
_('Path to config file'), []),
('h', 'help', False,
_('This help message'), []),
('V', 'verbose', False,
_('A slightly more verbose output'), []),
('v', 'version', False,
_('Version information'), [])
]
21 changes: 17 additions & 4 deletions git_lint/reporters.py
@@ -1,5 +1,6 @@
from __future__ import print_function
from .git_lint import load_config, run_linters, git_base
import operator
import gettext
_ = gettext.gettext

Expand Down Expand Up @@ -30,22 +31,34 @@ def print_report(results, unlintable_filenames, cant_lint_filenames,
sort_position = 0
grouping = _('Filename: {}')
grouped_results = group_by(results, sort_position)

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]))
for (filename, lintername, returncode, text) in group[1]:
print('\n'.join(base_file_cleaner(text)))
print('')
if len(broken_linter_names):
if text:
print('\n'.join(base_file_cleaner(text)))
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))
if len(cant_lint_filenames):
print(_('As a result, these files were not linted:'))
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('\n'.join([' {}'.format(f) for f in unlintable_filenames]))
print('')

if len(unfindable_filenames):
print(_('The following files could not be found:'))
print('\n'.join([' {}'.format(f) for f in unfindable_filenames]))
print('')


def print_help(options, name):
Expand Down
11 changes: 11 additions & 0 deletions tests/test_git_lint.py
Expand Up @@ -102,6 +102,17 @@ def test_02_empty_repository():
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():
with gittemp() as path:
os.chdir(path)
Expand Down

0 comments on commit 13b0378

Please sign in to comment.