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 distutils.version import LooseVersion | 10 from distutils.version import LooseVersion |
| 11 from multiprocessing.pool import ThreadPool | 11 from multiprocessing.pool import ThreadPool |
| 12 import base64 | 12 import base64 |
| 13 import collections | 13 import collections |
| 14 import glob | 14 import glob |
| 15 import httplib | 15 import httplib |
| 16 import json | 16 import json |
| 17 import logging | 17 import logging |
| 18 import multiprocessing | 18 import multiprocessing |
| 19 import optparse | 19 import optparse |
| 20 import os | 20 import os |
| 21 import Queue | |
| 22 import re | 21 import re |
| 23 import stat | 22 import stat |
| 24 import sys | 23 import sys |
| 25 import tempfile | |
| 26 import textwrap | 24 import textwrap |
| 27 import time | 25 import time |
| 28 import traceback | 26 import traceback |
| 29 import urllib | 27 import urllib |
| 30 import urllib2 | 28 import urllib2 |
| 31 import urlparse | 29 import urlparse |
| 32 import uuid | 30 import uuid |
| 33 import webbrowser | 31 import webbrowser |
| 34 import zlib | 32 import zlib |
| 35 | 33 |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 125 stdout=subprocess2.PIPE, | 123 stdout=subprocess2.PIPE, |
| 126 stderr=stderr) | 124 stderr=stderr) |
| 127 return code, out[0] | 125 return code, out[0] |
| 128 except ValueError: | 126 except ValueError: |
| 129 # When the subprocess fails, it returns None. That triggers a ValueError | 127 # When the subprocess fails, it returns None. That triggers a ValueError |
| 130 # when trying to unpack the return value into (out, code). | 128 # when trying to unpack the return value into (out, code). |
| 131 return 1, '' | 129 return 1, '' |
| 132 | 130 |
| 133 | 131 |
| 134 def RunGitSilent(args): | 132 def RunGitSilent(args): |
| 135 """Returns stdout, suppresses stderr and ingores the return code.""" | 133 """Returns stdout, suppresses stderr and ignores the return code.""" |
| 136 return RunGitWithCode(args, suppress_stderr=True)[1] | 134 return RunGitWithCode(args, suppress_stderr=True)[1] |
| 137 | 135 |
| 138 | 136 |
| 139 def IsGitVersionAtLeast(min_version): | 137 def IsGitVersionAtLeast(min_version): |
| 140 prefix = 'git version ' | 138 prefix = 'git version ' |
| 141 version = RunGit(['--version']).strip() | 139 version = RunGit(['--version']).strip() |
| 142 return (version.startswith(prefix) and | 140 return (version.startswith(prefix) and |
| 143 LooseVersion(version[len(prefix):]) >= LooseVersion(min_version)) | 141 LooseVersion(version[len(prefix):]) >= LooseVersion(min_version)) |
| 144 | 142 |
| 145 | 143 |
| (...skipping 776 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 922 global settings | 920 global settings |
| 923 if not settings: | 921 if not settings: |
| 924 # Happens when git_cl.py is used as a utility library. | 922 # Happens when git_cl.py is used as a utility library. |
| 925 settings = Settings() | 923 settings = Settings() |
| 926 | 924 |
| 927 if issue: | 925 if issue: |
| 928 assert codereview, 'codereview must be known, if issue is known' | 926 assert codereview, 'codereview must be known, if issue is known' |
| 929 | 927 |
| 930 self.branchref = branchref | 928 self.branchref = branchref |
| 931 if self.branchref: | 929 if self.branchref: |
| 930 assert branchref.startswith('refs/heads/') | |
| 932 self.branch = ShortBranchName(self.branchref) | 931 self.branch = ShortBranchName(self.branchref) |
| 933 else: | 932 else: |
| 934 self.branch = None | 933 self.branch = None |
| 935 self.upstream_branch = None | 934 self.upstream_branch = None |
| 936 self.lookedup_issue = False | 935 self.lookedup_issue = False |
| 937 self.issue = issue or None | 936 self.issue = issue or None |
| 938 self.has_description = False | 937 self.has_description = False |
| 939 self.description = None | 938 self.description = None |
| 940 self.lookedup_patchset = False | 939 self.lookedup_patchset = False |
| 941 self.patchset = None | 940 self.patchset = None |
| (...skipping 500 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1442 | 1441 |
| 1443 def GetApprovingReviewers(self): | 1442 def GetApprovingReviewers(self): |
| 1444 return self._codereview_impl.GetApprovingReviewers() | 1443 return self._codereview_impl.GetApprovingReviewers() |
| 1445 | 1444 |
| 1446 def GetMostRecentPatchset(self): | 1445 def GetMostRecentPatchset(self): |
| 1447 return self._codereview_impl.GetMostRecentPatchset() | 1446 return self._codereview_impl.GetMostRecentPatchset() |
| 1448 | 1447 |
| 1449 def __getattr__(self, attr): | 1448 def __getattr__(self, attr): |
| 1450 # This is because lots of untested code accesses Rietveld-specific stuff | 1449 # This is because lots of untested code accesses Rietveld-specific stuff |
| 1451 # directly, and it's hard to fix for sure. So, just let it work, and fix | 1450 # directly, and it's hard to fix for sure. So, just let it work, and fix |
| 1452 # on a cases by case basis. | 1451 # on a case by case basis. |
| 1453 return getattr(self._codereview_impl, attr) | 1452 return getattr(self._codereview_impl, attr) |
| 1454 | 1453 |
| 1455 | 1454 |
| 1456 class _ChangelistCodereviewBase(object): | 1455 class _ChangelistCodereviewBase(object): |
| 1457 """Abstract base class encapsulating codereview specifics of a changelist.""" | 1456 """Abstract base class encapsulating codereview specifics of a changelist.""" |
| 1458 def __init__(self, changelist): | 1457 def __init__(self, changelist): |
| 1459 self._changelist = changelist # instance of Changelist | 1458 self._changelist = changelist # instance of Changelist |
| 1460 | 1459 |
| 1461 def __getattr__(self, attr): | 1460 def __getattr__(self, attr): |
| 1462 # Forward methods to changelist. | 1461 # Forward methods to changelist. |
| (...skipping 465 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1928 target_ref = GetTargetRef(remote, remote_branch, options.target_branch, | 1927 target_ref = GetTargetRef(remote, remote_branch, options.target_branch, |
| 1929 settings.GetPendingRefPrefix()) | 1928 settings.GetPendingRefPrefix()) |
| 1930 if target_ref: | 1929 if target_ref: |
| 1931 upload_args.extend(['--target_ref', target_ref]) | 1930 upload_args.extend(['--target_ref', target_ref]) |
| 1932 | 1931 |
| 1933 # Look for dependent patchsets. See crbug.com/480453 for more details. | 1932 # Look for dependent patchsets. See crbug.com/480453 for more details. |
| 1934 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch()) | 1933 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch()) |
| 1935 upstream_branch = ShortBranchName(upstream_branch) | 1934 upstream_branch = ShortBranchName(upstream_branch) |
| 1936 if remote is '.': | 1935 if remote is '.': |
| 1937 # A local branch is being tracked. | 1936 # A local branch is being tracked. |
| 1938 local_branch = ShortBranchName(upstream_branch) | 1937 local_branch = upstream_branch |
| 1939 if settings.GetIsSkipDependencyUpload(local_branch): | 1938 if settings.GetIsSkipDependencyUpload(local_branch): |
| 1940 print | 1939 print |
| 1941 print ('Skipping dependency patchset upload because git config ' | 1940 print ('Skipping dependency patchset upload because git config ' |
| 1942 'branch.%s.skip-deps-uploads is set to True.' % local_branch) | 1941 'branch.%s.skip-deps-uploads is set to True.' % local_branch) |
| 1943 print | 1942 print |
| 1944 else: | 1943 else: |
| 1945 auth_config = auth.extract_auth_config_from_options(options) | 1944 auth_config = auth.extract_auth_config_from_options(options) |
| 1946 branch_cl = Changelist(branchref=local_branch, | 1945 branch_cl = Changelist(branchref='refs/heads/'+local_branch, |
| 1947 auth_config=auth_config) | 1946 auth_config=auth_config) |
| 1948 branch_cl_issue_url = branch_cl.GetIssueURL() | 1947 branch_cl_issue_url = branch_cl.GetIssueURL() |
| 1949 branch_cl_issue = branch_cl.GetIssue() | 1948 branch_cl_issue = branch_cl.GetIssue() |
| 1950 branch_cl_patchset = branch_cl.GetPatchset() | 1949 branch_cl_patchset = branch_cl.GetPatchset() |
| 1951 if branch_cl_issue_url and branch_cl_issue and branch_cl_patchset: | 1950 if branch_cl_issue_url and branch_cl_issue and branch_cl_patchset: |
| 1952 upload_args.extend( | 1951 upload_args.extend( |
| 1953 ['--depends_on_patchset', '%s:%s' % ( | 1952 ['--depends_on_patchset', '%s:%s' % ( |
| 1954 branch_cl_issue, branch_cl_patchset)]) | 1953 branch_cl_issue, branch_cl_patchset)]) |
| 1955 print( | 1954 print( |
| 1956 '\n' | 1955 '\n' |
| (...skipping 955 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2912 return { | 2911 return { |
| 2913 'unsent': Fore.RED, | 2912 'unsent': Fore.RED, |
| 2914 'waiting': Fore.BLUE, | 2913 'waiting': Fore.BLUE, |
| 2915 'reply': Fore.YELLOW, | 2914 'reply': Fore.YELLOW, |
| 2916 'lgtm': Fore.GREEN, | 2915 'lgtm': Fore.GREEN, |
| 2917 'commit': Fore.MAGENTA, | 2916 'commit': Fore.MAGENTA, |
| 2918 'closed': Fore.CYAN, | 2917 'closed': Fore.CYAN, |
| 2919 'error': Fore.WHITE, | 2918 'error': Fore.WHITE, |
| 2920 }.get(status, Fore.WHITE) | 2919 }.get(status, Fore.WHITE) |
| 2921 | 2920 |
| 2922 def fetch_cl_status(branch, auth_config=None): | |
| 2923 """Fetches information for an issue and returns (branch, issue, status).""" | |
| 2924 cl = Changelist(branchref=branch, auth_config=auth_config) | |
| 2925 url = cl.GetIssueURL() | |
| 2926 status = cl.GetStatus() | |
| 2927 | |
| 2928 if url and (not status or status == 'error'): | |
| 2929 # The issue probably doesn't exist anymore. | |
| 2930 url += ' (broken)' | |
| 2931 | |
| 2932 return (branch, url, status) | |
| 2933 | 2921 |
| 2934 def get_cl_statuses( | 2922 def get_cl_statuses( |
|
tandrii(chromium)
2016/05/20 20:56:06
nit: merge two lines.
| |
| 2935 branches, fine_grained, max_processes=None, auth_config=None): | 2923 changes, fine_grained, max_processes=None): |
| 2936 """Returns a blocking iterable of (branch, issue, status) for given branches. | 2924 """Returns a blocking iterable of (cl, status) for given branches. |
| 2937 | 2925 |
| 2938 If fine_grained is true, this will fetch CL statuses from the server. | 2926 If fine_grained is true, this will fetch CL statuses from the server. |
| 2939 Otherwise, simply indicate if there's a matching url for the given branches. | 2927 Otherwise, simply indicate if there's a matching url for the given branches. |
| 2940 | 2928 |
| 2941 If max_processes is specified, it is used as the maximum number of processes | 2929 If max_processes is specified, it is used as the maximum number of processes |
| 2942 to spawn to fetch CL status from the server. Otherwise 1 process per branch is | 2930 to spawn to fetch CL status from the server. Otherwise 1 process per branch is |
| 2943 spawned. | 2931 spawned. |
| 2944 | 2932 |
| 2945 See GetStatus() for a list of possible statuses. | 2933 See GetStatus() for a list of possible statuses. |
| 2946 """ | 2934 """ |
| 2947 def fetch(branch): | |
| 2948 if not branch: | |
| 2949 return None | |
| 2950 | |
| 2951 return fetch_cl_status(branch, auth_config=auth_config) | |
| 2952 | |
| 2953 # Silence upload.py otherwise it becomes unwieldly. | 2935 # Silence upload.py otherwise it becomes unwieldly. |
| 2954 upload.verbosity = 0 | 2936 upload.verbosity = 0 |
| 2955 | 2937 |
| 2956 if fine_grained: | 2938 if fine_grained: |
| 2957 # Process one branch synchronously to work through authentication, then | 2939 # Process one branch synchronously to work through authentication, then |
| 2958 # spawn processes to process all the other branches in parallel. | 2940 # spawn processes to process all the other branches in parallel. |
| 2959 if branches: | 2941 if changes: |
| 2942 fetch = lambda cl: (cl, cl.GetStatus()) | |
| 2943 yield fetch(changes[0]) | |
| 2960 | 2944 |
| 2961 yield fetch(branches[0]) | 2945 changes_to_fetch = changes[1:] |
| 2946 pool = ThreadPool( | |
| 2947 min(max_processes, len(changes_to_fetch)) | |
| 2948 if max_processes is not None | |
| 2949 else len(changes_to_fetch)) | |
| 2962 | 2950 |
| 2963 branches_to_fetch = branches[1:] | 2951 fetched_cls = set() |
| 2964 pool = ThreadPool( | 2952 it = pool.imap_unordered(fetch, changes_to_fetch).__iter__() |
| 2965 min(max_processes, len(branches_to_fetch)) | |
| 2966 if max_processes is not None | |
| 2967 else len(branches_to_fetch)) | |
| 2968 | |
| 2969 fetched_branches = set() | |
| 2970 it = pool.imap_unordered(fetch, branches_to_fetch).__iter__() | |
| 2971 while True: | 2953 while True: |
| 2972 try: | 2954 try: |
| 2973 row = it.next(timeout=5) | 2955 row = it.next(timeout=5) |
| 2974 except multiprocessing.TimeoutError: | 2956 except multiprocessing.TimeoutError: |
| 2975 break | 2957 break |
| 2976 | 2958 |
| 2977 fetched_branches.add(row[0]) | 2959 fetched_cls.add(row[0]) |
| 2978 yield row | 2960 yield row |
| 2979 | 2961 |
| 2980 # Add any branches that failed to fetch. | 2962 # Add any branches that failed to fetch. |
| 2981 for b in set(branches_to_fetch) - fetched_branches: | 2963 for cl in set(changes_to_fetch) - fetched_cls: |
| 2982 cl = Changelist(branchref=b, auth_config=auth_config) | 2964 yield (cl, 'error') |
| 2983 yield (b, cl.GetIssueURL() if b else None, 'error') | |
| 2984 | 2965 |
| 2985 else: | 2966 else: |
| 2986 # Do not use GetApprovingReviewers(), since it requires an HTTP request. | 2967 # Do not use GetApprovingReviewers(), since it requires an HTTP request. |
| 2987 for b in branches: | 2968 for cl in changes: |
| 2988 cl = Changelist(branchref=b, auth_config=auth_config) | 2969 yield (cl, 'waiting' if cl.GetIssueURL() else 'error') |
| 2989 url = cl.GetIssueURL() if b else None | |
| 2990 yield (b, url, 'waiting' if url else 'error') | |
| 2991 | 2970 |
| 2992 | 2971 |
| 2993 def upload_branch_deps(cl, args): | 2972 def upload_branch_deps(cl, args): |
| 2994 """Uploads CLs of local branches that are dependents of the current branch. | 2973 """Uploads CLs of local branches that are dependents of the current branch. |
| 2995 | 2974 |
| 2996 If the local branch dependency tree looks like: | 2975 If the local branch dependency tree looks like: |
| 2997 test1 -> test2.1 -> test3.1 | 2976 test1 -> test2.1 -> test3.1 |
| 2998 -> test3.2 | 2977 -> test3.2 |
| 2999 -> test2.2 -> test3.3 | 2978 -> test2.2 -> test3.3 |
| 3000 | 2979 |
| (...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3133 url = cl.GetIssueURL() | 3112 url = cl.GetIssueURL() |
| 3134 if url: | 3113 if url: |
| 3135 print url | 3114 print url |
| 3136 return 0 | 3115 return 0 |
| 3137 | 3116 |
| 3138 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) | 3117 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) |
| 3139 if not branches: | 3118 if not branches: |
| 3140 print('No local branch found.') | 3119 print('No local branch found.') |
| 3141 return 0 | 3120 return 0 |
| 3142 | 3121 |
| 3143 changes = ( | 3122 changes = [ |
| 3144 Changelist(branchref=b, auth_config=auth_config) | 3123 Changelist(branchref=b, auth_config=auth_config) |
| 3145 for b in branches.splitlines()) | 3124 for b in branches.splitlines()] |
| 3146 # TODO(tandrii): refactor to use CLs list instead of branches list. | |
| 3147 branches = [c.GetBranch() for c in changes] | |
| 3148 alignment = max(5, max(len(b) for b in branches)) | |
| 3149 print 'Branches associated with reviews:' | 3125 print 'Branches associated with reviews:' |
| 3150 output = get_cl_statuses(branches, | 3126 output = get_cl_statuses(changes, |
| 3151 fine_grained=not options.fast, | 3127 fine_grained=not options.fast, |
| 3152 max_processes=options.maxjobs, | 3128 max_processes=options.maxjobs) |
| 3153 auth_config=auth_config) | |
| 3154 | 3129 |
| 3155 branch_statuses = {} | 3130 branch_statuses = {} |
| 3156 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) | 3131 alignment = max(5, max(len(ShortBranchName(c.GetBranch())) for c in changes)) |
| 3157 for branch in sorted(branches): | 3132 for cl in sorted(changes, key=lambda c: c.GetBranch()): |
| 3133 branch = cl.GetBranch() | |
| 3158 while branch not in branch_statuses: | 3134 while branch not in branch_statuses: |
| 3159 b, i, status = output.next() | 3135 c, status = output.next() |
| 3160 branch_statuses[b] = (i, status) | 3136 branch_statuses[c.GetBranch()] = status |
| 3161 issue_url, status = branch_statuses.pop(branch) | 3137 status = branch_statuses.pop(branch) |
| 3138 url = cl.GetIssueURL() | |
| 3139 if url and (not status or status == 'error'): | |
| 3140 # The issue probably doesn't exist anymore. | |
| 3141 url += ' (broken)' | |
| 3142 | |
| 3162 color = color_for_status(status) | 3143 color = color_for_status(status) |
| 3163 reset = Fore.RESET | 3144 reset = Fore.RESET |
| 3164 if not setup_color.IS_TTY: | 3145 if not setup_color.IS_TTY: |
| 3165 color = '' | 3146 color = '' |
| 3166 reset = '' | 3147 reset = '' |
| 3167 status_str = '(%s)' % status if status else '' | 3148 status_str = '(%s)' % status if status else '' |
| 3168 print ' %*s : %s%s %s%s' % ( | 3149 print ' %*s : %s%s %s%s' % ( |
| 3169 alignment, ShortBranchName(branch), color, issue_url, status_str, | 3150 alignment, ShortBranchName(branch), color, url, |
| 3170 reset) | 3151 status_str, reset) |
| 3171 | 3152 |
| 3172 cl = Changelist(auth_config=auth_config) | 3153 cl = Changelist(auth_config=auth_config) |
| 3173 print | 3154 print |
| 3174 print 'Current branch:', | 3155 print 'Current branch:', |
| 3175 print cl.GetBranch() | 3156 print cl.GetBranch() |
| 3176 if not cl.GetIssue(): | 3157 if not cl.GetIssue(): |
| 3177 print 'No issue assigned.' | 3158 print 'No issue assigned.' |
| 3178 return 0 | 3159 return 0 |
| 3179 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) | 3160 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) |
| 3180 if not options.fast: | 3161 if not options.fast: |
| (...skipping 1720 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 4901 if __name__ == '__main__': | 4882 if __name__ == '__main__': |
| 4902 # These affect sys.stdout so do it outside of main() to simplify mocks in | 4883 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 4903 # unit testing. | 4884 # unit testing. |
| 4904 fix_encoding.fix_encoding() | 4885 fix_encoding.fix_encoding() |
| 4905 setup_color.init() | 4886 setup_color.init() |
| 4906 try: | 4887 try: |
| 4907 sys.exit(main(sys.argv[1:])) | 4888 sys.exit(main(sys.argv[1:])) |
| 4908 except KeyboardInterrupt: | 4889 except KeyboardInterrupt: |
| 4909 sys.stderr.write('interrupted\n') | 4890 sys.stderr.write('interrupted\n') |
| 4910 sys.exit(1) | 4891 sys.exit(1) |
| OLD | NEW |