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 |
| (...skipping 2911 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2922 def fetch_cl_status(branch, auth_config=None): | 2922 def fetch_cl_status(branch, auth_config=None): |
| 2923 """Fetches information for an issue and returns (branch, issue, status).""" | 2923 """Fetches information for an issue and returns (branch, issue, status).""" |
| 2924 cl = Changelist(branchref=branch, auth_config=auth_config) | 2924 cl = Changelist(branchref=branch, auth_config=auth_config) |
| 2925 url = cl.GetIssueURL() | 2925 url = cl.GetIssueURL() |
| 2926 status = cl.GetStatus() | 2926 status = cl.GetStatus() |
| 2927 | 2927 |
| 2928 if url and (not status or status == 'error'): | 2928 if url and (not status or status == 'error'): |
| 2929 # The issue probably doesn't exist anymore. | 2929 # The issue probably doesn't exist anymore. |
| 2930 url += ' (broken)' | 2930 url += ' (broken)' |
| 2931 | 2931 |
| 2932 return (branch, url, status) | 2932 return (branch, cl.GetIssue(), url, status) |
| 2933 | 2933 |
| 2934 def get_cl_statuses( | 2934 def get_cl_statuses( |
| 2935 branches, fine_grained, max_processes=None, auth_config=None): | 2935 branches, fine_grained, max_processes=None, auth_config=None): |
| 2936 """Returns a blocking iterable of (branch, issue, status) for given branches. | 2936 """Returns a blocking iterable of (branch, issue ID, issue URL, status) for |
| 2937 given branches. | |
| 2937 | 2938 |
| 2938 If fine_grained is true, this will fetch CL statuses from the server. | 2939 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. | 2940 Otherwise, simply indicate if there's a matching url for the given branches. |
| 2940 | 2941 |
| 2941 If max_processes is specified, it is used as the maximum number of processes | 2942 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 | 2943 to spawn to fetch CL status from the server. Otherwise 1 process per branch is |
| 2943 spawned. | 2944 spawned. |
| 2944 | 2945 |
| 2945 See GetStatus() for a list of possible statuses. | 2946 See GetStatus() for a list of possible statuses. |
| 2946 """ | 2947 """ |
| 2947 def fetch(branch): | 2948 def fetch(branch): |
| 2948 if not branch: | 2949 if not branch: |
| 2949 return None | 2950 return None |
| 2950 | 2951 |
| 2951 return fetch_cl_status(branch, auth_config=auth_config) | 2952 return fetch_cl_status(branch, auth_config=auth_config) |
| 2952 | 2953 |
| 2953 # Silence upload.py otherwise it becomes unwieldly. | 2954 # Silence upload.py otherwise it becomes unwieldly. |
| 2954 upload.verbosity = 0 | 2955 upload.verbosity = 0 |
| 2955 | 2956 |
| 2956 if fine_grained: | 2957 if fine_grained: |
| 2957 # Process one branch synchronously to work through authentication, then | 2958 # Process one branch synchronously to work through authentication, then |
| 2958 # spawn processes to process all the other branches in parallel. | 2959 # spawn processes to process all the other branches in parallel. |
| 2959 if branches: | 2960 if branches: |
| 2960 | 2961 |
| 2961 yield fetch(branches[0]) | 2962 yield fetch(branches[0]) |
| 2962 | 2963 |
| 2963 branches_to_fetch = branches[1:] | 2964 branches_to_fetch = branches[1:] |
| 2965 if len(branches_to_fetch) == 0: | |
| 2966 # Exit early if there was only one branch to fetch. | |
| 2967 return | |
|
tandrii(chromium)
2016/05/20 20:56:19
are you sure this works? I thought return can't be
Kevin M
2016/05/20 22:18:14
"return" from a generator raises a StopIteration.
| |
| 2968 | |
| 2964 pool = ThreadPool( | 2969 pool = ThreadPool( |
| 2965 min(max_processes, len(branches_to_fetch)) | 2970 min(max_processes, len(branches_to_fetch)) |
| 2966 if max_processes is not None | 2971 if max_processes is not None |
| 2967 else len(branches_to_fetch)) | 2972 else len(branches_to_fetch)) |
| 2968 | 2973 |
| 2969 fetched_branches = set() | 2974 fetched_branches = set() |
| 2970 it = pool.imap_unordered(fetch, branches_to_fetch).__iter__() | 2975 it = pool.imap_unordered(fetch, branches_to_fetch).__iter__() |
| 2971 while True: | 2976 while True: |
| 2972 try: | 2977 try: |
| 2973 row = it.next(timeout=5) | 2978 row = it.next(timeout=5) |
| 2974 except multiprocessing.TimeoutError: | 2979 except multiprocessing.TimeoutError: |
| 2975 break | 2980 break |
| 2976 | 2981 |
| 2977 fetched_branches.add(row[0]) | 2982 fetched_branches.add(row[0]) |
| 2978 yield row | 2983 yield row |
| 2979 | 2984 |
| 2980 # Add any branches that failed to fetch. | 2985 # Add any branches that failed to fetch. |
| 2981 for b in set(branches_to_fetch) - fetched_branches: | 2986 for b in set(branches_to_fetch) - fetched_branches: |
| 2982 cl = Changelist(branchref=b, auth_config=auth_config) | 2987 cl = Changelist(branchref=b, auth_config=auth_config) |
| 2983 yield (b, cl.GetIssueURL() if b else None, 'error') | 2988 yield (b, cl.GetIssue(), cl.GetIssueURL() if b else None, 'error') |
| 2984 | 2989 |
| 2985 else: | 2990 else: |
| 2986 # Do not use GetApprovingReviewers(), since it requires an HTTP request. | 2991 # Do not use GetApprovingReviewers(), since it requires an HTTP request. |
| 2987 for b in branches: | 2992 for b in branches: |
| 2988 cl = Changelist(branchref=b, auth_config=auth_config) | 2993 cl = Changelist(branchref=b, auth_config=auth_config) |
| 2989 url = cl.GetIssueURL() if b else None | 2994 url = cl.GetIssueURL() if b else None |
| 2990 yield (b, url, 'waiting' if url else 'error') | 2995 yield (b, cl.GetIssue(), url, 'waiting' if url else 'error') |
| 2991 | 2996 |
| 2992 | 2997 |
| 2993 def upload_branch_deps(cl, args): | 2998 def upload_branch_deps(cl, args): |
| 2994 """Uploads CLs of local branches that are dependents of the current branch. | 2999 """Uploads CLs of local branches that are dependents of the current branch. |
| 2995 | 3000 |
| 2996 If the local branch dependency tree looks like: | 3001 If the local branch dependency tree looks like: |
| 2997 test1 -> test2.1 -> test3.1 | 3002 test1 -> test2.1 -> test3.1 |
| 2998 -> test3.2 | 3003 -> test3.2 |
| 2999 -> test2.2 -> test3.3 | 3004 -> test2.2 -> test3.3 |
| 3000 | 3005 |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3082 | 3087 |
| 3083 print | 3088 print |
| 3084 print 'Upload complete for dependent branches!' | 3089 print 'Upload complete for dependent branches!' |
| 3085 for dependent_branch in dependents: | 3090 for dependent_branch in dependents: |
| 3086 upload_status = 'failed' if failures.get(dependent_branch) else 'succeeded' | 3091 upload_status = 'failed' if failures.get(dependent_branch) else 'succeeded' |
| 3087 print ' %s : %s' % (dependent_branch, upload_status) | 3092 print ' %s : %s' % (dependent_branch, upload_status) |
| 3088 print | 3093 print |
| 3089 | 3094 |
| 3090 return 0 | 3095 return 0 |
| 3091 | 3096 |
| 3097 def CMDcleanup(parser, args): | |
| 3098 """Archives closed branches from the Git using tags and removes them from | |
| 3099 the Git branch list.""" | |
| 3100 parser.add_option( | |
| 3101 '-j', '--maxjobs', action='store', type=int, | |
| 3102 help='The maximum number of jobs to use when retrieving review status') | |
| 3103 | |
| 3104 parser.add_option( | |
| 3105 '-y', action='store_true', dest='no_prompt', default=False, | |
|
tandrii(chromium)
2016/05/20 20:56:19
-f, --force to be inline with other cmds.
Kevin M
2016/05/20 22:18:14
Done.
| |
| 3106 help='Bypasses the confirmation prompt.') | |
| 3107 | |
| 3108 auth.add_auth_options(parser) | |
| 3109 options, args = parser.parse_args(args) | |
| 3110 if args: | |
| 3111 parser.error('Unsupported args: %s' % args) | |
| 3112 auth_config = auth.extract_auth_config_from_options(options) | |
| 3113 | |
| 3114 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) | |
| 3115 if not branches: | |
| 3116 print('No local branch found.') | |
|
tandrii(chromium)
2016/05/20 20:56:19
nit: why parenthesis here and not below?
Kevin M
2016/05/20 22:18:14
C++ habits, I guess. Done.
| |
| 3117 return 0 | |
| 3118 | |
| 3119 print 'Finding all branches associated with closed issues...' | |
| 3120 changes = ( | |
| 3121 Changelist(branchref=b, auth_config=auth_config) | |
| 3122 for b in branches.splitlines()) | |
| 3123 branches = [c.GetBranch() for c in changes] | |
|
martiniss
2016/05/20 03:11:55
Any reason to not combine this and the previous li
Kevin M
2016/05/20 16:54:41
No reason other than avoiding nested list comprehe
| |
| 3124 alignment = max(5, max(len(b) for b in branches)) | |
| 3125 branch_statuses = get_cl_statuses(branches, | |
| 3126 fine_grained=True, | |
| 3127 max_processes=options.maxjobs, | |
| 3128 auth_config=auth_config) | |
| 3129 proposal = [(branch, 'submitted-%s-%s' % (branch, issue_id)) | |
| 3130 for branch, issue_id, _, status in branch_statuses | |
| 3131 if status == 'closed'] | |
| 3132 | |
| 3133 if len(proposal) == 0: | |
| 3134 print 'No closed branches found.' | |
| 3135 return 0 | |
| 3136 | |
| 3137 print '\nBranches with closed issues that will be tagged and deleted:' | |
| 3138 print "%*s | %s" % (alignment, "Branch name", "Archival tag name") | |
| 3139 for next_item in proposal: | |
| 3140 print '%*s %s' % (alignment, next_item[0], next_item[1]) | |
| 3141 | |
| 3142 prompt = None | |
| 3143 if not options.no_prompt: | |
| 3144 prompt = raw_input('\nProceed with deletion (Y/N)? ') | |
| 3145 if prompt == 'Y' or prompt == 'y' or options.no_prompt: | |
| 3146 for next_item in proposal: | |
| 3147 branch, tagname = next_item | |
| 3148 RunGit(['tag', tagname, branch]) | |
|
martiniss
2016/05/20 03:11:55
What is the purpose of tagging the branch? Bookkee
Kevin M
2016/05/20 16:54:41
The tags allow you to access your local Git commit
| |
| 3149 RunGit(['branch', '-D', branch]) | |
| 3150 print '\nJob\'s done!' | |
| 3151 else: | |
| 3152 print 'Aborted.' | |
| 3153 | |
| 3154 return 0 | |
| 3092 | 3155 |
| 3093 def CMDstatus(parser, args): | 3156 def CMDstatus(parser, args): |
| 3094 """Show status of changelists. | 3157 """Show status of changelists. |
| 3095 | 3158 |
| 3096 Colors are used to tell the state of the CL unless --fast is used: | 3159 Colors are used to tell the state of the CL unless --fast is used: |
| 3097 - Red not sent for review or broken | 3160 - Red not sent for review or broken |
| 3098 - Blue waiting for review | 3161 - Blue waiting for review |
| 3099 - Yellow waiting for you to reply to review | 3162 - Yellow waiting for you to reply to review |
| 3100 - Green LGTM'ed | 3163 - Green LGTM'ed |
| 3101 - Magenta in the commit queue | 3164 - Magenta in the commit queue |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3149 print 'Branches associated with reviews:' | 3212 print 'Branches associated with reviews:' |
| 3150 output = get_cl_statuses(branches, | 3213 output = get_cl_statuses(branches, |
| 3151 fine_grained=not options.fast, | 3214 fine_grained=not options.fast, |
| 3152 max_processes=options.maxjobs, | 3215 max_processes=options.maxjobs, |
| 3153 auth_config=auth_config) | 3216 auth_config=auth_config) |
| 3154 | 3217 |
| 3155 branch_statuses = {} | 3218 branch_statuses = {} |
| 3156 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) | 3219 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) |
| 3157 for branch in sorted(branches): | 3220 for branch in sorted(branches): |
| 3158 while branch not in branch_statuses: | 3221 while branch not in branch_statuses: |
| 3159 b, i, status = output.next() | 3222 b, _, i, status = output.next() |
| 3160 branch_statuses[b] = (i, status) | 3223 branch_statuses[b] = (i, status) |
| 3161 issue_url, status = branch_statuses.pop(branch) | 3224 issue_url, status = branch_statuses.pop(branch) |
| 3162 color = color_for_status(status) | 3225 color = color_for_status(status) |
| 3163 reset = Fore.RESET | 3226 reset = Fore.RESET |
| 3164 if not setup_color.IS_TTY: | 3227 if not setup_color.IS_TTY: |
| 3165 color = '' | 3228 color = '' |
| 3166 reset = '' | 3229 reset = '' |
| 3167 status_str = '(%s)' % status if status else '' | 3230 status_str = '(%s)' % status if status else '' |
| 3168 print ' %*s : %s%s %s%s' % ( | 3231 print ' %*s : %s%s %s%s' % ( |
| 3169 alignment, ShortBranchName(branch), color, issue_url, status_str, | 3232 alignment, ShortBranchName(branch), color, issue_url, status_str, |
| (...skipping 1731 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 4901 if __name__ == '__main__': | 4964 if __name__ == '__main__': |
| 4902 # These affect sys.stdout so do it outside of main() to simplify mocks in | 4965 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 4903 # unit testing. | 4966 # unit testing. |
| 4904 fix_encoding.fix_encoding() | 4967 fix_encoding.fix_encoding() |
| 4905 setup_color.init() | 4968 setup_color.init() |
| 4906 try: | 4969 try: |
| 4907 sys.exit(main(sys.argv[1:])) | 4970 sys.exit(main(sys.argv[1:])) |
| 4908 except KeyboardInterrupt: | 4971 except KeyboardInterrupt: |
| 4909 sys.stderr.write('interrupted\n') | 4972 sys.stderr.write('interrupted\n') |
| 4910 sys.exit(1) | 4973 sys.exit(1) |
| OLD | NEW |