Mercurial > hg4j
comparison src/org/tmatesoft/hg/repo/Revlog.java @ 324:283b294d1079
Explore alternatives to access file-changelog combined history
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Mon, 03 Oct 2011 06:47:20 +0200 |
parents | 09628675bcee |
children | 3f09b8c19142 |
comparison
equal
deleted
inserted
replaced
323:4c7e3ba67213 | 324:283b294d1079 |
---|---|
31 import org.tmatesoft.hg.core.HgBadStateException; | 31 import org.tmatesoft.hg.core.HgBadStateException; |
32 import org.tmatesoft.hg.core.HgException; | 32 import org.tmatesoft.hg.core.HgException; |
33 import org.tmatesoft.hg.core.Nodeid; | 33 import org.tmatesoft.hg.core.Nodeid; |
34 import org.tmatesoft.hg.internal.ArrayHelper; | 34 import org.tmatesoft.hg.internal.ArrayHelper; |
35 import org.tmatesoft.hg.internal.DataAccess; | 35 import org.tmatesoft.hg.internal.DataAccess; |
36 import org.tmatesoft.hg.internal.Experimental; | |
36 import org.tmatesoft.hg.internal.RevlogStream; | 37 import org.tmatesoft.hg.internal.RevlogStream; |
38 import org.tmatesoft.hg.util.Adaptable; | |
37 import org.tmatesoft.hg.util.ByteChannel; | 39 import org.tmatesoft.hg.util.ByteChannel; |
38 import org.tmatesoft.hg.util.CancelSupport; | 40 import org.tmatesoft.hg.util.CancelSupport; |
39 import org.tmatesoft.hg.util.CancelledException; | 41 import org.tmatesoft.hg.util.CancelledException; |
40 import org.tmatesoft.hg.util.ProgressSupport; | 42 import org.tmatesoft.hg.util.ProgressSupport; |
41 | 43 |
213 System.arraycopy(pc.nodeid, 0, parent2, 0, 20); | 215 System.arraycopy(pc.nodeid, 0, parent2, 0, 20); |
214 } | 216 } |
215 } | 217 } |
216 } | 218 } |
217 | 219 |
220 @Experimental | |
221 public void walk(int start, int end, final Revlog.Inspector inspector) { | |
222 int lastRev = getLastRevision(); | |
223 if (start == TIP) { | |
224 start = lastRev; | |
225 } | |
226 if (end == TIP) { | |
227 end = lastRev; | |
228 } | |
229 final RevisionInspector revisionInsp = getAdapter(inspector, RevisionInspector.class); | |
230 final ParentInspector parentInsp = getAdapter(inspector, ParentInspector.class); | |
231 final Nodeid[] allRevisions = parentInsp == null ? null : new Nodeid[end - start + 1]; | |
232 | |
233 content.iterate(start, end, false, new RevlogStream.Inspector() { | |
234 | |
235 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { | |
236 Nodeid nid = Nodeid.fromBinary(nodeid, 0); | |
237 if (revisionInsp != null) { | |
238 revisionInsp.next(revisionNumber, nid, linkRevision); | |
239 } | |
240 if (parentInsp != null) { | |
241 Nodeid p1 = parent1Revision == -1 ? Nodeid.NULL : allRevisions[parent1Revision]; | |
242 Nodeid p2 = parent2Revision == -1 ? Nodeid.NULL : allRevisions[parent2Revision]; | |
243 allRevisions[revisionNumber] = nid; | |
244 parentInsp.next(revisionNumber, nid, parent1Revision, parent2Revision, p1, p2); | |
245 } | |
246 } | |
247 }); | |
248 } | |
249 private static <T> T getAdapter(Object o, Class<T> adapterClass) { | |
250 if (adapterClass.isInstance(o)) { | |
251 return adapterClass.cast(o); | |
252 } | |
253 if (o instanceof Adaptable) { | |
254 return ((Adaptable) o).getAdapter(adapterClass); | |
255 } | |
256 return null; | |
257 } | |
258 | |
259 /** | |
260 * MARKER | |
261 */ | |
262 @Experimental | |
263 public interface Inspector { | |
264 } | |
265 | |
266 @Experimental | |
267 public interface RevisionInspector extends Inspector { | |
268 void next(int localRevision, Nodeid revision, int linkedRevision); | |
269 } | |
270 | |
271 @Experimental | |
272 public interface ParentInspector extends Inspector { | |
273 void next(int localRevision, Nodeid revision, int parent1, int parent2, Nodeid nidParent1, Nodeid nidParent2); | |
274 } | |
275 | |
218 /* | 276 /* |
219 * XXX think over if it's better to do either: | 277 * XXX think over if it's better to do either: |
220 * pw = getChangelog().new ParentWalker(); pw.init() and pass pw instance around as needed | 278 * pw = getChangelog().new ParentWalker(); pw.init() and pass pw instance around as needed |
221 * or | 279 * or |
222 * add Revlog#getParentWalker(), static class, make cons() and #init package-local, and keep SoftReference to allow walker reuse. | 280 * add Revlog#getParentWalker(), static class, make cons() and #init package-local, and keep SoftReference to allow walker reuse. |
223 * | 281 * |
224 * and yes, walker is not a proper name | 282 * and yes, walker is not a proper name |
225 */ | 283 */ |
226 public final class ParentWalker { | 284 public final class ParentWalker implements ParentInspector { |
227 | 285 |
228 | 286 |
229 private Nodeid[] sequential; // natural repository order, childrenOf rely on ordering | 287 private Nodeid[] sequential; // natural repository order, childrenOf rely on ordering |
230 private Nodeid[] sorted; // for binary search | 288 private Nodeid[] sorted; // for binary search |
231 private int[] sorted2natural; | 289 private int[] sorted2natural; |
239 | 297 |
240 public HgRepository getRepo() { | 298 public HgRepository getRepo() { |
241 return Revlog.this.getRepo(); | 299 return Revlog.this.getRepo(); |
242 } | 300 } |
243 | 301 |
302 public void next(int revisionNumber, Nodeid revision, int parent1Revision, int parent2Revision, Nodeid nidParent1, Nodeid nidParent2) { | |
303 if (parent1Revision >= revisionNumber || parent2Revision >= revisionNumber) { | |
304 throw new IllegalStateException(); // sanity, revisions are sequential | |
305 } | |
306 int ix = revisionNumber; | |
307 sequential[ix] = sorted[ix] = revision; | |
308 if (parent1Revision != -1) { | |
309 firstParent[ix] = sequential[parent1Revision]; | |
310 } | |
311 if (parent2Revision != -1) { // revlog of DataAccess.java has p2 set when p1 is -1 | |
312 secondParent[ix] = sequential[parent2Revision]; | |
313 } | |
314 } | |
315 | |
244 public void init() { | 316 public void init() { |
245 final RevlogStream stream = Revlog.this.content; | 317 final int revisionCount = Revlog.this.getRevisionCount(); |
246 final int revisionCount = stream.revisionCount(); | |
247 firstParent = new Nodeid[revisionCount]; | 318 firstParent = new Nodeid[revisionCount]; |
248 // although branches/merges are less frequent, and most of secondParent would be -1/null, some sort of | 319 // although branches/merges are less frequent, and most of secondParent would be -1/null, some sort of |
249 // SparseOrderedList might be handy, provided its inner structures do not overweight simplicity of an array | 320 // SparseOrderedList might be handy, provided its inner structures do not overweight simplicity of an array |
321 // FIXME IntMap is right candidate? | |
250 secondParent = new Nodeid[revisionCount]; | 322 secondParent = new Nodeid[revisionCount]; |
251 // | 323 // |
252 sequential = new Nodeid[revisionCount]; | 324 sequential = new Nodeid[revisionCount]; |
253 sorted = new Nodeid[revisionCount]; | 325 sorted = new Nodeid[revisionCount]; |
254 | 326 Revlog.this.walk(0, TIP, this); |
255 RevlogStream.Inspector insp = new RevlogStream.Inspector() { | |
256 | |
257 int ix = 0; | |
258 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) { | |
259 if (ix != revisionNumber) { | |
260 // XXX temp code, just to make sure I understand what's going on here | |
261 // FIXME check against cpython or another tool-mangled repository | |
262 throw new IllegalStateException(); | |
263 } | |
264 if (parent1Revision >= revisionNumber || parent2Revision >= revisionNumber) { | |
265 throw new IllegalStateException(); // sanity, revisions are sequential | |
266 } | |
267 final Nodeid nid = new Nodeid(nodeid, true); | |
268 sequential[ix] = sorted[ix] = nid; | |
269 if (parent1Revision != -1) { | |
270 assert parent1Revision < ix; | |
271 firstParent[ix] = sequential[parent1Revision]; | |
272 } | |
273 if (parent2Revision != -1) { // revlog of DataAccess.java has p2 set when p1 is -1 | |
274 assert parent2Revision < ix; | |
275 secondParent[ix] = sequential[parent2Revision]; | |
276 } | |
277 ix++; | |
278 } | |
279 }; | |
280 stream.iterate(0, TIP, false, insp); | |
281 Arrays.sort(sorted); | 327 Arrays.sort(sorted); |
282 sorted2natural = new int[revisionCount]; | 328 sorted2natural = new int[revisionCount]; |
283 for (int i = 0; i < revisionCount; i++) { | 329 for (int i = 0; i < revisionCount; i++) { |
284 Nodeid n = sequential[i]; | 330 Nodeid n = sequential[i]; |
285 int x = Arrays.binarySearch(sorted, n); | 331 int x = Arrays.binarySearch(sorted, n); |
422 * multiple {@link Revlog#getLocalRevision(Nodeid)} calls. | 468 * multiple {@link Revlog#getLocalRevision(Nodeid)} calls. |
423 * | 469 * |
424 * getLocalRevision(Nodeid) with straightforward lookup approach performs O(n/2) | 470 * getLocalRevision(Nodeid) with straightforward lookup approach performs O(n/2) |
425 * #localRevision() is log(n), plus initialization is O(n) | 471 * #localRevision() is log(n), plus initialization is O(n) |
426 */ | 472 */ |
427 public final class RevisionMap { | 473 public final class RevisionMap implements RevisionInspector { |
428 /* | 474 /* |
429 * in fact, initialization is much slower as it instantiates Nodeids, while #getLocalRevision | 475 * in fact, initialization is much slower as it instantiates Nodeids, while #getLocalRevision |
430 * compares directly against byte buffer. Measuring cpython with 70k+ gives 3 times difference (47 vs 171) | 476 * compares directly against byte buffer. Measuring cpython with 70k+ gives 3 times difference (47 vs 171) |
431 * for complete changelog iteration. | 477 * for complete changelog iteration. |
432 */ | 478 */ |
433 | 479 |
434 /* | 480 /* |
435 * XXX 3 * (x * 4) bytes. Can I do better? | 481 * XXX 3 * (x * 4) bytes. Can I do better? |
482 * It seems, yes. Don't need to keep sorted, always can emulate it with indirect access to sequential through sorted2natural. | |
483 * i.e. instead sorted[mid].compareTo(toFind), do sequential[sorted2natural[mid]].compareTo(toFind) | |
436 */ | 484 */ |
437 private Nodeid[] sequential; // natural repository order, childrenOf rely on ordering | 485 private Nodeid[] sequential; // natural repository order, childrenOf rely on ordering |
438 private Nodeid[] sorted; // for binary search | 486 private Nodeid[] sorted; // for binary search |
439 private int[] sorted2natural; | 487 private int[] sorted2natural; |
440 | 488 |
441 public RevisionMap() { | 489 public RevisionMap() { |
442 } | 490 } |
443 | 491 |
444 public HgRepository getRepo() { | 492 public HgRepository getRepo() { |
445 return Revlog.this.getRepo(); | 493 return Revlog.this.getRepo(); |
494 } | |
495 | |
496 public void next(int localRevision, Nodeid revision, int linkedRevision) { | |
497 sequential[localRevision] = sorted[localRevision] = revision; | |
446 } | 498 } |
447 | 499 |
448 /** | 500 /** |
449 * @return <code>this</code> for convenience. | 501 * @return <code>this</code> for convenience. |
450 */ | 502 */ |
451 public RevisionMap init(/*XXX Pool<Nodeid> to reuse nodeids, if possible. */) { | 503 public RevisionMap init(/*XXX Pool<Nodeid> to reuse nodeids, if possible. */) { |
452 // XXX HgRepository.register((RepoChangeListener) this); // listen to changes in repo, re-init if needed? | 504 // XXX HgRepository.register((RepoChangeListener) this); // listen to changes in repo, re-init if needed? |
453 final int revisionCount = Revlog.this.content.revisionCount(); | 505 final int revisionCount = Revlog.this.getRevisionCount(); |
454 sequential = new Nodeid[revisionCount]; | 506 sequential = new Nodeid[revisionCount]; |
455 sorted = new Nodeid[revisionCount]; | 507 sorted = new Nodeid[revisionCount]; |
456 RevlogStream.Inspector insp = new RevlogStream.Inspector() { | 508 Revlog.this.walk(0, TIP, this); |
457 | |
458 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { | |
459 final Nodeid nid = new Nodeid(nodeid, true); | |
460 sequential[revisionNumber] = sorted[revisionNumber] = nid; | |
461 } | |
462 }; | |
463 Revlog.this.content.iterate(0, TIP, false, insp); | |
464 // next is alternative to Arrays.sort(sorted), and build sorted2natural looking up each element of sequential in sorted. | 509 // next is alternative to Arrays.sort(sorted), and build sorted2natural looking up each element of sequential in sorted. |
465 // the way sorted2natural was build is O(n*log n). | 510 // the way sorted2natural was build is O(n*log n). |
466 final ArrayHelper ah = new ArrayHelper(); | 511 final ArrayHelper ah = new ArrayHelper(); |
467 ah.sort(sorted); | 512 ah.sort(sorted); |
468 // note, values in ArrayHelper#getReversed are 1-based indexes, not 0-based | 513 // note, values in ArrayHelper#getReversed are 1-based indexes, not 0-based |