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

Commit e5c0786

Browse files
committed
Allow git-lint to check branches
This modified the behaviour (for Git only) to accept --branch specifier which will allow users to get linting diffs between branches.Useful for testing after commits and PRs have been made. This also now gives the ability (for git) to test arbitrary commit distances apart (although that UI is not implemented here)
1 parent 34d738e commit e5c0786

File tree

8 files changed

+172
-103
lines changed

8 files changed

+172
-103
lines changed

README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,11 @@ Contributors
262262
Changelog
263263
=========
264264

265+
v0.0.9 (2016-06-12)
266+
-------------------
267+
268+
* Add support for comparing branches with --branch=<BRANCH>
269+
265270
v0.0.8 (2015-10-14)
266271
-------------------
267272

gitlint/__init__.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,20 @@
2424
Usage:
2525
git-lint [-f | --force] [--json] [--last-commit] [FILENAME ...]
2626
git-lint [-t | --tracked] [-f | --force] [--json] [--last-commit]
27+
git-lint --branch=<NAME>
2728
git-lint -h | --version
2829
2930
Options:
30-
-h Show the usage patterns.
31-
--version Prints the version number.
32-
-f --force Shows all the lines with problems.
33-
-t --tracked Lints only tracked files.
34-
--json Prints the result as a json string. Useful to use it in
35-
conjunction with other tools.
36-
--last-commit Checks the last checked-out commit. This is mostly useful
37-
when used as: git checkout <revid>; git lint --last-commit.
31+
-h Show the usage patterns.
32+
--version Prints the version number.
33+
-f --force Shows all the lines with problems.
34+
-t --tracked Lints only tracked files.
35+
--json Prints the result as a json string. Useful to use it in
36+
conjunction with other tools.
37+
--last-commit Checks the last checked-out commit. This is mostly useful
38+
when used as: git checkout <revid>; git lint --last-commit.
39+
--branch=<NAME> Checks the diff of the current HEAD against target branch
40+
and lints all differences (git only)
3841
"""
3942

4043
from __future__ import unicode_literals
@@ -54,7 +57,7 @@
5457
import gitlint.linters as linters
5558

5659

57-
__VERSION__ = '0.0.6.1'
60+
__VERSION__ = '0.0.9.0'
5861

5962
ERROR = termcolor.colored('ERROR', 'red', attrs=('bold',))
6063
SKIPPED = termcolor.colored('SKIPPED', 'yellow', attrs=('bold',))
@@ -186,9 +189,12 @@ def main(argv, stdout=sys.stdout, stderr=sys.stderr):
186189
stderr.write('fatal: Not a git repository' + linesep)
187190
return 128
188191

189-
commit = None
192+
commit_a, commit_b = None, None
190193
if arguments['--last-commit']:
191-
commit = vcs.last_commit()
194+
commit_a, commit_b = vcs.last_commit()
195+
196+
if arguments['--branch']:
197+
commit_a, commit_b = vcs.branch_commit(arguments['--branch'])
192198

193199
if arguments['FILENAME']:
194200
invalid_filenames = find_invalid_filenames(arguments['FILENAME'],
@@ -201,7 +207,8 @@ def main(argv, stdout=sys.stdout, stderr=sys.stderr):
201207

202208
changed_files = vcs.modified_files(repository_root,
203209
tracked_only=arguments['--tracked'],
204-
commit=commit)
210+
commit_a=commit_a,
211+
commit_b=commit_b)
205212
modified_files = {}
206213
for filename in arguments['FILENAME']:
207214
normalized_filename = os.path.abspath(filename)
@@ -210,7 +217,8 @@ def main(argv, stdout=sys.stdout, stderr=sys.stderr):
210217
else:
211218
modified_files = vcs.modified_files(repository_root,
212219
tracked_only=arguments['--tracked'],
213-
commit=commit)
220+
commit_a=commit_a,
221+
commit_b=commit_b)
214222

215223
linter_not_found = False
216224
files_with_problems = 0
@@ -228,7 +236,8 @@ def main(argv, stdout=sys.stdout, stderr=sys.stderr):
228236
else:
229237
modified_lines = vcs.modified_lines(filename,
230238
modified_files[filename],
231-
commit=commit)
239+
commit_a=commit_a,
240+
commit_b=commit_b)
232241

233242
result = linters.lint(
234243
filename, modified_lines, gitlint_config)

gitlint/git.py

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,46 @@
1515

1616
import os.path
1717
import subprocess
18+
import itertools
19+
import re
1820

1921
import gitlint.utils as utils
2022

2123

24+
def rev_parse(commit):
25+
"""Returns a SHA1 of a commit or banch"""
26+
args = ['git', 'rev-parse', commit]
27+
return (
28+
subprocess
29+
.check_output(args, stderr=subprocess.STDOUT)
30+
.strip()
31+
.decode('utf-8')
32+
)
33+
34+
2235
def repository_root():
2336
"""Returns the root of the repository as an absolute path."""
2437
try:
25-
root = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'],
26-
stderr=subprocess.STDOUT).strip()
27-
# Convert to unicode first
28-
return root.decode('utf-8')
38+
return rev_parse('--show-toplevel')
2939
except subprocess.CalledProcessError:
3040
return None
3141

3242

3343
def last_commit():
34-
"""Returns the SHA1 of the last commit."""
44+
"""Returns the SHA1 of the last 2 commits."""
3545
try:
36-
root = subprocess.check_output(['git', 'rev-parse', 'HEAD'],
37-
stderr=subprocess.STDOUT).strip()
38-
# Convert to unicode first
39-
return root.decode('utf-8')
46+
return rev_parse('HEAD'), rev_parse('HEAD~1')
4047
except subprocess.CalledProcessError:
41-
return None
48+
return None, None
4249

4350

51+
def branch_commit(branch):
52+
""""Returns the SHA1 of the last commit in HEAD and target branch"""
53+
try:
54+
return rev_parse('HEAD'), rev_parse(branch)
55+
except subprocess.CalledProcessError:
56+
return None
57+
4458
def _remove_filename_quotes(filename):
4559
"""Removes the quotes from a filename returned by git status."""
4660
if filename.startswith('"') and filename.endswith('"'):
@@ -49,7 +63,7 @@ def _remove_filename_quotes(filename):
4963
return filename
5064

