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