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

Side by Side Diff: presubmit_support.py

Issue 155489: Add a presubmit check for accidental checkins of files under a SVN modified d... (Closed) Base URL: svn://chrome-svn/chrome/trunk/tools/depot_tools/
Patch Set: '' Created 11 years, 4 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 | Annotate | Revision Log
« no previous file with comments | « presubmit_canned_checks.py ('k') | 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) 2006-2009 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2006-2009 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.2' 9 __version__ = '1.3.2'
10 10
(...skipping 325 matching lines...) Expand 10 before | Expand all | Expand 10 after
336 integer line number (1-based); and 336 integer line number (1-based); and
337 the contents of the line as a string. 337 the contents of the line as a string.
338 338
339 Note: The cariage return (LF or CR) is stripped off. 339 Note: The cariage return (LF or CR) is stripped off.
340 """ 340 """
341 files = self.AffectedSourceFiles(source_file_filter) 341 files = self.AffectedSourceFiles(source_file_filter)
342 return InputApi._RightHandSideLinesImpl(files) 342 return InputApi._RightHandSideLinesImpl(files)
343 343
344 def ReadFile(self, file, mode='r'): 344 def ReadFile(self, file, mode='r'):
345 """Reads an arbitrary file. 345 """Reads an arbitrary file.
346 346
347 Deny reading anything outside the repository. 347 Deny reading anything outside the repository.
348 """ 348 """
349 if isinstance(file, AffectedFile): 349 if isinstance(file, AffectedFile):
350 file = file.AbsoluteLocalPath() 350 file = file.AbsoluteLocalPath()
351 if not file.startswith(self.change.RepositoryRoot()): 351 if not file.startswith(self.change.RepositoryRoot()):
352 raise IOError('Access outside the repository root is denied.') 352 raise IOError('Access outside the repository root is denied.')
353 return gcl.ReadFile(file, mode) 353 return gcl.ReadFile(file, mode)
354 354
355 @staticmethod 355 @staticmethod
356 def _RightHandSideLinesImpl(affected_files): 356 def _RightHandSideLinesImpl(affected_files):
357 """Implements RightHandSideLines for InputApi and GclChange.""" 357 """Implements RightHandSideLines for InputApi and GclChange."""
358 for af in affected_files: 358 for af in affected_files:
359 lines = af.NewContents() 359 lines = af.NewContents()
360 line_number = 0 360 line_number = 0
361 for line in lines: 361 for line in lines:
362 line_number += 1 362 line_number += 1
363 yield (af, line_number, line) 363 yield (af, line_number, line)
364 364
365 365
366 class AffectedFile(object): 366 class AffectedFile(object):
367 """Representation of a file in a change.""" 367 """Representation of a file in a change."""
368 368
369 def __init__(self, path, action, repository_root=''): 369 def __init__(self, path, action, repository_root=''):
370 self._path = path 370 self._path = path
371 self._action = action 371 self._action = action
372 self._local_root = repository_root 372 self._local_root = repository_root
373 self._is_directory = None 373 self._is_directory = None
374 self._properties = {} 374 self._properties = {}
375 self.scm = ''
376 375
377 def ServerPath(self): 376 def ServerPath(self):
378 """Returns a path string that identifies the file in the SCM system. 377 """Returns a path string that identifies the file in the SCM system.
379 378
380 Returns the empty string if the file does not exist in SCM. 379 Returns the empty string if the file does not exist in SCM.
381 """ 380 """
382 return "" 381 return ""
383 382
384 def LocalPath(self): 383 def LocalPath(self):
385 """Returns the path of this file on the local disk relative to client root. 384 """Returns the path of this file on the local disk relative to client root.
(...skipping 20 matching lines...) Expand all
406 return self._action 405 return self._action
407 406
408 def Property(self, property_name): 407 def Property(self, property_name):
409 """Returns the specified SCM property of this file, or None if no such 408 """Returns the specified SCM property of this file, or None if no such
410 property. 409 property.
411 """ 410 """
412 return self._properties.get(property_name, None) 411 return self._properties.get(property_name, None)
413 412
414 def IsTextFile(self): 413 def IsTextFile(self):
415 """Returns True if the file is a text file and not a binary file. 414 """Returns True if the file is a text file and not a binary file.
416 415
417 Deleted files are not text file.""" 416 Deleted files are not text file."""
418 raise NotImplementedError() # Implement when needed 417 raise NotImplementedError() # Implement when needed
419 418
420 def NewContents(self): 419 def NewContents(self):
421 """Returns an iterator over the lines in the new version of file. 420 """Returns an iterator over the lines in the new version of file.
422 421
423 The new version is the file in the user's workspace, i.e. the "right hand 422 The new version is the file in the user's workspace, i.e. the "right hand
424 side". 423 side".
425 424
426 Contents will be empty if the file is a directory or does not exist. 425 Contents will be empty if the file is a directory or does not exist.
(...skipping 24 matching lines...) Expand all
451 return self.LocalPath() 450 return self.LocalPath()
452 451
453 452
454 class SvnAffectedFile(AffectedFile): 453 class SvnAffectedFile(AffectedFile):
455 """Representation of a file in a change out of a Subversion checkout.""" 454 """Representation of a file in a change out of a Subversion checkout."""
456 455
457 def __init__(self, *args, **kwargs): 456 def __init__(self, *args, **kwargs):
458 AffectedFile.__init__(self, *args, **kwargs) 457 AffectedFile.__init__(self, *args, **kwargs)
459 self._server_path = None 458 self._server_path = None
460 self._is_text_file = None 459 self._is_text_file = None
461 self.scm = 'svn'
462 460
463 def ServerPath(self): 461 def ServerPath(self):
464 if self._server_path is None: 462 if self._server_path is None:
465 self._server_path = gclient.CaptureSVNInfo( 463 self._server_path = gclient.CaptureSVNInfo(
466 self.AbsoluteLocalPath()).get('URL', '') 464 self.AbsoluteLocalPath()).get('URL', '')
467 return self._server_path 465 return self._server_path
468 466
469 def IsDirectory(self): 467 def IsDirectory(self):
470 if self._is_directory is None: 468 if self._is_directory is None:
471 path = self.AbsoluteLocalPath() 469 path = self.AbsoluteLocalPath()
(...skipping 26 matching lines...) Expand all
498 return self._is_text_file 496 return self._is_text_file
499 497
500 498
501 class GitAffectedFile(AffectedFile): 499 class GitAffectedFile(AffectedFile):
502 """Representation of a file in a change out of a git checkout.""" 500 """Representation of a file in a change out of a git checkout."""
503 501
504 def __init__(self, *args, **kwargs): 502 def __init__(self, *args, **kwargs):
505 AffectedFile.__init__(self, *args, **kwargs) 503 AffectedFile.__init__(self, *args, **kwargs)
506 self._server_path = None 504 self._server_path = None
507 self._is_text_file = None 505 self._is_text_file = None
508 self.scm = 'git'
509 506
510 def ServerPath(self): 507 def ServerPath(self):
511 if self._server_path is None: 508 if self._server_path is None:
512 raise NotImplementedException() # TODO(maruel) Implement. 509 raise NotImplementedException() # TODO(maruel) Implement.
513 return self._server_path 510 return self._server_path
514 511
515 def IsDirectory(self): 512 def IsDirectory(self):
516 if self._is_directory is None: 513 if self._is_directory is None:
517 path = self.AbsoluteLocalPath() 514 path = self.AbsoluteLocalPath()
518 if os.path.exists(path): 515 if os.path.exists(path):
(...skipping 21 matching lines...) Expand all
540 # raise NotImplementedException() # TODO(maruel) Implement. 537 # raise NotImplementedException() # TODO(maruel) Implement.
541 self._is_text_file = os.path.isfile(self.AbsoluteLocalPath()) 538 self._is_text_file = os.path.isfile(self.AbsoluteLocalPath())
542 return self._is_text_file 539 return self._is_text_file
543 540
544 541
545 class Change(object): 542 class Change(object):
546 """Describe a change. 543 """Describe a change.
547 544
548 Used directly by the presubmit scripts to query the current change being 545 Used directly by the presubmit scripts to query the current change being
549 tested. 546 tested.
550 547
551 Instance members: 548 Instance members:
552 tags: Dictionnary of KEY=VALUE pairs found in the change description. 549 tags: Dictionnary of KEY=VALUE pairs found in the change description.
553 self.KEY: equivalent to tags['KEY'] 550 self.KEY: equivalent to tags['KEY']
554 """ 551 """
555 552
556 _AFFECTED_FILES = AffectedFile 553 _AFFECTED_FILES = AffectedFile
557 554
558 # Matches key/value (or "tag") lines in changelist descriptions. 555 # Matches key/value (or "tag") lines in changelist descriptions.
559 _TAG_LINE_RE = re.compile( 556 _TAG_LINE_RE = re.compile(
560 '^\s*(?P<key>[A-Z][A-Z_0-9]*)\s*=\s*(?P<value>.*?)\s*$') 557 '^\s*(?P<key>[A-Z][A-Z_0-9]*)\s*=\s*(?P<value>.*?)\s*$')
561 558
562 def __init__(self, name, description, local_root, files, issue, patchset): 559 def __init__(self, name, description, local_root, files, issue, patchset):
563 if files is None: 560 if files is None:
564 files = [] 561 files = []
565 self._name = name 562 self._name = name
566 self._full_description = description 563 self._full_description = description
567 self._local_root = local_root 564 self._local_root = local_root
568 self.issue = issue 565 self.issue = issue
569 self.patchset = patchset 566 self.patchset = patchset
567 self.scm = ''
570 568
571 # From the description text, build up a dictionary of key/value pairs 569 # From the description text, build up a dictionary of key/value pairs
572 # plus the description minus all key/value or "tag" lines. 570 # plus the description minus all key/value or "tag" lines.
573 self._description_without_tags = [] 571 self._description_without_tags = []
574 self.tags = {} 572 self.tags = {}
575 for line in self._full_description.splitlines(): 573 for line in self._full_description.splitlines():
576 m = self._TAG_LINE_RE.match(line) 574 m = self._TAG_LINE_RE.match(line)
577 if m: 575 if m:
578 self.tags[m.group('key')] = m.group('value') 576 self.tags[m.group('key')] = m.group('value')
579 else: 577 else:
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
674 the contents of the line as a string. 672 the contents of the line as a string.
675 """ 673 """
676 return InputApi._RightHandSideLinesImpl( 674 return InputApi._RightHandSideLinesImpl(
677 filter(lambda x: x.IsTextFile(), 675 filter(lambda x: x.IsTextFile(),
678 self.AffectedFiles(include_deletes=False))) 676 self.AffectedFiles(include_deletes=False)))
679 677
680 678
681 class SvnChange(Change): 679 class SvnChange(Change):
682 _AFFECTED_FILES = SvnAffectedFile 680 _AFFECTED_FILES = SvnAffectedFile
683 681
682 def __init__(self, *args, **kwargs):
683 Change.__init__(self, *args, **kwargs)
684 self.scm = 'svn'
685
686 def GetAllModifiedFiles(self):
687 """Get all modified files."""
688 changelists = gcl.GetModifiedFiles()
689 all_modified_files = []
690 for cl in changelists.values():
691 all_modified_files.extend([f[1] for f in cl])
692 return all_modified_files
693
694 def GetModifiedFiles(self):
695 """Get modified files in the current CL."""
696 changelists = gcl.GetModifiedFiles()
697 return [f[1] for f in changelists[self.Name()]]
698
684 699
685 class GitChange(Change): 700 class GitChange(Change):
686 _AFFECTED_FILES = GitAffectedFile 701 _AFFECTED_FILES = GitAffectedFile
687 702
703 def __init__(self, *args, **kwargs):
704 Change.__init__(self, *args, **kwargs)
705 self.scm = 'git'
706
688 707
689 def ListRelevantPresubmitFiles(files, root): 708 def ListRelevantPresubmitFiles(files, root):
690 """Finds all presubmit files that apply to a given set of source files. 709 """Finds all presubmit files that apply to a given set of source files.
691 710
692 Args: 711 Args:
693 files: An iterable container containing file paths. 712 files: An iterable container containing file paths.
694 root: Path where to stop searching. 713 root: Path where to stop searching.
695 714
696 Return: 715 Return:
697 List of absolute paths of the existing PRESUBMIT.py scripts. 716 List of absolute paths of the existing PRESUBMIT.py scripts.
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after
924 options.commit, 943 options.commit,
925 options.verbose, 944 options.verbose,
926 sys.stdout, 945 sys.stdout,
927 sys.stdin, 946 sys.stdin,
928 options.default_presubmit, 947 options.default_presubmit,
929 options.may_prompt) 948 options.may_prompt)
930 949
931 950
932 if __name__ == '__main__': 951 if __name__ == '__main__':
933 sys.exit(Main(sys.argv)) 952 sys.exit(Main(sys.argv))
OLDNEW
« no previous file with comments | « presubmit_canned_checks.py ('k') | tests/presubmit_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698