1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.patch;
45
46 import static org.eclipse.jgit.util.RawParseUtils.match;
47 import static org.eclipse.jgit.util.RawParseUtils.nextLF;
48 import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
49
50 import java.io.IOException;
51 import java.io.OutputStream;
52 import java.text.MessageFormat;
53
54 import org.eclipse.jgit.diff.Edit;
55 import org.eclipse.jgit.diff.EditList;
56 import org.eclipse.jgit.internal.JGitText;
57 import org.eclipse.jgit.lib.AbbreviatedObjectId;
58 import org.eclipse.jgit.util.MutableInteger;
59
60
61 public class HunkHeader {
62
63 public abstract static class OldImage {
64
65 int startLine;
66
67
68 int lineCount;
69
70
71 int nDeleted;
72
73
74 int nAdded;
75
76
77 public int getStartLine() {
78 return startLine;
79 }
80
81
82 public int getLineCount() {
83 return lineCount;
84 }
85
86
87 public int getLinesDeleted() {
88 return nDeleted;
89 }
90
91
92 public int getLinesAdded() {
93 return nAdded;
94 }
95
96
97 public abstract AbbreviatedObjectId getId();
98 }
99
100 final FileHeader file;
101
102
103 final int startOffset;
104
105
106 int endOffset;
107
108 private final OldImage old;
109
110
111 int newStartLine;
112
113
114 int newLineCount;
115
116
117 int nContext;
118
119 private EditList editList;
120
121 HunkHeader(final FileHeader fh, final int offset) {
122 this(fh, offset, new OldImage() {
123 @Override
124 public AbbreviatedObjectId getId() {
125 return fh.getOldId();
126 }
127 });
128 }
129
130 HunkHeader(final FileHeader fh, final int offset, final OldImage oi) {
131 file = fh;
132 startOffset = offset;
133 old = oi;
134 }
135
136 HunkHeader(final FileHeader fh, final EditList editList) {
137 this(fh, fh.buf.length);
138 this.editList = editList;
139 endOffset = startOffset;
140 nContext = 0;
141 if (editList.isEmpty()) {
142 newStartLine = 0;
143 newLineCount = 0;
144 } else {
145 newStartLine = editList.get(0).getBeginB();
146 Edit last = editList.get(editList.size() - 1);
147 newLineCount = last.getEndB() - newStartLine;
148 }
149 }
150
151
152 public FileHeader getFileHeader() {
153 return file;
154 }
155
156
157 public byte[] getBuffer() {
158 return file.buf;
159 }
160
161
162 public int getStartOffset() {
163 return startOffset;
164 }
165
166
167 public int getEndOffset() {
168 return endOffset;
169 }
170
171
172 public OldImage getOldImage() {
173 return old;
174 }
175
176
177 public int getNewStartLine() {
178 return newStartLine;
179 }
180
181
182 public int getNewLineCount() {
183 return newLineCount;
184 }
185
186
187 public int getLinesContext() {
188 return nContext;
189 }
190
191
192 public EditList toEditList() {
193 if (editList == null) {
194 editList = new EditList();
195 final byte[] buf = file.buf;
196 int c = nextLF(buf, startOffset);
197 int oLine = old.startLine;
198 int nLine = newStartLine;
199 Edit in = null;
200
201 SCAN: for (; c < endOffset; c = nextLF(buf, c)) {
202 switch (buf[c]) {
203 case ' ':
204 case '\n':
205 in = null;
206 oLine++;
207 nLine++;
208 continue;
209
210 case '-':
211 if (in == null) {
212 in = new Edit(oLine - 1, nLine - 1);
213 editList.add(in);
214 }
215 oLine++;
216 in.extendA();
217 continue;
218
219 case '+':
220 if (in == null) {
221 in = new Edit(oLine - 1, nLine - 1);
222 editList.add(in);
223 }
224 nLine++;
225 in.extendB();
226 continue;
227
228 case '\\':
229 continue;
230
231 default:
232 break SCAN;
233 }
234 }
235 }
236 return editList;
237 }
238
239 void parseHeader() {
240
241
242 final byte[] buf = file.buf;
243 final MutableInteger ptr = new MutableInteger();
244 ptr.value = nextLF(buf, startOffset, ' ');
245 old.startLine = -parseBase10(buf, ptr.value, ptr);
246 if (buf[ptr.value] == ',')
247 old.lineCount = parseBase10(buf, ptr.value + 1, ptr);
248 else
249 old.lineCount = 1;
250
251 newStartLine = parseBase10(buf, ptr.value + 1, ptr);
252 if (buf[ptr.value] == ',')
253 newLineCount = parseBase10(buf, ptr.value + 1, ptr);
254 else
255 newLineCount = 1;
256 }
257
258 int parseBody(final Patch script, final int end) {
259 final byte[] buf = file.buf;
260 int c = nextLF(buf, startOffset), last = c;
261
262 old.nDeleted = 0;
263 old.nAdded = 0;
264
265 SCAN: for (; c < end; last = c, c = nextLF(buf, c)) {
266 switch (buf[c]) {
267 case ' ':
268 case '\n':
269 nContext++;
270 continue;
271
272 case '-':
273 old.nDeleted++;
274 continue;
275
276 case '+':
277 old.nAdded++;
278 continue;
279
280 case '\\':
281 continue;
282
283 default:
284 break SCAN;
285 }
286 }
287
288 if (last < end && nContext + old.nDeleted - 1 == old.lineCount
289 && nContext + old.nAdded == newLineCount
290 && match(buf, last, Patch.SIG_FOOTER) >= 0) {
291
292
293
294
295
296 old.nDeleted--;
297 return last;
298 }
299
300 if (nContext + old.nDeleted < old.lineCount) {
301 final int missingCount = old.lineCount - (nContext + old.nDeleted);
302 script.error(buf, startOffset, MessageFormat.format(
303 JGitText.get().truncatedHunkOldLinesMissing,
304 Integer.valueOf(missingCount)));
305
306 } else if (nContext + old.nAdded < newLineCount) {
307 final int missingCount = newLineCount - (nContext + old.nAdded);
308 script.error(buf, startOffset, MessageFormat.format(
309 JGitText.get().truncatedHunkNewLinesMissing,
310 Integer.valueOf(missingCount)));
311
312 } else if (nContext + old.nDeleted > old.lineCount
313 || nContext + old.nAdded > newLineCount) {
314 final String oldcnt = old.lineCount + ":" + newLineCount;
315 final String newcnt = (nContext + old.nDeleted) + ":"
316 + (nContext + old.nAdded);
317 script.warn(buf, startOffset, MessageFormat.format(
318 JGitText.get().hunkHeaderDoesNotMatchBodyLineCountOf, oldcnt, newcnt));
319 }
320
321 return c;
322 }
323
324 void extractFileLines(final OutputStream[] out) throws IOException {
325 final byte[] buf = file.buf;
326 int ptr = startOffset;
327 int eol = nextLF(buf, ptr);
328 if (endOffset <= eol)
329 return;
330
331
332
333
334
335 out[0].write(buf, ptr, eol - ptr);
336
337 SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
338 eol = nextLF(buf, ptr);
339 switch (buf[ptr]) {
340 case ' ':
341 case '\n':
342 case '\\':
343 out[0].write(buf, ptr, eol - ptr);
344 out[1].write(buf, ptr, eol - ptr);
345 break;
346 case '-':
347 out[0].write(buf, ptr, eol - ptr);
348 break;
349 case '+':
350 out[1].write(buf, ptr, eol - ptr);
351 break;
352 default:
353 break SCAN;
354 }
355 }
356 }
357
358 void extractFileLines(final StringBuilder sb, final String[] text,
359 final int[] offsets) {
360 final byte[] buf = file.buf;
361 int ptr = startOffset;
362 int eol = nextLF(buf, ptr);
363 if (endOffset <= eol)
364 return;
365 copyLine(sb, text, offsets, 0);
366 SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
367 eol = nextLF(buf, ptr);
368 switch (buf[ptr]) {
369 case ' ':
370 case '\n':
371 case '\\':
372 copyLine(sb, text, offsets, 0);
373 skipLine(text, offsets, 1);
374 break;
375 case '-':
376 copyLine(sb, text, offsets, 0);
377 break;
378 case '+':
379 copyLine(sb, text, offsets, 1);
380 break;
381 default:
382 break SCAN;
383 }
384 }
385 }
386
387 void copyLine(final StringBuilder sb, final String[] text,
388 final int[] offsets, final int fileIdx) {
389 final String s = text[fileIdx];
390 final int start = offsets[fileIdx];
391 int end = s.indexOf('\n', start);
392 if (end < 0)
393 end = s.length();
394 else
395 end++;
396 sb.append(s, start, end);
397 offsets[fileIdx] = end;
398 }
399
400 void skipLine(final String[] text, final int[] offsets,
401 final int fileIdx) {
402 final String s = text[fileIdx];
403 final int end = s.indexOf('\n', offsets[fileIdx]);
404 offsets[fileIdx] = end < 0 ? s.length() : end + 1;
405 }
406
407 @SuppressWarnings("nls")
408 @Override
409 public String toString() {
410 StringBuilder buf = new StringBuilder();
411 buf.append("HunkHeader[");
412 buf.append(getOldImage().getStartLine());
413 buf.append(',');
414 buf.append(getOldImage().getLineCount());
415 buf.append("->");
416 buf.append(getNewStartLine()).append(',').append(getNewLineCount());
417 buf.append(']');
418 return buf.toString();
419 }
420 }