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

Side by Side Diff: presubmit_support.py

Issue 113899: Starts converting GclChange to a method-less class. (Closed)
Patch Set: Fix a small error and unit test Created 11 years, 6 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) 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.1' 9 __version__ = '1.1'
10 10
(...skipping 18 matching lines...) Expand all
29 import urllib2 # Exposed through the API. 29 import urllib2 # Exposed through the API.
30 30
31 # Local imports. 31 # Local imports.
32 # TODO(joi) Would be cleaner to factor out utils in gcl to separate module, but 32 # TODO(joi) Would be cleaner to factor out utils in gcl to separate module, but
33 # for now it would only be a couple of functions so hardly worth it. 33 # for now it would only be a couple of functions so hardly worth it.
34 import gcl 34 import gcl
35 import gclient 35 import gclient
36 import presubmit_canned_checks 36 import presubmit_canned_checks
37 37
38 38
39 # Matches key/value (or "tag") lines in changelist descriptions.
40 _tag_line_re = re.compile(
41 '^\s*(?P<key>[A-Z][A-Z_0-9]*)\s*=\s*(?P<value>.*?)\s*$')
42
43
44 class NotImplementedException(Exception): 39 class NotImplementedException(Exception):
45 """We're leaving placeholders in a bunch of places to remind us of the 40 """We're leaving placeholders in a bunch of places to remind us of the
46 design of the API, but we have not implemented all of it yet. Implement as 41 design of the API, but we have not implemented all of it yet. Implement as
47 the need arises. 42 the need arises.
48 """ 43 """
49 pass 44 pass
50 45
51 46
52 def normpath(path): 47 def normpath(path):
53 '''Version of os.path.normpath that also changes backward slashes to 48 '''Version of os.path.normpath that also changes backward slashes to
54 forward slashes when not running on Windows. 49 forward slashes when not running on Windows.
55 ''' 50 '''
56 # This is safe to always do because the Windows version of os.path.normpath 51 # This is safe to always do because the Windows version of os.path.normpath
57 # will replace forward slashes with backward slashes. 52 # will replace forward slashes with backward slashes.
58 path = path.replace(os.sep, '/') 53 path = path.replace(os.sep, '/')
59 return os.path.normpath(path) 54 return os.path.normpath(path)
60 55
61 56
62
63 class OutputApi(object): 57 class OutputApi(object):
64 """This class (more like a module) gets passed to presubmit scripts so that 58 """This class (more like a module) gets passed to presubmit scripts so that
65 they can specify various types of results. 59 they can specify various types of results.
66 """ 60 """
67 61
68 class PresubmitResult(object): 62 class PresubmitResult(object):
69 """Base class for result objects.""" 63 """Base class for result objects."""
70 64
71 def __init__(self, message, items=None, long_text=''): 65 def __init__(self, message, items=None, long_text=''):
72 """ 66 """
(...skipping 333 matching lines...) Expand 10 before | Expand all | Expand 10 after
406 return self.is_directory 400 return self.is_directory
407 401
408 def Property(self, property_name): 402 def Property(self, property_name):
409 if not property_name in self.properties: 403 if not property_name in self.properties:
410 self.properties[property_name] = gcl.GetSVNFileProperty( 404 self.properties[property_name] = gcl.GetSVNFileProperty(
411 self.AbsoluteLocalPath(), property_name) 405 self.AbsoluteLocalPath(), property_name)
412 return self.properties[property_name] 406 return self.properties[property_name]
413 407
414 408
415 class GclChange(object): 409 class GclChange(object):
416 """A gcl change. See gcl.ChangeInfo for more info.""" 410 """Describe a change.
411
412 Used directly by the presubmit scripts to query the current change being
413 tested.
414
415 Instance members:
416 tags: Dictionnary of KEY=VALUE pairs found in the change description.
417 self.KEY: equivalent to tags['KEY']
418 """
419
420 # Matches key/value (or "tag") lines in changelist descriptions.
421 _tag_line_re = re.compile(
422 '^\s*(?P<key>[A-Z][A-Z_0-9]*)\s*=\s*(?P<value>.*?)\s*$')
417 423
418 def __init__(self, change_info, repository_root=''): 424 def __init__(self, change_info, repository_root=''):
419 self.name = change_info.name 425 # Do not keep a reference to the original change_info.
420 self.full_description = change_info.description 426 self._name = change_info.name
421 self.repository_root = repository_root 427 self._full_description = change_info.description
428 self._repository_root = repository_root
422 429
423 # From the description text, build up a dictionary of key/value pairs 430 # From the description text, build up a dictionary of key/value pairs
424 # plus the description minus all key/value or "tag" lines. 431 # plus the description minus all key/value or "tag" lines.
425 self.description_without_tags = [] 432 self._description_without_tags = []
426 self.tags = {} 433 self.tags = {}
427 for line in change_info.description.splitlines(): 434 for line in change_info.description.splitlines():
428 m = _tag_line_re.match(line) 435 m = self._tag_line_re.match(line)
429 if m: 436 if m:
430 self.tags[m.group('key')] = m.group('value') 437 self.tags[m.group('key')] = m.group('value')
431 else: 438 else:
432 self.description_without_tags.append(line) 439 self._description_without_tags.append(line)
433 440
434 # Change back to text and remove whitespace at end. 441 # Change back to text and remove whitespace at end.
435 self.description_without_tags = '\n'.join(self.description_without_tags) 442 self._description_without_tags = '\n'.join(self._description_without_tags)
436 self.description_without_tags = self.description_without_tags.rstrip() 443 self._description_without_tags = self._description_without_tags.rstrip()
437 444
438 self.affected_files = [ 445 self._affected_files = [
439 SvnAffectedFile(info[1], info[0].strip(), repository_root) 446 SvnAffectedFile(info[1], info[0].strip(), repository_root)
440 for info in change_info.files 447 for info in change_info.files
441 ] 448 ]
442 449
443 def Change(self): 450 def Change(self):
444 """Returns the change name.""" 451 """Returns the change name."""
445 return self.name 452 return self._name
446 453
447 def DescriptionText(self): 454 def DescriptionText(self):
448 """Returns the user-entered changelist description, minus tags. 455 """Returns the user-entered changelist description, minus tags.
449 456
450 Any line in the user-provided description starting with e.g. "FOO=" 457 Any line in the user-provided description starting with e.g. "FOO="
451 (whitespace permitted before and around) is considered a tag line. Such 458 (whitespace permitted before and around) is considered a tag line. Such
452 lines are stripped out of the description this function returns. 459 lines are stripped out of the description this function returns.
453 """ 460 """
454 return self.description_without_tags 461 return self._description_without_tags
455 462
456 def FullDescriptionText(self): 463 def FullDescriptionText(self):
457 """Returns the complete changelist description including tags.""" 464 """Returns the complete changelist description including tags."""
458 return self.full_description 465 return self._full_description
459 466
460 def RepositoryRoot(self): 467 def RepositoryRoot(self):
461 """Returns the repository root for this change, as an absolute path.""" 468 """Returns the repository root for this change, as an absolute path."""
462 return self.repository_root 469 return self._repository_root
463 470
464 def __getattr__(self, attr): 471 def __getattr__(self, attr):
465 """Return keys directly as attributes on the object. 472 """Return keys directly as attributes on the object.
466 473
467 You may use a friendly name (from SPECIAL_KEYS) or the actual name of 474 You may use a friendly name (from SPECIAL_KEYS) or the actual name of
468 the key. 475 the key.
469 """ 476 """
470 return self.tags.get(attr) 477 return self.tags.get(attr)
471 478
472 def AffectedFiles(self, include_dirs=False, include_deletes=True): 479 def AffectedFiles(self, include_dirs=False, include_deletes=True):
473 """Returns a list of AffectedFile instances for all files in the change. 480 """Returns a list of AffectedFile instances for all files in the change.
474 481
475 Args: 482 Args:
476 include_deletes: If false, deleted files will be filtered out. 483 include_deletes: If false, deleted files will be filtered out.
477 include_dirs: True to include directories in the list 484 include_dirs: True to include directories in the list
478 485
479 Returns: 486 Returns:
480 [AffectedFile(path, action), AffectedFile(path, action)] 487 [AffectedFile(path, action), AffectedFile(path, action)]
481 """ 488 """
482 if include_dirs: 489 if include_dirs:
483 affected = self.affected_files 490 affected = self._affected_files
484 else: 491 else:
485 affected = filter(lambda x: not x.IsDirectory(), self.affected_files) 492 affected = filter(lambda x: not x.IsDirectory(), self._affected_files)
486 493
487 if include_deletes: 494 if include_deletes:
488 return affected 495 return affected
489 else: 496 else:
490 return filter(lambda x: x.Action() != 'D', affected) 497 return filter(lambda x: x.Action() != 'D', affected)
491 498
492 def AffectedTextFiles(self, include_deletes=True): 499 def AffectedTextFiles(self, include_deletes=True):
493 """Return a list of the text files in a change. 500 """Return a list of the text files in a change.
494 501
495 It's common to want to iterate over only the text files. 502 It's common to want to iterate over only the text files.
(...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after
720 return not DoPresubmitChecks(gcl.ChangeInfo(name='temp', files=files), 727 return not DoPresubmitChecks(gcl.ChangeInfo(name='temp', files=files),
721 options.commit, 728 options.commit,
722 options.verbose, 729 options.verbose,
723 sys.stdout, 730 sys.stdout,
724 sys.stdin, 731 sys.stdin,
725 default_presubmit=None) 732 default_presubmit=None)
726 733
727 734
728 if __name__ == '__main__': 735 if __name__ == '__main__':
729 sys.exit(Main(sys.argv)) 736 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