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