changeset 442:6865eb742883

Tests for subrepo API, refactor status tests for reuse, better subrepos API
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 27 Apr 2012 20:57:20 +0200
parents 2a08466838d3
children 072b5f3ed0c8
files build.xml cmdline/org/tmatesoft/hg/console/Main.java src/org/tmatesoft/hg/internal/SubrepoManager.java src/org/tmatesoft/hg/repo/HgInternals.java src/org/tmatesoft/hg/repo/HgRepositoryFiles.java src/org/tmatesoft/hg/repo/HgStatusInspector.java src/org/tmatesoft/hg/repo/HgSubrepoLocation.java test-data/test-repos.jar test/org/tmatesoft/hg/test/ErrorCollectorExt.java test/org/tmatesoft/hg/test/TestStatus.java test/org/tmatesoft/hg/test/TestSubrepo.java
diffstat 11 files changed, 399 insertions(+), 169 deletions(-) [+]
line wrap: on
line diff
--- a/build.xml	Thu Apr 26 12:42:32 2012 +0200
+++ b/build.xml	Fri Apr 27 20:57:20 2012 +0200
@@ -27,7 +27,7 @@
 
 	<property name="junit.jar" value="lib/junit-4.8.2.jar" />
 	<property name="ver.qualifier" value="" />
-	<property name="version.lib" value="0.9.0" />
+	<property name="version.lib" value="0.9.5" />
 	<property name="version.jar" value="${version.lib}${ver.qualifier}" />
 	<property name="compile-with-debug" value="yes"/>
 
@@ -92,6 +92,7 @@
 			<test name="org.tmatesoft.hg.test.TestDirstate" />
 			<test name="org.tmatesoft.hg.test.TestBranches" />
 			<test name="org.tmatesoft.hg.test.TestByteChannel" />
+			<test name="org.tmatesoft.hg.test.TestSubrepo" />
 			<test name="org.tmatesoft.hg.test.TestClone" />
 			<test name="org.tmatesoft.hg.test.TestIncoming" />
 			<test name="org.tmatesoft.hg.test.TestOutgoing" />
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Thu Apr 26 12:42:32 2012 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Main.java	Fri Apr 27 20:57:20 2012 +0200
@@ -97,14 +97,14 @@
 
 	public static void main(String[] args) throws Exception {
 		Main m = new Main(args);
-		m.checkWalkFileRevisions();
+//		m.checkWalkFileRevisions();
 //		m.checkSubProgress();
 //		m.checkFileFlags();
 //		m.buildFileLog();
 //		m.testConsoleLog();
 //		m.testTreeTraversal();
 //		m.testRevisionMap();
-//		m.testSubrepos();
+		m.testSubrepos();
 //		m.testReadWorkingCopy();
 //		m.testParents();
 //		m.testEffectiveFileLog();
@@ -318,7 +318,9 @@
 		
 	}
 
