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, branch
=None, reverse
=False):
13 self
._header
_re
= re
.compile('^@@ -(\d+),(\d+) \+(\d+),(\d+) @@.*')
19 self
._diff
_offsets
= []
27 (header
, diff
) = gitcmds
.diff_helper(filename
=filename
,
29 with_diff_header
=True,
30 cached
=cached
and not bool(branch
),
31 reverse
=cached
or bool(branch
) or reverse
)
37 # Always index into the non-reversed diff
38 self
.fwd_header
, self
.fwd_diff
= \
39 gitcmds
.diff_helper(filename
=filename
,
41 with_diff_header
=True,
42 cached
=cached
and not bool(branch
),
45 def write_diff(self
,filename
,which
,selected
=False,noop
=False):
46 """Writes a new diff corresponding to the user's selection."""
47 if not noop
and which
< len(self
.diffs
):
48 diff
= self
.diffs
[which
]
49 utils
.write(filename
, self
.header
+ '\n' + diff
+ '\n')
55 """Returns the list of diffs."""
58 def diff_subset(self
, diff
, start
, end
):
59 """Processes the diffs and returns a selected subset from that diff.
65 offset
= self
._diff
_spans
[diff
][0]
66 diffguts
= '\n'.join(self
._diffs
[diff
])
68 for line
in self
._diffs
[diff
]:
69 line_start
= offset
+ local_offset
70 local_offset
+= len(line
) + 1 #\n
71 line_end
= offset
+ local_offset
72 # |line1 |line2 |line3 |
75 # selection has head of diff (line3)
76 if start
< line_start
and end
> line_start
and end
< line_end
:
78 if line
.startswith('+'):
80 if line
.startswith('-'):
82 # selection has all of diff (line2)
83 elif start
<= line_start
and end
>= line_end
:
85 if line
.startswith('+'):
87 if line
.startswith('-'):
89 # selection has tail of diff (line1)
90 elif start
>= line_start
and start
< line_end
- 1:
92 if line
.startswith('+'):
94 if line
.startswith('-'):
97 # Don't add new lines unless selected
98 if line
.startswith('+'):
100 elif line
.startswith('-'):
101 # Don't remove lines unless selected
102 newdiff
.append(' ' + line
[1:])
106 new_count
= self
._headers
[diff
][1] + adds
- deletes
107 if new_count
!= self
._headers
[diff
][3]:
108 header
= '@@ -%d,%d +%d,%d @@' % (
109 self
._headers
[diff
][0],
110 self
._headers
[diff
][1],
111 self
._headers
[diff
][2],
115 return (self
.header
+ '\n' + '\n'.join(newdiff
) + '\n')
118 """Returns the line spans of each hunk."""
119 return self
._diff
_spans
122 """Returns the offsets."""
123 return self
._diff
_offsets
125 def set_diff_to_offset(self
, offset
):
126 """Sets the diff selection to be the hunk at a particular offset."""
128 self
.diffs
, self
.selected
= self
.diff_for_offset(offset
)
130 def set_diffs_to_range(self
, start
, end
):
131 """Sets the diff selection to be a range of hunks."""
134 self
.diffs
, self
.selected
= self
.diffs_for_range(start
,end
)
136 def diff_for_offset(self
, offset
):
137 """Returns the hunks for a particular offset."""
138 for idx
, diff_offset
in enumerate(self
._diff
_offsets
):
139 if offset
< diff_offset
:
140 return (['\n'.join(self
._diffs
[idx
])], [idx
])
143 def diffs_for_range(self
, start
, end
):
144 """Returns the hunks for a selected range."""
147 for idx
, span
in enumerate(self
._diff
_spans
):
148 has_end_of_diff
= start
>= span
[0] and start
< span
[1]
149 has_all_of_diff
= start
<= span
[0] and end
>= span
[1]
150 has_head_of_diff
= end
>= span
[0] and end
<= span
[1]
152 selected_diff
=(has_end_of_diff
156 diff
= '\n'.join(self
._diffs
[idx
])
159 return diffs
, indices
161 def parse_diff(self
, diff
):
162 """Parses a diff and extracts headers, offsets, hunks, etc.
168 for idx
, line
in enumerate(diff
.split('\n')):
169 match
= self
._header
_re
.match(line
)
171 self
._headers
.append([
177 self
._diffs
.append( [line
] )
179 line_len
= len(line
) + 1 #\n
180 self
._diff
_spans
.append([total_offset
,
181 total_offset
+ line_len
])
182 total_offset
+= line_len
183 self
._diff
_offsets
.append(total_offset
)
187 errmsg
= 'Malformed diff?\n\n%s' % diff
188 raise AssertionError, errmsg
189 line_len
= len(line
) + 1
190 total_offset
+= line_len
192 self
._diffs
[self
._idx
].append(line
)
193 self
._diff
_spans
[-1][-1] += line_len
194 self
._diff
_offsets
[self
._idx
] += line_len
196 def process_diff_selection(self
, selected
, offset
, selection
,
197 apply_to_worktree
=False):
198 """Processes a diff selection and applies changes to git."""
200 # qt destroys \r\n and makes it \n with no way of going back.
201 # boo! we work around that here.
202 # I think this was win32-specific. We might want to do
203 # this on win32 only (TODO verify)
204 if selection
not in self
.fwd_diff
:
205 special_selection
= selection
.replace('\n', '\r\n')
206 if special_selection
in self
.fwd_diff
:
207 selection
= special_selection
210 start
= self
.fwd_diff
.index(selection
)
211 end
= start
+ len(selection
)
212 self
.set_diffs_to_range(start
, end
)
214 self
.set_diff_to_offset(offset
)
219 # Process diff selection only
221 for idx
in self
.selected
:
222 contents
= self
.diff_subset(idx
, start
, end
)
225 tmpfile
= self
.model
.tmp_filename()
226 utils
.write(tmpfile
, contents
)
227 if apply_to_worktree
:
228 stat
, out
= self
.model
.apply_diff_to_worktree(tmpfile
)
230 status
= max(status
, stat
)
232 stat
, out
= self
.model
.apply_diff(tmpfile
)
234 status
= max(status
, stat
)
236 # Process a complete hunk
238 for idx
, diff
in enumerate(self
.diffs
):
239 tmpfile
= self
.model
.tmp_filename()
240 if not self
.write_diff(tmpfile
,idx
):
242 if apply_to_worktree
:
243 stat
, out
= self
.model
.apply_diff_to_worktree(tmpfile
)
245 status
= max(status
, stat
)
247 stat
, out
= self
.model
.apply_diff(tmpfile
)
249 status
= max(status
, stat
)
251 return status
, output