OLD | NEW |
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.4' | 9 __version__ = '1.4' |
10 | 10 |
(...skipping 796 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
807 | 807 |
808 | 808 |
809 class GitChange(Change): | 809 class GitChange(Change): |
810 _AFFECTED_FILES = GitAffectedFile | 810 _AFFECTED_FILES = GitAffectedFile |
811 | 811 |
812 def __init__(self, *args, **kwargs): | 812 def __init__(self, *args, **kwargs): |
813 Change.__init__(self, *args, **kwargs) | 813 Change.__init__(self, *args, **kwargs) |
814 self.scm = 'git' | 814 self.scm = 'git' |
815 | 815 |
816 | 816 |
| 817 class ChangeDescription(object): |
| 818 """Contains a parsed form of the change description.""" |
| 819 MAX_SUBJECT_LENGTH = 100 |
| 820 |
| 821 def __init__(self, subject=None, description=None, reviewers=None, tbr=False, |
| 822 editor=None): |
| 823 self.subject = (subject or '').strip() |
| 824 self.description = (description or '').strip() |
| 825 self.reviewers = reviewers or [] |
| 826 self.tbr = tbr |
| 827 self.editor = editor or gclient_utils.UserEdit |
| 828 |
| 829 if self.description: |
| 830 if not self.description.startswith(self.subject): |
| 831 self.description = self.subject + '\n\n' + self.description |
| 832 elif self.subject: |
| 833 self.description = self.subject |
| 834 self.Parse(self.EditableDescription()) |
| 835 |
| 836 def EditableDescription(self): |
| 837 text = self.description.strip() |
| 838 if text: |
| 839 text += '\n' |
| 840 |
| 841 tbr_present = False |
| 842 r_present = False |
| 843 bug_present = False |
| 844 test_present = False |
| 845 for l in text.splitlines(): |
| 846 l = l.strip() |
| 847 r_present = r_present or l.startswith('R=') |
| 848 tbr_present = tbr_present or l.startswith('TBR=') |
| 849 |
| 850 if text and not (r_present or tbr_present): |
| 851 text += '\n' |
| 852 |
| 853 if not tbr_present and not r_present: |
| 854 if self.tbr: |
| 855 text += 'TBR=' + ','.join(self.reviewers) + '\n' |
| 856 else: |
| 857 text += 'R=' + ','.join(self.reviewers) + '\n' |
| 858 if not bug_present: |
| 859 text += 'BUG=\n' |
| 860 if not test_present: |
| 861 text += 'TEST=\n' |
| 862 |
| 863 return text |
| 864 |
| 865 def UserEdit(self): |
| 866 """Allows the user to update the description. |
| 867 |
| 868 Uses the editor callback passed to the constructor.""" |
| 869 self.Parse(self.editor(self.EditableDescription())) |
| 870 |
| 871 def Parse(self, text): |
| 872 """Parse the text returned from UserEdit() and update our state.""" |
| 873 parsed_lines = [] |
| 874 reviewers_regexp = re.compile('\s*(TBR|R)=(.+)') |
| 875 reviewers = [] |
| 876 subject = '' |
| 877 tbr = False |
| 878 for l in text.splitlines(): |
| 879 l = l.strip() |
| 880 |
| 881 # Throw away empty BUG=, TEST=, and R= lines. We leave in TBR= lines |
| 882 # to indicate that this change was meant to be "unreviewed". |
| 883 if l in ('BUG=', 'TEST=', 'R='): |
| 884 continue |
| 885 |
| 886 if not subject: |
| 887 subject = l |
| 888 matched_reviewers = reviewers_regexp.match(l) |
| 889 if matched_reviewers: |
| 890 tbr = (matched_reviewers.group(1) == 'TBR') |
| 891 reviewers.extend(matched_reviewers.group(2).split(',')) |
| 892 parsed_lines.append(l) |
| 893 |
| 894 if len(subject) > self.MAX_SUBJECT_LENGTH: |
| 895 subject = subject[:self.MAX_SUBJECT_LENGTH - 3] + '...' |
| 896 |
| 897 self.description = '\n'.join(parsed_lines).strip() |
| 898 self.subject = subject |
| 899 self.reviewers = reviewers |
| 900 self.tbr = tbr |
| 901 |
| 902 def IsEmpty(self): |
| 903 return not self.description |
| 904 |
| 905 |
| 906 |
817 def ListRelevantPresubmitFiles(files, root): | 907 def ListRelevantPresubmitFiles(files, root): |
818 """Finds all presubmit files that apply to a given set of source files. | 908 """Finds all presubmit files that apply to a given set of source files. |
819 | 909 |
820 If inherit-review-settings-ok is present right under root, looks for | 910 If inherit-review-settings-ok is present right under root, looks for |
821 PRESUBMIT.py in directories enclosing root. | 911 PRESUBMIT.py in directories enclosing root. |
822 | 912 |
823 Args: | 913 Args: |
824 files: An iterable container containing file paths. | 914 files: An iterable container containing file paths. |
825 root: Path where to stop searching. | 915 root: Path where to stop searching. |
826 | 916 |
(...skipping 362 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1189 sys.stdout, | 1279 sys.stdout, |
1190 sys.stdin, | 1280 sys.stdin, |
1191 options.default_presubmit, | 1281 options.default_presubmit, |
1192 options.may_prompt) | 1282 options.may_prompt) |
1193 return not results.should_continue() | 1283 return not results.should_continue() |
1194 | 1284 |
1195 | 1285 |
1196 if __name__ == '__main__': | 1286 if __name__ == '__main__': |
1197 fix_encoding.fix_encoding() | 1287 fix_encoding.fix_encoding() |
1198 sys.exit(Main(None)) | 1288 sys.exit(Main(None)) |
OLD | NEW |