| 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 |