comparison src/org/tmatesoft/hg/internal/AnnotateFacility.java @ 552:45751456b471

Annotate file changes through few revisions, walking either direction (old to new and vice versa)
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 20 Feb 2013 22:23:50 +0100
parents 4ea0351ca878
children 093a2022dad5
comparison
equal deleted inserted replaced
551:4ea0351ca878 552:45751456b471
17 package org.tmatesoft.hg.internal; 17 package org.tmatesoft.hg.internal;
18 18
19 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; 19 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
20 import static org.tmatesoft.hg.repo.HgRepository.TIP; 20 import static org.tmatesoft.hg.repo.HgRepository.TIP;
21 21
22 import java.util.BitSet;
23 import java.util.HashSet;
24 import java.util.LinkedHashMap;
25 import java.util.LinkedList;
26 import java.util.ListIterator;
27
28 import org.tmatesoft.hg.core.HgIterateDirection;
22 import org.tmatesoft.hg.core.Nodeid; 29 import org.tmatesoft.hg.core.Nodeid;
23 import org.tmatesoft.hg.internal.DiffHelper.LineSequence; 30 import org.tmatesoft.hg.internal.DiffHelper.LineSequence;
24 import org.tmatesoft.hg.repo.HgDataFile; 31 import org.tmatesoft.hg.repo.HgDataFile;
25 import org.tmatesoft.hg.repo.HgInvalidStateException; 32 import org.tmatesoft.hg.repo.HgInvalidStateException;
26 import org.tmatesoft.hg.util.CancelledException; 33 import org.tmatesoft.hg.util.CancelledException;
34 import org.tmatesoft.hg.util.Pair;
27 35
28 /** 36 /**
29 * 37 *
30 * @author Artem Tikhomirov 38 * @author Artem Tikhomirov
31 * @author TMate Software Ltd. 39 * @author TMate Software Ltd.
32 */ 40 */
33 @Experimental(reason="work in progress") 41 @Experimental(reason="work in progress")
34 public class AnnotateFacility { 42 public class AnnotateFacility {
35 43
36 /** 44 /**
37 * mimic 'hg diff -r csetRevIndex1 -r csetRevIndex2' 45 * mimic 'hg diff -r clogRevIndex1 -r clogRevIndex2'
38 */ 46 */
39 public void diff(HgDataFile df, int csetRevIndex1, int csetRevIndex2, BlockInspector insp) { 47 public void diff(HgDataFile df, int clogRevIndex1, int clogRevIndex2, BlockInspector insp) {
40 int fileRevIndex1 = fileRevIndex(df, csetRevIndex1); 48 int fileRevIndex1 = fileRevIndex(df, clogRevIndex1);
41 int fileRevIndex2 = fileRevIndex(df, csetRevIndex2); 49 int fileRevIndex2 = fileRevIndex(df, clogRevIndex2);
42 LineSequence c1 = lines(df, fileRevIndex1); 50 FileLinesCache fileInfoCache = new FileLinesCache(df, 5);
43 LineSequence c2 = lines(df, fileRevIndex2); 51 LineSequence c1 = fileInfoCache.lines(fileRevIndex1);
52 LineSequence c2 = fileInfoCache.lines(fileRevIndex2);
44 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>(); 53 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>();
45 pg.init(c1, c2); 54 pg.init(c1, c2);
46 pg.findMatchingBlocks(new BlameBlockInspector(insp, csetRevIndex1, csetRevIndex2)); 55 pg.findMatchingBlocks(new BlameBlockInspector(insp, clogRevIndex1, clogRevIndex2));
56 }
57
58 public void annotate(HgDataFile df, int changelogRevisionIndex, BlockInspector insp, HgIterateDirection iterateOrder) {
59 if (!df.exists()) {
60 return;
61 }
62 // Note, changelogRevisionIndex may be TIP, while #implAnnotateChange doesn't tolerate constants
63 //
64 // XXX df.indexWalk(0, fileRevIndex, ) might be more effective
65 int fileRevIndex = fileRevIndex(df, changelogRevisionIndex);
66 int[] fileRevParents = new int[2];
67 IntVector fileParentRevs = new IntVector((fileRevIndex+1) * 2, 0);
68 fileParentRevs.add(NO_REVISION, NO_REVISION);
69 for (int i = 1; i <= fileRevIndex; i++) {
70 df.parents(i, fileRevParents, null, null);
71 fileParentRevs.add(fileRevParents[0], fileRevParents[1]);
72 }
73 // collect file revisions to visit, from newest to oldest
74 IntVector fileRevsToVisit = new IntVector(fileRevIndex + 1, 0);
75 LinkedList<Integer> queue = new LinkedList<Integer>();
76 BitSet seen = new BitSet(fileRevIndex + 1);
77 queue.add(fileRevIndex);
78 do {
79 int x = queue.removeFirst();
80 if (seen.get(x)) {
81 continue;
82 }
83 seen.set(x);
84 fileRevsToVisit.add(x);
85 int p1 = fileParentRevs.get(2*x);
86 int p2 = fileParentRevs.get(2*x + 1);
87 if (p1 != NO_REVISION) {
88 queue.addLast(p1);
89 }
90 if (p2 != NO_REVISION) {
91 queue.addLast(p2);
92 }
93 } while (!queue.isEmpty());
94 FileLinesCache fileInfoCache = new FileLinesCache(df, 10);
95 // fileRevsToVisit now { r10, r7, r6, r5, r0 }
96 // and we'll iterate it from behind, e.g. old to new unless reversed
97 if (iterateOrder == HgIterateDirection.NewToOld) {
98 fileRevsToVisit.reverse();
99 }
100 for (int i = fileRevsToVisit.size() - 1; i >= 0; i--) {
101 int fri = fileRevsToVisit.get(i);
102 int clogRevIndex = df.getChangesetRevisionIndex(fri);
103 fileRevParents[0] = fileParentRevs.get(fri * 2);
104 fileRevParents[1] = fileParentRevs.get(fri * 2 + 1);
105 implAnnotateChange(fileInfoCache, clogRevIndex, fri, fileRevParents, insp);
106 }
47 } 107 }
48 108
49 /** 109 /**
50 * Annotate file revision, line by line. 110 * Annotate file revision, line by line.
51 */ 111 */
52 public void annotate(HgDataFile df, int changesetRevisionIndex, LineInspector insp) { 112 public void annotate(HgDataFile df, int changelogRevisionIndex, LineInspector insp) {
53 if (!df.exists()) { 113 if (!df.exists()) {
54 return; 114 return;
55 } 115 }
56 int fileRevIndex = fileRevIndex(df, changesetRevisionIndex);
57 int[] fileRevParents = new int[2];
58 FileAnnotation fa = new FileAnnotation(insp); 116 FileAnnotation fa = new FileAnnotation(insp);
59 do { 117 annotate(df, changelogRevisionIndex, fa, HgIterateDirection.NewToOld);
60 // also covers changesetRevisionIndex == TIP, #implAnnotateChange doesn't tolerate constants
61 changesetRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex);
62 df.parents(fileRevIndex, fileRevParents, null, null);
63 implAnnotateChange(df, changesetRevisionIndex, fileRevIndex, fileRevParents, fa);
64 fileRevIndex = fileRevParents[0];
65 } while (fileRevIndex != NO_REVISION);
66 } 118 }
67 119
68 /** 120 /**
69 * Annotates changes of the file against its parent(s) 121 * Annotates changes of the file against its parent(s)
70 */ 122 */
71 public void annotateChange(HgDataFile df, int changesetRevisionIndex, BlockInspector insp) { 123 public void annotateChange(HgDataFile df, int changelogRevisionIndex, BlockInspector insp) {
72 // TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f 124 // TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f
73 int fileRevIndex = fileRevIndex(df, changesetRevisionIndex); 125 int fileRevIndex = fileRevIndex(df, changelogRevisionIndex);
74 int[] fileRevParents = new int[2]; 126 int[] fileRevParents = new int[2];
75 df.parents(fileRevIndex, fileRevParents, null, null); 127 df.parents(fileRevIndex, fileRevParents, null, null);
76 if (changesetRevisionIndex == TIP) { 128 if (changelogRevisionIndex == TIP) {
77 changesetRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex); 129 changelogRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex);
78 } 130 }
79 implAnnotateChange(df, changesetRevisionIndex, fileRevIndex, fileRevParents, insp); 131 implAnnotateChange(new FileLinesCache(df, 5), changelogRevisionIndex, fileRevIndex, fileRevParents, insp);
80 } 132 }
81 133
82 private void implAnnotateChange(HgDataFile df, int csetRevIndex, int fileRevIndex, int[] fileParentRevs, BlockInspector insp) { 134 private void implAnnotateChange(FileLinesCache fl, int csetRevIndex, int fileRevIndex, int[] fileParentRevs, BlockInspector insp) {
83 final LineSequence fileRevLines = lines(df, fileRevIndex); 135 final LineSequence fileRevLines = fl.lines(fileRevIndex);
84 if (fileParentRevs[0] != NO_REVISION && fileParentRevs[1] != NO_REVISION) { 136 if (fileParentRevs[0] != NO_REVISION && fileParentRevs[1] != NO_REVISION) {
85 LineSequence p1Lines = lines(df, fileParentRevs[0]); 137 LineSequence p1Lines = fl.lines(fileParentRevs[0]);
86 LineSequence p2Lines = lines(df, fileParentRevs[1]); 138 LineSequence p2Lines = fl.lines(fileParentRevs[1]);
87 int p1ClogIndex = df.getChangesetRevisionIndex(fileParentRevs[0]); 139 int p1ClogIndex = fl.getChangesetRevisionIndex(fileParentRevs[0]);
88 int p2ClogIndex = df.getChangesetRevisionIndex(fileParentRevs[1]); 140 int p2ClogIndex = fl.getChangesetRevisionIndex(fileParentRevs[1]);
89 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>(); 141 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>();
90 pg.init(p2Lines, fileRevLines); 142 pg.init(p2Lines, fileRevLines);
91 EqualBlocksCollector p2MergeCommon = new EqualBlocksCollector(); 143 EqualBlocksCollector p2MergeCommon = new EqualBlocksCollector();
92 pg.findMatchingBlocks(p2MergeCommon); 144 pg.findMatchingBlocks(p2MergeCommon);
93 // 145 //
104 bbi.match(0, fileRevLines.chunkCount()-1, 0); 156 bbi.match(0, fileRevLines.chunkCount()-1, 0);
105 bbi.end(); 157 bbi.end();
106 } else { 158 } else {
107 int soleParent = fileParentRevs[0] == NO_REVISION ? fileParentRevs[1] : fileParentRevs[0]; 159 int soleParent = fileParentRevs[0] == NO_REVISION ? fileParentRevs[1] : fileParentRevs[0];
108 assert soleParent != NO_REVISION; 160 assert soleParent != NO_REVISION;
109 LineSequence parentLines = lines(df, soleParent); 161 LineSequence parentLines = fl.lines(soleParent);
110 162
111 int parentChangesetRevIndex = df.getChangesetRevisionIndex(soleParent); 163 int parentChangesetRevIndex = fl.getChangesetRevisionIndex(soleParent);
112 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>(); 164 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>();
113 pg.init(parentLines, fileRevLines); 165 pg.init(parentLines, fileRevLines);
114 pg.findMatchingBlocks(new BlameBlockInspector(insp, parentChangesetRevIndex, csetRevIndex)); 166 pg.findMatchingBlocks(new BlameBlockInspector(insp, parentChangesetRevIndex, csetRevIndex));
115 } 167 }
116 } 168 }
118 private static int fileRevIndex(HgDataFile df, int csetRevIndex) { 170 private static int fileRevIndex(HgDataFile df, int csetRevIndex) {
119 Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath()); 171 Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath());
120 return df.getRevisionIndex(fileRev); 172 return df.getRevisionIndex(fileRev);
121 } 173 }
122 174
123 private static LineSequence lines(HgDataFile df, int fileRevIndex) { 175 private static class FileLinesCache {
124 try { 176 private final HgDataFile df;
125 ByteArrayChannel c; 177 private final LinkedList<Pair<Integer, LineSequence>> lruCache;
126 df.content(fileRevIndex, c = new ByteArrayChannel()); 178 private final int limit;
127 return LineSequence.newlines(c.toArray()); 179 private IntMap<Integer> fileToClogIndexMap = new IntMap<Integer>(20);
128 } catch (CancelledException ex) { 180
129 // TODO likely it was bad idea to throw cancelled exception from content() 181 public FileLinesCache(HgDataFile file, int lruLimit) {
130 // deprecate and provide alternative? 182 df = file;
131 HgInvalidStateException ise = new HgInvalidStateException("ByteArrayChannel never throws CancelledException"); 183 limit = lruLimit;
132 ise.initCause(ex); 184 lruCache = new LinkedList<Pair<Integer, LineSequence>>();
133 throw ise; 185 }
186
187 public int getChangesetRevisionIndex(int fileRevIndex) {
188 Integer cached = fileToClogIndexMap.get(fileRevIndex);
189 if (cached == null) {
190 cached = df.getChangesetRevisionIndex(fileRevIndex);
191 fileToClogIndexMap.put(fileRevIndex, cached);
192 }
193 return cached.intValue();
194 }
195
196 public LineSequence lines(int fileRevIndex) {
197 Pair<Integer, LineSequence> cached = checkCache(fileRevIndex);
198 if (cached != null) {
199 return cached.second();
200 }
201 try {
202 ByteArrayChannel c;
203 df.content(fileRevIndex, c = new ByteArrayChannel());
204 LineSequence rv = LineSequence.newlines(c.toArray());
205 lruCache.addFirst(new Pair<Integer, LineSequence>(fileRevIndex, rv));
206 if (lruCache.size() > limit) {
207 lruCache.removeLast();
208 }
209 return rv;
210 } catch (CancelledException ex) {
211 // TODO likely it was bad idea to throw cancelled exception from content()
212 // deprecate and provide alternative?
213 HgInvalidStateException ise = new HgInvalidStateException("ByteArrayChannel never throws CancelledException");
214 ise.initCause(ex);
215 throw ise;
216 }
217 }
218
219 private Pair<Integer,LineSequence> checkCache(int fileRevIndex) {
220 Pair<Integer, LineSequence> rv = null;
221 for (ListIterator<Pair<Integer, LineSequence>> it = lruCache.listIterator(); it.hasNext(); ) {
222 Pair<Integer, LineSequence> p = it.next();
223 if (p.first() == fileRevIndex) {
224 rv = p;
225 it.remove();
226 break;
227 }
228 }
229 if (rv != null) {
230 lruCache.addFirst(rv);
231 }
232 return rv;
134 } 233 }
135 } 234 }
136 235
137 @Callback 236 @Callback
138 public interface BlockInspector { 237 public interface BlockInspector {