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(reader);
506 walk.addTree(a);
507 walk.addTree(b);
508 walk.setRecursive(true);
509
510 TreeFilter filter = getDiffTreeFilterFor(a, b);
511 if (pathFilter instanceof FollowFilter) {
512 walk.setFilter(AndTreeFilter.create(
513 PathFilter.create(((FollowFilter) pathFilter).getPath()),
514 filter));
515 } else {
516 walk.setFilter(AndTreeFilter.create(pathFilter, filter));
517 }
518
519 source = new ContentSource.Pair(source(a), source(b));
520
521 List<DiffEntry> files = DiffEntry.scan(walk);
522 if (pathFilter instanceof FollowFilter && isAdd(files)) {
523
524
525
526
527 a.reset();
528 b.reset();
529 walk.reset();
530 walk.addTree(a);
531 walk.addTree(b);
532 walk.setFilter(filter);
533
534 if (renameDetector == null)
535 setDetectRenames(true);
536 files = updateFollowFilter(detectRenames(DiffEntry.scan(walk)));
537
538 } else if (renameDetector != null)
539 files = detectRenames(files);
540
541 return files;
542 }
543
544 private static TreeFilter getDiffTreeFilterFor(AbstractTreeIterator a,
545 AbstractTreeIterator b) {
546 if (a instanceof DirCacheIterator && b instanceof WorkingTreeIterator)
547 return new IndexDiffFilter(0, 1);
548
549 if (a instanceof WorkingTreeIterator && b instanceof DirCacheIterator)
550 return new IndexDiffFilter(1, 0);
551
552 TreeFilter filter = TreeFilter.ANY_DIFF;
553 if (a instanceof WorkingTreeIterator)
554 filter = AndTreeFilter.create(new NotIgnoredFilter(0), filter);
555 if (b instanceof WorkingTreeIterator)
556 filter = AndTreeFilter.create(new NotIgnoredFilter(1), filter);
557 return filter;
558 }
559
560 private ContentSource source(AbstractTreeIterator iterator) {
561 if (iterator instanceof WorkingTreeIterator)
562 return ContentSource.create((WorkingTreeIterator) iterator);
563 return ContentSource.create(reader);
564 }
565
566 private List<DiffEntry> detectRenames(List<DiffEntry> files)
567 throws IOException {
568 renameDetector.reset();
569 renameDetector.addAll(files);
570 try {
571 return renameDetector.compute(reader, progressMonitor);
572 } catch (CancelledException e) {
573
574
575
576 return Collections.emptyList();
577 }
578 }
579
580 private boolean isAdd(List<DiffEntry> files) {
581 String oldPath = ((FollowFilter) pathFilter).getPath();
582 for (DiffEntry ent : files) {
583 if (ent.getChangeType() == ADD && ent.getNewPath().equals(oldPath))
584 return true;
585 }
586 return false;
587 }
588
589 private List<DiffEntry> updateFollowFilter(List<DiffEntry> files) {
590 String oldPath = ((FollowFilter) pathFilter).getPath();
591 for (DiffEntry ent : files) {
592 if (isRename(ent) && ent.getNewPath().equals(oldPath)) {
593 pathFilter = FollowFilter.create(ent.getOldPath(), diffCfg);
594 return Collections.singletonList(ent);
595 }
596 }
597 return Collections.emptyList();
598 }
599
600 private static boolean isRename(DiffEntry ent) {
601 return ent.getChangeType() == RENAME || ent.getChangeType() == COPY;
602 }
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621 public void format(AnyObjectId="../../../../org/eclipse/jgit/lib/AnyObjectId.html#AnyObjectId">AnyObjectId a, AnyObjectId b) throws IOException {
622 format(scan(a, b));
623 }
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643 public void format(RevTreehref="../../../../org/eclipse/jgit/revwalk/RevTree.html#RevTree">RevTree a, RevTree b) throws IOException {
644 format(scan(a, b));
645 }
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664 public void format(AbstractTreeIterator./../org/eclipse/jgit/treewalk/AbstractTreeIterator.html#AbstractTreeIterator">AbstractTreeIterator a, AbstractTreeIterator b)
665 throws IOException {
666 format(scan(a, b));
667 }
668
669
670
671
672
673
674
675
676
677
678
679
680 public void format(List<? extends DiffEntry> entries) throws IOException {
681 for (DiffEntry ent : entries)
682 format(ent);
683 }
684
685
686
687
688
689
690
691
692
693
694 public void format(DiffEntry ent) throws IOException {
695 FormatResult res = createFormatResult(ent);
696 format(res.header, res.a, res.b);
697 }
698
699 private static byte[] writeGitLinkText(AbbreviatedObjectId id) {
700 if (ObjectId.zeroId().equals(id.toObjectId())) {
701 return EMPTY;
702 }
703 return encodeASCII("Subproject commit " + id.name()
704 + "\n");
705 }
706
707 private String format(AbbreviatedObjectId id) {
708 if (id.isComplete() && reader != null) {
709 try {
710 id = reader.abbreviate(id.toObjectId(), abbreviationLength);
711 } catch (IOException cannotAbbreviate) {
712
713 }
714 }
715 return id.name();
716 }
717
718 private String quotePath(String path) {
719 if (quotePaths == null || quotePaths.booleanValue()) {
720 return QuotedString.GIT_PATH.quote(path);
721 }
722 return QuotedString.GIT_PATH_MINIMAL.quote(path);
723 }
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745 public void format(FileHeader head, RawTexthref="../../../../org/eclipse/jgit/diff/RawText.html#RawText">RawText a, RawText b)
746 throws IOException {
747
748
749
750
751 final int start = head.getStartOffset();
752 int end = head.getEndOffset();
753 if (!head.getHunks().isEmpty())
754 end = head.getHunks().get(0).getStartOffset();
755 out.write(head.getBuffer(), start, end - start);
756 if (head.getPatchType() == PatchType.UNIFIED)
757 format(head.toEditList(), a, b);
758 }
759
760
761
762
763
764
765
766
767
768
769
770
771 public void format(EditList edits, RawTexthref="../../../../org/eclipse/jgit/diff/RawText.html#RawText">RawText a, RawText b)
772 throws IOException {
773 for (int curIdx = 0; curIdx < edits.size();) {
774 Edit curEdit = edits.get(curIdx);
775 final int endIdx = findCombinedEnd(edits, curIdx);
776 final Edit endEdit = edits.get(endIdx);
777
778 int aCur = (int) Math.max(0, (long) curEdit.getBeginA() - context);
779 int bCur = (int) Math.max(0, (long) curEdit.getBeginB() - context);
780 final int aEnd = (int) Math.min(a.size(), (long) endEdit.getEndA() + context);
781 final int bEnd = (int) Math.min(b.size(), (long) endEdit.getEndB() + context);
782
783 writeHunkHeader(aCur, aEnd, bCur, bEnd);
784
785 while (aCur < aEnd || bCur < bEnd) {
786 if (aCur < curEdit.getBeginA() || endIdx + 1 < curIdx) {
787 writeContextLine(a, aCur);
788 if (isEndOfLineMissing(a, aCur))
789 out.write(noNewLine);
790 aCur++;
791 bCur++;
792 } else if (aCur < curEdit.getEndA()) {
793 writeRemovedLine(a, aCur);
794 if (isEndOfLineMissing(a, aCur))
795 out.write(noNewLine);
796 aCur++;
797 } else if (bCur < curEdit.getEndB()) {
798 writeAddedLine(b, bCur);
799 if (isEndOfLineMissing(b, bCur))
800 out.write(noNewLine);
801 bCur++;
802 }
803
804 if (end(curEdit, aCur, bCur) && ++curIdx < edits.size())
805 curEdit = edits.get(curIdx);
806 }
807 }
808 }
809
810
811
812
813
814
815
816
817
818
819 protected void writeContextLine(RawText text, int line)
820 throws IOException {
821 writeLine(' ', text, line);
822 }
823
824 private static boolean isEndOfLineMissing(RawText text, int line) {
825 return line + 1 == text.size() && text.isMissingNewlineAtEnd();
826 }
827
828
829
830
831
832
833
834
835
836
837 protected void writeAddedLine(RawText text, int line)
838 throws IOException {
839 writeLine('+', text, line);
840 }
841
842
843
844
845
846
847
848
849
850
851 protected void writeRemovedLine(RawText text, int line)
852 throws IOException {
853 writeLine('-', text, line);
854 }
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869 protected void writeHunkHeader(int aStartLine, int aEndLine,
870 int bStartLine, int bEndLine) throws IOException {
871 out.write('@');
872 out.write('@');
873 writeRange('-', aStartLine + 1, aEndLine - aStartLine);
874 writeRange('+', bStartLine + 1, bEndLine - bStartLine);
875 out.write(' ');
876 out.write('@');
877 out.write('@');
878 out.write('\n');
879 }
880
881 private void writeRange(char prefix, int begin, int cnt)
882 throws IOException {
883 out.write(' ');
884 out.write(prefix);
885 switch (cnt) {
886 case 0:
887
888
889
890
891
892 out.write(encodeASCII(begin - 1));
893 out.write(',');
894 out.write('0');
895 break;
896
897 case 1:
898
899
900 out.write(encodeASCII(begin));
901 break;
902
903 default:
904 out.write(encodeASCII(begin));
905 out.write(',');
906 out.write(encodeASCII(cnt));
907 break;
908 }
909 }
910
911
912
913
914
915
916
917
918
919
920
921
922
923 protected void writeLine(final char prefix, final RawText text,
924 final int cur) throws IOException {
925 out.write(prefix);
926 text.writeLine(out, cur);
927 out.write('\n');
928 }
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953 public FileHeader toFileHeader(DiffEntry ent) throws IOException,
954 CorruptObjectException, MissingObjectException {
955 return createFormatResult(ent).header;
956 }
957
958 private static class FormatResult {
959 FileHeader header;
960
961 RawText a;
962
963 RawText b;
964 }
965
966 private FormatResult createFormatResult(DiffEntry ent) throws IOException,
967 CorruptObjectException, MissingObjectException {
968 final FormatResult res = new FormatResult();
969 ByteArrayOutputStream buf = new ByteArrayOutputStream();
970 final EditList editList;
971 final FileHeader.PatchType type;
972
973 formatHeader(buf, ent);
974
975 if (ent.getOldId() == null || ent.getNewId() == null) {
976
977 editList = new EditList();
978 type = PatchType.UNIFIED;
979 res.header = new FileHeader(buf.toByteArray(), editList, type);
980 return res;
981 }
982
983 assertHaveReader();
984
985 RawText aRaw = null;
986 RawText bRaw = null;
987 if (ent.getOldMode() == GITLINK || ent.getNewMode() == GITLINK) {
988 aRaw = new RawText(writeGitLinkText(ent.getOldId()));
989 bRaw = new RawText(writeGitLinkText(ent.getNewId()));
990 } else {
991 try {
992 aRaw = open(OLD, ent);
993 bRaw = open(NEW, ent);
994 } catch (BinaryBlobException e) {
995
996 formatOldNewPaths(buf, ent);
997 buf.write(encodeASCII("Binary files differ\n"));
998 editList = new EditList();
999 type = PatchType.BINARY;
1000 res.header = new FileHeader(buf.toByteArray(), editList, type);
1001 return res;
1002 }
1003 }
1004
1005 res.a = aRaw;
1006 res.b = bRaw;
1007 editList = diff(res.a, res.b);
1008 type = PatchType.UNIFIED;
1009
1010 switch (ent.getChangeType()) {
1011 case RENAME:
1012 case COPY:
1013 if (!editList.isEmpty())
1014 formatOldNewPaths(buf, ent);
1015 break;
1016
1017 default:
1018 formatOldNewPaths(buf, ent);
1019 break;
1020 }
1021
1022
1023 res.header = new FileHeader(buf.toByteArray(), editList, type);
1024 return res;
1025 }
1026
1027 private EditList diff(RawTexthref="../../../../org/eclipse/jgit/diff/RawText.html#RawText">RawText a, RawText b) {
1028 return diffAlgorithm.diff(comparator, a, b);
1029 }
1030
1031 private void assertHaveReader() {
1032 if (reader == null) {
1033 throw new IllegalStateException(JGitText.get().readerIsRequired);
1034 }
1035 }
1036
1037 private RawText open(DiffEntry.Side side, DiffEntry entry)
1038 throws IOException, BinaryBlobException {
1039 if (entry.getMode(side) == FileMode.MISSING)
1040 return RawText.EMPTY_TEXT;
1041
1042 if (entry.getMode(side).getObjectType() != Constants.OBJ_BLOB)
1043 return RawText.EMPTY_TEXT;
1044
1045 AbbreviatedObjectId id = entry.getId(side);
1046 if (!id.isComplete()) {
1047 Collection<ObjectId> ids = reader.resolve(id);
1048 if (ids.size() == 1) {
1049 id = AbbreviatedObjectId.fromObjectId(ids.iterator().next());
1050 switch (side) {
1051 case OLD:
1052 entry.oldId = id;
1053 break;
1054 case NEW:
1055 entry.newId = id;
1056 break;
1057 }
1058 } else if (ids.isEmpty())
1059 throw new MissingObjectException(id, Constants.OBJ_BLOB);
1060 else
1061 throw new AmbiguousObjectException(id, ids);
1062 }
1063
1064 ObjectLoader ldr = LfsFactory.getInstance().applySmudgeFilter(repository,
1065 source.open(side, entry), entry.getDiffAttribute());
1066 return RawText.load(ldr, binaryFileThreshold);
1067 }
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083 protected void formatGitDiffFirstHeaderLine(ByteArrayOutputStream o,
1084 final ChangeType type, final String oldPath, final String newPath)
1085 throws IOException {
1086 o.write(encodeASCII("diff --git "));
1087 o.write(encode(quotePath(oldPrefix + (type == ADD ? newPath : oldPath))));
1088 o.write(' ');
1089 o.write(encode(quotePath(newPrefix
1090 + (type == DELETE ? oldPath : newPath))));
1091 o.write('\n');
1092 }
1093
1094 private void formatHeader(ByteArrayOutputStream o, DiffEntry ent)
1095 throws IOException {
1096 final ChangeType type = ent.getChangeType();
1097 final String oldp = ent.getOldPath();
1098 final String newp = ent.getNewPath();
1099 final FileMode oldMode = ent.getOldMode();
1100 final FileMode newMode = ent.getNewMode();
1101
1102 formatGitDiffFirstHeaderLine(o, type, oldp, newp);
1103
1104 if ((type == MODIFY || type == COPY || type == RENAME)
1105 && !oldMode.equals(newMode)) {
1106 o.write(encodeASCII("old mode "));
1107 oldMode.copyTo(o);
1108 o.write('\n');
1109
1110 o.write(encodeASCII("new mode "));
1111 newMode.copyTo(o);
1112 o.write('\n');
1113 }
1114
1115 switch (type) {
1116 case ADD:
1117 o.write(encodeASCII("new file mode "));
1118 newMode.copyTo(o);
1119 o.write('\n');
1120 break;
1121
1122 case DELETE:
1123 o.write(encodeASCII("deleted file mode "));
1124 oldMode.copyTo(o);
1125 o.write('\n');
1126 break;
1127
1128 case RENAME:
1129 o.write(encodeASCII("similarity index " + ent.getScore() + "%"));
1130 o.write('\n');
1131
1132 o.write(encode("rename from " + quotePath(oldp)));
1133 o.write('\n');
1134
1135 o.write(encode("rename to " + quotePath(newp)));
1136 o.write('\n');
1137 break;
1138
1139 case COPY:
1140 o.write(encodeASCII("similarity index " + ent.getScore() + "%"));
1141 o.write('\n');
1142
1143 o.write(encode("copy from " + quotePath(oldp)));
1144 o.write('\n');
1145
1146 o.write(encode("copy to " + quotePath(newp)));
1147 o.write('\n');
1148 break;
1149
1150 case MODIFY:
1151 if (0 < ent.getScore()) {
1152 o.write(encodeASCII("dissimilarity index "
1153 + (100 - ent.getScore()) + "%"));
1154 o.write('\n');
1155 }
1156 break;
1157 }
1158
1159 if (ent.getOldId() != null && !ent.getOldId().equals(ent.getNewId())) {
1160 formatIndexLine(o, ent);
1161 }
1162 }
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174 protected void formatIndexLine(OutputStream o, DiffEntry ent)
1175 throws IOException {
1176 o.write(encodeASCII("index "
1177 + format(ent.getOldId())
1178 + ".."
1179 + format(ent.getNewId())));
1180 if (ent.getOldMode().equals(ent.getNewMode())) {
1181 o.write(' ');
1182 ent.getNewMode().copyTo(o);
1183 }
1184 o.write('\n');
1185 }
1186
1187 private void formatOldNewPaths(ByteArrayOutputStream o, DiffEntry ent)
1188 throws IOException {
1189 if (ent.oldId.equals(ent.newId))
1190 return;
1191
1192 final String oldp;
1193 final String newp;
1194
1195 switch (ent.getChangeType()) {
1196 case ADD:
1197 oldp = DiffEntry.DEV_NULL;
1198 newp = quotePath(newPrefix + ent.getNewPath());
1199 break;
1200
1201 case DELETE:
1202 oldp = quotePath(oldPrefix + ent.getOldPath());
1203 newp = DiffEntry.DEV_NULL;
1204 break;
1205
1206 default:
1207 oldp = quotePath(oldPrefix + ent.getOldPath());
1208 newp = quotePath(newPrefix + ent.getNewPath());
1209 break;
1210 }
1211
1212 o.write(encode("--- " + oldp + "\n"));
1213 o.write(encode("+++ " + newp + "\n"));
1214 }
1215
1216 private int findCombinedEnd(List<Edit> edits, int i) {
1217 int end = i + 1;
1218 while (end < edits.size()
1219 && (combineA(edits, end) || combineB(edits, end)))
1220 end++;
1221 return end - 1;
1222 }
1223
1224 private boolean combineA(List<Edit> e, int i) {
1225 return e.get(i).getBeginA() - e.get(i - 1).getEndA() <= 2 * context;
1226 }
1227
1228 private boolean combineB(List<Edit> e, int i) {
1229 return e.get(i).getBeginB() - e.get(i - 1).getEndB() <= 2 * context;
1230 }
1231
1232 private static boolean end(Edit edit, int a, int b) {
1233 return edit.getEndA() <= a && edit.getEndB() <= b;
1234 }
1235 }