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 |