diff --git a/gitpylib/file.py b/gitpylib/file.py index 0f6ce77..3fad88e 100644 --- a/gitpylib/file.py +++ b/gitpylib/file.py @@ -5,7 +5,9 @@ """Module for dealing with Git files.""" +import collections import os.path +import re import common @@ -14,6 +16,12 @@ FILE_NOT_FOUND = 2 FILE_NOT_FOUND_AT_CP = 3 +# Possible diff output lines. +DIFF_INFO = 4 # line carrying diff info for new hunk. +DIFF_SAME = 5 # line that git diff includes for context. +DIFF_ADDED = 6 +DIFF_MINUS = 7 + def stage(fp): """Stages the given file. @@ -118,7 +126,7 @@ def diff(fp): fp = common.real_case(fp) out, unused_err = common.safe_git_call('diff -- "%s"' % fp) - return out + return _process_diff_output(_strip_diff_header(out.splitlines())) def staged_diff(fp): @@ -133,4 +141,75 @@ def staged_diff(fp): fp = common.real_case(fp) out, unused_err = common.safe_git_call('diff --cached -- "%s"' % fp) - return out + return _process_diff_output(_strip_diff_header(out.splitlines())) + + +# Private functions. + + +def _strip_diff_header(diff_out): + """Removes the diff header lines.""" + first_non_header_line = 0 + for line in diff_out: + if line.startswith('@@'): + break + first_non_header_line += 1 + return diff_out[first_non_header_line:] + + +def _process_diff_output(diff_out): + """Process the git diff output. + + Args: + diff_out: a list of lines output by the git diff command. + + Returns: + a 2-tuple of: + - a list of namedtuples with fields 'line', 'status', 'old_line_number' + and 'new_line_number' where 'status' is one of DIFF_INFO, DIFF_SAME, + DIFF_ADDED or DIFF_MINUS and 'old_line_number', 'new_line_number' + correspond to the line's old line number and new line number respectively. + (Note that, for example, if the line is DIFF_ADDED, then 'old_line_number' + is None since that line is not present in the old file). + - max_line_digits: return the maximum amount of line digits found while + parsing the git diff output, this is useful for padding. + """ + MIN_LINE_PADDING = 8 + LineData = collections.namedtuple( + 'LineData', + ['line', 'status', 'old_line_number', 'new_line_number']) + + resulting = [] # accumulates line information for formatting. + max_line_digits = 0 + old_line_number = 1 + new_line_number = 1 + + for line in diff_out: + # @@ -(start of old),(length of old) +(start of new),(length of new) @@ + new_hunk_regex = "^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@" + new_hunk_info = re.search(new_hunk_regex, line) + if new_hunk_info: + old_line_number = int(new_hunk_info.group(1)) + old_diff_length = int(new_hunk_info.group(2)) + new_line_number = int(new_hunk_info.group(3)) + new_diff_length = int(new_hunk_info.group(4)) + resulting.append( + LineData(line, DIFF_INFO, old_line_number, new_line_number)) + max_line_digits = max([old_line_number + old_diff_length, + new_line_number + new_diff_length, + max_line_digits]) # start + length of each diff. + elif line.startswith(' '): + resulting.append( + LineData(line, DIFF_SAME, old_line_number, new_line_number)) + old_line_number += 1 + new_line_number += 1 + elif line.startswith('-'): + resulting.append(LineData(line, DIFF_MINUS, old_line_number, None)) + old_line_number += 1 + elif line.startswith('+'): + resulting.append(LineData(line, DIFF_ADDED, None, new_line_number)) + new_line_number += 1 + + max_line_digits = len(str(max_line_digits)) # digits = len(string of number). + max_line_digits = max(MIN_LINE_PADDING, max_line_digits + 1) + return resulting, max_line_digits