changeset 653:629a7370554c

Tests for recent changes in HgParentChildMap and RepositoryComparator (outgoing to respect drafts and Issue 47)
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 03 Jul 2013 14:38:30 +0200
parents cd77bf51b562
children 12a4f60ea972
files build.xml src/org/tmatesoft/hg/core/HgOutgoingCommand.java src/org/tmatesoft/hg/core/HgPushCommand.java src/org/tmatesoft/hg/internal/RepositoryComparator.java src/org/tmatesoft/hg/internal/RevisionSet.java src/org/tmatesoft/hg/repo/HgParentChildMap.java test/org/tmatesoft/hg/test/HgServer.java test/org/tmatesoft/hg/test/RepoUtils.java test/org/tmatesoft/hg/test/TestIncoming.java test/org/tmatesoft/hg/test/TestOutgoing.java test/org/tmatesoft/hg/test/TestPush.java test/org/tmatesoft/hg/test/TestRevisionMaps.java test/org/tmatesoft/hg/test/TestRevisionSet.java
diffstat 13 files changed, 349 insertions(+), 177 deletions(-) [+]
line wrap: on
line diff
--- a/build.xml	Tue Jul 02 23:21:16 2013 +0200
+++ b/build.xml	Wed Jul 03 14:38:30 2013 +0200
@@ -111,6 +111,7 @@
 			<test name="org.tmatesoft.hg.test.TestDiffHelper" />
 			<test name="org.tmatesoft.hg.test.TestRepositoryLock" />
 			<test name="org.tmatesoft.hg.test.TestRevisionSet" />
+			<test name="org.tmatesoft.hg.test.TestRevisionMaps" />
 			<test name="org.tmatesoft.hg.test.TestPush" />
 			<test name="org.tmatesoft.hg.test.ComplexTest" />
 		</junit>
--- a/src/org/tmatesoft/hg/core/HgOutgoingCommand.java	Tue Jul 02 23:21:16 2013 +0200
+++ b/src/org/tmatesoft/hg/core/HgOutgoingCommand.java	Wed Jul 03 14:38:30 2013 +0200
@@ -21,7 +21,9 @@
 import java.util.TreeSet;
 
 import org.tmatesoft.hg.internal.Internals;
+import org.tmatesoft.hg.internal.PhasesHelper;
 import org.tmatesoft.hg.internal.RepositoryComparator;
+import org.tmatesoft.hg.internal.RevisionSet;
 import org.tmatesoft.hg.repo.HgChangelog;
 import org.tmatesoft.hg.repo.HgParentChildMap;
 import org.tmatesoft.hg.repo.HgRemoteRepository;
@@ -103,8 +105,7 @@
 	public List<Nodeid> executeLite() throws HgRemoteConnectionException, HgException, CancelledException {
 		final ProgressSupport ps = getProgressSupport(null);
 		try {
-			ps.start(10);
-			return getComparator(new ProgressSupport.Sub(ps, 5), getCancelSupport(null, true)).getLocalOnlyRevisions();
+			return getOutgoingRevisions(ps, getCancelSupport(null, true));
 		} catch (HgRuntimeException ex) {
 			throw new HgLibraryFailureException(ex);
 		} finally {
@@ -128,10 +129,16 @@
 		final ProgressSupport ps = getProgressSupport(handler);
 		final CancelSupport cs = getCancelSupport(handler, true);
 		try {
-			ps.start(-1);
-			ChangesetTransformer inspector = new ChangesetTransformer(localRepo, handler, getParentHelper(), ps, cs);
+			ps.start(200);
+			ChangesetTransformer inspector = new ChangesetTransformer(localRepo, handler, getParentHelper(), new ProgressSupport.Sub(ps, 100), cs);
 			inspector.limitBranches(branches);
-			getComparator(new ProgressSupport.Sub(ps, 1), cs).visitLocalOnlyRevisions(inspector);
+			List<Nodeid> out = getOutgoingRevisions(new ProgressSupport.Sub(ps, 100), cs);
+			int[] outRevIndex = new int[out.size()];
+			int i = 0;
+			for (Nodeid o : out) {
+				outRevIndex[i++] = localRepo.getChangelog().getRevisionIndex(o);
+			}
+			localRepo.getChangelog().range(inspector, outRevIndex);
 			inspector.checkFailure();
 		} catch (HgRuntimeException ex) {
 			throw new HgLibraryFailureException(ex);
@@ -159,4 +166,17 @@
 		return parentHelper;
 	}
 
+	
+	private List<Nodeid> getOutgoingRevisions(ProgressSupport ps, CancelSupport cs) throws HgRemoteConnectionException, HgException, CancelledException {
+		ps.start(10);
+		final RepositoryComparator c = getComparator(new ProgressSupport.Sub(ps, 5), cs);
+		List<Nodeid> local = c.getLocalOnlyRevisions();
+		ps.worked(3);
+		PhasesHelper phaseHelper = new PhasesHelper(Internals.getInstance(localRepo));
+		if (phaseHelper.isCapableOfPhases() && phaseHelper.withSecretRoots()) {
+			local = new RevisionSet(local).subtract(phaseHelper.allSecret()).asList();
+		}
+		ps.worked(2);
+		return local;
+	}
 }
--- a/src/org/tmatesoft/hg/core/HgPushCommand.java	Tue Jul 02 23:21:16 2013 +0200
+++ b/src/org/tmatesoft/hg/core/HgPushCommand.java	Wed Jul 03 14:38:30 2013 +0200
@@ -18,7 +18,6 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -39,10 +38,10 @@
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.LogFacility.Severity;
 import org.tmatesoft.hg.util.Outcome;
 import org.tmatesoft.hg.util.Pair;
 import org.tmatesoft.hg.util.ProgressSupport;
-import org.tmatesoft.hg.util.LogFacility.Severity;
 
 /**
  * 
@@ -220,15 +219,4 @@
 		remoteDrafts = remoteDrafts.subtract(localChildrenNotSent);
 		return remoteDrafts;
 	}
-	
-	/*
-	 * To test, start a server:
-	 * $ hg --config web.allow_push=* --config web.push_ssl=False --config server.validate=True --debug serve
-	 */
-	public static void main(String[] args) throws Exception {
-		final HgLookup hgLookup = new HgLookup();
-		HgRepository r = hgLookup.detect("/home/artem/hg/test-phases/");
-		HgRemoteRepository rr = hgLookup.detect(new URL("http://localhost:8000/"));
-		new HgPushCommand(r).destination(rr).execute();
-	}
 }
