5 from cola
import gitcmds
8 class DiffParser(object):
9 """Handles parsing diff for use by the interactive index editor."""
10 def __init__(self
, model
, filename
='',
11 cached
=True, reverse
=False):
13 self
._header
_re
= re
.compile('^@@ -(\d+),(\d+) \+(\d+),(\d+) @@.*')
19 self
._diff
_offsets
= []
21 self
.head
= model
.head
22 self
.amending
= model
.amending()
29 (header
, diff
) = gitcmds
.diff_helper(head
=self
.head
,
30 amending
=self
.amending
,
32 with_diff_header
=True,
34 reverse
=cached
or reverse
)
40 # Always index into the non-reversed diff
41 self
.fwd_header
, self
.fwd_diff
= \
42 gitcmds
.diff_helper(head
=self
.head
,
43 amending
=self
.amending
,
45 with_diff_header
=True,
48 def write_diff(self
,filename
,which
,selected
=False,noop
=False):
49 """Writes a new diff corresponding to the user's selection."""
50 if not noop
and which
< len(self
.diffs
):
51 diff
= self
.diffs
[which
]
52 utils
.write(filename
, self
.header
+ '\n' + diff
+ '\n')
58 """Returns the list of diffs."""
61 def diff_subset(self
, diff
, start
, end
):
62 """Processes the diffs and returns a selected subset from that diff.
68 offset
= self
._diff
_spans
[diff
][0]
69 diffguts
= '\n'.join(self
._diffs
[diff
])
71 for line
in self
._diffs
[diff
]:
72 line_start
= offset
+ local_offset
73 local_offset
+= len(line
) + 1 #\n
74 line_end
= offset
+ local_offset
75 # |line1 |line2 |line3 |
78 # selection has head of diff (line3)
79 if start
< line_start
and end
> line_start
and end
< line_end
:
81 if line
.startswith('+'):
83 if line
.startswith('-'):
85 # selection has all of diff (line2)
86 elif start
<= line_start
and end
>= line_end
:
88 if line
.startswith('+'):
90 if line
.startswith('-'):
92 # selection has tail of diff (line1)
93 elif start
>= line_start
and start
< line_end
- 1:
95 if line
.startswith('+'):
97 if line
.startswith('-'):
100 # Don't add new lines unless selected
101 if line
.startswith('+'):
103 elif line
.startswith('-'):
104 # Don't remove lines unless selected
105 newdiff
.append(' ' + line
[1:])
109 new_count
= self
._headers
[diff
][1] + adds
- deletes
110 if new_count
!= self
._headers
[diff
][3]:
111 header
= '@@ -%d,%d +%d,%d @@' % (
112 self
._headers
[diff
][0],
113 self
._headers
[diff
][1],
114 self
._headers
[diff
][2],
118 return (self
.header
+ '\n' + '\n'.join(newdiff
) + '\n')
121 """Returns the line spans of each hunk."""
122 return self
._diff
_spans
125 """Returns the offsets."""
126 return self
._diff
_offsets
128 def set_diff_to_offset(self
, offset
):
129 """Sets the diff selection to be the hunk at a particular offset."""
131 self
.diffs
, self
.selected
= self
.diff_for_offset(offset
)
133 def set_diffs_to_range(self
, start
, end
):
134 """Sets the diff selection to be a range of hunks."""
137 self
.diffs
, self
.selected
= self
.diffs_for_range(start
,end
)
139 def diff_for_offset(self
, offset
):
140 """Returns the hunks for a particular offset."""
141 for idx
, diff_offset
in enumerate(self
._diff
_offsets
):
142 if offset
< diff_offset
:
143 return (['\n'.join(self
._diffs
[idx
])], [idx
])
146 def diffs_for_range(self
, start
, end
):
147 """Returns the hunks for a selected range."""
150 for idx
, span
in enumerate(self
._diff
_spans
):
151 has_end_of_diff
= start
>= span
[0] and start
< span
[1]
152 has_all_of_diff
= start
<= span
[0] and end
>= span
[1]
153 has_head_of_diff
= end
>= span
[0] and end
<= span
[1]
155 selected_diff
=(has_end_of_diff
159 diff
= '\n'.join(self
._diffs
[idx
])
162 return diffs
, indices
164 def parse_diff(self
, diff
):
165 """Parses a diff and extracts headers, offsets, hunks, etc.
171 for idx
, line
in enumerate(diff
.split('\n')):
172 match
= self
._header
_re
.match(line
)
174 self
._headers
.append([
180 self
._diffs
.append( [line
] )
182 line_len
= len(line
) + 1 #\n
183 self
._diff
_spans
.append([total_offset
,
184 total_offset
+ line_len
])
185 total_offset
+= line_len
186 self
._diff
_offsets
.append(total_offset
)
190 errmsg
= 'Malformed diff?\n\n%s' % diff
191 raise AssertionError, errmsg
192 line_len
= len(line
) + 1
193 total_offset
+= line_len
195 self
._diffs
[self
._idx
].append(line
)
196 self
._diff
_spans
[-1][-1] += line_len
197 self
._diff
_offsets
[self
._idx
] += line_len
199 def process_diff_selection(self
, selected
, offset
, selection
,
200 apply_to_worktree
=False):
201 """Processes a diff selection and applies changes to git."""
203 # qt destroys \r\n and makes it \n with no way of going back.
204 # boo! we work around that here.
205 # I think this was win32-specific. We might want to do
206 # this on win32 only (TODO verify)
207 if selection
not in self
.fwd_diff
:
208 special_selection
= selection
.replace('\n', '\r\n')
209 if special_selection
in self
.fwd_diff
:
210 selection
= special_selection
213 start
= self
.fwd_diff
.index(selection
)
214 end
= start
+ len(selection
)
215 self
.set_diffs_to_range(start
, end
)
217 self
.set_diff_to_offset(offset
)
222 # Process diff selection only
224 for idx
in self
.selected
:
225 contents
= self
.diff_subset(idx
, start
, end
)
228 tmpfile
= utils
.tmp_filename('selection')
229 utils
.write(tmpfile
, contents
)
230 if apply_to_worktree
:
231 stat
, out
= self
.model
.apply_diff_to_worktree(tmpfile
)
233 status
= max(status
, stat
)
235 stat
, out
= self
.model
.apply_diff(tmpfile
)
237 status
= max(status
, stat
)
239 # Process a complete hunk
241 for idx
, diff
in enumerate(self
.diffs
):
242 tmpfile
= utils
.tmp_filename('patch%02d' % idx
)
243 if not self
.write_diff(tmpfile
,idx
):
245 if apply_to_worktree
:
246 stat
, out
= self
.model
.apply_diff_to_worktree(tmpfile
)
248 status
= max(status
, stat
)
250 stat
, out
= self
.model
.apply_diff(tmpfile
)
252 status
= max(status
, stat
)
254 return status
, output