Mercurial > hg4j
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 } |