--- a/src/org/tmatesoft/hg/internal/RepositoryComparator.java	Tue Jul 02 23:21:16 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/RepositoryComparator.java	Wed Jul 03 14:38:30 2013 +0200
@@ -38,7 +38,6 @@
 import org.tmatesoft.hg.repo.HgRemoteRepository;
 import org.tmatesoft.hg.repo.HgRemoteRepository.Range;
 import org.tmatesoft.hg.repo.HgRemoteRepository.RemoteBranch;
-import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.util.CancelSupport;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.ProgressSupport;
@@ -113,45 +112,6 @@
 		}
 	}
 	
-	/**
-	 * Similar to @link {@link #getLocalOnlyRevisions()}, use this one if you need access to changelog entry content, not 
-	 * only its revision number. 
-	 * @param inspector delegate to analyze changesets, shall not be <code>null</code>
-	 */
-	public void visitLocalOnlyRevisions(HgChangelog.Inspector inspector) throws HgRuntimeException {
-		if (inspector == null) {
-			throw new IllegalArgumentException();
-		}
-		// one can use localRepo.childrenOf(getCommon()) and then iterate over nodeids, but there seems to be
-		// another approach to get all changes after common:
-		// find index of earliest revision, and report all that were later
-		final HgChangelog changelog = localRepo.getRepo().getChangelog();
-		int earliestRevision = Integer.MAX_VALUE;
-		List<Nodeid> commonKnown = getCommon();
-		for (Nodeid n : commonKnown) {
-			if (!localRepo.hasChildren(n)) {
-				// there might be (old) nodes, known both locally and remotely, with no children
-				// hence, we don't need to consider their local revision number
-				continue;
-			}
-			int lr = changelog.getRevisionIndex(n);
-			if (lr < earliestRevision) {
-				earliestRevision = lr;
-			}
-		}
-		if (earliestRevision == Integer.MAX_VALUE) {
-			// either there are no common nodes (known locally and at remote)
-			// or no local children found (local is up to date). In former case, perhaps I shall bit return silently,
-			// but check for possible wrong repo comparison (hs says 'repository is unrelated' if I try to 
-			// check in/out for a repo that has no common nodes.
-			return;
-		}
-		if (earliestRevision < 0 || earliestRevision >= changelog.getLastRevision()) {
-			throw new HgInvalidStateException(String.format("Invalid index of common known revision: %d in total of %d", earliestRevision, 1+changelog.getLastRevision()));
-		}
-		changelog.range(earliestRevision+1, changelog.getLastRevision(), inspector);
-	}
-
 	private List<Nodeid> findCommonWithRemote() throws HgRemoteConnectionException {
 		remoteHeads = remoteRepo.heads();
 		LinkedList<Nodeid> resultCommon = new LinkedList<Nodeid>(); // these remotes are known in local
--- a/src/org/tmatesoft/hg/internal/RevisionSet.java	Tue Jul 02 23:21:16 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/RevisionSet.java	Wed Jul 03 14:38:30 2013 +0200
@@ -19,6 +19,7 @@
 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -41,6 +42,10 @@
 	
 	private final Set<Nodeid> elements;
 	
+	public RevisionSet(Nodeid... revisions) {
+		this(revisions == null ? null : Arrays.asList(revisions));
+	}
+	
 	public RevisionSet(Collection<Nodeid> revisions) {
 		this(revisions == null ? new HashSet<Nodeid>() : new HashSet<Nodeid>(revisions));
 	}
--- a/src/org/tmatesoft/hg/repo/HgParentChildMap.java	Tue Jul 02 23:21:16 2013 +0200
+++ b/src/org/tmatesoft/hg/repo/HgParentChildMap.java	Wed Jul 03 14:38:30 2013 +0200
@@ -112,6 +112,7 @@
 		heads = new BitSet(revisionCount);
 		revlog.indexWalk(0, TIP, this);
 		Arrays.sort(sorted);
+		// FIXME use ArrayHelper instead
 		sorted2natural = new int[revisionCount];
 		for (int i = 0; i < revisionCount; i++) {
 			Nodeid n = sequential[i];
@@ -218,11 +219,14 @@
 	 * @return revisions that have supplied revision as their immediate parent
 	 */
 	public List<Nodeid> directChildren(Nodeid nid) {
-		LinkedList<Nodeid> result = new LinkedList<Nodeid>();
 		int x = Arrays.binarySearch(sorted, nid);
 		assertSortedIndex(x);
 		nid = sorted[x]; // canonical instance
 		int start = sorted2natural[x];
+		if (!hasChildren(start)) {
+			return Collections.emptyList();
+		}
+		LinkedList<Nodeid> result = new LinkedList<Nodeid>();
 		for (int i = start + 1; i < sequential.length; i++) {
 			if (nid == firstParent[i] || nid == secondParent[i]) {
 				result.add(sequential[i]);
@@ -291,6 +295,9 @@
 		int index = 0;
 		do {
 			index = heads.nextClearBit(index);
+			// nextClearBit(length-1) gives length when bit is set,
+			// however, last revision can't be a parent of any other, and
+			// the last bit would be always 0, and no AIOOBE 
 			result.add(sequential[index]);
 			index++;
 		} while (index < sequential.length);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/org/tmatesoft/hg/test/HgServer.java	Wed Jul 03 14:38:30 2013 +0200
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2013 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Wraps hg server
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+class HgServer {
+	private Process serverProcess;
+	private boolean publish = true;
+	
+	public HgServer publishing(boolean pub) {
+		publish = pub;
+		return this;
+	}
+
+	public HgServer start(File dir) throws IOException, InterruptedException {
+		if (serverProcess != null) {
+			stop();
+		}
+		List<String> cmdline = new ArrayList<String>();
+		cmdline.add("hg");
+		cmdline.add("--config");
+		cmdline.add("web.allow_push=*");
+		cmdline.add("--config");
+		cmdline.add("web.push_ssl=False");
+		cmdline.add("--config");
+		cmdline.add("server.validate=True");
+		cmdline.add("--config");
+		cmdline.add(String.format("web.port=%d", port()));
+		if (!publish) {
+			cmdline.add("--config");
+			cmdline.add("phases.publish=False");
+		}
+		cmdline.add("serve");
+		serverProcess = new ProcessBuilder(cmdline).directory(dir).start();
+		Thread.sleep(500);
+		return this;
+	}
+	
+	public URL getURL() throws MalformedURLException {
+		return new URL(String.format("http://localhost:%d/", port()));
+	}
+
+	public int port() {
+		return 9090;
+	}
+	
+	public void stop() {
+		if (serverProcess == null) {
+			return;
+		}
+		// if Process#destroy() doesn't perform well with scripts and child processes
+		// may need to write server pid to a file and send a kill <pid> here
+		serverProcess.destroy();
+		serverProcess = null;
+	}
+}
\ No newline at end of file
--- a/test/org/tmatesoft/hg/test/RepoUtils.java	Tue Jul 02 23:21:16 2013 +0200
+++ b/test/org/tmatesoft/hg/test/RepoUtils.java	Wed Jul 03 14:38:30 2013 +0200
@@ -34,6 +34,7 @@
 
 import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.HgInitCommand;
+import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.FileUtils;
 import org.tmatesoft.hg.internal.StreamLogFacility;
 import org.tmatesoft.hg.repo.HgRepository;
@@ -190,4 +191,12 @@
 		}
 		dest.delete();
 	}
+
+	static Nodeid[] allRevisions(HgRepository repo) {
+		Nodeid[] allRevs = new Nodeid[repo.getChangelog().getRevisionCount()];
+		for (int i = 0; i < allRevs.length; i++) {
+			allRevs[i] = repo.getChangelog().getRevision(i);
+		}
+		return allRevs;
+	}
 }
--- a/test/org/tmatesoft/hg/test/TestIncoming.java	Tue Jul 02 23:21:16 2013 +0200
+++ b/test/org/tmatesoft/hg/test/TestIncoming.java	Wed Jul 03 14:38:30 2013 +0200
@@ -115,8 +115,8 @@
 		HashSet<Nodeid> set = new HashSet<Nodeid>(liteResult);
 		for (Nodeid nid : expected) {
 			boolean removed = set.remove(nid);
-			errorCollector.checkThat(what + " Missing " +  nid.shortNotation() + " in HgIncomingCommand.execLite result", removed, equalTo(true));
+			errorCollector.checkThat(what + " Missing " +  nid.shortNotation() + " in execLite result", removed, equalTo(true));
 		}
-		errorCollector.checkThat(what + " Superfluous cset reported by HgIncomingCommand.execLite", set.isEmpty(), equalTo(true));
+		errorCollector.checkThat(what + " Superfluous cset reported by execLite", set.isEmpty(), equalTo(true));
 	}
 }
