Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Commit 3f3d2bd

Browse files
committed
Invalidate cache if linter path or arguments change (#19)
1 parent 72897ab commit 3f3d2bd

File tree

3 files changed

+46
-29
lines changed

3 files changed

+46
-29
lines changed

gitlint/linters.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,14 @@ def missing_requirements_command(missing_programs, installation_string,
5555

5656

5757
# TODO(skreft): add test case for result already in cache.
58-
def lint_command(name, program, arguments, filter_regex, filename, lines):
58+
def lint_command(program, arguments, filter_regex, filename, lines):
5959
"""Executes a lint program and filter the output.
6060
6161
Executes the lint tool 'program' with arguments 'arguments' over the file
6262
'filename' returning only those lines matching the regular expression
6363
'filter_regex'.
6464
6565
Args:
66-
name: string: the name of the linter.
6766
program: string: lint program.
6867
arguments: list[string]: extra arguments for the program.
6968
filter_regex: string: regular expression to filter lines.
@@ -73,7 +72,8 @@ def lint_command(name, program, arguments, filter_regex, filename, lines):
7372
7473
Returns: dict: a dict with the extracted info from the message.
7574
"""
76-
output = utils.get_output_from_cache(name, filename)
75+
linter_hash = utils.calculate_hash(program, arguments)
76+
output = utils.get_output_from_cache(linter_hash, filename)
7777

7878
if output is None:
7979
call_arguments = [program] + arguments + [filename]
@@ -91,7 +91,7 @@ def lint_command(name, program, arguments, filter_regex, filename, lines):
9191
}
9292
}
9393
output = output.decode('utf-8')
94-
utils.save_output_in_cache(name, filename, output)
94+
utils.save_output_in_cache(linter_hash, filename, output)
9595

9696
output_lines = output.split(os.linesep)
9797

@@ -137,7 +137,7 @@ def parse_yaml_config(yaml_config, repo_home):
137137
'REPO_HOME': repo_home,
138138
}
139139

