66import subprocess
77import collections
88import ConfigParser
9+ import contextlib
910
1011
1112ExecutionResult = collections .namedtuple (
@@ -32,6 +33,15 @@ def _current_commit():
3233 return 'HEAD'
3334
3435
36+ def _current_stash ():
37+ res = _execute ('git rev-parse -q --verify refs/stash' .split ())
38+ if res .status :
39+ # not really as meaningful for a stash, but makes some sense
40+ return '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
41+ else :
42+ return res .stdout
43+
44+
3545def _get_list_of_committed_files ():
3646 """ Returns a list of files about to be commited. """
3747 files = []
@@ -81,6 +91,41 @@ def _parse_score(pylint_output):
8191 return 0.0
8292
8393
94+ @contextlib .contextmanager
95+ def _stash_unstaged ():
96+ """Stashes any changes on entry and restores them on exit.
97+
98+ If there is no initial commit, print a warning and do nothing.
99+
100+ """
101+ if _current_commit () != 'HEAD' :
102+ # git stash doesn't work with no initial commit, so warn and do nothing
103+ print ('WARNING: unable to stash changes with no initial commit' )
104+ yield
105+ return
106+
107+ # git stash still returns 0 if there is nothing to stash,
108+ # so inspect the current stash before and after saving
109+ original_stash = _current_stash ()
110+ # leave a message marking the stash as ours in case something goes wrong
111+ # so that the user can work out what happened and fix things manually
112+ subprocess .check_call ('git stash save -q --keep-index '
113+ 'git-pylint-commit-hook' .split ())
114+ new_stash = _current_stash ()
115+
116+ try :
117+ # let the caller do whatever they wanted to do
118+ # (but we still want to restore the tree if an exception was thrown)
119+ yield
120+ finally :
121+ # only restore if we actually stashed something
122+ if original_stash != new_stash :
123+ # avoid merge issues
124+ subprocess .check_call ('git reset --hard -q' .split ())
125+ # restore everything to how it was
126+ subprocess .check_call ('git stash pop --index -q' .split ())
127+
128+
84129def check_repo (
85130 limit , pylint = 'pylint' , pylintrc = '.pylintrc' , pylint_params = None ,
86131 suppress_report = False ):
@@ -103,92 +148,95 @@ def check_repo(
103148 # Set the exit code
104149 all_filed_passed = True
105150
106- # Find Python files
107- for filename in _get_list_of_committed_files ():
108- try :
109- if _is_python_file (filename ):
110- python_files .append ((filename , None ))
111- except IOError :
112- print 'File not found (probably deleted): {}\t \t SKIPPED' .format (
113- filename )
114-
115- # Don't do anything if there are no Python files
116- if len (python_files ) == 0 :
117- sys .exit (0 )
118-
119- # Load any pre-commit-hooks options from a .pylintrc file (if there is one)
120- if os .path .exists (pylintrc ):
121- conf = ConfigParser .SafeConfigParser ()
122- conf .read (pylintrc )
123- if conf .has_option ('pre-commit-hook' , 'command' ):
124- pylint = conf .get ('pre-commit-hook' , 'command' )
125- if conf .has_option ('pre-commit-hook' , 'params' ):
126- pylint_params += ' ' + conf .get ('pre-commit-hook' , 'params' )
127- if conf .has_option ('pre-commit-hook' , 'limit' ):
128- limit = float (conf .get ('pre-commit-hook' , 'limit' ))
129-
130- # Pylint Python files
131- i = 1
132- for python_file , score in python_files :
133- # Allow __init__.py files to be completely empty
134- if os .path .basename (python_file ) == '__init__.py' :
135- if os .stat (python_file ).st_size == 0 :
136- print (
137- 'Skipping pylint on {} (empty __init__.py)..'
138- '\t SKIPPED' .format (python_file ))
139-
140- # Bump parsed files
141- i += 1
142- continue
143-
144- # Start pylinting
145- sys .stdout .write ("Running pylint on {} (file {}/{})..\t " .format (
146- python_file , i , len (python_files )))
147- sys .stdout .flush ()
148- try :
149- command = [pylint ]
150-
151- if pylint_params :
152- command += pylint_params .split ()
153- if '--rcfile' not in pylint_params :
151+ # Stash any unstaged changes while we look at the tree
152+ with _stash_unstaged ():
153+ # Find Python files
154+ for filename in _get_list_of_committed_files ():
155+ try :
156+ if _is_python_file (filename ):
157+ python_files .append ((filename , None ))
158+ except IOError :
159+ print 'File not found (probably deleted): {}\t \t SKIPPED' .format (
160+ filename )
161+
162+ # Don't do anything if there are no Python files
163+ if len (python_files ) == 0 :
164+ sys .exit (0 )
165+
166+ # Load any pre-commit-hooks options from a .pylintrc file
167+ # (if there is one)
168+ if os .path .exists (pylintrc ):
169+ conf = ConfigParser .SafeConfigParser ()
170+ conf .read (pylintrc )
171+ if conf .has_option ('pre-commit-hook' , 'command' ):
172+ pylint = conf .get ('pre-commit-hook' , 'command' )
173+ if conf .has_option ('pre-commit-hook' , 'params' ):
174+ pylint_params += ' ' + conf .get ('pre-commit-hook' , 'params' )
175+ if conf .has_option ('pre-commit-hook' , 'limit' ):
176+ limit = float (conf .get ('pre-commit-hook' , 'limit' ))
177+
178+ # Pylint Python files
179+ i = 1
180+ for python_file , score in python_files :
181+ # Allow __init__.py files to be completely empty
182+ if os .path .basename (python_file ) == '__init__.py' :
183+ if os .stat (python_file ).st_size == 0 :
184+ print (
185+ 'Skipping pylint on {} (empty __init__.py)..'
186+ '\t SKIPPED' .format (python_file ))
187+
188+ # Bump parsed files
189+ i += 1
190+ continue
191+
192+ # Start pylinting
193+ sys .stdout .write ("Running pylint on {} (file {}/{})..\t " .format (
194+ python_file , i , len (python_files )))
195+ sys .stdout .flush ()
196+ try :
197+ command = [pylint ]
198+
199+ if pylint_params :
200+ command += pylint_params .split ()
201+ if '--rcfile' not in pylint_params :
202+ command .append ('--rcfile={}' .format (pylintrc ))
203+ else :
154204 command .append ('--rcfile={}' .format (pylintrc ))
155- else :
156- command .append ('--rcfile={}' .format (pylintrc ))
157-
158-
159- command .append (python_file )
160-
161- proc = subprocess .Popen (
162- command ,
163- stdout = subprocess .PIPE ,
164- stderr = subprocess .PIPE )
165- out , _ = proc .communicate ()
166- except OSError :
167- print ("\n An error occurred. Is pylint installed?" )
168- sys .exit (1 )
169-
170- # Verify the score
171- score = _parse_score (out )
172- if score >= float (limit ):
173- status = 'PASSED'
174- else :
175- status = 'FAILED'
176- all_filed_passed = False
177-
178- # Add some output
179- print ('{:.2}/10.00\t {}' .format (decimal .Decimal (score ), status ))
180- if 'FAILED' in status :
181- if suppress_report :
182- command .append ('--reports=n' )
205+
206+
207+ command .append (python_file )
208+
183209 proc = subprocess .Popen (
184210 command ,
185211 stdout = subprocess .PIPE ,
186212 stderr = subprocess .PIPE )
187213 out , _ = proc .communicate ()
188- print out
189-
190-
191- # Bump parsed files
192- i += 1
214+ except OSError :
215+ print ("\n An error occurred. Is pylint installed?" )
216+ sys .exit (1 )
217+
218+ # Verify the score
219+ score = _parse_score (out )
220+ if score >= float (limit ):
221+ status = 'PASSED'
222+ else :
223+ status = 'FAILED'
224+ all_filed_passed = False
225+
226+ # Add some output
227+ print ('{:.2}/10.00\t {}' .format (decimal .Decimal (score ), status ))
228+ if 'FAILED' in status :
229+ if suppress_report :
230+ command .append ('--reports=n' )
231+ proc = subprocess .Popen (
232+ command ,
233+ stdout = subprocess .PIPE ,
234+ stderr = subprocess .PIPE )
235+ out , _ = proc .communicate ()
236+ print out
237+
238+
239+ # Bump parsed files
240+ i += 1
193241
194242 return all_filed_passed
0 commit comments