changeset 526:2f9ed6bcefa2

Initial support for Revert command with accompanying minor refactoring
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 15 Jan 2013 17:07:19 +0100
parents 0be5be8d57e9
children 47b7bedf0569
files build.xml cmdline/org/tmatesoft/hg/console/Main.java src/org/tmatesoft/hg/core/HgCheckoutCommand.java src/org/tmatesoft/hg/core/HgIncomingCommand.java src/org/tmatesoft/hg/core/HgLogCommand.java src/org/tmatesoft/hg/core/HgOutgoingCommand.java src/org/tmatesoft/hg/core/HgRevertCommand.java src/org/tmatesoft/hg/core/HgStatusCommand.java src/org/tmatesoft/hg/internal/DirstateBuilder.java src/org/tmatesoft/hg/internal/DirstateReader.java src/org/tmatesoft/hg/internal/EncodingHelper.java src/org/tmatesoft/hg/internal/Internals.java src/org/tmatesoft/hg/internal/WorkingDirFileWriter.java src/org/tmatesoft/hg/repo/HgBundle.java src/org/tmatesoft/hg/repo/HgDirstate.java src/org/tmatesoft/hg/repo/HgLookup.java src/org/tmatesoft/hg/repo/HgRepository.java src/org/tmatesoft/hg/repo/HgSubrepoLocation.java test/org/tmatesoft/hg/test/LogOutputParser.java test/org/tmatesoft/hg/test/TestCheckout.java test/org/tmatesoft/hg/test/TestRevert.java
diffstat 21 files changed, 715 insertions(+), 237 deletions(-) [+]
line wrap: on
line diff
--- a/build.xml	Fri Jan 11 18:12:39 2013 +0100
+++ b/build.xml	Tue Jan 15 17:07:19 2013 +0100
@@ -101,6 +101,8 @@
 			<test name="org.tmatesoft.hg.test.TestMqExtension" />
 			<test name="org.tmatesoft.hg.test.TestFileFlags" />
 			<test name="org.tmatesoft.hg.test.TestCatCommand" />
+			<test name="org.tmatesoft.hg.test.TestRevert" />
+			<test name="org.tmatesoft.hg.test.TestCheckout" />
 		</junit>
 	</target>
 
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/cmdline/org/tmatesoft/hg/console/Main.java	Tue Jan 15 17:07:19 2013 +0100
@@ -36,6 +36,7 @@
 import org.tmatesoft.hg.core.HgLogCommand;
 import org.tmatesoft.hg.core.HgManifestCommand;
 import org.tmatesoft.hg.core.HgManifestHandler;
+import org.tmatesoft.hg.core.HgRevertCommand;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.BasicSessionContext;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
@@ -99,7 +100,8 @@
 
 	public static void main(String[] args) throws Exception {
 		Main m = new Main(args);
-		m.testCheckout();
+		m.testRevert();
+//		m.testCheckout();
 //		m.tryExtensions();
 //		m.dumpBookmarks();
 //		m.readConfigFile();
@@ -125,6 +127,11 @@
 //		m.bunchOfTests();
 	}
 	