--- a/test/org/tmatesoft/hg/test/TestOutgoing.java	Tue Jul 02 23:21:16 2013 +0200
+++ b/test/org/tmatesoft/hg/test/TestOutgoing.java	Wed Jul 03 14:38:30 2013 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2013 TMate Software Ltd
  *  
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -16,19 +16,23 @@
  */
 package org.tmatesoft.hg.test;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
 import java.util.List;
 
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
+import org.tmatesoft.hg.core.HgCheckoutCommand;
+import org.tmatesoft.hg.core.HgCommitCommand;
 import org.tmatesoft.hg.core.HgLogCommand;
 import org.tmatesoft.hg.core.HgOutgoingCommand;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.repo.HgLookup;
 import org.tmatesoft.hg.repo.HgRemoteRepository;
+import org.tmatesoft.hg.repo.HgRepository;
 
 /**
  *
@@ -71,10 +75,10 @@
 			TestIncoming.report(collector, outParser, liteResult, errorCollector);
 			//
 			File f = new File(dest, "Test.txt");
-			append(f, "1");
+			RepoUtils.createFile(f, "1");
 			eh0.run("hg", "add");
 			eh0.run("hg", "commit", "-m", "1");
-			append(f, "2");
+			RepoUtils.modifyFileAppend(f, "2");
 			eh0.run("hg", "commit", "-m", "2");
 			//
 			cmd = new HgOutgoingCommand(lookup.detect(dest)).against(hgRemote);
@@ -85,10 +89,41 @@
 			TestIncoming.report(collector, outParser, liteResult, errorCollector);
 		}
 	}
-
-	static void append(File f, String s) throws IOException {
-		FileWriter fw = new FileWriter(f);
-		fw.append(s);
-		fw.close();
+	
+	/**
+	 * Issue 47: Incorrect set of outgoing changes when revision spins off prior to common revision of local and remote repos
+	 */
+	@Test
+	public void testOutgoingPreceedsCommon() throws Exception {
+		File srcRepoLoc = RepoUtils.cloneRepoToTempLocation("test-annotate", "test-outgoing-src", false);
+		File dstRepoLoc = RepoUtils.cloneRepoToTempLocation("test-annotate", "test-outgoing-dst", false);
+		File f1 = new File(srcRepoLoc, "file1");
+		assertTrue("[sanity]", f1.canWrite());
+		HgServer server = new HgServer().start(dstRepoLoc);
+		try {
+			final HgLookup hgLookup = new HgLookup();
+			final HgRepository srcRepo = hgLookup.detect(srcRepoLoc);
+			final HgRemoteRepository dstRemote = hgLookup.detect(server.getURL());
+			new HgCheckoutCommand(srcRepo).changeset(6).clean(true).execute();
+			assertEquals("[sanity]", "with-merge", srcRepo.getWorkingCopyBranchName());
+			RepoUtils.modifyFileAppend(f1, "change1");
+			new HgCommitCommand(srcRepo).message("Commit 1").execute();
+			new HgCheckoutCommand(srcRepo).changeset(5).clean(true).execute();
+			assertEquals("[sanity]", "no-merge", srcRepo.getWorkingCopyBranchName());
+			RepoUtils.modifyFileAppend(f1, "change2");
+			new HgCommitCommand(srcRepo).message("Commit 2").execute();
+			//
+			HgOutgoingCommand cmd = new HgOutgoingCommand(srcRepo).against(dstRemote);
+			LogOutputParser outParser = new LogOutputParser(true);
+			ExecHelper eh = new ExecHelper(outParser, srcRepoLoc);
+			HgLogCommand.CollectHandler collector = new HgLogCommand.CollectHandler();
+			//
+			List<Nodeid> liteResult = cmd.executeLite();
+			cmd.executeFull(collector);
+			eh.run("hg", "outgoing", "--debug", dstRemote.getLocation());
+			TestIncoming.report(collector, outParser, liteResult, errorCollector);
+		} finally {
+			server.stop();
+		}
 	}
 }
