1
2
3
4
5
6
7
8
9
10
11
12 package org.eclipse.jgit.diff;
13
14 import static org.eclipse.jgit.diff.DiffEntry.ChangeType.ADD;
15 import static org.eclipse.jgit.diff.DiffEntry.ChangeType.COPY;
16 import static org.eclipse.jgit.diff.DiffEntry.ChangeType.DELETE;
17 import static org.eclipse.jgit.diff.DiffEntry.ChangeType.MODIFY;
18 import static org.eclipse.jgit.diff.DiffEntry.ChangeType.RENAME;
19 import static org.eclipse.jgit.diff.DiffEntry.Side.NEW;
20 import static org.eclipse.jgit.diff.DiffEntry.Side.OLD;
21 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
22 import static org.eclipse.jgit.lib.Constants.encode;
23 import static org.eclipse.jgit.lib.Constants.encodeASCII;
24 import static org.eclipse.jgit.lib.FileMode.GITLINK;
25
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.io.OutputStream;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.List;
32
33 import org.eclipse.jgit.api.errors.CanceledException;
34 import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
35 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
36 import org.eclipse.jgit.dircache.DirCacheIterator;
37 import org.eclipse.jgit.errors.AmbiguousObjectException;
38 import org.eclipse.jgit.errors.BinaryBlobException;
39 import org.eclipse.jgit.errors.CorruptObjectException;
40 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
41 import org.eclipse.jgit.errors.MissingObjectException;
42 import org.eclipse.jgit.internal.JGitText;
43 import org.eclipse.jgit.lib.AbbreviatedObjectId;
44 import org.eclipse.jgit.lib.AnyObjectId;
45 import org.eclipse.jgit.lib.Config;
46 import org.eclipse.jgit.lib.ConfigConstants;
47 import org.eclipse.jgit.lib.Constants;
48 import org.eclipse.jgit.lib.FileMode;
49 import org.eclipse.jgit.lib.ObjectId;
50 import org.eclipse.jgit.lib.ObjectLoader;
51 import org.eclipse.jgit.lib.ObjectReader;
52 import org.eclipse.jgit.lib.ProgressMonitor;
53 import org.eclipse.jgit.lib.Repository;
54 import org.eclipse.jgit.patch.FileHeader;
55 import org.eclipse.jgit.patch.FileHeader.PatchType;
56 import org.eclipse.jgit.revwalk.FollowFilter;
57 import org.eclipse.jgit.revwalk.RevTree;
58 import org.eclipse.jgit.revwalk.RevWalk;
59 import org.eclipse.jgit.storage.pack.PackConfig;
60 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
61 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
62 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
63 import org.eclipse.jgit.treewalk.TreeWalk;
64 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
65 import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
66 import org.eclipse.jgit.treewalk.filter.IndexDiffFilter;
67 import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
68 import org.eclipse.jgit.treewalk.filter.PathFilter;
69 import org.eclipse.jgit.treewalk.filter.TreeFilter;
70 import org.eclipse.jgit.util.LfsFactory;
71 import org.eclipse.jgit.util.QuotedString;
72
73
74
75
76 public class DiffFormatter implements AutoCloseable {
77 private static final int DEFAULT_BINARY_FILE_THRESHOLD = PackConfig.DEFAULT_BIG_FILE_THRESHOLD;
78
79 private static final byte[] noNewLine = encodeASCII("\\ No newline at end of file\n");
80
81
82 private static final byte[] EMPTY = new byte[] {};
83
84 private final OutputStream out;
85
86 private ObjectReader reader;
87
88 private boolean closeReader;
89
90 private DiffConfig diffCfg;
91
92 private int context = 3;
93
94 private int abbreviationLength = OBJECT_ID_ABBREV_STRING_LENGTH;
95
96 private DiffAlgorithm diffAlgorithm;
97
98 private RawTextComparator comparator = RawTextComparator.DEFAULT;
99
100 private int binaryFileThreshold = DEFAULT_BINARY_FILE_THRESHOLD;
101
102 private String oldPrefix = "a/";
103
104 private String newPrefix = "b/";
105
106 private TreeFilter pathFilter = TreeFilter.ALL;
107
108 private RenameDetector renameDetector;
109
110 private ProgressMonitor progressMonitor;
111
112 private ContentSource.Pair source;
113
114 private Repository repository;
115
116 private Boolean quotePaths;
117
118
119
120
121
122
123
124
125
126 public DiffFormatter(OutputStream out) {
127 this.out = out;
128 }
129
130
131
132
133
134
135 protected OutputStream getOutputStream() {
136 return out;
137 }
138
139
140
141
142
143
144
145
146
147
148 public void setRepository(Repository repository) {
149 this.repository = repository;
150 setReader(repository.newObjectReader(), repository.getConfig(), true);
151 }
152
153
154
155
156
157
158
159
160
161
162
163 public void setReader(ObjectReader reader, Config cfg) {
164 setReader(reader, cfg, false);
165 }
166
167 private void setReader(ObjectReader reader, Config cfg, boolean closeReader) {
168 close();
169 this.closeReader = closeReader;
170 this.reader = reader;
171 this.diffCfg = cfg.get(DiffConfig.KEY);
172 if (quotePaths == null) {
173 quotePaths = Boolean
174 .valueOf(cfg.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
175 ConfigConstants.CONFIG_KEY_QUOTE_PATH, true));
176 }
177
178 ContentSource cs = ContentSource.create(reader);
179 source = new ContentSource.Pair(cs, cs);
180
181 if (diffCfg.isNoPrefix()) {
182 setOldPrefix("");
183 setNewPrefix("");
184 }
185 setDetectRenames(diffCfg.isRenameDetectionEnabled());
186
187 diffAlgorithm = DiffAlgorithm.getAlgorithm(cfg.getEnum(
188 ConfigConstants.CONFIG_DIFF_SECTION, null,
189 ConfigConstants.CONFIG_KEY_ALGORITHM,
190 SupportedAlgorithm.HISTOGRAM));
191 }
192
193
194
195
196
197
198
199
200
201 public void setContext(int lineCount) {
202 if (lineCount < 0)
203 throw new IllegalArgumentException(
204 JGitText.get().contextMustBeNonNegative);
205 context = lineCount;
206 }
207
208
209
210
211
212
213
214 public void setAbbreviationLength(int count) {
215 if (count < 0)
216 throw new IllegalArgumentException(
217 JGitText.get().abbreviationLengthMustBeNonNegative);
218 abbreviationLength = count;
219 }
220
221
222
223
224
225
226
227
228 public void setDiffAlgorithm(DiffAlgorithm alg) {
229 diffAlgorithm = alg;
230 }
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245 public void setDiffComparator(RawTextComparator cmp) {
246 comparator = cmp;
247 }
248
249
250
251
252
253
254
255
256
257
258
259 public void setBinaryFileThreshold(int threshold) {
260 this.binaryFileThreshold = threshold;
261 }
262
263
264
265
266
267
268
269
270
271
272 public void setOldPrefix(String prefix) {
273 oldPrefix = prefix;
274 }
275
276
277
278
279
280
281
282 public String getOldPrefix() {
283 return this.oldPrefix;
284 }
285
286
287
288
289
290
291
292
293
294
295 public void setNewPrefix(String prefix) {
296 newPrefix = prefix;
297 }
298
299
300
301
302
303
304
305 public String getNewPrefix() {
306 return this.newPrefix;
307 }
308
309
310
311
312
313
314 public boolean isDetectRenames() {
315 return renameDetector != null;
316 }
317
318
319
320
321
322
323
324
325
326
327
328
329 public void setDetectRenames(boolean on) {
330 if (on && renameDetector == null) {
331 assertHaveReader();
332 renameDetector = new RenameDetector(reader, diffCfg);
333 } else if (!on)
334 renameDetector = null;
335 }
336
337
338
339
340
341
342 public RenameDetector getRenameDetector() {
343 return renameDetector;
344 }
345
346
347
348
349
350
351
352 public void setProgressMonitor(ProgressMonitor pm) {
353 progressMonitor = pm;
354 }
355
356
357
358
359
360
361
362
363
364
365
366
367 public void setQuotePaths(boolean quote) {
368 quotePaths = Boolean.valueOf(quote);
369 }
370
371
372
373
374
375
376
377
378
379
380
381
382 public void setPathFilter(TreeFilter filter) {
383 pathFilter = filter != null ? filter : TreeFilter.ALL;
384 }
385
386
387
388
389
390
391 public TreeFilter getPathFilter() {
392 return pathFilter;
393 }
394
395
396
397
398
399
400
401 public void flush() throws IOException {
402 out.flush();
403 }
404
405
406
407
408
409
410
411
412 @Override
413 public void close() {
414 if (reader != null && closeReader) {
415 reader.close();
416 }
417 }
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438 public List<DiffEntry> scan(AnyObjectId a, AnyObjectId b)
439 throws IOException {
440 assertHaveReader();
441
442 try (RevWalk rw = new RevWalk(reader)) {
443 RevTree aTree = a != null ? rw.parseTree(a) : null;
444 RevTree bTree = b != null ? rw.parseTree(b) : null;
445 return scan(aTree, bTree);
446 }
447 }
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468 public List<DiffEntry> scan(RevTree a, RevTree b) throws IOException {
469 assertHaveReader();
470
471 AbstractTreeIterator aIterator = makeIteratorFromTreeOrNull(a);
472 AbstractTreeIterator bIterator = makeIteratorFromTreeOrNull(b);
473 return scan(aIterator, bIterator);
474 }
475
476 private AbstractTreeIterator makeIteratorFromTreeOrNull(RevTree tree)
477 throws IncorrectObjectTypeException, IOException {
478 if (tree != null) {
479 CanonicalTreeParser parser = new CanonicalTreeParser();
480 parser.reset(reader, tree);
481 return parser;
482 }
483 return new EmptyTreeIterator();
484 }
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502 public List<DiffEntry> scan(AbstractTreeIterator a, AbstractTreeIterator b)
503 throws IOException {
504 assertHaveReader();
505
506 TreeWalk walk = new TreeWalk(repository, reader);
507 int aIndex = walk.addTree(a);
508 int bIndex = walk.addTree(b);
509 if (repository != null) {
510 if (a instanceof WorkingTreeIterator
511 && b instanceof DirCacheIterator) {
512 ((WorkingTreeIterator) a).setDirCacheIterator(walk, bIndex);
513 } else if (b instanceof WorkingTreeIterator
514 && a instanceof DirCacheIterator) {
515 ((WorkingTreeIterator) b).setDirCacheIterator(walk, aIndex);
516 }
517 }
518 walk.setRecursive(true);
519
520 TreeFilter filter = getDiffTreeFilterFor(a, b);
521 if (pathFilter instanceof FollowFilter) {
522 walk.setFilter(AndTreeFilter.create(
523 PathFilter.create(((FollowFilter) pathFilter).getPath()),
524 filter));
525 } else {
526 walk.setFilter(AndTreeFilter.create(pathFilter, filter));
527 }
528
529 source = new ContentSource.Pair(source(a), source(b));
530
531 List<DiffEntry> files = DiffEntry.scan(walk);
532 if (pathFilter instanceof FollowFilter && isAdd(files)) {
533
534
535
536
537 a.reset();
538 b.reset();
539 walk.reset();
540 walk.addTree(a);
541 walk.addTree(b);
542 walk.setFilter(filter);
543
544 if (renameDetector == null)
545 setDetectRenames(true);
546 files = updateFollowFilter(detectRenames(DiffEntry.scan(walk)));
547
548 } else if (renameDetector != null)
549 files = detectRenames(files);
550
551 return files;
552 }
553
554 private static TreeFilter getDiffTreeFilterFor(AbstractTreeIterator a,
555 AbstractTreeIterator b) {
556 if (a instanceof DirCacheIterator && b instanceof WorkingTreeIterator)
557 return new IndexDiffFilter(0, 1);
558
559 if (a instanceof WorkingTreeIterator && b instanceof DirCacheIterator)
560 return new IndexDiffFilter(1, 0);
561
562 TreeFilter filter = TreeFilter.ANY_DIFF;
563 if (a instanceof WorkingTreeIterator)
564 filter = AndTreeFilter.create(new NotIgnoredFilter(0), filter);
565 if (b instanceof WorkingTreeIterator)
566 filter = AndTreeFilter.create(new NotIgnoredFilter(1), filter);
567 return filter;
568 }
569
570 private ContentSource source(AbstractTreeIterator iterator) {
571 if (iterator instanceof WorkingTreeIterator)
572 return ContentSource.create((WorkingTreeIterator) iterator);
573 return ContentSource.create(reader);
574 }
575
576 private List<DiffEntry> detectRenames(List<DiffEntry> files)
577 throws IOException {
578 renameDetector.reset();
579 renameDetector.addAll(files);
580 try {
581 return renameDetector.compute(reader, progressMonitor);
582 } catch (CanceledException e) {
583
584
585
586 return Collections.emptyList();
587 }
588 }
589
590 private boolean isAdd(List<DiffEntry> files) {
591 String oldPath = ((FollowFilter) pathFilter).getPath();
592 for (DiffEntry ent : files) {
593 if (ent.getChangeType() == ADD && ent.getNewPath().equals(oldPath))
594 return true;
595 }
596 return false;
597 }
598
599 private List<DiffEntry> updateFollowFilter(List<DiffEntry> files) {
600 String oldPath = ((FollowFilter) pathFilter).getPath();
601 for (DiffEntry ent : files) {
602 if (isRename(ent) && ent.getNewPath().equals(oldPath)) {
603 pathFilter = FollowFilter.create(ent.getOldPath(), diffCfg);
604 return Collections.singletonList(ent);
605 }
606 }
607 return Collections.emptyList();
608 }
609
610 private static boolean isRename(DiffEntry ent) {
611 return ent.getChangeType() == RENAME || ent.getChangeType() == COPY;
612 }
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631 public void format(AnyObjectId a, AnyObjectId b) throws IOException {
632 format(scan(a, b));
633 }
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653 public void format(RevTree a, RevTree b) throws IOException {
654 format(scan(a, b));
655 }
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674 public void format(AbstractTreeIterator a, AbstractTreeIterator b)
675 throws IOException {
676 format(scan(a, b));
677 }
678
679
680
681
682
683
684
685
686
687
688
689
690 public void format(List<? extends DiffEntry> entries) throws IOException {
691 for (DiffEntry ent : entries)
692 format(ent);
693 }
694
695
696
697
698
699
700
701
702
703
704 public void format(DiffEntry ent) throws IOException {
705 FormatResult res = createFormatResult(ent);
706 format(res.header, res.a, res.b);
707 }
708
709 private static byte[] writeGitLinkText(AbbreviatedObjectId id) {
710 if (ObjectId.zeroId().equals(id.toObjectId())) {
711 return EMPTY;
712 }
713 return encodeASCII("Subproject commit " + id.name()
714 + "\n");
715 }
716
717 private String format(AbbreviatedObjectId id) {
718 if (id.isComplete() && reader != null) {
719 try {
720 id = reader.abbreviate(id.toObjectId(), abbreviationLength);
721 } catch (IOException cannotAbbreviate) {
722
723 }
724 }
725 return id.name();
726 }
727
728 private String quotePath(String path) {
729 if (quotePaths == null || quotePaths.booleanValue()) {
730 return QuotedString.GIT_PATH.quote(path);
731 }
732 return QuotedString.GIT_PATH_MINIMAL.quote(path);
733 }
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755 public void format(FileHeader head, RawText a, RawText b)
756 throws IOException {
757
758
759
760
761 final int start = head.getStartOffset();
762 int end = head.getEndOffset();
763 if (!head.getHunks().isEmpty())
764 end = head.getHunks().get(0).getStartOffset();
765 out.write(head.getBuffer(), start, end - start);
766 if (head.getPatchType() == PatchType.UNIFIED)
767 format(head.toEditList(), a, b);
768 }
769
770
771
772
773
774
775
776
777
778
779
780
781 public void format(EditList edits, RawText a, RawText b)
782 throws IOException {
783 for (int curIdx = 0; curIdx < edits.size();) {
784 Edit curEdit = edits.get(curIdx);
785 final int endIdx = findCombinedEnd(edits, curIdx);
786 final Edit endEdit = edits.get(endIdx);
787
788 int aCur = (int) Math.max(0, (long) curEdit.getBeginA() - context);
789 int bCur = (int) Math.max(0, (long) curEdit.getBeginB() - context);
790 final int aEnd = (int) Math.min(a.size(), (long) endEdit.getEndA() + context);
791 final int bEnd = (int) Math.min(b.size(), (long) endEdit.getEndB() + context);
792
793 writeHunkHeader(aCur, aEnd, bCur, bEnd);
794
795 while (aCur < aEnd || bCur < bEnd) {
796 if (aCur < curEdit.getBeginA() || endIdx + 1 < curIdx) {
797 writeContextLine(a, aCur);
798 if (isEndOfLineMissing(a, aCur))
799 out.write(noNewLine);
800 aCur++;
801 bCur++;
802 } else if (aCur < curEdit.getEndA()) {
803 writeRemovedLine(a, aCur);
804 if (isEndOfLineMissing(a, aCur))
805 out.write(noNewLine);
806 aCur++;
807 } else if (bCur < curEdit.getEndB()) {
808 writeAddedLine(b, bCur);
809 if (isEndOfLineMissing(b, bCur))
810 out.write(noNewLine);
811 bCur++;
812 }
813
814 if (end(curEdit, aCur, bCur) && ++curIdx < edits.size())
815 curEdit = edits.get(curIdx);
816 }
817 }
818 }
819
820
821
822
823
824
825
826
827
828
829 protected void writeContextLine(RawText text, int line)
830 throws IOException {
831 writeLine(' ', text, line);
832 }
833
834 private static boolean isEndOfLineMissing(RawText text, int line) {
835 return line + 1 == text.size() && text.isMissingNewlineAtEnd();
836 }
837
838
839
840
841
842
843
844
845
846
847 protected void writeAddedLine(RawText text, int line)
848 throws IOException {
849 writeLine('+', text, line);
850 }
851
852
853
854
855
856
857
858
859
860
861 protected void writeRemovedLine(RawText text, int line)
862 throws IOException {
863 writeLine('-', text, line);
864 }
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879 protected void writeHunkHeader(int aStartLine, int aEndLine,
880 int bStartLine, int bEndLine) throws IOException {
881 out.write('@');
882 out.write('@');
883 writeRange('-', aStartLine + 1, aEndLine - aStartLine);
884 writeRange('+', bStartLine + 1, bEndLine - bStartLine);
885 out.write(' ');
886 out.write('@');
887 out.write('@');
888 out.write('\n');
889 }
890
891 private void writeRange(char prefix, int begin, int cnt)
892 throws IOException {
893 out.write(' ');
894 out.write(prefix);
895 switch (cnt) {
896 case 0:
897
898
899
900
901
902 out.write(encodeASCII(begin - 1));
903 out.write(',');
904 out.write('0');
905 break;
906
907 case 1:
908
909
910 out.write(encodeASCII(begin));
911 break;
912
913 default:
914 out.write(encodeASCII(begin));
915 out.write(',');
916 out.write(encodeASCII(cnt));
917 break;
918 }
919 }
920
921
922
923
924
925
926
927
928
929
930
931
932
933 protected void writeLine(final char prefix, final RawText text,
934 final int cur) throws IOException {
935 out.write(prefix);
936 text.writeLine(out, cur);
937 out.write('\n');
938 }
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963 public FileHeader toFileHeader(DiffEntry ent) throws IOException,
964 CorruptObjectException, MissingObjectException {
965 return createFormatResult(ent).header;
966 }
967
968 private static class FormatResult {
969 FileHeader header;
970
971 RawText a;
972
973 RawText b;
974 }
975
976 private FormatResult createFormatResult(DiffEntry ent) throws IOException,
977 CorruptObjectException, MissingObjectException {
978 final FormatResult res = new FormatResult();
979 ByteArrayOutputStream buf = new ByteArrayOutputStream();
980 final EditList editList;
981 final FileHeader.PatchType type;
982
983 formatHeader(buf, ent);
984
985 if (ent.getOldId() == null || ent.getNewId() == null) {
986
987 editList = new EditList();
988 type = PatchType.UNIFIED;
989 res.header = new FileHeader(buf.toByteArray(), editList, type);
990 return res;
991 }
992
993 assertHaveReader();
994
995 RawText aRaw = null;
996 RawText bRaw = null;
997 if (ent.getOldMode() == GITLINK || ent.getNewMode() == GITLINK) {
998 aRaw = new RawText(writeGitLinkText(ent.getOldId()));
999 bRaw = new RawText(writeGitLinkText(ent.getNewId()));
1000 } else {
1001 try {
1002 aRaw = open(OLD, ent);
1003 bRaw = open(NEW, ent);
1004 } catch (BinaryBlobException e) {
1005
1006 formatOldNewPaths(buf, ent);
1007 buf.write(encodeASCII("Binary files differ\n"));
1008 editList = new EditList();
1009 type = PatchType.BINARY;
1010 res.header = new FileHeader(buf.toByteArray(), editList, type);
1011 return res;
1012 }
1013 }
1014
1015 res.a = aRaw;
1016 res.b = bRaw;
1017 editList = diff(res.a, res.b);
1018 type = PatchType.UNIFIED;
1019
1020 switch (ent.getChangeType()) {
1021 case RENAME:
1022 case COPY:
1023 if (!editList.isEmpty())
1024 formatOldNewPaths(buf, ent);
1025 break;
1026
1027 default:
1028 formatOldNewPaths(buf, ent);
1029 break;
1030 }
1031
1032
1033 res.header = new FileHeader(buf.toByteArray(), editList, type);
1034 return res;
1035 }
1036
1037 private EditList diff(RawText a, RawText b) {
1038 return diffAlgorithm.diff(comparator, a, b);
1039 }
1040
1041 private void assertHaveReader() {
1042 if (reader == null) {
1043 throw new IllegalStateException(JGitText.get().readerIsRequired);
1044 }
1045 }
1046
1047 private RawText open(DiffEntry.Side side, DiffEntry entry)
1048 throws IOException, BinaryBlobException {
1049 if (entry.getMode(side) == FileMode.MISSING)
1050 return RawText.EMPTY_TEXT;
1051
1052 if (entry.getMode(side).getObjectType() != Constants.OBJ_BLOB)
1053 return RawText.EMPTY_TEXT;
1054
1055 AbbreviatedObjectId id = entry.getId(side);
1056 if (!id.isComplete()) {
1057 Collection<ObjectId> ids = reader.resolve(id);
1058 if (ids.size() == 1) {
1059 id = AbbreviatedObjectId.fromObjectId(ids.iterator().next());
1060 switch (side) {
1061 case OLD:
1062 entry.oldId = id;
1063 break;
1064 case NEW:
1065 entry.newId = id;
1066 break;
1067 }
1068 } else if (ids.isEmpty())
1069 throw new MissingObjectException(id, Constants.OBJ_BLOB);
1070 else
1071 throw new AmbiguousObjectException(id, ids);
1072 }
1073
1074 ObjectLoader ldr = LfsFactory.getInstance().applySmudgeFilter(repository,
1075 source.open(side, entry), entry.getDiffAttribute());
1076 return RawText.load(ldr, binaryFileThreshold);
1077 }
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093 protected void formatGitDiffFirstHeaderLine(ByteArrayOutputStream o,
1094 final ChangeType type, final String oldPath, final String newPath)
1095 throws IOException {
1096 o.write(encodeASCII("diff --git "));
1097 o.write(encode(quotePath(oldPrefix + (type == ADD ? newPath : oldPath))));
1098 o.write(' ');
1099 o.write(encode(quotePath(newPrefix
1100 + (type == DELETE ? oldPath : newPath))));
1101 o.write('\n');
1102 }
1103
1104 private void formatHeader(ByteArrayOutputStream o, DiffEntry ent)
1105 throws IOException {
1106 final ChangeType type = ent.getChangeType();
1107 final String oldp = ent.getOldPath();
1108 final String newp = ent.getNewPath();
1109 final FileMode oldMode = ent.getOldMode();
1110 final FileMode newMode = ent.getNewMode();
1111
1112 formatGitDiffFirstHeaderLine(o, type, oldp, newp);
1113
1114 if ((type == MODIFY || type == COPY || type == RENAME)
1115 && !oldMode.equals(newMode)) {
1116 o.write(encodeASCII("old mode "));
1117 oldMode.copyTo(o);
1118 o.write('\n');
1119
1120 o.write(encodeASCII("new mode "));
1121 newMode.copyTo(o);
1122 o.write('\n');
1123 }
1124
1125 switch (type) {
1126 case ADD:
1127 o.write(encodeASCII("new file mode "));
1128 newMode.copyTo(o);
1129 o.write('\n');
1130 break;
1131
1132 case DELETE:
1133 o.write(encodeASCII("deleted file mode "));
1134 oldMode.copyTo(o);
1135 o.write('\n');
1136 break;
1137
1138 case RENAME:
1139 o.write(encodeASCII("similarity index " + ent.getScore() + "%"));
1140 o.write('\n');
1141
1142 o.write(encode("rename from " + quotePath(oldp)));
1143 o.write('\n');
1144
1145 o.write(encode("rename to " + quotePath(newp)));
1146 o.write('\n');
1147 break;
1148
1149 case COPY:
1150 o.write(encodeASCII("similarity index " + ent.getScore() + "%"));
1151 o.write('\n');
1152
1153 o.write(encode("copy from " + quotePath(oldp)));
1154 o.write('\n');
1155
1156 o.write(encode("copy to " + quotePath(newp)));
1157 o.write('\n');
1158 break;
1159
1160 case MODIFY:
1161 if (0 < ent.getScore()) {
1162 o.write(encodeASCII("dissimilarity index "
1163 + (100 - ent.getScore()) + "%"));
1164 o.write('\n');
1165 }
1166 break;
1167 }
1168
1169 if (ent.getOldId() != null && !ent.getOldId().equals(ent.getNewId())) {
1170 formatIndexLine(o, ent);
1171 }
1172 }
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184 protected void formatIndexLine(OutputStream o, DiffEntry ent)
1185 throws IOException {
1186 o.write(encodeASCII("index "
1187 + format(ent.getOldId())
1188 + ".."
1189 + format(ent.getNewId())));
1190 if (ent.getOldMode().equals(ent.getNewMode())) {
1191 o.write(' ');
1192 ent.getNewMode().copyTo(o);
1193 }
1194 o.write('\n');
1195 }
1196
1197 private void formatOldNewPaths(ByteArrayOutputStream o, DiffEntry ent)
1198 throws IOException {
1199 if (ent.oldId.equals(ent.newId))
1200 return;
1201
1202 final String oldp;
1203 final String newp;
1204
1205 switch (ent.getChangeType()) {
1206 case ADD:
1207 oldp = DiffEntry.DEV_NULL;
1208 newp = quotePath(newPrefix + ent.getNewPath());
1209 break;
1210
1211 case DELETE:
1212 oldp = quotePath(oldPrefix + ent.getOldPath());
1213 newp = DiffEntry.DEV_NULL;
1214 break;
1215
1216 default:
1217 oldp = quotePath(oldPrefix + ent.getOldPath());
1218 newp = quotePath(newPrefix + ent.getNewPath());
1219 break;
1220 }
1221
1222 o.write(encode("--- " + oldp + "\n"));
1223 o.write(encode("+++ " + newp + "\n"));
1224 }
1225
1226 private int findCombinedEnd(List<Edit> edits, int i) {
1227 int end = i + 1;
1228 while (end < edits.size()
1229 && (combineA(edits, end) || combineB(edits, end)))
1230 end++;
1231 return end - 1;
1232 }
1233
1234 private boolean combineA(List<Edit> e, int i) {
1235 return e.get(i).getBeginA() - e.get(i - 1).getEndA() <= 2 * context;
1236 }
1237
1238 private boolean combineB(List<Edit> e, int i) {
1239 return e.get(i).getBeginB() - e.get(i - 1).getEndB() <= 2 * context;
1240 }
1241
1242 private static boolean end(Edit edit, int a, int b) {
1243 return edit.getEndA() <= a && edit.getEndB() <= b;
1244 }
1245 }