+	private void testRevert() throws Exception {
+		HgRevertCommand cmd = new HgRevertCommand(hgRepo);
+		cmd.file(Path.create("a.txt")).execute();
+	}
+	
 	private void testCheckout() throws Exception {
 		HgCheckoutCommand coCmd = new HgCheckoutCommand(hgRepo);
 		coCmd.changeset(17).execute();
--- a/src/org/tmatesoft/hg/core/HgCheckoutCommand.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/core/HgCheckoutCommand.java	Tue Jan 15 17:07:19 2013 +0100
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.core;
 
+import static org.tmatesoft.hg.repo.HgRepositoryFiles.Dirstate;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -27,11 +29,10 @@
 import org.tmatesoft.hg.internal.WorkingDirFileWriter;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgInvalidRevisionException;
-import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgManifest;
+import org.tmatesoft.hg.repo.HgManifest.Flags;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgRuntimeException;
-import org.tmatesoft.hg.repo.HgManifest.Flags;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.Path;
 
@@ -93,61 +94,83 @@
 	 * @throws CancelledException
 	 */
 	public void execute() throws HgException, CancelledException {
-		Internals internalRepo = Internals.getInstance(repo);
-		// remove tracked files from wd (perhaps, just forget 'Added'?)
-		// TODO
-		final DirstateBuilder dirstateBuilder = new DirstateBuilder(internalRepo.buildFileNameEncodingHelper());
-		final Exception[] failure = new Exception[1];
-		HgManifest.Inspector worker = new HgManifest.Inspector() {
-			
-			public boolean next(Nodeid nid, Path fname, Flags flags) {
-				try {
-					HgDataFile df = repo.getFileNode(fname);
-					int fileRevIndex = df.getRevisionIndex(nid);
-					// check out files based on manifest
-					// FIXME links!
-					WorkingDirFileWriter workingDirWriter = new WorkingDirFileWriter(repo);
-					workingDirWriter.processFile(df, fileRevIndex);
-					// new dirstate based on manifest
-					dirstateBuilder.recordNormal(fname, flags, workingDirWriter.bytesWritten());
+		try {
+			Internals internalRepo = Internals.getInstance(repo);
+			// FIXME remove tracked files from wd (perhaps, just forget 'Added'?)
+			// TODO
+			final DirstateBuilder dirstateBuilder = new DirstateBuilder(internalRepo);
+			final CheckoutWorker worker = new CheckoutWorker(internalRepo);
+			HgManifest.Inspector insp = new HgManifest.Inspector() {
+				
+				public boolean next(Nodeid nid, Path fname, Flags flags) {
+					if (worker.next(nid, fname, flags)) {
+						// new dirstate based on manifest
+						dirstateBuilder.recordNormal(fname, flags, worker.getLastWrittenFileSize());
+						return true;
+					}
+					return false;
+				}
+				
+				public boolean end(int manifestRevision) {
+					return false;
+				}
+				
+				public boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision) {
 					return true;
-				} catch (IOException ex) {
-					failure[0] = ex;
-				} catch (HgRuntimeException ex) {
-					failure[0] = ex;
 				}
-				return false;
-			}
-			
-			public boolean end(int manifestRevision) {
-				return false;
+			};
+			dirstateBuilder.parents(repo.getChangelog().getRevision(revisionToCheckout), null);
+			repo.getManifest().walk(revisionToCheckout, revisionToCheckout, insp);
+			worker.checkFailed();
+			File dirstateFile = internalRepo.getRepositoryFile(Dirstate);
+			try {
+				FileChannel dirstate = new FileOutputStream(dirstateFile).getChannel();
+				dirstateBuilder.serialize(dirstate);
+				dirstate.close();
+			} catch (IOException ex) {
+				throw new HgIOException("Can't write down new directory state", ex, dirstateFile);
 			}
-			
-			public boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision) {
+			// FIXME write down branch file
+		} catch (HgRuntimeException ex) {
+			throw new HgLibraryFailureException(ex);
+		}
+	}
+
+	static class CheckoutWorker {
+		private final Internals hgRepo;
+		private HgException failure;
+		private int lastWrittenFileSize;
+		
+		CheckoutWorker(Internals implRepo) {
+			hgRepo = implRepo;
+		}
+		
+		public boolean next(Nodeid nid, Path fname, Flags flags) {
+			try {
+				HgDataFile df = hgRepo.getRepo().getFileNode(fname);
+				int fileRevIndex = df.getRevisionIndex(nid);
+				// check out files based on manifest
+				// FIXME links!
+				WorkingDirFileWriter workingDirWriter = new WorkingDirFileWriter(hgRepo);
+				workingDirWriter.processFile(df, fileRevIndex);
+				lastWrittenFileSize = workingDirWriter.bytesWritten();
 				return true;
-			}
-		};
-		dirstateBuilder.parents(repo.getChangelog().getRevision(revisionToCheckout), null);
-		repo.getManifest().walk(revisionToCheckout, revisionToCheckout, worker);
-		if (failure[0] != null) {
-			if (failure[0] instanceof IOException) {
-				throw new HgIOException("Failed to write down file revision", failure[0], /*FIXME file*/null);
-			}
-			if (failure[0] instanceof HgRuntimeException) {
-				throw new HgLibraryFailureException((HgRuntimeException) failure[0]);
+			} catch (IOException ex) {
+				failure = new HgIOException("Failed to write down file revision", ex, /*FIXME file*/null);
+			} catch (HgRuntimeException ex) {
+				failure = new HgLibraryFailureException(ex);
 			}
-			HgInvalidStateException e = new HgInvalidStateException("Unexpected exception");
-			e.initCause(failure[0]);
-			throw e;
+			return false;
 		}
-		File dirstateFile = internalRepo.getFileFromRepoDir("dirstate");
-		try {
-			FileChannel dirstate = new FileOutputStream(dirstateFile).getChannel();
-			dirstateBuilder.serialize(dirstate);
-			dirstate.close();
-		} catch (IOException ex) {
-			throw new HgIOException("Can't write down new directory state", ex, dirstateFile);
+		
+		public int getLastWrittenFileSize() {
+			return lastWrittenFileSize;
 		}
-		// FIXME write down branch file
-	}
+		
+		public void checkFailed() throws HgException {
+			if (failure != null) {
+				throw failure;
+			}
+		}
+	};
 }
--- a/src/org/tmatesoft/hg/core/HgIncomingCommand.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/core/HgIncomingCommand.java	Tue Jan 15 17:07:19 2013 +0100
@@ -25,6 +25,7 @@
 import java.util.Set;
 import java.util.TreeSet;
 
+import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.internal.RepositoryComparator;
 import org.tmatesoft.hg.internal.RepositoryComparator.BranchChain;
 import org.tmatesoft.hg.repo.HgBundle;
@@ -95,7 +96,7 @@
 	 */
 	public HgIncomingCommand subrepo(boolean include) {
 		includeSubrepo = include;
-		throw HgRepository.notImplemented();
+		throw Internals.notImplemented();
 	}
 
 	/**
--- a/src/org/tmatesoft/hg/core/HgLogCommand.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/core/HgLogCommand.java	Tue Jan 15 17:07:19 2013 +0100
@@ -38,6 +38,7 @@
 import org.tmatesoft.hg.internal.BatchRangeHelper;
 import org.tmatesoft.hg.internal.IntMap;
 import org.tmatesoft.hg.internal.IntVector;
+import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.internal.Lifecycle;
 import org.tmatesoft.hg.internal.LifecycleProxy;
 import org.tmatesoft.hg.repo.HgChangelog;
@@ -143,7 +144,7 @@
 		this.date = date;
 		// TODO post-1.0 implement
 		// isSet(field) - false => don't use in detection of 'same date'
-		throw HgRepository.notImplemented();
+		throw Internals.notImplemented();
 	}
 	
 	/**
--- a/src/org/tmatesoft/hg/core/HgOutgoingCommand.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/core/HgOutgoingCommand.java	Tue Jan 15 17:07:19 2013 +0100
@@ -20,6 +20,7 @@
 import java.util.Set;
 import java.util.TreeSet;
 
+import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.internal.RepositoryComparator;
 import org.tmatesoft.hg.repo.HgChangelog;
 import org.tmatesoft.hg.repo.HgInvalidControlFileException;
@@ -88,7 +89,7 @@
 	 */
 	public HgOutgoingCommand subrepo(boolean include) {
 		includeSubrepo = include;
-		throw HgRepository.notImplemented();
+		throw Internals.notImplemented();
 	}
 
 	/**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgRevertCommand.java	Tue Jan 15 17:07:19 2013 +0100
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2013 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.core;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.tmatesoft.hg.internal.DirstateBuilder;
+import org.tmatesoft.hg.internal.DirstateReader;
+import org.tmatesoft.hg.internal.Experimental;
+import org.tmatesoft.hg.internal.Internals;
+import org.tmatesoft.hg.repo.HgInvalidRevisionException;
+import org.tmatesoft.hg.repo.HgManifest;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRuntimeException;
+import org.tmatesoft.hg.repo.HgManifest.Flags;
+import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * WORK IN PROGRESS.
+ * 
+ * Restore files to their checkout state, 'hg revert' counterpart.
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@Experimental(reason="Work in progress")
+public class HgRevertCommand extends HgAbstractCommand<HgRevertCommand> {
+
+	private final HgRepository repo;
+	private final Set<Path> files = new LinkedHashSet<Path>();
+	private int changesetToCheckout = HgRepository.WORKING_COPY; // XXX WORKING_COPY_PARENT, in fact
+	private boolean keepOriginal = true;
+
+	public HgRevertCommand(HgRepository hgRepo) {
+		repo = hgRepo;
+	}
+
+	/**
+	 * Additive
+	 * 
+	 * @param paths files to revert
+	 * @return <code>this</code> for convenience
+	 */
+	public HgRevertCommand file(Path... paths) {
+		files.addAll(Arrays.asList(paths));
+		return this;
+	}
+
+	/**
+	 * Revert the given files to their states as of a specific revision
+	 * 
+	 * @param changesetRevIndex
+	 * @return <code>this</code> for convenience
+	 * @throws HgBadArgumentException
+	 */
+	public HgRevertCommand changeset(int changesetRevIndex) throws HgBadArgumentException {
+		int lastCsetIndex = repo.getChangelog().getLastRevision();
+		if (changesetRevIndex < 0 || changesetRevIndex > lastCsetIndex) {
+			throw new HgBadArgumentException(String.format("Bad revision index %d, value from [0..%d] expected", changesetRevIndex, lastCsetIndex), null).setRevisionIndex(changesetRevIndex);
+		}
+		changesetToCheckout = changesetRevIndex;
+		return this;
+	}
+	
+	/**
+	 * Handy supplement to {@link #changeset(int)}
+	 * 
+	 * @param revision
+	 * @return <code>this</code> for convenience
+	 * @throws HgBadArgumentException
+	 */
+	public HgRevertCommand changeset(Nodeid revision) throws HgBadArgumentException {
+		try {
+			return changeset(repo.getChangelog().getRevisionIndex(revision));
+		} catch (HgInvalidRevisionException ex) {
+			throw new HgBadArgumentException("Can't find revision", ex).setRevision(revision);
+		}
+	}
+	
+	// TODO keepOriginal() to save .orig
+
+	/**
+	 * Perform the back out for the given files
+	 * 
+	 * @throws HgIOException 
+	 * @throws HgException
+	 * @throws CancelledException
+	 */
+	public void execute() throws HgException, CancelledException {
+		try {
+			final int csetRevision;
+			if (changesetToCheckout == HgRepository.WORKING_COPY) {
+				csetRevision = repo.getChangelog().getRevisionIndex(repo.getWorkingCopyParents().first());
+			} else {
+				csetRevision = changesetToCheckout;
+			}
+			Internals implRepo = Internals.getInstance(repo);
+			final DirstateBuilder dirstateBuilder = new DirstateBuilder(implRepo);
+			dirstateBuilder.fillFrom(new DirstateReader(implRepo, new Path.SimpleSource()));
+			final HgCheckoutCommand.CheckoutWorker worker = new HgCheckoutCommand.CheckoutWorker(implRepo);
+			
+			HgManifest.Inspector insp = new HgManifest.Inspector() {
+				
+				public boolean next(Nodeid nid, Path fname, Flags flags) {
+					if (worker.next(nid, fname, flags)) {
+						dirstateBuilder.recordUncertain(fname);
+						return true;
+					}
+					return false;
+				}
+				
+				public boolean end(int manifestRevision) {
+					return false;
+				}
+				
+				public boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision) {
+					return true;
+				}
+			};
+
+			for (Path file : files) {
+				File f = new File(repo.getWorkingDir(), file.toString());
+				if (f.isFile()) {
+					if (keepOriginal) {
+						File copy = new File(f.getParentFile(), f.getName() + ".orig");
+						if (copy.exists()) {
+							copy.delete();
+						}
+						f.renameTo(copy);
+					} else {
+						f.delete();
+					}
+				}
+				repo.getManifest().walkFileRevisions(file, insp, csetRevision);
+				worker.checkFailed();
+			}
+			dirstateBuilder.serialize();
+		} catch (HgRuntimeException ex) {
+			throw new HgLibraryFailureException(ex);
+		}
+	}
+}
--- a/src/org/tmatesoft/hg/core/HgStatusCommand.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/core/HgStatusCommand.java	Tue Jan 15 17:07:19 2013 +0100
@@ -24,6 +24,7 @@
 import java.util.ConcurrentModificationException;
 
 import org.tmatesoft.hg.internal.ChangelogHelper;