140-
for name, data in yaml_config.items():
140+
for _, data in yaml_config.items():
141141
command = _replace_variables([data['command']], variables)[0]
142142
requirements = _replace_variables(
143143
data.get('requirements', []), variables)
@@ -149,7 +149,7 @@ def parse_yaml_config(yaml_config, repo_home):
149149
linter_command = Partial(missing_requirements_command,
150150
not_found_programs, data['installation'])
151151
else:
152-
linter_command = Partial(lint_command, name, command, arguments,
152+
linter_command = Partial(lint_command, command, arguments,
153153
data['filter'])
154154
for extension in data['extensions']:
155155
config[extension].append(linter_command)

gitlint/utils.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414
"""Common function used across modules."""
1515

16+
import hashlib
1617
import io
1718
import os
1819
import re
@@ -72,29 +73,45 @@ def _open_for_write(filename):
7273
return io.open(filename, 'w')
7374

7475

75-
def _get_cache_filename(name, filename):
76-
"""Returns the cache location for filename and linter name."""
76+
def _get_cache_filename(linter_hash, filename):
77+
"""Returns the cache location for filename and linter hash."""
7778
filename = os.path.abspath(filename)[1:]
7879
home_folder = os.path.expanduser('~')
7980
base_cache_dir = os.path.join(home_folder, '.git-lint', 'cache')
8081

81-
return os.path.join(base_cache_dir, name, filename)
82+
return os.path.join(base_cache_dir, linter_hash, filename)
8283

8384

84-
def get_output_from_cache(name, filename):
85+
def calculate_hash(program, arguments):
86+
"""Calculate sha256 hash as hex string over program and arguments.
87+
88+
Args:
89+
program: string: lint program.
90+
arguments: list[string]: extra arguments for the program.
91+
92+
Returns: string: a string with the calculated sha256 hex value.
93+
"""
94+
algorithm = hashlib.sha256()
95+
algorithm.update(program)
96+
for argument in arguments:
97+
algorithm.update(argument)
98+
return algorithm.hexdigest()
99+
100+
101+
def get_output_from_cache(linter_hash, filename):
85102
"""Returns the output from the cache if still valid.
86103
87104
It checks that the cache file is defined and that its modification time is
88105
after the modification time of the original file.
89106
90107
Args:
91-
name: string: name of the linter.
108+
linter_hash: string: hash representing linter binary and arguments.
92109
filename: string: path of the filename for which we are retrieving the
93110
output.
94111
95112
Returns: a string with the output, if it is still valid, or None otherwise.
96113
"""
97-
cache_filename = _get_cache_filename(name, filename)
114+
cache_filename = _get_cache_filename(linter_hash, filename)
98115
if (os.path.exists(cache_filename)
99116
and os.path.getmtime(filename) < os.path.getmtime(cache_filename)):
100117
with io.open(cache_filename) as f:
@@ -103,14 +120,14 @@ def get_output_from_cache(name, filename):
103120
return None
104121

105122

106-
def save_output_in_cache(name, filename, output):
123+
def save_output_in_cache(linter_hash, filename, output):
107124
"""Saves output in the cache location.
108125
109126
Args:
110-
name: string: name of the linter.
127+
linter_hash: string: hash representing linter binary and arguments.
111128
filename: string: path of the filename for which we are saving the output.
112129
output: string: full output (not yet filetered) of the lint command.
113130
"""
114-
cache_filename = _get_cache_filename(name, filename)
131+
cache_filename = _get_cache_filename(linter_hash, filename)
115132
with _open_for_write(cache_filename) as f:
116133
f.write(output)

test/unittest/test_linters.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def test_lint_command_success(self):
3636
['Line 1:1: 1', 'Line 5:2: 5 ·', 'Line 7:3: 7',
3737
'Line 9:4: 9']).encode('utf-8')
3838
command = functools.partial(
39-
linters.lint_command, 'l', 'linter', ['-f', '--compact'],
39+
linters.lint_command, 'linter', ['-f', '--compact'],
4040
r'^Line (?P<line>{lines}):(?P<column>\d+): (?P<message>.*)$')
4141
filename = 'foo.txt'
4242
self.assertEqual({
@@ -97,7 +97,7 @@ def test_lint_command_all_fields(self):
9797
check_output.return_value = os.linesep.join(
9898
['ERROR: line 1, col 1: (W32) missing foo']).encode('utf-8')
9999
command = functools.partial(
100-
linters.lint_command, 'l', 'linter', ['-f', '--compact'],
100+
linters.lint_command, 'linter', ['-f', '--compact'],
101101
r'^(?P<severity>.*): line (?P<line>{lines})(, col ' +
102102
r'(?P<column>\d+)): \((?P<message_id>.*)\) (?P<message>.*)$')
103103
filename = 'foo.txt'
@@ -124,7 +124,7 @@ def test_lint_command_error(self):
124124
1, 'linter', output)) as check_output, \
125125
mock.patch('os.path.getmtime', side_effect=[1, 0, 1, 0]):
126126
command = functools.partial(
127-
linters.lint_command, 'l', 'linter', ['-f', '--compact'],
127+
linters.lint_command, 'linter', ['-f', '--compact'],
128128
'^Line (?P<line>{lines}): (?P<message>.*)$')
129129
filename = 'foo.txt'
130130
self.assertEqual({
@@ -177,7 +177,7 @@ def test_lint_command_not_found(self):
177177
with mock.patch('subprocess.check_output',
178178
side_effect=OSError('Not found')) as check_output, \
179179
mock.patch('os.path.getmtime', side_effect=[1, 0]):
180-
command = functools.partial(linters.lint_command, 'l', 'linter',
180+
command = functools.partial(linters.lint_command, 'linter',
181181
['-f', '--compact'],
182182
'^Line ({lines}):')
183183
filename = 'foo.txt'
@@ -194,10 +194,10 @@ def test_lint_command_not_found(self):
194194

195195
def test_lint(self):
196196
linter1 = functools.partial(
197-
linters.lint_command, 'l1', 'linter1', ['-f'],
197+
linters.lint_command, 'linter1', ['-f'],
198198
'^Line (?P<line>{lines}): (?P<message>.*)$')
199199
linter2 = functools.partial(
200-
linters.lint_command, 'l2', 'linter2', [],
200+
linters.lint_command, 'linter2', [],
201201
'^ line (?P<line>{lines}): (?P<message>.*)$')
202202
config = {'.txt': [linter1, linter2]}
203203
outputs = [
@@ -231,15 +231,15 @@ def test_lint(self):
231231

232232
def test_lint_output_is_sorted(self):
233233
linter1 = functools.partial(
234-
linters.lint_command, 'l1', 'linter1', ['-f'],
234+
linters.lint_command, 'linter1', ['-f'],
235235
'^Line (?P<line>{lines}): (?P<message>.*)$')
236236
linter2 = functools.partial(
237-
linters.lint_command, 'l2', 'linter2', [],
237+
linters.lint_command, 'linter2', [],
238238
'^ line (?P<line>{lines}): (?P<message>.*)$')
239-
linter3 = functools.partial(linters.lint_command, 'l3', 'linter3', [],
239+
linter3 = functools.partial(linters.lint_command, 'linter3', [],
240240
'^(?P<message>.*)$')
241241
linter4 = functools.partial(
242-
linters.lint_command, 'l4', 'linter4', [],
242+
linters.lint_command, 'linter4', [],
243243
r'^(?P<line>{lines}):(?P<column>\d+): (?P<message>.*)$')
244244
config = {'.txt': [linter1, linter2, linter3, linter4]}
245245
outputs = [
@@ -285,10 +285,10 @@ def test_lint_output_is_sorted(self):
285285

286286
def test_lint_one_empty_lint(self):
287287
linter1 = functools.partial(
288-
linters.lint_command, 'l1', 'linter1', ['-f'],
288+
linters.lint_command, 'linter1', ['-f'],
289289
'^Line (?P<line>{lines}): (?P<message>.*)$')
290290
linter2 = functools.partial(
291-
linters.lint_command, 'l2', 'linter2', [],
291+
linters.lint_command, 'linter2', [],
292292
'^ line (?P<line>{lines}): (?P<message>.*)$')
293293
config = {'.txt': [linter1, linter2]}
294294
outputs = [b'', os.linesep.join([' line 4: 4']).encode('utf-8')]
@@ -314,9 +314,9 @@ def test_lint_one_empty_lint(self):
314314
self.assertEqual(expected_calls, check_output.call_args_list)
315315

316316
def test_lint_all_empty_lint(self):
317-
linter1 = functools.partial(linters.lint_command, 'l1', 'linter1',
317+
linter1 = functools.partial(linters.lint_command, 'linter1',
318318
['-f'], '^Line {lines}:')
319-
linter2 = functools.partial(linters.lint_command, 'l2', 'linter2', [],
319+
linter2 = functools.partial(linters.lint_command, 'linter2', [],
320320
'^ line {lines}:')
321321
config = {'.txt': [linter1, linter2]}
322322
outputs = [b'', b'']

0 commit comments

Comments
 (0)