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