5
5
"""Module for dealing with Git files."""
6
6
7
7
8
+ import collections
8
9
import os .path
10
+ import re
9
11
10
12
import common
11
13
14
16
FILE_NOT_FOUND = 2
15
17
FILE_NOT_FOUND_AT_CP = 3
16
18
19
+ # Possible diff output lines.
20
+ DIFF_INFO = 4 # line carrying diff info for new hunk.
21
+ DIFF_SAME = 5 # line that git diff includes for context.
22
+ DIFF_ADDED = 6
23
+ DIFF_MINUS = 7
24
+
17
25
18
26
def stage (fp ):
19
27
"""Stages the given file.
@@ -118,7 +126,7 @@ def diff(fp):
118
126
fp = common .real_case (fp )
119
127
120
128
out , unused_err = common .safe_git_call ('diff -- "%s"' % fp )
121
- return out
129
+ return _process_diff_output ( _strip_diff_header ( out . splitlines ()))
122
130
123
131
124
132
def staged_diff (fp ):
@@ -133,4 +141,75 @@ def staged_diff(fp):
133
141
fp = common .real_case (fp )
134
142
135
143
out , unused_err = common .safe_git_call ('diff --cached -- "%s"' % fp )
136
- return out
144
+ return _process_diff_output (_strip_diff_header (out .splitlines ()))
145
+
146
+
147
+ # Private functions.
148
+
149
+
150
+ def _strip_diff_header (diff_out ):
151
+ """Removes the diff header lines."""
152
+ first_non_header_line = 0
153
+ for line in diff_out :
154
+ if line .startswith ('@@' ):
155
+ break
156
+ first_non_header_line += 1
157
+ return diff_out [first_non_header_line :]
158
+
159
+
160
+ def _process_diff_output (diff_out ):
161
+ """Process the git diff output.
162
+
163
+ Args:
164
+ diff_out: a list of lines output by the git diff command.
165
+
166
+ Returns:
167
+ a 2-tuple of:
168
+ - a list of namedtuples with fields 'line', 'status', 'old_line_number'
169
+ and 'new_line_number' where 'status' is one of DIFF_INFO, DIFF_SAME,
170
+ DIFF_ADDED or DIFF_MINUS and 'old_line_number', 'new_line_number'
171
+ correspond to the line's old line number and new line number respectively.
172
+ (Note that, for example, if the line is DIFF_ADDED, then 'old_line_number'
173
+ is None since that line is not present in the old file).
174
+ - max_line_digits: return the maximum amount of line digits found while
175
+ parsing the git diff output, this is useful for padding.
176
+ """
177
+ MIN_LINE_PADDING = 8
178
+ LineData = collections .namedtuple (
179
+ 'LineData' ,
180
+ ['line' , 'status' , 'old_line_number' , 'new_line_number' ])
181
+
182
+ resulting = [] # accumulates line information for formatting.
183
+ max_line_digits = 0
184
+ old_line_number = 1
185
+ new_line_number = 1
186
+
187
+ for line in diff_out :
188
+ # @@ -(start of old),(length of old) +(start of new),(length of new) @@
189
+ new_hunk_regex = "^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@"
190
+ new_hunk_info = re .search (new_hunk_regex , line )
191
+ if new_hunk_info :
192
+ old_line_number = int (new_hunk_info .group (1 ))
193
+ old_diff_length = int (new_hunk_info .group (2 ))
194
+ new_line_number = int (new_hunk_info .group (3 ))
195
+ new_diff_length = int (new_hunk_info .group (4 ))
196
+ resulting .append (
197
+ LineData (line , DIFF_INFO , old_line_number , new_line_number ))
198
+ max_line_digits = max ([old_line_number + old_diff_length ,
199
+ new_line_number + new_diff_length ,
200
+ max_line_digits ]) # start + length of each diff.
201
+ elif line .startswith (' ' ):
202
+ resulting .append (
203
+ LineData (line , DIFF_SAME , old_line_number , new_line_number ))
204
+ old_line_number += 1
205
+ new_line_number += 1
206
+ elif line .startswith ('-' ):
207
+ resulting .append (LineData (line , DIFF_MINUS , old_line_number , None ))
208
+ old_line_number += 1
209
+ elif line .startswith ('+' ):
210
+ resulting .append (LineData (line , DIFF_ADDED , None , new_line_number ))
211
+ new_line_number += 1
212
+
213
+ max_line_digits = len (str (max_line_digits )) # digits = len(string of number).
214
+ max_line_digits = max (MIN_LINE_PADDING , max_line_digits + 1 )
215
+ return resulting , max_line_digits
0 commit comments