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