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 java.nio.charset.StandardCharsets.UTF_8;
47 import static org.eclipse.jgit.lib.Constants.encodeASCII;
48 import static org.eclipse.jgit.util.RawParseUtils.decode;
49 import static org.eclipse.jgit.util.RawParseUtils.decodeNoFallback;
50 import static org.eclipse.jgit.util.RawParseUtils.extractBinaryString;
51 import static org.eclipse.jgit.util.RawParseUtils.match;
52 import static org.eclipse.jgit.util.RawParseUtils.nextLF;
53 import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
54
55 import java.io.IOException;
56 import java.nio.charset.CharacterCodingException;
57 import java.nio.charset.Charset;
58 import java.text.MessageFormat;
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.List;
62
63 import org.eclipse.jgit.diff.DiffEntry;
64 import org.eclipse.jgit.diff.EditList;
65 import org.eclipse.jgit.internal.JGitText;
66 import org.eclipse.jgit.lib.AbbreviatedObjectId;
67 import org.eclipse.jgit.lib.FileMode;
68 import org.eclipse.jgit.util.QuotedString;
69 import org.eclipse.jgit.util.RawParseUtils;
70 import org.eclipse.jgit.util.TemporaryBuffer;
71
72
73
74
75 public class FileHeader extends DiffEntry {
76 private static final byte[] OLD_MODE = encodeASCII("old mode ");
77
78 private static final byte[] NEW_MODE = encodeASCII("new mode ");
79
80 static final byte[] DELETED_FILE_MODE = encodeASCII("deleted file mode ");
81
82 static final byte[] NEW_FILE_MODE = encodeASCII("new file mode ");
83
84 private static final byte[] COPY_FROM = encodeASCII("copy from ");
85
86 private static final byte[] COPY_TO = encodeASCII("copy to ");
87
88 private static final byte[] RENAME_OLD = encodeASCII("rename old ");
89
90 private static final byte[] RENAME_NEW = encodeASCII("rename new ");
91
92 private static final byte[] RENAME_FROM = encodeASCII("rename from ");
93
94 private static final byte[] RENAME_TO = encodeASCII("rename to ");
95
96 private static final byte[] SIMILARITY_INDEX = encodeASCII("similarity index ");
97
98 private static final byte[] DISSIMILARITY_INDEX = encodeASCII("dissimilarity index ");
99
100 static final byte[] INDEX = encodeASCII("index ");
101
102 static final byte[] OLD_NAME = encodeASCII("--- ");
103
104 static final byte[] NEW_NAME = encodeASCII("+++ ");
105
106
107 public static enum PatchType {
108
109 UNIFIED,
110
111
112 BINARY,
113
114
115 GIT_BINARY;
116 }
117
118
119 final byte[] buf;
120
121
122 final int startOffset;
123
124
125 int endOffset;
126
127
128 PatchType patchType;
129
130
131 private List<HunkHeader> hunks;
132
133
134 BinaryHunk forwardBinaryHunk;
135
136
137 BinaryHunk reverseBinaryHunk;
138
139
140
141
142
143
144
145
146
147
148
149 public FileHeader(byte[] headerLines, EditList edits, PatchType type) {
150 this(headerLines, 0);
151 endOffset = headerLines.length;
152 int ptr = parseGitFileName(Patch.DIFF_GIT.length, headerLines.length);
153 parseGitHeaders(ptr, headerLines.length);
154 this.patchType = type;
155 addHunk(new HunkHeader(this, edits));
156 }
157
158 FileHeader(byte[] b, int offset) {
159 buf = b;
160 startOffset = offset;
161 changeType = ChangeType.MODIFY;
162 patchType = PatchType.UNIFIED;
163 }
164
165 int getParentCount() {
166 return 1;
167 }
168
169
170
171
172
173
174 public byte[] getBuffer() {
175 return buf;
176 }
177
178
179
180
181
182
183
184 public int getStartOffset() {
185 return startOffset;
186 }
187
188
189
190
191
192
193 public int getEndOffset() {
194 return endOffset;
195 }
196
197
198
199
200
201
202
203
204
205
206 public String getScriptText() {
207 return getScriptText(null, null);
208 }
209
210
211
212
213
214
215
216
217
218
219 public String getScriptText(Charset oldCharset, Charset newCharset) {
220 return getScriptText(new Charset[] { oldCharset, newCharset });
221 }
222
223 String getScriptText(Charset[] charsetGuess) {
224 if (getHunks().isEmpty()) {
225
226
227
228
229
230
231
232
233 return extractBinaryString(buf, startOffset, endOffset);
234 }
235
236 if (charsetGuess != null && charsetGuess.length != getParentCount() + 1)
237 throw new IllegalArgumentException(MessageFormat.format(
238 JGitText.get().expectedCharacterEncodingGuesses,
239 Integer.valueOf(getParentCount() + 1)));
240
241 if (trySimpleConversion(charsetGuess)) {
242 Charset cs = charsetGuess != null ? charsetGuess[0] : null;
243 if (cs == null) {
244 cs = UTF_8;
245 }
246 try {
247 return decodeNoFallback(cs, buf, startOffset, endOffset);
248 } catch (CharacterCodingException cee) {
249
250
251 }
252 }
253
254 final StringBuilder r = new StringBuilder(endOffset - startOffset);
255
256
257
258
259 final int hdrEnd = getHunks().get(0).getStartOffset();
260 for (int ptr = startOffset; ptr < hdrEnd;) {
261 final int eol = Math.min(hdrEnd, nextLF(buf, ptr));
262 r.append(extractBinaryString(buf, ptr, eol));
263 ptr = eol;
264 }
265
266 final String[] files = extractFileLines(charsetGuess);
267 final int[] offsets = new int[files.length];
268 for (HunkHeader h : getHunks())
269 h.extractFileLines(r, files, offsets);
270 return r.toString();
271 }
272
273 private static boolean trySimpleConversion(Charset[] charsetGuess) {
274 if (charsetGuess == null)
275 return true;
276 for (int i = 1; i < charsetGuess.length; i++) {
277 if (charsetGuess[i] != charsetGuess[0])
278 return false;
279 }
280 return true;
281 }
282
283 private String[] extractFileLines(Charset[] csGuess) {
284 final TemporaryBuffer[] tmp = new TemporaryBuffer[getParentCount() + 1];
285 try {
286 for (int i = 0; i < tmp.length; i++)
287 tmp[i] = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
288 for (HunkHeader h : getHunks())
289 h.extractFileLines(tmp);
290
291 final String[] r = new String[tmp.length];
292 for (int i = 0; i < tmp.length; i++) {
293 Charset cs = csGuess != null ? csGuess[i] : null;
294 if (cs == null) {
295 cs = UTF_8;
296 }
297 r[i] = RawParseUtils.decode(cs, tmp[i].toByteArray());
298 }
299 return r;
300 } catch (IOException ioe) {
301 throw new RuntimeException(JGitText.get().cannotConvertScriptToText, ioe);
302 }
303 }
304
305
306
307
308
309
310 public PatchType getPatchType() {
311 return patchType;
312 }
313
314
315
316
317
318
319 public boolean hasMetaDataChanges() {
320 return changeType != ChangeType.MODIFY || newMode != oldMode;
321 }
322
323
324
325
326
327
328 public List<? extends HunkHeader> getHunks() {
329 if (hunks == null)
330 return Collections.emptyList();
331 return hunks;
332 }
333
334 void addHunk(HunkHeader h) {
335 if (h.getFileHeader() != this)
336 throw new IllegalArgumentException(JGitText.get().hunkBelongsToAnotherFile);
337 if (hunks == null)
338 hunks = new ArrayList<>();
339 hunks.add(h);
340 }
341
342 HunkHeader newHunkHeader(int offset) {
343 return new HunkHeader(this, offset);
344 }
345
346
347
348
349
350
351
352
353 public BinaryHunk getForwardBinaryHunk() {
354 return forwardBinaryHunk;
355 }
356
357
358
359
360
361
362
363
364 public BinaryHunk getReverseBinaryHunk() {
365 return reverseBinaryHunk;
366 }
367
368
369
370
371
372
373 public EditList toEditList() {
374 final EditList r = new EditList();
375 for (HunkHeader hunk : hunks)
376 r.addAll(hunk.toEditList());
377 return r;
378 }
379
380
381
382
383
384
385
386
387
388
389 int parseGitFileName(int ptr, int end) {
390 final int eol = nextLF(buf, ptr);
391 final int bol = ptr;
392 if (eol >= end) {
393 return -1;
394 }
395
396
397
398
399
400
401
402
403 final int aStart = nextLF(buf, ptr, '/');
404 if (aStart >= eol)
405 return eol;
406
407 while (ptr < eol) {
408 final int sp = nextLF(buf, ptr, ' ');
409 if (sp >= eol) {
410
411
412
413 return eol;
414 }
415 final int bStart = nextLF(buf, sp, '/');
416 if (bStart >= eol)
417 return eol;
418
419
420
421
422 if (eq(aStart, sp - 1, bStart, eol - 1)) {
423 if (buf[bol] == '"') {
424
425
426
427
428 if (buf[sp - 2] != '"') {
429 return eol;
430 }
431 oldPath = QuotedString.GIT_PATH.dequote(buf, bol, sp - 1);
432 oldPath = p1(oldPath);
433 } else {
434 oldPath = decode(UTF_8, buf, aStart, sp - 1);
435 }
436 newPath = oldPath;
437 return eol;
438 }
439
440
441
442
443 ptr = sp;
444 }
445
446 return eol;
447 }
448
449 int parseGitHeaders(int ptr, int end) {
450 while (ptr < end) {
451 final int eol = nextLF(buf, ptr);
452 if (isHunkHdr(buf, ptr, eol) >= 1) {
453
454 break;
455
456 } else if (match(buf, ptr, OLD_NAME) >= 0) {
457 parseOldName(ptr, eol);
458
459 } else if (match(buf, ptr, NEW_NAME) >= 0) {
460 parseNewName(ptr, eol);
461
462 } else if (match(buf, ptr, OLD_MODE) >= 0) {
463 oldMode = parseFileMode(ptr + OLD_MODE.length, eol);
464
465 } else if (match(buf, ptr, NEW_MODE) >= 0) {
466 newMode = parseFileMode(ptr + NEW_MODE.length, eol);
467
468 } else if (match(buf, ptr, DELETED_FILE_MODE) >= 0) {
469 oldMode = parseFileMode(ptr + DELETED_FILE_MODE.length, eol);
470 newMode = FileMode.MISSING;
471 changeType = ChangeType.DELETE;
472
473 } else if (match(buf, ptr, NEW_FILE_MODE) >= 0) {
474 parseNewFileMode(ptr, eol);
475
476 } else if (match(buf, ptr, COPY_FROM) >= 0) {
477 oldPath = parseName(oldPath, ptr + COPY_FROM.length, eol);
478 changeType = ChangeType.COPY;
479
480 } else if (match(buf, ptr, COPY_TO) >= 0) {
481 newPath = parseName(newPath, ptr + COPY_TO.length, eol);
482 changeType = ChangeType.COPY;
483
484 } else if (match(buf, ptr, RENAME_OLD) >= 0) {
485 oldPath = parseName(oldPath, ptr + RENAME_OLD.length, eol);
486 changeType = ChangeType.RENAME;
487
488 } else if (match(buf, ptr, RENAME_NEW) >= 0) {
489 newPath = parseName(newPath, ptr + RENAME_NEW.length, eol);
490 changeType = ChangeType.RENAME;
491
492 } else if (match(buf, ptr, RENAME_FROM) >= 0) {
493 oldPath = parseName(oldPath, ptr + RENAME_FROM.length, eol);
494 changeType = ChangeType.RENAME;
495
496 } else if (match(buf, ptr, RENAME_TO) >= 0) {
497 newPath = parseName(newPath, ptr + RENAME_TO.length, eol);
498 changeType = ChangeType.RENAME;
499
500 } else if (match(buf, ptr, SIMILARITY_INDEX) >= 0) {
501 score = parseBase10(buf, ptr + SIMILARITY_INDEX.length, null);
502
503 } else if (match(buf, ptr, DISSIMILARITY_INDEX) >= 0) {
504 score = parseBase10(buf, ptr + DISSIMILARITY_INDEX.length, null);
505
506 } else if (match(buf, ptr, INDEX) >= 0) {
507 parseIndexLine(ptr + INDEX.length, eol);
508
509 } else {
510
511 break;
512 }
513
514 ptr = eol;
515 }
516 return ptr;
517 }
518
519 void parseOldName(int ptr, int eol) {
520 oldPath = p1(parseName(oldPath, ptr + OLD_NAME.length, eol));
521 if (oldPath == DEV_NULL)
522 changeType = ChangeType.ADD;
523 }
524
525 void parseNewName(int ptr, int eol) {
526 newPath = p1(parseName(newPath, ptr + NEW_NAME.length, eol));
527 if (newPath == DEV_NULL)
528 changeType = ChangeType.DELETE;
529 }
530
531 void parseNewFileMode(int ptr, int eol) {
532 oldMode = FileMode.MISSING;
533 newMode = parseFileMode(ptr + NEW_FILE_MODE.length, eol);
534 changeType = ChangeType.ADD;
535 }
536
537 int parseTraditionalHeaders(int ptr, int end) {
538 while (ptr < end) {
539 final int eol = nextLF(buf, ptr);
540 if (isHunkHdr(buf, ptr, eol) >= 1) {
541
542 break;
543
544 } else if (match(buf, ptr, OLD_NAME) >= 0) {
545 parseOldName(ptr, eol);
546
547 } else if (match(buf, ptr, NEW_NAME) >= 0) {
548 parseNewName(ptr, eol);
549
550 } else {
551
552 break;
553 }
554
555 ptr = eol;
556 }
557 return ptr;
558 }
559
560 private String parseName(String expect, int ptr, int end) {
561 if (ptr == end)
562 return expect;
563
564 String r;
565 if (buf[ptr] == '"') {
566
567
568 r = QuotedString.GIT_PATH.dequote(buf, ptr, end - 1);
569 } else {
570
571
572 int tab = end;
573 while (ptr < tab && buf[tab - 1] != '\t')
574 tab--;
575 if (ptr == tab)
576 tab = end;
577 r = decode(UTF_8, buf, ptr, tab - 1);
578 }
579
580 if (r.equals(DEV_NULL))
581 r = DEV_NULL;
582 return r;
583 }
584
585 private static String p1(final String r) {
586 final int s = r.indexOf('/');
587 return s > 0 ? r.substring(s + 1) : r;
588 }
589
590 FileMode parseFileMode(int ptr, int end) {
591 int tmp = 0;
592 while (ptr < end - 1) {
593 tmp <<= 3;
594 tmp += buf[ptr++] - '0';
595 }
596 return FileMode.fromBits(tmp);
597 }
598
599 void parseIndexLine(int ptr, int end) {
600
601
602
603 final int dot2 = nextLF(buf, ptr, '.');
604 final int mode = nextLF(buf, dot2, ' ');
605
606 oldId = AbbreviatedObjectId.fromString(buf, ptr, dot2 - 1);
607 newId = AbbreviatedObjectId.fromString(buf, dot2 + 1, mode - 1);
608
609 if (mode < end)
610 newMode = oldMode = parseFileMode(mode, end);
611 }
612
613 private boolean eq(int aPtr, int aEnd, int bPtr, int bEnd) {
614 if (aEnd - aPtr != bEnd - bPtr) {
615 return false;
616 }
617 while (aPtr < aEnd) {
618 if (buf[aPtr++] != buf[bPtr++])
619 return false;
620 }
621 return true;
622 }
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641 static int isHunkHdr(byte[] buf, int start, int end) {
642 int ptr = start;
643 while (ptr < end && buf[ptr] == '@')
644 ptr++;
645 if (ptr - start < 2)
646 return 0;
647 if (ptr == end || buf[ptr++] != ' ')
648 return 0;
649 if (ptr == end || buf[ptr++] != '-')
650 return 0;
651 return (ptr - 3) - start;
652 }
653 }