@@ -148,28 +148,49 @@ def get_commit_files():
148148
149149
150150def parse_diff_header (header_line ):
151- changed_lines = []
151+ '''Parse "git diff --unified=0" header lines to get changed line numbers
152+
153+ The line number is in relation to the file after the change.
154+
155+ :param header_line: A header in the git diff --unified=0 output
156+ :returns: a string that represents either a line number or a range
157+ '''
152158 match = parse_diff_header .pattern .match (header_line )
153159 start = int (match .group (1 ))
154160 if match .group (2 ):
155- for num in range ( int (match .group (3 ))):
156- changed_lines . append ( start + num )
161+ num = int (match .group (3 ))
162+ changed_lines = f' { start } - { start + num - 1 } '
157163 else :
158- changed_lines . append (start )
164+ changed_lines = str (start )
159165 return changed_lines
160166parse_diff_header .pattern = re .compile (r'^@@\s[^\s]+\s\+?(\d+)(,(\d+))?\s@@.*' )
161167
162168
163169class TestParseDiffHeaderPattern (unittest .TestCase ):
164170 def test_various_strings (self ):
165171 def _test (input , output ):
166- self .assertListEqual (output , parse_diff_header (input ))
167- _test ('@@ -142 +178,3 @@' , [178 , 179 , 180 ])
168- _test ('@@ -142 +178,7 @@' , [178 , 179 , 180 , 181 , 182 , 183 , 184 ])
172+ self .assertEqual (output , parse_diff_header (input ))
173+ _test ('@@ -142 +178 @@' , '178' )
174+ _test ('@@ -142 +178,3 @@' , '178-180' )
175+ _test ('@@ -142 +178,7 @@' , '178-184' )
169176
170177
171178def get_changed_lines (modified_file ):
172- '''New and modified lines in modified file in current commit'''
179+ '''New and modified lines in modified file in current commit
180+
181+ Depending on the context, the change is defined as (old -> new):
182+
183+ pull request: BASE -> HEAD (ie. main -> feature_branch)
184+ push: HEAD~ -> HEAD (ie. previous commit -> current commit)
185+ precommit: HEAD -> index (ie. current commit -> new commit)
186+
187+ The returned list contains line numbers of lines that have changed. The
188+ list can contain ranges. For example ['3','6-9','12'] means lines
189+ 3,6,7,8,9,12.
190+
191+ :param modified_file: The file which has changed
192+ :returns: A list of line number (integers and ranges) of changed lines
193+ '''
173194 if _is_github_event ():
174195 if _is_pull_request ():
175196 output = _get_output (
@@ -185,10 +206,30 @@ def get_changed_lines(modified_file):
185206 for line in output .splitlines ():
186207 if not line .startswith ('@@' ):
187208 continue
188- lines .extend (parse_diff_header (line ))
209+ lines .append (parse_diff_header (line ))
189210 return lines
190211
191212
213+ def yield_changed_lines (changed_lines ):
214+ '''Yield individual line numbers from list returned by get_changed_lines'''
215+ for line_num_range in changed_lines :
216+ if '-' in line_num_range :
217+ start , end = map (int , line_num_range .split ('-' ))
218+ else :
219+ start , end = int (line_num_range ), int (line_num_range )
220+ for line_num in range (start , end + 1 ):
221+ yield line_num
222+
223+
224+ class TestYieldChangedLines (unittest .TestCase ):
225+ def test_various_lists (self ):
226+ def _test (input , output ):
227+ self .assertListEqual (output , list (yield_changed_lines (input )))
228+ _test (['12' ], [12 ])
229+ _test (['12-15' ], [12 ,13 ,14 ,15 ])
230+ _test (['12-15' ,'44' ,'55-57' ], [12 ,13 ,14 ,15 ,44 ,55 ,56 ,57 ])
231+
232+
192233def get_config_setting (setting ):
193234 '''Get the value of a config setting'''
194235 try :
@@ -241,11 +282,11 @@ def check_do_not_merge_in_file(filename, new_file=False):
241282 lines = fileobj .read ().decode ().splitlines (True )
242283
243284 if new_file :
244- line_nums = range ( 1 , len (lines )+ 1 )
285+ line_nums = [ f'1- { len (lines )} ' ]
245286 else :
246287 line_nums = get_changed_lines (filename )
247288
248- for line_num in line_nums :
289+ for line_num in yield_changed_lines ( line_nums ) :
249290 try :
250291 line = lines [line_num - 1 ]
251292 except IndexError as exc :
@@ -314,14 +355,14 @@ def trim_trailing_whitespace_in_file(filename, new_file, in_place):
314355 return
315356
316357 if new_file :
317- line_nums = range ( 1 , len (lines )+ 1 )
358+ line_nums = [ f'1- { len (lines )} ' ]
318359 else :
319360 line_nums = get_changed_lines (filename )
320361
321362 modified_file = False # if trimming white space in place
322363 modified_lines = [] # if flagging instead of trimming in place
323364
324- for line_num in line_nums :
365+ for line_num in yield_changed_lines ( line_nums ) :
325366 try :
326367 before = lines [line_num - 1 ]
327368 except IndexError as exc :
0 commit comments