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.""" | 8 """A git-command for integrating reviews on Rietveld.""" |
9 | 9 |
10 from distutils.version import LooseVersion | 10 from distutils.version import LooseVersion |
(...skipping 18 matching lines...) Expand all Loading... |
29 pass | 29 pass |
30 | 30 |
31 | 31 |
32 from third_party import colorama | 32 from third_party import colorama |
33 from third_party import upload | 33 from third_party import upload |
34 import breakpad # pylint: disable=W0611 | 34 import breakpad # pylint: disable=W0611 |
35 import clang_format | 35 import clang_format |
36 import fix_encoding | 36 import fix_encoding |
37 import gclient_utils | 37 import gclient_utils |
38 import git_common | 38 import git_common |
| 39 import owners |
39 import owners_finder | 40 import owners_finder |
40 import presubmit_support | 41 import presubmit_support |
41 import rietveld | 42 import rietveld |
42 import scm | 43 import scm |
43 import subcommand | 44 import subcommand |
44 import subprocess2 | 45 import subprocess2 |
45 import watchlists | 46 import watchlists |
46 | 47 |
47 __version__ = '1.0' | 48 __version__ = '1.0' |
48 | 49 |
(...skipping 903 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
952 if isinstance(desc, basestring): | 953 if isinstance(desc, basestring): |
953 lines = desc.splitlines() | 954 lines = desc.splitlines() |
954 else: | 955 else: |
955 lines = [line.rstrip() for line in desc] | 956 lines = [line.rstrip() for line in desc] |
956 while lines and not lines[0]: | 957 while lines and not lines[0]: |
957 lines.pop(0) | 958 lines.pop(0) |
958 while lines and not lines[-1]: | 959 while lines and not lines[-1]: |
959 lines.pop(-1) | 960 lines.pop(-1) |
960 self._description_lines = lines | 961 self._description_lines = lines |
961 | 962 |
962 def update_reviewers(self, reviewers): | 963 def update_reviewers(self, reviewers, add_owners_tbr=False, change=None): |
963 """Rewrites the R=/TBR= line(s) as a single line each.""" | 964 """Rewrites the R=/TBR= line(s) as a single line each.""" |
964 assert isinstance(reviewers, list), reviewers | 965 assert isinstance(reviewers, list), reviewers |
965 if not reviewers: | 966 if not reviewers and not add_owners_tbr: |
966 return | 967 return |
967 reviewers = reviewers[:] | 968 reviewers = reviewers[:] |
968 | 969 |
969 # Get the set of R= and TBR= lines and remove them from the desciption. | 970 # Get the set of R= and TBR= lines and remove them from the desciption. |
970 regexp = re.compile(self.R_LINE) | 971 regexp = re.compile(self.R_LINE) |
971 matches = [regexp.match(line) for line in self._description_lines] | 972 matches = [regexp.match(line) for line in self._description_lines] |
972 new_desc = [l for i, l in enumerate(self._description_lines) | 973 new_desc = [l for i, l in enumerate(self._description_lines) |
973 if not matches[i]] | 974 if not matches[i]] |
974 self.set_description(new_desc) | 975 self.set_description(new_desc) |
975 | 976 |
976 # Construct new unified R= and TBR= lines. | 977 # Construct new unified R= and TBR= lines. |
977 r_names = [] | 978 r_names = [] |
978 tbr_names = [] | 979 tbr_names = [] |
979 for match in matches: | 980 for match in matches: |
980 if not match: | 981 if not match: |
981 continue | 982 continue |
982 people = cleanup_list([match.group(2).strip()]) | 983 people = cleanup_list([match.group(2).strip()]) |
983 if match.group(1) == 'TBR': | 984 if match.group(1) == 'TBR': |
984 tbr_names.extend(people) | 985 tbr_names.extend(people) |
985 else: | 986 else: |
986 r_names.extend(people) | 987 r_names.extend(people) |
987 for name in r_names: | 988 for name in r_names: |
988 if name not in reviewers: | 989 if name not in reviewers: |
989 reviewers.append(name) | 990 reviewers.append(name) |
| 991 if add_owners_tbr: |
| 992 owners_db = owners.Database(change.RepositoryRoot(), |
| 993 fopen=file, os_path=os.path, glob=glob.glob) |
| 994 all_reviewers = set(tbr_names + reviewers) |
| 995 missing_files = owners_db.files_not_covered_by(change.LocalPaths(), |
| 996 all_reviewers) |
| 997 tbr_names.extend(owners_db.reviewers_for(missing_files, |
| 998 change.author_email)) |
990 new_r_line = 'R=' + ', '.join(reviewers) if reviewers else None | 999 new_r_line = 'R=' + ', '.join(reviewers) if reviewers else None |
991 new_tbr_line = 'TBR=' + ', '.join(tbr_names) if tbr_names else None | 1000 new_tbr_line = 'TBR=' + ', '.join(tbr_names) if tbr_names else None |
992 | 1001 |
993 # Put the new lines in the description where the old first R= line was. | 1002 # Put the new lines in the description where the old first R= line was. |
994 line_loc = next((i for i, match in enumerate(matches) if match), -1) | 1003 line_loc = next((i for i, match in enumerate(matches) if match), -1) |
995 if 0 <= line_loc < len(self._description_lines): | 1004 if 0 <= line_loc < len(self._description_lines): |
996 if new_tbr_line: | 1005 if new_tbr_line: |
997 self._description_lines.insert(line_loc, new_tbr_line) | 1006 self._description_lines.insert(line_loc, new_tbr_line) |
998 if new_r_line: | 1007 if new_r_line: |
999 self._description_lines.insert(line_loc, new_r_line) | 1008 self._description_lines.insert(line_loc, new_r_line) |
(...skipping 518 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1518 log_desc = options.message or CreateDescriptionFromLog(args) | 1527 log_desc = options.message or CreateDescriptionFromLog(args) |
1519 git_command = ['commit', '--amend', '-m', log_desc] | 1528 git_command = ['commit', '--amend', '-m', log_desc] |
1520 RunGit(git_command) | 1529 RunGit(git_command) |
1521 new_log_desc = CreateDescriptionFromLog(args) | 1530 new_log_desc = CreateDescriptionFromLog(args) |
1522 if CHANGE_ID in new_log_desc: | 1531 if CHANGE_ID in new_log_desc: |
1523 print 'git-cl: Added Change-Id to commit message.' | 1532 print 'git-cl: Added Change-Id to commit message.' |
1524 else: | 1533 else: |
1525 print >> sys.stderr, 'ERROR: Gerrit commit-msg hook not available.' | 1534 print >> sys.stderr, 'ERROR: Gerrit commit-msg hook not available.' |
1526 | 1535 |
1527 | 1536 |
1528 def GerritUpload(options, args, cl): | 1537 def GerritUpload(options, args, cl, change): |
1529 """upload the current branch to gerrit.""" | 1538 """upload the current branch to gerrit.""" |
1530 # We assume the remote called "origin" is the one we want. | 1539 # We assume the remote called "origin" is the one we want. |
1531 # It is probably not worthwhile to support different workflows. | 1540 # It is probably not worthwhile to support different workflows. |
1532 remote = 'origin' | 1541 remote = 'origin' |
1533 branch = 'master' | 1542 branch = 'master' |
1534 if options.target_branch: | 1543 if options.target_branch: |
1535 branch = options.target_branch | 1544 branch = options.target_branch |
1536 | 1545 |
1537 change_desc = ChangeDescription( | 1546 change_desc = ChangeDescription( |
1538 options.message or CreateDescriptionFromLog(args)) | 1547 options.message or CreateDescriptionFromLog(args)) |
1539 if not change_desc.description: | 1548 if not change_desc.description: |
1540 print "Description is empty; aborting." | 1549 print "Description is empty; aborting." |
1541 return 1 | 1550 return 1 |
1542 if CHANGE_ID not in change_desc.description: | 1551 if CHANGE_ID not in change_desc.description: |
1543 AddChangeIdToCommitMessage(options, args) | 1552 AddChangeIdToCommitMessage(options, args) |
1544 | 1553 |
1545 commits = RunGit(['rev-list', '%s/%s..' % (remote, branch)]).splitlines() | 1554 commits = RunGit(['rev-list', '%s/%s..' % (remote, branch)]).splitlines() |
1546 if len(commits) > 1: | 1555 if len(commits) > 1: |
1547 print('WARNING: This will upload %d commits. Run the following command ' | 1556 print('WARNING: This will upload %d commits. Run the following command ' |
1548 'to see which commits will be uploaded: ' % len(commits)) | 1557 'to see which commits will be uploaded: ' % len(commits)) |
1549 print('git log %s/%s..' % (remote, branch)) | 1558 print('git log %s/%s..' % (remote, branch)) |
1550 print('You can also use `git squash-branch` to squash these into a single' | 1559 print('You can also use `git squash-branch` to squash these into a single' |
1551 'commit.') | 1560 'commit.') |
1552 ask_for_data('About to upload; enter to confirm.') | 1561 ask_for_data('About to upload; enter to confirm.') |
1553 | 1562 |
1554 if options.reviewers: | 1563 if options.reviewers or options.tbr_owners: |
1555 change_desc.update_reviewers(options.reviewers) | 1564 change_desc.update_reviewers(options.reviewers, options.tbr_owners, change) |
1556 | 1565 |
1557 receive_options = [] | 1566 receive_options = [] |
1558 cc = cl.GetCCList().split(',') | 1567 cc = cl.GetCCList().split(',') |
1559 if options.cc: | 1568 if options.cc: |
1560 cc.extend(options.cc) | 1569 cc.extend(options.cc) |
1561 cc = filter(None, cc) | 1570 cc = filter(None, cc) |
1562 if cc: | 1571 if cc: |
1563 receive_options += ['--cc=' + email for email in cc] | 1572 receive_options += ['--cc=' + email for email in cc] |
1564 if change_desc.get_reviewers(): | 1573 if change_desc.get_reviewers(): |
1565 receive_options.extend( | 1574 receive_options.extend( |
1566 '--reviewer=' + email for email in change_desc.get_reviewers()) | 1575 '--reviewer=' + email for email in change_desc.get_reviewers()) |
1567 | 1576 |
1568 git_command = ['push'] | 1577 git_command = ['push'] |
1569 if receive_options: | 1578 if receive_options: |
1570 git_command.append('--receive-pack=git receive-pack %s' % | 1579 git_command.append('--receive-pack=git receive-pack %s' % |
1571 ' '.join(receive_options)) | 1580 ' '.join(receive_options)) |
1572 git_command += [remote, 'HEAD:refs/for/' + branch] | 1581 git_command += [remote, 'HEAD:refs/for/' + branch] |
1573 RunGit(git_command) | 1582 RunGit(git_command) |
1574 # TODO(ukai): parse Change-Id: and set issue number? | 1583 # TODO(ukai): parse Change-Id: and set issue number? |
1575 return 0 | 1584 return 0 |
1576 | 1585 |
1577 | 1586 |
1578 def RietveldUpload(options, args, cl): | 1587 def RietveldUpload(options, args, cl, change): |
1579 """upload the patch to rietveld.""" | 1588 """upload the patch to rietveld.""" |
1580 upload_args = ['--assume_yes'] # Don't ask about untracked files. | 1589 upload_args = ['--assume_yes'] # Don't ask about untracked files. |
1581 upload_args.extend(['--server', cl.GetRietveldServer()]) | 1590 upload_args.extend(['--server', cl.GetRietveldServer()]) |
1582 if options.emulate_svn_auto_props: | 1591 if options.emulate_svn_auto_props: |
1583 upload_args.append('--emulate_svn_auto_props') | 1592 upload_args.append('--emulate_svn_auto_props') |
1584 | 1593 |
1585 change_desc = None | 1594 change_desc = None |
1586 | 1595 |
1587 if options.email is not None: | 1596 if options.email is not None: |
1588 upload_args.extend(['--email', options.email]) | 1597 upload_args.extend(['--email', options.email]) |
1589 | 1598 |
1590 if cl.GetIssue(): | 1599 if cl.GetIssue(): |
1591 if options.title: | 1600 if options.title: |
1592 upload_args.extend(['--title', options.title]) | 1601 upload_args.extend(['--title', options.title]) |
1593 if options.message: | 1602 if options.message: |
1594 upload_args.extend(['--message', options.message]) | 1603 upload_args.extend(['--message', options.message]) |
1595 upload_args.extend(['--issue', str(cl.GetIssue())]) | 1604 upload_args.extend(['--issue', str(cl.GetIssue())]) |
1596 print ("This branch is associated with issue %s. " | 1605 print ("This branch is associated with issue %s. " |
1597 "Adding patch to that issue." % cl.GetIssue()) | 1606 "Adding patch to that issue." % cl.GetIssue()) |
1598 else: | 1607 else: |
1599 if options.title: | 1608 if options.title: |
1600 upload_args.extend(['--title', options.title]) | 1609 upload_args.extend(['--title', options.title]) |
1601 message = options.title or options.message or CreateDescriptionFromLog(args) | 1610 message = options.title or options.message or CreateDescriptionFromLog(args) |
1602 change_desc = ChangeDescription(message) | 1611 change_desc = ChangeDescription(message) |
1603 if options.reviewers: | 1612 if options.reviewers or options.tbr_owners: |
1604 change_desc.update_reviewers(options.reviewers) | 1613 change_desc.update_reviewers(options.reviewers, |
| 1614 options.tbr_owners, |
| 1615 change) |
1605 if not options.force: | 1616 if not options.force: |
1606 change_desc.prompt() | 1617 change_desc.prompt() |
1607 | 1618 |
1608 if not change_desc.description: | 1619 if not change_desc.description: |
1609 print "Description is empty; aborting." | 1620 print "Description is empty; aborting." |
1610 return 1 | 1621 return 1 |
1611 | 1622 |
1612 upload_args.extend(['--message', change_desc.description]) | 1623 upload_args.extend(['--message', change_desc.description]) |
1613 if change_desc.get_reviewers(): | 1624 if change_desc.get_reviewers(): |
1614 upload_args.append('--reviewers=' + ','.join(change_desc.get_reviewers())) | 1625 upload_args.append('--reviewers=' + ','.join(change_desc.get_reviewers())) |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1728 parser.add_option('-c', '--use-commit-queue', action='store_true', | 1739 parser.add_option('-c', '--use-commit-queue', action='store_true', |
1729 help='tell the commit queue to commit this patchset') | 1740 help='tell the commit queue to commit this patchset') |
1730 parser.add_option('--private', action='store_true', | 1741 parser.add_option('--private', action='store_true', |
1731 help='set the review private (rietveld only)') | 1742 help='set the review private (rietveld only)') |
1732 parser.add_option('--target_branch', | 1743 parser.add_option('--target_branch', |
1733 '--target-branch', | 1744 '--target-branch', |
1734 help='When uploading to gerrit, remote branch to ' | 1745 help='When uploading to gerrit, remote branch to ' |
1735 'use for CL. Default: master') | 1746 'use for CL. Default: master') |
1736 parser.add_option('--email', default=None, | 1747 parser.add_option('--email', default=None, |
1737 help='email address to use to connect to Rietveld') | 1748 help='email address to use to connect to Rietveld') |
| 1749 parser.add_option('--tbr-owners', dest='tbr_owners', action='store_true', |
| 1750 help='add a set of OWNERS to TBR') |
1738 | 1751 |
1739 add_git_similarity(parser) | 1752 add_git_similarity(parser) |
1740 (options, args) = parser.parse_args(args) | 1753 (options, args) = parser.parse_args(args) |
1741 | 1754 |
1742 if options.target_branch and not settings.GetIsGerrit(): | 1755 if options.target_branch and not settings.GetIsGerrit(): |
1743 parser.error('Use --target_branch for non gerrit repository.') | 1756 parser.error('Use --target_branch for non gerrit repository.') |
1744 | 1757 |
1745 if is_dirty_git_tree('upload'): | 1758 if is_dirty_git_tree('upload'): |
1746 return 1 | 1759 return 1 |
1747 | 1760 |
(...skipping 10 matching lines...) Expand all Loading... |
1758 args = [base_branch, 'HEAD'] | 1771 args = [base_branch, 'HEAD'] |
1759 | 1772 |
1760 # Apply watchlists on upload. | 1773 # Apply watchlists on upload. |
1761 change = cl.GetChange(base_branch, None) | 1774 change = cl.GetChange(base_branch, None) |
1762 watchlist = watchlists.Watchlists(change.RepositoryRoot()) | 1775 watchlist = watchlists.Watchlists(change.RepositoryRoot()) |
1763 files = [f.LocalPath() for f in change.AffectedFiles()] | 1776 files = [f.LocalPath() for f in change.AffectedFiles()] |
1764 if not options.bypass_watchlists: | 1777 if not options.bypass_watchlists: |
1765 cl.SetWatchers(watchlist.GetWatchersForPaths(files)) | 1778 cl.SetWatchers(watchlist.GetWatchersForPaths(files)) |
1766 | 1779 |
1767 if not options.bypass_hooks: | 1780 if not options.bypass_hooks: |
1768 if options.reviewers: | 1781 if options.reviewers or options.tbr_owners: |
1769 # Set the reviewer list now so that presubmit checks can access it. | 1782 # Set the reviewer list now so that presubmit checks can access it. |
1770 change_description = ChangeDescription(change.FullDescriptionText()) | 1783 change_description = ChangeDescription(change.FullDescriptionText()) |
1771 change_description.update_reviewers(options.reviewers) | 1784 change_description.update_reviewers(options.reviewers, |
| 1785 options.tbr_owners, |
| 1786 change) |
1772 change.SetDescriptionText(change_description.description) | 1787 change.SetDescriptionText(change_description.description) |
1773 hook_results = cl.RunHook(committing=False, | 1788 hook_results = cl.RunHook(committing=False, |
1774 may_prompt=not options.force, | 1789 may_prompt=not options.force, |
1775 verbose=options.verbose, | 1790 verbose=options.verbose, |
1776 change=change) | 1791 change=change) |
1777 if not hook_results.should_continue(): | 1792 if not hook_results.should_continue(): |
1778 return 1 | 1793 return 1 |
1779 if not options.reviewers and hook_results.reviewers: | 1794 if not options.reviewers and hook_results.reviewers: |
1780 options.reviewers = hook_results.reviewers.split(',') | 1795 options.reviewers = hook_results.reviewers.split(',') |
1781 | 1796 |
1782 if cl.GetIssue(): | 1797 if cl.GetIssue(): |
1783 latest_patchset = cl.GetMostRecentPatchset() | 1798 latest_patchset = cl.GetMostRecentPatchset() |
1784 local_patchset = cl.GetPatchset() | 1799 local_patchset = cl.GetPatchset() |
1785 if latest_patchset and local_patchset and local_patchset != latest_patchset: | 1800 if latest_patchset and local_patchset and local_patchset != latest_patchset: |
1786 print ('The last upload made from this repository was patchset #%d but ' | 1801 print ('The last upload made from this repository was patchset #%d but ' |
1787 'the most recent patchset on the server is #%d.' | 1802 'the most recent patchset on the server is #%d.' |
1788 % (local_patchset, latest_patchset)) | 1803 % (local_patchset, latest_patchset)) |
1789 print ('Uploading will still work, but if you\'ve uploaded to this issue ' | 1804 print ('Uploading will still work, but if you\'ve uploaded to this issue ' |
1790 'from another machine or branch the patch you\'re uploading now ' | 1805 'from another machine or branch the patch you\'re uploading now ' |
1791 'might not include those changes.') | 1806 'might not include those changes.') |
1792 ask_for_data('About to upload; enter to confirm.') | 1807 ask_for_data('About to upload; enter to confirm.') |
1793 | 1808 |
1794 print_stats(options.similarity, options.find_copies, args) | 1809 print_stats(options.similarity, options.find_copies, args) |
1795 if settings.GetIsGerrit(): | 1810 if settings.GetIsGerrit(): |
1796 return GerritUpload(options, args, cl) | 1811 return GerritUpload(options, args, cl, change) |
1797 ret = RietveldUpload(options, args, cl) | 1812 ret = RietveldUpload(options, args, cl, change) |
1798 if not ret: | 1813 if not ret: |
1799 git_set_branch_value('last-upload-hash', | 1814 git_set_branch_value('last-upload-hash', |
1800 RunGit(['rev-parse', 'HEAD']).strip()) | 1815 RunGit(['rev-parse', 'HEAD']).strip()) |
1801 | 1816 |
1802 return ret | 1817 return ret |
1803 | 1818 |
1804 | 1819 |
1805 def IsSubmoduleMergeCommit(ref): | 1820 def IsSubmoduleMergeCommit(ref): |
1806 # When submodules are added to the repo, we expect there to be a single | 1821 # When submodules are added to the repo, we expect there to be a single |
1807 # non-git-svn merge commit at remote HEAD with a signature comment. | 1822 # non-git-svn merge commit at remote HEAD with a signature comment. |
(...skipping 989 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2797 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 2812 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
2798 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 2813 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
2799 | 2814 |
2800 | 2815 |
2801 if __name__ == '__main__': | 2816 if __name__ == '__main__': |
2802 # These affect sys.stdout so do it outside of main() to simplify mocks in | 2817 # These affect sys.stdout so do it outside of main() to simplify mocks in |
2803 # unit testing. | 2818 # unit testing. |
2804 fix_encoding.fix_encoding() | 2819 fix_encoding.fix_encoding() |
2805 colorama.init() | 2820 colorama.init() |
2806 sys.exit(main(sys.argv[1:])) | 2821 sys.exit(main(sys.argv[1:])) |
OLD | NEW |