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.util.RawParseUtils.decode;
48 import static org.eclipse.jgit.util.RawParseUtils.decodeNoFallback;
49 import static org.eclipse.jgit.util.RawParseUtils.extractBinaryString;
50 import static org.eclipse.jgit.util.RawParseUtils.match;
51 import static org.eclipse.jgit.util.RawParseUtils.nextLF;
52 import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
53
54 import java.io.IOException;
55 import java.nio.charset.CharacterCodingException;
56 import java.nio.charset.Charset;
57 import java.text.MessageFormat;
58 import java.util.ArrayList;
59 import java.util.Collections;
60 import java.util.List;
61
62 import org.eclipse.jgit.diff.DiffEntry;
63 import org.eclipse.jgit.diff.EditList;
64 import org.eclipse.jgit.internal.JGitText;
65 import org.eclipse.jgit.lib.AbbreviatedObjectId;
66 import org.eclipse.jgit.lib.Constants;
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 = Constants.CHARSET;
245 try {
246 return decodeNoFallback(cs, buf, startOffset, endOffset);
247 } catch (CharacterCodingException cee) {
248
249
250 }
251 }
252
253 final StringBuilder r = new StringBuilder(endOffset - startOffset);
254
255
256
257
258 final int hdrEnd = getHunks().get(0).getStartOffset();
259 for (int ptr = startOffset; ptr < hdrEnd;) {
260 final int eol = Math.min(hdrEnd, nextLF(buf, ptr));
261 r.append(extractBinaryString(buf, ptr, eol));
262 ptr = eol;
263 }
264
265 final String[] files = extractFileLines(charsetGuess);
266 final int[] offsets = new int[files.length];
267 for (HunkHeader h : getHunks())
268 h.extractFileLines(r, files, offsets);
269 return r.toString();
270 }
271
272 private static boolean trySimpleConversion(Charset[] charsetGuess) {
273 if (charsetGuess == null)
274 return true;
275 for (int i = 1; i < charsetGuess.length; i++) {
276 if (charsetGuess[i] != charsetGuess[0])
277 return false;
278 }
279 return true;
280 }
281
282 private String[] extractFileLines(Charset[] csGuess) {
283 final TemporaryBuffer[] tmp = new TemporaryBuffer[getParentCount() + 1];
284 try {
285 for (int i = 0; i < tmp.length; i++)
286 tmp[i] = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
287 for (HunkHeader h : getHunks())
288 h.extractFileLines(tmp);
289
290 final String[] r = new String[tmp.length];
291 for (int i = 0; i < tmp.length; i++) {
292 Charset cs = csGuess != null ? csGuess[i] : null;
293 if (cs == null)
294 cs = Constants.CHARSET;
295 r[i] = RawParseUtils.decode(cs, tmp[i].toByteArray());
296 }
297 return r;
298 } catch (IOException ioe) {
299 throw new RuntimeException(JGitText.get().cannotConvertScriptToText, ioe);
300 }
301 }
302
303
304
305
306
307
308 public PatchType getPatchType() {
309 return patchType;
310 }
311
312
313
314
315
316
317 public boolean hasMetaDataChanges() {
318 return changeType != ChangeType.MODIFY || newMode != oldMode;
319 }
320
321
322
323
324
325
326 public List<? extends HunkHeader> getHunks() {
327 if (hunks == null)
328 return Collections.emptyList();
329 return hunks;
330 }
331
332 void addHunk(HunkHeader h) {
333 if (h.getFileHeader() != this)
334 throw new IllegalArgumentException(JGitText.get().hunkBelongsToAnotherFile);
335 if (hunks == null)
336 hunks = new ArrayList<>();
337 hunks.add(h);
338 }
339
340 HunkHeader newHunkHeader(int offset) {
341 return new HunkHeader(this, offset);
342 }
343
344
345
346
347
348
349
350
351 public BinaryHunk getForwardBinaryHunk() {
352 return forwardBinaryHunk;
353 }
354
355
356
357
358
359
360
361
362 public BinaryHunk getReverseBinaryHunk() {
363 return reverseBinaryHunk;
364 }
365
366
367
368
369
370
371 public EditList toEditList() {
372 final EditList r = new EditList();
373 for (HunkHeader hunk : hunks)
374 r.addAll(hunk.toEditList());
375 return r;
376 }
377
378
379
380
381
382
383
384
385
386
387 int parseGitFileName(int ptr, int end) {
388 final int eol = nextLF(buf, ptr);
389 final int bol = ptr;
390 if (eol >= end) {
391 return -1;
392 }
393
394
395
396
397
398
399
400
401 final int aStart = nextLF(buf, ptr, '/');
402 if (aStart >= eol)
403 return eol;
404
405 while (ptr < eol) {
406 final int sp = nextLF(buf, ptr, ' ');
407 if (sp >= eol) {
408
409
410
411 return eol;
412 }
413 final int bStart = nextLF(buf, sp, '/');
414 if (bStart >= eol)
415 return eol;
416
417
418
419
420 if (eq(aStart, sp - 1, bStart, eol - 1)) {
421 if (buf[bol] == '"') {
422
423
424
425
426 if (buf[sp - 2] != '"') {
427 return eol;
428 }
429 oldPath = QuotedString.GIT_PATH.dequote(buf, bol, sp - 1);
430 oldPath = p1(oldPath);
431 } else {
432 oldPath = decode(Constants.CHARSET, buf, aStart, sp - 1);
433 }
434 newPath = oldPath;
435 return eol;
436 }
437
438
439
440
441 ptr = sp;
442 }
443
444 return eol;
445 }
446
447 int parseGitHeaders(int ptr, int end) {
448 while (ptr < end) {
449 final int eol = nextLF(buf, ptr);
450 if (isHunkHdr(buf, ptr, eol) >= 1) {
451
452 break;
453
454 } else if (match(buf, ptr, OLD_NAME) >= 0) {
455 parseOldName(ptr, eol);
456
457 } else if (match(buf, ptr, NEW_NAME) >= 0) {
458 parseNewName(ptr, eol);
459
460 } else if (match(buf, ptr, OLD_MODE) >= 0) {
461 oldMode = parseFileMode(ptr + OLD_MODE.length, eol);
462
463 } else if (match(buf, ptr, NEW_MODE) >= 0) {
464 newMode = parseFileMode(ptr + NEW_MODE.length, eol);
465
466 } else if (match(buf, ptr, DELETED_FILE_MODE) >= 0) {
467 oldMode = parseFileMode(ptr + DELETED_FILE_MODE.length, eol);
468 newMode = FileMode.MISSING;
469 changeType = ChangeType.DELETE;
470
471 } else if (match(buf, ptr, NEW_FILE_MODE) >= 0) {
472 parseNewFileMode(ptr, eol);
473
474 } else if (match(buf, ptr, COPY_FROM) >= 0) {
475 oldPath = parseName(oldPath, ptr + COPY_FROM.length, eol);
476 changeType = ChangeType.COPY;
477
478 } else if (match(buf, ptr, COPY_TO) >= 0) {
479 newPath = parseName(newPath, ptr + COPY_TO.length, eol);
480 changeType = ChangeType.COPY;
481
482 } else if (match(buf, ptr, RENAME_OLD) >= 0) {
483 oldPath = parseName(oldPath, ptr + RENAME_OLD.length, eol);
484 changeType = ChangeType.RENAME;
485
486 } else if (match(buf, ptr, RENAME_NEW) >= 0) {
487 newPath = parseName(newPath, ptr + RENAME_NEW.length, eol);
488 changeType = ChangeType.RENAME;
489
490 } else if (match(buf, ptr, RENAME_FROM) >= 0) {
491 oldPath = parseName(oldPath, ptr + RENAME_FROM.length, eol);
492 changeType = ChangeType.RENAME;
493
494 } else if (match(buf, ptr, RENAME_TO) >= 0) {
495 newPath = parseName(newPath, ptr + RENAME_TO.length, eol);
496 changeType = ChangeType.RENAME;
497
498 } else if (match(buf, ptr, SIMILARITY_INDEX) >= 0) {
499 score = parseBase10(buf, ptr + SIMILARITY_INDEX.length, null);
500
501 } else if (match(buf, ptr, DISSIMILARITY_INDEX) >= 0) {
502 score = parseBase10(buf, ptr + DISSIMILARITY_INDEX.length, null);
503
504 } else if (match(buf, ptr, INDEX) >= 0) {
505 parseIndexLine(ptr + INDEX.length, eol);
506
507 } else {
508
509 break;
510 }
511
512 ptr = eol;
513 }
514 return ptr;
515 }
516
517 void parseOldName(int ptr, int eol) {
518 oldPath = p1(parseName(oldPath, ptr + OLD_NAME.length, eol));
519 if (oldPath == DEV_NULL)
520 changeType = ChangeType.ADD;
521 }
522
523 void parseNewName(int ptr, int eol) {
524 newPath = p1(parseName(newPath, ptr + NEW_NAME.length, eol));
525 if (newPath == DEV_NULL)
526 changeType = ChangeType.DELETE;
527 }
528
529 void parseNewFileMode(int ptr, int eol) {
530 oldMode = FileMode.MISSING;
531 newMode = parseFileMode(ptr + NEW_FILE_MODE.length, eol);
532 changeType = ChangeType.ADD;
533 }
534
535 int parseTraditionalHeaders(int ptr, int end) {
536 while (ptr < end) {
537 final int eol = nextLF(buf, ptr);
538 if (isHunkHdr(buf, ptr, eol) >= 1) {
539
540 break;
541
542 } else if (match(buf, ptr, OLD_NAME) >= 0) {
543 parseOldName(ptr, eol);
544
545 } else if (match(buf, ptr, NEW_NAME) >= 0) {
546 parseNewName(ptr, eol);
547
548 } else {
549
550 break;
551 }
552
553 ptr = eol;
554 }
555 return ptr;
556 }
557
558 private String parseName(String expect, int ptr, int end) {
559 if (ptr == end)
560 return expect;
561
562 String r;
563 if (buf[ptr] == '"') {
564
565
566 r = QuotedString.GIT_PATH.dequote(buf, ptr, end - 1);
567 } else {
568
569
570 int tab = end;
571 while (ptr < tab && buf[tab - 1] != '\t')
572 tab--;
573 if (ptr == tab)
574 tab = end;
575 r = decode(Constants.CHARSET, buf, ptr, tab - 1);
576 }
577
578 if (r.equals(DEV_NULL))
579 r = DEV_NULL;
580 return r;
581 }
582
583 private static String p1(final String r) {
584 final int s = r.indexOf('/');
585 return s > 0 ? r.substring(s + 1) : r;
586 }
587
588 FileMode parseFileMode(int ptr, int end) {
589 int tmp = 0;
590 while (ptr < end - 1) {
591 tmp <<= 3;
592 tmp += buf[ptr++] - '0';
593 }
594 return FileMode.fromBits(tmp);
595 }
596
597 void parseIndexLine(int ptr, int end) {
598
599
600
601 final int dot2 = nextLF(buf, ptr, '.');
602 final int mode = nextLF(buf, dot2, ' ');
603
604 oldId = AbbreviatedObjectId.fromString(buf, ptr, dot2 - 1);
605 newId = AbbreviatedObjectId.fromString(buf, dot2 + 1, mode - 1);
606
607 if (mode < end)
608 newMode = oldMode = parseFileMode(mode, end);
609 }
610
611 private boolean eq(int aPtr, int aEnd, int bPtr, int bEnd) {
612 if (aEnd - aPtr != bEnd - bPtr) {
613 return false;
614 }
615 while (aPtr < aEnd) {
616 if (buf[aPtr++] != buf[bPtr++])
617 return false;
618 }
619 return true;
620 }
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639 static int isHunkHdr(byte[] buf, int start, int end) {
640 int ptr = start;
641 while (ptr < end && buf[ptr] == '@')
642 ptr++;
643 if (ptr - start < 2)
644 return 0;
645 if (ptr == end || buf[ptr++] != ' ')
646 return 0;
647 if (ptr == end || buf[ptr++] != '-')
648 return 0;
649 return (ptr - 3) - start;
650 }
651 }