+	// any repository with subrepositories
 	private void testSubrepos() throws Exception {
+		// @see TestSubrepo#testAccessAPI
 		for (HgSubrepoLocation l : hgRepo.getSubrepositories()) {
 			System.out.println(l.getLocation());
 			System.out.println(l.getSource());
@@ -326,10 +328,10 @@
 			System.out.println(l.isCommitted() ? l.getRevision() : "not yet committed");
 			if (l.getType() == Kind.Hg) {
 				HgRepository r = l.getRepo();
-				System.out.printf("%s has %d revisions\n", l.getLocation(), r.getChangelog().getLastRevision() + 1);
+				System.out.printf("%s (%s) has %d revisions\n", l.getLocation(), r.getLocation(), r.getChangelog().getLastRevision() + 1);
 				if (r.getChangelog().getLastRevision() >= 0) {
 					final RawChangeset c = r.getChangelog().range(TIP, TIP).get(0);
-					System.out.printf("TIP: %s %s %s\n", c.user(), c.dateString(), c.comment());
+					System.out.printf("TIP: %s %s '%s'\n", c.user(), c.dateString(), c.comment());
 				}
 			}
 		}
--- a/src/org/tmatesoft/hg/internal/SubrepoManager.java	Thu Apr 26 12:42:32 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/SubrepoManager.java	Fri Apr 27 20:57:20 2012 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -27,12 +27,17 @@
 import java.util.List;
 import java.util.Map;
 
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgInvalidControlFileException;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgSubrepoLocation;
+import org.tmatesoft.hg.util.Path;
 
 /**
  * 
+ * @see http://mercurial.selenic.com/wiki/SubrepoWork
+ * @see http://mercurial.selenic.com/wiki/Subrepository
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
@@ -75,6 +80,7 @@
 		try {
 			String line;
 			LinkedList<HgSubrepoLocation> res = new LinkedList<HgSubrepoLocation>();
+			HgInternals hgRepoInternal = new HgInternals(repo);
 			while ((line = br.readLine()) != null) {
 				int sep = line.indexOf('=');
 				if (sep == -1) {
@@ -84,7 +90,7 @@
 				// to have separate String instances (new String(line.substring()))
 				String key = line.substring(0, sep).trim();
 				String value = line.substring(sep + 1).trim();
-				if (value.length() == 0) {
+				if (key.length() == 0 || value.length() == 0) {
 					// XXX log bad line?
 					continue;
 				}
@@ -100,7 +106,12 @@
 					}
 				}
 				// TODO respect paths mappings in config file
-				HgSubrepoLocation loc = new HgSubrepoLocation(repo, key, value, kind, substate.get(key));
+				//
+				// apparently, key value can't end with '/', `hg commit` fails if it does:
+				// abort: path ends in directory separator: fourth/
+				Path p = Path.create(key.charAt(key.length()-1) == '/' ? key : key + '/');
+				String revValue = substate.get(key);
+				HgSubrepoLocation loc = hgRepoInternal.newSubrepo(p, value, kind, revValue == null ? null : Nodeid.fromAscii(revValue));
 				res.add(loc);
 			}
 			return Arrays.asList(res.toArray(new HgSubrepoLocation[res.size()]));
@@ -110,6 +121,7 @@
 	}
 
 	private Map<String, String> readState(BufferedReader br) throws IOException {
+		// TODO reuse for other files with <revision><space><value> format, like .hgtags
 		HashMap<String, String> rv = new HashMap<String, String>();
 		try {
 			String line;
--- a/src/org/tmatesoft/hg/repo/HgInternals.java	Thu Apr 26 12:42:32 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgInternals.java	Fri Apr 27 20:57:20 2012 +0200
@@ -25,11 +25,13 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
+import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.internal.RelativePathRewrite;
 import org.tmatesoft.hg.internal.WinToNixPathRewrite;
+import org.tmatesoft.hg.repo.HgSubrepoLocation.Kind;
 import org.tmatesoft.hg.util.FileIterator;
 import org.tmatesoft.hg.util.FileWalker;
 import org.tmatesoft.hg.util.Path;
@@ -39,7 +41,9 @@
 /**
  * DO NOT USE THIS CLASS, INTENDED FOR TESTING PURPOSES.
  * 
- * This class gives access to repository internals, and holds methods that I'm not confident have to be widely accessible
+ * <p>This class is not part of the public API and may change or vanish any moment.
+ * 
+ * <p>This class gives access to repository internals, and holds methods that I'm not confident have to be widely accessible
  * Debug helper, to access otherwise restricted (package-local) methods
  * 
  * @author Artem Tikhomirov
@@ -81,6 +85,10 @@
 		}
 		return rv;
 	}
+	
+	public HgSubrepoLocation newSubrepo(Path loc, String src, Kind kind, Nodeid rev) {
+		return new HgSubrepoLocation(repo, loc, src, kind, rev);
+	}
 
 	public static File getRepositoryDir(HgRepository hgRepo) {
 		return hgRepo.getRepositoryRoot();
--- a/src/org/tmatesoft/hg/repo/HgRepositoryFiles.java	Thu Apr 26 12:42:32 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRepositoryFiles.java	Fri Apr 27 20:57:20 2012 +0200
@@ -19,7 +19,8 @@
 import org.tmatesoft.hg.internal.Experimental;
 
 /**
- *
+ * Names of some Mercurial configuration/service files.
+ * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
@@ -27,7 +28,8 @@
 public enum HgRepositoryFiles {
 
 	HgIgnore(".hgignore"), HgTags(".hgtags"), HgEol(".hgeol"), 
-	Dirstate(".hg/dirstate"), HgLocalTags(".hg/localtags");
+	Dirstate(".hg/dirstate"), HgLocalTags(".hg/localtags"),
+	HgSub(".hgsub"), HgSubstate(".hgsubstate");
 
 	private String fname;
 	
--- a/src/org/tmatesoft/hg/repo/HgStatusInspector.java	Thu Apr 26 12:42:32 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgStatusInspector.java	Fri Apr 27 20:57:20 2012 +0200
@@ -31,8 +31,11 @@
 	void added(Path fname);
 	/**
 	 * This method is invoked for files that we added as a result of a copy/move operation, and it's the sole
-	 * method invoked in this case, that is {@link #added(Path)} method is NOT invoked along with it.
-	 * If copied files of no interest, it is implementation responsibility to delegate to <code>this.added(fnameAdded)</code>
+	 * method invoked in this case, that is {@link #added(Path)} method is NOT invoked along with it. 
+	 * Note, however, {@link #removed(Path)} IS invoked for the removed file in all cases, regardless whether it's a mere rename or not.
+	 * <p>The reason why it's not symmetrical ({@link #copied(Path, Path)} and {@link #removed(Path)} but not {@link #added(Path)}) is that Mercurial 
+	 * does it this way ('copy' is just an extra attribute for Added file), and we try to stay as close as possible here.  
+	 * <p>If copied files of no interest, it is implementation responsibility to delegate to <code>this.added(fnameAdded)</code>
 	 */
 	void copied(Path fnameOrigin, Path fnameAdded);
 	void removed(Path fname);
--- a/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java	Thu Apr 26 12:42:32 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java	Fri Apr 27 20:57:20 2012 +0200
@@ -19,11 +19,14 @@
 import java.io.File;
 
 import org.tmatesoft.hg.core.HgRepositoryNotFoundException;
+import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.util.Path;
 
 /**
  * WORK IN PROGRESS, DO NOT USE
+ * 
+ * @see http://mercurial.selenic.com/wiki/Subrepository
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
@@ -34,58 +37,92 @@
 	private final Kind kind;
 	private final Path location;
 	private final String source;
-	private final String revInfo;
+	private final Nodeid revInfo;
 
 	public enum Kind { Hg, SVN, Git, }
 	
-	public HgSubrepoLocation(HgRepository parentRepo, String repoLocation, String actualLocation, Kind type, String revision) {
+	/**
+	 * 
+	 * @param parentRepo
+	 * @param repoLocation path, shall be valid directory (i.e. even if .hgsub doesn't specify trailing slash, this one shall)
+	 * @param actualLocation
+	 * @param type
+	 * @param revision may be <code>null</code>
+	 */
+	/*package-local*/ HgSubrepoLocation(HgRepository parentRepo, Path repoLocation, String actualLocation, Kind type, Nodeid revision) {
 		owner = parentRepo;
-		location = Path.create(repoLocation);
+		location = repoLocation;
 		source = actualLocation;
 		kind = type;
 		revInfo = revision;
 	}
 	
-	// as defined in .hgsub, key value
+	/**
+	 * Sub-repository's location within owning repository, always directory, <code>path/to/nested</code>.
+	 * <p>
+	 * May differ from left-hand, key value from <code>.hgsub</code> if the latter doesn't include trailing slash, which is required 
+	 * for {@link Path} objects
+	 * 
+	 * @return path to nested repository relative to owner's location
+	 */
 	public Path getLocation() {
 		return location;
 	}
 
-	// value from .hgsub
+	/**
+	 * Right-hand value from <code>.hgsub</code>, with <code>[kind]</code> stripped, if any.
+	 * @return sub-repository's source
+	 */
 	public String getSource() {
 		return source;
 	}
 	
+	/**
+	 * Sub-repository kind, either Mercurial, Subversion or Git
+	 * @return one of predefined constants
+	 */
 	public Kind getType() {
 		return kind;
 	}
 	
-	public String getRevision() {
+	/**
+	 * For a nested repository that has been committed at least once, returns
+	 * its revision as known from <code>.hgsubstate</code>
+	 * 
+	 * <p>Note, this revision belongs to the nested repository history, not that of owning repository.
+	 * 
+	 * @return revision of the nested repository, or <code>null</code> if not yet committed
+	 */
+	public Nodeid getRevision() {
 		return revInfo;
 	}
 
 	/**
-	 * @return whether this sub repository is known only locally
+	 * Answers whether this sub repository has ever been part of a commit of the owner repository
+	 * 
+	 * @return <code>true</code> if owning repository records {@link #getRevision() revision} of this sub-repository  
 	 */
 	public boolean isCommitted() {
 		return revInfo != null;
 	}
 	
 	/**
-	 * @return <code>true</code> when there are local changes in the sub repository
+	 * Answers whether there are local changes in the sub-repository,  
+	 * @return <code>true</code> if it's dirty
 	 */
 	public boolean hasChanges() {
 		throw HgRepository.notImplemented();
 	}
-	
-//	public boolean isLocal() {
-//	}
-	
+
+	/**
+	 * Access repository that owns nested one described by this object
+	 */
 	public HgRepository getOwner() {
 		return owner;
 	}
 
 	/**
+	 * Access nested repository as a full-fledged Mercurial repository
 	 * 
 	 * @return object to access sub-repository
 	 * @throws HgRepositoryNotFoundException if failed to find repository
Binary file test-data/test-repos.jar has changed
--- a/test/org/tmatesoft/hg/test/ErrorCollectorExt.java	Thu Apr 26 12:42:32 2012 +0200
+++ b/test/org/tmatesoft/hg/test/ErrorCollectorExt.java	Fri Apr 27 20:57:20 2012 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 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
@@ -20,6 +20,7 @@
 
 import java.util.concurrent.Callable;
 
+import org.hamcrest.CoreMatchers;
 import org.hamcrest.Matcher;
 import org.junit.internal.runners.model.MultipleFailureException;
 import org.junit.rules.ErrorCollector;
@@ -51,6 +52,10 @@
 		});
 	}
 	
+	public void assertTrue(final boolean value) {
+		assertTrue(null, value);
+	}
+
 	public void assertTrue(final String reason, final boolean value) {
 		checkSucceeds(new Callable<Object>() {
 			public Object call() throws Exception {
@@ -59,4 +64,8 @@
 			}
 		});
 	}
+	
+	public <T> void assertEquals(T expected, T actual) {
+		checkThat(null, actual, CoreMatchers.equalTo(expected));
+	}
 }
\ No newline at end of file
--- a/test/org/tmatesoft/hg/test/TestStatus.java	Thu Apr 26 12:42:32 2012 +0200
+++ b/test/org/tmatesoft/hg/test/TestStatus.java	Fri Apr 27 20:57:20 2012 +0200
@@ -50,7 +50,6 @@
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.Status;
 
-
 /**
  * 
  * @author Artem Tikhomirov
@@ -64,6 +63,7 @@
 	private HgRepository repo;
 	private StatusOutputParser statusParser;
 	private ExecHelper eh;
+	private StatusReporter sr;
 
 	public static void main(String[] args) throws Throwable {
 		TestStatus test = new TestStatus();
@@ -79,7 +79,7 @@
 		t3.testDirstateParentOtherThanTipNoUpdate();
 		t3.errorCollector.verify();
 	}
-	
+
 	public TestStatus() throws Exception {
 		this(new HgLookup().detectFromWorkingDir());
 	}
@@ -89,41 +89,42 @@
 		Assume.assumeTrue(!repo.isInvalid());
 		statusParser = new StatusOutputParser();
 		eh = new ExecHelper(statusParser, hgRepo.getWorkingDir());
+		sr = new StatusReporter(errorCollector, statusParser);
 	}
-	
+
 	@Test
 	public void testLowLevel() throws Exception {
 		final HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(repo);
 		statusParser.reset();
 		eh.run("hg", "status", "-A");
 		HgStatusCollector.Record r = wcc.status(HgRepository.TIP);
-		report("hg status -A", r, statusParser);
+		sr.report("hg status -A", r);
 		//
 		statusParser.reset();
 		int revision = 3;
 		eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
 		r = wcc.status(revision);
-		report("status -A --rev " + revision, r, statusParser);
+		sr.report("status -A --rev " + revision, r);
 		//
 		statusParser.reset();
 		eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
 		r = new HgStatusCollector.Record();
 		new HgStatusCollector(repo).change(revision, r);
-		report("status -A --change " + revision, r, statusParser);
+		sr.report("status -A --change " + revision, r);
 		//
 		statusParser.reset();
 		int rev2 = 80;
 		final String range = String.valueOf(revision) + ":" + String.valueOf(rev2);
 		eh.run("hg", "status", "-A", "--rev", range);
 		r = new HgStatusCollector(repo).status(revision, rev2);
-		report("Status -A -rev " + range, r, statusParser);
+		sr.report("Status -A -rev " + range, r);
 	}
 
 	/**
 	 * hg up --rev <earlier rev>; hg status
 	 * 
 	 * To check if HgWorkingCopyStatusCollector respects actual working copy parent (takes from dirstate)
-	 * and if status is calculated correctly 
+	 * and if status is calculated correctly
 	 */
 	@Test
 	@Ignore("modifies test repository, needs careful configuration")
@@ -145,16 +146,15 @@
 		//
 		eh.run("hg", "status", "-A");
 		HgStatusCollector.Record r = wcc.status(HgRepository.TIP);
-		report("hg status -A", r, statusParser);
+		sr.report("hg status -A", r);
 		//
 		statusParser.reset();
 		int revision = 3;
 		eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
 		r = wcc.status(revision);
-		report("status -A --rev " + revision, r, statusParser);
+		sr.report("status -A --rev " + revision, r);
 	}
 
-	
 	@Test
 	public void testStatusCommand() throws Exception {
 		final HgStatusCommand sc = new HgStatusCommand(repo).all();
@@ -162,26 +162,27 @@
 		statusParser.reset();
 		eh.run("hg", "status", "-A");
 		sc.execute(r = new StatusCollector());
-		report("hg status -A", r);
+		sr.report("hg status -A", r);
 		//
 		statusParser.reset();
 		int revision = 3;
 		eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
 		sc.base(revision).execute(r = new StatusCollector());
-		report("status -A --rev " + revision, r);
+		sr.report("status -A --rev " + revision, r);
 		//
 		statusParser.reset();
 		eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
 		sc.base(TIP).revision(revision).execute(r = new StatusCollector());
-		report("status -A --change " + revision, r);
-		
+		sr.report("status -A --change " + revision, r);
+
 		// TODO check not -A, but defaults()/custom set of modifications 
 	}
-	
-	private static class StatusCollector implements HgStatusHandler {
+
+	static class StatusCollector implements HgStatusHandler {
 		private final Map<Kind, List<Path>> kind2names = new TreeMap<Kind, List<Path>>();
 		private final Map<Path, List<Kind>> name2kinds = new TreeMap<Path, List<Kind>>();
 		private final Map<Path, Status> name2error = new LinkedHashMap<Path, Status>();
+		private final Map<Path, Path> new2oldName = new LinkedHashMap<Path, Path>();
 
 		public void status(HgStatus s) {
 			List<Path> l = kind2names.get(s.getKind());
@@ -189,6 +190,9 @@
 				kind2names.put(s.getKind(), l = new LinkedList<Path>());
 			}
 			l.add(s.getPath());
+			if (s.isCopy()) {
+				new2oldName.put(s.getPath(), s.getOriginalPath());
+			}
 			//
 			List<Kind> k = name2kinds.get(s.getPath());
 			if (k == null) {
@@ -196,24 +200,56 @@
 			}
 			k.add(s.getKind());
 		}
-		
+
 		public void error(Path file, Status s) {
 			name2error.put(file, s);
 		}
-		
+
 		public List<Path> get(Kind k) {
 			List<Path> rv = kind2names.get(k);
-			return rv == null ? Collections.<Path>emptyList() : rv;
+			return rv == null ? Collections.<Path> emptyList() : rv;
 		}
-		
+
 		public List<Kind> get(Path p) {
 			List<Kind> rv = name2kinds.get(p);
-			return rv == null ? Collections.<Kind>emptyList() : rv;
+			return rv == null ? Collections.<Kind> emptyList() : rv;
 		}
-		
+
 		public Map<Path, Status> getErrors() {
 			return name2error;
 		}
+
+		public HgStatusCollector.Record asStatusRecord() {
+			HgStatusCollector.Record rv = new HgStatusCollector.Record();
+			for (Path p : get(Modified)) {
+				rv.modified(p);
+			}
+			for (Path p : get(Added)) {
+				if (!new2oldName.containsKey(p)) {
+					// new files that are result of a copy get reported separately, below
+					rv.added(p);
+				}
+			}
+			for (Path p : get(Removed)) {
+				rv.removed(p);
+			}
+			for (Path p : get(Clean)) {
+				rv.clean(p);
+			}
+			for (Path p : get(Ignored)) {
+				rv.ignored(p);
+			}
+			for (Path p : get(Missing)) {
+				rv.missing(p);
+			}
+			for (Path p : get(Unknown)) {
+				rv.unknown(p);
+			}
+			for (Map.Entry<Path, Path> e : new2oldName.entrySet()) {
+				rv.copied(e.getValue(), e.getKey());
+			}
+			return rv;
+		}
 	}
 
 	/*
@@ -232,7 +268,7 @@
 		// shall not be listed at all
 		assertTrue(sc.get(file5).isEmpty());
 	}
-	
+
 	/*
 	 * status-1/file2 is tracked, but later .hgignore got entry to ignore it, file2 got modified
 	 * HG doesn't respect .hgignore for tracked files.
@@ -253,8 +289,8 @@
 
 	/*
 	 * status/dir/file4, added in rev 3, has been scheduled for removal (hg remove -Af file4), but still there in the WC.
-	 * Shall be reported as Removed, when comparing against rev 3 
-	 * (despite both rev 3 and WC's parent has file4,  there are different paths in the code for wc against parent and wc against rev)
+	 * Shall be reported as Removed, when comparing against rev 3
+	 * (despite both rev 3 and WC's parent has file4, there are different paths in the code for wc against parent and wc against rev)
 	 */
 	@Test
 	public void testMarkedRemovedButStillInWC() throws Exception {
@@ -280,10 +316,10 @@
 	}
 
 	/*
-	 * status-1/dir/file3 tracked, listed in .hgignore since rev 4, removed (hg remove file3)  from repo and WC 
+	 * status-1/dir/file3 tracked, listed in .hgignore since rev 4, removed (hg remove file3) from repo and WC
 	 * (but entry in .hgignore left) in revision 5, and new file3 got created in WC.
 	 * Shall be reported as ignored when comparing against WC's parent,
-	 * and both ignored and removed when comparing against revision 3 
+	 * and both ignored and removed when comparing against revision 3
 	 */
 	@Test
 	public void testRemovedIgnoredInWC() throws Exception {
@@ -317,7 +353,7 @@
 
 	/*
 	 * status/file1 was removed in cset 2. New file with the same name in the WC.
-	 * Shall report 2 statuses (as cmdline hg does): unknown and removed when comparing against that revision. 
+	 * Shall report 2 statuses (as cmdline hg does): unknown and removed when comparing against that revision.
 	 */
 	@Test
 	public void testNewFileWithSameNameAsDeletedOld() throws Exception {
@@ -339,7 +375,7 @@
 		assertTrue(sc.get(file1).contains(Unknown));
 		assertTrue(sc.get(file1).size() == 1);
 	}
-	
+
 	@Test
 	public void testSubTreeStatus() throws Exception {
 		repo = Configuration.get().find("status-1");
@@ -373,8 +409,7 @@
 		assertTrue(sc.get(Ignored).size() == 1);
 		assertTrue(sc.get(Removed).size() == 2);
 	}
-	
-	
+
 	@Test
 	public void testSpecificFileStatus() throws Exception {
 		repo = Configuration.get().find("status-1");
@@ -413,13 +448,13 @@
 		assertTrue(r.getIgnored().size() == 1);
 		assertTrue(r.getModified().isEmpty());
 	}
-	
+
 	@Test
 	public void testSameResultDirectPathVsMatcher() throws Exception {
 		repo = Configuration.get().find("status-1");
 		final Path file3 = Path.create("dir/file3");
 		final Path file5 = Path.create("dir/file5");
-		
+
 		HgWorkingCopyStatusCollector sc = HgWorkingCopyStatusCollector.create(repo, file3, file5);
 		HgStatusCollector.Record r;
 		sc.walk(WORKING_COPY, r = new HgStatusCollector.Record());
@@ -432,7 +467,7 @@
 		assertTrue(r.getRemoved().contains(file5));
 		assertTrue(r.getIgnored().contains(file3));
 	}
-	
+
 	@Test
 	public void testScopeInHistoricalStatus() throws Exception {
 		repo = Configuration.get().find("status-1");
@@ -459,7 +494,7 @@
 		assertTrue(sc.get(Added).size() == 1);
 
 	}
-	
+
 	/**
 	 * Issue 22
 	 */
@@ -473,34 +508,35 @@
 		// shall pass without exception
 		assertTrue(sc.getErrors().isEmpty());
 		for (HgStatus.Kind k : HgStatus.Kind.values()) {
-			assertTrue("Kind " + k.name() + " shall be empty",sc.get(k).isEmpty());
+			assertTrue("Kind " + k.name() + " shall be empty", sc.get(k).isEmpty());
 		}
 	}
-	
+
 	/**
 	 * Issue 22, two subsequent commits that remove all repository files, each in a different branch.
 	 * Here's excerpt from my RevlogWriter utility:
+	 * 
 	 * <pre>
 	 * 		final List<String> filesList = Collections.singletonList("file1");
-	 *	//
-	 *	file1.writeUncompressed(-1, -1, 0, 0, "garbage".getBytes());
-	 *	//
-	 *	ManifestBuilder mb = new ManifestBuilder();
-	 *	mb.reset().add("file1", file1.getRevision(0));
-	 *	manifest.writeUncompressed(-1, -1, 0, 0, mb.build()); // manifest revision 0
-	 *	final byte[] cset1 = buildChangelogEntry(manifest.getRevision(0), Collections.<String, String>emptyMap(), filesList, "Add a file");
-	 *	changelog.writeUncompressed(-1, -1, 0, 0, cset1);
-	 *	//
-	 *	// pretend we delete all files in a branch 1
-	 *	manifest.writeUncompressed(0, -1, 1, 1, new byte[0]); // manifest revision 1
-	 *	final byte[] cset2 = buildChangelogEntry(manifest.getRevision(1), Collections.singletonMap("branch", "delete-all-1"), filesList, "Delete all files in a first branch");
-	 *	 changelog.writeUncompressed(0, -1, 1, 1, cset2);
-	 *	//
-	 *	// pretend we delete all files in a branch 2 (which is based on revision 0, same as branch 1)
-	 *	manifest.writeUncompressed(1, -1, 1 /*!!! here comes baseRevision != index * /, 2, new byte[0]); // manifest revision 2
-	 *	final byte[] cset3 = buildChangelogEntry(manifest.getRevision(2), Collections.singletonMap("branch", "delete-all-2"), filesList, "Again delete all files but in another branch");
-	 *	changelog.writeUncompressed(0, -1, 2, 2, cset3);
-	 * </pre> 
+	 * //
+	 * file1.writeUncompressed(-1, -1, 0, 0, "garbage".getBytes());
+	 * //
+	 * ManifestBuilder mb = new ManifestBuilder();
+	 * mb.reset().add("file1", file1.getRevision(0));
+	 * manifest.writeUncompressed(-1, -1, 0, 0, mb.build()); // manifest revision 0
+	 * final byte[] cset1 = buildChangelogEntry(manifest.getRevision(0), Collections.<String, String>emptyMap(), filesList, "Add a file");
+	 * changelog.writeUncompressed(-1, -1, 0, 0, cset1);
+	 * //
+	 * // pretend we delete all files in a branch 1
+	 * manifest.writeUncompressed(0, -1, 1, 1, new byte[0]); // manifest revision 1
+	 * final byte[] cset2 = buildChangelogEntry(manifest.getRevision(1), Collections.singletonMap("branch", "delete-all-1"), filesList, "Delete all files in a first branch");
+	 *  changelog.writeUncompressed(0, -1, 1, 1, cset2);
+	 * //
+	 * // pretend we delete all files in a branch 2 (which is based on revision 0, same as branch 1)
+	 * manifest.writeUncompressed(1, -1, 1 /*!!! here comes baseRevision != index * /, 2, new byte[0]); // manifest revision 2
+	 * final byte[] cset3 = buildChangelogEntry(manifest.getRevision(2), Collections.singletonMap("branch", "delete-all-2"), filesList, "Again delete all files but in another branch");
+	 * changelog.writeUncompressed(0, -1, 2, 2, cset3);
+	 * </pre>
 	 */
 	@Test
 	public void testOnEmptyRepositoryWithAllFilesDeletedInBranch() throws Exception {
@@ -512,12 +548,12 @@
 		// shall pass without exception
 		assertTrue(sc.getErrors().isEmpty());
 		for (HgStatus.Kind k : HgStatus.Kind.values()) {
-			assertTrue("Kind " + k.name() + " shall be empty",sc.get(k).isEmpty());
+			assertTrue("Kind " + k.name() + " shall be empty", sc.get(k).isEmpty());
 		}
 	}
-	
+
 	/**
-	 * Issue 23: HgInvalidRevisionException for svn imported repository (changeset 0 references nullid manifest) 
+	 * Issue 23: HgInvalidRevisionException for svn imported repository (changeset 0 references nullid manifest)
 	 */
 	@Test
 	public void testImportedRepoWithOddManifestRevisions() throws Exception {
@@ -529,32 +565,32 @@
 		// shall pass without exception
 		assertTrue(sc.getErrors().isEmpty());
 	}
-	
+
 	/**
 	 * Issue 24: IllegalArgumentException in FilterDataAccess
 	 * There were two related defects in RevlogStream
-	 *  a) for compressedLen == 0, a byte was read and FilterDataAccess  (of length 0, but it didn't help too much) was created - first byte happen to be 0.
-	 *     Patch was not applied (userDataAccess.isEmpty() check thanks to Issue 22)
-	 *  b) That FilterDataAccess (with 0 size represents patch more or less relevantly, but didn't represent actual revision) get successfully
-	 *     reassigned as lastUserData for the next iteration. And at the next step attempt to apply patch recorded in the next revision failed
-	 *     because baseRevisionData is 0 length FilterDataAccess
+	 * a) for compressedLen == 0, a byte was read and FilterDataAccess (of length 0, but it didn't help too much) was created - first byte happen to be 0.
+	 * Patch was not applied (userDataAccess.isEmpty() check thanks to Issue 22)
+	 * b) That FilterDataAccess (with 0 size represents patch more or less relevantly, but didn't represent actual revision) get successfully
+	 * reassigned as lastUserData for the next iteration. And at the next step attempt to apply patch recorded in the next revision failed
+	 * because baseRevisionData is 0 length FilterDataAccess
 	 * 
-	 * Same applies for 
+	 * Same applies for
 	 * Issue 25: IOException: Underflow. Rewind past end of the slice in InflaterDataAccess
 	 * with the difference in separate .i and .d (thus not 0 but 'x' first byte was read)
-	 *
+	 * 
 	 * Sample:
-	 *  status-5/file1 has 3 revisions, second is zero-length patch:
-	 *  Index	   	 Offset    Packed     Actual   Base Rev
-	 *     0:             0     8          7          0
-	 *  DATA
-	 *     1:             8     0          7          0
-	 *  NO DATA
-	 *     2:             8     14         6          0
-	 *  PATCH
+	 * status-5/file1 has 3 revisions, second is zero-length patch:
+	 * Index Offset Packed Actual Base Rev
+	 * 0: 0 8 7 0
+	 * DATA
+	 * 1: 8 0 7 0
+	 * NO DATA
+	 * 2: 8 14 6 0
+	 * PATCH
 	 */
 	@Test
-	public void testZeroLengthPatchAgainstNonEmptyBaseRev() throws Exception{
+	public void testZeroLengthPatchAgainstNonEmptyBaseRev() throws Exception {
 		repo = Configuration.get().find("status-5");
 		// pretend we modified files in the working copy
 		// for HgWorkingCopyStatusCollector to go and retrieve its content from repository 
@@ -569,7 +605,7 @@
 		cmd.execute(sc);
 		// shall pass without exception
 		//
-		for (Map.Entry<Path,Status> e : sc.getErrors().entrySet()) {
+		for (Map.Entry<Path, Status> e : sc.getErrors().entrySet()) {
 			System.out.printf("%s : (%s %s)\n", e.getKey(), e.getValue().getKind(), e.getValue().getMessage());
 		}
 		assertTrue(sc.getErrors().isEmpty());
@@ -579,13 +615,13 @@
 	 * Issue 26: UnsupportedOperationException when patching empty base revision
 	 * 
 	 * Sample:
-	 *  status-5/file2 has 3 revisions, second is patch (complete revision content in a form of the patch) for empty base revision:
-	 *  Index    Offset      Packed     Actual   Base Rev
-	 *     0:         0      0          0          0
-	 *  NO DATA
-	 *     1:         0      20         7          0
-	 *  PATCH: 0..0, 7:garbage
-	 *     2:        20      16         7          0
+	 * status-5/file2 has 3 revisions, second is patch (complete revision content in a form of the patch) for empty base revision:
+	 * Index Offset Packed Actual Base Rev
+	 * 0: 0 0 0 0
+	 * NO DATA
+	 * 1: 0 20 7 0
+	 * PATCH: 0..0, 7:garbage
+	 * 2: 20 16 7 0
 	 */
 	@Test
 	public void testPatchZeroLengthBaseRevision() throws Exception {
@@ -600,13 +636,12 @@
 		cmd.execute(sc);
 		// shall pass without exception
 		//
-		for (Map.Entry<Path,Status> e : sc.getErrors().entrySet()) {
+		for (Map.Entry<Path, Status> e : sc.getErrors().entrySet()) {
 			System.out.printf("%s : (%s %s)\n", e.getKey(), e.getValue().getKind(), e.getValue().getMessage());
 		}
 		assertTrue(sc.getErrors().isEmpty());
 	}
 
-	
 	/*
 	 * With warm-up of previous tests, 10 runs, time in milliseconds
 	 * 'hg status -A': Native client total 953 (95 per run), Java client 94 (9)
@@ -614,13 +649,13 @@
 	 * 'hg log --debug', 10 runs: Native client total 1766 (176 per run), Java client 78 (7)
 	 * 
 	 * 18.02.2011
-	 * 'hg status -A --rev 3:80', 10 runs:  Native client total 2000 (200 per run), Java client 250 (25)
+	 * 'hg status -A --rev 3:80', 10 runs: Native client total 2000 (200 per run), Java client 250 (25)
 	 * 'hg log --debug', 10 runs: Native client total 2297 (229 per run), Java client 125 (12)
 	 * 
 	 * 9.3.2011 (DataAccess instead of byte[] in ReflogStream.Inspector
-	 * 'hg status -A',				10 runs:  Native client total 1516 (151 per run), Java client 219 (21)
-	 * 'hg status -A --rev 3:80',	10 runs:  Native client total 1875 (187 per run), Java client 3187 (318) (!!! ???)
-	 * 'hg log --debug',			10 runs: Native client total 2484 (248 per run), Java client 344 (34)
+	 * 'hg status -A', 10 runs: Native client total 1516 (151 per run), Java client 219 (21)
+	 * 'hg status -A --rev 3:80', 10 runs: Native client total 1875 (187 per run), Java client 3187 (318) (!!! ???)
+	 * 'hg log --debug', 10 runs: Native client total 2484 (248 per run), Java client 344 (34)
 	 */
 	public void testPerformance() throws Exception {
 		final int runs = 10;
@@ -635,67 +670,71 @@
 			new HgStatusCommand(repo).all().base(3).revision(80).execute(r);
 		}
 		final long end = System.currentTimeMillis();
-		System.out.printf("'hg status -A --rev 3:80', %d runs:  Native client total %d (%d per run), Java client %d (%d)\n", runs, start2-start1, (start2-start1)/runs, end-start2, (end-start2)/runs);
+		System.out.printf("'hg status -A --rev 3:80', %d runs:  Native client total %d (%d per run), Java client %d (%d)\n", runs, start2 - start1, (start2 - start1) / runs, end - start2,
+				(end - start2) / runs);
 	}
 	
-	private void report(String what, StatusCollector r) {
-		assertTrue(r.getErrors().isEmpty());
-		reportNotEqual(what + "#MODIFIED", r.get(Modified), statusParser.getModified());
-		reportNotEqual(what + "#ADDED", r.get(Added), statusParser.getAdded());
-		reportNotEqual(what + "#REMOVED", r.get(Removed), statusParser.getRemoved());
-		reportNotEqual(what + "#CLEAN", r.get(Clean), statusParser.getClean());
-		reportNotEqual(what + "#IGNORED", r.get(Ignored), statusParser.getIgnored());
-		reportNotEqual(what + "#MISSING", r.get(Missing), statusParser.getMissing());
-		reportNotEqual(what + "#UNKNOWN", r.get(Unknown), statusParser.getUnknown());
-		// TODO test copies
-	}
+	static class StatusReporter {
+		private final StatusOutputParser statusParser;
+		private final ErrorCollectorExt errorCollector;
 
-	private void report(String what, HgStatusCollector.Record r, StatusOutputParser statusParser) {
-		reportNotEqual(what + "#MODIFIED", r.getModified(), statusParser.getModified());
-		reportNotEqual(what + "#ADDED", r.getAdded(), statusParser.getAdded());
-		reportNotEqual(what + "#REMOVED", r.getRemoved(), statusParser.getRemoved());
-		reportNotEqual(what + "#CLEAN", r.getClean(), statusParser.getClean());
-		reportNotEqual(what + "#IGNORED", r.getIgnored(), statusParser.getIgnored());
-		reportNotEqual(what + "#MISSING", r.getMissing(), statusParser.getMissing());
-		reportNotEqual(what + "#UNKNOWN", r.getUnknown(), statusParser.getUnknown());
-		List<Path> copiedKeyDiff = difference(r.getCopied().keySet(), statusParser.getCopied().keySet());
-		HashMap<Path, String> copyDiff = new HashMap<Path,String>();
-		if (copiedKeyDiff.isEmpty()) {
-			for (Path jk : r.getCopied().keySet()) {
-				Path jv = r.getCopied().get(jk);
-				if (statusParser.getCopied().containsKey(jk)) {
-					Path cmdv = statusParser.getCopied().get(jk);
-					if (!jv.equals(cmdv)) {
-						copyDiff.put(jk, jv + " instead of " + cmdv);
+		public StatusReporter(ErrorCollectorExt ec, StatusOutputParser sp) {
+			errorCollector = ec;
+			statusParser = sp;
+		}
+	
+		public void report(String what, StatusCollector r) {
+			errorCollector.assertTrue(what, r.getErrors().isEmpty());
+			report(what, r.asStatusRecord());
+		}
+
+		public void report(String what, HgStatusCollector.Record r) {
+			reportNotEqual(what + "#MODIFIED", r.getModified(), statusParser.getModified());
+			reportNotEqual(what + "#ADDED", r.getAdded(), statusParser.getAdded());
+			reportNotEqual(what + "#REMOVED", r.getRemoved(), statusParser.getRemoved());
+			reportNotEqual(what + "#CLEAN", r.getClean(), statusParser.getClean());
+			reportNotEqual(what + "#IGNORED", r.getIgnored(), statusParser.getIgnored());
+			reportNotEqual(what + "#MISSING", r.getMissing(), statusParser.getMissing());
+			reportNotEqual(what + "#UNKNOWN", r.getUnknown(), statusParser.getUnknown());
+			List<Path> copiedKeyDiff = difference(r.getCopied().keySet(), statusParser.getCopied().keySet());
+			HashMap<Path, String> copyDiff = new HashMap<Path, String>();
+			if (copiedKeyDiff.isEmpty()) {
+				for (Path jk : r.getCopied().keySet()) {
+					Path jv = r.getCopied().get(jk);
+					if (statusParser.getCopied().containsKey(jk)) {
+						Path cmdv = statusParser.getCopied().get(jk);
+						if (!jv.equals(cmdv)) {
+							copyDiff.put(jk, jv + " instead of " + cmdv);
+						}
+					} else {
+						copyDiff.put(jk, "ERRONEOUSLY REPORTED IN JAVA");
 					}
-				} else {
-					copyDiff.put(jk, "ERRONEOUSLY REPORTED IN JAVA");
 				}
 			}
+			errorCollector.checkThat(what + "#Non-matching 'copied' keys: ", copiedKeyDiff, equalTo(Collections.<Path> emptyList()));
+			errorCollector.checkThat(what + "#COPIED", copyDiff, equalTo(Collections.<Path, String> emptyMap()));
 		}
-		errorCollector.checkThat(what + "#Non-matching 'copied' keys: ", copiedKeyDiff, equalTo(Collections.<Path>emptyList()));
-		errorCollector.checkThat(what + "#COPIED", copyDiff, equalTo(Collections.<Path,String>emptyMap()));
-	}
-	
-	private <T extends Comparable<? super T>> void reportNotEqual(String what, Collection<T> l1, Collection<T> l2) {
-//		List<T> diff = difference(l1, l2);
-//		errorCollector.checkThat(what, diff, equalTo(Collections.<T>emptyList()));
-		ArrayList<T> sl1 = new ArrayList<T>(l1);
-		Collections.sort(sl1);
-		ArrayList<T> sl2 = new ArrayList<T>(l2);
-		Collections.sort(sl2);
-		errorCollector.checkThat(what, sl1, equalTo(sl2));
-	}
 
-	private static <T> List<T> difference(Collection<T> l1, Collection<T> l2) {
-		LinkedList<T> result = new LinkedList<T>(l2);
-		for (T t : l1) {
-			if (l2.contains(t)) {
-				result.remove(t);
-			} else {
-				result.add(t);
+		private <T extends Comparable<? super T>> void reportNotEqual(String what, Collection<T> l1, Collection<T> l2) {
+		//		List<T> diff = difference(l1, l2);
+		//		errorCollector.checkThat(what, diff, equalTo(Collections.<T>emptyList()));
+			ArrayList<T> sl1 = new ArrayList<T>(l1);
+			Collections.sort(sl1);
+			ArrayList<T> sl2 = new ArrayList<T>(l2);
+			Collections.sort(sl2);
+			errorCollector.checkThat(what, sl1, equalTo(sl2));
+		}
+
+		public static <T> List<T> difference(Collection<T> l1, Collection<T> l2) {
+			LinkedList<T> result = new LinkedList<T>(l2);
+			for (T t : l1) {
+				if (l2.contains(t)) {
+					result.remove(t);
+				} else {
+					result.add(t);
+				}
 			}
+			return result;
 		}
-		return result;
 	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/org/tmatesoft/hg/test/TestSubrepo.java	Fri Apr 27 20:57:20 2012 +0200
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2012 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 static org.junit.Assert.assertEquals;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.io.File;
+import java.util.List;
+
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.tmatesoft.hg.core.HgStatusCommand;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgSubrepoLocation;
+import org.tmatesoft.hg.repo.HgSubrepoLocation.Kind;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestSubrepo {
+
+	@Rule
+	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
+
+	private HgRepository repo;
+	private StatusOutputParser statusParser;
+	private ExecHelper eh;
+	
+	/*
+	 * Layout of status-subrepo:
+	 * first/					regular subrepo
+	 * dir/second/				subrepo nested under a tracked folder
+	 * third/					subrepo with another one
+	 * third/fourth				2nd level of subrepo nesting (registered in third/.hgsub)
+	 * third/fourth/file4_1		A, added file
+	 * third/fourth/file4_2		?, untracked file
+	 * fifth/					nested repository not yet registered in .hgsub
+	 * fifth/file5				untracked file
+	 * 
+	 * Curiously, fifth/ shall not be reported (neither 'hg status -AS' nor '-A' don't report
+	 * anything for it, no '?' for the file5 in particular. Once fifth/.hg/ is removed,
+	 * file5 gets its ? as one would expect)
+	 */
+
+	@Test
+	public void testAccessAPI() throws Exception {
+		repo = Configuration.get().find("status-subrepo");
+		List<HgSubrepoLocation> subrepositories = repo.getSubrepositories();
+		assertEquals(3, subrepositories.size());
+		checkHgSubrepo(Path.create("first/"), true, repo, subrepositories.get(0));
+		checkHgSubrepo(Path.create("dir/second/"), true, repo, subrepositories.get(1));
+		checkHgSubrepo(Path.create("third/"), false, repo, subrepositories.get(2));
+	}
+	
+	private void checkHgSubrepo(Path expectedLocation, boolean isCommitted, HgRepository topRepo, HgSubrepoLocation l) throws Exception {
+		errorCollector.assertEquals(expectedLocation, l.getLocation());
+		errorCollector.assertEquals(Kind.Hg, l.getType());
+		if (isCommitted) {
+			errorCollector.assertTrue(l.isCommitted());
+			errorCollector.assertTrue(l.getRevision() != null);
+			errorCollector.assertTrue(!l.getRevision().isNull());
+		} else {
+			errorCollector.assertTrue(!l.isCommitted());
+			errorCollector.assertTrue(l.getRevision() == null);
+		}
+		errorCollector.assertEquals(topRepo, l.getOwner());
+		HgRepository r = l.getRepo();
+		String expectedSubRepoLoc = new File(topRepo.getLocation(), expectedLocation.toString()).toString();
+		errorCollector.assertEquals(expectedSubRepoLoc, r.getLocation());
+		errorCollector.assertTrue(r.getChangelog().getRevisionCount() > 0);
+		if (isCommitted) {
+			errorCollector.assertEquals(r.getChangelog().getRevision(TIP), l.getRevision());
+		}
+	}
+
+	@Test
+	@Ignore("StatusCommand doesn't suport subrepositories yet")
+	public void testStatusCommand() throws Exception {
+		repo = Configuration.get().find("status-subrepo");
+		statusParser = new StatusOutputParser();
+		eh = new ExecHelper(statusParser, repo.getWorkingDir());
+		TestStatus.StatusReporter sr = new TestStatus.StatusReporter(errorCollector, statusParser);
+		HgStatusCommand cmd = new HgStatusCommand(repo).all();
+		TestStatus.StatusCollector sc;
+
+		eh.run("hg", "status", "-A", "-S");
+		cmd.subrepo(true);
+		cmd.execute(sc = new TestStatus.StatusCollector());
+		sr.report("status -A -S", sc);
+		
+		eh.run("hg", "status", "-A", "-S");
+		cmd.subrepo(false);
+		cmd.execute(sc = new TestStatus.StatusCollector());
+		sr.report("status -A", sc);
+		
+	}
+	
+}