5165

52-
def modified_files(root, tracked_only=False, commit=None):
66+
def modified_files(root, tracked_only=False, commit_a=None, commit_b=None):
5367
"""Returns a list of files that has been modified since the last commit.
5468
5569
Args:
@@ -64,8 +78,8 @@ def modified_files(root, tracked_only=False, commit=None):
6478
"""
6579
assert os.path.isabs(root), "Root has to be absolute, got: %s" % root
6680

67-
if commit:
68-
return _modified_files_with_commit(root, commit)
81+
if commit_a:
82+
return _modified_files_with_commit(root, commit_a, commit_b)
6983

7084
# Convert to unicode and split
7185
status_lines = subprocess.check_output([
@@ -86,11 +100,11 @@ def modified_files(root, tracked_only=False, commit=None):
86100
for filename, mode in modified_file_status)
87101

88102

89-
def _modified_files_with_commit(root, commit):
103+
def _modified_files_with_commit(root, commit_a, commit_b):
90104
# Convert to unicode and split
91105
status_lines = subprocess.check_output(
92106
['git', 'diff-tree', '-r', '--root', '--no-commit-id', '--name-status',
93-
commit]).decode('utf-8').split(os.linesep)
107+
commit_a, commit_b]).decode('utf-8').split(os.linesep)
94108

95109
modified_file_status = utils.filter_lines(
96110
status_lines,
@@ -103,37 +117,59 @@ def _modified_files_with_commit(root, commit):
103117
mode + ' ') for filename, mode in modified_file_status)
104118

105119

106-
def modified_lines(filename, extra_data, commit=None):
120+
def modified_lines(filename, extra_data, commit_a=None, commit_b=None):
107121
"""Returns the lines that have been modifed for this file.
108122
109123
Args:
110124
filename: the file to check.
111125
extra_data: is the extra_data returned by modified_files. Additionally, a
112126
value of None means that the file was not modified.
113-
commit: the complete sha1 (40 chars) of the commit. Note that specifying
114-
this value will only work (100%) when commit == last_commit (with
115-
respect to the currently checked out revision), otherwise, we could miss
116-
some lines.
127+
commit_a: the complete sha1 (40 chars) of the latest commit.
128+
commit_b: the complete sha1 (40 chars) of the oldest commit.
117129
118-
Returns: a list of lines that were modified, or None in case all lines are
119-
new.
130+
Returns: a list of line numbers that were modified, or None in case all
131+
lines are new.
120132
"""
121133
if extra_data is None:
122134
return []
135+
123136
if extra_data not in ('M ', ' M', 'MM'):
124137
return None
125138

