Mercurial > jhg
comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 518:0d5e1ea7955e
Tests for HgLogCommand#execute(HgChangesetHandler) with various combination of follow renames and ancestry
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Thu, 20 Dec 2012 19:55:45 +0100 |
parents | 9922d1f7cb2a |
children | 1ee452f31187 |
comparison
equal
deleted
inserted
replaced
517:9922d1f7cb2a | 518:0d5e1ea7955e |
---|---|
14 * the terms of a license other than GNU General Public License | 14 * the terms of a license other than GNU General Public License |
15 * contact TMate Software at support@hg4j.com | 15 * contact TMate Software at support@hg4j.com |
16 */ | 16 */ |
17 package org.tmatesoft.hg.core; | 17 package org.tmatesoft.hg.core; |
18 | 18 |
19 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; | |
19 import static org.tmatesoft.hg.repo.HgRepository.TIP; | 20 import static org.tmatesoft.hg.repo.HgRepository.TIP; |
20 import static org.tmatesoft.hg.util.LogFacility.Severity.Error; | 21 import static org.tmatesoft.hg.util.LogFacility.Severity.Error; |
21 | 22 |
22 import java.util.ArrayList; | 23 import java.util.ArrayList; |
23 import java.util.Arrays; | 24 import java.util.Arrays; |
33 import java.util.Set; | 34 import java.util.Set; |
34 import java.util.TreeSet; | 35 import java.util.TreeSet; |
35 | 36 |
36 import org.tmatesoft.hg.internal.IntMap; | 37 import org.tmatesoft.hg.internal.IntMap; |
37 import org.tmatesoft.hg.internal.IntVector; | 38 import org.tmatesoft.hg.internal.IntVector; |
39 import org.tmatesoft.hg.internal.Lifecycle; | |
38 import org.tmatesoft.hg.repo.HgChangelog; | 40 import org.tmatesoft.hg.repo.HgChangelog; |
39 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; | 41 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; |
40 import org.tmatesoft.hg.repo.HgDataFile; | 42 import org.tmatesoft.hg.repo.HgDataFile; |
41 import org.tmatesoft.hg.repo.HgInvalidControlFileException; | 43 import org.tmatesoft.hg.repo.HgInvalidControlFileException; |
42 import org.tmatesoft.hg.repo.HgInvalidRevisionException; | 44 import org.tmatesoft.hg.repo.HgInvalidRevisionException; |
63 * Not thread-safe (each thread has to use own {@link HgLogCommand} instance). | 65 * Not thread-safe (each thread has to use own {@link HgLogCommand} instance). |
64 * | 66 * |
65 * @author Artem Tikhomirov | 67 * @author Artem Tikhomirov |
66 * @author TMate Software Ltd. | 68 * @author TMate Software Ltd. |
67 */ | 69 */ |
68 public class HgLogCommand extends HgAbstractCommand<HgLogCommand> implements HgChangelog.Inspector { | 70 public class HgLogCommand extends HgAbstractCommand<HgLogCommand> { |
69 | 71 |
70 private final HgRepository repo; | 72 private final HgRepository repo; |
71 private Set<String> users; | 73 private Set<String> users; |
72 private Set<String> branches; | 74 private Set<String> branches; |
73 private int limit = 0, count = 0; | 75 private int limit = 0, count = 0; |
274 throw new IllegalArgumentException(); | 276 throw new IllegalArgumentException(); |
275 } | 277 } |
276 if (csetTransform != null) { | 278 if (csetTransform != null) { |
277 throw new ConcurrentModificationException(); | 279 throw new ConcurrentModificationException(); |
278 } | 280 } |
281 final int lastCset = endRev == TIP ? repo.getChangelog().getLastRevision() : endRev; | |
282 // XXX pretty much like HgInternals.checkRevlogRange | |
283 if (lastCset < 0 || lastCset > repo.getChangelog().getLastRevision()) { | |
284 throw new HgBadArgumentException(String.format("Bad value %d for end revision", endRev), null); | |
285 } | |
286 if (startRev < 0 || startRev > lastCset) { | |
287 throw new HgBadArgumentException(String.format("Bad value %d for start revision for range [%1$d..%d]", startRev, lastCset), null); | |
288 } | |
279 final ProgressSupport progressHelper = getProgressSupport(handler); | 289 final ProgressSupport progressHelper = getProgressSupport(handler); |
280 try { | 290 try { |
281 count = 0; | 291 count = 0; |
282 HgParentChildMap<HgChangelog> pw = getParentHelper(file == null); // leave it uninitialized unless we iterate whole repo | 292 HgParentChildMap<HgChangelog> pw = getParentHelper(file == null); // leave it uninitialized unless we iterate whole repo |
283 // ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above | 293 // ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above |
284 // may utilize it as well. CommandContext? How about StatusCollector there as well? | 294 // may utilize it as well. CommandContext? How about StatusCollector there as well? |
285 csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler, true)); | 295 csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler, true)); |
296 FilteringInspector filterInsp = new FilteringInspector(); | |
297 filterInsp.changesets(startRev, lastCset); | |
286 if (file == null) { | 298 if (file == null) { |
287 progressHelper.start(endRev - startRev + 1); | 299 progressHelper.start(endRev - startRev + 1); |
288 repo.getChangelog().range(startRev, endRev, this); | 300 repo.getChangelog().range(startRev, endRev, filterInsp); |
289 csetTransform.checkFailure(); | 301 csetTransform.checkFailure(); |
290 } else { | 302 } else { |
303 final HgFileRenameHandlerMixin withCopyHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); | |
304 List<Pair<HgDataFile, Nodeid>> fileRenames = buildFileRenamesQueue(); | |
291 progressHelper.start(-1/*XXX enum const, or a dedicated method startUnspecified(). How about startAtLeast(int)?*/); | 305 progressHelper.start(-1/*XXX enum const, or a dedicated method startUnspecified(). How about startAtLeast(int)?*/); |
292 HgDataFile fileNode = repo.getFileNode(file); | 306 |
293 if (!fileNode.exists()) { | 307 for (int nameIndex = 0, fileRenamesSize = fileRenames.size(); nameIndex < fileRenamesSize; nameIndex++) { |
294 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file); | 308 Pair<HgDataFile, Nodeid> curRename = fileRenames.get(nameIndex); |
295 } | 309 HgDataFile fileNode = curRename.first(); |
296 // FIXME startRev and endRev ARE CHANGESET REVISIONS, not that of FILE!!! | 310 if (followAncestry) { |
297 fileNode.history(startRev, endRev, this); | 311 TreeBuildInspector treeBuilder = new TreeBuildInspector(followAncestry); |
298 csetTransform.checkFailure(); | 312 List<HistoryNode> fileAncestry = treeBuilder.go(fileNode, curRename.second()); |
299 final HgFileRenameHandlerMixin withCopyHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); | 313 int[] commitRevisions = narrowChangesetRange(treeBuilder.getCommitRevisions(), startRev, lastCset); |
300 if (fileNode.isCopy()) { | 314 if (iterateDirection == IterateDirection.FromOldToNew) { |
301 // even if we do not follow history, report file rename | 315 repo.getChangelog().range(filterInsp, commitRevisions); |
302 do { | 316 } else { |
303 if (withCopyHandler != null) { | 317 assert iterateDirection == IterateDirection.FromNewToOld; |
304 HgFileRevision src = new HgFileRevision(repo, fileNode.getCopySourceRevision(), null, fileNode.getCopySourceName()); | 318 // visit one by one in the opposite direction |
305 HgFileRevision dst = new HgFileRevision(repo, fileNode.getRevision(0), null, fileNode.getPath(), src.getPath()); | 319 for (int i = commitRevisions.length-1; i >= 0; i--) { |
306 withCopyHandler.copy(src, dst); | 320 int csetWithFileChange = commitRevisions[i]; |
321 repo.getChangelog().range(csetWithFileChange, csetWithFileChange, filterInsp); | |
322 } | |
307 } | 323 } |
308 if (limit > 0 && count >= limit) { | 324 } else { |
309 // if limit reach, follow is useless. | 325 // report complete file history (XXX may narrow range with [startRev, endRev], but need to go from file rev to link rev) |
310 break; | 326 int fileStartRev = 0; //fileNode.getChangesetRevisionIndex(0) >= startRev |
327 int fileEndRev = fileNode.getLastRevision(); | |
328 fileNode.history(fileStartRev, fileEndRev, filterInsp); | |
329 csetTransform.checkFailure(); | |
330 } | |
331 if (followRenames && withCopyHandler != null && nameIndex + 1 < fileRenamesSize) { | |
332 Pair<HgDataFile, Nodeid> nextRename = fileRenames.get(nameIndex+1); | |
333 HgFileRevision src, dst; | |
334 // A -> B | |
335 if (iterateDirection == IterateDirection.FromOldToNew) { | |
336 // curRename: A, nextRename: B | |
337 src = new HgFileRevision(fileNode, curRename.second(), null); | |
338 dst = new HgFileRevision(nextRename.first(), nextRename.first().getRevision(0), src.getPath()); | |
339 } else { | |
340 assert iterateDirection == IterateDirection.FromNewToOld; | |
341 // curRename: B, nextRename: A | |
342 src = new HgFileRevision(nextRename.first(), nextRename.second(), null); | |
343 dst = new HgFileRevision(fileNode, fileNode.getRevision(0), src.getPath()); | |
311 } | 344 } |
312 if (followRenames) { | 345 withCopyHandler.copy(src, dst); |
313 fileNode = repo.getFileNode(fileNode.getCopySourceName()); | 346 } |
314 fileNode.history(this); | 347 } // for renames |
315 csetTransform.checkFailure(); | 348 } // file != null |
316 } | |
317 } while (followRenames && fileNode.isCopy()); | |
318 } | |
319 } | |
320 } catch (HgRuntimeException ex) { | 349 } catch (HgRuntimeException ex) { |
321 throw new HgLibraryFailureException(ex); | 350 throw new HgLibraryFailureException(ex); |
322 } finally { | 351 } finally { |
323 csetTransform = null; | 352 csetTransform = null; |
324 progressHelper.done(); | 353 progressHelper.done(); |
325 } | 354 } |
355 } | |
356 | |
357 // public static void main(String[] args) { | |
358 // int[] r = new int[] {17, 19, 21, 23, 25, 29}; | |
359 // System.out.println(Arrays.toString(narrowChangesetRange(r, 0, 45))); | |
360 // System.out.println(Arrays.toString(narrowChangesetRange(r, 0, 25))); | |
361 // System.out.println(Arrays.toString(narrowChangesetRange(r, 5, 26))); | |
362 // System.out.println(Arrays.toString(narrowChangesetRange(r, 20, 26))); | |
363 // System.out.println(Arrays.toString(narrowChangesetRange(r, 26, 28))); | |
364 // } | |
365 | |
366 private static int[] narrowChangesetRange(int[] csetRange, int startCset, int endCset) { | |
367 int lastInRange = csetRange[csetRange.length-1]; | |
368 assert csetRange.length < 2 || csetRange[0] < lastInRange; // sorted | |
369 assert startCset >= 0 && startCset <= endCset; | |
370 if (csetRange[0] >= startCset && lastInRange <= endCset) { | |
371 // completely fits in | |
372 return csetRange; | |
373 } | |
374 if (csetRange[0] > endCset || lastInRange < startCset) { | |
375 return new int[0]; // trivial | |
376 } | |
377 int i = 0; | |
378 while (i < csetRange.length && csetRange[i] < startCset) { | |
379 i++; | |
380 } | |
381 int j = csetRange.length - 1; | |
382 while (j > i && csetRange[j] > endCset) { | |
383 j--; | |
384 } | |
385 if (i == j) { | |
386 // no values in csetRange fit into [startCset, endCset] | |
387 return new int[0]; | |
388 } | |
389 int[] rv = new int[j-i+1]; | |
390 System.arraycopy(csetRange, i, rv, 0, rv.length); | |
391 return rv; | |
326 } | 392 } |
327 | 393 |
328 /** | 394 /** |
329 * Tree-wise iteration of a file history, with handy access to parent-child relations between changesets. | 395 * Tree-wise iteration of a file history, with handy access to parent-child relations between changesets. |
330 * When file history is being followed, handler may additionally implement {@link HgFileRenameHandlerMixin} | 396 * When file history is being followed, handler may additionally implement {@link HgFileRenameHandlerMixin} |
421 * TODO may use HgFileRevision (after some refactoring to accept HgDataFile and Nodeid) instead of Pair | 487 * TODO may use HgFileRevision (after some refactoring to accept HgDataFile and Nodeid) instead of Pair |
422 * and possibly reuse this functionality | 488 * and possibly reuse this functionality |
423 * | 489 * |
424 * @return list of file renames, ordered with respect to {@link #iterateDirection} | 490 * @return list of file renames, ordered with respect to {@link #iterateDirection} |
425 */ | 491 */ |
426 private List<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() { | 492 private List<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() throws HgPathNotFoundException { |
427 LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>(); | 493 LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>(); |
428 Nodeid startRev = null; | 494 Nodeid startRev = null; |
429 HgDataFile fileNode = repo.getFileNode(file); | 495 HgDataFile fileNode = repo.getFileNode(file); |
496 if (!fileNode.exists()) { | |
497 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file); | |
498 } | |
430 if (followAncestry) { | 499 if (followAncestry) { |
431 // TODO subject to dedicated method either in HgRepository (getWorkingCopyParentRevisionIndex) | 500 // TODO subject to dedicated method either in HgRepository (getWorkingCopyParentRevisionIndex) |
432 // or in the HgDataFile (getWorkingCopyOriginRevision) | 501 // or in the HgDataFile (getWorkingCopyOriginRevision) |
433 Nodeid wdParentChangeset = repo.getWorkingCopyParents().first(); | 502 Nodeid wdParentChangeset = repo.getWorkingCopyParents().first(); |
434 if (!wdParentChangeset.isNull()) { | 503 if (!wdParentChangeset.isNull()) { |
749 } | 818 } |
750 | 819 |
751 | 820 |
752 // | 821 // |
753 | 822 |
754 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { | 823 private class FilteringInspector implements HgChangelog.Inspector, Lifecycle { |
755 if (limit > 0 && count >= limit) { | 824 |
756 return; | 825 private Callback lifecycle; |
757 } | 826 private int firstCset = BAD_REVISION, lastCset = BAD_REVISION; |
758 if (branches != null && !branches.contains(cset.branch())) { | 827 |
759 return; | 828 // limit to changesets in this range only |
760 } | 829 public void changesets(int start, int end) { |
761 if (users != null) { | 830 firstCset = start; |
762 String csetUser = cset.user().toLowerCase(); | 831 lastCset = end; |
763 boolean found = false; | 832 } |
764 for (String u : users) { | 833 |
765 if (csetUser.indexOf(u) != -1) { | 834 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { |
766 found = true; | 835 if (limit > 0 && count >= limit) { |
767 break; | |
768 } | |
769 } | |
770 if (!found) { | |
771 return; | 836 return; |
772 } | 837 } |
773 } | 838 // XXX may benefit from optional interface with #isInterested(int csetRev) - to avoid |
774 if (date != null) { | 839 // RawChangeset instantiation |
775 // TODO post-1.0 implement date support for log | 840 if (firstCset != BAD_REVISION && revisionNumber < firstCset) { |
776 } | 841 return; |
777 count++; | 842 } |
778 csetTransform.next(revisionNumber, nodeid, cset); | 843 if (lastCset != BAD_REVISION && revisionNumber > lastCset) { |
844 return; | |
845 } | |
846 if (branches != null && !branches.contains(cset.branch())) { | |
847 return; | |
848 } | |
849 if (users != null) { | |
850 String csetUser = cset.user().toLowerCase(); | |
851 boolean found = false; | |
852 for (String u : users) { | |
853 if (csetUser.indexOf(u) != -1) { | |
854 found = true; | |
855 break; | |
856 } | |
857 } | |
858 if (!found) { | |
859 return; | |
860 } | |
861 } | |
862 if (date != null) { | |
863 // TODO post-1.0 implement date support for log | |
864 } | |
865 csetTransform.next(revisionNumber, nodeid, cset); | |
866 if (++count >= limit) { | |
867 if (lifecycle != null) { // FIXME support Lifecycle delegation | |
868 lifecycle.stop(); | |
869 } | |
870 } | |
871 } | |
872 | |
873 public void start(int count, Callback callback, Object token) { | |
874 lifecycle = callback; | |
875 } | |
876 | |
877 public void finish(Object token) { | |
878 } | |
779 } | 879 } |
780 | 880 |
781 private HgParentChildMap<HgChangelog> getParentHelper(boolean create) throws HgInvalidControlFileException { | 881 private HgParentChildMap<HgChangelog> getParentHelper(boolean create) throws HgInvalidControlFileException { |
782 if (parentHelper == null && create) { | 882 if (parentHelper == null && create) { |
783 parentHelper = new HgParentChildMap<HgChangelog>(repo.getChangelog()); | 883 parentHelper = new HgParentChildMap<HgChangelog>(repo.getChangelog()); |