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.lib.Constants.encodeASCII;
47 import static org.eclipse.jgit.patch.FileHeader.isHunkHdr;
48 import static org.eclipse.jgit.patch.FileHeader.NEW_NAME;
49 import static org.eclipse.jgit.patch.FileHeader.OLD_NAME;
50 import static org.eclipse.jgit.util.RawParseUtils.match;
51 import static org.eclipse.jgit.util.RawParseUtils.nextLF;
52
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.util.ArrayList;
56 import java.util.List;
57
58 import org.eclipse.jgit.internal.JGitText;
59 import org.eclipse.jgit.util.TemporaryBuffer;
60
61
62 public class Patch {
63 static final byte[] DIFF_GIT = encodeASCII("diff --git ");
64
65 private static final byte[] DIFF_CC = encodeASCII("diff --cc ");
66
67 private static final byte[] DIFF_COMBINED = encodeASCII("diff --combined ");
68
69 private static final byte[][] BIN_HEADERS = new byte[][] {
70 encodeASCII("Binary files "), encodeASCII("Files "), };
71
72 private static final byte[] BIN_TRAILER = encodeASCII(" differ\n");
73
74 private static final byte[] GIT_BINARY = encodeASCII("GIT binary patch\n");
75
76 static final byte[] SIG_FOOTER = encodeASCII("-- \n");
77
78
79 private final List<FileHeader> files;
80
81
82 private final List<FormatError> errors;
83
84
85 public Patch() {
86 files = new ArrayList<FileHeader>();
87 errors = new ArrayList<FormatError>(0);
88 }
89
90
91
92
93
94
95
96
97
98
99 public void addFile(final FileHeader fh) {
100 files.add(fh);
101 }
102
103
104 public List<? extends FileHeader> getFiles() {
105 return files;
106 }
107
108
109
110
111
112
113
114 public void addError(final FormatError err) {
115 errors.add(err);
116 }
117
118
119 public List<FormatError> getErrors() {
120 return errors;
121 }
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136 public void parse(final InputStream is) throws IOException {
137 final byte[] buf = readFully(is);
138 parse(buf, 0, buf.length);
139 }
140
141 private static byte[] readFully(final InputStream is) throws IOException {
142 TemporaryBuffer b = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
143 b.copy(is);
144 b.close();
145 return b.toByteArray();
146 }
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163 public void parse(final byte[] buf, int ptr, final int end) {
164 while (ptr < end)
165 ptr = parseFile(buf, ptr, end);
166 }
167
168 private int parseFile(final byte[] buf, int c, final int end) {
169 while (c < end) {
170 if (isHunkHdr(buf, c, end) >= 1) {
171
172
173
174
175 error(buf, c, JGitText.get().hunkDisconnectedFromFile);
176 c = nextLF(buf, c);
177 continue;
178 }
179
180
181
182 if (match(buf, c, DIFF_GIT) >= 0)
183 return parseDiffGit(buf, c, end);
184 if (match(buf, c, DIFF_CC) >= 0)
185 return parseDiffCombined(DIFF_CC, buf, c, end);
186 if (match(buf, c, DIFF_COMBINED) >= 0)
187 return parseDiffCombined(DIFF_COMBINED, buf, c, end);
188
189
190
191
192 final int n = nextLF(buf, c);
193 if (n >= end) {
194
195
196
197 return end;
198 }
199
200 if (n - c < 6) {
201
202
203
204 c = n;
205 continue;
206 }
207
208 if (match(buf, c, OLD_NAME) >= 0 && match(buf, n, NEW_NAME) >= 0) {
209
210
211
212 final int f = nextLF(buf, n);
213 if (f >= end)
214 return end;
215 if (isHunkHdr(buf, f, end) == 1)
216 return parseTraditionalPatch(buf, c, end);
217 }
218
219 c = n;
220 }
221 return c;
222 }
223
224 private int parseDiffGit(final byte[] buf, final int start, final int end) {
225 final FileHeader fh = new FileHeader(buf, start);
226 int ptr = fh.parseGitFileName(start + DIFF_GIT.length, end);
227 if (ptr < 0)
228 return skipFile(buf, start);
229
230 ptr = fh.parseGitHeaders(ptr, end);
231 ptr = parseHunks(fh, ptr, end);
232 fh.endOffset = ptr;
233 addFile(fh);
234 return ptr;
235 }
236
237 private int parseDiffCombined(final byte[] hdr, final byte[] buf,
238 final int start, final int end) {
239 final CombinedFileHeader fh = new CombinedFileHeader(buf, start);
240 int ptr = fh.parseGitFileName(start + hdr.length, end);
241 if (ptr < 0)
242 return skipFile(buf, start);
243
244 ptr = fh.parseGitHeaders(ptr, end);
245 ptr = parseHunks(fh, ptr, end);
246 fh.endOffset = ptr;
247 addFile(fh);
248 return ptr;
249 }
250
251 private int parseTraditionalPatch(final byte[] buf, final int start,
252 final int end) {
253 final FileHeader fh = new FileHeader(buf, start);
254 int ptr = fh.parseTraditionalHeaders(start, end);
255 ptr = parseHunks(fh, ptr, end);
256 fh.endOffset = ptr;
257 addFile(fh);
258 return ptr;
259 }
260
261 private static int skipFile(final byte[] buf, int ptr) {
262 ptr = nextLF(buf, ptr);
263 if (match(buf, ptr, OLD_NAME) >= 0)
264 ptr = nextLF(buf, ptr);
265 return ptr;
266 }
267
268 private int parseHunks(final FileHeader fh, int c, final int end) {
269 final byte[] buf = fh.buf;
270 while (c < end) {
271
272
273
274
275 if (match(buf, c, DIFF_GIT) >= 0)
276 break;
277 if (match(buf, c, DIFF_CC) >= 0)
278 break;
279 if (match(buf, c, DIFF_COMBINED) >= 0)
280 break;
281 if (match(buf, c, OLD_NAME) >= 0)
282 break;
283 if (match(buf, c, NEW_NAME) >= 0)
284 break;
285
286 if (isHunkHdr(buf, c, end) == fh.getParentCount()) {
287 final HunkHeader h = fh.newHunkHeader(c);
288 h.parseHeader();
289 c = h.parseBody(this, end);
290 h.endOffset = c;
291 fh.addHunk(h);
292 if (c < end) {
293 switch (buf[c]) {
294 case '@':
295 case 'd':
296 case '\n':
297 break;
298 default:
299 if (match(buf, c, SIG_FOOTER) < 0)
300 warn(buf, c, JGitText.get().unexpectedHunkTrailer);
301 }
302 }
303 continue;
304 }
305
306 final int eol = nextLF(buf, c);
307 if (fh.getHunks().isEmpty() && match(buf, c, GIT_BINARY) >= 0) {
308 fh.patchType = FileHeader.PatchType.GIT_BINARY;
309 return parseGitBinary(fh, eol, end);
310 }
311
312 if (fh.getHunks().isEmpty() && BIN_TRAILER.length < eol - c
313 && match(buf, eol - BIN_TRAILER.length, BIN_TRAILER) >= 0
314 && matchAny(buf, c, BIN_HEADERS)) {
315
316
317 fh.patchType = FileHeader.PatchType.BINARY;
318 return eol;
319 }
320
321
322
323
324 c = eol;
325 }
326
327 if (fh.getHunks().isEmpty()
328 && fh.getPatchType() == FileHeader.PatchType.UNIFIED
329 && !fh.hasMetaDataChanges()) {
330
331
332
333 fh.patchType = FileHeader.PatchType.BINARY;
334 }
335
336 return c;
337 }
338
339 private int parseGitBinary(final FileHeader fh, int c, final int end) {
340 final BinaryHunk postImage = new BinaryHunk(fh, c);
341 final int nEnd = postImage.parseHunk(c, end);
342 if (nEnd < 0) {
343
344
345 error(fh.buf, c, JGitText.get().missingForwardImageInGITBinaryPatch);
346 return c;
347 }
348 c = nEnd;
349 postImage.endOffset = c;
350 fh.forwardBinaryHunk = postImage;
351
352 final BinaryHunk preImage = new BinaryHunk(fh, c);
353 final int oEnd = preImage.parseHunk(c, end);
354 if (oEnd >= 0) {
355 c = oEnd;
356 preImage.endOffset = c;
357 fh.reverseBinaryHunk = preImage;
358 }
359
360 return c;
361 }
362
363 void warn(final byte[] buf, final int ptr, final String msg) {
364 addError(new FormatError(buf, ptr, FormatError.Severity.WARNING, msg));
365 }
366
367 void error(final byte[] buf, final int ptr, final String msg) {
368 addError(new FormatError(buf, ptr, FormatError.Severity.ERROR, msg));
369 }
370
371 private static boolean matchAny(final byte[] buf, final int c,
372 final byte[][] srcs) {
373 for (final byte[] s : srcs) {
374 if (match(buf, c, s) >= 0)
375 return true;
376 }
377 return false;
378 }
379 }