4 from cola
.utils
import write
7 class DiffParser(object):
8 """Handles parsing diff for use by the interactive index editor."""
9 def __init__(self
, model
, filename
='',
10 cached
=True, branch
=None, reverse
=False):
12 self
._header
_re
= re
.compile('^@@ -(\d+),(\d+) \+(\d+),(\d+) @@.*')
18 self
._diff
_offsets
= []
26 (header
, diff
) = model
.diff_helper(filename
=filename
,
28 with_diff_header
=True,
29 cached
=cached
and not bool(branch
),
30 reverse
=cached
or bool(branch
) or reverse
)
36 # Always index into the non-reversed diff
37 self
.fwd_header
, self
.fwd_diff
= \
38 model
.diff_helper(filename
=filename
,
40 with_diff_header
=True,
41 cached
=cached
and not bool(branch
),
44 def write_diff(self
,filename
,which
,selected
=False,noop
=False):
45 """Writes a new diff corresponding to the user's selection."""
46 if not noop
and which
< len(self
.diffs
):
47 diff
= self
.diffs
[which
]
48 write(filename
, self
.header
+ '\n' + diff
+ '\n')
54 """Returns the list of diffs."""
57 def diff_subset(self
, diff
, start
, end
):
58 """Processes the diffs and returns a selected subset from that diff.
64 offset
= self
._diff
_spans
[diff
][0]
65 diffguts
= '\n'.join(self
._diffs
[diff
])
67 for line
in self
._diffs
[diff
]:
68 line_start
= offset
+ local_offset
69 local_offset
+= len(line
) + 1 #\n
70 line_end
= offset
+ local_offset
71 # |line1 |line2 |line3 |
74 # selection has head of diff (line3)
75 if start
< line_start
and end
> line_start
and end
< line_end
:
77 if line
.startswith('+'):
79 if line
.startswith('-'):
81 # selection has all of diff (line2)
82 elif start
<= line_start
and end
>= line_end
:
84 if line
.startswith('+'):
86 if line
.startswith('-'):
88 # selection has tail of diff (line1)
89 elif start
>= line_start
and start
< line_end
- 1:
91 if line
.startswith('+'):
93 if line
.startswith('-'):
96 # Don't add new lines unless selected
97 if line
.startswith('+'):
99 elif line
.startswith('-'):
100 # Don't remove lines unless selected
101 newdiff
.append(' ' + line
[1:])
105 new_count
= self
._headers
[diff
][1] + adds
- deletes
106 if new_count
!= self
._headers
[diff
][3]:
107 header
= '@@ -%d,%d +%d,%d @@' % (
108 self
._headers
[diff
][0],
109 self
._headers
[diff
][1],
110 self
._headers
[diff
][2],
114 return (self
.header
+ '\n' + '\n'.join(newdiff
) + '\n')
117 """Returns the line spans of each hunk."""
118 return self
._diff
_spans
121 """Returns the offsets."""
122 return self
._diff
_offsets
124 def set_diff_to_offset(self
, offset
):
125 """Sets the diff selection to be the hunk at a particular offset."""
127 self
.diffs
, self
.selected
= self
.diff_for_offset(offset
)
129 def set_diffs_to_range(self
, start
, end
):
130 """Sets the diff selection to be a range of hunks."""
133 self
.diffs
, self
.selected
= self
.diffs_for_range(start
,end
)
135 def diff_for_offset(self
, offset
):
136 """Returns the hunks for a particular offset."""
137 for idx
, diff_offset
in enumerate(self
._diff
_offsets
):
138 if offset
< diff_offset
:
139 return (['\n'.join(self
._diffs
[idx
])], [idx
])
142 def diffs_for_range(self
, start
, end
):
143 """Returns the hunks for a selected range."""
146 for idx
, span
in enumerate(self
._diff
_spans
):
147 has_end_of_diff
= start
>= span
[0] and start
< span
[1]
148 has_all_of_diff
= start
<= span
[0] and end
>= span
[1]
149 has_head_of_diff
= end
>= span
[0] and end
<= span
[1]
151 selected_diff
=(has_end_of_diff
155 diff
= '\n'.join(self
._diffs
[idx
])
158 return diffs
, indices
160 def parse_diff(self
, diff
):
161 """Parses a diff and extracts headers, offsets, hunks, etc.
167 for idx
, line
in enumerate(diff
.split('\n')):
168 match
= self
._header
_re
.match(line
)
170 self
._headers
.append([
176 self
._diffs
.append( [line
] )
178 line_len
= len(line
) + 1 #\n
179 self
._diff
_spans
.append([total_offset
,
180 total_offset
+ line_len
])
181 total_offset
+= line_len
182 self
._diff
_offsets
.append(total_offset
)
186 errmsg
= 'Malformed diff?\n\n%s' % diff
187 raise AssertionError, errmsg
188 line_len
= len(line
) + 1
189 total_offset
+= line_len
191 self
._diffs
[self
._idx
].append(line
)
192 self
._diff
_spans
[-1][-1] += line_len
193 self
._diff
_offsets
[self
._idx
] += line_len
195 def process_diff_selection(self
, selected
, offset
, selection
,
196 apply_to_worktree
=False):
197 """Processes a diff selection and applies changes to git."""
199 # qt destroys \r\n and makes it \n with no way of going back.
200 # boo! we work around that here.
201 # I think this was win32-specific. We might want to do
202 # this on win32 only (TODO verify)
203 if selection
not in self
.fwd_diff
:
204 special_selection
= selection
.replace('\n', '\r\n')
205 if special_selection
in self
.fwd_diff
:
206 selection
= special_selection
209 start
= self
.fwd_diff
.index(selection
)
210 end
= start
+ len(selection
)
211 self
.set_diffs_to_range(start
, end
)
213 self
.set_diff_to_offset(offset
)
215 # Process diff selection only
217 for idx
in self
.selected
:
218 contents
= self
.diff_subset(idx
, start
, end
)
220 tmpfile
= self
.model
.tmp_filename()
221 write(tmpfile
, contents
)
222 if apply_to_worktree
:
223 self
.model
.apply_diff_to_worktree(tmpfile
)
225 self
.model
.apply_diff(tmpfile
)
227 # Process a complete hunk
229 for idx
, diff
in enumerate(self
.diffs
):
230 tmpfile
= self
.model
.tmp_filename()
231 if self
.write_diff(tmpfile
,idx
):
232 if apply_to_worktree
:
233 self
.model
.apply_diff_to_worktree(tmpfile
)
235 self
.model
.apply_diff(tmpfile
)