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) |