+import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.repo.HgStatusCollector;
@@ -156,7 +157,7 @@
 	}
 
 	public HgStatusCommand subrepo(boolean visit) {
-		throw HgRepository.notImplemented();
+		throw Internals.notImplemented();
 	}
 
 	/**
--- a/src/org/tmatesoft/hg/internal/DirstateBuilder.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/internal/DirstateBuilder.java	Tue Jan 15 17:07:19 2013 +0100
@@ -16,15 +16,23 @@
  */
 package org.tmatesoft.hg.internal;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
 import java.nio.channels.WritableByteChannel;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
+import org.tmatesoft.hg.core.HgIOException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.repo.HgDirstate;
+import org.tmatesoft.hg.repo.HgDirstate.EntryKind;
+import org.tmatesoft.hg.repo.HgDirstate.Record;
+import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgManifest.Flags;
+import org.tmatesoft.hg.repo.HgRepositoryFiles;
 import org.tmatesoft.hg.util.Path;
 
 /**
@@ -36,12 +44,17 @@
  * @author TMate Software Ltd.
  */
 public class DirstateBuilder {
-	private List<HgDirstate.Record> normal = new ArrayList<HgDirstate.Record>();
+	private Map<Path, HgDirstate.Record> normal = new TreeMap<Path, HgDirstate.Record>();
+	private Map<Path, HgDirstate.Record> added = new TreeMap<Path, HgDirstate.Record>();
+	private Map<Path, HgDirstate.Record> removed = new TreeMap<Path, HgDirstate.Record>();
+	private Map<Path, HgDirstate.Record> merged = new TreeMap<Path, HgDirstate.Record>();
 	private Nodeid parent1, parent2;
+	private final Internals hgRepo;
 	private final EncodingHelper encodingHelper;
 	
-	public DirstateBuilder(EncodingHelper encHelper) {
-		encodingHelper = encHelper;
+	public DirstateBuilder(Internals internalRepo) {
+		hgRepo = internalRepo;
+		encodingHelper = internalRepo.buildFileNameEncodingHelper();
 	}
 	
 	public void parents(Nodeid p1, Nodeid p2) {
@@ -60,8 +73,21 @@
 		// right away.
 		int fmode = flags == Flags.RegularFile ? 0666 : 0777; // FIXME actual unix flags
 		int mtime = (int) (System.currentTimeMillis() / 1000);
-		normal.add(new HgDirstate.Record(fmode, bytesWritten, mtime,fname, null));
-		
+		forget(fname);
+		normal.put(fname, new HgDirstate.Record(fmode, bytesWritten, mtime, fname, null));
+	}
+	
+	public void recordUncertain(Path fname) {
+		// `hg revert` puts "n 0 -1 unset" for the reverted file, so shall we
+		forget(fname);
+		normal.put(fname, new HgDirstate.Record(0, -1, -1, fname, null));
+	}
+	
+	private void forget(Path fname) {
+		normal.remove(fname);
+		added.remove(fname);
+		removed.remove(fname);
+		merged.remove(fname);
 	}
 
 	public void serialize(WritableByteChannel dest) throws IOException {
@@ -77,24 +103,65 @@
 		}
 		bb.clear();
 		// entries
-		for (HgDirstate.Record r : normal) {
-			// normal entry is 1+4+4+4+4+fname.length bytes
-			byte[] fname = encodingHelper.toDirstate(r.name().toString());
-			bb = ensureCapacity(bb, 17 + fname.length);
-			bb.put((byte) 'n');
-			bb.putInt(r.mode());
-			bb.putInt(r.size());
-			bb.putInt(r.modificationTime());
-			bb.putInt(fname.length);
-			bb.put(fname);
-			bb.flip();
-			written = dest.write(bb);
-			if (written != bb.limit()) {
-				throw new IOException("Incomplete write");
+		@SuppressWarnings("unchecked")
+		Map<Path, HgDirstate.Record>[] all = new Map[] {normal, added, removed, merged};
+		for (Map<Path, HgDirstate.Record> m : all) {
+			for (HgDirstate.Record r : m.values()) {
+				// regular entry is 1+4+4+4+4+fname.length bytes
+				// it might get extended with copy origin, prepended with 0 byte 
+				byte[] fname = encodingHelper.toDirstate(r.name());
+				byte[] copyOrigin = r.copySource() == null ? null : encodingHelper.toDirstate(r.copySource());
+				int length = fname.length + (copyOrigin == null ? 0 : (1 + copyOrigin.length)); 
+				bb = ensureCapacity(bb, 17 + length);
+				bb.put((byte) 'n');
+				bb.putInt(r.mode());
+				bb.putInt(r.size());
+				bb.putInt(r.modificationTime());
+				bb.putInt(length);
+				bb.put(fname);
+				if (copyOrigin != null) {
+					bb.put((byte) 0);
+					bb.put(copyOrigin);
+				}
+				bb.flip();
+				written = dest.write(bb);
+				if (written != bb.limit()) {
+					throw new IOException("Incomplete write");
+				}
+				bb.clear();
 			}
-			bb.clear();
 		}
 	}
+	
+	public void serialize() throws HgIOException {
+		File dirstateFile = hgRepo.getRepositoryFile(HgRepositoryFiles.Dirstate);
+		try {
+			FileChannel dirstate = new FileOutputStream(dirstateFile).getChannel();
+			serialize(dirstate);
+			dirstate.close();
+		} catch (IOException ex) {
+			throw new HgIOException("Can't write down new directory state", ex, dirstateFile);
+		}
+	}
+	
+	public void fillFrom(DirstateReader dirstate) {
+		// TODO preserve order, if reasonable and possible 
+		dirstate.readInto(new HgDirstate.Inspector() {
+			
+			public boolean next(EntryKind kind, Record entry) {
+				switch (kind) {
+				case Normal: normal.put(entry.name(), entry); break;
+				case Added : added.put(entry.name(), entry); break;
+				case Removed : removed.put(entry.name(), entry); break;
+				case Merged : merged.put(entry.name(), entry); break;
+				default: throw new HgInvalidStateException(String.format("Unexpected entry in the dirstate: %s", kind));
+				}
+				return true;
+			}
+		});
+		parents(dirstate.parents().first(), dirstate.parents().second());
+	}
+
 
 	private static ByteBuffer ensureCapacity(ByteBuffer buf, int cap) {
 		if (buf.capacity() >= cap) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/DirstateReader.java	Tue Jan 15 17:07:19 2013 +0100
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2010-2013 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.internal;
+
+import static org.tmatesoft.hg.core.Nodeid.NULL;
+import static org.tmatesoft.hg.repo.HgRepositoryFiles.Dirstate;
+import static org.tmatesoft.hg.util.LogFacility.Severity.Debug;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgDirstate;
+import org.tmatesoft.hg.repo.HgDirstate.EntryKind;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.LogFacility.Severity;
+import org.tmatesoft.hg.util.Pair;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ * Parse dirstate file
+ * 
+ * @see http://mercurial.selenic.com/wiki/DirState
+ * @see http://mercurial.selenic.com/wiki/FileFormats#dirstate
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public final class DirstateReader {
+	// dirstate read code originally lived in org.tmatesoft.hg.repo.HgDirstate
+	
+	private final Internals repo;
+	private final Path.Source pathPool;
+	private Pair<Nodeid, Nodeid> parents;
+	
+	public DirstateReader(Internals hgRepo, Path.Source pathSource) {
+		repo = hgRepo;
+		pathPool = pathSource;
+	}
+
+	public void readInto(HgDirstate.Inspector target) throws HgInvalidControlFileException {
+		EncodingHelper encodingHelper = repo.buildFileNameEncodingHelper();
+		parents = new Pair<Nodeid,Nodeid>(Nodeid.NULL, Nodeid.NULL);
+		File dirstateFile = getDirstateFile(repo);
+		if (dirstateFile == null || !dirstateFile.exists()) {
+			return;
+		}
+		DataAccess da = repo.getDataAccess().create(dirstateFile);
+		try {
+			if (da.isEmpty()) {
+				return;
+			}
+			parents = internalReadParents(da);
+			// hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only
+			while (!da.isEmpty()) {
+				final byte state = da.readByte();
+				final int fmode = da.readInt();
+				final int size = da.readInt();
+				final int time = da.readInt();
+				final int nameLen = da.readInt();
+				String fn1 = null, fn2 = null;
+				byte[] name = new byte[nameLen];
+				da.readBytes(name, 0, nameLen);
+				for (int i = 0; i < nameLen; i++) {
+					if (name[i] == 0) {
+						fn1 = encodingHelper.fromDirstate(name, 0, i);
+						fn2 = encodingHelper.fromDirstate(name, i+1, nameLen - i - 1);
+						break;
+					}
+				}
+				if (fn1 == null) {
+					fn1 = encodingHelper.fromDirstate(name, 0, nameLen);
+				}
+				HgDirstate.Record r = new HgDirstate.Record(fmode, size, time, pathPool.path(fn1), fn2 == null ? null : pathPool.path(fn2));
+				if (state == 'n') {
+					target.next(EntryKind.Normal, r);
+				} else if (state == 'a') {
+					target.next(EntryKind.Added, r);
+				} else if (state == 'r') {
+					target.next(EntryKind.Removed, r);
+				} else if (state == 'm') {
+					target.next(EntryKind.Merged, r);
+				} else {
+					repo.getSessionContext().getLog().dump(getClass(), Severity.Warn, "Dirstate record for file %s (size: %d, tstamp:%d) has unknown state '%c'", r.name(), r.size(), r.modificationTime(), state);
+				}
+			}
+		} catch (IOException ex) {
+			throw new HgInvalidControlFileException("Dirstate read failed", ex, dirstateFile); 
+		} finally {
+			da.done();
+		}
+	}
+
+	private static Pair<Nodeid, Nodeid> internalReadParents(DataAccess da) throws IOException {
+		byte[] parents = new byte[40];
+		da.readBytes(parents, 0, 40);
+		Nodeid n1 = Nodeid.fromBinary(parents, 0);
+		Nodeid n2 = Nodeid.fromBinary(parents, 20);
+		parents = null;
+		return new Pair<Nodeid, Nodeid>(n1, n2);
+	}
+	
+	/**
+	 * @return pair of working copy parents, with {@link Nodeid#NULL} for missing values.
+	 */
+	public Pair<Nodeid,Nodeid> parents() {
+		assert parents != null; // instance not initialized with #read()
+		return parents;
+	}
+	
+	private static File getDirstateFile(Internals repo) {
+		return repo.getFileFromRepoDir(Dirstate.getName());
+	}
+
+	/**
+	 * @return pair of parents, both {@link Nodeid#NULL} if dirstate is not available
+	 */
+	public static Pair<Nodeid, Nodeid> readParents(Internals internalRepo) throws HgInvalidControlFileException {
+		// do not read whole dirstate if all we need is WC parent information
+		File dirstateFile = getDirstateFile(internalRepo);
+		if (dirstateFile == null || !dirstateFile.exists()) {
+			return new Pair<Nodeid,Nodeid>(NULL, NULL);
+		}
+		DataAccess da = internalRepo.getDataAccess().create(dirstateFile);
+		try {
+			if (da.isEmpty()) {
+				return new Pair<Nodeid,Nodeid>(NULL, NULL);
+			}
+			return internalReadParents(da);
+		} catch (IOException ex) {
+			throw new HgInvalidControlFileException("Error reading working copy parents from dirstate", ex, dirstateFile);
+		} finally {
+			da.done();
+		}
+	}
+	
+	/**
+	 * TODO [post-1.0] it's really not a proper place for the method, need WorkingCopyContainer or similar
+	 * @return branch associated with the working directory
+	 */
+	public static String readBranch(Internals internalRepo) throws HgInvalidControlFileException {
+		File branchFile = internalRepo.getFileFromRepoDir("branch"); // FIXME constant in the HgRepositoryFiles
+		String branch = HgRepository.DEFAULT_BRANCH_NAME;
+		if (branchFile.exists()) {
+			try {
+				BufferedReader r = new BufferedReader(new FileReader(branchFile));
+				String b = r.readLine();
+				if (b != null) {
+					b = b.trim().intern();
+				}
+				branch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b;
+				r.close();
+			} catch (FileNotFoundException ex) {
+				internalRepo.getSessionContext().getLog().dump(HgDirstate.class, Debug, ex, null); // log verbose debug, exception might be legal here 
+				// IGNORE
+			} catch (IOException ex) {
+				throw new HgInvalidControlFileException("Error reading file with branch information", ex, branchFile);
+			}
+		}
+		return branch;
+	}
+}
--- a/src/org/tmatesoft/hg/internal/EncodingHelper.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/internal/EncodingHelper.java	Tue Jan 15 17:07:19 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2012 TMate Software Ltd
+ * Copyright (c) 2011-2013 TMate Software Ltd
  *  
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -60,7 +60,7 @@
 	/**
 	 * @return byte representation of the string directly comparable to bytes in manifest
 	 */
-	public byte[] toManifest(String s) {
+	public byte[] toManifest(CharSequence s) {
 		if (s == null) {
 			// perhaps, can return byte[0] in this case?
 			throw new IllegalArgumentException();
@@ -75,7 +75,7 @@
 		return decodeWithSystemDefaultFallback(data, start, length);
 	}
 	
-	public byte[] toDirstate(String fname) {
+	public byte[] toDirstate(CharSequence fname) {
 		if (fname == null) {
 			throw new IllegalArgumentException();
 		}
@@ -92,7 +92,7 @@
 		}
 	}
 	
-	private byte[] encodeWithSystemDefaultFallback(String s) {
+	private byte[] encodeWithSystemDefaultFallback(CharSequence s) {
 		try {
 			// synchronized(encoder) {
 			ByteBuffer bb = encoder.encode(CharBuffer.wrap(s));
@@ -103,7 +103,7 @@
 		} catch (CharacterCodingException ex) {
 			sessionContext.getLog().dump(getClass(), Error, ex, String.format("Use of charset %s failed, resort to system default", charset().name()));
 			// resort to system-default
-			return s.getBytes();
+			return s.toString().getBytes();
 		}
 	}
 
--- a/src/org/tmatesoft/hg/internal/Internals.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/internal/Internals.java	Tue Jan 15 17:07:19 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2012 TMate Software Ltd
+ * Copyright (c) 2011-2013 TMate Software Ltd
  *  
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -32,9 +32,11 @@
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgInternals;
-import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.repo.HgRepoConfig.ExtensionsSection;
 import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRepositoryFiles;
+import org.tmatesoft.hg.repo.HgRepositoryLock;
+import org.tmatesoft.hg.repo.HgRuntimeException;
 import org.tmatesoft.hg.util.PathRewrite;
 
 /**
@@ -117,6 +119,10 @@
 		return !repoDir.exists() || !repoDir.isDirectory();
 	}
 	
+	public File getRepositoryFile(HgRepositoryFiles f) {
+		return f.residesUnderRepositoryRoot() ? getFileFromRepoDir(f.getName()) : getFileFromDataDir(f.getName());
+	}
+
 	/**
 	 * Access files under ".hg/".
 	 * File not necessarily exists, this method is merely a factory for Files at specific, configuration-dependent location. 
@@ -222,7 +228,7 @@
 		return dataPathHelper.rewrite(df.getPath().toString());
 	}
 
-
+	
 	public static boolean runningOnWindows() {
 		return System.getProperty("os.name").indexOf("Windows") != -1;
 	}
@@ -411,6 +417,11 @@
 		return shallCacheRevlogsInRepo;
 	}
 	
+	// marker method
+	public static IllegalStateException notImplemented() {
+		return new IllegalStateException("Not implemented");
+	}
+
 	public static Internals getInstance(HgRepository repo) {
 		return HgInternals.getImplementationRepo(repo);
 	}
--- a/src/org/tmatesoft/hg/internal/WorkingDirFileWriter.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/internal/WorkingDirFileWriter.java	Tue Jan 15 17:07:19 2013 +0100
@@ -23,11 +23,10 @@
 import java.nio.channels.FileChannel;
 
 import org.tmatesoft.hg.repo.HgDataFile;
-import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.ByteChannel;
 import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.LogFacility.Severity;
 import org.tmatesoft.hg.util.Path;
-import org.tmatesoft.hg.util.LogFacility.Severity;
 
 /**
  * 
@@ -37,13 +36,13 @@
 public class WorkingDirFileWriter implements ByteChannel {
 
 	
-	private final HgRepository repo;
+	private final Internals hgRepo;
 	private File dest;
 	private FileChannel destChannel;
 	private int totalBytesWritten;
 
-	public WorkingDirFileWriter(HgRepository hgRepo) {
-		repo = hgRepo;
+	public WorkingDirFileWriter(Internals internalRepo) {
+		hgRepo = internalRepo;
 	}
 	
 	public void processFile(HgDataFile df, int fileRevIndex) throws IOException {
@@ -51,14 +50,14 @@
 			prepare(df.getPath());
 			df.contentWithFilters(fileRevIndex, this);
 		} catch (CancelledException ex) {
-			repo.getSessionContext().getLog().dump(getClass(), Severity.Error, ex, "Our impl doesn't throw cancellation");
+			hgRepo.getSessionContext().getLog().dump(getClass(), Severity.Error, ex, "Our impl doesn't throw cancellation");
 		}
 		finish();
 	}
 
 	public void prepare(Path fname) throws IOException {
 		String fpath = fname.toString();
-		dest = new File(repo.getWorkingDir(), fpath);
+		dest = new File(hgRepo.getRepo().getWorkingDir(), fpath);
 		if (fpath.indexOf('/') != -1) {
 			dest.getParentFile().mkdirs();
 		}
--- a/src/org/tmatesoft/hg/repo/HgBundle.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/repo/HgBundle.java	Tue Jan 15 17:07:19 2013 +0100
@@ -30,6 +30,7 @@
 import org.tmatesoft.hg.internal.DigestHelper;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.InflaterDataAccess;
+import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.internal.Lifecycle;
 import org.tmatesoft.hg.internal.Patch;
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
@@ -68,7 +69,7 @@
 					return new InflaterDataAccess(da, 6, da.length() - 6);
 				}
 				if (signature[4] == 'B' && signature[5] == 'Z') {
-					throw HgRepository.notImplemented();
+					throw Internals.notImplemented();
 				}
 				if (signature[4] != 'U' || signature[5] != 'N') {
 					throw new HgInvalidStateException(String.format("Bad bundle signature: %s",  String.valueOf(signature)));
--- a/src/org/tmatesoft/hg/repo/HgDirstate.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/repo/HgDirstate.java	Tue Jan 15 17:07:19 2013 +0100
@@ -16,15 +16,6 @@
  */
 package org.tmatesoft.hg.repo;
 
-import static org.tmatesoft.hg.core.Nodeid.NULL;
-import static org.tmatesoft.hg.repo.HgRepositoryFiles.Dirstate;
-import static org.tmatesoft.hg.util.LogFacility.Severity.Debug;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -32,13 +23,11 @@
 import java.util.TreeSet;
 
 import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.DataAccess;
-import org.tmatesoft.hg.internal.EncodingHelper;
+import org.tmatesoft.hg.internal.DirstateReader;
 import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.util.Pair;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.PathRewrite;
-import org.tmatesoft.hg.util.LogFacility.Severity;
 
 
 /**
@@ -75,7 +64,6 @@
 	}
 
 	/*package-local*/ void read() throws HgInvalidControlFileException {
-		EncodingHelper encodingHelper = repo.buildFileNameEncodingHelper();
 		normal = added = removed = merged = Collections.<Path, Record>emptyMap();
 		parents = new Pair<Nodeid,Nodeid>(Nodeid.NULL, Nodeid.NULL);
 		if (canonicalPathRewrite != null) {
@@ -83,87 +71,45 @@
 		} else {
 			canonical2dirstateName = Collections.emptyMap();
 		}
-		File dirstateFile = getDirstateFile(repo);
-		if (dirstateFile == null || !dirstateFile.exists()) {
-			return;
-		}
-		DataAccess da = repo.getDataAccess().create(dirstateFile);
-		try {
-			if (da.isEmpty()) {
-				return;
-			}
-			// not sure linked is really needed here, just for ease of debug
-			normal = new LinkedHashMap<Path, Record>();
-			added = new LinkedHashMap<Path, Record>();
-			removed = new LinkedHashMap<Path, Record>();
-			merged = new LinkedHashMap<Path, Record>();
+		// not sure linked is really needed here, just for ease of debug
+		normal = new LinkedHashMap<Path, Record>();
+		added = new LinkedHashMap<Path, Record>();
+		removed = new LinkedHashMap<Path, Record>();
+		merged = new LinkedHashMap<Path, Record>();
+
+		DirstateReader dirstateReader = new DirstateReader(repo, pathPool);
+		dirstateReader.readInto(new Inspector() {
 			
-			parents = internalReadParents(da);
-			// hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only
-			while (!da.isEmpty()) {
-				final byte state = da.readByte();
-				final int fmode = da.readInt();
-				final int size = da.readInt();
-				final int time = da.readInt();
-				final int nameLen = da.readInt();
-				String fn1 = null, fn2 = null;
-				byte[] name = new byte[nameLen];
-				da.readBytes(name, 0, nameLen);
-				for (int i = 0; i < nameLen; i++) {
-					if (name[i] == 0) {
-						fn1 = encodingHelper.fromDirstate(name, 0, i);
-						fn2 = encodingHelper.fromDirstate(name, i+1, nameLen - i - 1);
-						break;
-					}
-				}
-				if (fn1 == null) {
-					fn1 = encodingHelper.fromDirstate(name, 0, nameLen);
-				}
-				Record r = new Record(fmode, size, time, pathPool.path(fn1), fn2 == null ? null : pathPool.path(fn2));
+			public boolean next(EntryKind kind, Record r) {
 				if (canonicalPathRewrite != null) {
-					Path canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(fn1).toString());
+					Path canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(r.name()));
 					if (canonicalPath != r.name()) { // == as they come from the same pool
 						assert !canonical2dirstateName.containsKey(canonicalPath); // otherwise there's already a file with same canonical name
 						// which can't happen for case-insensitive file system (or there's erroneous PathRewrite, perhaps doing smth else)
 						canonical2dirstateName.put(canonicalPath, r.name());
 					}
-					if (fn2 != null) {
+					if (r.copySource() != null) {
 						// not sure I need copy origin in the map, I don't seem to use it anywhere,
 						// but I guess I'll have to use it some day.
-						canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(fn2).toString());
+						canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(r.copySource()));
 						if (canonicalPath != r.copySource()) {
 							canonical2dirstateName.put(canonicalPath, r.copySource());
 						}
 					}
 				}
-				if (state == 'n') {
-					normal.put(r.name1, r);
-				} else if (state == 'a') {
-					added.put(r.name1, r);
-				} else if (state == 'r') {
-					removed.put(r.name1, r);
-				} else if (state == 'm') {
-					merged.put(r.name1, r);
-				} else {
-					repo.getSessionContext().getLog().dump(getClass(), Severity.Warn, "Dirstate record for file %s (size: %d, tstamp:%d) has unknown state '%c'", r.name1, r.size(), r.time, state);
+				switch (kind) {
+				case Normal : normal.put(r.name(), r); break;
+				case Added :  added.put(r.name(), r); break;
+				case Removed : removed.put(r.name(), r); break;
+				case Merged : merged.put(r.name1, r); break;
+				default: throw new HgInvalidStateException(String.format("Unexpected entry in the dirstate: %s", kind));
 				}
+				return true;
 			}
-		} catch (IOException ex) {
-			throw new HgInvalidControlFileException("Dirstate read failed", ex, dirstateFile); 
-		} finally {
-			da.done();
-		}
+		});
+		parents = dirstateReader.parents();
 	}
 
-	private static Pair<Nodeid, Nodeid> internalReadParents(DataAccess da) throws IOException {
-		byte[] parents = new byte[40];
-		da.readBytes(parents, 0, 40);
-		Nodeid n1 = Nodeid.fromBinary(parents, 0);
-		Nodeid n2 = Nodeid.fromBinary(parents, 20);
-		parents = null;
-		return new Pair<Nodeid, Nodeid>(n1, n2);
-	}
-	
 	/**
 	 * @return pair of working copy parents, with {@link Nodeid#NULL} for missing values.
 	 */
@@ -172,58 +118,6 @@
 		return parents;
 	}
 	
-	private static File getDirstateFile(Internals repo) {
-		return repo.getFileFromRepoDir(Dirstate.getName());
-	}
-
-	/**
-	 * @return pair of parents, both {@link Nodeid#NULL} if dirstate is not available
-	 */
-	/*package-local*/ static Pair<Nodeid, Nodeid> readParents(Internals internalRepo) throws HgInvalidControlFileException {
-		// do not read whole dirstate if all we need is WC parent information
-		File dirstateFile = getDirstateFile(internalRepo);
-		if (dirstateFile == null || !dirstateFile.exists()) {
-			return new Pair<Nodeid,Nodeid>(NULL, NULL);
-		}
-		DataAccess da = internalRepo.getDataAccess().create(dirstateFile);
-		try {
-			if (da.isEmpty()) {
-				return new Pair<Nodeid,Nodeid>(NULL, NULL);
-			}
-			return internalReadParents(da);
-		} catch (IOException ex) {
-			throw new HgInvalidControlFileException("Error reading working copy parents from dirstate", ex, dirstateFile);
-		} finally {
-			da.done();
-		}
-	}
-	
-	/**
-	 * TODO [post-1.0] it's really not a proper place for the method, need WorkingCopyContainer or similar
-	 * @return branch associated with the working directory
-	 */
-	/*package-local*/ static String readBranch(Internals internalRepo) throws HgInvalidControlFileException {
-		File branchFile = internalRepo.getFileFromRepoDir("branch");
-		String branch = HgRepository.DEFAULT_BRANCH_NAME;
-		if (branchFile.exists()) {
-			try {
-				BufferedReader r = new BufferedReader(new FileReader(branchFile));
-				String b = r.readLine();
-				if (b != null) {
-					b = b.trim().intern();
-				}
-				branch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b;
-				r.close();
-			} catch (FileNotFoundException ex) {
-				internalRepo.getSessionContext().getLog().dump(HgDirstate.class, Debug, ex, null); // log verbose debug, exception might be legal here 
-				// IGNORE
-			} catch (IOException ex) {
-				throw new HgInvalidControlFileException("Error reading file with branch information", ex, branchFile);
-			}
-		}
-		return branch;
-	}
-
 	// new, modifiable collection
 	/*package-local*/ TreeSet<Path> all() {
 		assert normal != null;
--- a/src/org/tmatesoft/hg/repo/HgLookup.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/repo/HgLookup.java	Tue Jan 15 17:07:19 2013 +0100
@@ -28,6 +28,7 @@
 import org.tmatesoft.hg.internal.BasicSessionContext;
 import org.tmatesoft.hg.internal.ConfigFile;
 import org.tmatesoft.hg.internal.DataAccessProvider;
+import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.repo.HgRepoConfig.PathsSection;
 
 /**
@@ -129,7 +130,7 @@
 			throw new IllegalArgumentException();
 		}
 		if (Boolean.FALSE.booleanValue()) {
-			throw HgRepository.notImplemented();
+			throw Internals.notImplemented();
 		}
 		return new HgRemoteRepository(getContext(), url);
 	}
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/repo/HgRepository.java	Tue Jan 15 17:07:19 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2012 TMate Software Ltd
+ * Copyright (c) 2010-2013 TMate Software Ltd
  *  
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -34,6 +34,7 @@
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
 import org.tmatesoft.hg.internal.ConfigFile;
+import org.tmatesoft.hg.internal.DirstateReader;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.Filter;
 import org.tmatesoft.hg.internal.Internals;
@@ -93,11 +94,6 @@
 	 */
 	public static final String DEFAULT_BRANCH_NAME = "default";
 
-	// temp aux marker method
-	public static IllegalStateException notImplemented() {
-		return new IllegalStateException("Not implemented");
-	}
-	
 	private final File repoDir; // .hg folder
 	private final File workingDir; // .hg/../
 	private final String repoLocation;
@@ -299,7 +295,7 @@
 	 * @throws HgInvalidControlFileException if attempt to read information about working copy parents from dirstate failed 
 	 */
 	public Pair<Nodeid,Nodeid> getWorkingCopyParents() throws HgInvalidControlFileException {
-		return HgDirstate.readParents(impl);
+		return DirstateReader.readParents(impl);
 	}
 	
 	/**
@@ -308,7 +304,7 @@
 	 */
 	public String getWorkingCopyBranchName() throws HgInvalidControlFileException {
 		if (wcBranch == null) {
-			wcBranch = HgDirstate.readBranch(impl);
+			wcBranch = DirstateReader.readBranch(impl);
 		}
 		return wcBranch;
 	}
@@ -350,8 +346,8 @@
 		return repoConfig;
 	}
 
-	// XXX package-local, unless there are cases when required from outside (guess, working dir/revision walkers may hide dirstate access and no public visibility needed)
-	// XXX consider passing Path pool or factory to produce (shared) Path instead of Strings
+	// There seem to be no cases when access to HgDirstate is required from outside 
+	// (guess, working dir/revision walkers may hide dirstate access and no public visibility needed)
 	/*package-local*/ final HgDirstate loadDirstate(Path.Source pathFactory) throws HgInvalidControlFileException {
 		PathRewrite canonicalPath = null;
 		if (!impl.isCaseSensitiveFileSystem()) {
--- a/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java	Tue Jan 15 17:07:19 2013 +0100
@@ -20,6 +20,7 @@
 
 import org.tmatesoft.hg.core.HgRepositoryNotFoundException;
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.util.Path;
 
 /**
@@ -109,7 +110,7 @@
 	 * @return <code>true</code> if it's dirty
 	 */
 	public boolean hasChanges() {
-		throw HgRepository.notImplemented();
+		throw Internals.notImplemented();
 	}
 
 	/**
--- a/test/org/tmatesoft/hg/test/LogOutputParser.java	Fri Jan 11 18:12:39 2013 +0100
+++ b/test/org/tmatesoft/hg/test/LogOutputParser.java	Tue Jan 15 17:07:19 2013 +0100
@@ -21,7 +21,7 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.internal.Internals;
 
 
 /**
@@ -46,7 +46,7 @@
 			pattern5 = Pattern.compile("\\n\\n");
 			//p = "^manifest:\\s+(\\d+):([a-f0-9]{40})\nuser:(.+)$";
 		} else {
-			throw HgRepository.notImplemented();
+			throw Internals.notImplemented();
 		}
 	}
 	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/org/tmatesoft/hg/test/TestCheckout.java	Tue Jan 15 17:07:19 2013 +0100
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2013 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.test;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestCheckout {
+
+
+	@Test
+	public void testCleanCheckoutInEmptyDir() {
+		Assert.fail("clone without update, checkout, status");
+	}
+
+	@Test
+	public void testCleanCheckoutInDirtyDir() {
+		Assert.fail("Make sure WC is cleared prior to clean checkout");
+	}
+
+	@Test
+	public void testBranchCheckout() {
+		Assert.fail("Make sure branch file is written");
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/org/tmatesoft/hg/test/TestRevert.java	Tue Jan 15 17:07:19 2013 +0100
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2013 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.tmatesoft.hg.core.HgRevertCommand;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestRevert {
+
+	@Rule
+	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
+	private HgRepository repo;
+	private ExecHelper eh;
+
+	
+	public TestRevert() {
+	}
+	
+	@Test
+	public void testCommand() throws Exception {
+		File tmpDir = Configuration.get().getTempDir();
+		tmpDir.mkdirs();
+		eh = new ExecHelper(new OutputParser.Stub(), tmpDir);
+		File testRepoLoc = TestIncoming.createEmptyDir("test-revert");
+		// get a copy of a repository
+		eh.run("hg", "clone", Configuration.get().find("log-1").getWorkingDir().toString(), testRepoLoc.getName());
+		assertEquals("[sanity]", 0, eh.getExitValue());
+		
+		repo = new HgLookup().detect(testRepoLoc);
+		Path targetFile = Path.create("b");
+		modifyFileAppend(new File(testRepoLoc, targetFile.toString()));
+		
+		StatusOutputParser statusParser = new StatusOutputParser();
+		eh = new ExecHelper(statusParser, testRepoLoc);
+		eh.run("hg", "status", "-A");
+		assertEquals("[sanity]", 1, statusParser.getModified().size());
+		assertEquals("[sanity]", 2, statusParser.getClean().size());
+		assertEquals("[sanity]", targetFile, statusParser.getModified().get(0));
+		
+		HgRevertCommand cmd = new HgRevertCommand(repo);
+		cmd.file(targetFile).execute();
+		statusParser.reset();
+		eh.run("hg", "status", "-A");
+
+		assertEquals(3, statusParser.getClean().size());
+		assertTrue(statusParser.getClean().contains(targetFile));
+		assertEquals(1, statusParser.getUnknown().size());
+		assertEquals(targetFile.toString() + ".orig", statusParser.getUnknown().get(0).toString());
+	}
+
+	private static void modifyFileAppend(File f) throws Exception {
+		assertTrue(f.isFile());
+		FileOutputStream fos = new FileOutputStream(f, true);
+		fos.write("XXX".getBytes());
+		fos.close();
+	}
+}