--- a/test/org/tmatesoft/hg/test/TestPush.java	Tue Jul 02 23:21:16 2013 +0200
+++ b/test/org/tmatesoft/hg/test/TestPush.java	Wed Jul 03 14:38:30 2013 +0200
@@ -20,10 +20,6 @@
 import static org.tmatesoft.hg.repo.HgRepository.TIP;
 
 import java.io.File;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
 import java.util.List;
 
 import org.junit.Rule;
@@ -383,62 +379,9 @@
 		}
 	}
 
-
 	private void checkRepositoriesAreSame(HgRepository srcRepo, HgRepository dstRepo) {
 		errorCollector.assertEquals(srcRepo.getChangelog().getRevisionCount(), dstRepo.getChangelog().getRevisionCount());
 		errorCollector.assertEquals(srcRepo.getChangelog().getRevision(0), dstRepo.getChangelog().getRevision(0));
 		errorCollector.assertEquals(srcRepo.getChangelog().getRevision(TIP), dstRepo.getChangelog().getRevision(TIP));
 	}
-
-	static class HgServer {
-		private Process serverProcess;
-		private boolean publish = true;
-		
-		public HgServer publishing(boolean pub) {
-			publish = pub;
-			return this;
-		}
-
-		public HgServer start(File dir) throws IOException, InterruptedException {
-			if (serverProcess != null) {
-				stop();
-			}
-			List<String> cmdline = new ArrayList<String>();
-			cmdline.add("hg");
-			cmdline.add("--config");
-			cmdline.add("web.allow_push=*");
-			cmdline.add("--config");
-			cmdline.add("web.push_ssl=False");
-			cmdline.add("--config");
-			cmdline.add("server.validate=True");
-			cmdline.add("--config");
-			cmdline.add(String.format("web.port=%d", port()));
-			if (!publish) {
-				cmdline.add("--config");
-				cmdline.add("phases.publish=False");
-			}
-			cmdline.add("serve");
-			serverProcess = new ProcessBuilder(cmdline).directory(dir).start();
-			Thread.sleep(500);
-			return this;
-		}
-		
-		public URL getURL() throws MalformedURLException {
-			return new URL(String.format("http://localhost:%d/", port()));
-		}
-
-		public int port() {
-			return 9090;
-		}
-		
-		public void stop() {
-			if (serverProcess == null) {
-				return;
-			}
-			// if Process#destroy() doesn't perform well with scripts and child processes
-			// may need to write server pid to a file and send a kill <pid> here
-			serverProcess.destroy();
-			serverProcess = null;
-		}
-	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/org/tmatesoft/hg/test/TestRevisionMaps.java	Wed Jul 03 14:38:30 2013 +0200
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2013 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgParentChildMap;
+import org.tmatesoft.hg.repo.HgRepository;
+
+/**
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestRevisionMaps {
+
+	@Rule
+	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
+
+	@Test
+	public void testParentChildMap() throws HgException {
+		final HgRepository repo = Configuration.get().find("test-annotate");
+		Nodeid[] allRevs = RepoUtils.allRevisions(repo);
+		HgParentChildMap<HgChangelog> parentHelper = new HgParentChildMap<HgChangelog>(repo.getChangelog());
+		parentHelper.init();
+		errorCollector.assertEquals(Arrays.asList(allRevs), parentHelper.all());
+		for (Nodeid n : allRevs) {
+			errorCollector.assertTrue(parentHelper.knownNode(n));
+			// parents
+			final Nodeid p1 = parentHelper.safeFirstParent(n);
+			final Nodeid p2 = parentHelper.safeSecondParent(n);
+			errorCollector.assertFalse(p1 == null);
+			errorCollector.assertFalse(p2 == null);
+			errorCollector.assertEquals(p1.isNull() ? null : p1, parentHelper.firstParent(n));
+			errorCollector.assertEquals(p2.isNull() ? null : p2, parentHelper.secondParent(n));
+			HashSet<Nodeid> parents = new HashSet<Nodeid>();
+			boolean modified = parentHelper.appendParentsOf(n, parents);
+			errorCollector.assertEquals(p1.isNull() && p2.isNull(), !modified);
+			HashSet<Nodeid> cp = new HashSet<Nodeid>();
+			cp.add(parentHelper.firstParent(n));
+			cp.add(parentHelper.secondParent(n));
+			cp.remove(null);
+			errorCollector.assertEquals(cp, parents);
+			modified = parentHelper.appendParentsOf(n, parents);
+			errorCollector.assertFalse(modified);
+			//
+			// isChild, hasChildren, childrenOf, directChildren
+			if (!p1.isNull()) {
+				errorCollector.assertTrue(parentHelper.isChild(p1, n));
+				errorCollector.assertTrue(parentHelper.hasChildren(p1));
+				errorCollector.assertTrue(parentHelper.childrenOf(Collections.singleton(p1)).contains(n));
+				errorCollector.assertTrue(parentHelper.directChildren(p1).contains(n));
+			}
+			if (!p2.isNull()) {
+				errorCollector.assertTrue(parentHelper.isChild(p2, n));
+				errorCollector.assertTrue(parentHelper.hasChildren(p2));
+				errorCollector.assertTrue(parentHelper.childrenOf(Collections.singleton(p2)).contains(n));
+				errorCollector.assertTrue(parentHelper.directChildren(p2).contains(n));
+			}
+			errorCollector.assertFalse(parentHelper.isChild(n, p1));
+			errorCollector.assertFalse(parentHelper.isChild(n, p2));
+			//
+			
+		}
+		// heads
+		errorCollector.assertEquals(Arrays.asList(allRevs[7], allRevs[9]), parentHelper.heads());
+		// isChild
+		errorCollector.assertTrue(parentHelper.isChild(allRevs[1], allRevs[9]));
+		errorCollector.assertTrue(parentHelper.isChild(allRevs[0], allRevs[7]));
+		errorCollector.assertFalse(parentHelper.isChild(allRevs[4], allRevs[7]));
+		errorCollector.assertFalse(parentHelper.isChild(allRevs[2], allRevs[6]));
+		// childrenOf
+		errorCollector.assertEquals(Arrays.asList(allRevs[7]), parentHelper.childrenOf(Collections.singleton(allRevs[5])));
+		errorCollector.assertEquals(Arrays.asList(allRevs[8], allRevs[9]), parentHelper.childrenOf(Arrays.asList(allRevs[4], allRevs[6])));
+		errorCollector.assertEquals(Arrays.asList(allRevs[6], allRevs[8], allRevs[9]), parentHelper.childrenOf(Collections.singleton(allRevs[3])));
+		// directChildren
+		errorCollector.assertEquals(Arrays.asList(allRevs[2], allRevs[3]), parentHelper.directChildren(allRevs[1]));
+		errorCollector.assertEquals(Arrays.asList(allRevs[8]), parentHelper.directChildren(allRevs[6]));
+		errorCollector.assertEquals(Collections.emptyList(), parentHelper.directChildren(allRevs[7]));
+	}
+
+}
--- a/test/org/tmatesoft/hg/test/TestRevisionSet.java	Tue Jul 02 23:21:16 2013 +0200
+++ b/test/org/tmatesoft/hg/test/TestRevisionSet.java	Wed Jul 03 14:38:30 2013 +0200
@@ -16,8 +16,6 @@
  */
 package org.tmatesoft.hg.test;
 
-import java.util.Arrays;
-
 import org.junit.Rule;
 import org.junit.Test;
 import org.tmatesoft.hg.core.Nodeid;
@@ -42,13 +40,20 @@
 		Nodeid n2 = Nodeid.fromAscii("3b7d51ed4c65082f9235e3459e282d7ff723aa97");
 		Nodeid n3 = Nodeid.fromAscii("14dac192aa262feb8ff6645a102648498483a188");
 		Nodeid n4 = Nodeid.fromAscii("1deea2f332183c947937f6df988c2c6417efc217");
-		RevisionSet a = rs(n1, n2, n3);
-		RevisionSet b = rs(n3, n4);
-		RevisionSet union_ab = rs(n1, n2, n3, n4);
-		RevisionSet intersect_ab = rs(n3);
-		RevisionSet subtract_ab = rs(n1, n2);
-		RevisionSet subtract_ba = rs(n4);
-		RevisionSet symDiff_ab = rs(n1, n2, n4);
+		Nodeid[] nodes = { n1, n2, n3 };
+		RevisionSet a = new RevisionSet(nodes);
+		Nodeid[] nodes1 = { n3, n4 };
+		RevisionSet b = new RevisionSet(nodes1);
+		Nodeid[] nodes2 = { n1, n2, n3, n4 };
+		RevisionSet union_ab = new RevisionSet(nodes2);
+		Nodeid[] nodes3 = { n3 };
+		RevisionSet intersect_ab = new RevisionSet(nodes3);
+		Nodeid[] nodes4 = { n1, n2 };
+		RevisionSet subtract_ab = new RevisionSet(nodes4);
+		Nodeid[] nodes5 = { n4 };
+		RevisionSet subtract_ba = new RevisionSet(nodes5);
+		Nodeid[] nodes6 = { n1, n2, n4 };
+		RevisionSet symDiff_ab = new RevisionSet(nodes6);
 		
 		errorCollector.assertEquals(union_ab, a.union(b));
 		errorCollector.assertEquals(union_ab, b.union(a));
@@ -58,67 +63,81 @@
 		errorCollector.assertEquals(subtract_ba, b.subtract(a));
 		errorCollector.assertEquals(symDiff_ab, a.symmetricDifference(b));
 		errorCollector.assertEquals(symDiff_ab, b.symmetricDifference(a));
-		errorCollector.assertTrue(rs(n1, n2, n4).equals(rs(n4, n1, n2)));
-		errorCollector.assertTrue(rs().equals(rs()));
-		errorCollector.assertFalse(rs(n1).equals(rs(n2)));
+		Nodeid[] nodes7 = { n1, n2, n4 };
+		Nodeid[] nodes8 = { n4, n1, n2 };
+		errorCollector.assertTrue(new RevisionSet(nodes7).equals(new RevisionSet(nodes8)));
+		Nodeid[] nodes9 = {};
+		Nodeid[] nodes10 = {};
+		errorCollector.assertTrue(new RevisionSet(nodes9).equals(new RevisionSet(nodes10)));
+		Nodeid[] nodes11 = { n1 };
+		Nodeid[] nodes12 = { n2 };
+		errorCollector.assertFalse(new RevisionSet(nodes11).equals(new RevisionSet(nodes12)));
 	}
 	
 	@Test
 	public void testRootsAndHeads() throws Exception {
 		final HgRepository repo = Configuration.get().find("test-annotate");
-		Nodeid[] allRevs = allRevisions(repo);
+		Nodeid[] allRevs = RepoUtils.allRevisions(repo);
 		HgParentChildMap<HgChangelog> parentHelper = new HgParentChildMap<HgChangelog>(repo.getChangelog());
 		parentHelper.init();
-		final RevisionSet complete = rs(allRevs);
+		final RevisionSet complete = new RevisionSet(allRevs);
+		Nodeid[] nodes = { allRevs[0] };
 		// roots
-		errorCollector.assertEquals(rs(allRevs[0]), complete.roots(parentHelper));
-		RevisionSet fromR2 = complete.subtract(rs(allRevs[0], allRevs[1]));
-		RevisionSet fromR3 = complete.subtract(rs(allRevs[0], allRevs[1], allRevs[2]));
-		errorCollector.assertEquals(rs(allRevs[2], allRevs[3]), fromR2.roots(parentHelper));
-		errorCollector.assertEquals(rs(allRevs[3], allRevs[4], allRevs[5]), fromR3.roots(parentHelper));
+		errorCollector.assertEquals(new RevisionSet(nodes), complete.roots(parentHelper));
+		Nodeid[] nodes1 = { allRevs[0], allRevs[1] };
+		RevisionSet fromR2 = complete.subtract(new RevisionSet(nodes1));
+		Nodeid[] nodes2 = { allRevs[0], allRevs[1], allRevs[2] };
+		RevisionSet fromR3 = complete.subtract(new RevisionSet(nodes2));
+		Nodeid[] nodes3 = { allRevs[2], allRevs[3] };
+		errorCollector.assertEquals(new RevisionSet(nodes3), fromR2.roots(parentHelper));
+		Nodeid[] nodes4 = { allRevs[3], allRevs[4], allRevs[5] };
+		errorCollector.assertEquals(new RevisionSet(nodes4), fromR3.roots(parentHelper));
+		Nodeid[] nodes5 = { allRevs[9], allRevs[7] };
 		// heads
-		errorCollector.assertEquals(rs(allRevs[9], allRevs[7]), complete.heads(parentHelper));
-		RevisionSet toR7 = complete.subtract(rs(allRevs[9], allRevs[8]));
-		errorCollector.assertEquals(rs(allRevs[7], allRevs[6], allRevs[4]), toR7.heads(parentHelper));
-		RevisionSet withoutNoMergeBranch = toR7.subtract(rs(allRevs[5], allRevs[7]));
-		errorCollector.assertEquals(rs(allRevs[6], allRevs[4]), withoutNoMergeBranch.heads(parentHelper));
+		errorCollector.assertEquals(new RevisionSet(nodes5), complete.heads(parentHelper));
+		Nodeid[] nodes6 = { allRevs[9], allRevs[8] };
+		RevisionSet toR7 = complete.subtract(new RevisionSet(nodes6));
+		Nodeid[] nodes7 = { allRevs[7], allRevs[6], allRevs[4] };
+		errorCollector.assertEquals(new RevisionSet(nodes7), toR7.heads(parentHelper));
+		Nodeid[] nodes8 = { allRevs[5], allRevs[7] };
+		RevisionSet withoutNoMergeBranch = toR7.subtract(new RevisionSet(nodes8));
+		Nodeid[] nodes9 = { allRevs[6], allRevs[4] };
+		errorCollector.assertEquals(new RevisionSet(nodes9), withoutNoMergeBranch.heads(parentHelper));
 		errorCollector.assertEquals(complete.heads(parentHelper), complete.heads(parentHelper).heads(parentHelper));
 	}
 	
 	@Test
 	public void testAncestorsAndChildren() throws Exception {
 		final HgRepository repo = Configuration.get().find("test-annotate");
-		Nodeid[] allRevs = allRevisions(repo);
+		Nodeid[] allRevs = RepoUtils.allRevisions(repo);
 		HgParentChildMap<HgChangelog> parentHelper = new HgParentChildMap<HgChangelog>(repo.getChangelog());
 		parentHelper.init();
-		final RevisionSet complete = rs(allRevs);
+		final RevisionSet complete = new RevisionSet(allRevs);
+		Nodeid[] nodes = {};
 		// children
-		errorCollector.assertTrue(rs().children(parentHelper).isEmpty());
-		errorCollector.assertEquals(rs(allRevs[8], allRevs[9]), rs(allRevs[4]).children(parentHelper));
+		errorCollector.assertTrue(new RevisionSet(nodes).children(parentHelper).isEmpty());
+		Nodeid[] nodes1 = { allRevs[8], allRevs[9] };
+		Nodeid[] nodes2 = { allRevs[4] };
+		errorCollector.assertEquals(new RevisionSet(nodes1), new RevisionSet(nodes2).children(parentHelper));
+		Nodeid[] nodes3 = { allRevs[8], allRevs[9], allRevs[4], allRevs[5], allRevs[7] };
 		// default branch and no-merge branch both from r2 
-		RevisionSet s1 = rs(allRevs[8], allRevs[9], allRevs[4], allRevs[5], allRevs[7]);
-		errorCollector.assertEquals(s1, rs(allRevs[2]).children(parentHelper));
+		RevisionSet s1 = new RevisionSet(nodes3);
+		Nodeid[] nodes4 = { allRevs[2] };
+		errorCollector.assertEquals(s1, new RevisionSet(nodes4).children(parentHelper));
+		Nodeid[] nodes5 = { allRevs[0], allRevs[1] };
 		// ancestors
-		RevisionSet fromR2 = complete.subtract(rs(allRevs[0], allRevs[1]));
+		RevisionSet fromR2 = complete.subtract(new RevisionSet(nodes5));
+		Nodeid[] nodes6 = { allRevs[9], allRevs[5], allRevs[7], allRevs[8] };
 		// no-merge branch and r9 are not in ancestors of r8 (as well as r8 itself)
-		RevisionSet s3 = fromR2.subtract(rs(allRevs[9], allRevs[5], allRevs[7], allRevs[8]));
-		errorCollector.assertEquals(s3, fromR2.ancestors(rs(allRevs[8]), parentHelper));
+		RevisionSet s3 = fromR2.subtract(new RevisionSet(nodes6));
+		Nodeid[] nodes7 = { allRevs[8] };
+		errorCollector.assertEquals(s3, fromR2.ancestors(new RevisionSet(nodes7), parentHelper));
+		Nodeid[] nodes8 = { allRevs[5], allRevs[7] };
 		// ancestors of no-merge branch
-		RevisionSet branchNoMerge = rs(allRevs[5], allRevs[7]);
-		errorCollector.assertEquals(rs(allRevs[0], allRevs[1], allRevs[2]), complete.ancestors(branchNoMerge, parentHelper));
-		errorCollector.assertEquals(rs(allRevs[2]), fromR2.ancestors(branchNoMerge, parentHelper));
-	}
-	
-	private static Nodeid[] allRevisions(HgRepository repo) {
-		Nodeid[] allRevs = new Nodeid[repo.getChangelog().getRevisionCount()];
-		for (int i = 0; i < allRevs.length; i++) {
-			allRevs[i] = repo.getChangelog().getRevision(i);
-		}
-		return allRevs;
-	}
-
-	
-	private static RevisionSet rs(Nodeid... nodes) {
-		return new RevisionSet(Arrays.asList(nodes));
+		RevisionSet branchNoMerge = new RevisionSet(nodes8);
+		Nodeid[] nodes9 = { allRevs[0], allRevs[1], allRevs[2] };
+		errorCollector.assertEquals(new RevisionSet(nodes9), complete.ancestors(branchNoMerge, parentHelper));
+		Nodeid[] nodes10 = { allRevs[2] };
+		errorCollector.assertEquals(new RevisionSet(nodes10), fromR2.ancestors(branchNoMerge, parentHelper));
 	}
 }