| Index: git_cl.py
|
| diff --git a/git_cl.py b/git_cl.py
|
| index f37007148a55bd080a93922fdd2fef5b7da69045..2c391c7fc28007b0a8377babddffc9fb4179cf4f 100755
|
| --- a/git_cl.py
|
| +++ b/git_cl.py
|
| @@ -2912,9 +2912,69 @@ def color_for_status(status):
|
| 'error': Fore.WHITE,
|
| }.get(status, Fore.WHITE)
|
|
|
| +def split_diff_files(diff):
|
| + """Splits a diff for the individual files and outputs a dict
|
| + {file name: diff}.
|
| + """
|
| + files = {}
|
| + current_lines = []
|
| + current_file = ''
|
| + def append():
|
| + if current_file:
|
| + files[current_file] = '\n'.join(current_lines+[''])
|
| + for line in diff.splitlines():
|
| + if line.startswith('Index: '):
|
| + continue
|
| + if line.startswith('diff '):
|
| + append()
|
| + current_lines = []
|
| + full_file_name = line.split()[-1]
|
| + current_file = '/'.join(full_file_name.split('/')[1:])
|
| + line = re.sub(' [ab]/'+re.escape(current_file), ' '+current_file, line)
|
| + if line.startswith('+++ ') or line.startswith('--- '):
|
| + line = re.sub(' [ab]/'+re.escape(current_file), ' '+current_file, line)
|
| + current_lines.append(line)
|
| + append()
|
| + return files
|
| +
|
| +def diff_compare(left, right):
|
| + """Compares two diffs without regarding actual file names, line number
|
| + information or commit hashes.
|
| + Returns True if they are equal, False otherwise.
|
| + """
|
| + file_pattern = re.compile('^(diff |\+\+\+ |--- ).*$', re.MULTILINE)
|
| + line_pattern = re.compile('^@@[ +\-0-9,]+@@', re.MULTILINE)
|
| + index_pattern = re.compile('^index [0-9a-f]+\.\.[0-9a-f]+', re.MULTILINE)
|
| + left = re.sub(line_pattern, '@@', re.sub(index_pattern, 'index',
|
| + re.sub(file_pattern, '\\1', left)))
|
| + right = re.sub(line_pattern, '@@', re.sub(index_pattern, 'index',
|
| + re.sub(file_pattern, '\\1', right)))
|
| + return left == right
|
| +
|
| +def fetch_cl_status(cl, auth_config=None):
|
| + """Fetches information for an issue and returns (cl, status, outdated).
|
| + """
|
| + status = cl.GetStatus()
|
| + outdated = False
|
| + if cl.GetIssue():
|
| + latest_patchset = cl.GetMostRecentPatchset()
|
| + uploaded_diff = cl.GetPatchSetDiff(cl.GetIssue(), latest_patchset)
|
| + uploaded_file_diffs = split_diff_files(uploaded_diff)
|
| + change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
|
| + local_file_diffs = {f.LocalPath(): f.GenerateScmDiff()
|
| + for f in change.AffectedFiles()}
|
| + files = sorted(uploaded_file_diffs.keys())
|
| + if local_file_diffs and \
|
| + (files != sorted(local_file_diffs.keys()) or \
|
| + any(not diff_compare(uploaded_file_diffs[f], local_file_diffs[f])
|
| + for f in uploaded_file_diffs.keys())):
|
| + outdated = True
|
| +
|
| + return (cl, status, outdated)
|
| +
|
| def get_cl_statuses(
|
| changes, fine_grained, max_processes=None, auth_config=None):
|
| - """Returns a blocking iterable of (cl, status) for given branches.
|
| + """Returns a blocking iterable of (cl, status, outdated) for given branches.
|
|
|
| If fine_grained is true, this will fetch CL statuses from the server.
|
| Otherwise, simply indicate if there's a matching url for the given branches.
|
| @@ -2932,20 +2992,19 @@ def get_cl_statuses(
|
| # Process one branch synchronously to work through authentication, then
|
| # spawn processes to process all the other branches in parallel.
|
| if changes:
|
| - fetch = lambda cl: (cl, cl.GetStatus())
|
| - yield fetch(change_cls[0])
|
| + yield fetch_cl_status(change_cls[0])
|
|
|
| changes_to_fetch = change_cls[1:]
|
| pool = ThreadPool(
|
| min(max_processes, len(changes_to_fetch))
|
| if max_processes is not None
|
| else len(changes_to_fetch))
|
| - for x in pool.imap_unordered(fetch, changes_to_fetch):
|
| + for x in pool.imap_unordered(fetch_cl_status, changes_to_fetch):
|
| yield x
|
| else:
|
| # Do not use GetApprovingReviewers(), since it requires an HTTP request.
|
| for cl in change_cls:
|
| - yield (cl, 'waiting' if cl.GetIssueURL() else 'error')
|
| + yield (cl, 'waiting' if cl.GetIssueURL() else 'error', False)
|
|
|
|
|
| def upload_branch_deps(cl, args):
|
| @@ -3111,22 +3170,21 @@ def CMDstatus(parser, args):
|
| for cl in sorted(changes, key=lambda c: c.GetBranch()):
|
| branch = cl.GetBranch()
|
| while branch not in branch_statuses:
|
| - c, status = output.next()
|
| - branch_statuses[c.GetBranch()] = status
|
| - status = branch_statuses.pop(branch)
|
| + c, status, outdated = output.next()
|
| + branch_statuses[c.GetBranch()] = (status, outdated)
|
| + status, outdated = branch_statuses.pop(branch)
|
| url = cl.GetIssueURL()
|
| if url and (not status or status == 'error'):
|
| # The issue probably doesn't exist anymore.
|
| url += ' (broken)'
|
| -
|
| - color = color_for_status(status)
|
| - reset = Fore.RESET
|
| - if not setup_color.IS_TTY:
|
| - color = ''
|
| - reset = ''
|
| + make_color = lambda c : c if setup_color.IS_TTY else ''
|
| + color = make_color(color_for_status(status))
|
| + reset = make_color(Fore.RESET)
|
| + color_outdated = make_color(Fore.RED)
|
| status_str = '(%s)' % status if status else ''
|
| - print ' %*s : %s%s %s%s' % (
|
| - alignment, ShortBranchName(branch), color, url,
|
| + outdated_str = '%soutdated%s ' % (color_outdated, color) if outdated else ''
|
| + print ' %*s : %s%s %s%s%s' % (
|
| + alignment, ShortBranchName(branch), color, url, outdated_str,
|
| status_str, reset)
|
|
|
| cl = Changelist(auth_config=auth_config)
|
|
|