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
= []
27 (header
, diff
) = gitcmds
.diff_helper(filename
=filename
,
28 with_diff_header
=True,
30 reverse
=cached
or reverse
)
36 # Always index into the non-reversed diff
37 self
.fwd_header
, self
.fwd_diff
= \
38 gitcmds
.diff_helper(filename
=filename
,
39 with_diff_header
=True,
42 def write_diff(self
,filename
,which
,selected
=False,noop
=False):
43 """Writes a new diff corresponding to the user's selection."""
44 if not noop
and which
< len(self
.diffs
):
45 diff
= self
.diffs
[which
]
46 utils
.write(filename
, self
.header
+ '\n' + diff
+ '\n')
52 """Returns the list of diffs."""
55 def diff_subset(self
, diff
, start
, end
):
56 """Processes the diffs and returns a selected subset from that diff.
62 offset
= self
._diff
_spans
[diff
][0]
63 diffguts
= '\n'.join(self
._diffs
[diff
])
65 for line
in self
._diffs
[diff
]:
66 line_start
= offset
+ local_offset
67 local_offset
+= len(line
) + 1 #\n
68 line_end
= offset
+ local_offset
69 # |line1 |line2 |line3 |
72 # selection has head of diff (line3)
73 if start
< line_start
and end
> line_start
and end
< line_end
:
75 if line
.startswith('+'):
77 if line
.startswith('-'):
79 # selection has all of diff (line2)
80 elif start
<= line_start
and end
>= line_end
:
82 if line
.startswith('+'):
84 if line
.startswith('-'):
86 # selection has tail of diff (line1)
87 elif start
>= line_start
and start
< line_end
- 1:
89 if line
.startswith('+'):
91 if line
.startswith('-'):
94 # Don't add new lines unless selected
95 if line
.startswith('+'):
97 elif line
.startswith('-'):
98 # Don't remove lines unless selected
99 newdiff
.append(' ' + line
[1:])
103 new_count
= self
._headers
[diff
][1] + adds
- deletes
104 if new_count
!= self
._headers
[diff
][3]:
105 header
= '@@ -%d,%d +%d,%d @@' % (
106 self
._headers
[diff
][0],
107 self
._headers
[diff
][1],
108 self
._headers
[diff
][2],
112 return (self
.header
+ '\n' + '\n'.join(newdiff
) + '\n')
115 """Returns the line spans of each hunk."""
116 return self
._diff
_spans
119 """Returns the offsets."""
120 return self
._diff
_offsets
122 def set_diff_to_offset(self
, offset
):
123 """Sets the diff selection to be the hunk at a particular offset."""
125 self
.diffs
, self
.selected
= self
.diff_for_offset(offset
)
127 def set_diffs_to_range(self
, start
, end
):
128 """Sets the diff selection to be a range of hunks."""
131 self
.diffs
, self
.selected
= self
.diffs_for_range(start
,end
)
133 def diff_for_offset(self
, offset
):
134 """Returns the hunks for a particular offset."""
135 for idx
, diff_offset
in enumerate(self
._diff
_offsets
):
136 if offset
< diff_offset
:
137 return (['\n'.join(self
._diffs
[idx
])], [idx
])
140 def diffs_for_range(self
, start
, end
):
141 """Returns the hunks for a selected range."""
144 for idx
, span
in enumerate(self
._diff
_spans
):
145 has_end_of_diff
= start
>= span
[0] and start
< span
[1]
146 has_all_of_diff
= start
<= span
[0] and end
>= span
[1]
147 has_head_of_diff
= end
>= span
[0] and end
<= span
[1]
149 selected_diff
=(has_end_of_diff
153 diff
= '\n'.join(self
._diffs
[idx
])
156 return diffs
, indices
158 def parse_diff(self
, diff
):
159 """Parses a diff and extracts headers, offsets, hunks, etc.
165 for idx
, line
in enumerate(diff
.split('\n')):
166 match
= self
._header
_re
.match(line
)
168 self
._headers
.append([
174 self
._diffs
.append( [line
] )
176 line_len
= len(line
) + 1 #\n
177 self
._diff
_spans
.append([total_offset
,
178 total_offset
+ line_len
])
179 total_offset
+= line_len
180 self
._diff
_offsets
.append(total_offset
)
184 errmsg
= 'Malformed diff?\n\n%s' % diff
185 raise AssertionError, errmsg
186 line_len
= len(line
) + 1
187 total_offset
+= line_len
189 self
._diffs
[self
._idx
].append(line
)
190 self
._diff
_spans
[-1][-1] += line_len
191 self
._diff
_offsets
[self
._idx
] += line_len
193 def process_diff_selection(self
, selected
, offset
, selection
,
194 apply_to_worktree
=False):
195 """Processes a diff selection and applies changes to git."""
197 # qt destroys \r\n and makes it \n with no way of going back.
198 # boo! we work around that here.
199 # I think this was win32-specific. We might want to do
200 # this on win32 only (TODO verify)
201 if selection
not in self
.fwd_diff
:
202 special_selection
= selection
.replace('\n', '\r\n')
203 if special_selection
in self
.fwd_diff
:
204 selection
= special_selection
207 start
= self
.fwd_diff
.index(selection
)
208 end
= start
+ len(selection
)
209 self
.set_diffs_to_range(start
, end
)
211 self
.set_diff_to_offset(offset
)
216 # Process diff selection only
218 for idx
in self
.selected
:
219 contents
= self
.diff_subset(idx
, start
, end
)
222 tmpfile
= utils
.tmp_filename('selection')
223 utils
.write(tmpfile
, contents
)
224 if apply_to_worktree
:
225 stat
, out
= self
.model
.apply_diff_to_worktree(tmpfile
)
227 status
= max(status
, stat
)
229 stat
, out
= self
.model
.apply_diff(tmpfile
)
231 status
= max(status
, stat
)
233 # Process a complete hunk
235 for idx
, diff
in enumerate(self
.diffs
):
236 tmpfile
= utils
.tmp_filename('patch%02d' % idx
)
237 if not self
.write_diff(tmpfile
,idx
):
239 if apply_to_worktree
:
240 stat
, out
= self
.model
.apply_diff_to_worktree(tmpfile
)
242 status
= max(status
, stat
)
244 stat
, out
= self
.model
.apply_diff(tmpfile
)
246 status
= max(status
, stat
)
248 return status
, output