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