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

Side by Side Diff: git_cl.py

Issue 23072039: Fix R= line rewriter to handle TBRs well. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Nits Created 7 years, 3 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/git_cl_test.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/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 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 # Copyright (C) 2008 Evan Martin <martine@danga.com> 6 # Copyright (C) 2008 Evan Martin <martine@danga.com>
7 7
8 """A git-command for integrating reviews on Rietveld.""" 8 """A git-command for integrating reviews on Rietveld."""
9 9
10 from distutils.version import LooseVersion 10 from distutils.version import LooseVersion
(...skipping 831 matching lines...) Expand 10 before | Expand all | Expand 10 after
842 'tree-status-url', False) 842 'tree-status-url', False)
843 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url', True) 843 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url', True)
844 844
845 # TODO: configure a default branch to diff against, rather than this 845 # TODO: configure a default branch to diff against, rather than this
846 # svn-based hackery. 846 # svn-based hackery.
847 847
848 848
849 class ChangeDescription(object): 849 class ChangeDescription(object):
850 """Contains a parsed form of the change description.""" 850 """Contains a parsed form of the change description."""
851 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' 851 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$'
852 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$'
852 853
853 def __init__(self, description): 854 def __init__(self, description):
854 self._description = (description or '').strip() 855 self._description_lines = (description or '').strip().splitlines()
855 856
856 @property 857 @property # www.logilab.org/ticket/89786
857 def description(self): 858 def description(self): # pylint: disable=E0202
858 return self._description 859 return '\n'.join(self._description_lines)
860
861 def set_description(self, desc):
862 if isinstance(desc, basestring):
863 lines = desc.splitlines()
864 else:
865 lines = [line.rstrip() for line in desc]
866 while lines and not lines[0]:
867 lines.pop(0)
868 while lines and not lines[-1]:
869 lines.pop(-1)
870 self._description_lines = lines
859 871
860 def update_reviewers(self, reviewers): 872 def update_reviewers(self, reviewers):
861 """Rewrites the R=/TBR= line(s) as a single line.""" 873 """Rewrites the R=/TBR= line(s) as a single line each."""
862 assert isinstance(reviewers, list), reviewers 874 assert isinstance(reviewers, list), reviewers
863 if not reviewers: 875 if not reviewers:
864 return 876 return
865 regexp = re.compile(self.R_LINE, re.MULTILINE) 877 reviewers = reviewers[:]
866 matches = list(regexp.finditer(self._description))
867 is_tbr = any(m.group(1) == 'TBR' for m in matches)
868 if len(matches) > 1:
869 # Erase all except the first one.
870 for i in xrange(len(matches) - 1, 0, -1):
871 self._description = (
872 self._description[:matches[i].start()] +
873 self._description[matches[i].end():])
874 878
875 if is_tbr: 879 # Get the set of R= and TBR= lines and remove them from the desciption.
876 new_r_line = 'TBR=' + ', '.join(reviewers) 880 regexp = re.compile(self.R_LINE)
881 matches = [regexp.match(line) for line in self._description_lines]
882 new_desc = [l for i, l in enumerate(self._description_lines)
883 if not matches[i]]
884 self.set_description(new_desc)
885
886 # Construct new unified R= and TBR= lines.
887 r_names = []
888 tbr_names = []
889 for match in matches:
890 if not match:
891 continue
892 people = cleanup_list([match.group(2).strip()])
893 if match.group(1) == 'TBR':
894 tbr_names.extend(people)
895 else:
896 r_names.extend(people)
897 for name in r_names:
898 if name not in reviewers:
899 reviewers.append(name)
900 new_r_line = 'R=' + ', '.join(reviewers) if reviewers else None
901 new_tbr_line = 'TBR=' + ', '.join(tbr_names) if tbr_names else None
902
903 # Put the new lines in the description where the old first R= line was.
904 line_loc = next((i for i, match in enumerate(matches) if match), -1)
905 if 0 <= line_loc < len(self._description_lines):
906 if new_tbr_line:
907 self._description_lines.insert(line_loc, new_tbr_line)
908 if new_r_line:
909 self._description_lines.insert(line_loc, new_r_line)
877 else: 910 else:
878 new_r_line = 'R=' + ', '.join(reviewers) 911 if new_r_line:
879 912 self.append_footer(new_r_line)
880 if matches: 913 if new_tbr_line:
881 self._description = ( 914 self.append_footer(new_tbr_line)
882 self._description[:matches[0].start()] + new_r_line +
883 self._description[matches[0].end():]).strip()
884 else:
885 self.append_footer(new_r_line)
886 915
887 def prompt(self): 916 def prompt(self):
888 """Asks the user to update the description.""" 917 """Asks the user to update the description."""
889 self._description = ( 918 self.set_description([
890 '# Enter a description of the change.\n' 919 '# Enter a description of the change.',
891 '# This will be displayed on the codereview site.\n' 920 '# This will be displayed on the codereview site.',
892 '# The first line will also be used as the subject of the review.\n' 921 '# The first line will also be used as the subject of the review.',
893 '#--------------------This line is 72 characters long' 922 '#--------------------This line is 72 characters long'
894 '--------------------\n' 923 '--------------------',
895 ) + self._description 924 ] + self._description_lines)
896 925
897 if '\nBUG=' not in self._description: 926 regexp = re.compile(self.BUG_LINE)
927 if not any((regexp.match(line) for line in self._description_lines)):
898 self.append_footer('BUG=') 928 self.append_footer('BUG=')
899 content = gclient_utils.RunEditor(self._description, True, 929 content = gclient_utils.RunEditor(self.description, True,
900 git_editor=settings.GetGitEditor()) 930 git_editor=settings.GetGitEditor())
901 if not content: 931 if not content:
902 DieWithError('Running editor failed') 932 DieWithError('Running editor failed')
933 lines = content.splitlines()
903 934
904 # Strip off comments. 935 # Strip off comments.
905 content = re.compile(r'^#.*$', re.MULTILINE).sub('', content).strip() 936 clean_lines = [line.rstrip() for line in lines if not line.startswith('#')]
906 if not content: 937 if not clean_lines:
907 DieWithError('No CL description, aborting') 938 DieWithError('No CL description, aborting')
908 self._description = content 939 self.set_description(clean_lines)
909 940
910 def append_footer(self, line): 941 def append_footer(self, line):
911 # Adds a LF if the last line did not have 'FOO=BAR' or if the new one isn't. 942 if self._description_lines:
912 if self._description: 943 # Add an empty line if either the last line or the new line isn't a tag.
913 if '\n' not in self._description: 944 last_line = self._description_lines[-1]
914 self._description += '\n' 945 if (not presubmit_support.Change.TAG_LINE_RE.match(last_line) or
915 else: 946 not presubmit_support.Change.TAG_LINE_RE.match(line)):
916 last_line = self._description.rsplit('\n', 1)[1] 947 self._description_lines.append('')
917 if (not presubmit_support.Change.TAG_LINE_RE.match(last_line) or 948 self._description_lines.append(line)
918 not presubmit_support.Change.TAG_LINE_RE.match(line)):
919 self._description += '\n'
920 self._description += '\n' + line
921 949
922 def get_reviewers(self): 950 def get_reviewers(self):
923 """Retrieves the list of reviewers.""" 951 """Retrieves the list of reviewers."""
924 regexp = re.compile(self.R_LINE, re.MULTILINE) 952 matches = [re.match(self.R_LINE, line) for line in self._description_lines]
925 reviewers = [i.group(2).strip() for i in regexp.finditer(self._description)] 953 reviewers = [match.group(2).strip() for match in matches if match]
926 return cleanup_list(reviewers) 954 return cleanup_list(reviewers)
927 955
928 956
929 def get_approving_reviewers(props): 957 def get_approving_reviewers(props):
930 """Retrieves the reviewers that approved a CL from the issue properties with 958 """Retrieves the reviewers that approved a CL from the issue properties with
931 messages. 959 messages.
932 960
933 Note that the list may contain reviewers that are not committer, thus are not 961 Note that the list may contain reviewers that are not committer, thus are not
934 considered by the CQ. 962 considered by the CQ.
935 """ 963 """
(...skipping 1261 matching lines...) Expand 10 before | Expand all | Expand 10 after
2197 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' 2225 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
2198 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) 2226 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
2199 2227
2200 2228
2201 if __name__ == '__main__': 2229 if __name__ == '__main__':
2202 # These affect sys.stdout so do it outside of main() to simplify mocks in 2230 # These affect sys.stdout so do it outside of main() to simplify mocks in
2203 # unit testing. 2231 # unit testing.
2204 fix_encoding.fix_encoding() 2232 fix_encoding.fix_encoding()
2205 colorama.init() 2233 colorama.init()
2206 sys.exit(main(sys.argv[1:])) 2234 sys.exit(main(sys.argv[1:]))
OLDNEW
« no previous file with comments | « no previous file | tests/git_cl_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698