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