Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 and Gerrit.""" | 8 """A git-command for integrating reviews on Rietveld and Gerrit.""" |
| 9 | 9 |
| 10 from __future__ import print_function | 10 from __future__ import print_function |
| (...skipping 1898 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1909 if options.title is not None: | 1909 if options.title is not None: |
| 1910 upload_args.extend(['--title', options.title]) | 1910 upload_args.extend(['--title', options.title]) |
| 1911 message = (options.title or options.message or | 1911 message = (options.title or options.message or |
| 1912 CreateDescriptionFromLog(args)) | 1912 CreateDescriptionFromLog(args)) |
| 1913 change_desc = ChangeDescription(message) | 1913 change_desc = ChangeDescription(message) |
| 1914 if options.reviewers or options.tbr_owners: | 1914 if options.reviewers or options.tbr_owners: |
| 1915 change_desc.update_reviewers(options.reviewers, | 1915 change_desc.update_reviewers(options.reviewers, |
| 1916 options.tbr_owners, | 1916 options.tbr_owners, |
| 1917 change) | 1917 change) |
| 1918 if not options.force: | 1918 if not options.force: |
| 1919 change_desc.prompt() | 1919 change_desc.prompt(bug=options.bug) |
| 1920 | 1920 |
| 1921 if not change_desc.description: | 1921 if not change_desc.description: |
| 1922 print('Description is empty; aborting.') | 1922 print('Description is empty; aborting.') |
| 1923 return 1 | 1923 return 1 |
| 1924 | 1924 |
| 1925 upload_args.extend(['--message', change_desc.description]) | 1925 upload_args.extend(['--message', change_desc.description]) |
| 1926 if change_desc.get_reviewers(): | 1926 if change_desc.get_reviewers(): |
| 1927 upload_args.append('--reviewers=%s' % ','.join( | 1927 upload_args.append('--reviewers=%s' % ','.join( |
| 1928 change_desc.get_reviewers())) | 1928 change_desc.get_reviewers())) |
| 1929 if options.send_mail: | 1929 if options.send_mail: |
| (...skipping 528 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2458 'WARNING: issue %s has Change-Id footer(s):\n' | 2458 'WARNING: issue %s has Change-Id footer(s):\n' |
| 2459 ' %s\n' | 2459 ' %s\n' |
| 2460 'but issue has Change-Id %s, according to Gerrit.\n' | 2460 'but issue has Change-Id %s, according to Gerrit.\n' |
| 2461 'Please, check the proposed correction to the description, ' | 2461 'Please, check the proposed correction to the description, ' |
| 2462 'and edit it if necessary but keep the "Change-Id: %s" footer\n' | 2462 'and edit it if necessary but keep the "Change-Id: %s" footer\n' |
| 2463 % (self.GetIssue(), '\n '.join(footer_change_ids), change_id, | 2463 % (self.GetIssue(), '\n '.join(footer_change_ids), change_id, |
| 2464 change_id)) | 2464 change_id)) |
| 2465 ask_for_data('Press enter to edit now, Ctrl+C to abort') | 2465 ask_for_data('Press enter to edit now, Ctrl+C to abort') |
| 2466 if not options.force: | 2466 if not options.force: |
| 2467 change_desc = ChangeDescription(message) | 2467 change_desc = ChangeDescription(message) |
| 2468 change_desc.prompt() | 2468 change_desc.prompt(bug=options.bug) |
| 2469 message = change_desc.description | 2469 message = change_desc.description |
| 2470 if not message: | 2470 if not message: |
| 2471 DieWithError("Description is empty. Aborting...") | 2471 DieWithError("Description is empty. Aborting...") |
| 2472 # Continue the while loop. | 2472 # Continue the while loop. |
| 2473 # Sanity check of this code - we should end up with proper message | 2473 # Sanity check of this code - we should end up with proper message |
| 2474 # footer. | 2474 # footer. |
| 2475 assert [change_id] == git_footers.get_footer_change_id(message) | 2475 assert [change_id] == git_footers.get_footer_change_id(message) |
| 2476 change_desc = ChangeDescription(message) | 2476 change_desc = ChangeDescription(message) |
| 2477 else: | 2477 else: |
| 2478 change_desc = ChangeDescription( | 2478 change_desc = ChangeDescription( |
| 2479 options.message or CreateDescriptionFromLog(args)) | 2479 options.message or CreateDescriptionFromLog(args)) |
| 2480 if not options.force: | 2480 if not options.force: |
| 2481 change_desc.prompt() | 2481 change_desc.prompt(bug=options.bug) |
| 2482 if not change_desc.description: | 2482 if not change_desc.description: |
| 2483 DieWithError("Description is empty. Aborting...") | 2483 DieWithError("Description is empty. Aborting...") |
| 2484 message = change_desc.description | 2484 message = change_desc.description |
| 2485 change_ids = git_footers.get_footer_change_id(message) | 2485 change_ids = git_footers.get_footer_change_id(message) |
| 2486 if len(change_ids) > 1: | 2486 if len(change_ids) > 1: |
| 2487 DieWithError('too many Change-Id footers, at most 1 allowed.') | 2487 DieWithError('too many Change-Id footers, at most 1 allowed.') |
| 2488 if not change_ids: | 2488 if not change_ids: |
| 2489 # Generate the Change-Id automatically. | 2489 # Generate the Change-Id automatically. |
| 2490 message = git_footers.add_footer_change_id( | 2490 message = git_footers.add_footer_change_id( |
| 2491 message, GenerateGerritChangeId(message)) | 2491 message, GenerateGerritChangeId(message)) |
| (...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2653 def _process_codereview_select_options(parser, options): | 2653 def _process_codereview_select_options(parser, options): |
| 2654 if options.gerrit and options.rietveld: | 2654 if options.gerrit and options.rietveld: |
| 2655 parser.error('Options --gerrit and --rietveld are mutually exclusive') | 2655 parser.error('Options --gerrit and --rietveld are mutually exclusive') |
| 2656 options.forced_codereview = None | 2656 options.forced_codereview = None |
| 2657 if options.gerrit: | 2657 if options.gerrit: |
| 2658 options.forced_codereview = 'gerrit' | 2658 options.forced_codereview = 'gerrit' |
| 2659 elif options.rietveld: | 2659 elif options.rietveld: |
| 2660 options.forced_codereview = 'rietveld' | 2660 options.forced_codereview = 'rietveld' |
| 2661 | 2661 |
| 2662 | 2662 |
| 2663 def _get_bug_line_values(default_project, bugs): | |
| 2664 """Given default_project and comma separated list of bugs, yields bug line | |
| 2665 values. | |
| 2666 | |
| 2667 Each bug can be either: | |
| 2668 * a number, which is combined with default_project | |
| 2669 * string, which is left as is. | |
| 2670 | |
| 2671 This function may produce more than one line, because bugdroid expects one | |
| 2672 project per line. | |
| 2673 | |
| 2674 >>> list(_get_bug_line_values('v8', '123,chromium:789')) | |
| 2675 ['v8:123', 'chromium:789'] | |
| 2676 """ | |
| 2677 default_bugs = [] | |
| 2678 others = [] | |
| 2679 for bug in bugs.split(','): | |
| 2680 bug = bug.strip() | |
| 2681 if bug: | |
| 2682 try: | |
| 2683 default_bugs.append(int(bug)) | |
| 2684 except ValueError: | |
| 2685 others.append(bug) | |
| 2686 | |
| 2687 if default_bugs: | |
| 2688 default_bugs = ','.join(map(str, default_bugs)) | |
| 2689 if default_project: | |
| 2690 yield '%s:%s' % (default_project, default_bugs) | |
| 2691 else: | |
| 2692 yield default_bugs | |
| 2693 for other in sorted(others): | |
| 2694 # Don't bother finding common prefixes, CLs with >2 bugs are very very rare. | |
|
Sergiy Byelozyorov
2016/07/01 15:37:01
+1 for writing this comment
| |
| 2695 yield other | |
| 2696 | |
| 2697 | |
| 2663 class ChangeDescription(object): | 2698 class ChangeDescription(object): |
| 2664 """Contains a parsed form of the change description.""" | 2699 """Contains a parsed form of the change description.""" |
| 2665 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' | 2700 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' |
| 2666 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$' | 2701 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$' |
| 2667 | 2702 |
| 2668 def __init__(self, description): | 2703 def __init__(self, description): |
| 2669 self._description_lines = (description or '').strip().splitlines() | 2704 self._description_lines = (description or '').strip().splitlines() |
| 2670 | 2705 |
| 2671 @property # www.logilab.org/ticket/89786 | 2706 @property # www.logilab.org/ticket/89786 |
| 2672 def description(self): # pylint: disable=E0202 | 2707 def description(self): # pylint: disable=E0202 |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2728 if new_tbr_line: | 2763 if new_tbr_line: |
| 2729 self._description_lines.insert(line_loc, new_tbr_line) | 2764 self._description_lines.insert(line_loc, new_tbr_line) |
| 2730 if new_r_line: | 2765 if new_r_line: |
| 2731 self._description_lines.insert(line_loc, new_r_line) | 2766 self._description_lines.insert(line_loc, new_r_line) |
| 2732 else: | 2767 else: |
| 2733 if new_r_line: | 2768 if new_r_line: |
| 2734 self.append_footer(new_r_line) | 2769 self.append_footer(new_r_line) |
| 2735 if new_tbr_line: | 2770 if new_tbr_line: |
| 2736 self.append_footer(new_tbr_line) | 2771 self.append_footer(new_tbr_line) |
| 2737 | 2772 |
| 2738 def prompt(self): | 2773 def prompt(self, bug=None): |
| 2739 """Asks the user to update the description.""" | 2774 """Asks the user to update the description.""" |
| 2740 self.set_description([ | 2775 self.set_description([ |
| 2741 '# Enter a description of the change.', | 2776 '# Enter a description of the change.', |
| 2742 '# This will be displayed on the codereview site.', | 2777 '# This will be displayed on the codereview site.', |
| 2743 '# The first line will also be used as the subject of the review.', | 2778 '# The first line will also be used as the subject of the review.', |
| 2744 '#--------------------This line is 72 characters long' | 2779 '#--------------------This line is 72 characters long' |
| 2745 '--------------------', | 2780 '--------------------', |
| 2746 ] + self._description_lines) | 2781 ] + self._description_lines) |
| 2747 | 2782 |
| 2748 regexp = re.compile(self.BUG_LINE) | 2783 regexp = re.compile(self.BUG_LINE) |
| 2749 if not any((regexp.match(line) for line in self._description_lines)): | 2784 if not any((regexp.match(line) for line in self._description_lines)): |
| 2750 self.append_footer('BUG=%s' % settings.GetBugPrefix()) | 2785 prefix = settings.GetBugPrefix() |
| 2786 values = list(_get_bug_line_values(prefix, bug or '')) or [prefix] | |
| 2787 for value in values: | |
| 2788 # TODO(tandrii): change this to 'Bug: xxx' to be a proper Gerrit footer. | |
| 2789 self.append_footer('BUG=%s' % value) | |
| 2790 | |
| 2751 content = gclient_utils.RunEditor(self.description, True, | 2791 content = gclient_utils.RunEditor(self.description, True, |
| 2752 git_editor=settings.GetGitEditor()) | 2792 git_editor=settings.GetGitEditor()) |
| 2753 if not content: | 2793 if not content: |
| 2754 DieWithError('Running editor failed') | 2794 DieWithError('Running editor failed') |
| 2755 lines = content.splitlines() | 2795 lines = content.splitlines() |
| 2756 | 2796 |
| 2757 # Strip off comments. | 2797 # Strip off comments. |
| 2758 clean_lines = [line.rstrip() for line in lines if not line.startswith('#')] | 2798 clean_lines = [line.rstrip() for line in lines if not line.startswith('#')] |
| 2759 if not clean_lines: | 2799 if not clean_lines: |
| 2760 DieWithError('No CL description, aborting') | 2800 DieWithError('No CL description, aborting') |
| (...skipping 957 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3718 Can also set the above globally by using the --global flag. | 3758 Can also set the above globally by using the --global flag. |
| 3719 """ | 3759 """ |
| 3720 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 3760 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
| 3721 help='bypass upload presubmit hook') | 3761 help='bypass upload presubmit hook') |
| 3722 parser.add_option('--bypass-watchlists', action='store_true', | 3762 parser.add_option('--bypass-watchlists', action='store_true', |
| 3723 dest='bypass_watchlists', | 3763 dest='bypass_watchlists', |
| 3724 help='bypass watchlists auto CC-ing reviewers') | 3764 help='bypass watchlists auto CC-ing reviewers') |
| 3725 parser.add_option('-f', action='store_true', dest='force', | 3765 parser.add_option('-f', action='store_true', dest='force', |
| 3726 help="force yes to questions (don't prompt)") | 3766 help="force yes to questions (don't prompt)") |
| 3727 parser.add_option('-m', dest='message', help='message for patchset') | 3767 parser.add_option('-m', dest='message', help='message for patchset') |
| 3768 parser.add_option('-b', '--bug', | |
| 3769 help='pre-populate the bug number(s) for this issue. ' | |
| 3770 'If several, separate with commas') | |
| 3728 parser.add_option('--message-file', dest='message_file', | 3771 parser.add_option('--message-file', dest='message_file', |
| 3729 help='file which contains message for patchset') | 3772 help='file which contains message for patchset') |
| 3730 parser.add_option('-t', dest='title', | 3773 parser.add_option('-t', dest='title', |
| 3731 help='title for patchset (Rietveld only)') | 3774 help='title for patchset (Rietveld only)') |
| 3732 parser.add_option('-r', '--reviewers', | 3775 parser.add_option('-r', '--reviewers', |
| 3733 action='append', default=[], | 3776 action='append', default=[], |
| 3734 help='reviewer email addresses') | 3777 help='reviewer email addresses') |
| 3735 parser.add_option('--cc', | 3778 parser.add_option('--cc', |
| 3736 action='append', default=[], | 3779 action='append', default=[], |
| 3737 help='cc email addresses') | 3780 help='cc email addresses') |
| (...skipping 1340 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 5078 if __name__ == '__main__': | 5121 if __name__ == '__main__': |
| 5079 # These affect sys.stdout so do it outside of main() to simplify mocks in | 5122 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 5080 # unit testing. | 5123 # unit testing. |
| 5081 fix_encoding.fix_encoding() | 5124 fix_encoding.fix_encoding() |
| 5082 setup_color.init() | 5125 setup_color.init() |
| 5083 try: | 5126 try: |
| 5084 sys.exit(main(sys.argv[1:])) | 5127 sys.exit(main(sys.argv[1:])) |
| 5085 except KeyboardInterrupt: | 5128 except KeyboardInterrupt: |
| 5086 sys.stderr.write('interrupted\n') | 5129 sys.stderr.write('interrupted\n') |
| 5087 sys.exit(1) | 5130 sys.exit(1) |
| OLD | NEW |