Mercurial > jhg
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; |