126-
if commit is None:
127-
commit = '0' * 40
128-
commit = commit.encode('utf-8')
139+
args = ['git', 'diff', '-U0']
129140

130-
# Split as bytes, as the output may have some non unicode characters.
131-
blame_lines = subprocess.check_output(
132-
['git', 'blame', '--porcelain', filename]).split(
133-
os.linesep.encode('utf-8'))
134-
modified_line_numbers = utils.filter_lines(
135-
blame_lines,
136-
commit + br' (?P<line>\d+) (\d+)',
137-
groups=('line',))
141+
if commit_a is not None and commit_b is not None:
142+
args += [commit_b, commit_a]
138143

139-
return list(map(int, modified_line_numbers))
144+
args += ['--', filename]
145+
146+
try:
147+
diff = subprocess.check_output(args).split(os.linesep.encode('utf-8'))
148+
return format_lines(filter_diff(diff))
149+
except:
150+
return []
151+
152+
153+
def filter_diff(lines):
154+
matcher = re.compile('@@ (-[0-9]+(,[0-9]+)?)? ?(\+[0-9]+(,[0-9]+)?) @@')
155+
for l in lines:
156+
m = re.match(matcher, l.decode('utf-8'))
157+
if m and m.group(3):
158+
yield m.group(3)
159+
160+
161+
def format_lines(lines):
162+
"""Formats a series of git diff line numbers
163+
164+
Args:
165+
lines: a list of lines from git-diff specifying changed line numbers
166+
167+
Returns: a list of line numbers that have been modified
168+
"""
169+
lines = (l.strip('+').split(',') for l in lines if l)
170+
for l in lines:
171+
if (len(l) > 1):
172+
for i in range(int(l[0]), int(l[1])+int(l[0])):
173+
yield i
174+
else:
175+
yield int(l[0])

gitlint/hg.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,16 @@ def last_commit():
3636
root = subprocess.check_output(['hg', 'parent', '--template={node}'],
3737
stderr=subprocess.STDOUT).strip()
3838
# Convert to unicode first
39-
return root.decode('utf-8')
39+
return root.decode('utf-8'), None
4040
except subprocess.CalledProcessError:
41-
return None
41+
return None, None
42+
43+
44+
def branch_commit(branch):
45+
raise NotImplementedError('Branch commits are not implemented for hg')
4246

4347

44-
def modified_files(root, tracked_only=False, commit=None):
48+
def modified_files(root, tracked_only=False, commit_a=None, commit_b=None):
4549
"""Returns a list of files that has been modified since the last commit.
4650
4751
Args:
@@ -57,8 +61,8 @@ def modified_files(root, tracked_only=False, commit=None):
5761
assert os.path.isabs(root), "Root has to be absolute, got: %s" % root
5862

5963
command = ['hg', 'status']
60-
if commit:
61-
command.append('--change=%s' % commit)
64+
if commit_a:
65+
command.append('--change=%s' % commit_a)
6266

6367
# Convert to unicode and split
6468
status_lines = subprocess.check_output(
@@ -78,7 +82,7 @@ def modified_files(root, tracked_only=False, commit=None):
7882
for filename, mode in modified_file_status)
7983

8084

81-
def modified_lines(filename, extra_data, commit=None):
85+
def modified_lines(filename, extra_data, commit_a=None, commit_b=None):
8286
"""Returns the lines that have been modifed for this file.
8387
8488
Args:
@@ -99,8 +103,8 @@ def modified_lines(filename, extra_data, commit=None):
99103
return None
100104

101105
command = ['hg', 'diff', '-U', '0']
102-
if commit:
103-
command.append('--change=%s' % commit)
106+
if commit_a:
107+
command.append('--change=%s' % commit_a)
104108
command.append(filename)
105109

106110
# Split as bytes, as the output may have some non unicode characters.

scripts/git-lint

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ import gitlint
2020
try:
2121
sys.exit(gitlint.main(sys.argv))
2222
except KeyboardInterrupt:
23-
sys.exit(128)
23+
sys.exit(128)

0 commit comments

Comments
 (0)