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