comparison src/org/tmatesoft/hg/internal/BlameHelper.java @ 680:58a6900f845d

Blame: alternative strategy to handle merge revisions: map(diff(p1->base->p2)) to understand merge intentions better
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Sun, 21 Jul 2013 17:15:34 +0200
parents 8625cba0a5a8
children
comparison
equal deleted inserted replaced
679:19f5167c2155 680:58a6900f845d
17 package org.tmatesoft.hg.internal; 17 package org.tmatesoft.hg.internal;
18 18
19 import static org.tmatesoft.hg.core.HgIterateDirection.OldToNew; 19 import static org.tmatesoft.hg.core.HgIterateDirection.OldToNew;
20 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; 20 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
21 21
22 import java.util.ArrayList;
23 import java.util.Arrays;
22 import java.util.Iterator; 24 import java.util.Iterator;
23 import java.util.LinkedList; 25 import java.util.LinkedList;
26 import java.util.List;
24 import java.util.ListIterator; 27 import java.util.ListIterator;
25 28
26 import org.tmatesoft.hg.core.HgCallbackTargetException; 29 import org.tmatesoft.hg.core.HgCallbackTargetException;
30 import org.tmatesoft.hg.core.Nodeid;
27 import org.tmatesoft.hg.internal.DiffHelper.LineSequence; 31 import org.tmatesoft.hg.internal.DiffHelper.LineSequence;
28 import org.tmatesoft.hg.internal.DiffHelper.LineSequence.ByteChain; 32 import org.tmatesoft.hg.internal.DiffHelper.LineSequence.ByteChain;
33 import org.tmatesoft.hg.internal.diff.DiffRangeMap;
34 import org.tmatesoft.hg.internal.diff.DiffRangeMap.RangePair;
29 import org.tmatesoft.hg.core.HgBlameInspector; 35 import org.tmatesoft.hg.core.HgBlameInspector;
30 import org.tmatesoft.hg.core.HgBlameInspector.*; 36 import org.tmatesoft.hg.core.HgBlameInspector.*;
37 import org.tmatesoft.hg.repo.HgChangelog;
31 import org.tmatesoft.hg.repo.HgDataFile; 38 import org.tmatesoft.hg.repo.HgDataFile;
32 import org.tmatesoft.hg.repo.HgInvalidStateException; 39 import org.tmatesoft.hg.repo.HgInvalidStateException;
40 import org.tmatesoft.hg.repo.HgParentChildMap;
41 import org.tmatesoft.hg.repo.HgRepository;
42 import org.tmatesoft.hg.repo.HgRevisionMap;
33 import org.tmatesoft.hg.repo.HgRuntimeException; 43 import org.tmatesoft.hg.repo.HgRuntimeException;
34 import org.tmatesoft.hg.util.Adaptable; 44 import org.tmatesoft.hg.util.Adaptable;
35 import org.tmatesoft.hg.util.CancelledException; 45 import org.tmatesoft.hg.util.CancelledException;
36 import org.tmatesoft.hg.util.Pair; 46 import org.tmatesoft.hg.util.Pair;
37 47
43 */ 53 */
44 public class BlameHelper { 54 public class BlameHelper {
45 55
46 private final HgBlameInspector insp; 56 private final HgBlameInspector insp;
47 private FileLinesCache linesCache; 57 private FileLinesCache linesCache;
58 private HgParentChildMap<HgChangelog> clogMap;
48 59
49 public BlameHelper(HgBlameInspector inspector) { 60 public BlameHelper(HgBlameInspector inspector) {
50 insp = inspector; 61 insp = inspector;
51 } 62 }
52 63
98 if (fileParentClogRevs[0] != NO_REVISION && fileParentClogRevs[1] != NO_REVISION) { 109 if (fileParentClogRevs[0] != NO_REVISION && fileParentClogRevs[1] != NO_REVISION) {
99 int p1ClogIndex = fileParentClogRevs[0]; 110 int p1ClogIndex = fileParentClogRevs[0];
100 int p2ClogIndex = fileParentClogRevs[1]; 111 int p2ClogIndex = fileParentClogRevs[1];
101 LineSequence p1Lines = linesCache.lines(p1ClogIndex, fileParentRevs[0]); 112 LineSequence p1Lines = linesCache.lines(p1ClogIndex, fileParentRevs[0]);
102 LineSequence p2Lines = linesCache.lines(p2ClogIndex, fileParentRevs[1]); 113 LineSequence p2Lines = linesCache.lines(p2ClogIndex, fileParentRevs[1]);
114 MergeResolutionStrategy mergeResolver = createMergeStrategy(fileRevLines, p1Lines, p2Lines, csetRevIndex, fileParentClogRevs);
115 //
103 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>(); 116 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>();
104 pg.init(p2Lines, fileRevLines); 117 pg.init(p1Lines, fileRevLines);
105 EqualBlocksCollector p2MergeCommon = new EqualBlocksCollector();
106 pg.findMatchingBlocks(p2MergeCommon);
107 //
108 pg.init(p1Lines);
109 BlameBlockInspector bbi = new BlameBlockInspector(targetFile, fileRevIndex, insp, p1ClogIndex, csetRevIndex); 118 BlameBlockInspector bbi = new BlameBlockInspector(targetFile, fileRevIndex, insp, p1ClogIndex, csetRevIndex);
110 bbi.setMergeParent2(new MergeStrategy1(p2MergeCommon.matches), p2ClogIndex); 119 bbi.setMergeParent2(mergeResolver, p2ClogIndex);
111 pg.findMatchingBlocks(bbi); 120 pg.findMatchingBlocks(bbi);
112 bbi.checkErrors(); 121 bbi.checkErrors();
113 } else if (fileParentClogRevs[0] == fileParentClogRevs[1]) { 122 } else if (fileParentClogRevs[0] == fileParentClogRevs[1]) {
114 // may be equal iff both are unset 123 // may be equal iff both are unset
115 assert fileParentClogRevs[0] == NO_REVISION; 124 assert fileParentClogRevs[0] == NO_REVISION;
130 pg.findMatchingBlocks(bbi); 139 pg.findMatchingBlocks(bbi);
131 bbi.checkErrors(); 140 bbi.checkErrors();
132 } 141 }
133 } 142 }
134 143
144 private static final boolean useNewStrategy = Boolean.TRUE.booleanValue();
145
146 private MergeResolutionStrategy createMergeStrategy(LineSequence fileRevLines, LineSequence p1Lines, LineSequence p2Lines, int csetRevIndex, int[] fileParentClogRevs) {
147 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>();
148 if (useNewStrategy) {
149 final ArrayList<RangePairSeq> allMatches = new ArrayList<RangePairSeq>();
150 pg.init(p2Lines, fileRevLines);
151 pg.findAllMatchAlternatives(new DiffHelper.MatchInspector<LineSequence>() {
152 private RangePairSeq matches;
153
154 public void begin(LineSequence s1, LineSequence s2) {
155 matches = new RangePairSeq();
156 }
157
158 public void match(int startSeq1, int startSeq2, int matchLength) {
159 matches.add(startSeq1, startSeq2, matchLength);
160 }
161
162 public void end() {
163 if (matches.size() > 0) {
164 allMatches.add(matches);
165 }
166 }
167
168 });
169 //
170 LineSequence baseLines = getBaseRevisionLines(csetRevIndex, fileParentClogRevs);
171 pg.init(p1Lines, baseLines);
172 DiffRangeMap p1ToBase = new DiffRangeMap().fill(pg);
173 pg.init(baseLines, p2Lines);
174 DiffRangeMap baseToP2 = new DiffRangeMap().fill(pg);
175 return new MergeStrategy2(allMatches, p1ToBase, baseToP2);
176 } else {
177 pg.init(p2Lines, fileRevLines);
178 EqualBlocksCollector p2MergeCommon = new EqualBlocksCollector();
179 pg.findMatchingBlocks(p2MergeCommon);
180 return new MergeStrategy1(p2MergeCommon.matches);
181 }
182 }
183
184 private LineSequence getBaseRevisionLines(int clogRevIndex, int[] fileParentClogRevs) {
185 assert fileParentClogRevs[0] >= 0;
186 assert fileParentClogRevs[1] >= 0;
187 HgDataFile targetFile = linesCache.getFile(clogRevIndex);
188 final HgRepository repo = targetFile.getRepo();
189 if (clogMap == null) {
190 // FIXME replace HgParentChildMap with revlog.indexWalk(AncestorIterator))
191 clogMap = new HgParentChildMap<HgChangelog>(repo.getChangelog());
192 clogMap.init();
193 }
194 final HgRevisionMap<HgChangelog> m = clogMap.getRevisionMap();
195 Nodeid ancestor = clogMap.ancestor(m.revision(fileParentClogRevs[0]), m.revision(fileParentClogRevs[1]));
196 final int ancestorRevIndex = m.revisionIndex(ancestor);
197 Nodeid fr = repo.getManifest().getFileRevision(ancestorRevIndex, targetFile.getPath());
198 if (fr == null) {
199 return LineSequence.newlines(new byte[0]);
200 }
201 return linesCache.lines(ancestorRevIndex, targetFile.getRevisionIndex(fr));
202 }
203
135 private static class FileLinesCache { 204 private static class FileLinesCache {
136 private final LinkedList<Pair<Integer, LineSequence>> lruCache; 205 private final LinkedList<Pair<Integer, LineSequence>> lruCache;
137 private final int limit; 206 private final int limit;
138 private final LinkedList<Pair<Integer, HgDataFile>> files; // TODO in fact, need sparse array 207 private final LinkedList<Pair<Integer, HgDataFile>> files; // TODO in fact, need sparse array
139 208
279 if (shallStop()) { 348 if (shallStop()) {
280 return; 349 return;
281 } 350 }
282 try { 351 try {
283 if (p2MergeCommon != null) { 352 if (p2MergeCommon != null) {
284 IntSliceSeq mergeRanges = p2MergeCommon.combineAndMarkRangesWithSource(s2From, s2To - s2From, csetOrigin, csetMergeParent); 353 IntSliceSeq mergeRanges = p2MergeCommon.combineAndMarkRangesWithSource(s1From, s1To, s2From, s2To, csetOrigin, csetMergeParent);
285 354
286 /* 355 /*
287 * Usecases, how it USED TO BE initially: 356 * Usecases, how it USED TO BE initially:
288 * 3 lines changed to 10 lines. range of 10 lines breaks down to 2 from p2, 3 from p1, and 5 from p2. 357 * 3 lines changed to 10 lines. range of 10 lines breaks down to 2 from p2, 3 from p1, and 5 from p2.
289 * We report: 2 lines changed to 2(p2), then 1 line changed with 3(p1) and 5 lines added from p2. 358 * We report: 2 lines changed to 2(p2), then 1 line changed with 3(p1) and 5 lines added from p2.
343 if (shallStop()) { 412 if (shallStop()) {
344 return; 413 return;
345 } 414 }
346 try { 415 try {
347 if (p2MergeCommon != null) { 416 if (p2MergeCommon != null) {
348 IntSliceSeq mergeRanges = p2MergeCommon.combineAndMarkRangesWithSource(s2From, s2To - s2From, csetOrigin, csetMergeParent); 417 IntSliceSeq mergeRanges = p2MergeCommon.combineAndMarkRangesWithSource(s1InsertPoint, s2From, s2To, csetOrigin, csetMergeParent);
349 int insPoint = s1InsertPoint; // track changes to insertion point 418 int insPoint = s1InsertPoint; // track changes to insertion point
350 for (IntTuple mergeRange : mergeRanges) { 419 for (IntTuple mergeRange : mergeRanges) {
351 int rangeOrigin = mergeRange.at(0); 420 int rangeOrigin = mergeRange.at(0);
352 int rangeStart = mergeRange.at(1); 421 int rangeStart = mergeRange.at(1);
353 int rangeLen = mergeRange.at(2); 422 int rangeLen = mergeRange.at(2);
615 684
616 public byte[] asArray() { 685 public byte[] asArray() {
617 return contentBlock.seq.data(from, from + length); 686 return contentBlock.seq.data(from, from + length);
618 } 687 }
619 } 688 }
620 689
621
622 interface MergeResolutionStrategy {
623 /**
624 * breaks region [start..start+length) into ranges according to deduced (or simply guessed)
625 * matching of these lines to lines in source1 and source2
626 * @return list of tuples (source, start, length), where source is one of the identifiers supplied
627 */
628 public IntSliceSeq combineAndMarkRangesWithSource(int start, int length, int source1, int source2);
629 public int getLineInP2(int mergeLine);
630 }
631
632 private static class EqualBlocksCollector implements DiffHelper.MatchInspector<LineSequence> { 690 private static class EqualBlocksCollector implements DiffHelper.MatchInspector<LineSequence> {
633 private final RangePairSeq matches = new RangePairSeq(); 691 private final RangePairSeq matches = new RangePairSeq();
634 692
635 public void begin(LineSequence s1, LineSequence s2) { 693 public void begin(LineSequence s1, LineSequence s2) {
636 } 694 }
658 result.add((start + length) - s); 716 result.add((start + length) - s);
659 } 717 }
660 } 718 }
661 719
662 } 720 }
663 721
722 interface MergeResolutionStrategy {
723 /**
724 * breaks region [start2..end2) into ranges according to deduced (or simply guessed)
725 * matching of [start1..end1) lines to lines in source1 and source2
726 * @return list of tuples (source, start, length), where source is one of the identifiers supplied
727 */
728 public IntSliceSeq combineAndMarkRangesWithSource(int start1, int end1, int start2, int end2, int source1, int source2);
729 public IntSliceSeq combineAndMarkRangesWithSource(int insPoint, int start, int end, int source1, int source2);
730 public int getLineInP2(int mergeLine);
731 }
732
733 // report lines as merged from p2 solely based on whether target line belongs
734 // to a region that is equal to p2 region
664 private static class MergeStrategy1 implements MergeResolutionStrategy { 735 private static class MergeStrategy1 implements MergeResolutionStrategy {
665 // equal ranges in p2 and merged revision 736 // equal ranges in p2 and merged revision
666 private final RangePairSeq matches; 737 private final RangePairSeq matches;
667 private final IntSliceSeq mergeRanges; 738 private final IntSliceSeq mergeRanges;
668 739
675 * intersects [start..start+length) with ranges of target lines, and based on the intersection 746 * intersects [start..start+length) with ranges of target lines, and based on the intersection
676 * breaks initial range into smaller ranges and records them into result, with marker to indicate 747 * breaks initial range into smaller ranges and records them into result, with marker to indicate
677 * whether the range is from initial range (markerSource) or is a result of the intersection with target 748 * whether the range is from initial range (markerSource) or is a result of the intersection with target
678 * (markerTarget) 749 * (markerTarget)
679 */ 750 */
680 public IntSliceSeq combineAndMarkRangesWithSource(int start, int length, int markerSource, int markerTarget) { 751 private IntSliceSeq doCombine(int start, int length, int markerSource, int markerTarget) {
681 mergeRanges.clear(); 752 mergeRanges.clear();
682 assert mergeRanges.sliceSize() == 3; 753 assert mergeRanges.sliceSize() == 3;
683 int sourceStart = start, targetStart = start, sourceEnd = start + length; 754 int sourceStart = start, targetStart = start, sourceEnd = start + length;
684 for (int l = sourceStart; l < sourceEnd; l++) { 755 for (int l = sourceStart; l < sourceEnd; l++) {
685 if (matches.includesTargetLine(l)) { 756 if (matches.includesTargetLine(l)) {
714 } 785 }
715 786
716 public int getLineInP2(int mergeLine) { 787 public int getLineInP2(int mergeLine) {
717 return matches.reverseMapLine(mergeLine); 788 return matches.reverseMapLine(mergeLine);
718 } 789 }
719 } 790
791 public IntSliceSeq combineAndMarkRangesWithSource(int start1, int end1, int start2, int end2, int source1, int source2) {
792 return doCombine(start2, end2 - start2, source1, source2);
793 }
794
795 public IntSliceSeq combineAndMarkRangesWithSource(int insPoint, int start, int end, int source1, int source2) {
796 return doCombine(start, end - start, source1, source2);
797 }
798 }
799
800 private static class MergeStrategy2 implements MergeResolutionStrategy {
801 // equal ranges in p2 and merged revision
802 private final List<RangePairSeq> matches;
803 private final IntSliceSeq mergeRanges;
804 private final DiffRangeMap p1ToBase;
805 private final DiffRangeMap baseToP2;
806
807 public MergeStrategy2(List<RangePairSeq> p2EqualToM, DiffRangeMap p1ToBaseRanges, DiffRangeMap baseToP2Ranges) {
808 matches = p2EqualToM;
809 p1ToBase = p1ToBaseRanges;
810 baseToP2= baseToP2Ranges;
811 mergeRanges = new IntSliceSeq(3, 10, 10);
812 }
813
814
815 public IntSliceSeq combineAndMarkRangesWithSource(int insPoint, int start, int end, int source1, int source2) {
816 return combineAndMarkRangesWithSource(insPoint, insPoint, start, end, source1, source2);
817 }
818
819 public IntSliceSeq combineAndMarkRangesWithSource(int start1, int end1, int start2, int end2, int source1, int source2) {
820 mergeRanges.clear();
821 IntSliceSeq mergedLines = new IntSliceSeq(2, end2-start2, 0);
822 for (int i = start2; i < end2; i++) {
823 mergedLines.add(source1, 0);
824 }
825 // [s1Start..s1End) // range in p1 seen as changed in m
826 for (RangePair p1_b : p1ToBase.findInSource(start1, end1)) {
827 // there might be few ranges in (p1-base) that overlap with (p1-m) changes
828 for (RangePair b_p2 : baseToP2.findInSource(p1_b.start2(), p1_b.end2())) {
829 // regions in p2 that correspond to affected regions in base
830 for (int p2Line = b_p2.start2(); p2Line < b_p2.end2(); p2Line++) {
831 for (RangePairSeq eq : matches) {
832 if (eq.includesOriginLine(p2Line)) {
833 // this line in p2 is equal to some line in merge
834 int mergeLine = eq.mapLineIndex(p2Line);
835 if (mergeLine >= start2 && mergeLine < end2) {
836 mergedLines.set(mergeLine - start2, source2, p2Line);
837 }
838 }
839 }
840 }
841 }
842 }
843 int lineCount = 0, start = start2;
844 int lastSeenSource = source1;
845 for (IntTuple t : mergedLines) {
846 if (t.at(0) == lastSeenSource) {
847 lineCount++;
848 } else {
849 if (lineCount > 0) {
850 mergeRanges.add(lastSeenSource, start, lineCount);
851 start += lineCount;
852 }
853 lineCount = 1;
854 lastSeenSource = t.at(0);
855 }
856 }
857 if (lineCount > 0) {
858 mergeRanges.add(lastSeenSource, start, lineCount);
859 }
860 return mergeRanges;
861 }
862
863 public int getLineInP2(int mergeLine) {
864 for (RangePairSeq eq : matches) {
865 if (eq.includesTargetLine(mergeLine)) {
866 return eq.reverseMapLine(mergeLine);
867 }
868 }
869 return -1;
870 }
871 }
872
720 873
721 private static class AnnotateRev implements RevisionDescriptor { 874 private static class AnnotateRev implements RevisionDescriptor {
722 public ContentBlock origin, target; 875 public ContentBlock origin, target;
723 public int originCset, targetCset, mergeCset, fileRevIndex; 876 public int originCset, targetCset, mergeCset, fileRevIndex;
724 public HgDataFile df; 877 public HgDataFile df;
787 for (int i = 0; i < r.size(); i+=2) { 940 for (int i = 0; i < r.size(); i+=2) {
788 System.out.printf("[%d..%d) ", r.get(i), r.get(i) + r.get(i+1)); 941 System.out.printf("[%d..%d) ", r.get(i), r.get(i) + r.get(i+1));
789 } 942 }
790 System.out.println(); 943 System.out.println();
791 MergeStrategy1 ms = new MergeStrategy1(bc.matches); 944 MergeStrategy1 ms = new MergeStrategy1(bc.matches);
792 IntSliceSeq mr = ms.combineAndMarkRangesWithSource(0, 16, 508, 514); 945 IntSliceSeq mr = ms.doCombine(0, 16, 508, 514);
793 for (IntTuple t : mr) { 946 for (IntTuple t : mr) {
794 System.out.printf("%d:[%d..%d) ", t.at(0), t.at(1), t.at(1) + t.at(2)); 947 System.out.printf("%d:[%d..%d) ", t.at(0), t.at(1), t.at(1) + t.at(2));
795 } 948 }
949 System.out.println();
950 System.out.println();
951 DiffRangeMap m1 = new DiffRangeMap(); // p1 -> base
952 m1.match(0, 0, 1); // =1..1 -> 1..1
953 m1.match(7, 3, 0); // *2..7 -> 2..3
954 DiffRangeMap m2 = new DiffRangeMap(); // base -> p2
955 m2.match(0, 0, 1); // =1..1 -> 1..1
956 m2.match(3, 3, 0); // *2..3 -> 2..3
957 RangePairSeq eq1 = new RangePairSeq();
958 eq1.add(0, 0, 3);
959 RangePairSeq eq2 = new RangePairSeq();
960 eq2.add(0, 4, 3);
961 MergeStrategy2 ms2 = new MergeStrategy2(Arrays.asList(eq1, eq2), m1, m2);
962 mr = ms2.combineAndMarkRangesWithSource(5, 7, 5, 7, 33, 44);
963 for (IntTuple t : mr) {
964 System.out.printf("%d:[%d..%d) ", t.at(0), t.at(1), t.at(1) + t.at(2));
965 }
796 } 966 }
797 } 967 }