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 |