comparison src/org/tmatesoft/hg/repo/HgBlameFacility.java @ 568:8ed4f4f4f0a6

Blame facility refactored, get ready for follow/no-follow support
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 10 Apr 2013 15:45:53 +0200
parents 6fbca6506bb5
children c4fd1037bc6f
comparison
equal deleted inserted replaced
567:88f04c7cfedb 568:8ed4f4f4f0a6
30 import org.tmatesoft.hg.internal.Callback; 30 import org.tmatesoft.hg.internal.Callback;
31 import org.tmatesoft.hg.internal.DiffHelper; 31 import org.tmatesoft.hg.internal.DiffHelper;
32 import org.tmatesoft.hg.internal.Experimental; 32 import org.tmatesoft.hg.internal.Experimental;
33 import org.tmatesoft.hg.internal.IntMap; 33 import org.tmatesoft.hg.internal.IntMap;
34 import org.tmatesoft.hg.internal.IntVector; 34 import org.tmatesoft.hg.internal.IntVector;
35 import org.tmatesoft.hg.internal.Internals;
35 import org.tmatesoft.hg.internal.DiffHelper.LineSequence; 36 import org.tmatesoft.hg.internal.DiffHelper.LineSequence;
36 import org.tmatesoft.hg.internal.DiffHelper.LineSequence.ByteChain; 37 import org.tmatesoft.hg.internal.DiffHelper.LineSequence.ByteChain;
37 import org.tmatesoft.hg.internal.RangeSeq; 38 import org.tmatesoft.hg.internal.RangeSeq;
38 import org.tmatesoft.hg.repo.HgBlameFacility.RevisionDescriptor.Recipient; 39 import org.tmatesoft.hg.repo.HgBlameFacility.RevisionDescriptor.Recipient;
39 import org.tmatesoft.hg.util.Adaptable; 40 import org.tmatesoft.hg.util.Adaptable;
46 * @author Artem Tikhomirov 47 * @author Artem Tikhomirov
47 * @author TMate Software Ltd. 48 * @author TMate Software Ltd.
48 */ 49 */
49 @Experimental(reason="Unstable API") 50 @Experimental(reason="Unstable API")
50 public final class HgBlameFacility { 51 public final class HgBlameFacility {
52 private final HgDataFile df;
53
54 public HgBlameFacility(HgDataFile file) {
55 if (file == null) {
56 throw new IllegalArgumentException();
57 }
58 df = file;
59 }
51 60
52 /** 61 /**
53 * mimic 'hg diff -r clogRevIndex1 -r clogRevIndex2' 62 * mimic 'hg diff -r clogRevIndex1 -r clogRevIndex2'
54 */ 63 */
55 public void diff(HgDataFile df, int clogRevIndex1, int clogRevIndex2, Inspector insp) throws HgCallbackTargetException { 64 public void diff(int clogRevIndex1, int clogRevIndex2, Inspector insp) throws HgCallbackTargetException {
56 int fileRevIndex1 = fileRevIndex(df, clogRevIndex1); 65 int fileRevIndex1 = fileRevIndex(df, clogRevIndex1);
57 int fileRevIndex2 = fileRevIndex(df, clogRevIndex2); 66 int fileRevIndex2 = fileRevIndex(df, clogRevIndex2);
58 FileLinesCache fileInfoCache = new FileLinesCache(df, 5); 67 FileLinesCache fileInfoCache = new FileLinesCache(df, 5);
59 LineSequence c1 = fileInfoCache.lines(fileRevIndex1); 68 LineSequence c1 = fileInfoCache.lines(fileRevIndex1);
60 LineSequence c2 = fileInfoCache.lines(fileRevIndex2); 69 LineSequence c2 = fileInfoCache.lines(fileRevIndex2);
64 pg.findMatchingBlocks(bbi); 73 pg.findMatchingBlocks(bbi);
65 bbi.checkErrors(); 74 bbi.checkErrors();
66 } 75 }
67 76
68 /** 77 /**
69 * Walk file history up to revision at given changeset and report changes for each revision 78 * Walk file history up/down to revision at given changeset and report changes for each revision
70 */ 79 */
71 public void annotate(HgDataFile df, int changelogRevisionIndex, Inspector insp, HgIterateDirection iterateOrder) throws HgCallbackTargetException { 80 public void annotate(int changelogRevisionIndex, Inspector insp, HgIterateDirection iterateOrder) throws HgCallbackTargetException {
72 if (!df.exists()) { 81 if (!df.exists()) {
73 return; 82 return;
74 } 83 }
75 // Note, changelogRevisionIndex may be TIP, while #implAnnotateChange doesn't tolerate constants 84 // Note, changelogRevisionIndex may be TIP, while #implAnnotateChange doesn't tolerate constants
76 // 85 //
77 // XXX df.indexWalk(0, fileRevIndex, ) might be more effective 86 FileRevisionHistoryChunk fileHistory = new FileRevisionHistoryChunk(df);
78 int fileRevIndex = fileRevIndex(df, changelogRevisionIndex); 87 fileHistory.init(changelogRevisionIndex);
88 // fileHistory.linkTo(null); FIXME
89
79 int[] fileRevParents = new int[2]; 90 int[] fileRevParents = new int[2];
80 IntVector fileParentRevs = new IntVector((fileRevIndex+1) * 2, 0);
81 fileParentRevs.add(NO_REVISION, NO_REVISION);
82 for (int i = 1; i <= fileRevIndex; i++) {
83 df.parents(i, fileRevParents, null, null);
84 fileParentRevs.add(fileRevParents[0], fileRevParents[1]);
85 }
86 // collect file revisions to visit, from newest to oldest:
87 // traverse parents, starting from the given file revision
88 // this ignores all file revision made in parallel to the one of interest
89 IntVector fileRevsToVisit = new IntVector(fileRevIndex + 1, 0);
90 LinkedList<Integer> queue = new LinkedList<Integer>();
91 BitSet seen = new BitSet(fileRevIndex + 1);
92 queue.add(fileRevIndex);
93 do {
94 int x = queue.removeFirst();
95 if (seen.get(x)) {
96 continue;
97 }
98 seen.set(x);
99 fileRevsToVisit.add(x);
100 int p1 = fileParentRevs.get(2*x);
101 int p2 = fileParentRevs.get(2*x + 1);
102 if (p1 != NO_REVISION) {
103 queue.addLast(p1);
104 }
105 if (p2 != NO_REVISION) {
106 queue.addLast(p2);
107 }
108 } while (!queue.isEmpty());
109 FileLinesCache fileInfoCache = new FileLinesCache(df, 10); 91 FileLinesCache fileInfoCache = new FileLinesCache(df, 10);
110 // make sure no child is processed before we handled all (grand-)parents of the element 92 for (int fri : fileHistory.fileRevisions(iterateOrder)) {
111 fileRevsToVisit.sort(false);
112 // fileRevsToVisit now { r10, r7, r6, r5, r0 }
113 // and we'll iterate it from behind, e.g. old to new unless reversed
114 if (iterateOrder == HgIterateDirection.NewToOld) {
115 fileRevsToVisit.reverse();
116 }
117 for (int i = fileRevsToVisit.size() - 1; i >= 0; i--) {
118 int fri = fileRevsToVisit.get(i);
119 int clogRevIndex = df.getChangesetRevisionIndex(fri); 93 int clogRevIndex = df.getChangesetRevisionIndex(fri);
120 fileRevParents[0] = fileParentRevs.get(fri * 2); 94 fileHistory.getParents(fri, fileRevParents);
121 fileRevParents[1] = fileParentRevs.get(fri * 2 + 1);
122 implAnnotateChange(fileInfoCache, clogRevIndex, fri, fileRevParents, insp); 95 implAnnotateChange(fileInfoCache, clogRevIndex, fri, fileRevParents, insp);
123 } 96 }
124 } 97 }
125 98
126 /** 99 /**
127 * Annotates changes of the file against its parent(s). 100 * Annotates changes of the file against its parent(s).
128 * Unlike {@link #annotate(HgDataFile, int, Inspector, HgIterateDirection)}, doesn't 101 * Unlike {@link #annotate(HgDataFile, int, Inspector, HgIterateDirection)}, doesn't
129 * walk file history, looks at the specified revision only. Handles both parents (if merge revision). 102 * walk file history, looks at the specified revision only. Handles both parents (if merge revision).
130 */ 103 */
131 public void annotateSingleRevision(HgDataFile df, int changelogRevisionIndex, Inspector insp) throws HgCallbackTargetException { 104 public void annotateSingleRevision(int changelogRevisionIndex, Inspector insp) throws HgCallbackTargetException {
132 // TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f 105 // TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f
133 int fileRevIndex = fileRevIndex(df, changelogRevisionIndex); 106 int fileRevIndex = fileRevIndex(df, changelogRevisionIndex);
134 int[] fileRevParents = new int[2]; 107 int[] fileRevParents = new int[2];
135 df.parents(fileRevIndex, fileRevParents, null, null); 108 df.parents(fileRevIndex, fileRevParents, null, null);
136 if (changelogRevisionIndex == TIP) { 109 if (changelogRevisionIndex == TIP) {
180 } 153 }
181 154
182 private static int fileRevIndex(HgDataFile df, int csetRevIndex) { 155 private static int fileRevIndex(HgDataFile df, int csetRevIndex) {
183 Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath()); 156 Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath());
184 return df.getRevisionIndex(fileRev); 157 return df.getRevisionIndex(fileRev);
158 }
159
160 private static class FileRevisionHistoryChunk {
161 private final HgDataFile df;
162 private IntVector fileRevsToVisit;
163 private IntVector fileParentRevs;
164
165 public FileRevisionHistoryChunk(HgDataFile file) {
166 df = file;
167 }
168
169 public void getParents(int fileRevIndex, int[] fileRevParents) {
170 fileRevParents[0] = fileParentRevs.get(fileRevIndex * 2);
171 fileRevParents[1] = fileParentRevs.get(fileRevIndex * 2 + 1);
172 }
173
174 public void init (int changelogRevisionIndex) {
175 // XXX df.indexWalk(0, fileRevIndex, ) might be more effective
176 int fileRevIndex = fileRevIndex(df, changelogRevisionIndex);
177 int[] fileRevParents = new int[2];
178 fileParentRevs = new IntVector((fileRevIndex+1) * 2, 0);
179 fileParentRevs.add(NO_REVISION, NO_REVISION); // parents of fileRevIndex == 0
180 for (int i = 1; i <= fileRevIndex; i++) {
181 df.parents(i, fileRevParents, null, null);
182 fileParentRevs.add(fileRevParents[0], fileRevParents[1]);
183 }
184 fileRevsToVisit = new IntVector(fileRevIndex + 1, 0);
185 LinkedList<Integer> queue = new LinkedList<Integer>();
186 BitSet seen = new BitSet(fileRevIndex + 1);
187 queue.add(fileRevIndex);
188 do {
189 int x = queue.removeFirst();
190 if (seen.get(x)) {
191 continue;
192 }
193 seen.set(x);
194 fileRevsToVisit.add(x);
195 int p1 = fileParentRevs.get(2*x);
196 int p2 = fileParentRevs.get(2*x + 1);
197 if (p1 != NO_REVISION) {
198 queue.addLast(p1);
199 }
200 if (p2 != NO_REVISION) {
201 queue.addLast(p2);
202 }
203 } while (!queue.isEmpty());
204 // make sure no child is processed before we handled all (grand-)parents of the element
205 fileRevsToVisit.sort(false);
206 // now fileRevsToVisit keep file change ancestry from new to old
207 }
208
209 public void linkTo(FileRevisionHistoryChunk origin) {
210 Internals.notImplemented();
211 }
212
213 public int[] fileRevisions(HgIterateDirection iterateOrder) {
214 // fileRevsToVisit is { r10, r7, r6, r5, r0 }, new to old
215 int[] rv = fileRevsToVisit.toArray();
216 if (iterateOrder == HgIterateDirection.OldToNew) {
217 // reverse return value
218 for (int a = 0, b = rv.length-1; a < b; a++, b--) {
219 int t = rv[b];
220 rv[b] = rv[a];
221 rv[a] = t;
222 }
223 }
224 return rv;
225 }
185 } 226 }
186 227
187 private static class FileLinesCache { 228 private static class FileLinesCache {
188 private final HgDataFile df; 229 private final HgDataFile df;
189 private final LinkedList<Pair<Integer, LineSequence>> lruCache; 230 private final LinkedList<Pair<Integer, LineSequence>> lruCache;