Mercurial > hg4j
comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 328:a674b8590362
Move file tree history to upper API level
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Wed, 05 Oct 2011 07:13:57 +0200 |
| parents | d68dcb3b5f49 |
| children | 5f9073eabf06 |
comparison
equal
deleted
inserted
replaced
| 327:3f09b8c19142 | 328:a674b8590362 |
|---|---|
| 16 */ | 16 */ |
| 17 package org.tmatesoft.hg.core; | 17 package org.tmatesoft.hg.core; |
| 18 | 18 |
| 19 import static org.tmatesoft.hg.repo.HgRepository.TIP; | 19 import static org.tmatesoft.hg.repo.HgRepository.TIP; |
| 20 | 20 |
| 21 import java.util.ArrayList; | |
| 22 import java.util.Arrays; | |
| 21 import java.util.Calendar; | 23 import java.util.Calendar; |
| 24 import java.util.Collection; | |
| 22 import java.util.Collections; | 25 import java.util.Collections; |
| 23 import java.util.ConcurrentModificationException; | 26 import java.util.ConcurrentModificationException; |
| 24 import java.util.LinkedList; | 27 import java.util.LinkedList; |
| 25 import java.util.List; | 28 import java.util.List; |
| 26 import java.util.Set; | 29 import java.util.Set; |
| 27 import java.util.TreeSet; | 30 import java.util.TreeSet; |
| 28 | 31 |
| 32 import org.tmatesoft.hg.internal.IntMap; | |
| 33 import org.tmatesoft.hg.internal.IntVector; | |
| 29 import org.tmatesoft.hg.repo.HgChangelog; | 34 import org.tmatesoft.hg.repo.HgChangelog; |
| 30 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; | 35 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; |
| 31 import org.tmatesoft.hg.repo.HgDataFile; | 36 import org.tmatesoft.hg.repo.HgDataFile; |
| 32 import org.tmatesoft.hg.repo.HgRepository; | 37 import org.tmatesoft.hg.repo.HgRepository; |
| 38 import org.tmatesoft.hg.repo.HgStatusCollector; | |
| 39 import org.tmatesoft.hg.util.CancelSupport; | |
| 33 import org.tmatesoft.hg.util.CancelledException; | 40 import org.tmatesoft.hg.util.CancelledException; |
| 41 import org.tmatesoft.hg.util.Pair; | |
| 34 import org.tmatesoft.hg.util.Path; | 42 import org.tmatesoft.hg.util.Path; |
| 35 import org.tmatesoft.hg.util.ProgressSupport; | 43 import org.tmatesoft.hg.util.ProgressSupport; |
| 36 | 44 |
| 37 | 45 |
| 38 /** | 46 /** |
| 203 throw new ConcurrentModificationException(); | 211 throw new ConcurrentModificationException(); |
| 204 } | 212 } |
| 205 final ProgressSupport progressHelper = getProgressSupport(handler); | 213 final ProgressSupport progressHelper = getProgressSupport(handler); |
| 206 try { | 214 try { |
| 207 count = 0; | 215 count = 0; |
| 208 HgChangelog.ParentWalker pw = parentHelper; // leave it uninitialized unless we iterate whole repo | 216 HgChangelog.ParentWalker pw = getParentHelper(file == null); // leave it uninitialized unless we iterate whole repo |
| 209 if (file == null) { | |
| 210 pw = getParentHelper(); | |
| 211 } | |
| 212 // ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above | 217 // ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above |
| 213 // may utilize it as well. CommandContext? How about StatusCollector there as well? | 218 // may utilize it as well. CommandContext? How about StatusCollector there as well? |
| 214 csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler, true)); | 219 csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler, true)); |
| 215 if (file == null) { | 220 if (file == null) { |
| 216 progressHelper.start(endRev - startRev + 1); | 221 progressHelper.start(endRev - startRev + 1); |
| 248 } finally { | 253 } finally { |
| 249 csetTransform = null; | 254 csetTransform = null; |
| 250 progressHelper.done(); | 255 progressHelper.done(); |
| 251 } | 256 } |
| 252 } | 257 } |
| 253 | 258 |
| 259 public void execute(HgChangesetTreeHandler handler) throws CancelledException { | |
| 260 if (handler == null) { | |
| 261 throw new IllegalArgumentException(); | |
| 262 } | |
| 263 if (csetTransform != null) { | |
| 264 throw new ConcurrentModificationException(); | |
| 265 } | |
| 266 if (file == null) { | |
| 267 throw new IllegalArgumentException("History tree is supported for files only (at least now), please specify file"); | |
| 268 } | |
| 269 if (followHistory) { | |
| 270 throw new UnsupportedOperationException("Can't follow file history when building tree (yet?)"); | |
| 271 } | |
| 272 class TreeBuildInspector implements HgChangelog.ParentInspector, HgChangelog.RevisionInspector { | |
| 273 HistoryNode[] completeHistory; | |
| 274 int[] commitRevisions; | |
| 275 | |
| 276 public void next(int revisionNumber, Nodeid revision, int linkedRevision) { | |
| 277 commitRevisions[revisionNumber] = linkedRevision; | |
| 278 } | |
| 279 | |
| 280 public void next(int revisionNumber, Nodeid revision, int parent1, int parent2, Nodeid nidParent1, Nodeid nidParent2) { | |
| 281 HistoryNode p1 = null, p2 = null; | |
| 282 if (parent1 != -1) { | |
| 283 p1 = completeHistory[parent1]; | |
| 284 } | |
| 285 if (parent2!= -1) { | |
| 286 p2 = completeHistory[parent2]; | |
| 287 } | |
| 288 completeHistory[revisionNumber] = new HistoryNode(commitRevisions[revisionNumber], revision, p1, p2); | |
| 289 } | |
| 290 | |
| 291 HistoryNode[] go(HgDataFile fileNode) { | |
| 292 completeHistory = new HistoryNode[fileNode.getRevisionCount()]; | |
| 293 commitRevisions = new int[completeHistory.length]; | |
| 294 fileNode.walk(0, TIP, this); | |
| 295 return completeHistory; | |
| 296 } | |
| 297 }; | |
| 298 final ProgressSupport progressHelper = getProgressSupport(handler); | |
| 299 progressHelper.start(4); | |
| 300 final CancelSupport cancelHelper = getCancelSupport(handler, true); | |
| 301 cancelHelper.checkCancelled(); | |
| 302 HgDataFile fileNode = repo.getFileNode(file); | |
| 303 // build tree of nodes according to parents in file's revlog | |
| 304 final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(); | |
| 305 final HistoryNode[] completeHistory = treeBuildInspector.go(fileNode); | |
| 306 progressHelper.worked(1); | |
| 307 cancelHelper.checkCancelled(); | |
| 308 ElementImpl ei = new ElementImpl(treeBuildInspector.commitRevisions.length); | |
| 309 final ProgressSupport ph2; | |
| 310 if (treeBuildInspector.commitRevisions.length < 100 /*XXX is it really worth it? */) { | |
| 311 ei.initTransform(); | |
| 312 repo.getChangelog().range(ei, treeBuildInspector.commitRevisions); | |
| 313 progressHelper.worked(1); | |
| 314 ph2 = new ProgressSupport.Sub(progressHelper, 2); | |
| 315 } else { | |
| 316 ph2 = new ProgressSupport.Sub(progressHelper, 3); | |
| 317 } | |
| 318 ph2.start(completeHistory.length); | |
| 319 // XXX shall sort completeHistory according to changeset numbers? | |
| 320 for (int i = 0; i < completeHistory.length; i++ ) { | |
| 321 final HistoryNode n = completeHistory[i]; | |
| 322 handler.next(ei.init(n)); | |
| 323 ph2.worked(1); | |
| 324 cancelHelper.checkCancelled(); | |
| 325 } | |
| 326 progressHelper.done(); | |
| 327 } | |
| 328 | |
| 254 // | 329 // |
| 255 | 330 |
| 256 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { | 331 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { |
| 257 if (limit > 0 && count >= limit) { | 332 if (limit > 0 && count >= limit) { |
| 258 return; | 333 return; |
| 278 } | 353 } |
| 279 count++; | 354 count++; |
| 280 csetTransform.next(revisionNumber, nodeid, cset); | 355 csetTransform.next(revisionNumber, nodeid, cset); |
| 281 } | 356 } |
| 282 | 357 |
| 283 private HgChangelog.ParentWalker getParentHelper() { | 358 private HgChangelog.ParentWalker getParentHelper(boolean create) { |
| 284 if (parentHelper == null) { | 359 if (parentHelper == null && create) { |
| 285 parentHelper = repo.getChangelog().new ParentWalker(); | 360 parentHelper = repo.getChangelog().new ParentWalker(); |
| 286 parentHelper.init(); | 361 parentHelper.init(); |
| 287 } | 362 } |
| 288 return parentHelper; | 363 return parentHelper; |
| 289 } | 364 } |
| 315 | 390 |
| 316 public void next(HgChangeset changeset) { | 391 public void next(HgChangeset changeset) { |
| 317 result.add(changeset.clone()); | 392 result.add(changeset.clone()); |
| 318 } | 393 } |
| 319 } | 394 } |
| 395 | |
| 396 private static class HistoryNode { | |
| 397 final int changeset; | |
| 398 final Nodeid fileRevision; | |
| 399 final HistoryNode parent1, parent2; | |
| 400 List<HistoryNode> children; | |
| 401 | |
| 402 HistoryNode(int cs, Nodeid revision, HistoryNode p1, HistoryNode p2) { | |
| 403 changeset = cs; | |
| 404 fileRevision = revision; | |
| 405 parent1 = p1; | |
| 406 parent2 = p2; | |
| 407 if (p1 != null) { | |
| 408 p1.addChild(this); | |
| 409 } | |
| 410 if (p2 != null) { | |
| 411 p2.addChild(this); | |
| 412 } | |
| 413 } | |
| 414 | |
| 415 void addChild(HistoryNode child) { | |
| 416 if (children == null) { | |
| 417 children = new ArrayList<HistoryNode>(2); | |
| 418 } | |
| 419 children.add(child); | |
| 420 } | |
| 421 } | |
| 422 | |
| 423 private class ElementImpl implements HgChangesetTreeHandler.TreeElement, HgChangelog.Inspector { | |
| 424 private HistoryNode historyNode; | |
| 425 private Pair<HgChangeset, HgChangeset> parents; | |
| 426 private List<HgChangeset> children; | |
| 427 private IntMap<HgChangeset> cachedChangesets; | |
| 428 private ChangesetTransformer.Transformation transform; | |
| 429 private Nodeid changesetRevision; | |
| 430 private Pair<Nodeid,Nodeid> parentRevisions; | |
| 431 private List<Nodeid> childRevisions; | |
| 432 | |
| 433 public ElementImpl(int total) { | |
| 434 cachedChangesets = new IntMap<HgChangeset>(total); | |
| 435 } | |
| 436 | |
| 437 ElementImpl init(HistoryNode n) { | |
| 438 historyNode = n; | |
| 439 parents = null; | |
| 440 children = null; | |
| 441 changesetRevision = null; | |
| 442 parentRevisions = null; | |
| 443 childRevisions = null; | |
| 444 return this; | |
| 445 } | |
| 446 | |
| 447 public Nodeid fileRevision() { | |
| 448 return historyNode.fileRevision; | |
| 449 } | |
| 450 | |
| 451 public HgChangeset changeset() { | |
| 452 return get(historyNode.changeset)[0]; | |
| 453 } | |
| 454 | |
| 455 public Pair<HgChangeset, HgChangeset> parents() { | |
| 456 if (parents != null) { | |
| 457 return parents; | |
| 458 } | |
| 459 HistoryNode p; | |
| 460 final int p1, p2; | |
| 461 if ((p = historyNode.parent1) != null) { | |
| 462 p1 = p.changeset; | |
| 463 } else { | |
| 464 p1 = -1; | |
| 465 } | |
| 466 if ((p = historyNode.parent2) != null) { | |
| 467 p2 = p.changeset; | |
| 468 } else { | |
| 469 p2 = -1; | |
| 470 } | |
| 471 HgChangeset[] r = get(p1, p2); | |
| 472 return parents = new Pair<HgChangeset, HgChangeset>(r[0], r[1]); | |
| 473 } | |
| 474 | |
| 475 public Collection<HgChangeset> children() { | |
| 476 if (children != null) { | |
| 477 return children; | |
| 478 } | |
| 479 if (historyNode.children == null) { | |
| 480 children = Collections.emptyList(); | |
| 481 } else { | |
| 482 int[] childrentChangesetNumbers = new int[historyNode.children.size()]; | |
| 483 int j = 0; | |
| 484 for (HistoryNode hn : historyNode.children) { | |
| 485 childrentChangesetNumbers[j++] = hn.changeset; | |
| 486 } | |
| 487 children = Arrays.asList(get(childrentChangesetNumbers)); | |
| 488 } | |
| 489 return children; | |
| 490 } | |
| 491 | |
| 492 void populate(HgChangeset cs) { | |
| 493 cachedChangesets.put(cs.getRevision(), cs); | |
| 494 } | |
| 495 | |
| 496 private HgChangeset[] get(int... changelogRevisionNumber) { | |
| 497 HgChangeset[] rv = new HgChangeset[changelogRevisionNumber.length]; | |
| 498 IntVector misses = new IntVector(changelogRevisionNumber.length, -1); | |
| 499 for (int i = 0; i < changelogRevisionNumber.length; i++) { | |
| 500 if (changelogRevisionNumber[i] == -1) { | |
| 501 rv[i] = null; | |
| 502 continue; | |
| 503 } | |
| 504 HgChangeset cached = cachedChangesets.get(changelogRevisionNumber[i]); | |
| 505 if (cached != null) { | |
| 506 rv[i] = cached; | |
| 507 } else { | |
| 508 misses.add(changelogRevisionNumber[i]); | |
| 509 } | |
| 510 } | |
| 511 if (misses.size() > 0) { | |
| 512 final int[] changesets2read = misses.toArray(); | |
| 513 initTransform(); | |
| 514 repo.getChangelog().range(this, changesets2read); | |
| 515 for (int changeset2read : changesets2read) { | |
| 516 HgChangeset cs = cachedChangesets.get(changeset2read); | |
| 517 if (cs == null) { | |
| 518 throw new HgBadStateException(); | |
| 519 } | |
| 520 // HgChangelog.range may reorder changesets according to their order in the changelog | |
| 521 // thus need to find original index | |
| 522 boolean sanity = false; | |
| 523 for (int i = 0; i < changelogRevisionNumber.length; i++) { | |
| 524 if (changelogRevisionNumber[i] == cs.getRevision()) { | |
| 525 rv[i] = cs; | |
| 526 sanity = true; | |
| 527 break; | |
| 528 } | |
| 529 } | |
| 530 assert sanity; | |
| 531 } | |
| 532 } | |
| 533 return rv; | |
| 534 } | |
| 535 | |
| 536 // init only when needed | |
| 537 void initTransform() { | |
| 538 if (transform == null) { | |
| 539 transform = new ChangesetTransformer.Transformation(new HgStatusCollector(repo)/*XXX try to reuse from context?*/, getParentHelper(false)); | |
| 540 } | |
| 541 } | |
| 542 | |
| 543 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { | |
| 544 HgChangeset cs = transform.handle(revisionNumber, nodeid, cset); | |
| 545 populate(cs.clone()); | |
| 546 } | |
| 547 | |
| 548 public Nodeid changesetRevision() { | |
| 549 if (changesetRevision == null) { | |
| 550 changesetRevision = getRevision(historyNode.changeset); | |
| 551 } | |
| 552 return changesetRevision; | |
| 553 } | |
| 554 | |
| 555 public Pair<Nodeid, Nodeid> parentRevisions() { | |
| 556 if (parentRevisions == null) { | |
| 557 HistoryNode p; | |
| 558 final Nodeid p1, p2; | |
| 559 if ((p = historyNode.parent1) != null) { | |
| 560 p1 = getRevision(p.changeset); | |
| 561 } else { | |
| 562 p1 = Nodeid.NULL;; | |
| 563 } | |
| 564 if ((p = historyNode.parent2) != null) { | |
| 565 p2 = getRevision(p.changeset); | |
| 566 } else { | |
| 567 p2 = Nodeid.NULL; | |
| 568 } | |
| 569 parentRevisions = new Pair<Nodeid, Nodeid>(p1, p2); | |
| 570 } | |
| 571 return parentRevisions; | |
| 572 } | |
| 573 | |
| 574 public Collection<Nodeid> childRevisions() { | |
| 575 if (childRevisions != null) { | |
| 576 return childRevisions; | |
| 577 } | |
| 578 if (historyNode.children == null) { | |
| 579 childRevisions = Collections.emptyList(); | |
| 580 } else { | |
| 581 ArrayList<Nodeid> rv = new ArrayList<Nodeid>(historyNode.children.size()); | |
| 582 for (HistoryNode hn : historyNode.children) { | |
| 583 rv.add(getRevision(hn.changeset)); | |
| 584 } | |
| 585 childRevisions = Collections.unmodifiableList(rv); | |
| 586 } | |
| 587 return childRevisions; | |
| 588 } | |
| 589 | |
| 590 // reading nodeid involves reading index only, guess, can afford not to optimize multiple reads | |
| 591 private Nodeid getRevision(int changelogRevisionNumber) { | |
| 592 // XXX pipe through pool | |
| 593 HgChangeset cs = cachedChangesets.get(changelogRevisionNumber); | |
| 594 if (cs != null) { | |
| 595 return cs.getNodeid(); | |
| 596 } else { | |
| 597 return repo.getChangelog().getRevision(changelogRevisionNumber); | |
| 598 } | |
| 599 } | |
| 600 } | |
| 320 } | 601 } |
