 Chromium Code Reviews
 Chromium Code Reviews Issue 1895463002:
  Mark outdated branches on 'cl status'  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@show-outdated-cls
    
  
    Issue 1895463002:
  Mark outdated branches on 'cl status'  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@show-outdated-cls| 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 2882 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2893 return { | 2893 return { | 
| 2894 'unsent': Fore.RED, | 2894 'unsent': Fore.RED, | 
| 2895 'waiting': Fore.BLUE, | 2895 'waiting': Fore.BLUE, | 
| 2896 'reply': Fore.YELLOW, | 2896 'reply': Fore.YELLOW, | 
| 2897 'lgtm': Fore.GREEN, | 2897 'lgtm': Fore.GREEN, | 
| 2898 'commit': Fore.MAGENTA, | 2898 'commit': Fore.MAGENTA, | 
| 2899 'closed': Fore.CYAN, | 2899 'closed': Fore.CYAN, | 
| 2900 'error': Fore.WHITE, | 2900 'error': Fore.WHITE, | 
| 2901 }.get(status, Fore.WHITE) | 2901 }.get(status, Fore.WHITE) | 
| 2902 | 2902 | 
| 2903 def split_diff_files(diff): | |
| 2904 """Splits a diff for the individual files and outputs a dict | |
| 2905 {file name: diff}. | |
| 2906 """ | |
| 2907 files = {} | |
| 2908 current_lines = [] | |
| 2909 current_file = '' | |
| 2910 def append(): | |
| 2911 if current_file: | |
| 2912 files[current_file] = '\n'.join(current_lines+['']) | |
| 2913 for line in diff.splitlines(): | |
| 2914 if line.startswith('Index: '): | |
| 2915 continue | |
| 2916 if line.startswith('diff '): | |
| 2917 append() | |
| 2918 current_lines = [] | |
| 2919 full_file_name = line.split()[-1] | |
| 2920 current_file = '/'.join(full_file_name.split('/')[1:]) | |
| 2921 line = re.sub(' [ab]/'+re.escape(current_file), ' '+current_file, line) | |
| 2922 if line.startswith('+++ ') or line.startswith('--- '): | |
| 2923 line = re.sub(' [ab]/'+re.escape(current_file), ' '+current_file, line) | |
| 2924 current_lines.append(line) | |
| 2925 append() | |
| 2926 return files | |
| 2927 | |
| 2928 def diff_compare(left, right): | |
| 2929 """Compares two diffs without regarding actual file names, line number | |
| 2930 information or commit hashes. | |
| 
tandrii(chromium)
2016/04/15 15:15:13
nit: add empty line.
 | |
| 2931 Returns True if they are equal, False otherwise. | |
| 2932 """ | |
| 2933 file_pattern = re.compile('^(diff |\+\+\+ |--- ).*$', re.MULTILINE) | |
| 2934 line_pattern = re.compile('^@@[ +\-0-9,]+@@', re.MULTILINE) | |
| 2935 index_pattern = re.compile('^index [0-9a-f]+\.\.[0-9a-f]+', re.MULTILINE) | |
| 2936 left = re.sub(line_pattern, '@@', re.sub(index_pattern, 'index', | |
| 2937 re.sub(file_pattern, '\\1', left))) | |
| 2938 right = re.sub(line_pattern, '@@', re.sub(index_pattern, 'index', | |
| 2939 re.sub(file_pattern, '\\1', right))) | |
| 2940 return left == right | |
| 2941 | |
| 2903 def fetch_cl_status(cl, auth_config=None): | 2942 def fetch_cl_status(cl, auth_config=None): | 
| 2904 """Fetches information for an issue and returns (branch, issue, status).""" | 2943 """Fetches information for an issue and returns | 
| 2944 (branch, issue, status, outdated). | |
| 2945 """ | |
| 2905 url = cl.GetIssueURL() | 2946 url = cl.GetIssueURL() | 
| 2906 status = cl.GetStatus() | 2947 status = cl.GetStatus() | 
| 2948 outdated = False | |
| 2949 if cl.GetIssue(): | |
| 2950 latest_patchset = cl.GetMostRecentPatchset() | |
| 2951 uploaded_diff = cl.GetPatchSetDiff(cl.GetIssue(), latest_patchset) | |
| 2952 uploaded_file_diffs = split_diff_files(uploaded_diff) | |
| 2953 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None) | |
| 2954 local_file_diffs = {f.LocalPath(): f.GenerateScmDiff() | |
| 2955 for f in change.AffectedFiles()} | |
| 2956 files = sorted(uploaded_file_diffs.keys()) | |
| 2957 if files != sorted(local_file_diffs.keys()) or \ | |
| 2958 any(not diff_compare(uploaded_file_diffs[f], local_file_diffs[f]) | |
| 2959 for f in uploaded_file_diffs.keys()): | |
| 2960 outdated = True | |
| 2907 | 2961 | 
| 2908 if url and (not status or status == 'error'): | 2962 if url and (not status or status == 'error'): | 
| 2909 # The issue probably doesn't exist anymore. | 2963 # The issue probably doesn't exist anymore. | 
| 2910 url += ' (broken)' | 2964 url += ' (broken)' | 
| 2911 | 2965 | 
| 2912 return (cl, url, status) | 2966 return (cl, url, status, outdated) | 
| 2913 | 2967 | 
| 2914 def get_cl_statuses( | 2968 def get_cl_statuses( | 
| 2915 changes, fine_grained, max_processes=None, auth_config=None): | 2969 changes, fine_grained, max_processes=None, auth_config=None): | 
| 2916 """Returns a blocking iterable of (branch, issue, color) for given branches. | 2970 """Returns a blocking iterable of (branch, issue, color, outdated) for given | 
| 2971 branches. | |
| 2917 | 2972 | 
| 2918 If fine_grained is true, this will fetch CL statuses from the server. | 2973 If fine_grained is true, this will fetch CL statuses from the server. | 
| 2919 Otherwise, simply indicate if there's a matching url for the given branches. | 2974 Otherwise, simply indicate if there's a matching url for the given branches. | 
| 2920 | 2975 | 
| 2921 If max_processes is specified, it is used as the maximum number of processes | 2976 If max_processes is specified, it is used as the maximum number of processes | 
| 2922 to spawn to fetch CL status from the server. Otherwise 1 process per branch is | 2977 to spawn to fetch CL status from the server. Otherwise 1 process per branch is | 
| 2923 spawned. | 2978 spawned. | 
| 2924 """ | 2979 """ | 
| 2925 # Silence upload.py otherwise it becomes unwieldly. | 2980 # Silence upload.py otherwise it becomes unwieldly. | 
| 2926 upload.verbosity = 0 | 2981 upload.verbosity = 0 | 
| (...skipping 10 matching lines...) Expand all Loading... | |
| 2937 min(max_processes, len(changes_to_fetch)) | 2992 min(max_processes, len(changes_to_fetch)) | 
| 2938 if max_processes is not None | 2993 if max_processes is not None | 
| 2939 else len(changes_to_fetch)) | 2994 else len(changes_to_fetch)) | 
| 2940 for x in pool.imap_unordered(fetch, changes_to_fetch): | 2995 for x in pool.imap_unordered(fetch, changes_to_fetch): | 
| 2941 yield x | 2996 yield x | 
| 2942 else: | 2997 else: | 
| 2943 # Do not use GetApprovingReviewers(), since it requires an HTTP request. | 2998 # Do not use GetApprovingReviewers(), since it requires an HTTP request. | 
| 2944 for c in changes: | 2999 for c in changes: | 
| 2945 cl = Changelist(branchref=c.GetBranch(), auth_config=auth_config) | 3000 cl = Changelist(branchref=c.GetBranch(), auth_config=auth_config) | 
| 2946 url = cl.GetIssueURL() | 3001 url = cl.GetIssueURL() | 
| 2947 yield (c, url, 'waiting' if url else 'error') | 3002 yield (c, url, 'waiting' if url else 'error', False) | 
| 2948 | 3003 | 
| 2949 | 3004 | 
| 2950 def upload_branch_deps(cl, args): | 3005 def upload_branch_deps(cl, args): | 
| 2951 """Uploads CLs of local branches that are dependents of the current branch. | 3006 """Uploads CLs of local branches that are dependents of the current branch. | 
| 2952 | 3007 | 
| 2953 If the local branch dependency tree looks like: | 3008 If the local branch dependency tree looks like: | 
| 2954 test1 -> test2.1 -> test3.1 | 3009 test1 -> test2.1 -> test3.1 | 
| 2955 -> test3.2 | 3010 -> test3.2 | 
| 2956 -> test2.2 -> test3.3 | 3011 -> test2.2 -> test3.3 | 
| 2957 | 3012 | 
| (...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3104 output = get_cl_statuses(changes, | 3159 output = get_cl_statuses(changes, | 
| 3105 fine_grained=not options.fast, | 3160 fine_grained=not options.fast, | 
| 3106 max_processes=options.maxjobs, | 3161 max_processes=options.maxjobs, | 
| 3107 auth_config=auth_config) | 3162 auth_config=auth_config) | 
| 3108 | 3163 | 
| 3109 branch_statuses = {} | 3164 branch_statuses = {} | 
| 3110 alignment = max(5, max(len(ShortBranchName(c.GetBranch())) for c in changes)) | 3165 alignment = max(5, max(len(ShortBranchName(c.GetBranch())) for c in changes)) | 
| 3111 for cl in sorted(changes, key = lambda c : c.GetBranch()): | 3166 for cl in sorted(changes, key = lambda c : c.GetBranch()): | 
| 3112 branch = cl.GetBranch() | 3167 branch = cl.GetBranch() | 
| 3113 while branch not in branch_statuses: | 3168 while branch not in branch_statuses: | 
| 3114 c, i, status = output.next() | 3169 c, i, status, outdated = output.next() | 
| 3115 branch_statuses[c.GetBranch()] = (i, status) | 3170 branch_statuses[c.GetBranch()] = (i, status, outdated) | 
| 3116 issue_url, status = branch_statuses.pop(branch) | 3171 issue_url, status, outdated = branch_statuses.pop(branch) | 
| 3117 color = color_for_status(status) | 3172 make_color = lambda c : c if setup_color.IS_TTY else '' | 
| 3118 reset = Fore.RESET | 3173 color = make_color(color_for_status(status)) | 
| 3119 if not setup_color.IS_TTY: | 3174 reset = make_color(Fore.RESET) | 
| 3120 color = '' | 3175 color_outdated = make_color(Fore.RED) | 
| 3121 reset = '' | |
| 3122 status_str = '(%s)' % status if status else '' | 3176 status_str = '(%s)' % status if status else '' | 
| 3123 print ' %*s : %s%s %s%s' % ( | 3177 outdated_str = '%soutdated%s ' % (color_outdated, color) if outdated else '' | 
| 3124 alignment, ShortBranchName(branch), color, issue_url, | 3178 print ' %*s : %s%s %s%s%s' % ( | 
| 3179 alignment, ShortBranchName(branch), color, issue_url, outdated_str, | |
| 3125 status_str, reset) | 3180 status_str, reset) | 
| 3126 | 3181 | 
| 3127 cl = Changelist(auth_config=auth_config) | 3182 cl = Changelist(auth_config=auth_config) | 
| 3128 print | 3183 print | 
| 3129 print 'Current branch:', | 3184 print 'Current branch:', | 
| 3130 print cl.GetBranch() | 3185 print cl.GetBranch() | 
| 3131 if not cl.GetIssue(): | 3186 if not cl.GetIssue(): | 
| 3132 print 'No issue assigned.' | 3187 print 'No issue assigned.' | 
| 3133 return 0 | 3188 return 0 | 
| 3134 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) | 3189 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) | 
| (...skipping 1691 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 4826 if __name__ == '__main__': | 4881 if __name__ == '__main__': | 
| 4827 # These affect sys.stdout so do it outside of main() to simplify mocks in | 4882 # These affect sys.stdout so do it outside of main() to simplify mocks in | 
| 4828 # unit testing. | 4883 # unit testing. | 
| 4829 fix_encoding.fix_encoding() | 4884 fix_encoding.fix_encoding() | 
| 4830 setup_color.init() | 4885 setup_color.init() | 
| 4831 try: | 4886 try: | 
| 4832 sys.exit(main(sys.argv[1:])) | 4887 sys.exit(main(sys.argv[1:])) | 
| 4833 except KeyboardInterrupt: | 4888 except KeyboardInterrupt: | 
| 4834 sys.stderr.write('interrupted\n') | 4889 sys.stderr.write('interrupted\n') | 
| 4835 sys.exit(1) | 4890 sys.exit(1) | 
| OLD | NEW |