changeset 94:af1f3b78b918

*StatusCollector renamed to Hg*StatusCollector
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 27 Jan 2011 21:18:47 +0100
parents d55d4eedfc57
children bcd31a4c638a
files cmdline/org/tmatesoft/hg/console/Status.java src/org/tmatesoft/hg/core/Cset.java src/org/tmatesoft/hg/core/LogCommand.java src/org/tmatesoft/hg/core/StatusCommand.java src/org/tmatesoft/hg/repo/HgStatusCollector.java src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java src/org/tmatesoft/hg/repo/StatusCollector.java src/org/tmatesoft/hg/repo/WorkingCopyStatusCollector.java test/org/tmatesoft/hg/test/StatusOutputParser.java test/org/tmatesoft/hg/test/TestStatus.java
diffstat 10 files changed, 672 insertions(+), 672 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Status.java	Thu Jan 27 21:15:21 2011 +0100
+++ b/cmdline/org/tmatesoft/hg/console/Status.java	Thu Jan 27 21:18:47 2011 +0100
@@ -29,9 +29,9 @@
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgStatusInspector;
 import org.tmatesoft.hg.repo.Internals;
-import org.tmatesoft.hg.repo.StatusCollector;
-import org.tmatesoft.hg.repo.StatusCollector.Record;
-import org.tmatesoft.hg.repo.WorkingCopyStatusCollector;
+import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.repo.HgStatusCollector.Record;
+import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
 
 /**
  *
@@ -58,8 +58,8 @@
 	}
 
 	private static void statusWorkingCopy(HgRepository hgRepo) {
-		WorkingCopyStatusCollector wcc = new WorkingCopyStatusCollector(hgRepo);
-		StatusCollector.Record r = new StatusCollector.Record();
+		HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(hgRepo);
+		HgStatusCollector.Record r = new HgStatusCollector.Record();
 		wcc.walk(TIP, r);
 		mardu(r);
 	}
@@ -75,8 +75,8 @@
 	}
 	
 	private static void statusRevVsWorkingCopy(HgRepository hgRepo) {
-		WorkingCopyStatusCollector wcc = new WorkingCopyStatusCollector(hgRepo);
-		StatusCollector.Record r = new StatusCollector.Record();
+		HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(hgRepo);
+		HgStatusCollector.Record r = new HgStatusCollector.Record();
 		wcc.walk(3, r);
 		mardu(r);
 	}
@@ -87,13 +87,13 @@
 		final StatusDump dump = new StatusDump();
 		dump.showIgnored = false;
 		dump.showClean = false;
-		StatusCollector sc = new StatusCollector(hgRepo);
+		HgStatusCollector sc = new HgStatusCollector(hgRepo);
 		final int r1 = 0, r2 = 3;
 		System.out.printf("Status for changes between revision %d and %d:\n", r1, r2);
 		sc.walk(r1, r2, dump);
 		// 
 		System.out.println("\n\nSame, but sorted in the way hg status does:");
-		StatusCollector.Record r = sc.status(r1, r2);
+		HgStatusCollector.Record r = sc.status(r1, r2);
 		sortAndPrint('M', r.getModified());
 		sortAndPrint('A', r.getAdded());
 		sortAndPrint('R', r.getRemoved());
@@ -101,7 +101,7 @@
 		System.out.println("\n\nTry hg status --change <rev>:");
 		sc.change(0, dump);
 		System.out.println("\nStatus against working dir:");
-		WorkingCopyStatusCollector wcc = new WorkingCopyStatusCollector(hgRepo);
+		HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(hgRepo);
 		wcc.walk(TIP, dump);
 		System.out.println();
 		System.out.printf("Manifest of the revision %d:\n", r2);
--- a/src/org/tmatesoft/hg/core/Cset.java	Thu Jan 27 21:15:21 2011 +0100
+++ b/src/org/tmatesoft/hg/core/Cset.java	Thu Jan 27 21:18:47 2011 +0100
@@ -23,7 +23,7 @@
 import org.tmatesoft.hg.core.LogCommand.FileRevision;
 import org.tmatesoft.hg.repo.Changeset;
 import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.repo.StatusCollector;
+import org.tmatesoft.hg.repo.HgStatusCollector;
 import org.tmatesoft.hg.util.PathPool;
 
 
@@ -35,7 +35,7 @@
  * @author TMate Software Ltd.
  */
 public class Cset implements Cloneable {
-	private final StatusCollector statusHelper;
+	private final HgStatusCollector statusHelper;
 	private final PathPool pathHelper;
 
 	//
@@ -49,7 +49,7 @@
 
 	// XXX consider CommandContext with StatusCollector, PathPool etc. Commands optionally get CC through a cons or create new
 	// and pass it around
-	/*package-local*/Cset(StatusCollector statusCollector, PathPool pathPool) {
+	/*package-local*/Cset(HgStatusCollector statusCollector, PathPool pathPool) {
 		statusHelper = statusCollector;
 		pathHelper = pathPool;
 	}
@@ -132,7 +132,7 @@
 		ArrayList<Path> deleted = new ArrayList<Path>();
 		ArrayList<FileRevision> modified = new ArrayList<FileRevision>();
 		ArrayList<FileRevision> added = new ArrayList<FileRevision>();
-		StatusCollector.Record r = new StatusCollector.Record();
+		HgStatusCollector.Record r = new HgStatusCollector.Record();
 		statusHelper.change(revNumber, r);
 		final HgRepository repo = statusHelper.getRepo();
 		for (Path s : r.getModified()) {
--- a/src/org/tmatesoft/hg/core/LogCommand.java	Thu Jan 27 21:15:21 2011 +0100
+++ b/src/org/tmatesoft/hg/core/LogCommand.java	Thu Jan 27 21:18:47 2011 +0100
@@ -29,7 +29,7 @@
 import org.tmatesoft.hg.repo.Changeset;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.repo.StatusCollector;
+import org.tmatesoft.hg.repo.HgStatusCollector;
 import org.tmatesoft.hg.util.PathPool;
 
 
@@ -169,7 +169,7 @@
 		try {
 			delegate = handler;
 			count = 0;
-			changeset = new Cset(new StatusCollector(repo), new PathPool(repo.getPathHelper()));
+			changeset = new Cset(new HgStatusCollector(repo), new PathPool(repo.getPathHelper()));
 			if (file == null) {
 				repo.getChangelog().range(startRev, endRev, this);
 			} else {
--- a/src/org/tmatesoft/hg/core/StatusCommand.java	Thu Jan 27 21:15:21 2011 +0100
+++ b/src/org/tmatesoft/hg/core/StatusCommand.java	Thu Jan 27 21:18:47 2011 +0100
@@ -26,8 +26,8 @@
 import org.tmatesoft.hg.core.Path.Matcher;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgStatusInspector;
-import org.tmatesoft.hg.repo.StatusCollector;
-import org.tmatesoft.hg.repo.WorkingCopyStatusCollector;
+import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
 
 /**
  *
@@ -151,14 +151,14 @@
 			throw new ConcurrentModificationException();
 		}
 		visitor = handler;
-		StatusCollector sc = new StatusCollector(repo); // TODO from CommandContext
+		HgStatusCollector sc = new HgStatusCollector(repo); // TODO from CommandContext
 //		PathPool pathHelper = new PathPool(repo.getPathHelper()); // TODO from CommandContext
 		try {
 			// XXX if I need a rough estimation (for ProgressMonitor) of number of work units,
 			// I may use number of files in either rev1 or rev2 manifest edition
 			mediator.start();
 			if (endRevision == WORKING_COPY) {
-				WorkingCopyStatusCollector wcsc = new WorkingCopyStatusCollector(repo);
+				HgWorkingCopyStatusCollector wcsc = new HgWorkingCopyStatusCollector(repo);
 				wcsc.setBaseRevisionCollector(sc);
 				wcsc.walk(startRevision, mediator);
 			} else {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Thu Jan 27 21:18:47 2011 +0100
@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) 2011 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@svnkit.com
+ */
+package org.tmatesoft.hg.repo;
+
+import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.core.Path;
+import org.tmatesoft.hg.util.PathPool;
+import org.tmatesoft.hg.util.PathRewrite;
+
+
+/**
+ * RevisionWalker?
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgStatusCollector {
+
+	private final HgRepository repo;
+	private final Map<Integer, ManifestRevisionInspector> cache; // sparse array, in fact
+	private PathPool pathPool;
+
+	public HgStatusCollector(HgRepository hgRepo) {
+		this.repo = hgRepo;
+		cache = new TreeMap<Integer, ManifestRevisionInspector>();
+		ManifestRevisionInspector emptyFakeState = new ManifestRevisionInspector();
+		emptyFakeState.begin(-1, null);
+		emptyFakeState.end(-1); // FIXME HgRepo.TIP == -1 as well, need to distinguish fake "prior to first" revision from "the very last" 
+		cache.put(-1, emptyFakeState);
+	}
+	
+	public HgRepository getRepo() {
+		return repo;
+	}
+	
+	private ManifestRevisionInspector get(int rev) {
+		ManifestRevisionInspector i = cache.get(rev);
+		if (i == null) {
+			i = new ManifestRevisionInspector();
+			cache.put(rev, i);
+			repo.getManifest().walk(rev, rev, i);
+		}
+		return i;
+	}
+	
+	/*package-local*/ ManifestRevisionInspector raw(int rev) {
+		return get(rev);
+	}
+	/*package-local*/ PathPool getPathPool() {
+		if (pathPool == null) {
+			pathPool = new PathPool(new PathRewrite.Empty());
+		}
+		return pathPool;
+	}
+
+	public void setPathPool(PathPool pathPool) {
+		this.pathPool = pathPool;
+	}
+		
+	
+	// hg status --change <rev>
+	public void change(int rev, HgStatusInspector inspector) {
+		int[] parents = new int[2];
+		repo.getChangelog().parents(rev, parents, null, null);
+		walk(parents[0], rev, inspector);
+	}
+
+	// I assume revision numbers are the same for changelog and manifest - here 
+	// user would like to pass changelog revision numbers, and I use them directly to walk manifest.
+	// if this assumption is wrong, fix this (lookup manifest revisions from changeset).
+	public void walk(int rev1, int rev2, HgStatusInspector inspector) {
+		if (rev1 == rev2) {
+			throw new IllegalArgumentException();
+		}
+		if (inspector == null) {
+			throw new IllegalArgumentException();
+		}
+		if (inspector instanceof Record) {
+			((Record) inspector).init(rev1, rev2, this);
+		}
+		if (rev1 == TIP) {
+			rev1 = repo.getManifest().getRevisionCount() - 1;
+		}
+		if (rev2 == TIP) {
+			rev2 = repo.getManifest().getRevisionCount() - 1; // XXX add Revlog.tip() func ? 
+		}
+		// in fact, rev1 and rev2 are often next (or close) to each other,
+		// thus, we can optimize Manifest reads here (manifest.walk(rev1, rev2))
+		ManifestRevisionInspector r1, r2 ;
+		if (!cache.containsKey(rev1) && !cache.containsKey(rev2) && Math.abs(rev1 - rev2) < 5 /*subjective equivalent of 'close enough'*/) {
+			int minRev = rev1 < rev2 ? rev1 : rev2;
+			int maxRev = minRev == rev1 ? rev2 : rev1;
+			if (minRev > 0) {
+				minRev--; // expand range a bit
+				// XXX perhaps, if revlog.baseRevision is cheap, shall expand minRev up to baseRevision
+				// which gonna be read anyway
+			}
+	
+			repo.getManifest().walk(minRev, maxRev, new HgManifest.Inspector() {
+				private ManifestRevisionInspector delegate;
+
+				public boolean begin(int revision, Nodeid nid) {
+					cache.put(revision, delegate = new ManifestRevisionInspector());
+					delegate.begin(revision, nid);
+					return true;
+				}
+
+				public boolean next(Nodeid nid, String fname, String flags) {
+					delegate.next(nid, fname, flags);
+					return true;
+				}
+				
+				public boolean end(int revision) {
+					delegate.end(revision);
+					delegate = null;
+					return true;
+				}
+			});
+		}
+		r1 = get(rev1);
+		r2 = get(rev2);
+
+		PathPool pp = getPathPool();
+
+		TreeSet<String> r1Files = new TreeSet<String>(r1.files());
+		for (String fname : r2.files()) {
+			if (r1Files.remove(fname)) {
+				Nodeid nidR1 = r1.nodeid(fname);
+				Nodeid nidR2 = r2.nodeid(fname);
+				String flagsR1 = r1.flags(fname);
+				String flagsR2 = r2.flags(fname);
+				if (nidR1.equals(nidR2) && ((flagsR2 == null && flagsR1 == null) || flagsR2.equals(flagsR1))) {
+					inspector.clean(pp.path(fname));
+				} else {
+					inspector.modified(pp.path(fname));
+				}
+			} else {
+				String copyOrigin = getOriginIfCopy(repo, fname, r1Files, rev1);
+				if (copyOrigin != null) {
+					inspector.copied(pp.path(copyOrigin), pp.path(fname));
+				} else {
+					inspector.added(pp.path(fname));
+				}
+			}
+		}
+		for (String left : r1Files) {
+			inspector.removed(pp.path(left));
+		}
+	}
+	
+	public Record status(int rev1, int rev2) {
+		Record rv = new Record();
+		walk(rev1, rev2, rv);
+		return rv;
+	}
+	
+	/*package-local*/static String getOriginIfCopy(HgRepository hgRepo, String fname, Collection<String> originals, int originalChangelogRevision) {
+		HgDataFile df = hgRepo.getFileNode(fname);
+		while (df.isCopy()) {
+			Path original = df.getCopySourceName();
+			if (originals.contains(original.toString())) {
+				df = hgRepo.getFileNode(original);
+				int changelogRevision = df.getChangesetLocalRevision(0);
+				if (changelogRevision <= originalChangelogRevision) {
+					// copy/rename source was known prior to rev1 
+					// (both r1Files.contains is true and original was created earlier than rev1)
+					// without r1Files.contains changelogRevision <= rev1 won't suffice as the file
+					// might get removed somewhere in between (changelogRevision < R < rev1)
+					return original.toString();
+				}
+				break; // copy/rename done later
+			} 
+			df = hgRepo.getFileNode(original); // try more steps away
+		}
+		return null;
+	}
+
+	// XXX for r1..r2 status, only modified, added, removed (and perhaps, clean) make sense
+	// XXX Need to specify whether copy targets are in added or not (@see Inspector#copied above)
+	public static class Record implements HgStatusInspector {
+		private List<Path> modified, added, removed, clean, missing, unknown, ignored;
+		private Map<Path, Path> copied;
+		
+		private int startRev, endRev;
+		private HgStatusCollector statusHelper;
+		
+		// XXX StatusCollector may additionally initialize Record instance to speed lookup of changed file revisions
+		// here I need access to ManifestRevisionInspector via #raw(). Perhaps, non-static class (to get
+		// implicit reference to StatusCollector) may be better?
+		// Since users may want to reuse Record instance we've once created (and initialized), we need to  
+		// ensure functionality is correct for each/any call (#walk checks instanceof Record and fixes it up)
+		// Perhaps, distinct helper (sc.getRevisionHelper().nodeid(fname)) would be better, just not clear
+		// how to supply [start..end] values there easily
+		/*package-local*/void init(int startRevision, int endRevision, HgStatusCollector self) {
+			startRev = startRevision;
+			endRev = endRevision;
+			statusHelper = self;
+		}
+		
+		public Nodeid nodeidBeforeChange(Path fname) {
+			if (statusHelper == null || startRev == BAD_REVISION) {
+				return null;
+			}
+			if ((modified == null || !modified.contains(fname)) && (removed == null || !removed.contains(fname))) {
+				return null;
+			}
+			return statusHelper.raw(startRev).nodeid(fname.toString());
+		}
+		public Nodeid nodeidAfterChange(Path fname) {
+			if (statusHelper == null || endRev == BAD_REVISION) {
+				return null;
+			}
+			if ((modified == null || !modified.contains(fname)) && (added == null || !added.contains(fname))) {
+				return null;
+			}
+			return statusHelper.raw(endRev).nodeid(fname.toString());
+		}
+		
+		public List<Path> getModified() {
+			return proper(modified);
+		}
+
+		public List<Path> getAdded() {
+			return proper(added);
+		}
+
+		public List<Path> getRemoved() {
+			return proper(removed);
+		}
+
+		public Map<Path,Path> getCopied() {
+			if (copied == null) {
+				return Collections.emptyMap();
+			}
+			return Collections.unmodifiableMap(copied);
+		}
+
+		public List<Path> getClean() {
+			return proper(clean);
+		}
+
+		public List<Path> getMissing() {
+			return proper(missing);
+		}
+
+		public List<Path> getUnknown() {
+			return proper(unknown);
+		}
+
+		public List<Path> getIgnored() {
+			return proper(ignored);
+		}
+		
+		private List<Path> proper(List<Path> l) {
+			if (l == null) {
+				return Collections.emptyList();
+			}
+			return Collections.unmodifiableList(l);
+		}
+
+		//
+		//
+		
+		public void modified(Path fname) {
+			modified = doAdd(modified, fname);
+		}
+
+		public void added(Path fname) {
+			added = doAdd(added, fname);
+		}
+
+		public void copied(Path fnameOrigin, Path fnameAdded) {
+			if (copied == null) {
+				copied = new LinkedHashMap<Path, Path>();
+			}
+			added(fnameAdded);
+			copied.put(fnameAdded, fnameOrigin);
+		}
+
+		public void removed(Path fname) {
+			removed = doAdd(removed, fname);
+		}
+
+		public void clean(Path fname) {
+			clean = doAdd(clean, fname);
+		}
+
+		public void missing(Path fname) {
+			missing = doAdd(missing, fname);
+		}
+
+		public void unknown(Path fname) {
+			unknown = doAdd(unknown, fname);
+		}
+
+		public void ignored(Path fname) {
+			ignored = doAdd(ignored, fname);
+		}
+
+		private static List<Path> doAdd(List<Path> l, Path p) {
+			if (l == null) {
+				l = new LinkedList<Path>();
+			}
+			l.add(p);
+			return l;
+		}
+	}
+
+	/*package-local*/ static final class ManifestRevisionInspector implements HgManifest.Inspector {
+		private final TreeMap<String, Nodeid> idsMap;
+		private final TreeMap<String, String> flagsMap;
+
+		public ManifestRevisionInspector() {
+			idsMap = new TreeMap<String, Nodeid>();
+			flagsMap = new TreeMap<String, String>();
+		}
+		
+		public Collection<String> files() {
+			return idsMap.keySet();
+		}
+
+		public Nodeid nodeid(String fname) {
+			return idsMap.get(fname);
+		}
+
+		public String flags(String fname) {
+			return flagsMap.get(fname);
+		}
+
+		//
+
+		public boolean next(Nodeid nid, String fname, String flags) {
+			idsMap.put(fname, nid);
+			flagsMap.put(fname, flags);
+			return true;
+		}
+
+		public boolean end(int revision) {
+			// in fact, this class cares about single revision
+			return false; 
+		}
+
+		public boolean begin(int revision, Nodeid nid) {
+			return true;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Thu Jan 27 21:18:47 2011 +0100
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2011 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@svnkit.com
+ */
+package org.tmatesoft.hg.repo;
+
+import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgStatusCollector.ManifestRevisionInspector;
+import org.tmatesoft.hg.util.FileWalker;
+import org.tmatesoft.hg.util.PathPool;
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgWorkingCopyStatusCollector {
+
+	private final HgRepository repo;
+	private final FileWalker repoWalker;
+	private HgDirstate dirstate;
+	private HgStatusCollector baseRevisionCollector;
+	private PathPool pathPool;
+
+	public HgWorkingCopyStatusCollector(HgRepository hgRepo) {
+		this(hgRepo, hgRepo.createWorkingDirWalker());
+	}
+
+	HgWorkingCopyStatusCollector(HgRepository hgRepo, FileWalker hgRepoWalker) {
+		this.repo = hgRepo;
+		this.repoWalker = hgRepoWalker;
+	}
+	
+	/**
+	 * Optionally, supply a collector instance that may cache (or have already cached) base revision
+	 * @param sc may be null
+	 */
+	public void setBaseRevisionCollector(HgStatusCollector sc) {
+		baseRevisionCollector = sc;
+	}
+
+	/*package-local*/ PathPool getPathPool() {
+		if (pathPool == null) {
+			if (baseRevisionCollector == null) {
+				pathPool = new PathPool(new PathRewrite.Empty());
+			} else {
+				return baseRevisionCollector.getPathPool();
+			}
+		}
+		return pathPool;
+	}
+
+	public void setPathPool(PathPool pathPool) {
+		this.pathPool = pathPool;
+	}
+
+	
+	private HgDirstate getDirstate() {
+		if (dirstate == null) {
+			dirstate = repo.loadDirstate();
+		}
+		return dirstate;
+	}
+
+	// may be invoked few times
+	public void walk(int baseRevision, HgStatusInspector inspector) {
+		final HgIgnore hgIgnore = repo.getIgnore();
+		TreeSet<String> knownEntries = getDirstate().all();
+		final boolean isTipBase;
+		if (baseRevision == TIP) {
+			baseRevision = repo.getManifest().getRevisionCount() - 1;
+			isTipBase = true;
+		} else {
+			isTipBase = baseRevision == repo.getManifest().getRevisionCount() - 1;
+		}
+		HgStatusCollector.ManifestRevisionInspector collect = null;
+		Set<String> baseRevFiles = Collections.emptySet();
+		if (!isTipBase) {
+			if (baseRevisionCollector != null) {
+				collect = baseRevisionCollector.raw(baseRevision);
+			} else {
+				collect = new HgStatusCollector.ManifestRevisionInspector();
+				repo.getManifest().walk(baseRevision, baseRevision, collect);
+			}
+			baseRevFiles = new TreeSet<String>(collect.files());
+		}
+		if (inspector instanceof HgStatusCollector.Record) {
+			HgStatusCollector sc = baseRevisionCollector == null ? new HgStatusCollector(repo) : baseRevisionCollector;
+			((HgStatusCollector.Record) inspector).init(baseRevision, BAD_REVISION, sc);
+		}
+		repoWalker.reset();
+		final PathPool pp = getPathPool();
+		while (repoWalker.hasNext()) {
+			repoWalker.next();
+			String fname = repoWalker.name();
+			File f = repoWalker.file();
+			if (hgIgnore.isIgnored(fname)) {
+				inspector.ignored(pp.path(fname));
+			} else if (knownEntries.remove(fname)) {
+				// modified, added, removed, clean
+				if (collect != null) { // need to check against base revision, not FS file
+					checkLocalStatusAgainstBaseRevision(baseRevFiles, collect, baseRevision, fname, f, inspector);
+					baseRevFiles.remove(fname);
+				} else {
+					checkLocalStatusAgainstFile(fname, f, inspector);
+				}
+			} else {
+				inspector.unknown(pp.path(fname));
+			}
+		}
+		if (collect != null) {
+			for (String r : baseRevFiles) {
+				inspector.removed(pp.path(r));
+			}
+		}
+		for (String m : knownEntries) {
+			// missing known file from a working dir  
+			if (getDirstate().checkRemoved(m) == null) {
+				// not removed from the repository = 'deleted'  
+				inspector.missing(pp.path(m));
+			} else {
+				// removed from the repo
+				// if we check against non-tip revision, do not report files that were added past that revision and now removed.
+				if (collect == null || baseRevFiles.contains(m)) {
+					inspector.removed(pp.path(m));
+				}
+			}
+		}
+	}
+
+	public HgStatusCollector.Record status(int baseRevision) {
+		HgStatusCollector.Record rv = new HgStatusCollector.Record();
+		walk(baseRevision, rv);
+		return rv;
+	}
+
+	//********************************************
+
+	
+	private void checkLocalStatusAgainstFile(String fname, File f, HgStatusInspector inspector) {
+		HgDirstate.Record r;
+		if ((r = getDirstate().checkNormal(fname)) != null) {
+			// either clean or modified
+			if (f.lastModified() / 1000 == r.time && r.size == f.length()) {
+				inspector.clean(getPathPool().path(fname));
+			} else {
+				// FIXME check actual content to avoid false modified files
+				inspector.modified(getPathPool().path(fname));
+			}
+		} else if ((r = getDirstate().checkAdded(fname)) != null) {
+			if (r.name2 == null) {
+				inspector.added(getPathPool().path(fname));
+			} else {
+				inspector.copied(getPathPool().path(r.name2), getPathPool().path(fname));
+			}
+		} else if ((r = getDirstate().checkRemoved(fname)) != null) {
+			inspector.removed(getPathPool().path(fname));
+		} else if ((r = getDirstate().checkMerged(fname)) != null) {
+			inspector.modified(getPathPool().path(fname));
+		}
+	}
+	
+	// XXX refactor checkLocalStatus methods in more OO way
+	private void checkLocalStatusAgainstBaseRevision(Set<String> baseRevNames, ManifestRevisionInspector collect, int baseRevision, String fname, File f, HgStatusInspector inspector) {
+		// fname is in the dirstate, either Normal, Added, Removed or Merged
+		Nodeid nid1 = collect.nodeid(fname);
+		String flags = collect.flags(fname);
+		HgDirstate.Record r;
+		if (nid1 == null) {
+			// normal: added?
+			// added: not known at the time of baseRevision, shall report
+			// merged: was not known, report as added?
+			if ((r = getDirstate().checkNormal(fname)) != null) {
+				String origin = HgStatusCollector.getOriginIfCopy(repo, fname, baseRevNames, baseRevision);
+				if (origin != null) {
+					inspector.copied(getPathPool().path(origin), getPathPool().path(fname));
+					return;
+				}
+			} else if ((r = getDirstate().checkAdded(fname)) != null) {
+				if (r.name2 != null && baseRevNames.contains(r.name2)) {
+					baseRevNames.remove(r.name2); // XXX surely I shall not report rename source as Removed?
+					inspector.copied(getPathPool().path(r.name2), getPathPool().path(fname));
+					return;
+				}
+				// fall-through, report as added
+			} else if (getDirstate().checkRemoved(fname) != null) {
+				// removed: removed file was not known at the time of baseRevision, and we should not report it as removed
+				return;
+			}
+			inspector.added(getPathPool().path(fname));
+		} else {
+			// was known; check whether clean or modified
+			// when added - seems to be the case of a file added once again, hence need to check if content is different
+			if ((r = getDirstate().checkNormal(fname)) != null || (r = getDirstate().checkMerged(fname)) != null || (r = getDirstate().checkAdded(fname)) != null) {
+				// either clean or modified
+				HgDataFile fileNode = repo.getFileNode(fname);
+				final int lengthAtRevision = fileNode.length(nid1);
+				if (r.size /* XXX File.length() ?! */ != lengthAtRevision || flags != todoGenerateFlags(fname /*java.io.File*/)) {
+					inspector.modified(getPathPool().path(fname));
+				} else {
+					// check actual content to see actual changes
+					// XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison
+					if (areTheSame(f, fileNode.content(nid1))) {
+						inspector.clean(getPathPool().path(fname));
+					} else {
+						inspector.modified(getPathPool().path(fname));
+					}
+				}
+			}
+			// only those left in idsMap after processing are reported as removed 
+		}
+
+		// TODO think over if content comparison may be done more effectively by e.g. calculating nodeid for a local file and comparing it with nodeid from manifest
+		// we don't need to tell exact difference, hash should be enough to detect difference, and it doesn't involve reading historical file content, and it's relatively 
+		// cheap to calc hash on a file (no need to keep it completely in memory). OTOH, if I'm right that the next approach is used for nodeids: 
+		// changeset nodeid + hash(actual content) => entry (Nodeid) in the next Manifest
+		// then it's sufficient to check parents from dirstate, and if they do not match parents from file's baseRevision (non matching parents means different nodeids).
+		// The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean'
+	}
+
+	private static String todoGenerateFlags(String fname) {
+		// FIXME implement
+		return null;
+	}
+	private static boolean areTheSame(File f, byte[] data) {
+		try {
+			BufferedInputStream is = new BufferedInputStream(new FileInputStream(f));
+			int i = 0;
+			while (i < data.length && data[i] == is.read()) {
+				i++; // increment only for successful match, otherwise won't tell last byte in data was the same as read from the stream
+			}
+			return i == data.length && is.read() == -1; // although data length is expected to be the same (see caller), check that we reached EOF, no more data left.
+		} catch (IOException ex) {
+			ex.printStackTrace(); // log warn
+		}
+		return false;
+	}
+
+}
--- a/src/org/tmatesoft/hg/repo/StatusCollector.java	Thu Jan 27 21:15:21 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,374 +0,0 @@
-/*
- * Copyright (c) 2011 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@svnkit.com
- */
-package org.tmatesoft.hg.repo;
-
-import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.core.Path;
-import org.tmatesoft.hg.util.PathPool;
-import org.tmatesoft.hg.util.PathRewrite;
-
-
-/**
- * RevisionWalker?
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class StatusCollector {
-
-	private final HgRepository repo;
-	private final Map<Integer, ManifestRevisionInspector> cache; // sparse array, in fact
-	private PathPool pathPool;
-
-	public StatusCollector(HgRepository hgRepo) {
-		this.repo = hgRepo;
-		cache = new TreeMap<Integer, ManifestRevisionInspector>();
-		ManifestRevisionInspector emptyFakeState = new ManifestRevisionInspector();
-		emptyFakeState.begin(-1, null);
-		emptyFakeState.end(-1); // FIXME HgRepo.TIP == -1 as well, need to distinguish fake "prior to first" revision from "the very last" 
-		cache.put(-1, emptyFakeState);
-	}
-	
-	public HgRepository getRepo() {
-		return repo;
-	}
-	
-	private ManifestRevisionInspector get(int rev) {
-		ManifestRevisionInspector i = cache.get(rev);
-		if (i == null) {
-			i = new ManifestRevisionInspector();
-			cache.put(rev, i);
-			repo.getManifest().walk(rev, rev, i);
-		}
-		return i;
-	}
-	
-	/*package-local*/ ManifestRevisionInspector raw(int rev) {
-		return get(rev);
-	}
-	/*package-local*/ PathPool getPathPool() {
-		if (pathPool == null) {
-			pathPool = new PathPool(new PathRewrite.Empty());
-		}
-		return pathPool;
-	}
-
-	public void setPathPool(PathPool pathPool) {
-		this.pathPool = pathPool;
-	}
-		
-	
-	// hg status --change <rev>
-	public void change(int rev, HgStatusInspector inspector) {
-		int[] parents = new int[2];
-		repo.getChangelog().parents(rev, parents, null, null);
-		walk(parents[0], rev, inspector);
-	}
-
-	// I assume revision numbers are the same for changelog and manifest - here 
-	// user would like to pass changelog revision numbers, and I use them directly to walk manifest.
-	// if this assumption is wrong, fix this (lookup manifest revisions from changeset).
-	public void walk(int rev1, int rev2, HgStatusInspector inspector) {
-		if (rev1 == rev2) {
-			throw new IllegalArgumentException();
-		}
-		if (inspector == null) {
-			throw new IllegalArgumentException();
-		}
-		if (inspector instanceof Record) {
-			((Record) inspector).init(rev1, rev2, this);
-		}
-		if (rev1 == TIP) {
-			rev1 = repo.getManifest().getRevisionCount() - 1;
-		}
-		if (rev2 == TIP) {
-			rev2 = repo.getManifest().getRevisionCount() - 1; // XXX add Revlog.tip() func ? 
-		}
-		// in fact, rev1 and rev2 are often next (or close) to each other,
-		// thus, we can optimize Manifest reads here (manifest.walk(rev1, rev2))
-		ManifestRevisionInspector r1, r2 ;
-		if (!cache.containsKey(rev1) && !cache.containsKey(rev2) && Math.abs(rev1 - rev2) < 5 /*subjective equivalent of 'close enough'*/) {
-			int minRev = rev1 < rev2 ? rev1 : rev2;
-			int maxRev = minRev == rev1 ? rev2 : rev1;
-			if (minRev > 0) {
-				minRev--; // expand range a bit
-				// XXX perhaps, if revlog.baseRevision is cheap, shall expand minRev up to baseRevision
-				// which gonna be read anyway
-			}
-	
-			repo.getManifest().walk(minRev, maxRev, new HgManifest.Inspector() {
-				private ManifestRevisionInspector delegate;
-
-				public boolean begin(int revision, Nodeid nid) {
-					cache.put(revision, delegate = new ManifestRevisionInspector());
-					delegate.begin(revision, nid);
-					return true;
-				}
-
-				public boolean next(Nodeid nid, String fname, String flags) {
-					delegate.next(nid, fname, flags);
-					return true;
-				}
-				
-				public boolean end(int revision) {
-					delegate.end(revision);
-					delegate = null;
-					return true;
-				}
-			});
-		}
-		r1 = get(rev1);
-		r2 = get(rev2);
-
-		PathPool pp = getPathPool();
-
-		TreeSet<String> r1Files = new TreeSet<String>(r1.files());
-		for (String fname : r2.files()) {
-			if (r1Files.remove(fname)) {
-				Nodeid nidR1 = r1.nodeid(fname);
-				Nodeid nidR2 = r2.nodeid(fname);
-				String flagsR1 = r1.flags(fname);
-				String flagsR2 = r2.flags(fname);
-				if (nidR1.equals(nidR2) && ((flagsR2 == null && flagsR1 == null) || flagsR2.equals(flagsR1))) {
-					inspector.clean(pp.path(fname));
-				} else {
-					inspector.modified(pp.path(fname));
-				}
-			} else {
-				String copyOrigin = getOriginIfCopy(repo, fname, r1Files, rev1);
-				if (copyOrigin != null) {
-					inspector.copied(pp.path(copyOrigin), pp.path(fname));
-				} else {
-					inspector.added(pp.path(fname));
-				}
-			}
-		}
-		for (String left : r1Files) {
-			inspector.removed(pp.path(left));
-		}
-	}
-	
-	public Record status(int rev1, int rev2) {
-		Record rv = new Record();
-		walk(rev1, rev2, rv);
-		return rv;
-	}
-	
-	/*package-local*/static String getOriginIfCopy(HgRepository hgRepo, String fname, Collection<String> originals, int originalChangelogRevision) {
-		HgDataFile df = hgRepo.getFileNode(fname);
-		while (df.isCopy()) {
-			Path original = df.getCopySourceName();
-			if (originals.contains(original.toString())) {
-				df = hgRepo.getFileNode(original);
-				int changelogRevision = df.getChangesetLocalRevision(0);
-				if (changelogRevision <= originalChangelogRevision) {
-					// copy/rename source was known prior to rev1 
-					// (both r1Files.contains is true and original was created earlier than rev1)
-					// without r1Files.contains changelogRevision <= rev1 won't suffice as the file
-					// might get removed somewhere in between (changelogRevision < R < rev1)
-					return original.toString();
-				}
-				break; // copy/rename done later
-			} 
-			df = hgRepo.getFileNode(original); // try more steps away
-		}
-		return null;
-	}
-
-	// XXX for r1..r2 status, only modified, added, removed (and perhaps, clean) make sense
-	// XXX Need to specify whether copy targets are in added or not (@see Inspector#copied above)
-	public static class Record implements HgStatusInspector {
-		private List<Path> modified, added, removed, clean, missing, unknown, ignored;
-		private Map<Path, Path> copied;
-		
-		private int startRev, endRev;
-		private StatusCollector statusHelper;
-		
-		// XXX StatusCollector may additionally initialize Record instance to speed lookup of changed file revisions
-		// here I need access to ManifestRevisionInspector via #raw(). Perhaps, non-static class (to get
-		// implicit reference to StatusCollector) may be better?
-		// Since users may want to reuse Record instance we've once created (and initialized), we need to  
-		// ensure functionality is correct for each/any call (#walk checks instanceof Record and fixes it up)
-		// Perhaps, distinct helper (sc.getRevisionHelper().nodeid(fname)) would be better, just not clear
-		// how to supply [start..end] values there easily
-		/*package-local*/void init(int startRevision, int endRevision, StatusCollector self) {
-			startRev = startRevision;
-			endRev = endRevision;
-			statusHelper = self;
-		}
-		
-		public Nodeid nodeidBeforeChange(Path fname) {
-			if (statusHelper == null || startRev == BAD_REVISION) {
-				return null;
-			}
-			if ((modified == null || !modified.contains(fname)) && (removed == null || !removed.contains(fname))) {
-				return null;
-			}
-			return statusHelper.raw(startRev).nodeid(fname.toString());
-		}
-		public Nodeid nodeidAfterChange(Path fname) {
-			if (statusHelper == null || endRev == BAD_REVISION) {
-				return null;
-			}
-			if ((modified == null || !modified.contains(fname)) && (added == null || !added.contains(fname))) {
-				return null;
-			}
-			return statusHelper.raw(endRev).nodeid(fname.toString());
-		}
-		
-		public List<Path> getModified() {
-			return proper(modified);
-		}
-
-		public List<Path> getAdded() {
-			return proper(added);
-		}
-
-		public List<Path> getRemoved() {
-			return proper(removed);
-		}
-
-		public Map<Path,Path> getCopied() {
-			if (copied == null) {
-				return Collections.emptyMap();
-			}
-			return Collections.unmodifiableMap(copied);
-		}
-
-		public List<Path> getClean() {
-			return proper(clean);
-		}
-
-		public List<Path> getMissing() {
-			return proper(missing);
-		}
-
-		public List<Path> getUnknown() {
-			return proper(unknown);
-		}
-
-		public List<Path> getIgnored() {
-			return proper(ignored);
-		}
-		
-		private List<Path> proper(List<Path> l) {
-			if (l == null) {
-				return Collections.emptyList();
-			}
-			return Collections.unmodifiableList(l);
-		}
-
-		//
-		//
-		
-		public void modified(Path fname) {
-			modified = doAdd(modified, fname);
-		}
-
-		public void added(Path fname) {
-			added = doAdd(added, fname);
-		}
-
-		public void copied(Path fnameOrigin, Path fnameAdded) {
-			if (copied == null) {
-				copied = new LinkedHashMap<Path, Path>();
-			}
-			added(fnameAdded);
-			copied.put(fnameAdded, fnameOrigin);
-		}
-
-		public void removed(Path fname) {
-			removed = doAdd(removed, fname);
-		}
-
-		public void clean(Path fname) {
-			clean = doAdd(clean, fname);
-		}
-
-		public void missing(Path fname) {
-			missing = doAdd(missing, fname);
-		}
-
-		public void unknown(Path fname) {
-			unknown = doAdd(unknown, fname);
-		}
-
-		public void ignored(Path fname) {
-			ignored = doAdd(ignored, fname);
-		}
-
-		private static List<Path> doAdd(List<Path> l, Path p) {
-			if (l == null) {
-				l = new LinkedList<Path>();
-			}
-			l.add(p);
-			return l;
-		}
-	}
-
-	/*package-local*/ static final class ManifestRevisionInspector implements HgManifest.Inspector {
-		private final TreeMap<String, Nodeid> idsMap;
-		private final TreeMap<String, String> flagsMap;
-
-		public ManifestRevisionInspector() {
-			idsMap = new TreeMap<String, Nodeid>();
-			flagsMap = new TreeMap<String, String>();
-		}
-		
-		public Collection<String> files() {
-			return idsMap.keySet();
-		}
-
-		public Nodeid nodeid(String fname) {
-			return idsMap.get(fname);
-		}
-
-		public String flags(String fname) {
-			return flagsMap.get(fname);
-		}
-
-		//
-
-		public boolean next(Nodeid nid, String fname, String flags) {
-			idsMap.put(fname, nid);
-			flagsMap.put(fname, flags);
-			return true;
-		}
-
-		public boolean end(int revision) {
-			// in fact, this class cares about single revision
-			return false; 
-		}
-
-		public boolean begin(int revision, Nodeid nid) {
-			return true;
-		}
-	}
-
-}
--- a/src/org/tmatesoft/hg/repo/WorkingCopyStatusCollector.java	Thu Jan 27 21:15:21 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,263 +0,0 @@
-/*
- * Copyright (c) 2011 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@svnkit.com
- */
-package org.tmatesoft.hg.repo;
-
-import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
-import static org.tmatesoft.hg.repo.HgRepository.TIP;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.repo.StatusCollector.ManifestRevisionInspector;
-import org.tmatesoft.hg.util.FileWalker;
-import org.tmatesoft.hg.util.PathPool;
-import org.tmatesoft.hg.util.PathRewrite;
-
-/**
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class WorkingCopyStatusCollector {
-
-	private final HgRepository repo;
-	private final FileWalker repoWalker;
-	private HgDirstate dirstate;
-	private StatusCollector baseRevisionCollector;
-	private PathPool pathPool;
-
-	public WorkingCopyStatusCollector(HgRepository hgRepo) {
-		this(hgRepo, hgRepo.createWorkingDirWalker());
-	}
-
-	WorkingCopyStatusCollector(HgRepository hgRepo, FileWalker hgRepoWalker) {
-		this.repo = hgRepo;
-		this.repoWalker = hgRepoWalker;
-	}
-	
-	/**
-	 * Optionally, supply a collector instance that may cache (or have already cached) base revision
-	 * @param sc may be null
-	 */
-	public void setBaseRevisionCollector(StatusCollector sc) {
-		baseRevisionCollector = sc;
-	}
-
-	/*package-local*/ PathPool getPathPool() {
-		if (pathPool == null) {
-			if (baseRevisionCollector == null) {
-				pathPool = new PathPool(new PathRewrite.Empty());
-			} else {
-				return baseRevisionCollector.getPathPool();
-			}
-		}
-		return pathPool;
-	}
-
-	public void setPathPool(PathPool pathPool) {
-		this.pathPool = pathPool;
-	}
-
-	
-	private HgDirstate getDirstate() {
-		if (dirstate == null) {
-			dirstate = repo.loadDirstate();
-		}
-		return dirstate;
-	}
-
-	// may be invoked few times
-	public void walk(int baseRevision, HgStatusInspector inspector) {
-		final HgIgnore hgIgnore = repo.getIgnore();
-		TreeSet<String> knownEntries = getDirstate().all();
-		final boolean isTipBase;
-		if (baseRevision == TIP) {
-			baseRevision = repo.getManifest().getRevisionCount() - 1;
-			isTipBase = true;
-		} else {
-			isTipBase = baseRevision == repo.getManifest().getRevisionCount() - 1;
-		}
-		StatusCollector.ManifestRevisionInspector collect = null;
-		Set<String> baseRevFiles = Collections.emptySet();
-		if (!isTipBase) {
-			if (baseRevisionCollector != null) {
-				collect = baseRevisionCollector.raw(baseRevision);
-			} else {
-				collect = new StatusCollector.ManifestRevisionInspector();
-				repo.getManifest().walk(baseRevision, baseRevision, collect);
-			}
-			baseRevFiles = new TreeSet<String>(collect.files());
-		}
-		if (inspector instanceof StatusCollector.Record) {
-			StatusCollector sc = baseRevisionCollector == null ? new StatusCollector(repo) : baseRevisionCollector;
-			((StatusCollector.Record) inspector).init(baseRevision, BAD_REVISION, sc);
-		}
-		repoWalker.reset();
-		final PathPool pp = getPathPool();
-		while (repoWalker.hasNext()) {
-			repoWalker.next();
-			String fname = repoWalker.name();
-			File f = repoWalker.file();
-			if (hgIgnore.isIgnored(fname)) {
-				inspector.ignored(pp.path(fname));
-			} else if (knownEntries.remove(fname)) {
-				// modified, added, removed, clean
-				if (collect != null) { // need to check against base revision, not FS file
-					checkLocalStatusAgainstBaseRevision(baseRevFiles, collect, baseRevision, fname, f, inspector);
-					baseRevFiles.remove(fname);
-				} else {
-					checkLocalStatusAgainstFile(fname, f, inspector);
-				}
-			} else {
-				inspector.unknown(pp.path(fname));
-			}
-		}
-		if (collect != null) {
-			for (String r : baseRevFiles) {
-				inspector.removed(pp.path(r));
-			}
-		}
-		for (String m : knownEntries) {
-			// missing known file from a working dir  
-			if (getDirstate().checkRemoved(m) == null) {
-				// not removed from the repository = 'deleted'  
-				inspector.missing(pp.path(m));
-			} else {
-				// removed from the repo
-				// if we check against non-tip revision, do not report files that were added past that revision and now removed.
-				if (collect == null || baseRevFiles.contains(m)) {
-					inspector.removed(pp.path(m));
-				}
-			}
-		}
-	}
-
-	public StatusCollector.Record status(int baseRevision) {
-		StatusCollector.Record rv = new StatusCollector.Record();
-		walk(baseRevision, rv);
-		return rv;
-	}
-
-	//********************************************
-
-	
-	private void checkLocalStatusAgainstFile(String fname, File f, HgStatusInspector inspector) {
-		HgDirstate.Record r;
-		if ((r = getDirstate().checkNormal(fname)) != null) {
-			// either clean or modified
-			if (f.lastModified() / 1000 == r.time && r.size == f.length()) {
-				inspector.clean(getPathPool().path(fname));
-			} else {
-				// FIXME check actual content to avoid false modified files
-				inspector.modified(getPathPool().path(fname));
-			}
-		} else if ((r = getDirstate().checkAdded(fname)) != null) {
-			if (r.name2 == null) {
-				inspector.added(getPathPool().path(fname));
-			} else {
-				inspector.copied(getPathPool().path(r.name2), getPathPool().path(fname));
-			}
-		} else if ((r = getDirstate().checkRemoved(fname)) != null) {
-			inspector.removed(getPathPool().path(fname));
-		} else if ((r = getDirstate().checkMerged(fname)) != null) {
-			inspector.modified(getPathPool().path(fname));
-		}
-	}
-	
-	// XXX refactor checkLocalStatus methods in more OO way
-	private void checkLocalStatusAgainstBaseRevision(Set<String> baseRevNames, ManifestRevisionInspector collect, int baseRevision, String fname, File f, HgStatusInspector inspector) {
-		// fname is in the dirstate, either Normal, Added, Removed or Merged
-		Nodeid nid1 = collect.nodeid(fname);
-		String flags = collect.flags(fname);
-		HgDirstate.Record r;
-		if (nid1 == null) {
-			// normal: added?
-			// added: not known at the time of baseRevision, shall report
-			// merged: was not known, report as added?
-			if ((r = getDirstate().checkNormal(fname)) != null) {
-				String origin = StatusCollector.getOriginIfCopy(repo, fname, baseRevNames, baseRevision);
-				if (origin != null) {
-					inspector.copied(getPathPool().path(origin), getPathPool().path(fname));
-					return;
-				}
-			} else if ((r = getDirstate().checkAdded(fname)) != null) {
-				if (r.name2 != null && baseRevNames.contains(r.name2)) {
-					baseRevNames.remove(r.name2); // XXX surely I shall not report rename source as Removed?
-					inspector.copied(getPathPool().path(r.name2), getPathPool().path(fname));
-					return;
-				}
-				// fall-through, report as added
-			} else if (getDirstate().checkRemoved(fname) != null) {
-				// removed: removed file was not known at the time of baseRevision, and we should not report it as removed
-				return;
-			}
-			inspector.added(getPathPool().path(fname));
-		} else {
-			// was known; check whether clean or modified
-			// when added - seems to be the case of a file added once again, hence need to check if content is different
-			if ((r = getDirstate().checkNormal(fname)) != null || (r = getDirstate().checkMerged(fname)) != null || (r = getDirstate().checkAdded(fname)) != null) {
-				// either clean or modified
-				HgDataFile fileNode = repo.getFileNode(fname);
-				final int lengthAtRevision = fileNode.length(nid1);
-				if (r.size /* XXX File.length() ?! */ != lengthAtRevision || flags != todoGenerateFlags(fname /*java.io.File*/)) {
-					inspector.modified(getPathPool().path(fname));
-				} else {
-					// check actual content to see actual changes
-					// XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison
-					if (areTheSame(f, fileNode.content(nid1))) {
-						inspector.clean(getPathPool().path(fname));
-					} else {
-						inspector.modified(getPathPool().path(fname));
-					}
-				}
-			}
-			// only those left in idsMap after processing are reported as removed 
-		}
-
-		// TODO think over if content comparison may be done more effectively by e.g. calculating nodeid for a local file and comparing it with nodeid from manifest
-		// we don't need to tell exact difference, hash should be enough to detect difference, and it doesn't involve reading historical file content, and it's relatively 
-		// cheap to calc hash on a file (no need to keep it completely in memory). OTOH, if I'm right that the next approach is used for nodeids: 
-		// changeset nodeid + hash(actual content) => entry (Nodeid) in the next Manifest
-		// then it's sufficient to check parents from dirstate, and if they do not match parents from file's baseRevision (non matching parents means different nodeids).
-		// The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean'
-	}
-
-	private static String todoGenerateFlags(String fname) {
-		// FIXME implement
-		return null;
-	}
-	private static boolean areTheSame(File f, byte[] data) {
-		try {
-			BufferedInputStream is = new BufferedInputStream(new FileInputStream(f));
-			int i = 0;
-			while (i < data.length && data[i] == is.read()) {
-				i++; // increment only for successful match, otherwise won't tell last byte in data was the same as read from the stream
-			}
-			return i == data.length && is.read() == -1; // although data length is expected to be the same (see caller), check that we reached EOF, no more data left.
-		} catch (IOException ex) {
-			ex.printStackTrace(); // log warn
-		}
-		return false;
-	}
-
-}
--- a/test/org/tmatesoft/hg/test/StatusOutputParser.java	Thu Jan 27 21:15:21 2011 +0100
+++ b/test/org/tmatesoft/hg/test/StatusOutputParser.java	Thu Jan 27 21:18:47 2011 +0100
@@ -24,7 +24,7 @@
 import java.util.regex.Pattern;
 
 import org.tmatesoft.hg.core.Path;
-import org.tmatesoft.hg.repo.StatusCollector;
+import org.tmatesoft.hg.repo.HgStatusCollector;
 import org.tmatesoft.hg.util.PathPool;
 import org.tmatesoft.hg.util.PathRewrite;
 
@@ -38,7 +38,7 @@
 	private final Pattern pattern;
 	// although using StatusCollector.Record is not really quite honest for testing,
 	// it's deemed acceptable as long as that class is primitive 'collect all results'
-	private StatusCollector.Record result = new StatusCollector.Record();
+	private HgStatusCollector.Record result = new HgStatusCollector.Record();
 	private final PathPool pathHelper;
 
 	public StatusOutputParser() {
@@ -59,7 +59,7 @@
 	}
 
 	public void reset() {
-		result = new StatusCollector.Record();
+		result = new HgStatusCollector.Record();
 	}
 
 	public void parse(CharSequence seq) {
--- a/test/org/tmatesoft/hg/test/TestStatus.java	Thu Jan 27 21:15:21 2011 +0100
+++ b/test/org/tmatesoft/hg/test/TestStatus.java	Thu Jan 27 21:18:47 2011 +0100
@@ -27,8 +27,8 @@
 import org.tmatesoft.hg.core.StatusCommand;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.Lookup;
-import org.tmatesoft.hg.repo.StatusCollector;
-import org.tmatesoft.hg.repo.WorkingCopyStatusCollector;
+import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
 
 
 /**
@@ -56,10 +56,10 @@
 	}
 	
 	public void testLowLevel() throws Exception {
-		final WorkingCopyStatusCollector wcc = new WorkingCopyStatusCollector(repo);
+		final HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(repo);
 		statusParser.reset();
 		eh.run("hg", "status", "-A");
-		StatusCollector.Record r = wcc.status(HgRepository.TIP);
+		HgStatusCollector.Record r = wcc.status(HgRepository.TIP);
 		report("hg status -A", r, statusParser);
 		//
 		statusParser.reset();
@@ -70,35 +70,35 @@
 		//
 		statusParser.reset();
 		eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
-		r = new StatusCollector.Record();
-		new StatusCollector(repo).change(revision, r);
+		r = new HgStatusCollector.Record();
+		new HgStatusCollector(repo).change(revision, r);
 		report("status -A --change " + revision, r, statusParser);
 		//
 		statusParser.reset();
 		int rev2 = 80;
 		final String range = String.valueOf(revision) + ":" + String.valueOf(rev2);
 		eh.run("hg", "status", "-A", "--rev", range);
-		r = new StatusCollector(repo).status(revision, rev2);
+		r = new HgStatusCollector(repo).status(revision, rev2);
 		report("Status -A -rev " + range, r, statusParser);
 	}
 	
 	public void testStatusCommand() throws Exception {
 		final StatusCommand sc = new StatusCommand(repo).all();
-		StatusCollector.Record r;
+		HgStatusCollector.Record r;
 		statusParser.reset();
 		eh.run("hg", "status", "-A");
-		sc.execute(r = new StatusCollector.Record());
+		sc.execute(r = new HgStatusCollector.Record());
 		report("hg status -A", r, statusParser);
 		//
 		statusParser.reset();
 		int revision = 3;
 		eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
-		sc.base(revision).execute(r = new StatusCollector.Record());
+		sc.base(revision).execute(r = new HgStatusCollector.Record());
 		report("status -A --rev " + revision, r, statusParser);
 		//
 		statusParser.reset();
 		eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
-		sc.base(TIP).revision(revision).execute(r = new StatusCollector.Record());
+		sc.base(TIP).revision(revision).execute(r = new HgStatusCollector.Record());
 		report("status -A --change " + revision, r, statusParser);
 		
 		// TODO check not -A, but defaults()/custom set of modifications 
@@ -112,7 +112,7 @@
 		 */
 	}
 	
-	private static void report(String what, StatusCollector.Record r, StatusOutputParser statusParser) {
+	private static void report(String what, HgStatusCollector.Record r, StatusOutputParser statusParser) {
 		System.out.println(">>>" + what);
 		reportNotEqual("MODIFIED", r.getModified(), statusParser.getModified());
 		reportNotEqual("ADDED", r.getAdded(), statusParser.getAdded());