5 from cola
import gitcmds
6 from cola
import gitcfg
12 def __init__(self
, begin
, end
):
13 self
.begin
= self
._parse
(begin
)
14 self
.end
= self
._parse
(end
)
16 def _parse(self
, range_str
):
18 begin
, end
= range_str
.split(',')
19 return [int(begin
), int(end
)]
21 return [int(range_str
), int(range_str
)]
24 return '@@ -%s +%s @@' % (self
._span
(self
.begin
), self
._span
(self
.end
))
26 def set_begin_count(self
, count
):
27 self
._set
_count
(self
.begin
, count
)
29 def set_end_count(self
, count
):
30 self
._set
_count
(self
.end
, count
)
32 def _set_count(self
, which
, count
):
35 if count
== 1 and which
[0] == 0:
36 # the file would be empty in the diff, but we're only
37 # partially applying it, and thus it's not a +0,0 diff
47 return '%d,%d' % (a
, b
)
50 class DiffSource(object):
51 def get(self
, head
, amending
, filename
, cached
, reverse
):
52 return gitcmds
.diff_helper(head
=head
,
55 with_diff_header
=True,
60 class DiffParser(object):
62 """Handles parsing diff for use by the interactive index editor."""
64 HEADER_RE
= re
.compile(r
'^@@ -([0-9,]+) \+([0-9,]+) @@.*')
66 def __init__(self
, model
, filename
='',
67 cached
=True, reverse
=False,
73 self
._diff
_offsets
= []
76 self
.config
= gitcfg
.instance()
77 self
.head
= model
.head
78 self
.amending
= model
.amending()
84 self
.filename
= filename
85 self
.diff_source
= diff_source
or DiffSource()
87 (header
, diff
) = self
.diff_source
.get(self
.head
, self
.amending
,
95 # Always index into the non-reversed diff
96 self
.fwd_header
, self
.fwd_diff
= \
97 self
.diff_source
.get(self
.head
,
102 def write_diff(self
,filename
,which
,selected
=False,noop
=False):
103 """Writes a new diff corresponding to the user's selection."""
104 if not noop
and which
< len(self
.diff_sel
):
105 diff
= self
.diff_sel
[which
]
106 encoding
= self
.config
.file_encoding(self
.filename
)
107 core
.write(filename
, self
.header
+ '\n' + diff
+ '\n',
114 """Return the diff header ranges"""
118 """Returns the list of diffs."""
121 def diff_subset(self
, diff
, start
, end
):
122 """Processes the diffs and returns a selected subset from that diff.
129 offset
= self
._diff
_spans
[diff
][0]
135 for line
in self
._diffs
[diff
]:
136 line_start
= offset
+ local_offset
137 local_offset
+= len(line
) + 1 #\n
138 line_end
= offset
+ local_offset
139 # |line1 |line2 |line3 |
143 # selection has head of diff (line3)
144 has_head
= start
<= line_start
and end
> line_start
and end
<= line_end
145 # selection has all of diff (line2)
146 has_all
= start
<= line_start
and end
>= line_end
147 # selection has tail of diff (line1)
148 has_tail
= start
>= line_start
and start
< line_end
- 1
151 if has_head
or has_all
or has_tail
:
160 # Don't add new lines unless selected
164 # Don't remove lines unless selected
165 newdiff
.append(' ' + line
[1:])
173 diff_range
= self
._ranges
[diff
]
174 begin_count
= existing
+ deletes
175 end_count
= existing
+ adds
177 diff_range
.set_begin_count(begin_count
)
178 diff_range
.set_end_count(end_count
)
179 newdiff
[0] = diff_range
.make()
181 return (self
.header
+ '\n' + '\n'.join(newdiff
) + '\n')
184 """Returns the line spans of each hunk."""
185 return self
._diff
_spans
188 """Returns the offsets."""
189 return self
._diff
_offsets
191 def set_diff_to_offset(self
, offset
):
192 """Sets the diff selection to be the hunk at a particular offset."""
194 self
.diff_sel
, self
.selected
= self
.diff_for_offset(offset
)
196 def set_diffs_to_range(self
, start
, end
):
197 """Sets the diff selection to be a range of hunks."""
200 self
.diff_sel
, self
.selected
= self
.diffs_for_range(start
,end
)
202 def diff_for_offset(self
, offset
):
203 """Returns the hunks for a particular offset."""
204 for idx
, diff_offset
in enumerate(self
._diff
_offsets
):
205 if offset
< diff_offset
:
206 return (['\n'.join(self
._diffs
[idx
])], [idx
])
209 def diffs_for_range(self
, start
, end
):
210 """Returns the hunks for a selected range."""
213 for idx
, span
in enumerate(self
._diff
_spans
):
214 has_end_of_diff
= start
>= span
[0] and start
< span
[1]
215 has_all_of_diff
= start
<= span
[0] and end
>= span
[1]
216 has_head_of_diff
= end
>= span
[0] and end
<= span
[1]
218 selected_diff
=(has_end_of_diff
222 diff
= '\n'.join(self
._diffs
[idx
])
225 return diffs
, indices
227 def parse_diff(self
, diff
):
228 """Parses a diff and extracts headers, offsets, hunks, etc.
233 for line
in diff
.split('\n'):
234 match
= self
.HEADER_RE
.match(line
)
236 self
._ranges
.append(Range(match
.group(1), match
.group(2)))
237 self
._diffs
.append([line
])
239 line_len
= len(line
) + 1 #\n
240 self
._diff
_spans
.append([total_offset
,
241 total_offset
+ line_len
])
242 total_offset
+= line_len
243 self
._diff
_offsets
.append(total_offset
)
248 errmsg
= 'Malformed diff?: %s' % diff
249 raise AssertionError(errmsg
)
251 line_len
= len(line
) + 1
252 total_offset
+= line_len
254 self
._diffs
[self
._idx
].append(line
)
255 self
._diff
_spans
[-1][-1] += line_len
256 self
._diff
_offsets
[self
._idx
] += line_len
258 def process_diff_selection(self
, selected
, offset
, selection
,
259 apply_to_worktree
=False):
260 """Processes a diff selection and applies changes to git."""
262 # qt destroys \r\n and makes it \n with no way of going back.
263 # boo! we work around that here.
264 # I think this was win32-specific. We might want to do
265 # this on win32 only (TODO verify)
266 if selection
not in self
.fwd_diff
:
267 special_selection
= selection
.replace('\n', '\r\n')
268 if special_selection
in self
.fwd_diff
:
269 selection
= special_selection
272 start
= self
.fwd_diff
.index(selection
)
273 end
= start
+ len(selection
)
274 self
.set_diffs_to_range(start
, end
)
276 self
.set_diff_to_offset(offset
)
282 # Process diff selection only
284 encoding
= self
.config
.file_encoding(self
.filename
)
285 for idx
in self
.selected
:
286 contents
= self
.diff_subset(idx
, start
, end
)
289 tmpfile
= utils
.tmp_filename('selection')
290 core
.write(tmpfile
, contents
, encoding
=encoding
)
291 if apply_to_worktree
:
292 stat
, out
, err
= self
.model
.apply_diff_to_worktree(tmpfile
)
295 status
= max(status
, stat
)
297 stat
, out
, err
= self
.model
.apply_diff(tmpfile
)
300 status
= max(status
, stat
)
302 # Process a complete hunk
304 for idx
, diff
in enumerate(self
.diff_sel
):
305 tmpfile
= utils
.tmp_filename('patch%02d' % idx
)
306 if not self
.write_diff(tmpfile
,idx
):
308 if apply_to_worktree
:
309 stat
, out
, err
= self
.model
.apply_diff_to_worktree(tmpfile
)
312 status
= max(status
, stat
)
314 stat
, out
, err
= self
.model
.apply_diff(tmpfile
)
317 status
= max(status
, stat
)
319 return status
, output
, error