Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(433)

Side by Side Diff: presubmit_support.py

Issue 6461011: Modify presubmit checks to work on changed lines only. (Closed)
Patch Set: Fix more presubmit errors. Created 9 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tests/presubmit_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2010 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Enables directory-specific presubmit checks to run at upload and/or commit. 6 """Enables directory-specific presubmit checks to run at upload and/or commit.
7 """ 7 """
8 8
9 __version__ = '1.3.5' 9 __version__ = '1.3.5'
10 10
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
79 79
80 def PromptYesNo(input_stream, output_stream, prompt): 80 def PromptYesNo(input_stream, output_stream, prompt):
81 output_stream.write(prompt) 81 output_stream.write(prompt)
82 response = input_stream.readline().strip().lower() 82 response = input_stream.readline().strip().lower()
83 return response == 'y' or response == 'yes' 83 return response == 'y' or response == 'yes'
84 84
85 85
86 def _RightHandSideLinesImpl(affected_files): 86 def _RightHandSideLinesImpl(affected_files):
87 """Implements RightHandSideLines for InputApi and GclChange.""" 87 """Implements RightHandSideLines for InputApi and GclChange."""
88 for af in affected_files: 88 for af in affected_files:
89 lines = af.NewContents() 89 lines = af.ChangedContents()
90 line_number = 0
91 for line in lines: 90 for line in lines:
92 line_number += 1 91 yield (af, line[0], line[1])
93 yield (af, line_number, line)
94 92
95 93
96 class OutputApi(object): 94 class OutputApi(object):
97 """This class (more like a module) gets passed to presubmit scripts so that 95 """This class (more like a module) gets passed to presubmit scripts so that
98 they can specify various types of results. 96 they can specify various types of results.
99 """ 97 """
100 # Method could be a function 98 # Method could be a function
101 # pylint: disable=R0201 99 # pylint: disable=R0201
102 class PresubmitResult(object): 100 class PresubmitResult(object):
103 """Base class for result objects.""" 101 """Base class for result objects."""
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
181 know stuff about the change they're looking at. 179 know stuff about the change they're looking at.
182 """ 180 """
183 # Method could be a function 181 # Method could be a function
184 # pylint: disable=R0201 182 # pylint: disable=R0201
185 183
186 # File extensions that are considered source files from a style guide 184 # File extensions that are considered source files from a style guide
187 # perspective. Don't modify this list from a presubmit script! 185 # perspective. Don't modify this list from a presubmit script!
188 DEFAULT_WHITE_LIST = ( 186 DEFAULT_WHITE_LIST = (
189 # C++ and friends 187 # C++ and friends
190 r".*\.c$", r".*\.cc$", r".*\.cpp$", r".*\.h$", r".*\.m$", r".*\.mm$", 188 r".*\.c$", r".*\.cc$", r".*\.cpp$", r".*\.h$", r".*\.m$", r".*\.mm$",
191 r".*\.inl$", r".*\.asm$", r".*\.hxx$", r".*\.hpp$", 189 r".*\.inl$", r".*\.asm$", r".*\.hxx$", r".*\.hpp$", r".*\.s$", r".*\.S$",
192 # Scripts 190 # Scripts
193 r".*\.js$", r".*\.py$", r".*\.sh$", r".*\.rb$", r".*\.pl$", r".*\.pm$", 191 r".*\.js$", r".*\.py$", r".*\.sh$", r".*\.rb$", r".*\.pl$", r".*\.pm$",
194 # No extension at all, note that ALL CAPS files are black listed in 192 # No extension at all, note that ALL CAPS files are black listed in
195 # DEFAULT_BLACK_LIST below. 193 # DEFAULT_BLACK_LIST below.
196 r"(^|.*?[\\\/])[^.]+$", 194 r"(^|.*?[\\\/])[^.]+$",
197 # Other 195 # Other
198 r".*\.java$", r".*\.mk$", r".*\.am$", 196 r".*\.java$", r".*\.mk$", r".*\.am$",
199 ) 197 )
200 198
201 # Path regexp that should be excluded from being considered containing source 199 # Path regexp that should be excluded from being considered containing source
202 # files. Don't modify this list from a presubmit script! 200 # files. Don't modify this list from a presubmit script!
203 DEFAULT_BLACK_LIST = ( 201 DEFAULT_BLACK_LIST = (
204 r".*\bexperimental[\\\/].*", 202 r".*\bexperimental[\\\/].*",
205 r".*\bthird_party[\\\/].*", 203 r".*\bthird_party[\\\/].*",
206 # Output directories (just in case) 204 # Output directories (just in case)
207 r".*\bDebug[\\\/].*", 205 r".*\bDebug[\\\/].*",
208 r".*\bRelease[\\\/].*", 206 r".*\bRelease[\\\/].*",
209 r".*\bxcodebuild[\\\/].*", 207 r".*\bxcodebuild[\\\/].*",
210 r".*\bsconsbuild[\\\/].*", 208 r".*\bsconsbuild[\\\/].*",
211 # All caps files like README and LICENCE. 209 # All caps files like README and LICENCE.
212 r".*\b[A-Z0-9_]+$", 210 r".*\b[A-Z0-9_]{2,}$",
213 # SCM (can happen in dual SCM configuration). (Slightly over aggressive) 211 # SCM (can happen in dual SCM configuration). (Slightly over aggressive)
214 r"(|.*[\\\/])\.git[\\\/].*", 212 r"(|.*[\\\/])\.git[\\\/].*",
215 r"(|.*[\\\/])\.svn[\\\/].*", 213 r"(|.*[\\\/])\.svn[\\\/].*",
216 ) 214 )
217 215
218 def __init__(self, change, presubmit_path, is_committing): 216 def __init__(self, change, presubmit_path, is_committing):
219 """Builds an InputApi object. 217 """Builds an InputApi object.
220 218
221 Args: 219 Args:
222 change: A presubmit.Change object. 220 change: A presubmit.Change object.
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after
338 336
339 If white_list or black_list is None, InputApi.DEFAULT_WHITE_LIST 337 If white_list or black_list is None, InputApi.DEFAULT_WHITE_LIST
340 and InputApi.DEFAULT_BLACK_LIST is used respectively. 338 and InputApi.DEFAULT_BLACK_LIST is used respectively.
341 339
342 The lists will be compiled as regular expression and 340 The lists will be compiled as regular expression and
343 AffectedFile.LocalPath() needs to pass both list. 341 AffectedFile.LocalPath() needs to pass both list.
344 342
345 Note: Copy-paste this function to suit your needs or use a lambda function. 343 Note: Copy-paste this function to suit your needs or use a lambda function.
346 """ 344 """
347 def Find(affected_file, items): 345 def Find(affected_file, items):
346 local_path = affected_file.LocalPath()
348 for item in items: 347 for item in items:
349 local_path = affected_file.LocalPath()
350 if self.re.match(item, local_path): 348 if self.re.match(item, local_path):
351 logging.debug("%s matched %s" % (item, local_path)) 349 logging.debug("%s matched %s" % (item, local_path))
352 return True 350 return True
353 return False 351 return False
354 return (Find(affected_file, white_list or self.DEFAULT_WHITE_LIST) and 352 return (Find(affected_file, white_list or self.DEFAULT_WHITE_LIST) and
355 not Find(affected_file, black_list or self.DEFAULT_BLACK_LIST)) 353 not Find(affected_file, black_list or self.DEFAULT_BLACK_LIST))
356 354
357 def AffectedSourceFiles(self, source_file): 355 def AffectedSourceFiles(self, source_file):
358 """Filter the list of AffectedTextFiles by the function source_file. 356 """Filter the list of AffectedTextFiles by the function source_file.
359 357
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after
474 472
475 def OldFileTempPath(self): 473 def OldFileTempPath(self):
476 """Returns the path on local disk where the old contents resides. 474 """Returns the path on local disk where the old contents resides.
477 475
478 The old version is the file in depot, i.e. the "left hand side". 476 The old version is the file in depot, i.e. the "left hand side".
479 This is a read-only cached copy of the old contents. *DO NOT* try to 477 This is a read-only cached copy of the old contents. *DO NOT* try to
480 modify this file. 478 modify this file.
481 """ 479 """
482 raise NotImplementedError() # Implement if/when needed. 480 raise NotImplementedError() # Implement if/when needed.
483 481
482 def ChangedContents(self):
483 """Returns a list of tuples (line number, line text) of all new lines.
484
485 This relies on the scm diff output describing each changed code section
486 with a line of the form
487
488 ^@@ <old line num>,<old size> <new line num>,<new size> @@$
489 """
490 new_lines = []
491 line_num = 0
492
493 if self.IsDirectory():
494 return []
495
496 for line in self.GenerateScmDiff().splitlines():
497 m = re.match(r'^@@ [0-9\,\+\-]+ \+([0-9]+)\,[0-9]+ @@', line)
498 if m:
499 line_num = int(m.groups(1)[0])
500 continue
501 if line.startswith('+') and not line.startswith('++'):
502 new_lines.append((line_num, line[1:]))
503 if not line.startswith('-'):
504 line_num += 1
505 return new_lines
506
484 def __str__(self): 507 def __str__(self):
485 return self.LocalPath() 508 return self.LocalPath()
486 509
510 def GenerateScmDiff(self):
511 raise NotImplementedError() # Implemented in derived classes.
487 512
488 class SvnAffectedFile(AffectedFile): 513 class SvnAffectedFile(AffectedFile):
489 """Representation of a file in a change out of a Subversion checkout.""" 514 """Representation of a file in a change out of a Subversion checkout."""
490 # Method 'NNN' is abstract in class 'NNN' but is not overridden 515 # Method 'NNN' is abstract in class 'NNN' but is not overridden
491 # pylint: disable=W0223 516 # pylint: disable=W0223
492 517
493 def __init__(self, *args, **kwargs): 518 def __init__(self, *args, **kwargs):
494 AffectedFile.__init__(self, *args, **kwargs) 519 AffectedFile.__init__(self, *args, **kwargs)
495 self._server_path = None 520 self._server_path = None
496 self._is_text_file = None 521 self._is_text_file = None
(...skipping 28 matching lines...) Expand all
525 # A deleted file is not a text file. 550 # A deleted file is not a text file.
526 self._is_text_file = False 551 self._is_text_file = False
527 elif self.IsDirectory(): 552 elif self.IsDirectory():
528 self._is_text_file = False 553 self._is_text_file = False
529 else: 554 else:
530 mime_type = scm.SVN.GetFileProperty(self.AbsoluteLocalPath(), 555 mime_type = scm.SVN.GetFileProperty(self.AbsoluteLocalPath(),
531 'svn:mime-type') 556 'svn:mime-type')
532 self._is_text_file = (not mime_type or mime_type.startswith('text/')) 557 self._is_text_file = (not mime_type or mime_type.startswith('text/'))
533 return self._is_text_file 558 return self._is_text_file
534 559
560 def GenerateScmDiff(self):
561 return scm.SVN.GenerateDiff(self.AbsoluteLocalPath())
535 562
536 class GitAffectedFile(AffectedFile): 563 class GitAffectedFile(AffectedFile):
537 """Representation of a file in a change out of a git checkout.""" 564 """Representation of a file in a change out of a git checkout."""
538 # Method 'NNN' is abstract in class 'NNN' but is not overridden 565 # Method 'NNN' is abstract in class 'NNN' but is not overridden
539 # pylint: disable=W0223 566 # pylint: disable=W0223
540 567
541 def __init__(self, *args, **kwargs): 568 def __init__(self, *args, **kwargs):
542 AffectedFile.__init__(self, *args, **kwargs) 569 AffectedFile.__init__(self, *args, **kwargs)
543 self._server_path = None 570 self._server_path = None
544 self._is_text_file = None 571 self._is_text_file = None
(...skipping 25 matching lines...) Expand all
570 if self.Action() == 'D': 597 if self.Action() == 'D':
571 # A deleted file is not a text file. 598 # A deleted file is not a text file.
572 self._is_text_file = False 599 self._is_text_file = False
573 elif self.IsDirectory(): 600 elif self.IsDirectory():
574 self._is_text_file = False 601 self._is_text_file = False
575 else: 602 else:
576 # raise NotImplementedException() # TODO(maruel) Implement. 603 # raise NotImplementedException() # TODO(maruel) Implement.
577 self._is_text_file = os.path.isfile(self.AbsoluteLocalPath()) 604 self._is_text_file = os.path.isfile(self.AbsoluteLocalPath())
578 return self._is_text_file 605 return self._is_text_file
579 606
607 def GenerateScmDiff(self):
608 return scm.GIT.GenerateDiff(self._local_root, files=[self.LocalPath(),])
580 609
581 class Change(object): 610 class Change(object):
582 """Describe a change. 611 """Describe a change.
583 612
584 Used directly by the presubmit scripts to query the current change being 613 Used directly by the presubmit scripts to query the current change being
585 tested. 614 tested.
586 615
587 Instance members: 616 Instance members:
588 tags: Dictionnary of KEY=VALUE pairs found in the change description. 617 tags: Dictionnary of KEY=VALUE pairs found in the change description.
589 self.KEY: equivalent to tags['KEY'] 618 self.KEY: equivalent to tags['KEY']
(...skipping 538 matching lines...) Expand 10 before | Expand all | Expand 10 after
1128 options.commit, 1157 options.commit,
1129 options.verbose, 1158 options.verbose,
1130 sys.stdout, 1159 sys.stdout,
1131 sys.stdin, 1160 sys.stdin,
1132 options.default_presubmit, 1161 options.default_presubmit,
1133 options.may_prompt) 1162 options.may_prompt)
1134 1163
1135 1164
1136 if __name__ == '__main__': 1165 if __name__ == '__main__':
1137 sys.exit(Main(sys.argv)) 1166 sys.exit(Main(sys.argv))
OLDNEW
« no previous file with comments | « no previous file | tests/presubmit_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698