Merge branch 'rename-bug'

* rename-bug:
  Fix ArrayIndexOutOfBounds on non-square exact rename matrix

Conflicts:
	org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java

Change-Id: Ie0b8dd3e1ec174f79ba39dc4706bb0694cc8be29
diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
index b9aeef5..edaa1ed 100644
--- a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
@@ -17,6 +17,7 @@
  org.eclipse.jgit.lib;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.nls;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)",
+ org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.transport;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.util;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.util.io;version="[0.9.0,0.10.0)"
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java
index dff1e82..d217fe1 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java
@@ -53,8 +53,8 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jgit.lib.ObjectDatabase;
-import org.eclipse.jgit.lib.ObjectDirectory;
-import org.eclipse.jgit.lib.PackFile;
+import org.eclipse.jgit.storage.file.ObjectDirectory;
+import org.eclipse.jgit.storage.file.PackFile;
 
 /** Sends the current list of pack files, sorted most recent first. */
 class InfoPacksServlet extends HttpServlet {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
index f667ce9..647919e 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
@@ -74,30 +74,35 @@ public void doGet(final HttpServletRequest req,
 
 		final Repository db = getRepository(req);
 		final RevWalk walk = new RevWalk(db);
-		final RevFlag ADVERTISED = walk.newFlag("ADVERTISED");
+		try {
+			final RevFlag ADVERTISED = walk.newFlag("ADVERTISED");
 
-		final OutputStreamWriter out = new OutputStreamWriter(
-				new SmartOutputStream(req, rsp), Constants.CHARSET);
-		final RefAdvertiser adv = new RefAdvertiser() {
-			@Override
-			protected void writeOne(final CharSequence line) throws IOException {
-				// Whoever decided that info/refs should use a different
-				// delimiter than the native git:// protocol shouldn't
-				// be allowed to design this sort of stuff. :-(
-				out.append(line.toString().replace(' ', '\t'));
-			}
+			final OutputStreamWriter out = new OutputStreamWriter(
+					new SmartOutputStream(req, rsp), Constants.CHARSET);
+			final RefAdvertiser adv = new RefAdvertiser() {
+				@Override
+				protected void writeOne(final CharSequence line)
+						throws IOException {
+					// Whoever decided that info/refs should use a different
+					// delimiter than the native git:// protocol shouldn't
+					// be allowed to design this sort of stuff. :-(
+					out.append(line.toString().replace(' ', '\t'));
+				}
 
-			@Override
-			protected void end() {
-				// No end marker required for info/refs format.
-			}
-		};
-		adv.init(walk, ADVERTISED);
-		adv.setDerefTags(true);
+				@Override
+				protected void end() {
+					// No end marker required for info/refs format.
+				}
+			};
+			adv.init(walk, ADVERTISED);
+			adv.setDerefTags(true);
 
-		Map<String, Ref> refs = db.getAllRefs();
-		refs.remove(Constants.HEAD);
-		adv.send(refs);
-		out.close();
+			Map<String, Ref> refs = db.getAllRefs();
+			refs.remove(Constants.HEAD);
+			adv.send(refs);
+			out.close();
+		} finally {
+			walk.release();
+		}
 	}
 }
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java
index 34edf82..019ec90 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java
@@ -56,8 +56,8 @@
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jgit.lib.ObjectDirectory;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.ObjectDirectory;
 
 /**
  * Requires the target {@link Repository} to be available via local filesystem.
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
index 5d774a8..8486512 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
@@ -60,8 +60,8 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jgit.lib.ObjectDirectory;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.ObjectDirectory;
 
 /** Sends any object from {@code GIT_DIR/objects/??/0 38}, or any pack file. */
 abstract class ObjectFileServlet extends HttpServlet {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
index 49fd535..4bc05c1 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
@@ -83,7 +83,12 @@ static class InfoRefs extends SmartServiceInfoRefs {
 		protected void advertise(HttpServletRequest req, Repository db,
 				PacketLineOutRefAdvertiser pck) throws IOException,
 				ServiceNotEnabledException, ServiceNotAuthorizedException {
-			receivePackFactory.create(req, db).sendAdvertisedRefs(pck);
+			ReceivePack rp = receivePackFactory.create(req, db);
+			try {
+				rp.sendAdvertisedRefs(pck);
+			} finally {
+				rp.getRevWalk().release();
+			}
 		}
 	}
 
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java
index 5bf5546..650059b 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java
@@ -80,6 +80,8 @@ public void doGet(final HttpServletRequest req,
 
 	private byte[] read(final HttpServletRequest req) throws IOException {
 		final File gitdir = getRepository(req).getDirectory();
+		if (gitdir == null)
+			throw new FileNotFoundException(fileName);
 		return IO.readFully(new File(gitdir, fileName));
 	}
 }
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
index 92d41a0..602d66a 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
@@ -83,7 +83,12 @@ static class InfoRefs extends SmartServiceInfoRefs {
 		protected void advertise(HttpServletRequest req, Repository db,
 				PacketLineOutRefAdvertiser pck) throws IOException,
 				ServiceNotEnabledException, ServiceNotAuthorizedException {
-			uploadPackFactory.create(req, db).sendAdvertisedRefs(pck);
+			UploadPack up = uploadPackFactory.create(req, db);
+			try {
+				up.sendAdvertisedRefs(pck);
+			} finally {
+				up.getRevWalk().release();
+			}
 		}
 	}
 
@@ -107,7 +112,12 @@ public void doPost(final HttpServletRequest req,
 			up.setBiDirectionalPipe(false);
 			rsp.setContentType(RSP_TYPE);
 
-			final SmartOutputStream out = new SmartOutputStream(req, rsp);
+			final SmartOutputStream out = new SmartOutputStream(req, rsp) {
+				@Override
+				public void flush() throws IOException {
+					doFlush();
+				}
+			};
 			up.upload(getInputStream(req), out, null);
 			out.close();
 
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java
index cc062db..296725b 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java
@@ -138,8 +138,10 @@ protected boolean isExportOk(HttpServletRequest req, String repositoryName,
 			Repository db) throws IOException {
 		if (isExportAll())
 			return true;
-		else
+		else if (db.getDirectory() != null)
 			return new File(db.getDirectory(), "git-daemon-export-ok").exists();
+		else
+			return false;
 	}
 
 	private static boolean isUnreasonableName(final String name) {
diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
index 370bd40..21dd608 100644
--- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
@@ -29,5 +29,6 @@
  org.eclipse.jgit.junit;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.lib;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)",
+ org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.transport;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.util;version="[0.9.0,0.10.0)"
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java
index 47d7806..db4aa80 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java
@@ -62,23 +62,24 @@
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.RepositoryConfig;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileRepository;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.Transport;
 import org.eclipse.jgit.transport.URIish;
 
 public class AdvertiseErrorTest extends HttpTestCase {
-	private Repository remoteRepository;
+	private FileRepository remoteRepository;
 
 	private URIish remoteURI;
 
 	protected void setUp() throws Exception {
 		super.setUp();
 
-		final TestRepository src = createTestRepository();
+		final TestRepository<FileRepository> src = createTestRepository();
 		final String srcName = src.getRepository().getDirectory().getName();
 
 		ServletContextHandler app = server.addContext("/git");
@@ -114,7 +115,7 @@ public ReceivePack create(HttpServletRequest req, Repository db)
 		remoteRepository = src.getRepository();
 		remoteURI = toURIish(app, srcName);
 
-		RepositoryConfig cfg = remoteRepository.getConfig();
+		FileBasedConfig cfg = remoteRepository.getConfig();
 		cfg.setBoolean("http", null, "receivepack", true);
 		cfg.save();
 	}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
index 224ea05..18f8dc9 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
@@ -64,9 +64,10 @@
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.RepositoryConfig;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileRepository;
 import org.eclipse.jgit.transport.PreReceiveHook;
 import org.eclipse.jgit.transport.PushResult;
 import org.eclipse.jgit.transport.ReceiveCommand;
@@ -76,14 +77,14 @@
 import org.eclipse.jgit.transport.URIish;
 
 public class HookMessageTest extends HttpTestCase {
-	private Repository remoteRepository;
+	private FileRepository remoteRepository;
 
 	private URIish remoteURI;
 
 	protected void setUp() throws Exception {
 		super.setUp();
 
-		final TestRepository src = createTestRepository();
+		final TestRepository<FileRepository> src = createTestRepository();
 		final String srcName = src.getRepository().getDirectory().getName();
 
 		ServletContextHandler app = server.addContext("/git");
@@ -124,7 +125,7 @@ public void onPreReceive(ReceivePack rp,
 		remoteRepository = src.getRepository();
 		remoteURI = toURIish(app, srcName);
 
-		RepositoryConfig cfg = remoteRepository.getConfig();
+		FileBasedConfig cfg = remoteRepository.getConfig();
 		cfg.setBoolean("http", null, "receivepack", true);
 		cfg.save();
 	}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
index 729466d..4cc141b 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
@@ -66,12 +66,13 @@
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileRepository;
 import org.eclipse.jgit.transport.FetchConnection;
 import org.eclipse.jgit.transport.Transport;
 import org.eclipse.jgit.transport.URIish;
 
 public class HttpClientTests extends HttpTestCase {
-	private TestRepository remoteRepository;
+	private TestRepository<FileRepository> remoteRepository;
 
 	private URIish dumbAuthNoneURI;
 
@@ -95,7 +96,7 @@ protected void setUp() throws Exception {
 
 		server.setUp();
 
-		final String srcName = nameOf(remoteRepository);
+		final String srcName = nameOf(remoteRepository.getRepository());
 		dumbAuthNoneURI = toURIish(dNone, srcName);
 		dumbAuthBasicURI = toURIish(dBasic, srcName);
 
@@ -119,10 +120,10 @@ private ServletContextHandler smart(final String path) {
 			public Repository open(HttpServletRequest req, String name)
 					throws RepositoryNotFoundException,
 					ServiceNotEnabledException {
-				if (!name.equals(nameOf(remoteRepository)))
+				final FileRepository db = remoteRepository.getRepository();
+				if (!name.equals(nameOf(db)))
 					throw new RepositoryNotFoundException(name);
 
-				final Repository db = remoteRepository.getRepository();
 				db.incrementOpen();
 				return db;
 			}
@@ -133,8 +134,8 @@ public Repository open(HttpServletRequest req, String name)
 		return ctx;
 	}
 
-	private static String nameOf(final TestRepository db) {
-		return db.getRepository().getDirectory().getName();
+	private static String nameOf(final FileRepository db) {
+		return db.getDirectory().getName();
 	}
 
 	public void testRepositoryNotFound_Dumb() throws Exception {
@@ -198,7 +199,7 @@ public void testListRemote_Dumb_DetachedHEAD() throws Exception {
 	}
 
 	public void testListRemote_Dumb_NoHEAD() throws Exception {
-		Repository src = remoteRepository.getRepository();
+		FileRepository src = remoteRepository.getRepository();
 		File headref = new File(src.getDirectory(), Constants.HEAD);
 		assertTrue("HEAD used to be present", headref.delete());
 		assertFalse("HEAD is gone", headref.exists());
@@ -306,7 +307,7 @@ public void testListRemote_Smart_UploadPackNeedsAuth() throws Exception {
 	}
 
 	public void testListRemote_Smart_UploadPackDisabled() throws Exception {
-		Repository src = remoteRepository.getRepository();
+		FileRepository src = remoteRepository.getRepository();
 		src.getConfig().setBoolean("http", null, "uploadpack", false);
 		src.getConfig().save();
 
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index f7b3bdb..a7b51c6 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -79,11 +79,12 @@
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.ReflogReader;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.RepositoryConfig;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.storage.file.ReflogReader;
 import org.eclipse.jgit.transport.FetchConnection;
 import org.eclipse.jgit.transport.HttpTransport;
 import org.eclipse.jgit.transport.RemoteRefUpdate;
@@ -94,7 +95,7 @@
 public class SmartClientSmartServerTest extends HttpTestCase {
 	private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding";
 
-	private Repository remoteRepository;
+	private FileRepository remoteRepository;
 
 	private URIish remoteURI;
 
@@ -107,7 +108,7 @@ public class SmartClientSmartServerTest extends HttpTestCase {
 	protected void setUp() throws Exception {
 		super.setUp();
 
-		final TestRepository src = createTestRepository();
+		final TestRepository<FileRepository> src = createTestRepository();
 		final String srcName = src.getRepository().getDirectory().getName();
 
 		ServletContextHandler app = server.addContext("/git");
@@ -489,10 +490,10 @@ public void testPush_CreateBranch() throws Exception {
 	}
 
 	public void testPush_ChunkedEncoding() throws Exception {
-		final TestRepository src = createTestRepository();
+		final TestRepository<FileRepository> src = createTestRepository();
 		final RevBlob Q_bin = src.blob(new TestRng("Q").nextBytes(128 * 1024));
 		final RevCommit Q = src.commit().add("Q", Q_bin).create();
-		final Repository db = src.getRepository();
+		final FileRepository db = src.getRepository();
 		final String dstName = Constants.R_HEADS + "new.branch";
 		Transport t;
 
@@ -547,7 +548,7 @@ public void testPush_ChunkedEncoding() throws Exception {
 	}
 
 	private void enableReceivePack() throws IOException {
-		final RepositoryConfig cfg = remoteRepository.getConfig();
+		final FileBasedConfig cfg = remoteRepository.getConfig();
 		cfg.setBoolean("http", null, "receivepack", true);
 		cfg.save();
 	}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java
index e259757..313b6ad 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java
@@ -61,6 +61,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.storage.file.FileRepository;
 import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.URIish;
@@ -82,8 +83,9 @@ protected void tearDown() throws Exception {
 		super.tearDown();
 	}
 
-	protected TestRepository createTestRepository() throws Exception {
-		return new TestRepository(createBareRepository());
+	protected TestRepository<FileRepository> createTestRepository()
+			throws IOException {
+		return new TestRepository<FileRepository>(createBareRepository());
 	}
 
 	protected URIish toURIish(String path) throws URISyntaxException {
diff --git a/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF b/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF
index 4ff6144..31a19b5 100644
--- a/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF
@@ -15,6 +15,7 @@
  org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)",
+ org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.transport;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)",
diff --git a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogGenerator.java b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogGenerator.java
index f64c329..433d433 100644
--- a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogGenerator.java
+++ b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogGenerator.java
@@ -78,15 +78,13 @@
 import org.eclipse.jgit.diff.MyersDiff;
 import org.eclipse.jgit.diff.RawText;
 import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.iplog.Committer.ActiveRange;
 import org.eclipse.jgit.lib.BlobBasedConfig;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.MutableObjectId;
-import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.WindowCursor;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -144,7 +142,7 @@ public class IpLogGenerator {
 
 	private NameConflictTreeWalk tw;
 
-	private final WindowCursor curs = new WindowCursor();
+	private ObjectReader curs;
 
 	private final MutableObjectId idbuf = new MutableObjectId();
 
@@ -184,8 +182,9 @@ public void scan(Repository repo, RevCommit startCommit, String version)
 			throws IOException, ConfigInvalidException {
 		try {
 			db = repo;
-			rw = new RevWalk(db);
-			tw = new NameConflictTreeWalk(db);
+			curs = db.newObjectReader();
+			rw = new RevWalk(curs);
+			tw = new NameConflictTreeWalk(curs);
 
 			RevCommit c = rw.parseCommit(startCommit);
 
@@ -194,7 +193,7 @@ public void scan(Repository repo, RevCommit startCommit, String version)
 			scanProjectCommits(meta.getProjects().get(0), c);
 			commits.add(c);
 		} finally {
-			WindowCursor.release(curs);
+			curs.release();
 			db = null;
 			rw = null;
 			tw = null;
@@ -417,10 +416,7 @@ private void scanProjectCommits(Project proj, RevCommit start)
 
 	private byte[] openBlob(int side) throws IOException {
 		tw.getObjectId(idbuf, side);
-		ObjectLoader ldr = db.openObject(curs, idbuf);
-		if (ldr == null)
-			throw new MissingObjectException(idbuf.copy(), Constants.OBJ_BLOB);
-		return ldr.getCachedBytes();
+		return curs.open(idbuf, Constants.OBJ_BLOB).getCachedBytes();
 	}
 
 	/**
diff --git a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogMeta.java b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogMeta.java
index 89695bd..372469d 100644
--- a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogMeta.java
+++ b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogMeta.java
@@ -58,9 +58,10 @@
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileBasedConfig;
-import org.eclipse.jgit.lib.LockFile;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.file.LockFile;
+import org.eclipse.jgit.util.FS;
 
 /**
  * Manages the {@code .eclipse_iplog} file in a project.
@@ -167,6 +168,9 @@ private List<Project> parseProjects(final Config cfg,
 	 *
 	 * @param file
 	 *            local file to update with current CQ records.
+	 * @param fs
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
 	 * @param base
 	 *            base https:// URL of the IPzilla server.
 	 * @param username
@@ -181,16 +185,16 @@ private List<Project> parseProjects(final Config cfg,
 	 *             the local file cannot be read, as it is not a valid
 	 *             configuration file format.
 	 */
-	public void syncCQs(File file, URL base, String username, String password)
-			throws IOException, ConfigInvalidException {
+	public void syncCQs(File file, FS fs, URL base, String username,
+			String password) throws IOException, ConfigInvalidException {
 		if (!file.getParentFile().exists())
 			file.getParentFile().mkdirs();
 
-		LockFile lf = new LockFile(file);
+		LockFile lf = new LockFile(file, fs);
 		if (!lf.lock())
 			throw new IOException(MessageFormat.format(IpLogText.get().cannotLock, file));
 		try {
-			FileBasedConfig cfg = new FileBasedConfig(file);
+			FileBasedConfig cfg = new FileBasedConfig(file, fs);
 			cfg.load();
 			loadFrom(cfg);
 
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index 051079b..7f4dcfd 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -18,6 +18,8 @@
  org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)",
+ org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)",
+ org.eclipse.jgit.storage.pack;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.transport;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)",
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
index 001deb2..47956e5 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
@@ -62,12 +62,14 @@
 
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileBasedConfig;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryCache;
-import org.eclipse.jgit.lib.WindowCache;
-import org.eclipse.jgit.lib.WindowCacheConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.storage.file.WindowCache;
+import org.eclipse.jgit.storage.file.WindowCacheConfig;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.SystemReader;
 
@@ -127,7 +129,7 @@ public void run() {
 
 		mockSystemReader = new MockSystemReader();
 		mockSystemReader.userGitConfig = new FileBasedConfig(new File(trash,
-				"usergitconfig"));
+				"usergitconfig"), FS.DETECTED);
 		ceilTestDirectories(getCeilings());
 		SystemReader.setInstance(mockSystemReader);
 
@@ -259,7 +261,7 @@ private static void reportDeleteFailure(final String testName,
 	 * @throws IOException
 	 *             the repository could not be created in the temporary area
 	 */
-	protected Repository createBareRepository() throws IOException {
+	protected FileRepository createBareRepository() throws IOException {
 		return createRepository(true /* bare */);
 	}
 
@@ -270,7 +272,7 @@ protected Repository createBareRepository() throws IOException {
 	 * @throws IOException
 	 *             the repository could not be created in the temporary area
 	 */
-	protected Repository createWorkRepository() throws IOException {
+	protected FileRepository createWorkRepository() throws IOException {
 		return createRepository(false /* not bare */);
 	}
 
@@ -284,11 +286,11 @@ protected Repository createWorkRepository() throws IOException {
 	 * @throws IOException
 	 *             the repository could not be created in the temporary area
 	 */
-	private Repository createRepository(boolean bare) throws IOException {
+	private FileRepository createRepository(boolean bare) throws IOException {
 		String uniqueId = System.currentTimeMillis() + "_" + (testCount++);
 		String gitdirName = "test" + uniqueId + (bare ? "" : "/") + Constants.DOT_GIT;
 		File gitdir = new File(trash, gitdirName).getCanonicalFile();
-		Repository db = new Repository(gitdir);
+		FileRepository db = new FileRepository(gitdir);
 
 		assertFalse(gitdir.exists());
 		db.create();
@@ -323,7 +325,7 @@ protected int runHook(final Repository db, final File hook,
 		putPersonIdent(env, "AUTHOR", author);
 		putPersonIdent(env, "COMMITTER", committer);
 
-		final File cwd = db.getWorkDir();
+		final File cwd = db.getWorkTree();
 		final Process p = Runtime.getRuntime().exec(argv, toEnvArray(env), cwd);
 		p.getOutputStream().close();
 		p.getErrorStream().close();
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
index c502fb6..5c2e77f 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
@@ -52,7 +52,7 @@
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.SystemReader;
 
@@ -67,7 +67,7 @@ public MockSystemReader() {
 		init(Constants.GIT_AUTHOR_EMAIL_KEY);
 		init(Constants.GIT_COMMITTER_NAME_KEY);
 		init(Constants.GIT_COMMITTER_EMAIL_KEY);
-		userGitConfig = new FileBasedConfig(null) {
+		userGitConfig = new FileBasedConfig(null, null) {
 			@Override
 			public void load() throws IOException, ConfigInvalidException {
 				// Do nothing
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
index 59504aa..afe1c0b 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
@@ -73,22 +73,16 @@
 import org.eclipse.jgit.lib.Commit;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.LockFile;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectChecker;
-import org.eclipse.jgit.lib.ObjectDatabase;
-import org.eclipse.jgit.lib.ObjectDirectory;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectWriter;
-import org.eclipse.jgit.lib.PackFile;
-import org.eclipse.jgit.lib.PackWriter;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.RefWriter;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.Tag;
-import org.eclipse.jgit.lib.PackIndex.MutableEntry;
 import org.eclipse.jgit.revwalk.ObjectWalk;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -96,11 +90,22 @@
 import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.storage.file.LockFile;
+import org.eclipse.jgit.storage.file.ObjectDirectory;
+import org.eclipse.jgit.storage.file.PackFile;
+import org.eclipse.jgit.storage.file.PackIndex.MutableEntry;
+import org.eclipse.jgit.storage.pack.PackWriter;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
 
-/** Wrapper to make creating test data easier. */
-public class TestRepository {
+/**
+ * Wrapper to make creating test data easier.
+ *
+ * @param <R>
+ *            type of Repository the test data is stored on.
+ */
+public class TestRepository<R extends Repository> {
 	private static final PersonIdent author;
 
 	private static final PersonIdent committer;
@@ -119,11 +124,11 @@ public class TestRepository {
 		committer = new PersonIdent(cn, ce, now, tz);
 	}
 
-	private final Repository db;
+	private final R db;
 
 	private final RevWalk pool;
 
-	private final ObjectWriter writer;
+	private final ObjectInserter inserter;
 
 	private long now;
 
@@ -132,9 +137,9 @@ public class TestRepository {
 	 *
 	 * @param db
 	 *            the test repository to write into.
-	 * @throws Exception
+	 * @throws IOException
 	 */
-	public TestRepository(Repository db) throws Exception {
+	public TestRepository(R db) throws IOException {
 		this(db, new RevWalk(db));
 	}
 
@@ -145,17 +150,17 @@ public TestRepository(Repository db) throws Exception {
 	 *            the test repository to write into.
 	 * @param rw
 	 *            the RevObject pool to use for object lookup.
-	 * @throws Exception
+	 * @throws IOException
 	 */
-	public TestRepository(Repository db, RevWalk rw) throws Exception {
+	public TestRepository(R db, RevWalk rw) throws IOException {
 		this.db = db;
 		this.pool = rw;
-		this.writer = new ObjectWriter(db);
+		this.inserter = db.newObjectInserter();
 		this.now = 1236977987000L;
 	}
 
 	/** @return the repository this helper class operates against. */
-	public Repository getRepository() {
+	public R getRepository() {
 		return db;
 	}
 
@@ -200,7 +205,14 @@ public RevBlob blob(final String content) throws Exception {
 	 * @throws Exception
 	 */
 	public RevBlob blob(final byte[] content) throws Exception {
-		return pool.lookupBlob(writer.writeBlob(content));
+		ObjectId id;
+		try {
+			id = inserter.insert(Constants.OBJ_BLOB, content);
+			inserter.flush();
+		} finally {
+			inserter.release();
+		}
+		return pool.lookupBlob(id);
 	}
 
 	/**
@@ -236,7 +248,14 @@ public RevTree tree(final DirCacheEntry... entries) throws Exception {
 		for (final DirCacheEntry e : entries)
 			b.add(e);
 		b.finish();
-		return pool.lookupTree(dc.writeTree(writer));
+		ObjectId root;
+		try {
+			root = dc.writeTree(inserter);
+			inserter.flush();
+		} finally {
+			inserter.release();
+		}
+		return pool.lookupTree(root);
 	}
 
 	/**
@@ -253,7 +272,7 @@ public RevTree tree(final DirCacheEntry... entries) throws Exception {
 	 */
 	public RevObject get(final RevTree tree, final String path)
 			throws AssertionFailedError, Exception {
-		final TreeWalk tw = new TreeWalk(db);
+		final TreeWalk tw = new TreeWalk(pool.getObjectReader());
 		tw.setFilter(PathFilterGroup.createFromStrings(Collections
 				.singleton(path)));
 		tw.reset(tree);
@@ -346,7 +365,14 @@ public RevCommit commit(final int secDelta, final RevTree tree,
 		c.setAuthor(new PersonIdent(author, new Date(now)));
 		c.setCommitter(new PersonIdent(committer, new Date(now)));
 		c.setMessage("");
-		return pool.lookupCommit(writer.writeCommit(c));
+		ObjectId id;
+		try {
+			id = inserter.insert(Constants.OBJ_COMMIT, inserter.format(c));
+			inserter.flush();
+		} finally {
+			inserter.release();
+		}
+		return pool.lookupCommit(id);
 	}
 
 	/** @return a new commit builder. */
@@ -377,7 +403,14 @@ public RevTag tag(final String name, final RevObject dst) throws Exception {
 		t.setTag(name);
 		t.setTagger(new PersonIdent(committer, new Date(now)));
 		t.setMessage("");
-		return (RevTag) pool.lookupAny(writer.writeTag(t), Constants.OBJ_TAG);
+		ObjectId id;
+		try {
+			id = inserter.insert(Constants.OBJ_TAG, inserter.format(t));
+			inserter.flush();
+		} finally {
+			inserter.release();
+		}
+		return (RevTag) pool.lookupAny(id, Constants.OBJ_TAG);
 	}
 
 	/**
@@ -443,25 +476,27 @@ public <T extends AnyObjectId> T update(String ref, T obj) throws Exception {
 	 * @throws Exception
 	 */
 	public void updateServerInfo() throws Exception {
-		final ObjectDatabase odb = db.getObjectDatabase();
-		if (odb instanceof ObjectDirectory) {
-			RefWriter rw = new RefWriter(db.getAllRefs().values()) {
+		if (db instanceof FileRepository) {
+			final FileRepository fr = (FileRepository) db;
+			RefWriter rw = new RefWriter(fr.getAllRefs().values()) {
 				@Override
 				protected void writeFile(final String name, final byte[] bin)
 						throws IOException {
-					TestRepository.this.writeFile(name, bin);
+					File path = new File(fr.getDirectory(), name);
+					TestRepository.this.writeFile(path, bin);
 				}
 			};
 			rw.writePackedRefs();
 			rw.writeInfoRefs();
 
 			final StringBuilder w = new StringBuilder();
-			for (PackFile p : ((ObjectDirectory) odb).getPacks()) {
+			for (PackFile p : fr.getObjectDatabase().getPacks()) {
 				w.append("P ");
 				w.append(p.getPackFile().getName());
 				w.append('\n');
 			}
-			writeFile("objects/info/packs", Constants.encodeASCII(w.toString()));
+			writeFile(new File(new File(fr.getObjectDatabase().getDirectory(),
+					"info"), "packs"), Constants.encodeASCII(w.toString()));
 		}
 	}
 
@@ -528,7 +563,7 @@ public void fsck(RevObject... tips) throws MissingObjectException,
 			if (o == null)
 				break;
 
-			final byte[] bin = db.openObject(o).getCachedBytes();
+			final byte[] bin = db.open(o, o.getType()).getCachedBytes();
 			oc.checkCommit(bin);
 			assertHash(o, bin);
 		}
@@ -538,7 +573,7 @@ public void fsck(RevObject... tips) throws MissingObjectException,
 			if (o == null)
 				break;
 
-			final byte[] bin = db.openObject(o).getCachedBytes();
+			final byte[] bin = db.open(o, o.getType()).getCachedBytes();
 			oc.check(o.getType(), bin);
 			assertHash(o, bin);
 		}
@@ -563,38 +598,46 @@ private static void assertHash(RevObject id, byte[] bin) {
 	 * @throws Exception
 	 */
 	public void packAndPrune() throws Exception {
-		final ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase();
-		final PackWriter pw = new PackWriter(db, NullProgressMonitor.INSTANCE);
+		if (db.getObjectDatabase() instanceof ObjectDirectory) {
+			ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase();
+			NullProgressMonitor m = NullProgressMonitor.INSTANCE;
 
-		Set<ObjectId> all = new HashSet<ObjectId>();
-		for (Ref r : db.getAllRefs().values())
-			all.add(r.getObjectId());
-		pw.preparePack(all, Collections.<ObjectId> emptySet());
+			final File pack, idx;
+			PackWriter pw = new PackWriter(db);
+			try {
+				Set<ObjectId> all = new HashSet<ObjectId>();
+				for (Ref r : db.getAllRefs().values())
+					all.add(r.getObjectId());
+				pw.preparePack(m, all, Collections.<ObjectId> emptySet());
 
-		final ObjectId name = pw.computeName();
-		OutputStream out;
+				final ObjectId name = pw.computeName();
+				OutputStream out;
 
-		final File pack = nameFor(odb, name, ".pack");
-		out = new BufferedOutputStream(new FileOutputStream(pack));
-		try {
-			pw.writePack(out);
-		} finally {
-			out.close();
+				pack = nameFor(odb, name, ".pack");
+				out = new BufferedOutputStream(new FileOutputStream(pack));
+				try {
+					pw.writePack(m, m, out);
+				} finally {
+					out.close();
+				}
+				pack.setReadOnly();
+
+				idx = nameFor(odb, name, ".idx");
+				out = new BufferedOutputStream(new FileOutputStream(idx));
+				try {
+					pw.writeIndex(out);
+				} finally {
+					out.close();
+				}
+				idx.setReadOnly();
+			} finally {
+				pw.release();
+			}
+
+			odb.openPack(pack, idx);
+			updateServerInfo();
+			prunePacked(odb);
 		}
-		pack.setReadOnly();
-
-		final File idx = nameFor(odb, name, ".idx");
-		out = new BufferedOutputStream(new FileOutputStream(idx));
-		try {
-			pw.writeIndex(out);
-		} finally {
-			out.close();
-		}
-		idx.setReadOnly();
-
-		odb.openPack(pack, idx);
-		updateServerInfo();
-		prunePacked(odb);
 	}
 
 	private void prunePacked(ObjectDirectory odb) {
@@ -609,10 +652,9 @@ private static File nameFor(ObjectDirectory odb, ObjectId name, String t) {
 		return new File(packdir, "pack-" + name.name() + t);
 	}
 
-	private void writeFile(final String name, final byte[] bin)
-			throws IOException, ObjectWritingException {
-		final File p = new File(db.getDirectory(), name);
-		final LockFile lck = new LockFile(p);
+	private void writeFile(final File p, final byte[] bin) throws IOException,
+			ObjectWritingException {
+		final LockFile lck = new LockFile(p, db.getFS());
 		if (!lck.lock())
 			throw new ObjectWritingException("Can't write " + p);
 		try {
@@ -711,7 +753,8 @@ public CommitBuilder parent(RevCommit p) throws Exception {
 			if (parents.isEmpty()) {
 				DirCacheBuilder b = tree.builder();
 				parseBody(p);
-				b.addTree(new byte[0], DirCacheEntry.STAGE_0, db, p.getTree());
+				b.addTree(new byte[0], DirCacheEntry.STAGE_0, pool
+						.getObjectReader(), p.getTree());
 				b.finish();
 			}
 			parents.add(p);
@@ -769,13 +812,21 @@ public RevCommit create() throws Exception {
 				TestRepository.this.tick(tick);
 
 				final Commit c = new Commit(db);
-				c.setTreeId(pool.lookupTree(tree.writeTree(writer)));
 				c.setParentIds(parents.toArray(new RevCommit[parents.size()]));
 				c.setAuthor(new PersonIdent(author, new Date(now)));
 				c.setCommitter(new PersonIdent(committer, new Date(now)));
 				c.setMessage(message);
 
-				self = pool.lookupCommit(writer.writeCommit(c));
+				ObjectId commitId;
+				try {
+					c.setTreeId(tree.writeTree(inserter));
+					commitId = inserter.insert(Constants.OBJ_COMMIT, inserter
+							.format(c));
+					inserter.flush();
+				} finally {
+					inserter.release();
+				}
+				self = pool.lookupCommit(commitId);
 
 				if (branch != null)
 					branch.update(self);
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 8628896..d6eac23 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -17,6 +17,8 @@
  org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)",
+ org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)",
+ org.eclipse.jgit.storage.pack;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.transport;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)",
diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
index 0d4a140..075cade 100644
--- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
+++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
@@ -30,6 +30,7 @@
 org.eclipse.jgit.pgm.debug.ShowCacheTree
 org.eclipse.jgit.pgm.debug.ShowCommands
 org.eclipse.jgit.pgm.debug.ShowDirCache
+org.eclipse.jgit.pgm.debug.ShowPackDelta
 org.eclipse.jgit.pgm.debug.WriteDirCache
 
 org.eclipse.jgit.pgm.eclipse.Iplog
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties
index e879d6b..2fff6d4 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties
@@ -14,6 +14,7 @@
 branchIsNotAnAncestorOfYourCurrentHEAD=The branch '{0}' is not an ancestor of your current HEAD.\nIf you are sure you want to delete it, run 'jgit branch -D {0}'.
 branchNotFound=branch '{0}' not found.
 cacheTreePathInfo="{0}": {1} entries, {2} children
+configFileNotFound=configuration file {0} not found
 cannotBeRenamed={0} cannot be renamed
 cannotChekoutNoHeadsAdvertisedByRemote=cannot checkout; no HEAD advertised by remote
 cannotCreateCommand=Cannot create command {0}
@@ -61,6 +62,7 @@
 metaVar_command=command
 metaVar_commitOrTag=COMMIT|TAG
 metaVar_commitish=commit-ish
+metaVar_configFile=FILE
 metaVar_connProp=conn.prop
 metaVar_directory=DIRECTORY
 metaVar_file=FILE
@@ -138,6 +140,7 @@
 usage_beMoreVerbose=be more verbose
 usage_beVerbose=be verbose
 usage_cloneRepositoryIntoNewDir=Clone a repository into a new directory
+usage_configFile=configuration file
 usage_configureTheServiceInDaemonServicename=configure the service in daemon.servicename
 usage_deleteBranchEvenIfNotMerged=delete branch (even if not merged)
 usage_deleteFullyMergedBranch=delete fully merged branch
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java
index bae895c..14dcb1f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java
@@ -67,6 +67,7 @@ public static CLIText get() {
 	/***/ public String branchIsNotAnAncestorOfYourCurrentHEAD;
 	/***/ public String branchNotFound;
 	/***/ public String cacheTreePathInfo;
+	/***/ public String configFileNotFound;
 	/***/ public String cannotBeRenamed;
 	/***/ public String cannotChekoutNoHeadsAdvertisedByRemote;
 	/***/ public String cannotCreateCommand;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
index b0f51ec..22302bb 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
@@ -61,10 +61,10 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefComparator;
 import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.TextProgressMonitor;
 import org.eclipse.jgit.lib.Tree;
 import org.eclipse.jgit.lib.WorkDirCheckout;
+import org.eclipse.jgit.storage.file.FileRepository;
 import org.eclipse.jgit.transport.FetchResult;
 import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.transport.RemoteConfig;
@@ -82,6 +82,8 @@ class Clone extends AbstractFetchCommand {
 	@Argument(index = 1, metaVar = "metaVar_directory")
 	private String localName;
 
+	private FileRepository dst;
+
 	@Override
 	protected final boolean requiresRepository() {
 		return false;
@@ -103,10 +105,11 @@ protected void run() throws Exception {
 		if (gitdir == null)
 			gitdir = new File(localName, Constants.DOT_GIT);
 
-		db = new Repository(gitdir);
-		db.create();
-		db.getConfig().setBoolean("core", null, "bare", false);
-		db.getConfig().save();
+		dst = new FileRepository(gitdir);
+		dst.create();
+		dst.getConfig().setBoolean("core", null, "bare", false);
+		dst.getConfig().save();
+		db = dst;
 
 		out.format(CLIText.get().initializedEmptyGitRepositoryIn, gitdir.getAbsolutePath());
 		out.println();
@@ -120,13 +123,13 @@ protected void run() throws Exception {
 
 	private void saveRemote(final URIish uri) throws URISyntaxException,
 			IOException {
-		final RemoteConfig rc = new RemoteConfig(db.getConfig(), remoteName);
+		final RemoteConfig rc = new RemoteConfig(dst.getConfig(), remoteName);
 		rc.addURI(uri);
 		rc.addFetchRefSpec(new RefSpec().setForceUpdate(true)
 				.setSourceDestination(Constants.R_HEADS + "*",
 						Constants.R_REMOTES + remoteName + "/*"));
-		rc.update(db.getConfig());
-		db.getConfig().save();
+		rc.update(dst.getConfig());
+		dst.getConfig().save();
 	}
 
 	private FetchResult runFetch() throws NotSupportedException,
@@ -180,7 +183,7 @@ private void doCheckout(final Ref branch) throws IOException {
 		final Tree tree = commit.getTree();
 		final WorkDirCheckout co;
 
-		co = new WorkDirCheckout(db, db.getWorkDir(), index, tree);
+		co = new WorkDirCheckout(db, db.getWorkTree(), index, tree);
 		co.checkout();
 		index.write();
 	}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
index f015a9e..3cca87a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
@@ -48,13 +48,22 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executors;
 
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.file.WindowCache;
+import org.eclipse.jgit.storage.file.WindowCacheConfig;
+import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.transport.DaemonService;
+import org.eclipse.jgit.util.FS;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
-import org.eclipse.jgit.transport.DaemonService;
 
 @Command(common = true, usage = "usage_exportRepositoriesOverGit")
 class Daemon extends TextBuiltin {
+	@Option(name = "--config-file", metaVar = "metaVar_configFile", usage = "usage_configFile")
+	File configFile;
+
 	@Option(name = "--port", metaVar = "metaVar_port", usage = "usage_portNumberToListenOn")
 	int port = org.eclipse.jgit.transport.Daemon.DEFAULT_PORT;
 
@@ -89,12 +98,38 @@ protected boolean requiresRepository() {
 
 	@Override
 	protected void run() throws Exception {
+		PackConfig packConfig = new PackConfig();
+
+		if (configFile != null) {
+			if (!configFile.exists()) {
+				throw die(MessageFormat.format(
+						CLIText.get().configFileNotFound, //
+						configFile.getAbsolutePath()));
+			}
+
+			FileBasedConfig cfg = new FileBasedConfig(configFile, FS.DETECTED);
+			cfg.load();
+
+			WindowCacheConfig wcc = new WindowCacheConfig();
+			wcc.fromConfig(cfg);
+			WindowCache.reconfigure(wcc);
+
+			packConfig.fromConfig(cfg);
+		}
+
+		int threads = packConfig.getThreads();
+		if (threads <= 0)
+			threads = Runtime.getRuntime().availableProcessors();
+		if (1 < threads)
+			packConfig.setExecutor(Executors.newFixedThreadPool(threads));
+
 		final org.eclipse.jgit.transport.Daemon d;
 
 		d = new org.eclipse.jgit.transport.Daemon(
 				host != null ? new InetSocketAddress(host, port)
 						: new InetSocketAddress(port));
 		d.setExportAll(exportAll);
+		d.setPackConfig(packConfig);
 		if (0 <= timeout)
 			d.setTimeout(timeout);
 
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
index 77ed730..ebdb74f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
@@ -153,22 +153,22 @@ static void nameStatus(PrintWriter out, List<DiffEntry> files) {
 		for (DiffEntry ent : files) {
 			switch (ent.getChangeType()) {
 			case ADD:
-				out.println("A\t" + ent.getNewName());
+				out.println("A\t" + ent.getNewPath());
 				break;
 			case DELETE:
-				out.println("D\t" + ent.getOldName());
+				out.println("D\t" + ent.getOldPath());
 				break;
 			case MODIFY:
-				out.println("M\t" + ent.getNewName());
+				out.println("M\t" + ent.getNewPath());
 				break;
 			case COPY:
 				out.format("C%1$03d\t%2$s\t%3$s", ent.getScore(), //
-						ent.getOldName(), ent.getNewName());
+						ent.getOldPath(), ent.getNewPath());
 				out.println();
 				break;
 			case RENAME:
 				out.format("R%1$03d\t%2$s\t%3$s", ent.getScore(), //
-						ent.getOldName(), ent.getNewName());
+						ent.getOldPath(), ent.getNewPath());
 				out.println();
 				break;
 			}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java
index 3dfd8ff..ae11f67 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java
@@ -125,10 +125,12 @@ protected RevWalk createWalk() {
 	}
 
 	private String repoName() {
-		final File f = db.getDirectory();
-		String n = f.getName();
+		final File gitDir = db.getDirectory();
+		if (gitDir == null)
+			return db.toString();
+		String n = gitDir.getName();
 		if (Constants.DOT_GIT.equals(n))
-			n = f.getParentFile().getName();
+			n = gitDir.getParentFile().getName();
 		return n;
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java
index 35fd2a5..640c8ef 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java
@@ -49,6 +49,7 @@
 
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
+import org.eclipse.jgit.lib.CoreConfig;
 import org.eclipse.jgit.lib.TextProgressMonitor;
 
 class IndexPack extends TextBuiltin {
@@ -64,7 +65,8 @@ class IndexPack extends TextBuiltin {
 	@Override
 	protected void run() throws Exception {
 		if (indexVersion == -1)
-			indexVersion = db.getConfig().getCore().getPackIndexVersion();
+			indexVersion = db.getConfig().get(CoreConfig.KEY)
+					.getPackIndexVersion();
 		final BufferedInputStream in;
 		final org.eclipse.jgit.transport.IndexPack ip;
 		in = new BufferedInputStream(System.in);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java
index d8c7bdf..c5a696a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java
@@ -50,7 +50,7 @@
 
 import org.kohsuke.args4j.Option;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileRepository;
 
 @Command(common = true, usage = "usage_CreateAnEmptyGitRepository")
 class Init extends TextBuiltin {
@@ -66,7 +66,7 @@ protected final boolean requiresRepository() {
 	protected void run() throws Exception {
 		if (gitdir == null)
 			gitdir = new File(bare ? "." : Constants.DOT_GIT);
-		db = new Repository(gitdir);
+		db = new FileRepository(gitdir);
 		db.create(bare);
 		out.println(MessageFormat.format(CLIText.get().initializedEmptyGitRepositoryIn, gitdir.getAbsolutePath()));
 	}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
index 48a0591..fbc019f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
@@ -242,7 +242,7 @@ private boolean isAdd(List<DiffEntry> files) {
 		String oldPath = ((FollowFilter) pathFilter).getPath();
 		for (DiffEntry ent : files) {
 			if (ent.getChangeType() == ChangeType.ADD
-					&& ent.getNewName().equals(oldPath))
+					&& ent.getNewPath().equals(oldPath))
 				return true;
 		}
 		return false;
@@ -251,8 +251,8 @@ private boolean isAdd(List<DiffEntry> files) {
 	private List<DiffEntry> updateFollowFilter(List<DiffEntry> files) {
 		String oldPath = ((FollowFilter) pathFilter).getPath();
 		for (DiffEntry ent : files) {
-			if (isRename(ent) && ent.getNewName().equals(oldPath)) {
-				pathFilter = FollowFilter.create(ent.getOldName());
+			if (isRename(ent) && ent.getNewPath().equals(oldPath)) {
+				pathFilter = FollowFilter.create(ent.getOldPath());
 				return Collections.singletonList(ent);
 			}
 		}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
index 306ac81..ab11062 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
@@ -51,20 +51,15 @@
 import java.net.URL;
 import java.text.MessageFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 import org.eclipse.jgit.awtui.AwtAuthenticator;
 import org.eclipse.jgit.awtui.AwtSshSessionFactory;
 import org.eclipse.jgit.errors.TransportException;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryBuilder;
 import org.eclipse.jgit.pgm.opt.CmdLineParser;
 import org.eclipse.jgit.pgm.opt.SubcommandHandler;
 import org.eclipse.jgit.util.CachedAuthenticator;
-import org.eclipse.jgit.util.SystemReader;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.CmdLineException;
 import org.kohsuke.args4j.ExampleMode;
@@ -168,51 +163,17 @@ private void execute(final String[] argv) throws Exception {
 
 		final TextBuiltin cmd = subcommand;
 		if (cmd.requiresRepository()) {
-			if (gitdir == null) {
-				String gitDirEnv = SystemReader.getInstance().getenv(Constants.GIT_DIR_KEY);
-				if (gitDirEnv != null)
-					gitdir = new File(gitDirEnv);
-			}
-			if (gitdir == null)
-				gitdir = findGitDir();
-
-			File gitworktree;
-			String gitWorkTreeEnv = SystemReader.getInstance().getenv(Constants.GIT_WORK_TREE_KEY);
-			if (gitWorkTreeEnv != null)
-				gitworktree = new File(gitWorkTreeEnv);
-			else
-				gitworktree = null;
-
-			File indexfile;
-			String indexFileEnv = SystemReader.getInstance().getenv(Constants.GIT_INDEX_KEY);
-			if (indexFileEnv != null)
-				indexfile = new File(indexFileEnv);
-			else
-				indexfile = null;
-
-			File objectdir;
-			String objectDirEnv = SystemReader.getInstance().getenv(Constants.GIT_OBJECT_DIRECTORY_KEY);
-			if (objectDirEnv != null)
-				objectdir = new File(objectDirEnv);
-			else
-				objectdir = null;
-
-			File[] altobjectdirs;
-			String altObjectDirEnv = SystemReader.getInstance().getenv(Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY);
-			if (altObjectDirEnv != null) {
-				String[] parserdAltObjectDirEnv = altObjectDirEnv.split(File.pathSeparator);
-				altobjectdirs = new File[parserdAltObjectDirEnv.length];
-				for (int i = 0; i < parserdAltObjectDirEnv.length; i++)
-					altobjectdirs[i] = new File(parserdAltObjectDirEnv[i]);
-			} else
-				altobjectdirs = null;
-
-			if (gitdir == null || !gitdir.isDirectory()) {
+			RepositoryBuilder rb = new RepositoryBuilder() //
+					.setGitDir(gitdir) //
+					.readEnvironment() //
+					.findGitDir();
+			if (rb.getGitDir() == null) {
 				writer.println(CLIText.get().cantFindGitDirectory);
 				writer.flush();
 				System.exit(1);
 			}
-			cmd.init(new Repository(gitdir, gitworktree, objectdir, altobjectdirs, indexfile), gitdir);
+
+			cmd.init(rb.build(), null);
 		} else {
 			cmd.init(null, gitdir);
 		}
@@ -224,27 +185,6 @@ private void execute(final String[] argv) throws Exception {
 		}
 	}
 
-	private static File findGitDir() {
-		Set<String> ceilingDirectories = new HashSet<String>();
-		String ceilingDirectoriesVar = SystemReader.getInstance().getenv(
-				Constants.GIT_CEILING_DIRECTORIES_KEY);
-		if (ceilingDirectoriesVar != null) {
-			ceilingDirectories.addAll(Arrays.asList(ceilingDirectoriesVar
-					.split(File.pathSeparator)));
-		}
-		File current = new File("").getAbsoluteFile();
-		while (current != null) {
-			final File gitDir = new File(current, Constants.DOT_GIT);
-			if (gitDir.isDirectory())
-				return gitDir;
-			current = current.getParentFile();
-			if (current != null
-					&& ceilingDirectories.contains(current.getPath()))
-				break;
-		}
-		return null;
-	}
-
 	private static boolean installConsole() {
 		try {
 			install("org.eclipse.jgit.console.ConsoleAuthenticator");
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java
index 09a9f2b..7a27617 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java
@@ -47,9 +47,10 @@
 import java.io.File;
 import java.text.MessageFormat;
 
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.util.FS;
 import org.kohsuke.args4j.Argument;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
 
 @Command(common = false, usage = "usage_ServerSideBackendForJgitPush")
 class ReceivePack extends TextBuiltin {
@@ -65,11 +66,14 @@ protected final boolean requiresRepository() {
 	protected void run() throws Exception {
 		final org.eclipse.jgit.transport.ReceivePack rp;
 
-		if (new File(dstGitdir, Constants.DOT_GIT).isDirectory())
-			dstGitdir = new File(dstGitdir, Constants.DOT_GIT);
-		db = new Repository(dstGitdir);
-		if (!db.getObjectsDirectory().isDirectory())
-			throw die(MessageFormat.format(CLIText.get().notAGitRepository, dstGitdir.getPath()));
+		try {
+			FileKey key = FileKey.lenient(dstGitdir, FS.DETECTED);
+			db = key.open(true /* must exist */);
+		} catch (RepositoryNotFoundException notFound) {
+			throw die(MessageFormat.format(CLIText.get().notAGitRepository,
+					dstGitdir.getPath()));
+		}
+
 		rp = new org.eclipse.jgit.transport.ReceivePack(db);
 		rp.receive(System.in, System.out, System.err);
 	}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java
index 1b8711d..9f577ff 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java
@@ -67,9 +67,9 @@ class Rm extends TextBuiltin {
 
 	@Override
 	protected void run() throws Exception {
-		root = db.getWorkDir();
+		root = db.getWorkTree();
 
-		final DirCache dirc = DirCache.lock(db);
+		final DirCache dirc = db.lockDirCache();
 		final DirCacheBuilder edit = dirc.builder();
 
 		final TreeWalk walk = new TreeWalk(db);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java
index 63d26ea..c798950 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java
@@ -49,13 +49,12 @@
 
 import java.text.MessageFormat;
 
-import org.kohsuke.args4j.Argument;
-import org.kohsuke.args4j.Option;
-import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
 
 @Command(common = true, usage = "usage_CreateATag")
 class Tag extends TextBuiltin {
@@ -86,9 +85,7 @@ protected void run() throws Exception {
 					, tagName.substring(Constants.R_TAGS.length())));
 		}
 
-		final ObjectLoader ldr = db.openObject(object);
-		if (ldr == null)
-			throw new MissingObjectException(object, "any");
+		final ObjectLoader ldr = db.open(object);
 
 		org.eclipse.jgit.lib.Tag tag = new org.eclipse.jgit.lib.Tag(db);
 		tag.setObjId(object);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java
index 52d2488..d4e2bce 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java
@@ -47,10 +47,11 @@
 import java.io.File;
 import java.text.MessageFormat;
 
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.util.FS;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
 
 @Command(common = false, usage = "usage_ServerSideBackendForJgitFetch")
 class UploadPack extends TextBuiltin {
@@ -67,16 +68,19 @@ protected final boolean requiresRepository() {
 
 	@Override
 	protected void run() throws Exception {
-		final org.eclipse.jgit.transport.UploadPack rp;
+		final org.eclipse.jgit.transport.UploadPack up;
 
-		if (new File(srcGitdir, Constants.DOT_GIT).isDirectory())
-			srcGitdir = new File(srcGitdir, Constants.DOT_GIT);
-		db = new Repository(srcGitdir);
-		if (!db.getObjectsDirectory().isDirectory())
-			throw die(MessageFormat.format(CLIText.get().notAGitRepository, srcGitdir.getPath()));
-		rp = new org.eclipse.jgit.transport.UploadPack(db);
+		try {
+			FileKey key = FileKey.lenient(srcGitdir, FS.DETECTED);
+			db = key.open(true /* must exist */);
+		} catch (RepositoryNotFoundException notFound) {
+			throw die(MessageFormat.format(CLIText.get().notAGitRepository,
+					srcGitdir.getPath()));
+		}
+
+		up = new org.eclipse.jgit.transport.UploadPack(db);
 		if (0 <= timeout)
-			rp.setTimeout(timeout);
-		rp.upload(System.in, System.out, System.err);
+			up.setTimeout(timeout);
+		up.upload(System.in, System.out, System.err);
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java
index 5bad4ef..9b51b87 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java
@@ -55,4 +55,9 @@ protected void run() throws Exception {
 
 		out.println(MessageFormat.format(CLIText.get().jgitVersion, pkg.getImplementationVersion()));
 	}
+
+	@Override
+	protected final boolean requiresRepository() {
+		return false;
+	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java
index d772ffe..709b45a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java
@@ -54,7 +54,7 @@
 class MakeCacheTree extends TextBuiltin {
 	@Override
 	protected void run() throws Exception {
-		final DirCache cache = DirCache.read(db);
+		final DirCache cache = db.readDirCache();
 		final DirCacheTree tree = cache.getCacheTree(true);
 		show(tree);
 	}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java
index 2a1079b..0ca0508 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java
@@ -46,7 +46,6 @@
 
 import java.text.MessageFormat;
 
-import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.pgm.CLIText;
 import org.eclipse.jgit.pgm.TextBuiltin;
 
@@ -56,7 +55,7 @@ protected void run() throws Exception {
 		final int cnt = 100;
 		final long start = System.currentTimeMillis();
 		for (int i = 0; i < cnt; i++)
-			DirCache.read(db);
+			db.readDirCache();
 		final long end = System.currentTimeMillis();
 		out.print(" ");
 		out.println(MessageFormat.format(CLIText.get().averageMSPerRead, (end - start) / cnt));
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
index 1681dbc..b8e2a8f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
@@ -62,7 +62,6 @@
 import org.eclipse.jgit.errors.ObjectWritingException;
 import org.eclipse.jgit.lib.Commit;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.LockFile;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ObjectWriter;
@@ -76,6 +75,7 @@
 import org.eclipse.jgit.pgm.CLIText;
 import org.eclipse.jgit.pgm.TextBuiltin;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.LockFile;
 
 /**
  * Recreates a repository from another one's commit graph.
@@ -227,7 +227,7 @@ private void detachHead() throws IOException {
 		final ObjectId id = db.resolve(Constants.HEAD);
 		if (!ObjectId.isId(head) && id != null) {
 			final LockFile lf;
-			lf = new LockFile(new File(db.getDirectory(), Constants.HEAD));
+			lf = new LockFile(new File(db.getDirectory(), Constants.HEAD), db.getFS());
 			if (!lf.lock())
 				throw new IOException(MessageFormat.format(CLIText.get().cannotLock, Constants.HEAD));
 			lf.write(id);
@@ -254,7 +254,7 @@ private void recreateRefs() throws Exception {
 			protected void writeFile(final String name, final byte[] content)
 					throws IOException {
 				final File file = new File(db.getDirectory(), name);
-				final LockFile lck = new LockFile(file);
+				final LockFile lck = new LockFile(file, db.getFS());
 				if (!lck.lock())
 					throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
 				try {
@@ -297,6 +297,7 @@ private Map<String, Ref> computeNewRefs() throws IOException {
 						name, id));
 			}
 		} finally {
+			rw.release();
 			br.close();
 		}
 		return refs;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java
index 09796ed..c49aefb 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java
@@ -54,7 +54,7 @@
 class ShowCacheTree extends TextBuiltin {
 	@Override
 	protected void run() throws Exception {
-		final DirCache cache = DirCache.read(db);
+		final DirCache cache = db.readDirCache();
 		final DirCacheTree tree = cache.getCacheTree(false);
 		if (tree == null)
 			throw die(CLIText.get().noTREESectionInIndex);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java
index 854596c..a94d37f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java
@@ -59,7 +59,7 @@ protected void run() throws Exception {
 		final SimpleDateFormat fmt;
 		fmt = new SimpleDateFormat("yyyyMMdd,HHmmss.SSS");
 
-		final DirCache cache = DirCache.read(db);
+		final DirCache cache = db.readDirCache();
 		for (int i = 0; i < cache.getEntryCount(); i++) {
 			final DirCacheEntry ent = cache.getEntry(i);
 			final FileMode mode = FileMode.fromBits(ent.getRawMode());
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java
new file mode 100644
index 0000000..1718ef3
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.pgm.debug;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.InflaterInputStream;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.pgm.TextBuiltin;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.pack.BinaryDelta;
+import org.eclipse.jgit.storage.pack.ObjectReuseAsIs;
+import org.eclipse.jgit.storage.pack.ObjectToPack;
+import org.eclipse.jgit.storage.pack.PackOutputStream;
+import org.eclipse.jgit.storage.pack.PackWriter;
+import org.eclipse.jgit.storage.pack.StoredObjectRepresentation;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.kohsuke.args4j.Argument;
+
+class ShowPackDelta extends TextBuiltin {
+	@Argument(index = 0)
+	private ObjectId objectId;
+
+	@Override
+	protected void run() throws Exception {
+		ObjectReader reader = db.newObjectReader();
+		RevObject obj = new RevWalk(reader).parseAny(objectId);
+		byte[] delta = getDelta(reader, obj);
+
+		// We're crossing our fingers that this will be a delta. Double
+		// check the size field in the header, it should match.
+		//
+		long size = reader.getObjectSize(obj, obj.getType());
+		try {
+			if (BinaryDelta.getResultSize(delta) != size)
+				throw die("Object " + obj.name() + " is not a delta");
+		} catch (ArrayIndexOutOfBoundsException bad) {
+			throw die("Object " + obj.name() + " is not a delta");
+		}
+
+		out.println(BinaryDelta.format(delta));
+	}
+
+	private byte[] getDelta(ObjectReader reader, RevObject obj)
+			throws IOException, MissingObjectException,
+			StoredObjectRepresentationNotAvailableException {
+		ObjectReuseAsIs asis = (ObjectReuseAsIs) reader;
+		ObjectToPack target = asis.newObjectToPack(obj);
+
+		PackWriter pw = new PackWriter(reader) {
+			@Override
+			public void select(ObjectToPack otp, StoredObjectRepresentation next) {
+				otp.select(next);
+			}
+		};
+
+		ByteArrayOutputStream buf = new ByteArrayOutputStream();
+		asis.selectObjectRepresentation(pw, target);
+		asis.copyObjectAsIs(new PackOutputStream(NullProgressMonitor.INSTANCE,
+				buf, pw), target);
+
+		// At this point the object header has no delta information,
+		// because it was output as though it were a whole object.
+		// Skip over the header and inflate.
+		//
+		byte[] bufArray = buf.toByteArray();
+		int ptr = 0;
+		while ((bufArray[ptr] & 0x80) != 0)
+			ptr++;
+		ptr++;
+
+		TemporaryBuffer.Heap raw = new TemporaryBuffer.Heap(bufArray.length);
+		InflaterInputStream inf = new InflaterInputStream(
+				new ByteArrayInputStream(bufArray, ptr, bufArray.length));
+		raw.copy(inf);
+		inf.close();
+		return raw.toByteArray();
+	}
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java
index cee5966..142dbee 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java
@@ -51,7 +51,7 @@
 class WriteDirCache extends TextBuiltin {
 	@Override
 	protected void run() throws Exception {
-		final DirCache cache = DirCache.read(db);
+		final DirCache cache = db.readDirCache();
 		if (!cache.lock())
 			throw die(CLIText.get().failedToLockIndex);
 		cache.read();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Iplog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Iplog.java
index e13bb1f..84859a8 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Iplog.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Iplog.java
@@ -51,7 +51,6 @@
 import org.eclipse.jgit.iplog.IpLogGenerator;
 import org.eclipse.jgit.iplog.SimpleCookieManager;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.LockFile;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.pgm.CLIText;
 import org.eclipse.jgit.pgm.Command;
@@ -59,6 +58,7 @@
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.LockFile;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
 
@@ -98,7 +98,7 @@ else if (version == null)
 		if (output != null) {
 			if (!output.getParentFile().exists())
 				output.getParentFile().mkdirs();
-			LockFile lf = new LockFile(output);
+			LockFile lf = new LockFile(output, db.getFS());
 			if (!lf.lock())
 				throw die(MessageFormat.format(CLIText.get().cannotLock, output));
 			try {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Ipzilla.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Ipzilla.java
index 4f0e338..6653209 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Ipzilla.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Ipzilla.java
@@ -91,9 +91,9 @@ protected void run() throws Exception {
 		}
 
 		if (output == null)
-			output = new File(db.getWorkDir(), IpLogMeta.IPLOG_CONFIG_FILE);
+			output = new File(db.getWorkTree(), IpLogMeta.IPLOG_CONFIG_FILE);
 
 		IpLogMeta meta = new IpLogMeta();
-		meta.syncCQs(output, ipzilla, username, password);
+		meta.syncCQs(output, db.getFS(), ipzilla, username, password);
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
index 2043ac2..7126982 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
@@ -59,7 +59,7 @@
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.pgm.CLIText;
 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
@@ -103,7 +103,7 @@ public int parseArguments(final Parameters params) throws CmdLineException {
 		if (new File(name).isFile()) {
 			final DirCache dirc;
 			try {
-				dirc = DirCache.read(new File(name));
+				dirc = DirCache.read(new File(name), FS.DETECTED);
 			} catch (IOException e) {
 				throw new CmdLineException(MessageFormat.format(CLIText.get().notAnIndexFile, name), e);
 			}
@@ -121,9 +121,9 @@ public int parseArguments(final Parameters params) throws CmdLineException {
 			throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
 
 		final CanonicalTreeParser p = new CanonicalTreeParser();
-		final WindowCursor curs = new WindowCursor();
+		final ObjectReader curs = clp.getRepository().newObjectReader();
 		try {
-			p.reset(clp.getRepository(), clp.getRevWalk().parseTree(id), curs);
+			p.reset(curs, clp.getRevWalk().parseTree(id));
 		} catch (MissingObjectException e) {
 			throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
 		} catch (IncorrectObjectTypeException e) {
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 3aaa8a4..d6128af 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -29,6 +29,8 @@
  org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)",
+ org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)",
+ org.eclipse.jgit.storage.pack;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.transport;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)",
  org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)",
diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0007_GitIndexTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0007_GitIndexTest.java
index dd3b51e..c5591b9bf 100644
--- a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0007_GitIndexTest.java
+++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0007_GitIndexTest.java
@@ -116,7 +116,7 @@ public void run() {
 	protected void setUp() throws Exception {
 		super.setUp();
 		db = createWorkRepository();
-		trash = db.getWorkDir();
+		trash = db.getWorkTree();
 	}
 
 	public void testCreateEmptyIndex() throws Exception {
diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java
index ae9fb0e..ff442b6 100644
--- a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java
+++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java
@@ -108,9 +108,9 @@ void onCommit(String cid, byte[] buf) {
 			for (final FileHeader fh : p.getFiles()) {
 				final String fileName;
 				if (fh.getChangeType() != FileHeader.ChangeType.DELETE)
-					fileName = fh.getNewName();
+					fileName = fh.getNewPath();
 				else
-					fileName = fh.getOldName();
+					fileName = fh.getOldPath();
 				final StatInfo s = files.remove(fileName);
 				final String nid = fileName + " in " + cid;
 				assertNotNull("No " + nid, s);
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/.gitignore b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/.gitignore
deleted file mode 100644
index b3f6bc9..0000000
--- a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-!/notignored
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/.gitignore b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/.gitignore
deleted file mode 100644
index 09b8574..0000000
--- a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-notarealfile
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/.gitignore b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/.gitignore
deleted file mode 100644
index e69de29..0000000
--- a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/.gitignore
+++ /dev/null
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b1/.gitignore b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b1/.gitignore
deleted file mode 100644
index 82b0f5d..0000000
--- a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b1/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/c
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b1/test.stp b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b1/test.stp
deleted file mode 100644
index e69de29..0000000
--- a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b1/test.stp
+++ /dev/null
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b2/c/.gitignore b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b2/c/.gitignore
deleted file mode 100644
index 3c6cf10..0000000
--- a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b2/c/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/notarealfile2
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b2/c/test.stp b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b2/c/test.stp
deleted file mode 100644
index e69de29..0000000
--- a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/new/a/b2/c/test.stp
+++ /dev/null
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/notignored b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/notignored
deleted file mode 100644
index e69de29..0000000
--- a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/notignored
+++ /dev/null
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/test.stp b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/test.stp
deleted file mode 100644
index e69de29..0000000
--- a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/excludeTest/test.stp
+++ /dev/null
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
index 5eec1eb..a7d0984 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
@@ -44,15 +44,17 @@
 package org.eclipse.jgit.api;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.RepositoryTestCase;
 
 public class AddCommandTest extends RepositoryTestCase {
@@ -78,7 +80,7 @@ public void testAddNonExistingSingleFile() throws NoFilepatternException {
 	}
 
 	public void testAddExistingSingleFile() throws IOException, NoFilepatternException {
-		File file = new File(db.getWorkDir(), "a.txt");
+		File file = new File(db.getWorkTree(), "a.txt");
 		file.createNewFile();
 		PrintWriter writer = new PrintWriter(file);
 		writer.print("content");
@@ -86,20 +88,16 @@ public void testAddExistingSingleFile() throws IOException, NoFilepatternExcepti
 
 		Git git = new Git(db);
 
-		DirCache dc = git.add().addFilepattern("a.txt").call();
+		git.add().addFilepattern("a.txt").call();
 
-		assertEquals(1, dc.getEntryCount());
-		assertEquals("a.txt", dc.getEntry(0).getPathString());
-		assertNotNull(dc.getEntry(0).getObjectId());
-		assertEquals(file.lastModified(), dc.getEntry(0).getLastModified());
-		assertEquals(file.length(), dc.getEntry(0).getLength());
-		assertEquals(FileMode.REGULAR_FILE, dc.getEntry(0).getFileMode());
-		assertEquals(0, dc.getEntry(0).getStage());
+		assertEquals(
+				"[a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]",
+				indexState(CONTENT_ID));
 	}
 
 	public void testAddExistingSingleFileInSubDir() throws IOException, NoFilepatternException {
-		new File(db.getWorkDir(), "sub").mkdir();
-		File file = new File(db.getWorkDir(), "sub/a.txt");
+		new File(db.getWorkTree(), "sub").mkdir();
+		File file = new File(db.getWorkTree(), "sub/a.txt");
 		file.createNewFile();
 		PrintWriter writer = new PrintWriter(file);
 		writer.print("content");
@@ -107,19 +105,15 @@ public void testAddExistingSingleFileInSubDir() throws IOException, NoFilepatter
 
 		Git git = new Git(db);
 
-		DirCache dc = git.add().addFilepattern("sub/a.txt").call();
+		git.add().addFilepattern("sub/a.txt").call();
 
-		assertEquals(1, dc.getEntryCount());
-		assertEquals("sub/a.txt", dc.getEntry(0).getPathString());
-		assertNotNull(dc.getEntry(0).getObjectId());
-		assertEquals(file.lastModified(), dc.getEntry(0).getLastModified());
-		assertEquals(file.length(), dc.getEntry(0).getLength());
-		assertEquals(FileMode.REGULAR_FILE, dc.getEntry(0).getFileMode());
-		assertEquals(0, dc.getEntry(0).getStage());
+		assertEquals(
+				"[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]",
+				indexState(CONTENT_ID));
 	}
 
 	public void testAddExistingSingleFileTwice() throws IOException, NoFilepatternException {
-		File file = new File(db.getWorkDir(), "a.txt");
+		File file = new File(db.getWorkTree(), "a.txt");
 		file.createNewFile();
 		PrintWriter writer = new PrintWriter(file);
 		writer.print("content");
@@ -128,7 +122,7 @@ public void testAddExistingSingleFileTwice() throws IOException, NoFilepatternEx
 		Git git = new Git(db);
 		DirCache dc = git.add().addFilepattern("a.txt").call();
 
-		ObjectId id1 = dc.getEntry(0).getObjectId();
+		dc.getEntry(0).getObjectId();
 
 		writer = new PrintWriter(file);
 		writer.print("other content");
@@ -136,14 +130,13 @@ public void testAddExistingSingleFileTwice() throws IOException, NoFilepatternEx
 
 		dc = git.add().addFilepattern("a.txt").call();
 
-		assertEquals(1, dc.getEntryCount());
-		assertEquals("a.txt", dc.getEntry(0).getPathString());
-		assertNotSame(id1, dc.getEntry(0).getObjectId());
-		assertEquals(0, dc.getEntry(0).getStage());
+		assertEquals(
+				"[a.txt, mode:100644, sha1:4f41554f6e0045ef53848fc0c3f33b6a9abc24a9]",
+				indexState(CONTENT_ID));
 	}
 
 	public void testAddExistingSingleFileTwiceWithCommit() throws Exception {
-		File file = new File(db.getWorkDir(), "a.txt");
+		File file = new File(db.getWorkTree(), "a.txt");
 		file.createNewFile();
 		PrintWriter writer = new PrintWriter(file);
 		writer.print("content");
@@ -152,7 +145,7 @@ public void testAddExistingSingleFileTwiceWithCommit() throws Exception {
 		Git git = new Git(db);
 		DirCache dc = git.add().addFilepattern("a.txt").call();
 
-		ObjectId id1 = dc.getEntry(0).getObjectId();
+		dc.getEntry(0).getObjectId();
 
 		git.commit().setMessage("commit a.txt").call();
 
@@ -162,14 +155,13 @@ public void testAddExistingSingleFileTwiceWithCommit() throws Exception {
 
 		dc = git.add().addFilepattern("a.txt").call();
 
-		assertEquals(1, dc.getEntryCount());
-		assertEquals("a.txt", dc.getEntry(0).getPathString());
-		assertNotSame(id1, dc.getEntry(0).getObjectId());
-		assertEquals(0, dc.getEntry(0).getStage());
+		assertEquals(
+				"[a.txt, mode:100644, sha1:4f41554f6e0045ef53848fc0c3f33b6a9abc24a9]",
+				indexState(CONTENT_ID));
 	}
 
 	public void testAddRemovedFile() throws Exception {
-		File file = new File(db.getWorkDir(), "a.txt");
+		File file = new File(db.getWorkTree(), "a.txt");
 		file.createNewFile();
 		PrintWriter writer = new PrintWriter(file);
 		writer.print("content");
@@ -178,20 +170,19 @@ public void testAddRemovedFile() throws Exception {
 		Git git = new Git(db);
 		DirCache dc = git.add().addFilepattern("a.txt").call();
 
-		ObjectId id1 = dc.getEntry(0).getObjectId();
+		dc.getEntry(0).getObjectId();
 		file.delete();
 
 		// is supposed to do nothing
 		dc = git.add().addFilepattern("a.txt").call();
 
-		assertEquals(1, dc.getEntryCount());
-		assertEquals("a.txt", dc.getEntry(0).getPathString());
-		assertEquals(id1, dc.getEntry(0).getObjectId());
-		assertEquals(0, dc.getEntry(0).getStage());
+		assertEquals(
+				"[a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]",
+				indexState(CONTENT_ID));
 	}
 
 	public void testAddRemovedCommittedFile() throws Exception {
-		File file = new File(db.getWorkDir(), "a.txt");
+		File file = new File(db.getWorkTree(), "a.txt");
 		file.createNewFile();
 		PrintWriter writer = new PrintWriter(file);
 		writer.print("content");
@@ -202,118 +193,275 @@ public void testAddRemovedCommittedFile() throws Exception {
 
 		git.commit().setMessage("commit a.txt").call();
 
-		ObjectId id1 = dc.getEntry(0).getObjectId();
+		dc.getEntry(0).getObjectId();
 		file.delete();
 
 		// is supposed to do nothing
 		dc = git.add().addFilepattern("a.txt").call();
 
-		assertEquals(1, dc.getEntryCount());
-		assertEquals("a.txt", dc.getEntry(0).getPathString());
-		assertEquals(id1, dc.getEntry(0).getObjectId());
-		assertEquals(0, dc.getEntry(0).getStage());
+		assertEquals(
+				"[a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]",
+				indexState(CONTENT_ID));
 	}
 
 	public void testAddWithConflicts() throws Exception {
 		// prepare conflict
 
-		File file = new File(db.getWorkDir(), "a.txt");
+		File file = new File(db.getWorkTree(), "a.txt");
 		file.createNewFile();
 		PrintWriter writer = new PrintWriter(file);
 		writer.print("content");
 		writer.close();
 
-		File file2 = new File(db.getWorkDir(), "b.txt");
+		File file2 = new File(db.getWorkTree(), "b.txt");
 		file2.createNewFile();
 		writer = new PrintWriter(file2);
 		writer.print("content b");
 		writer.close();
 
-		ObjectWriter ow = new ObjectWriter(db);
-		DirCache dc = DirCache.lock(db);
+		ObjectInserter newObjectInserter = db.newObjectInserter();
+		DirCache dc = db.lockDirCache();
 		DirCacheBuilder builder = dc.builder();
 
-		addEntryToBuilder("b.txt", file2, ow, builder, 0);
-		addEntryToBuilder("a.txt", file, ow, builder, 1);
+		addEntryToBuilder("b.txt", file2, newObjectInserter, builder, 0);
+		addEntryToBuilder("a.txt", file, newObjectInserter, builder, 1);
 
 		writer = new PrintWriter(file);
 		writer.print("other content");
 		writer.close();
-		addEntryToBuilder("a.txt", file, ow, builder, 3);
+		addEntryToBuilder("a.txt", file, newObjectInserter, builder, 3);
 
 		writer = new PrintWriter(file);
 		writer.print("our content");
 		writer.close();
-		ObjectId id1 = addEntryToBuilder("a.txt", file, ow, builder, 2)
+		addEntryToBuilder("a.txt", file, newObjectInserter, builder, 2)
 				.getObjectId();
 
 		builder.commit();
 
-		assertEquals(4, dc.getEntryCount());
+		assertEquals(
+				"[a.txt, mode:100644, stage:1, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" +
+				"[a.txt, mode:100644, stage:2, sha1:b9f89ff733bdaf49e02711535867bb821f9db55e]" +
+				"[a.txt, mode:100644, stage:3, sha1:4f41554f6e0045ef53848fc0c3f33b6a9abc24a9]" +
+				"[b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]",
+				indexState(CONTENT_ID));
 
 		// now the test begins
 
 		Git git = new Git(db);
 		dc = git.add().addFilepattern("a.txt").call();
 
-		assertEquals(2, dc.getEntryCount());
-		assertEquals("a.txt", dc.getEntry("a.txt").getPathString());
-		assertEquals(id1, dc.getEntry("a.txt").getObjectId());
-		assertEquals(0, dc.getEntry("a.txt").getStage());
-		assertEquals(0, dc.getEntry("b.txt").getStage());
+		assertEquals(
+				"[a.txt, mode:100644, sha1:b9f89ff733bdaf49e02711535867bb821f9db55e]" +
+				"[b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]",
+				indexState(CONTENT_ID));
 	}
 
 	public void testAddTwoFiles() throws Exception  {
-		File file = new File(db.getWorkDir(), "a.txt");
+		File file = new File(db.getWorkTree(), "a.txt");
 		file.createNewFile();
 		PrintWriter writer = new PrintWriter(file);
 		writer.print("content");
 		writer.close();
 
-		File file2 = new File(db.getWorkDir(), "b.txt");
+		File file2 = new File(db.getWorkTree(), "b.txt");
 		file2.createNewFile();
 		writer = new PrintWriter(file2);
 		writer.print("content b");
 		writer.close();
 
 		Git git = new Git(db);
-		DirCache dc = git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
-		assertEquals("a.txt", dc.getEntry("a.txt").getPathString());
-		assertEquals("b.txt", dc.getEntry("b.txt").getPathString());
-		assertNotNull(dc.getEntry("a.txt").getObjectId());
-		assertNotNull(dc.getEntry("b.txt").getObjectId());
-		assertEquals(0, dc.getEntry("a.txt").getStage());
-		assertEquals(0, dc.getEntry("b.txt").getStage());
+		git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
+		assertEquals(
+				"[a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" +
+				"[b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]",
+				indexState(CONTENT_ID));
 	}
 
 	public void testAddFolder() throws Exception  {
-		new File(db.getWorkDir(), "sub").mkdir();
-		File file = new File(db.getWorkDir(), "sub/a.txt");
+		new File(db.getWorkTree(), "sub").mkdir();
+		File file = new File(db.getWorkTree(), "sub/a.txt");
 		file.createNewFile();
 		PrintWriter writer = new PrintWriter(file);
 		writer.print("content");
 		writer.close();
 
-		File file2 = new File(db.getWorkDir(), "sub/b.txt");
+		File file2 = new File(db.getWorkTree(), "sub/b.txt");
 		file2.createNewFile();
 		writer = new PrintWriter(file2);
 		writer.print("content b");
 		writer.close();
 
 		Git git = new Git(db);
-		DirCache dc = git.add().addFilepattern("sub").call();
-		assertEquals("sub/a.txt", dc.getEntry("sub/a.txt").getPathString());
-		assertEquals("sub/b.txt", dc.getEntry("sub/b.txt").getPathString());
-		assertNotNull(dc.getEntry("sub/a.txt").getObjectId());
-		assertNotNull(dc.getEntry("sub/b.txt").getObjectId());
-		assertEquals(0, dc.getEntry("sub/a.txt").getStage());
-		assertEquals(0, dc.getEntry("sub/b.txt").getStage());
+		git.add().addFilepattern("sub").call();
+		assertEquals(
+				"[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" +
+				"[sub/b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]",
+				indexState(CONTENT_ID));
+	}
+
+	public void testAddIgnoredFile() throws Exception  {
+		new File(db.getWorkTree(), "sub").mkdir();
+		File file = new File(db.getWorkTree(), "sub/a.txt");
+		file.createNewFile();
+		PrintWriter writer = new PrintWriter(file);
+		writer.print("content");
+		writer.close();
+
+		File ignoreFile = new File(db.getWorkTree(), ".gitignore");
+		ignoreFile.createNewFile();
+		writer = new PrintWriter(ignoreFile);
+		writer.print("sub/b.txt");
+		writer.close();
+
+		File file2 = new File(db.getWorkTree(), "sub/b.txt");
+		file2.createNewFile();
+		writer = new PrintWriter(file2);
+		writer.print("content b");
+		writer.close();
+
+		Git git = new Git(db);
+		git.add().addFilepattern("sub").call();
+
+		assertEquals(
+				"[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]",
+				indexState(CONTENT_ID));
+	}
+
+	public void testAddWholeRepo() throws Exception  {
+		new File(db.getWorkTree(), "sub").mkdir();
+		File file = new File(db.getWorkTree(), "sub/a.txt");
+		file.createNewFile();
+		PrintWriter writer = new PrintWriter(file);
+		writer.print("content");
+		writer.close();
+
+		File file2 = new File(db.getWorkTree(), "sub/b.txt");
+		file2.createNewFile();
+		writer = new PrintWriter(file2);
+		writer.print("content b");
+		writer.close();
+
+		Git git = new Git(db);
+		git.add().addFilepattern(".").call();
+		assertEquals(
+				"[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" +
+				"[sub/b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]",
+				indexState(CONTENT_ID));
+	}
+
+	// the same three cases as in testAddWithParameterUpdate
+	// file a exists in workdir and in index -> added
+	// file b exists not in workdir but in index -> unchanged
+	// file c exists in workdir but not in index -> added
+	public void testAddWithoutParameterUpdate() throws Exception {
+		new File(db.getWorkTree(), "sub").mkdir();
+		File file = new File(db.getWorkTree(), "sub/a.txt");
+		file.createNewFile();
+		PrintWriter writer = new PrintWriter(file);
+		writer.print("content");
+		writer.close();
+
+		File file2 = new File(db.getWorkTree(), "sub/b.txt");
+		file2.createNewFile();
+		writer = new PrintWriter(file2);
+		writer.print("content b");
+		writer.close();
+
+		Git git = new Git(db);
+		git.add().addFilepattern("sub").call();
+
+		assertEquals(
+				"[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" +
+				"[sub/b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]",
+				indexState(CONTENT_ID));
+
+		git.commit().setMessage("commit").call();
+
+		// new unstaged file sub/c.txt
+		File file3 = new File(db.getWorkTree(), "sub/c.txt");
+		file3.createNewFile();
+		writer = new PrintWriter(file3);
+		writer.print("content c");
+		writer.close();
+
+		// file sub/a.txt is modified
+		writer = new PrintWriter(file);
+		writer.print("modified content");
+		writer.close();
+
+		// file sub/b.txt is deleted
+		file2.delete();
+
+		git.add().addFilepattern("sub").call();
+		// change in sub/a.txt is staged
+		// deletion of sub/b.txt is not staged
+		// sub/c.txt is staged
+		assertEquals(
+				"[sub/a.txt, mode:100644, sha1:268af4e306cfcf6e79edd50fed9c553d211f68e3]" +
+				"[sub/b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]" +
+				"[sub/c.txt, mode:100644, sha1:fa08654474ae2ddc4f61ee3a43d017ba65a439c3]",
+				indexState(CONTENT_ID));
+	}
+
+	// file a exists in workdir and in index -> added
+	// file b exists not in workdir but in index -> deleted
+	// file c exists in workdir but not in index -> unchanged
+	public void testAddWithParameterUpdate() throws Exception {
+		new File(db.getWorkTree(), "sub").mkdir();
+		File file = new File(db.getWorkTree(), "sub/a.txt");
+		file.createNewFile();
+		PrintWriter writer = new PrintWriter(file);
+		writer.print("content");
+		writer.close();
+
+		File file2 = new File(db.getWorkTree(), "sub/b.txt");
+		file2.createNewFile();
+		writer = new PrintWriter(file2);
+		writer.print("content b");
+		writer.close();
+
+		Git git = new Git(db);
+		git.add().addFilepattern("sub").call();
+
+		assertEquals(
+				"[sub/a.txt, mode:100644, sha1:6b584e8ece562ebffc15d38808cd6b98fc3d97ea]" +
+				"[sub/b.txt, mode:100644, sha1:50e9cdb03f9719261dd39d7f2920b906db3711a3]",
+				indexState(CONTENT_ID));
+
+		git.commit().setMessage("commit").call();
+
+		// new unstaged file sub/c.txt
+		File file3 = new File(db.getWorkTree(), "sub/c.txt");
+		file3.createNewFile();
+		writer = new PrintWriter(file3);
+		writer.print("content c");
+		writer.close();
+
+		// file sub/a.txt is modified
+		writer = new PrintWriter(file);
+		writer.print("modified content");
+		writer.close();
+
+		file2.delete();
+
+		// change in sub/a.txt is staged
+		// deletion of sub/b.txt is staged
+		// sub/c.txt is not staged
+		git.add().addFilepattern("sub").setUpdate(true).call();
+		// change in sub/a.txt is staged
+		assertEquals(
+				"[sub/a.txt, mode:100644, sha1:268af4e306cfcf6e79edd50fed9c553d211f68e3]",
+				indexState(CONTENT_ID));
 	}
 
 	private DirCacheEntry addEntryToBuilder(String path, File file,
-			ObjectWriter ow, DirCacheBuilder builder, int stage)
+			ObjectInserter newObjectInserter, DirCacheBuilder builder, int stage)
 			throws IOException {
-		ObjectId id = ow.writeBlob(file);
+		FileInputStream inputStream = new FileInputStream(file);
+		ObjectId id = newObjectInserter.insert(
+				Constants.OBJ_BLOB, file.length(), inputStream);
+		inputStream.close();
 		DirCacheEntry entry = new DirCacheEntry(path, stage);
 		entry.setObjectId(id);
 		entry.setFileMode(FileMode.REGULAR_FILE);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java
index a62045d..cf30039 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java
@@ -45,6 +45,7 @@
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.PrintWriter;
 
 import org.eclipse.jgit.errors.UnmergedPathException;
 import org.eclipse.jgit.lib.Constants;
@@ -53,6 +54,7 @@
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.RepositoryTestCase;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.treewalk.TreeWalk;
 
 public class CommitAndLogCommandTests extends RepositoryTestCase {
 	public void testSomeCommits() throws NoHeadException, NoMessageException,
@@ -151,4 +153,36 @@ public void testMergeEmptyBranches() throws IOException, NoHeadException,
 		assertEquals(parents[1], second);
 		assertTrue(parents.length==2);
 	}
+
+	public void testAddUnstagedChanges() throws IOException, NoHeadException,
+			NoMessageException, ConcurrentRefUpdateException,
+			JGitInternalException, WrongRepositoryStateException,
+			NoFilepatternException {
+		File file = new File(db.getWorkTree(), "a.txt");
+		file.createNewFile();
+		PrintWriter writer = new PrintWriter(file);
+		writer.print("content");
+		writer.close();
+
+		Git git = new Git(db);
+		git.add().addFilepattern("a.txt").call();
+		RevCommit commit = git.commit().setMessage("initial commit").call();
+		TreeWalk tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
+		assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
+				tw.getObjectId(0).getName());
+
+		writer = new PrintWriter(file);
+		writer.print("content2");
+		writer.close();
+		commit = git.commit().setMessage("second commit").call();
+		tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
+		assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
+				tw.getObjectId(0).getName());
+
+		commit = git.commit().setAll(true).setMessage("third commit")
+				.setAll(true).call();
+		tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
+		assertEquals("db00fd65b218578127ea51f3dffac701f12f486a",
+				tw.getObjectId(0).getName());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
index c965c67..773d2f0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
@@ -100,20 +100,20 @@ public void testFastForwardWithFiles() throws Exception {
 		addNewFileToIndex("file1");
 		RevCommit first = git.commit().setMessage("initial commit").call();
 
-		assertTrue(new File(db.getWorkDir(), "file1").exists());
+		assertTrue(new File(db.getWorkTree(), "file1").exists());
 		createBranch(first, "refs/heads/branch1");
 
 		addNewFileToIndex("file2");
 		RevCommit second = git.commit().setMessage("second commit").call();
-		assertTrue(new File(db.getWorkDir(), "file2").exists());
+		assertTrue(new File(db.getWorkTree(), "file2").exists());
 
 		checkoutBranch("refs/heads/branch1");
-		assertFalse(new File(db.getWorkDir(), "file2").exists());
+		assertFalse(new File(db.getWorkTree(), "file2").exists());
 
 		MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call();
 
-		assertTrue(new File(db.getWorkDir(), "file1").exists());
-		assertTrue(new File(db.getWorkDir(), "file2").exists());
+		assertTrue(new File(db.getWorkTree(), "file1").exists());
+		assertTrue(new File(db.getWorkTree(), "file2").exists());
 		assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus());
 		assertEquals(second, result.getNewHead());
 	}
@@ -132,8 +132,8 @@ public void testMultipleHeads() throws Exception {
 		git.commit().setMessage("third commit").call();
 
 		checkoutBranch("refs/heads/branch1");
-		assertFalse(new File(db.getWorkDir(), "file2").exists());
-		assertFalse(new File(db.getWorkDir(), "file3").exists());
+		assertFalse(new File(db.getWorkTree(), "file2").exists());
+		assertFalse(new File(db.getWorkTree(), "file3").exists());
 
 		MergeCommand merge = git.merge();
 		merge.include(second.getId());
@@ -152,7 +152,7 @@ private void createBranch(ObjectId objectId, String branchName) throws IOExcepti
 	}
 
 	private void checkoutBranch(String branchName) throws Exception  {
-		File workDir = db.getWorkDir();
+		File workDir = db.getWorkTree();
 		if (workDir != null) {
 			WorkDirCheckout workDirCheckout = new WorkDirCheckout(db,
 					workDir, db.mapCommit(Constants.HEAD).getTree(),
@@ -176,7 +176,7 @@ private void addNewFileToIndex(String filename) throws IOException,
 		File writeTrashFile = writeTrashFile(filename, filename);
 
 		GitIndex index = db.getIndex();
-		Entry entry = index.add(db.getWorkDir(), writeTrashFile);
+		Entry entry = index.add(db.getWorkTree(), writeTrashFile);
 		entry.update(writeTrashFile);
 		index.write();
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/MyersDiffPerformanceTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/MyersDiffPerformanceTest.java
index fe63e3d..37a9a4d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/MyersDiffPerformanceTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/MyersDiffPerformanceTest.java
@@ -60,7 +60,7 @@
  * time for computing the diff between a and b should depend on the product of
  * a.length+b.length and the number of found differences. The tests compute
  * diffs between chunks of different length, measure the needed time and check
- * that time/(N*D) does not differ more than a certain factor (currently 10)
+ * that time/(N*D) does not differ more than a certain factor.
  */
 public class MyersDiffPerformanceTest extends TestCase {
 	private static final long longTaskBoundary = 5000000000L;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
index b5d94c0..862fc0a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
@@ -401,6 +401,116 @@ public void testNoRenames_SymlinkAndFileSamePath() throws Exception {
 		assertSame(b, entries.get(1));
 	}
 
+	public void testBreakModify_BreakAll() throws Exception {
+		ObjectId aId = blob("foo");
+		ObjectId bId = blob("bar");
+
+		DiffEntry m = DiffEntry.modify(PATH_A);
+		m.oldId = AbbreviatedObjectId.fromObjectId(aId);
+		m.newId = AbbreviatedObjectId.fromObjectId(bId);
+
+		DiffEntry a = DiffEntry.add(PATH_B, aId);
+
+		rd.add(a);
+		rd.add(m);
+
+		rd.setBreakScore(101);
+
+		List<DiffEntry> entries = rd.compute();
+		assertEquals(2, entries.size());
+		assertAdd(PATH_A, bId, FileMode.REGULAR_FILE, entries.get(0));
+		assertRename(DiffEntry.breakModify(m).get(0), a, 100, entries.get(1));
+	}
+
+	public void testBreakModify_BreakNone() throws Exception {
+		ObjectId aId = blob("foo");
+		ObjectId bId = blob("bar");
+
+		DiffEntry m = DiffEntry.modify(PATH_A);
+		m.oldId = AbbreviatedObjectId.fromObjectId(aId);
+		m.newId = AbbreviatedObjectId.fromObjectId(bId);
+
+		DiffEntry a = DiffEntry.add(PATH_B, aId);
+
+		rd.add(a);
+		rd.add(m);
+
+		rd.setBreakScore(-1);
+
+		List<DiffEntry> entries = rd.compute();
+		assertEquals(2, entries.size());
+		assertSame(m, entries.get(0));
+		assertSame(a, entries.get(1));
+	}
+
+	public void testBreakModify_BreakBelowScore() throws Exception {
+		ObjectId aId = blob("foo");
+		ObjectId bId = blob("bar");
+
+		DiffEntry m = DiffEntry.modify(PATH_A);
+		m.oldId = AbbreviatedObjectId.fromObjectId(aId);
+		m.newId = AbbreviatedObjectId.fromObjectId(bId);
+
+		DiffEntry a = DiffEntry.add(PATH_B, aId);
+
+		rd.add(a);
+		rd.add(m);
+
+		rd.setBreakScore(20); // Should break the modify
+
+		List<DiffEntry> entries = rd.compute();
+		assertEquals(2, entries.size());
+		assertAdd(PATH_A, bId, FileMode.REGULAR_FILE, entries.get(0));
+		assertRename(DiffEntry.breakModify(m).get(0), a, 100, entries.get(1));
+	}
+
+	public void testBreakModify_DontBreakAboveScore() throws Exception {
+		ObjectId aId = blob("blah\nblah\nfoo");
+		ObjectId bId = blob("blah\nblah\nbar");
+
+		DiffEntry m = DiffEntry.modify(PATH_A);
+		m.oldId = AbbreviatedObjectId.fromObjectId(aId);
+		m.newId = AbbreviatedObjectId.fromObjectId(bId);
+
+		DiffEntry a = DiffEntry.add(PATH_B, aId);
+
+		rd.add(a);
+		rd.add(m);
+
+		rd.setBreakScore(20); // Should not break the modify
+
+		List<DiffEntry> entries = rd.compute();
+		assertEquals(2, entries.size());
+		assertSame(m, entries.get(0));
+		assertSame(a, entries.get(1));
+	}
+
+	public void testBreakModify_RejoinIfUnpaired() throws Exception {
+		ObjectId aId = blob("foo");
+		ObjectId bId = blob("bar");
+
+		DiffEntry m = DiffEntry.modify(PATH_A);
+		m.oldId = AbbreviatedObjectId.fromObjectId(aId);
+		m.newId = AbbreviatedObjectId.fromObjectId(bId);
+
+		rd.add(m);
+
+		rd.setBreakScore(101); // Ensure m is broken apart
+
+		List<DiffEntry> entries = rd.compute();
+		assertEquals(1, entries.size());
+
+		DiffEntry modify = entries.get(0);
+		assertEquals(m.oldPath, modify.oldPath);
+		assertEquals(m.oldId, modify.oldId);
+		assertEquals(m.oldMode, modify.oldMode);
+		assertEquals(m.newPath, modify.newPath);
+		assertEquals(m.newId, modify.newId);
+		assertEquals(m.newMode, modify.newMode);
+		assertEquals(m.changeType, modify.changeType);
+		assertEquals(0, modify.score);
+	}
+
 	public void testSetRenameScore_IllegalArgs() throws Exception {
 		try {
 			rd.setRenameScore(-1);
@@ -453,8 +563,8 @@ private static void assertRename(DiffEntry o, DiffEntry n, int score,
 			DiffEntry rename) {
 		assertEquals(ChangeType.RENAME, rename.getChangeType());
 
-		assertEquals(o.getOldName(), rename.getOldName());
-		assertEquals(n.getNewName(), rename.getNewName());
+		assertEquals(o.getOldPath(), rename.getOldPath());
+		assertEquals(n.getNewPath(), rename.getNewPath());
 
 		assertEquals(o.getOldMode(), rename.getOldMode());
 		assertEquals(n.getNewMode(), rename.getNewMode());
@@ -469,8 +579,8 @@ private static void assertCopy(DiffEntry o, DiffEntry n, int score,
 			DiffEntry copy) {
 		assertEquals(ChangeType.COPY, copy.getChangeType());
 
-		assertEquals(o.getOldName(), copy.getOldName());
-		assertEquals(n.getNewName(), copy.getNewName());
+		assertEquals(o.getOldPath(), copy.getOldPath());
+		assertEquals(n.getNewPath(), copy.getNewPath());
 
 		assertEquals(o.getOldMode(), copy.getOldMode());
 		assertEquals(n.getNewMode(), copy.getNewMode());
@@ -480,4 +590,15 @@ private static void assertCopy(DiffEntry o, DiffEntry n, int score,
 
 		assertEquals(score, copy.getScore());
 	}
+
+	private static void assertAdd(String newName, ObjectId newId,
+			FileMode newMode, DiffEntry add) {
+		assertEquals(DiffEntry.DEV_NULL, add.oldPath);
+		assertEquals(DiffEntry.A_ZERO, add.oldId);
+		assertEquals(FileMode.MISSING, add.oldMode);
+		assertEquals(ChangeType.ADD, add.changeType);
+		assertEquals(newName, add.newPath);
+		assertEquals(AbbreviatedObjectId.fromObjectId(newId), add.newId);
+		assertEquals(newMode, add.newMode);
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java
index d6915eb..7e42e53 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java
@@ -43,12 +43,15 @@
 
 package org.eclipse.jgit.diff;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
 import junit.framework.TestCase;
 
 import org.eclipse.jgit.lib.Constants;
 
 public class SimilarityIndexTest extends TestCase {
-	public void testIndexing() {
+	public void testIndexingSmallObject() {
 		SimilarityIndex si = hash("" //
 				+ "A\n" //
 				+ "B\n" //
@@ -67,6 +70,17 @@ public void testIndexing() {
 		assertEquals(2, si.count(si.findIndex(key_D)));
 	}
 
+	public void testIndexingLargeObject() throws IOException {
+		byte[] in = ("" //
+				+ "A\n" //
+				+ "B\n" //
+				+ "B\n" //
+				+ "B\n").getBytes("UTF-8");
+		SimilarityIndex si = new SimilarityIndex();
+		si.hash(new ByteArrayInputStream(in), in.length);
+		assertEquals(2, si.size());
+	}
+
 	public void testCommonScore_SameFiles() {
 		String text = "" //
 				+ "A\n" //
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java
index f4692b1..b6e4e42 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java
@@ -54,7 +54,7 @@ public void testReadMissing_RealIndex() throws Exception {
 		final File idx = new File(db.getDirectory(), "index");
 		assertFalse(idx.exists());
 
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 		assertNotNull(dc);
 		assertEquals(0, dc.getEntryCount());
 	}
@@ -63,7 +63,7 @@ public void testReadMissing_TempIndex() throws Exception {
 		final File idx = new File(db.getDirectory(), "tmp_index");
 		assertFalse(idx.exists());
 
-		final DirCache dc = DirCache.read(idx);
+		final DirCache dc = DirCache.read(idx, db.getFS());
 		assertNotNull(dc);
 		assertEquals(0, dc.getEntryCount());
 	}
@@ -74,7 +74,7 @@ public void testLockMissing_RealIndex() throws Exception {
 		assertFalse(idx.exists());
 		assertFalse(lck.exists());
 
-		final DirCache dc = DirCache.lock(db);
+		final DirCache dc = db.lockDirCache();
 		assertNotNull(dc);
 		assertFalse(idx.exists());
 		assertTrue(lck.exists());
@@ -91,7 +91,7 @@ public void testLockMissing_TempIndex() throws Exception {
 		assertFalse(idx.exists());
 		assertFalse(lck.exists());
 
-		final DirCache dc = DirCache.lock(idx);
+		final DirCache dc = DirCache.lock(idx, db.getFS());
 		assertNotNull(dc);
 		assertFalse(idx.exists());
 		assertTrue(lck.exists());
@@ -108,7 +108,7 @@ public void testWriteEmptyUnlock_RealIndex() throws Exception {
 		assertFalse(idx.exists());
 		assertFalse(lck.exists());
 
-		final DirCache dc = DirCache.lock(db);
+		final DirCache dc = db.lockDirCache();
 		assertEquals(0, lck.length());
 		dc.write();
 		assertEquals(12 + 20, lck.length());
@@ -124,7 +124,7 @@ public void testWriteEmptyCommit_RealIndex() throws Exception {
 		assertFalse(idx.exists());
 		assertFalse(lck.exists());
 
-		final DirCache dc = DirCache.lock(db);
+		final DirCache dc = db.lockDirCache();
 		assertEquals(0, lck.length());
 		dc.write();
 		assertEquals(12 + 20, lck.length());
@@ -141,13 +141,13 @@ public void testWriteEmptyReadEmpty_RealIndex() throws Exception {
 		assertFalse(idx.exists());
 		assertFalse(lck.exists());
 		{
-			final DirCache dc = DirCache.lock(db);
+			final DirCache dc = db.lockDirCache();
 			dc.write();
 			assertTrue(dc.commit());
 			assertTrue(idx.exists());
 		}
 		{
-			final DirCache dc = DirCache.read(db);
+			final DirCache dc = db.readDirCache();
 			assertEquals(0, dc.getEntryCount());
 		}
 	}
@@ -158,13 +158,13 @@ public void testWriteEmptyLockEmpty_RealIndex() throws Exception {
 		assertFalse(idx.exists());
 		assertFalse(lck.exists());
 		{
-			final DirCache dc = DirCache.lock(db);
+			final DirCache dc = db.lockDirCache();
 			dc.write();
 			assertTrue(dc.commit());
 			assertTrue(idx.exists());
 		}
 		{
-			final DirCache dc = DirCache.lock(db);
+			final DirCache dc = db.lockDirCache();
 			assertEquals(0, dc.getEntryCount());
 			assertTrue(idx.exists());
 			assertTrue(lck.exists());
@@ -173,7 +173,7 @@ public void testWriteEmptyLockEmpty_RealIndex() throws Exception {
 	}
 
 	public void testBuildThenClear() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final String[] paths = { "a.", "a.b", "a/b", "a0b" };
 		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
@@ -195,7 +195,7 @@ public void testBuildThenClear() throws Exception {
 	}
 
 	public void testDetectUnmergedPaths() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 		final DirCacheEntry[] ents = new DirCacheEntry[3];
 
 		ents[0] = new DirCacheEntry("a", 1);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java
index 03bb7f5..a09f8e8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java
@@ -52,7 +52,7 @@
 
 public class DirCacheBuilderIteratorTest extends RepositoryTestCase {
 	public void testPathFilterGroup_DoesNotSkipTail() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final FileMode mode = FileMode.REGULAR_FILE;
 		final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" };
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
index e919e41..81ffab9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
@@ -52,7 +52,7 @@
 public class DirCacheBuilderTest extends RepositoryTestCase {
 	public void testBuildEmpty() throws Exception {
 		{
-			final DirCache dc = DirCache.lock(db);
+			final DirCache dc = db.lockDirCache();
 			final DirCacheBuilder b = dc.builder();
 			assertNotNull(b);
 			b.finish();
@@ -60,7 +60,7 @@ public void testBuildEmpty() throws Exception {
 			assertTrue(dc.commit());
 		}
 		{
-			final DirCache dc = DirCache.read(db);
+			final DirCache dc = db.readDirCache();
 			assertEquals(0, dc.getEntryCount());
 		}
 	}
@@ -86,7 +86,7 @@ public void testBuildOneFile_FinishWriteCommit() throws Exception {
 		final int length = 1342;
 		final DirCacheEntry entOrig;
 		{
-			final DirCache dc = DirCache.lock(db);
+			final DirCache dc = db.lockDirCache();
 			final DirCacheBuilder b = dc.builder();
 			assertNotNull(b);
 
@@ -113,7 +113,7 @@ public void testBuildOneFile_FinishWriteCommit() throws Exception {
 			assertTrue(dc.commit());
 		}
 		{
-			final DirCache dc = DirCache.read(db);
+			final DirCache dc = db.readDirCache();
 			assertEquals(1, dc.getEntryCount());
 
 			final DirCacheEntry entRead = dc.getEntry(0);
@@ -135,7 +135,7 @@ public void testBuildOneFile_Commit() throws Exception {
 		final int length = 1342;
 		final DirCacheEntry entOrig;
 		{
-			final DirCache dc = DirCache.lock(db);
+			final DirCache dc = db.lockDirCache();
 			final DirCacheBuilder b = dc.builder();
 			assertNotNull(b);
 
@@ -160,7 +160,7 @@ public void testBuildOneFile_Commit() throws Exception {
 			assertFalse(new File(db.getDirectory(), "index.lock").exists());
 		}
 		{
-			final DirCache dc = DirCache.read(db);
+			final DirCache dc = db.readDirCache();
 			assertEquals(1, dc.getEntryCount());
 
 			final DirCacheEntry entRead = dc.getEntry(0);
@@ -177,7 +177,7 @@ public void testBuildOneFile_Commit() throws Exception {
 
 	public void testFindSingleFile() throws Exception {
 		final String path = "a-file-path";
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 		final DirCacheBuilder b = dc.builder();
 		assertNotNull(b);
 
@@ -202,7 +202,7 @@ public void testFindSingleFile() throws Exception {
 	}
 
 	public void testAdd_InGitSortOrder() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final String[] paths = { "a.", "a.b", "a/b", "a0b" };
 		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
@@ -226,7 +226,7 @@ public void testAdd_InGitSortOrder() throws Exception {
 	}
 
 	public void testAdd_ReverseGitSortOrder() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final String[] paths = { "a.", "a.b", "a/b", "a0b" };
 		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
@@ -250,7 +250,7 @@ public void testAdd_ReverseGitSortOrder() throws Exception {
 	}
 
 	public void testBuilderClear() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final String[] paths = { "a.", "a.b", "a/b", "a0b" };
 		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
index fa5fea8..f37d040 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
@@ -58,6 +58,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.JGitTestUtil;
 
 public class DirCacheCGitCompatabilityTest extends LocalDiskRepositoryTestCase {
@@ -65,7 +66,7 @@ public class DirCacheCGitCompatabilityTest extends LocalDiskRepositoryTestCase {
 
 	public void testReadIndex_LsFiles() throws Exception {
 		final Map<String, CGitIndexRecord> ls = readLsFiles();
-		final DirCache dc = new DirCache(index);
+		final DirCache dc = new DirCache(index, FS.DETECTED);
 		assertEquals(0, dc.getEntryCount());
 		dc.read();
 		assertEquals(ls.size(), dc.getEntryCount());
@@ -79,7 +80,7 @@ public void testReadIndex_LsFiles() throws Exception {
 	public void testTreeWalk_LsFiles() throws Exception {
 		final Repository db = createBareRepository();
 		final Map<String, CGitIndexRecord> ls = readLsFiles();
-		final DirCache dc = new DirCache(index);
+		final DirCache dc = new DirCache(index, db.getFS());
 		assertEquals(0, dc.getEntryCount());
 		dc.read();
 		assertEquals(ls.size(), dc.getEntryCount());
@@ -102,14 +103,16 @@ public void testTreeWalk_LsFiles() throws Exception {
 	}
 
 	public void testUnsupportedOptionalExtension() throws Exception {
-		final DirCache dc = new DirCache(pathOf("gitgit.index.ZZZZ"));
+		final DirCache dc = new DirCache(pathOf("gitgit.index.ZZZZ"),
+				FS.DETECTED);
 		dc.read();
 		assertEquals(1, dc.getEntryCount());
 		assertEquals("A", dc.getEntry(0).getPathString());
 	}
 
 	public void testUnsupportedRequiredExtension() throws Exception {
-		final DirCache dc = new DirCache(pathOf("gitgit.index.aaaa"));
+		final DirCache dc = new DirCache(pathOf("gitgit.index.aaaa"),
+				FS.DETECTED);
 		try {
 			dc.read();
 			fail("Cache loaded an unsupported extension");
@@ -120,7 +123,8 @@ public void testUnsupportedRequiredExtension() throws Exception {
 	}
 
 	public void testCorruptChecksumAtFooter() throws Exception {
-		final DirCache dc = new DirCache(pathOf("gitgit.index.badchecksum"));
+		final DirCache dc = new DirCache(pathOf("gitgit.index.badchecksum"),
+				FS.DETECTED);
 		try {
 			dc.read();
 			fail("Cache loaded despite corrupt checksum");
@@ -143,7 +147,7 @@ private static void assertEqual(final CGitIndexRecord c,
 	public void testReadIndex_DirCacheTree() throws Exception {
 		final Map<String, CGitIndexRecord> cList = readLsFiles();
 		final Map<String, CGitLsTreeRecord> cTree = readLsTree();
-		final DirCache dc = new DirCache(index);
+		final DirCache dc = new DirCache(index, FS.DETECTED);
 		assertEquals(0, dc.getEntryCount());
 		dc.read();
 		assertEquals(cList.size(), dc.getEntryCount());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java
index d5a632c..5533fe3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java
@@ -48,7 +48,7 @@
 
 public class DirCacheFindTest extends RepositoryTestCase {
 	public void testEntriesWithin() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" };
 		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java
index efea117..24e3c34 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java
@@ -52,7 +52,7 @@
 
 public class DirCacheIteratorTest extends RepositoryTestCase {
 	public void testEmptyTree_NoTreeWalk() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 		assertEquals(0, dc.getEntryCount());
 
 		final DirCacheIterator i = new DirCacheIterator(dc);
@@ -60,7 +60,7 @@ public void testEmptyTree_NoTreeWalk() throws Exception {
 	}
 
 	public void testEmptyTree_WithTreeWalk() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 		assertEquals(0, dc.getEntryCount());
 
 		final TreeWalk tw = new TreeWalk(db);
@@ -70,7 +70,7 @@ public void testEmptyTree_WithTreeWalk() throws Exception {
 	}
 
 	public void testNoSubtree_NoTreeWalk() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final String[] paths = { "a.", "a0b" };
 		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
@@ -95,7 +95,7 @@ public void testNoSubtree_NoTreeWalk() throws Exception {
 	}
 
 	public void testNoSubtree_WithTreeWalk() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final String[] paths = { "a.", "a0b" };
 		final FileMode[] modes = { FileMode.EXECUTABLE_FILE, FileMode.GITLINK };
@@ -128,7 +128,7 @@ public void testNoSubtree_WithTreeWalk() throws Exception {
 	}
 
 	public void testSingleSubtree_NoRecursion() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" };
 		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
@@ -172,7 +172,7 @@ public void testSingleSubtree_NoRecursion() throws Exception {
 	}
 
 	public void testSingleSubtree_Recursive() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final FileMode mode = FileMode.REGULAR_FILE;
 		final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" };
@@ -207,7 +207,7 @@ public void testSingleSubtree_Recursive() throws Exception {
 	}
 
 	public void testTwoLevelSubtree_Recursive() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final FileMode mode = FileMode.REGULAR_FILE;
 		final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" };
@@ -241,7 +241,7 @@ public void testTwoLevelSubtree_Recursive() throws Exception {
 	}
 
 	public void testTwoLevelSubtree_FilterPath() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final FileMode mode = FileMode.REGULAR_FILE;
 		final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" };
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java
index 0926ab9..a6d7e39 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java
@@ -85,7 +85,7 @@ private void testLongPath(final int len) throws CorruptObjectException,
 		assertEquals(shortPath, shortEnt.getPathString());
 
 		{
-			final DirCache dc1 = DirCache.lock(db);
+			final DirCache dc1 = db.lockDirCache();
 			{
 				final DirCacheBuilder b = dc1.builder();
 				b.add(longEnt);
@@ -97,7 +97,7 @@ private void testLongPath(final int len) throws CorruptObjectException,
 			assertSame(shortEnt, dc1.getEntry(1));
 		}
 		{
-			final DirCache dc2 = DirCache.read(db);
+			final DirCache dc2 = db.readDirCache();
 			assertEquals(2, dc2.getEntryCount());
 
 			assertNotSame(longEnt, dc2.getEntry(0));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java
index 8345c5d..dfca2fb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java
@@ -51,12 +51,12 @@
 
 public class DirCacheTreeTest extends RepositoryTestCase {
 	public void testEmptyCache_NoCacheTree() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 		assertNull(dc.getCacheTree(false));
 	}
 
 	public void testEmptyCache_CreateEmptyCacheTree() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 		final DirCacheTree tree = dc.getCacheTree(true);
 		assertNotNull(tree);
 		assertSame(tree, dc.getCacheTree(false));
@@ -69,7 +69,7 @@ public void testEmptyCache_CreateEmptyCacheTree() throws Exception {
 	}
 
 	public void testEmptyCache_Clear_NoCacheTree() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 		final DirCacheTree tree = dc.getCacheTree(true);
 		assertNotNull(tree);
 		dc.clear();
@@ -78,7 +78,7 @@ public void testEmptyCache_Clear_NoCacheTree() throws Exception {
 	}
 
 	public void testSingleSubtree() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" };
 		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
@@ -115,7 +115,7 @@ public void testSingleSubtree() throws Exception {
 	}
 
 	public void testTwoLevelSubtree() throws Exception {
-		final DirCache dc = DirCache.read(db);
+		final DirCache dc = db.readDirCache();
 
 		final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" };
 		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
@@ -172,7 +172,7 @@ public void testTwoLevelSubtree() throws Exception {
 	 * @throws IOException
 	 */
 	public void testWriteReadTree() throws CorruptObjectException, IOException {
-		final DirCache dc = DirCache.lock(db);
+		final DirCache dc = db.lockDirCache();
 
 		final String A = String.format("a%2000s", "a");
 		final String B = String.format("b%2000s", "b");
@@ -188,7 +188,7 @@ public void testWriteReadTree() throws CorruptObjectException, IOException {
 			b.add(ents[i]);
 
 		b.commit();
-		DirCache read = DirCache.read(db);
+		DirCache read = db.readDirCache();
 
 		assertEquals(paths.length, read.getEntryCount());
 		assertEquals(1, read.getCacheTree(true).getChildCount());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreCacheTest.java
deleted file mode 100644
index a9c1c31..0000000
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreCacheTest.java
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Copyright (C) 2010, Red Hat Inc.
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.eclipse.jgit.ignore;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-
-import org.eclipse.jgit.lib.RepositoryTestCase;
-import org.eclipse.jgit.util.JGitTestUtil;
-
-/**
- * Tests for the ignore cache
- */
-public class IgnoreCacheTest extends RepositoryTestCase {
-
-	private File ignoreTestDir = JGitTestUtil.getTestResourceFile("excludeTest");
-	private SimpleIgnoreCache cache;
-	private final ArrayList<File> toDelete = new ArrayList<File>();
-
-	//TODO: Do not use OS dependent strings to encode file paths
-
-	public void tearDown() throws Exception {
-		super.tearDown();
-		deleteIgnoreFiles();
-		cache.clear();
-		toDelete.clear();
-	}
-
-	public void setUp() throws Exception {
-		super.setUp();
-		ignoreTestDir = JGitTestUtil.getTestResourceFile("excludeTest");
-		assertTrue("Test resource directory is not a directory",ignoreTestDir.isDirectory());
-
-		db = createWorkRepository();
-		recursiveCopy(ignoreTestDir, db.getDirectory().getParentFile());
-		cache = new SimpleIgnoreCache(db);
-		initCache();
-	}
-
-	protected void recursiveCopy(File src, File parent) throws IOException {
-		for (File file : src.listFiles()) {
-			String rel = file.getName();
-			File dst = new File(parent.toURI().resolve(rel));
-			copyFileOrDirectory(file, dst);
-			if (file.isDirectory())
-				recursiveCopy(file, dst);
-		}
-	}
-
-	protected static void copyFileOrDirectory(File src, File dst) throws IOException {
-		if (src.isDirectory())
-			dst.mkdir();
-		else
-			copyFile(src, dst);
-	}
-
-	public void testInitialization() {
-		File test = new File(db.getDirectory().getParentFile() + "/new/a/b1/test.stp");
-		assertTrue("Missing file " + test.getAbsolutePath(), test.exists());
-
-		/*
-		 * Every folder along the path has a .gitignore file. Therefore every
-		 * folder should have been added and initialized
-		 */
-		boolean result = isIgnored(getRelativePath(test));
-		assertFalse("Unexpected match for " + test.toString(), result);
-
-		/*
-		 * Check that every .gitignore along the path has been initialized
-		 */
-		File folder = test.getParentFile();
-		IgnoreNode rules = null;
-		String fp = folder.getAbsolutePath();
-		while (!folder.equals(db.getDirectory().getParentFile()) && fp.length() > 0) {
-			rules = cache.getRules(getRelativePath(folder));
-			assertNotNull("Ignore file not initialized for " + fp, rules);
-			if (getRelativePath(folder).endsWith("new/a"))
-				//The /new/a directory has an empty ignore file
-				assertEquals("Ignore file not initialized for " + fp, 0, rules.getRules().size());
-			else
-				assertEquals("Ignore file not initialized for " + fp, 1, rules.getRules().size());
-
-			folder = folder.getParentFile();
-			fp = folder.getAbsolutePath();
-		}
-		if (rules != null)
-			assertEquals(1, rules.getRules().size());
-		else
-			fail("Base directory not initialized");
-
-	}
-
-	public void testRules() {
-		ignoreTestDir = JGitTestUtil.getTestResourceFile("excludeTest");
-		assertTrue("Test resource directory is not a directory", ignoreTestDir.isDirectory());
-		createExcludeFile();
-		initCache();
-
-		File test = new File(db.getDirectory().getParentFile(), "test.stp");
-		String path = test.getAbsolutePath();
-		assertTrue("Could not find test file " + path, test.exists());
-
-		IgnoreNode baseRules = cache.getRules("");
-		assertNotNull("Could not find base rules", baseRules);
-
-		/*
-		 * .git/info/excludes:
-		 * /test.stp
-		 * /notignored
-		 *
-		 * new/.gitignore:
-		 * notarealfile
-		 *
-		 * new/a/.gitignore:
-		 * <empty>
-		 *
-		 * new/a/b2/.gitignore:
-		 * <does not exist>
-		 *
-		 * new/a/b1/.gitignore:
-		 * /c
-		 *
-		 * new/a/b1/c/.gitignore:
-		 * !/shouldbeignored.txt
-		 *
-		 * .gitignore:
-		 * !/notignored
-		 * /commentNotIgnored.tx#t
-		 * /commentIgnored.txt#comment
-		 * /commentIgnored.txt #comment
-		 */
-		boolean result = isIgnored(getRelativePath(test));
-		assertEquals(3, baseRules.getRules().size());
-		assertTrue(db.getDirectory().getParentFile().toURI().equals(baseRules.getBaseDir().toURI()));
-		//Test basic exclude file
-		assertTrue("Did not match file " + test.toString(), result);
-		//Test exclude file priority
-		assertNotIgnored("notignored");
-		//Test that /src/test.stp is not matched by /test.stp in exclude file (Do not reinitialize)
-		assertNotIgnored("/src/test.stp");
-		//Test file that is not mentioned -- should just return unmatched
-		assertNotIgnored("not/mentioned/file.txt");
-
-		//Test adding nonexistent node
-		test = new File(db.getDirectory().getParentFile(), "new/a/b2/d/test.stp");
-		assertNotIgnored("new/a/b2/d/test.stp");
-		assertNotIgnored("new/a/b2/d/");
-		assertNotIgnored("new/a/b2/d");
-
-		//Test folder
-		test = new File(db.getDirectory().getParentFile(), "new/a/b1/c");
-		assertIgnored("new/a/b1/c");
-		assertIgnored("new/a/b1/c/anything.c");
-		assertIgnored("new/a/b1/c/and.o");
-		assertIgnored("new/a/b1/c/everything.d");
-		assertIgnored("new/a/b1/c/everything.d");
-		//Special case -- the normally higher priority negation in c/.gitignore is cancelled by the folder being ignored
-		assertIgnored("new/a/b1/c/shouldbeignored.txt");
-
-		//Test name-only (use non-existent folders)
-		assertNotIgnored("notarealfile");
-		assertNotIgnored("/notarealfile");
-		assertIgnored("new/notarealfile");
-		assertIgnored("new/notarealfile/fake");
-		assertIgnored("new/a/notarealfile");
-		assertIgnored("new/a/b1/notarealfile");
-
-		//Test clearing node -- create empty .gitignore
-		createIgnoreFile(db.getDirectory().getParentFile() + "/new/a/b2/.gitignore", new String[0]);
-		test = new File(db.getDirectory().getParentFile(), "new/a/b2/c");
-		initCache();
-		baseRules = cache.getRules("new/a/b2");
-		assertNotNull(baseRules);
-		baseRules.clear();
-		assertEquals(baseRules.getRules().size(), 0);
-		try {
-			assertFalse("Node not properly cleared", baseRules.isIgnored(getRelativePath(test)));
-		} catch (IOException e) {
-			e.printStackTrace();
-			fail("IO exception when testing base rules");
-		}
-
-		//Test clearing entire cache, and isEmpty
-		assertNotNull(cache.getRules(""));
-		assertFalse(cache.isEmpty());
-		cache.clear();
-		assertNull(cache.getRules(""));
-		assertTrue(cache.isEmpty());
-		assertNotIgnored("/anything");
-		assertNotIgnored("/new/anything");
-		assertNotIgnored("/src/anything");
-	}
-
-	public void testPriorities() {
-		ignoreTestDir = JGitTestUtil.getTestResourceFile("excludeTest");
-		assertTrue("Test resource directory is not a directory",ignoreTestDir.isDirectory());
-		createExcludeFile();
-		initCache();
-
-		File test = new File(db.getDirectory().getParentFile(), "/src/test.stp");
-		assertTrue("Resource file " + test.getName() + " is missing", test.exists());
-
-		//Test basic exclude file
-		IgnoreNode node = cache.getRules("src");
-		assertNotNull("Excludes file was not initialized", node);
-
-		/*
-		 * src/.gitignore:
-		 * /*.st?
-		 * !/test.stp
-		 * !/a.c
-		 * /a.c
-		 *
-		 * ./.gitignore:
-		 * !/notignored
-		 *
-		 * .git/info/exclude:
-		 * /test.stp
-		 * /notignored
-		 */
-		assertIgnored("src/a.c");
-		assertIgnored("test.stp");
-		assertIgnored("src/blank.stp");
-		assertNotIgnored("notignored");
-		assertNotIgnored("src/test.stp");
-
-		assertEquals(4, node.getRules().size());
-
-		/*
-		 * new/.gitignore:
-		 * notarealfile
-		 *
-		 * new/a/.gitignore:
-		 * <empty>
-		 *
-		 * new/a/b2/.gitignore:
-		 * <does not exist>
-		 *
-		 * new/a/b2/c/.gitignore:
-		 * /notarealfile2
-		 */
-		assertIgnored("new/a/b2/c/notarealfile2");
-		assertIgnored("new/notarealfile");
-		assertIgnored("new/a/notarealfile");
-		assertNotIgnored("new/a/b2/c/test.stp");
-		assertNotIgnored("new/a/b2/c");
-		assertNotIgnored("new/a/b2/nonexistent");
-	}
-
-	/**
-	 * Check if a file is not matched as ignored
-	 * @param relativePath
-	 * 			  Path to file, relative to db.getDirectory. Use "/" as a separator,
-	 * 			  this method will replace all instances of "/" with File.separator
-	 */
-	private void assertNotIgnored(String relativePath) {
-		File test = new File(db.getDirectory().getParentFile(), relativePath);
-		assertFalse("Should not match " + test.toString(), isIgnored(getRelativePath(test)));
-	}
-
-	/**
-	 * Check if a file is matched as ignored
-	 * @param relativePath
-	 * 			  Path to file, relative to db.getDirectory. Use "/" as a separator,
-	 * 			  this method will replace all instances of "/" with File.separator.
-	 */
-	private void assertIgnored(String relativePath) {
-		File test = new File(db.getDirectory().getParentFile(), relativePath);
-		assertTrue("Failed to match " + test.toString(), isIgnored(getRelativePath(test)));
-	}
-
-	/**
-	 * Attempt to write an ignore file at the given location
-	 * @param path
-	 * 			  Will create file at this path
-	 * @param contents
-	 * 			  Each entry in contents will be entered on its own line
-	 */
-	private void createIgnoreFile(String path, String[] contents) {
-		File ignoreFile = new File(path);
-		ignoreFile.delete();
-		ignoreFile.deleteOnExit();	//Hope to catch in the event of crash
-		toDelete.add(ignoreFile);	//For teardown purposes
-
-		//Jump through some hoops to create the exclude file
-		try {
-			if (!ignoreFile.createNewFile())
-				fail("Could not create ignore file" + ignoreFile.getAbsolutePath());
-
-			BufferedWriter bw = new BufferedWriter(new FileWriter (ignoreFile));
-			for (String s : contents)
-				bw.write(s + System.getProperty("line.separator"));
-			bw.flush();
-			bw.close();
-		} catch (IOException e1) {
-			e1.printStackTrace();
-			fail("Could not create exclude file");
-		}
-	}
-
-	private void createExcludeFile() {
-		String[] content = new String[2];
-		content[0] = "/test.stp";
-		content[1] = "/notignored";
-
-		//We can do this because we explicitly delete parent directories later in deleteIgnoreFiles.
-		File parent= new File(db.getDirectory().getParentFile(), ".git/info");
-		if (!parent.exists())
-			parent.mkdirs();
-
-		createIgnoreFile(db.getDirectory().getParentFile() + "/.git/info/exclude", content);
-	}
-
-	private void deleteIgnoreFiles() {
-		for (File f : toDelete)
-			f.delete();
-
-		//Systematically delete exclude parent dirs
-		File f = new File(ignoreTestDir.getAbsoluteFile(), ".git/info");
-		f.delete();
-		f = new File(ignoreTestDir.getAbsoluteFile(), ".git");
-		f.delete();
-	}
-
-	/**
-	 * @param path
-	 * 			  Filepath relative to the git directory
-	 * @return
-	 * 			  Results of cache.isIgnored(path) -- true if ignored, false if
-	 * 			  a negation is encountered or if no rules apply
-	 */
-	private boolean isIgnored(String path) {
-		try {
-			return cache.isIgnored(path);
-		} catch (IOException e) {
-			fail("IOException when attempting to check ignored status");
-		}
-		return false;
-	}
-
-	private String getRelativePath(File file) {
-		String retVal = db.getDirectory().getParentFile().toURI().relativize(file.toURI()).getPath();
-		if (retVal.length() == file.getAbsolutePath().length())
-			fail("Not a child of the git directory");
-		if (retVal.endsWith("/"))
-			retVal = retVal.substring(0, retVal.length() - 1);
-
-		return retVal;
-	}
-
-	private void initCache() {
-		try {
-			cache.initialize();
-		} catch (IOException e) {
-			e.printStackTrace();
-			fail("Could not initialize cache");
-		}
-	}
-
-}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
new file mode 100644
index 0000000..ea78204
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2010, Red Hat Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.ignore;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.RepositoryTestCase;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+
+/**
+ * Tests ignore node behavior on the local filesystem.
+ */
+public class IgnoreNodeTest extends RepositoryTestCase {
+	private static final FileMode D = FileMode.TREE;
+
+	private static final FileMode F = FileMode.REGULAR_FILE;
+
+	private static final boolean ignored = true;
+
+	private static final boolean tracked = false;
+
+	private TreeWalk walk;
+
+	public void testRules() throws IOException {
+		writeIgnoreFile(".git/info/exclude", "*~", "/out");
+
+		writeIgnoreFile(".gitignore", "*.o", "/config");
+		writeTrashFile("config/secret", "");
+		writeTrashFile("mylib.c", "");
+		writeTrashFile("mylib.c~", "");
+		writeTrashFile("mylib.o", "");
+
+		writeTrashFile("out/object/foo.exe", "");
+		writeIgnoreFile("src/config/.gitignore", "lex.out");
+		writeTrashFile("src/config/lex.out", "");
+		writeTrashFile("src/config/config.c", "");
+		writeTrashFile("src/config/config.c~", "");
+		writeTrashFile("src/config/old/lex.out", "");
+
+		beginWalk();
+		assertEntry(F, tracked, ".gitignore");
+		assertEntry(D, ignored, "config");
+		assertEntry(F, ignored, "config/secret");
+		assertEntry(F, tracked, "mylib.c");
+		assertEntry(F, ignored, "mylib.c~");
+		assertEntry(F, ignored, "mylib.o");
+
+		assertEntry(D, ignored, "out");
+		assertEntry(D, ignored, "out/object");
+		assertEntry(F, ignored, "out/object/foo.exe");
+
+		assertEntry(D, tracked, "src");
+		assertEntry(D, tracked, "src/config");
+		assertEntry(F, tracked, "src/config/.gitignore");
+		assertEntry(F, tracked, "src/config/config.c");
+		assertEntry(F, ignored, "src/config/config.c~");
+		assertEntry(F, ignored, "src/config/lex.out");
+		assertEntry(D, tracked, "src/config/old");
+		assertEntry(F, ignored, "src/config/old/lex.out");
+	}
+
+	public void testNegation() throws IOException {
+		writeIgnoreFile(".gitignore", "*.o");
+		writeIgnoreFile("src/a/b/.gitignore", "!keep.o");
+		writeTrashFile("src/a/b/keep.o", "");
+		writeTrashFile("src/a/b/nothere.o", "");
+
+		beginWalk();
+		assertEntry(F, tracked, ".gitignore");
+		assertEntry(D, tracked, "src");
+		assertEntry(D, tracked, "src/a");
+		assertEntry(D, tracked, "src/a/b");
+		assertEntry(F, tracked, "src/a/b/.gitignore");
+		assertEntry(F, tracked, "src/a/b/keep.o");
+		assertEntry(F, ignored, "src/a/b/nothere.o");
+	}
+
+	public void testSlashOnlyMatchesDirectory() throws IOException {
+		writeIgnoreFile(".gitignore", "out/");
+		writeTrashFile("out", "");
+
+		beginWalk();
+		assertEntry(F, tracked, ".gitignore");
+		assertEntry(F, tracked, "out");
+
+		new File(trash, "out").delete();
+		writeTrashFile("out/foo", "");
+
+		beginWalk();
+		assertEntry(F, tracked, ".gitignore");
+		assertEntry(D, ignored, "out");
+		assertEntry(F, ignored, "out/foo");
+	}
+
+	public void testWithSlashDoesNotMatchInSubDirectory() throws IOException {
+		writeIgnoreFile(".gitignore", "a/b");
+		writeTrashFile("a/a", "");
+		writeTrashFile("a/b", "");
+		writeTrashFile("src/a/a", "");
+		writeTrashFile("src/a/b", "");
+
+		beginWalk();
+		assertEntry(F, tracked, ".gitignore");
+		assertEntry(D, tracked, "a");
+		assertEntry(F, tracked, "a/a");
+		assertEntry(F, ignored, "a/b");
+		assertEntry(D, tracked, "src");
+		assertEntry(D, tracked, "src/a");
+		assertEntry(F, tracked, "src/a/a");
+		assertEntry(F, tracked, "src/a/b");
+	}
+
+	private void beginWalk() throws CorruptObjectException {
+		walk = new TreeWalk(db);
+		walk.reset();
+		walk.addTree(new FileTreeIterator(db));
+	}
+
+	private void assertEntry(FileMode type, boolean entryIgnored,
+			String pathName) throws IOException {
+		assertTrue("walk has entry", walk.next());
+		assertEquals(pathName, walk.getPathString());
+		assertEquals(type, walk.getFileMode(0));
+
+		WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class);
+		assertNotNull("has tree", itr);
+		assertEquals("is ignored", entryIgnored, itr.isEntryIgnored());
+		if (D.equals(type))
+			walk.enterSubtree();
+	}
+
+	private void writeIgnoreFile(String name, String... rules)
+			throws IOException {
+		StringBuilder data = new StringBuilder();
+		for (String line : rules)
+			data.append(line + "\n");
+		writeTrashFile(name, data.toString());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
index ac7ce5b..c439bac 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
@@ -48,16 +48,21 @@
 import java.io.File;
 import java.io.IOException;
 
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+
 public class IndexDiffTest extends RepositoryTestCase {
 	public void testAdded() throws IOException {
 		GitIndex index = new GitIndex(db);
 		writeTrashFile("file1", "file1");
 		writeTrashFile("dir/subfile", "dir/subfile");
 		Tree tree = new Tree(db);
+		tree.setId(new ObjectWriter(db).writeTree(tree));
 
 		index.add(trash, new File(trash, "file1"));
 		index.add(trash, new File(trash, "dir/subfile"));
-		IndexDiff diff = new IndexDiff(tree, index);
+		index.write();
+		FileTreeIterator iterator = new FileTreeIterator(db);
+		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
 		diff.diff();
 		assertEquals(2, diff.getAdded().size());
 		assertTrue(diff.getAdded().contains("file1"));
@@ -68,7 +73,6 @@ public void testAdded() throws IOException {
 	}
 
 	public void testRemoved() throws IOException {
-		GitIndex index = new GitIndex(db);
 		writeTrashFile("file2", "file2");
 		writeTrashFile("dir/file3", "dir/file3");
 
@@ -82,7 +86,8 @@ public void testRemoved() throws IOException {
 		tree2.setId(new ObjectWriter(db).writeTree(tree2));
 		tree.setId(new ObjectWriter(db).writeTree(tree));
 
-		IndexDiff diff = new IndexDiff(tree, index);
+		FileTreeIterator iterator = new FileTreeIterator(db);
+		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
 		diff.diff();
 		assertEquals(2, diff.getRemoved().size());
 		assertTrue(diff.getRemoved().contains("file2"));
@@ -98,6 +103,7 @@ public void testModified() throws IOException {
 
 		index.add(trash, writeTrashFile("file2", "file2"));
 		index.add(trash, writeTrashFile("dir/file3", "dir/file3"));
+		index.write();
 
 		writeTrashFile("dir/file3", "changed");
 
@@ -109,7 +115,8 @@ public void testModified() throws IOException {
 		Tree tree2 = (Tree) tree.findTreeMember("dir");
 		tree2.setId(new ObjectWriter(db).writeTree(tree2));
 		tree.setId(new ObjectWriter(db).writeTree(tree));
-		IndexDiff diff = new IndexDiff(tree, index);
+		FileTreeIterator iterator = new FileTreeIterator(db);
+		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
 		diff.diff();
 		assertEquals(2, diff.getChanged().size());
 		assertTrue(diff.getChanged().contains("file2"));
@@ -128,6 +135,7 @@ public void testUnchangedSimple() throws IOException {
 		index.add(trash, writeTrashFile("a.c", "a.c"));
 		index.add(trash, writeTrashFile("a=c", "a=c"));
 		index.add(trash, writeTrashFile("a=d", "a=d"));
+		index.write();
 
 		Tree tree = new Tree(db);
 		// got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin
@@ -138,7 +146,8 @@ public void testUnchangedSimple() throws IOException {
 
 		tree.setId(new ObjectWriter(db).writeTree(tree));
 
-		IndexDiff diff = new IndexDiff(tree, index);
+		FileTreeIterator iterator = new FileTreeIterator(db);
+		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
 		diff.diff();
 		assertEquals(0, diff.getChanged().size());
 		assertEquals(0, diff.getAdded().size());
@@ -163,6 +172,7 @@ public void testUnchangedComplex() throws IOException {
 		index.add(trash, writeTrashFile("a/c", "a/c"));
 		index.add(trash, writeTrashFile("a=c", "a=c"));
 		index.add(trash, writeTrashFile("a=d", "a=d"));
+		index.write();
 
 		Tree tree = new Tree(db);
 		// got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin
@@ -180,7 +190,8 @@ public void testUnchangedComplex() throws IOException {
 		tree2.setId(new ObjectWriter(db).writeTree(tree2));
 		tree.setId(new ObjectWriter(db).writeTree(tree));
 
-		IndexDiff diff = new IndexDiff(tree, index);
+		FileTreeIterator iterator = new FileTreeIterator(db);
+		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
 		diff.diff();
 		assertEquals(0, diff.getChanged().size());
 		assertEquals(0, diff.getAdded().size());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java
new file mode 100644
index 0000000..fbb7924
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+
+public class MergeHeadMsgTest extends RepositoryTestCase {
+	private static final String mergeMsg = "merge a and b";
+
+	private static final String sampleId = "1c6db447abdbb291b25f07be38ea0b1bf94947c5";
+
+	public void testReadWriteMergeHeads() throws IOException {
+		assertEquals(db.readMergeHeads(), null);
+		db.writeMergeHeads(Arrays.asList(ObjectId.zeroId(),
+				ObjectId.fromString(sampleId)));
+		assertEquals(read(new File(db.getDirectory(), "MERGE_HEAD")), "0000000000000000000000000000000000000000\n1c6db447abdbb291b25f07be38ea0b1bf94947c5\n");
+		assertEquals(db.readMergeHeads().size(), 2);
+		assertEquals(db.readMergeHeads().get(0), ObjectId.zeroId());
+		assertEquals(db.readMergeHeads().get(1), ObjectId.fromString(sampleId));
+		// same test again, this time with lower-level io
+		FileOutputStream fos = new FileOutputStream(new File(db.getDirectory(),
+		"MERGE_HEAD"));
+		try {
+			fos.write("0000000000000000000000000000000000000000\n1c6db447abdbb291b25f07be38ea0b1bf94947c5\n".getBytes(Constants.CHARACTER_ENCODING));
+		} finally {
+			fos.close();
+		}
+		assertEquals(db.readMergeHeads().size(), 2);
+		assertEquals(db.readMergeHeads().get(0), ObjectId.zeroId());
+		assertEquals(db.readMergeHeads().get(1), ObjectId.fromString(sampleId));
+		db.writeMergeHeads(Collections.EMPTY_LIST);
+		assertEquals(read(new File(db.getDirectory(), "MERGE_HEAD")), "");
+		assertEquals(db.readMergeHeads(), null);
+		fos = new FileOutputStream(new File(db.getDirectory(),
+				"MERGE_HEAD"));
+		try {
+			fos.write(sampleId.getBytes(Constants.CHARACTER_ENCODING));
+		} finally {
+			fos.close();
+		}
+		assertEquals(db.readMergeHeads().size(), 1);
+		assertEquals(db.readMergeHeads().get(0), ObjectId.fromString(sampleId));
+	}
+
+	public void testReadWriteMergeMsg() throws IOException {
+		assertEquals(db.readMergeCommitMsg(), null);
+		assertFalse(new File(db.getDirectory(), "MERGE_MSG").exists());
+		db.writeMergeCommitMsg(mergeMsg);
+		assertEquals(db.readMergeCommitMsg(), mergeMsg);
+		assertEquals(read(new File(db.getDirectory(), "MERGE_MSG")), mergeMsg);
+		db.writeMergeCommitMsg(null);
+		assertEquals(db.readMergeCommitMsg(), null);
+		assertFalse(new File(db.getDirectory(), "MERGE_MSG").exists());
+		FileOutputStream fos = new FileOutputStream(new File(db.getDirectory(),
+				Constants.MERGE_MSG));
+		try {
+			fos.write(mergeMsg.getBytes(Constants.CHARACTER_ENCODING));
+		} finally {
+			fos.close();
+		}
+		assertEquals(db.readMergeCommitMsg(), mergeMsg);
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
new file mode 100644
index 0000000..e208b27
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.TreeSet;
+
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.FileTreeIteratorWithTimeControl;
+import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
+
+public class RacyGitTests extends RepositoryTestCase {
+	public void testIterator() throws IllegalStateException, IOException,
+			InterruptedException {
+		TreeSet<Long> modTimes = new TreeSet<Long>();
+		File lastFile = null;
+		for (int i = 0; i < 10; i++) {
+			lastFile = new File(db.getWorkTree(), "0." + i);
+			lastFile.createNewFile();
+			if (i == 5)
+				fsTick(lastFile);
+		}
+		modTimes.add(fsTick(lastFile));
+		for (int i = 0; i < 10; i++) {
+			lastFile = new File(db.getWorkTree(), "1." + i);
+			lastFile.createNewFile();
+		}
+		modTimes.add(fsTick(lastFile));
+		for (int i = 0; i < 10; i++) {
+			lastFile = new File(db.getWorkTree(), "2." + i);
+			lastFile.createNewFile();
+			if (i % 4 == 0)
+				fsTick(lastFile);
+		}
+		FileTreeIteratorWithTimeControl fileIt = new FileTreeIteratorWithTimeControl(
+				db, modTimes);
+		NameConflictTreeWalk tw = new NameConflictTreeWalk(db);
+		tw.reset();
+		tw.addTree(fileIt);
+		tw.setRecursive(true);
+		FileTreeIterator t;
+		long t0 = 0;
+		for (int i = 0; i < 10; i++) {
+			assertTrue(tw.next());
+			t = tw.getTree(0, FileTreeIterator.class);
+			if (i == 0)
+				t0 = t.getEntryLastModified();
+			else
+				assertEquals(t0, t.getEntryLastModified());
+		}
+		long t1 = 0;
+		for (int i = 0; i < 10; i++) {
+			assertTrue(tw.next());
+			t = tw.getTree(0, FileTreeIterator.class);
+			if (i == 0) {
+				t1 = t.getEntryLastModified();
+				assertTrue(t1 > t0);
+			} else
+				assertEquals(t1, t.getEntryLastModified());
+		}
+		long t2 = 0;
+		for (int i = 0; i < 10; i++) {
+			assertTrue(tw.next());
+			t = tw.getTree(0, FileTreeIterator.class);
+			if (i == 0) {
+				t2 = t.getEntryLastModified();
+				assertTrue(t2 > t1);
+			} else
+				assertEquals(t2, t.getEntryLastModified());
+		}
+	}
+
+	public void testRacyGitDetection() throws IOException,
+			IllegalStateException, InterruptedException {
+		TreeSet<Long> modTimes = new TreeSet<Long>();
+		File lastFile;
+
+		// wait to ensure that modtimes of the file doesn't match last index
+		// file modtime
+		modTimes.add(fsTick(db.getIndexFile()));
+
+		// create two files
+		addToWorkDir("a", "a");
+		lastFile = addToWorkDir("b", "b");
+
+		// wait to ensure that file-modTimes and therefore index entry modTime
+		// doesn't match the modtime of index-file after next persistance
+		modTimes.add(fsTick(lastFile));
+
+		// now add both files to the index. No racy git expected
+		addToIndex(modTimes);
+
+		assertEquals(
+				"[a, mode:100644, time:t0, length:1, sha1:2e65efe2a145dda7ee51d1741299f848e5bf752e]" +
+				"[b, mode:100644, time:t0, length:1, sha1:63d8dbd40c23542e740659a7168a0ce3138ea748]",
+				indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT_ID));
+
+		// Remember the last modTime of index file. All modifications times of
+		// further modification are translated to this value so it looks that
+		// files have been modified in the same time slot as the index file
+		modTimes.add(Long.valueOf(db.getIndexFile().lastModified()));
+
+		// modify one file
+		addToWorkDir("a", "a2");
+		// now update the index the index. 'a' has to be racily clean -- because
+		// it's modification time is exactly the same as the previous index file
+		// mod time.
+		addToIndex(modTimes);
+
+		db.readDirCache();
+		// although racily clean a should not be reported as being dirty
+		assertEquals(
+				"[a, mode:100644, time:t1, smudged, length:0]" +
+				"[b, mode:100644, time:t0, length:1]",
+				indexState(SMUDGE|MOD_TIME|LENGTH));
+	}
+
+	private void addToIndex(TreeSet<Long> modTimes)
+			throws FileNotFoundException, IOException {
+		DirCacheBuilder builder = db.lockDirCache().builder();
+		FileTreeIterator fIt = new FileTreeIteratorWithTimeControl(
+				db, modTimes);
+		DirCacheEntry dce;
+		while (!fIt.eof()) {
+			dce = new DirCacheEntry(fIt.getEntryPathString());
+			dce.setFileMode(fIt.getEntryFileMode());
+			dce.setLastModified(fIt.getEntryLastModified());
+			dce.setLength((int) fIt.getEntryLength());
+			dce.setObjectId(fIt.getEntryObjectId());
+			builder.add(dce);
+			fIt.next(1);
+		}
+		builder.commit();
+	}
+
+	private File addToWorkDir(String path, String content) throws IOException {
+		File f = new File(db.getWorkTree(), path);
+		FileOutputStream fos = new FileOutputStream(f);
+		try {
+			fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
+			return f;
+		} finally {
+			fos.close();
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java
index 5b4be43..2fa45c8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java
@@ -45,11 +45,9 @@
  */
 package org.eclipse.jgit.lib;
 
-import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -58,7 +56,6 @@
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.util.FS;
 
 public abstract class ReadTreeTest extends RepositoryTestCase {
 	protected Tree theHead;
@@ -132,13 +129,15 @@ private Tree buildTree(HashMap<String, String> headEntries) throws IOException {
 	}
 
 	ObjectId genSha1(String data) {
-		InputStream is = new ByteArrayInputStream(data.getBytes());
-		ObjectWriter objectWriter = new ObjectWriter(db);
+		ObjectInserter w = db.newObjectInserter();
 		try {
-			return objectWriter.writeObject(Constants.OBJ_BLOB, data
-					.getBytes().length, is, true);
+			ObjectId id = w.insert(Constants.OBJ_BLOB, data.getBytes());
+			w.flush();
+			return id;
 		} catch (IOException e) {
 			fail(e.toString());
+		} finally {
+			w.release();
 		}
 		return null;
 	}
@@ -613,7 +612,7 @@ public void assertWorkDir(HashMap<String, String> i)
 		TreeWalk walk = new TreeWalk(db);
 		walk.reset();
 		walk.setRecursive(true);
-		walk.addTree(new FileTreeIterator(db.getWorkDir(), FS.DETECTED));
+		walk.addTree(new FileTreeIterator(db));
 		String expectedValue;
 		String path;
 		int nrFiles = 0;
@@ -624,7 +623,7 @@ public void assertWorkDir(HashMap<String, String> i)
 			expectedValue = i.get(path);
 			assertNotNull("found unexpected file for path "
 					+ path + " in workdir", expectedValue);
-			File file = new File(db.getWorkDir(), path);
+			File file = new File(db.getWorkTree(), path);
 			assertTrue(file.exists());
 			if (file.isFile()) {
 				FileInputStream is = new FileInputStream(file);
@@ -662,8 +661,8 @@ public void assertIndex(HashMap<String, String> i)
 			assertTrue("unexpected content for path " + path
 					+ " in index. Expected: <" + expectedValue + ">",
 					Arrays.equals(
-							db.openBlob(theIndex.getMembers()[j].getObjectId())
-									.getBytes(), i.get(path).getBytes()));
+							db.open(theIndex.getMembers()[j].getObjectId())
+									.getCachedBytes(), i.get(path).getBytes()));
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
index 88bcf76..d78892b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
@@ -70,7 +70,7 @@ public void testlogAllRefUpdates() throws Exception {
 
 		// set the logAllRefUpdates parameter to true and check it
 		db.getConfig().setBoolean("core", null, "logallrefupdates", true);
-		assertTrue(db.getConfig().getCore().isLogAllRefUpdates());
+		assertTrue(db.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates());
 
 		// do one commit and check that reflog size is increased to 1
 		addFileToTree(t, "i-am-another-file", "and this is other data in me\n");
@@ -83,7 +83,7 @@ public void testlogAllRefUpdates() throws Exception {
 
 		// set the logAllRefUpdates parameter to false and check it
 		db.getConfig().setBoolean("core", null, "logallrefupdates", false);
-		assertFalse(db.getConfig().getCore().isLogAllRefUpdates());
+		assertFalse(db.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates());
 
 		// do one commit and check that reflog size is 2
 		addFileToTree(t, "i-am-anotheranother-file",
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java
index c5c6d99..963c9d0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java
@@ -52,8 +52,14 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.Reader;
+import java.util.Map;
+import java.util.TreeSet;
 
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
 
 /**
  * Base class for most JGit unit tests.
@@ -83,7 +89,7 @@ protected static void copyFile(final File src, final File dst)
 
 	protected File writeTrashFile(final String name, final String data)
 			throws IOException {
-		File path = new File(db.getWorkDir(), name);
+		File path = new File(db.getWorkTree(), name);
 		write(path, data);
 		return path;
 	}
@@ -102,7 +108,7 @@ protected static void checkFile(File f, final String checkData)
 	}
 
 	/** Test repository, initialized for this test case. */
-	protected Repository db;
+	protected FileRepository db;
 
 	/** Working directory of {@link #db}. */
 	protected File trash;
@@ -111,6 +117,156 @@ protected static void checkFile(File f, final String checkData)
 	protected void setUp() throws Exception {
 		super.setUp();
 		db = createWorkRepository();
-		trash = db.getWorkDir();
+		trash = db.getWorkTree();
+	}
+
+	public static final int MOD_TIME = 1;
+
+	public static final int SMUDGE = 2;
+
+	public static final int LENGTH = 4;
+
+	public static final int CONTENT_ID = 8;
+
+	/**
+	 * Represent the state of the index in one String. This representation is
+	 * useful when writing tests which do assertions on the state of the index.
+	 * By default information about path, mode, stage (if different from 0) is
+	 * included. A bitmask controls which additional info about
+	 * modificationTimes, smudge state and length is included.
+	 * <p>
+	 * The format of the returned string is described with this BNF:
+	 *
+	 * <pre>
+	 * result = ( "[" path mode stage? time? smudge? length? sha1? "]" )* .
+	 * mode = ", mode:" number .
+	 * stage = ", stage:" number .
+	 * time = ", time:t" timestamp-index .
+	 * smudge = "" | ", smudged" .
+	 * length = ", length:" number .
+	 * sha1 = ", sha1:" hex-sha1 .
+	 *
+	 * 'stage' is only presented when the stage is different from 0. All
+	 * reported time stamps are mapped to strings like "t0", "t1", ... "tn". The
+	 * smallest reported time-stamp will be called "t0". This allows to write
+	 * assertions against the string although the concrete value of the
+	 * time stamps is unknown.
+	 *
+	 * @param includedOptions
+	 *            a bitmask constructed out of the constants {@link #MOD_TIME},
+	 *            {@link #SMUDGE}, {@link #LENGTH} and {@link #CONTENT_ID}
+	 *            controlling which info is present in the resulting string.
+	 * @return a string encoding the index state
+	 * @throws IllegalStateException
+	 * @throws IOException
+	 */
+	public String indexState(int includedOptions)
+			throws IllegalStateException, IOException {
+		DirCache dc = db.readDirCache();
+		StringBuilder sb = new StringBuilder();
+		TreeSet<Long> timeStamps = null;
+
+		// iterate once over the dircache just to collect all time stamps
+		if (0 != (includedOptions & MOD_TIME)) {
+			timeStamps = new TreeSet<Long>();
+			for (int i=0; i<dc.getEntryCount(); ++i)
+				timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified()));
+		}
+
+		// iterate again, now produce the result string
+		NameConflictTreeWalk tw = new NameConflictTreeWalk(db);
+		tw.setRecursive(true);
+		tw.reset();
+		tw.addTree(new DirCacheIterator(dc));
+		while (tw.next()) {
+			DirCacheIterator dcIt = tw.getTree(0, DirCacheIterator.class);
+			sb.append("["+tw.getPathString()+", mode:" + dcIt.getEntryFileMode());
+			int stage = dcIt.getDirCacheEntry().getStage();
+			if (stage != 0)
+				sb.append(", stage:" + stage);
+			if (0 != (includedOptions & MOD_TIME)) {
+				sb.append(", time:t"+
+					timeStamps.headSet(Long.valueOf(dcIt.getDirCacheEntry().getLastModified())).size());
+			}
+			if (0 != (includedOptions & SMUDGE))
+				if (dcIt.getDirCacheEntry().isSmudged())
+					sb.append(", smudged");
+			if (0 != (includedOptions & LENGTH))
+				sb.append(", length:"
+						+ Integer.toString(dcIt.getDirCacheEntry().getLength()));
+			if (0 != (includedOptions & CONTENT_ID))
+				sb.append(", sha1:" + ObjectId.toString(dcIt
+								.getEntryObjectId()));
+			sb.append("]");
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Helper method to map arbitrary objects to user-defined names. This can be
+	 * used create short names for objects to produce small and stable debug
+	 * output. It is guaranteed that when you lookup the same object multiple
+	 * times even with different nameTemplates this method will always return
+	 * the same name which was derived from the first nameTemplate.
+	 * nameTemplates can contain "%n" which will be replaced by a running number
+	 * before used as a name.
+	 *
+	 * @param l
+	 *            the object to lookup
+	 * @param nameTemplate
+	 *            the name for that object. Can contain "%n" which will be
+	 *            replaced by a running number before used as a name. If the
+	 *            lookup table already contains the object this parameter will
+	 *            be ignored
+	 * @param lookupTable
+	 *            a table storing object-name mappings.
+	 * @return a name of that object. Is not guaranteed to be unique. Use
+	 *         nameTemplates containing "%n" to always have unique names
+	 */
+	public static String lookup(Object l, String nameTemplate,
+			Map<Object, String> lookupTable) {
+		String name = lookupTable.get(l);
+		if (name == null) {
+			name = nameTemplate.replaceAll("%n",
+					Integer.toString(lookupTable.size()));
+			lookupTable.put(l, name);
+		}
+		return name;
+	}
+
+	/**
+	 * Waits until it is guaranteed that a subsequent file modification has a
+	 * younger modification timestamp than the modification timestamp of the
+	 * given file. This is done by touching a temporary file, reading the
+	 * lastmodified attribute and, if needed, sleeping. After sleeping this loop
+	 * starts again until the filesystem timer has advanced enough.
+	 *
+	 * @param lastFile
+	 *            the file on which we want to wait until the filesystem timer
+	 *            has advanced more than the lastmodification timestamp of this
+	 *            file
+	 * @return return the last measured value of the filesystem timer which is
+	 *         greater than then the lastmodification time of lastfile.
+	 * @throws InterruptedException
+	 * @throws IOException
+	 */
+	public static long fsTick(File lastFile) throws InterruptedException,
+			IOException {
+		long sleepTime = 1;
+		File tmp = File.createTempFile("FileTreeIteratorWithTimeControl", null);
+		try {
+			long startTime = (lastFile == null) ? tmp.lastModified() : lastFile
+					.lastModified();
+			long actTime = tmp.lastModified();
+			while (actTime <= startTime) {
+				Thread.sleep(sleepTime);
+				sleepTime *= 5;
+				tmp.setLastModified(System.currentTimeMillis());
+				actTime = tmp.lastModified();
+			}
+			return actTime;
+		} finally {
+			tmp.delete();
+		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SampleDataRepositoryTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SampleDataRepositoryTestCase.java
index 10c0056..7bc9bb2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SampleDataRepositoryTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SampleDataRepositoryTestCase.java
@@ -65,7 +65,7 @@ protected void setUp() throws Exception {
 				"pack-e6d07037cbcf13376308a0a995d1fa48f8f76aaa",
 				"pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12"
 		};
-		final File packDir = new File(db.getObjectsDirectory(), "pack");
+		final File packDir = new File(db.getObjectDatabase().getDirectory(), "pack");
 		for (String n : packs) {
 			copyFile(JGitTestUtil.getTestResourceFile(n + ".pack"), new File(packDir, n + ".pack"));
 			copyFile(JGitTestUtil.getTestResourceFile(n + ".idx"), new File(packDir, n + ".idx"));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckout_ReadTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckout_ReadTreeTest.java
index ecaac58..7b5c3cd 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckout_ReadTreeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckout_ReadTreeTest.java
@@ -52,12 +52,12 @@
 public class WorkDirCheckout_ReadTreeTest extends ReadTreeTest {
 	private WorkDirCheckout wdc;
 	public void prescanTwoTrees(Tree head, Tree merge) throws IllegalStateException, IOException {
-		wdc = new WorkDirCheckout(db, db.getWorkDir(), head, db.getIndex(), merge);
+		wdc = new WorkDirCheckout(db, db.getWorkTree(), head, db.getIndex(), merge);
 		wdc.prescanTwoTrees();
 	}
 
 	public void checkout() throws IOException {
-		wdc = new WorkDirCheckout(db, db.getWorkDir(), theHead, db.getIndex(), theMerge);
+		wdc = new WorkDirCheckout(db, db.getWorkTree(), theHead, db.getIndex(), theMerge);
 		wdc.checkout();
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java
index 42e653b..1cd1261 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java
@@ -44,7 +44,8 @@
 
 package org.eclipse.jgit.merge;
 
-import java.io.ByteArrayInputStream;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
 
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
@@ -53,7 +54,7 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.RepositoryTestCase;
 import org.eclipse.jgit.treewalk.TreeWalk;
@@ -66,10 +67,10 @@ public void testPick() throws Exception {
 		// Cherry-pick "T" onto "O". This shouldn't introduce "p-fail", which
 		// was created by "P", nor should it modify "a", which was done by "P".
 		//
-		final DirCache treeB = DirCache.read(db);
-		final DirCache treeO = DirCache.read(db);
-		final DirCache treeP = DirCache.read(db);
-		final DirCache treeT = DirCache.read(db);
+		final DirCache treeB = db.readDirCache();
+		final DirCache treeO = db.readDirCache();
+		final DirCache treeP = db.readDirCache();
+		final DirCache treeT = db.readDirCache();
 		{
 			final DirCacheBuilder b = treeB.builder();
 			final DirCacheBuilder o = treeO.builder();
@@ -93,7 +94,7 @@ public void testPick() throws Exception {
 			t.finish();
 		}
 
-		final ObjectWriter ow = new ObjectWriter(db);
+		final ObjectInserter ow = db.newObjectInserter();
 		final ObjectId B = commit(ow, treeB, new ObjectId[] {});
 		final ObjectId O = commit(ow, treeO, new ObjectId[] { B });
 		final ObjectId P = commit(ow, treeP, new ObjectId[] { B });
@@ -128,15 +129,17 @@ private void assertCorrectId(final DirCache treeT, final TreeWalk tw) {
 				.getObjectId(0));
 	}
 
-	private ObjectId commit(final ObjectWriter ow, final DirCache treeB,
+	private ObjectId commit(final ObjectInserter odi, final DirCache treeB,
 			final ObjectId[] parentIds) throws Exception {
 		final Commit c = new Commit(db);
-		c.setTreeId(treeB.writeTree(ow));
+		c.setTreeId(treeB.writeTree(odi));
 		c.setAuthor(new PersonIdent("A U Thor", "a.u.thor", 1L, 0));
 		c.setCommitter(c.getAuthor());
 		c.setParentIds(parentIds);
 		c.setMessage("Tree " + c.getTreeId().name());
-		return ow.writeCommit(c);
+		ObjectId id = odi.insert(OBJ_COMMIT, odi.format(c));
+		odi.flush();
+		return id;
 	}
 
 	private DirCacheEntry makeEntry(final String path, final FileMode mode)
@@ -148,9 +151,8 @@ private DirCacheEntry makeEntry(final String path, final FileMode mode,
 			final String content) throws Exception {
 		final DirCacheEntry ent = new DirCacheEntry(path);
 		ent.setFileMode(mode);
-		final byte[] contentBytes = Constants.encode(content);
-		ent.setObjectId(new ObjectWriter(db).computeBlobSha1(
-				contentBytes.length, new ByteArrayInputStream(contentBytes)));
+		ent.setObjectId(new ObjectInserter.Formatter().idFor(OBJ_BLOB,
+				Constants.encode(content)));
 		return ent;
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java
index 690b166..8657c52 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java
@@ -44,7 +44,9 @@
 
 package org.eclipse.jgit.merge;
 
-import java.io.ByteArrayInputStream;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+
 import java.io.IOException;
 
 import org.eclipse.jgit.dircache.DirCache;
@@ -54,7 +56,7 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.SampleDataRepositoryTestCase;
 import org.eclipse.jgit.treewalk.TreeWalk;
@@ -103,9 +105,9 @@ public void testTrivialTwoWay_conflict() throws IOException {
 	}
 
 	public void testTrivialTwoWay_validSubtreeSort() throws Exception {
-		final DirCache treeB = DirCache.read(db);
-		final DirCache treeO = DirCache.read(db);
-		final DirCache treeT = DirCache.read(db);
+		final DirCache treeB = db.readDirCache();
+		final DirCache treeO = db.readDirCache();
+		final DirCache treeT = db.readDirCache();
 		{
 			final DirCacheBuilder b = treeB.builder();
 			final DirCacheBuilder o = treeO.builder();
@@ -126,7 +128,7 @@ public void testTrivialTwoWay_validSubtreeSort() throws Exception {
 			t.finish();
 		}
 
-		final ObjectWriter ow = new ObjectWriter(db);
+		final ObjectInserter ow = db.newObjectInserter();
 		final ObjectId b = commit(ow, treeB, new ObjectId[] {});
 		final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
 		final ObjectId t = commit(ow, treeT, new ObjectId[] { b });
@@ -155,9 +157,9 @@ public void testTrivialTwoWay_validSubtreeSort() throws Exception {
 	}
 
 	public void testTrivialTwoWay_concurrentSubtreeChange() throws Exception {
-		final DirCache treeB = DirCache.read(db);
-		final DirCache treeO = DirCache.read(db);
-		final DirCache treeT = DirCache.read(db);
+		final DirCache treeB = db.readDirCache();
+		final DirCache treeO = db.readDirCache();
+		final DirCache treeT = db.readDirCache();
 		{
 			final DirCacheBuilder b = treeB.builder();
 			final DirCacheBuilder o = treeO.builder();
@@ -177,7 +179,7 @@ public void testTrivialTwoWay_concurrentSubtreeChange() throws Exception {
 			t.finish();
 		}
 
-		final ObjectWriter ow = new ObjectWriter(db);
+		final ObjectInserter ow = db.newObjectInserter();
 		final ObjectId b = commit(ow, treeB, new ObjectId[] {});
 		final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
 		final ObjectId t = commit(ow, treeT, new ObjectId[] { b });
@@ -202,9 +204,9 @@ public void testTrivialTwoWay_concurrentSubtreeChange() throws Exception {
 	}
 
 	public void testTrivialTwoWay_conflictSubtreeChange() throws Exception {
-		final DirCache treeB = DirCache.read(db);
-		final DirCache treeO = DirCache.read(db);
-		final DirCache treeT = DirCache.read(db);
+		final DirCache treeB = db.readDirCache();
+		final DirCache treeO = db.readDirCache();
+		final DirCache treeT = db.readDirCache();
 		{
 			final DirCacheBuilder b = treeB.builder();
 			final DirCacheBuilder o = treeO.builder();
@@ -224,7 +226,7 @@ public void testTrivialTwoWay_conflictSubtreeChange() throws Exception {
 			t.finish();
 		}
 
-		final ObjectWriter ow = new ObjectWriter(db);
+		final ObjectInserter ow = db.newObjectInserter();
 		final ObjectId b = commit(ow, treeB, new ObjectId[] {});
 		final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
 		final ObjectId t = commit(ow, treeT, new ObjectId[] { b });
@@ -235,9 +237,9 @@ public void testTrivialTwoWay_conflictSubtreeChange() throws Exception {
 	}
 
 	public void testTrivialTwoWay_leftDFconflict1() throws Exception {
-		final DirCache treeB = DirCache.read(db);
-		final DirCache treeO = DirCache.read(db);
-		final DirCache treeT = DirCache.read(db);
+		final DirCache treeB = db.readDirCache();
+		final DirCache treeO = db.readDirCache();
+		final DirCache treeT = db.readDirCache();
 		{
 			final DirCacheBuilder b = treeB.builder();
 			final DirCacheBuilder o = treeO.builder();
@@ -256,7 +258,7 @@ public void testTrivialTwoWay_leftDFconflict1() throws Exception {
 			t.finish();
 		}
 
-		final ObjectWriter ow = new ObjectWriter(db);
+		final ObjectInserter ow = db.newObjectInserter();
 		final ObjectId b = commit(ow, treeB, new ObjectId[] {});
 		final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
 		final ObjectId t = commit(ow, treeT, new ObjectId[] { b });
@@ -267,9 +269,9 @@ public void testTrivialTwoWay_leftDFconflict1() throws Exception {
 	}
 
 	public void testTrivialTwoWay_rightDFconflict1() throws Exception {
-		final DirCache treeB = DirCache.read(db);
-		final DirCache treeO = DirCache.read(db);
-		final DirCache treeT = DirCache.read(db);
+		final DirCache treeB = db.readDirCache();
+		final DirCache treeO = db.readDirCache();
+		final DirCache treeT = db.readDirCache();
 		{
 			final DirCacheBuilder b = treeB.builder();
 			final DirCacheBuilder o = treeO.builder();
@@ -288,7 +290,7 @@ public void testTrivialTwoWay_rightDFconflict1() throws Exception {
 			t.finish();
 		}
 
-		final ObjectWriter ow = new ObjectWriter(db);
+		final ObjectInserter ow = db.newObjectInserter();
 		final ObjectId b = commit(ow, treeB, new ObjectId[] {});
 		final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
 		final ObjectId t = commit(ow, treeT, new ObjectId[] { b });
@@ -299,9 +301,9 @@ public void testTrivialTwoWay_rightDFconflict1() throws Exception {
 	}
 
 	public void testTrivialTwoWay_leftDFconflict2() throws Exception {
-		final DirCache treeB = DirCache.read(db);
-		final DirCache treeO = DirCache.read(db);
-		final DirCache treeT = DirCache.read(db);
+		final DirCache treeB = db.readDirCache();
+		final DirCache treeO = db.readDirCache();
+		final DirCache treeT = db.readDirCache();
 		{
 			final DirCacheBuilder b = treeB.builder();
 			final DirCacheBuilder o = treeO.builder();
@@ -318,7 +320,7 @@ public void testTrivialTwoWay_leftDFconflict2() throws Exception {
 			t.finish();
 		}
 
-		final ObjectWriter ow = new ObjectWriter(db);
+		final ObjectInserter ow = db.newObjectInserter();
 		final ObjectId b = commit(ow, treeB, new ObjectId[] {});
 		final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
 		final ObjectId t = commit(ow, treeT, new ObjectId[] { b });
@@ -329,9 +331,9 @@ public void testTrivialTwoWay_leftDFconflict2() throws Exception {
 	}
 
 	public void testTrivialTwoWay_rightDFconflict2() throws Exception {
-		final DirCache treeB = DirCache.read(db);
-		final DirCache treeO = DirCache.read(db);
-		final DirCache treeT = DirCache.read(db);
+		final DirCache treeB = db.readDirCache();
+		final DirCache treeO = db.readDirCache();
+		final DirCache treeT = db.readDirCache();
 		{
 			final DirCacheBuilder b = treeB.builder();
 			final DirCacheBuilder o = treeO.builder();
@@ -348,7 +350,7 @@ public void testTrivialTwoWay_rightDFconflict2() throws Exception {
 			t.finish();
 		}
 
-		final ObjectWriter ow = new ObjectWriter(db);
+		final ObjectInserter ow = db.newObjectInserter();
 		final ObjectId b = commit(ow, treeB, new ObjectId[] {});
 		final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
 		final ObjectId t = commit(ow, treeT, new ObjectId[] { b });
@@ -363,15 +365,17 @@ private void assertCorrectId(final DirCache treeT, final TreeWalk tw) {
 				.getObjectId(0));
 	}
 
-	private ObjectId commit(final ObjectWriter ow, final DirCache treeB,
+	private ObjectId commit(final ObjectInserter odi, final DirCache treeB,
 			final ObjectId[] parentIds) throws Exception {
 		final Commit c = new Commit(db);
-		c.setTreeId(treeB.writeTree(ow));
+		c.setTreeId(treeB.writeTree(odi));
 		c.setAuthor(new PersonIdent("A U Thor", "a.u.thor", 1L, 0));
 		c.setCommitter(c.getAuthor());
 		c.setParentIds(parentIds);
 		c.setMessage("Tree " + c.getTreeId().name());
-		return ow.writeCommit(c);
+		ObjectId id = odi.insert(OBJ_COMMIT, odi.format(c));
+		odi.flush();
+		return id;
 	}
 
 	private DirCacheEntry makeEntry(final String path, final FileMode mode)
@@ -383,9 +387,8 @@ private DirCacheEntry makeEntry(final String path, final FileMode mode,
 			final String content) throws Exception {
 		final DirCacheEntry ent = new DirCacheEntry(path);
 		ent.setFileMode(mode);
-		final byte[] contentBytes = Constants.encode(content);
-		ent.setObjectId(new ObjectWriter(db).computeBlobSha1(
-				contentBytes.length, new ByteArrayInputStream(contentBytes)));
+		ent.setObjectId(new ObjectInserter.Formatter().idFor(OBJ_BLOB,
+				Constants.encode(content)));
 		return ent;
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java
index 17e9977..813a701 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java
@@ -79,16 +79,16 @@ public void testParseGitFileName_Foo() {
 		final FileHeader fh = header(name);
 		assertEquals(gitLine(name).length(), fh.parseGitFileName(0,
 				fh.buf.length));
-		assertEquals(name, fh.getOldName());
-		assertSame(fh.getOldName(), fh.getNewName());
+		assertEquals(name, fh.getOldPath());
+		assertSame(fh.getOldPath(), fh.getNewPath());
 		assertFalse(fh.hasMetaDataChanges());
 	}
 
 	public void testParseGitFileName_FailFooBar() {
 		final FileHeader fh = data("a/foo b/bar\n-");
 		assertTrue(fh.parseGitFileName(0, fh.buf.length) > 0);
-		assertNull(fh.getOldName());
-		assertNull(fh.getNewName());
+		assertNull(fh.getOldPath());
+		assertNull(fh.getNewPath());
 		assertFalse(fh.hasMetaDataChanges());
 	}
 
@@ -97,8 +97,8 @@ public void testParseGitFileName_FooSpBar() {
 		final FileHeader fh = header(name);
 		assertEquals(gitLine(name).length(), fh.parseGitFileName(0,
 				fh.buf.length));
-		assertEquals(name, fh.getOldName());
-		assertSame(fh.getOldName(), fh.getNewName());
+		assertEquals(name, fh.getOldPath());
+		assertSame(fh.getOldPath(), fh.getNewPath());
 		assertFalse(fh.hasMetaDataChanges());
 	}
 
@@ -108,8 +108,8 @@ public void testParseGitFileName_DqFooTabBar() {
 		final FileHeader fh = dqHeader(dqName);
 		assertEquals(dqGitLine(dqName).length(), fh.parseGitFileName(0,
 				fh.buf.length));
-		assertEquals(name, fh.getOldName());
-		assertSame(fh.getOldName(), fh.getNewName());
+		assertEquals(name, fh.getOldPath());
+		assertSame(fh.getOldPath(), fh.getNewPath());
 		assertFalse(fh.hasMetaDataChanges());
 	}
 
@@ -119,8 +119,8 @@ public void testParseGitFileName_DqFooSpLfNulBar() {
 		final FileHeader fh = dqHeader(dqName);
 		assertEquals(dqGitLine(dqName).length(), fh.parseGitFileName(0,
 				fh.buf.length));
-		assertEquals(name, fh.getOldName());
-		assertSame(fh.getOldName(), fh.getNewName());
+		assertEquals(name, fh.getOldPath());
+		assertSame(fh.getOldPath(), fh.getNewPath());
 		assertFalse(fh.hasMetaDataChanges());
 	}
 
@@ -129,8 +129,8 @@ public void testParseGitFileName_SrcFooC() {
 		final FileHeader fh = header(name);
 		assertEquals(gitLine(name).length(), fh.parseGitFileName(0,
 				fh.buf.length));
-		assertEquals(name, fh.getOldName());
-		assertSame(fh.getOldName(), fh.getNewName());
+		assertEquals(name, fh.getOldPath());
+		assertSame(fh.getOldPath(), fh.getNewPath());
 		assertFalse(fh.hasMetaDataChanges());
 	}
 
@@ -139,8 +139,8 @@ public void testParseGitFileName_SrcFooCNonStandardPrefix() {
 		final String header = "project-v-1.0/" + name + " mydev/" + name + "\n";
 		final FileHeader fh = data(header + "-");
 		assertEquals(header.length(), fh.parseGitFileName(0, fh.buf.length));
-		assertEquals(name, fh.getOldName());
-		assertSame(fh.getOldName(), fh.getNewName());
+		assertEquals(name, fh.getOldPath());
+		assertSame(fh.getOldPath(), fh.getNewPath());
 		assertFalse(fh.hasMetaDataChanges());
 	}
 
@@ -153,9 +153,9 @@ public void testParseUnicodeName_NewFile() {
 				+ "@@ -0,0 +1 @@\n" + "+a\n");
 		assertParse(fh);
 
-		assertEquals("/dev/null", fh.getOldName());
-		assertSame(DiffEntry.DEV_NULL, fh.getOldName());
-		assertEquals("\u00c5ngstr\u00f6m", fh.getNewName());
+		assertEquals("/dev/null", fh.getOldPath());
+		assertSame(DiffEntry.DEV_NULL, fh.getOldPath());
+		assertEquals("\u00c5ngstr\u00f6m", fh.getNewPath());
 
 		assertSame(FileHeader.ChangeType.ADD, fh.getChangeType());
 		assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType());
@@ -178,9 +178,9 @@ public void testParseUnicodeName_DeleteFile() {
 				+ "@@ -1 +0,0 @@\n" + "-a\n");
 		assertParse(fh);
 
-		assertEquals("\u00c5ngstr\u00f6m", fh.getOldName());
-		assertEquals("/dev/null", fh.getNewName());
-		assertSame(DiffEntry.DEV_NULL, fh.getNewName());
+		assertEquals("\u00c5ngstr\u00f6m", fh.getOldPath());
+		assertEquals("/dev/null", fh.getNewPath());
+		assertSame(DiffEntry.DEV_NULL, fh.getNewPath());
 
 		assertSame(FileHeader.ChangeType.DELETE, fh.getChangeType());
 		assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType());
@@ -198,8 +198,8 @@ public void testParseModeChange() {
 		final FileHeader fh = data("diff --git a/a b b/a b\n"
 				+ "old mode 100644\n" + "new mode 100755\n");
 		assertParse(fh);
-		assertEquals("a b", fh.getOldName());
-		assertEquals("a b", fh.getNewName());
+		assertEquals("a b", fh.getOldPath());
+		assertEquals("a b", fh.getNewPath());
 
 		assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType());
 		assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType());
@@ -220,14 +220,14 @@ public void testParseRename100_NewStyle() {
 				+ "rename to \" c/\\303\\205ngstr\\303\\266m\"\n");
 		int ptr = fh.parseGitFileName(0, fh.buf.length);
 		assertTrue(ptr > 0);
-		assertNull(fh.getOldName()); // can't parse names on a rename
-		assertNull(fh.getNewName());
+		assertNull(fh.getOldPath()); // can't parse names on a rename
+		assertNull(fh.getNewPath());
 
 		ptr = fh.parseGitHeaders(ptr, fh.buf.length);
 		assertTrue(ptr > 0);
 
-		assertEquals("a", fh.getOldName());
-		assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName());
+		assertEquals("a", fh.getOldPath());
+		assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewPath());
 
 		assertSame(FileHeader.ChangeType.RENAME, fh.getChangeType());
 		assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType());
@@ -249,14 +249,14 @@ public void testParseRename100_OldStyle() {
 				+ "rename new \" c/\\303\\205ngstr\\303\\266m\"\n");
 		int ptr = fh.parseGitFileName(0, fh.buf.length);
 		assertTrue(ptr > 0);
-		assertNull(fh.getOldName()); // can't parse names on a rename
-		assertNull(fh.getNewName());
+		assertNull(fh.getOldPath()); // can't parse names on a rename
+		assertNull(fh.getNewPath());
 
 		ptr = fh.parseGitHeaders(ptr, fh.buf.length);
 		assertTrue(ptr > 0);
 
-		assertEquals("a", fh.getOldName());
-		assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName());
+		assertEquals("a", fh.getOldPath());
+		assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewPath());
 
 		assertSame(FileHeader.ChangeType.RENAME, fh.getChangeType());
 		assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType());
@@ -278,14 +278,14 @@ public void testParseCopy100() {
 				+ "copy to \" c/\\303\\205ngstr\\303\\266m\"\n");
 		int ptr = fh.parseGitFileName(0, fh.buf.length);
 		assertTrue(ptr > 0);
-		assertNull(fh.getOldName()); // can't parse names on a copy
-		assertNull(fh.getNewName());
+		assertNull(fh.getOldPath()); // can't parse names on a copy
+		assertNull(fh.getNewPath());
 
 		ptr = fh.parseGitHeaders(ptr, fh.buf.length);
 		assertTrue(ptr > 0);
 
-		assertEquals("a", fh.getOldName());
-		assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName());
+		assertEquals("a", fh.getOldPath());
+		assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewPath());
 
 		assertSame(FileHeader.ChangeType.COPY, fh.getChangeType());
 		assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType());
@@ -307,8 +307,8 @@ public void testParseFullIndexLine_WithMode() {
 				+ ".." + nid + " 100644\n" + "--- a/a\n" + "+++ b/a\n");
 		assertParse(fh);
 
-		assertEquals("a", fh.getOldName());
-		assertEquals("a", fh.getNewName());
+		assertEquals("a", fh.getOldPath());
+		assertEquals("a", fh.getNewPath());
 
 		assertSame(FileMode.REGULAR_FILE, fh.getOldMode());
 		assertSame(FileMode.REGULAR_FILE, fh.getNewMode());
@@ -331,8 +331,8 @@ public void testParseFullIndexLine_NoMode() {
 				+ ".." + nid + "\n" + "--- a/a\n" + "+++ b/a\n");
 		assertParse(fh);
 
-		assertEquals("a", fh.getOldName());
-		assertEquals("a", fh.getNewName());
+		assertEquals("a", fh.getOldPath());
+		assertEquals("a", fh.getNewPath());
 		assertFalse(fh.hasMetaDataChanges());
 
 		assertNull(fh.getOldMode());
@@ -357,8 +357,8 @@ public void testParseAbbrIndexLine_WithMode() {
 				+ " 100644\n" + "--- a/a\n" + "+++ b/a\n");
 		assertParse(fh);
 
-		assertEquals("a", fh.getOldName());
-		assertEquals("a", fh.getNewName());
+		assertEquals("a", fh.getOldPath());
+		assertEquals("a", fh.getNewPath());
 
 		assertSame(FileMode.REGULAR_FILE, fh.getOldMode());
 		assertSame(FileMode.REGULAR_FILE, fh.getNewMode());
@@ -386,8 +386,8 @@ public void testParseAbbrIndexLine_NoMode() {
 				+ "\n" + "--- a/a\n" + "+++ b/a\n");
 		assertParse(fh);
 
-		assertEquals("a", fh.getOldName());
-		assertEquals("a", fh.getNewName());
+		assertEquals("a", fh.getOldPath());
+		assertEquals("a", fh.getNewPath());
 
 		assertNull(fh.getOldMode());
 		assertNull(fh.getNewMode());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java
index 1d879cb..cef13f5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java
@@ -60,8 +60,8 @@ public void testParse_OneFileCc() throws IOException {
 		final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0);
 
 		assertEquals("org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java",
-				cfh.getNewName());
-		assertEquals(cfh.getNewName(), cfh.getOldName());
+				cfh.getNewPath());
+		assertEquals(cfh.getNewPath(), cfh.getOldPath());
 
 		assertEquals(98, cfh.startOffset);
 
@@ -114,8 +114,8 @@ public void testParse_CcNewFile() throws IOException {
 
 		final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0);
 
-		assertSame(DiffEntry.DEV_NULL, cfh.getOldName());
-		assertEquals("d", cfh.getNewName());
+		assertSame(DiffEntry.DEV_NULL, cfh.getOldPath());
+		assertEquals("d", cfh.getNewPath());
 
 		assertEquals(187, cfh.startOffset);
 
@@ -168,8 +168,8 @@ public void testParse_CcDeleteFile() throws IOException {
 
 		final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0);
 
-		assertEquals("a", cfh.getOldName());
-		assertSame(DiffEntry.DEV_NULL, cfh.getNewName());
+		assertEquals("a", cfh.getOldPath());
+		assertSame(DiffEntry.DEV_NULL, cfh.getNewPath());
 
 		assertEquals(187, cfh.startOffset);
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java
index 62a1071..67b3f5c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java
@@ -56,7 +56,7 @@ public void testError_DisconnectedHunk() throws IOException {
 			final FileHeader fh = p.getFiles().get(0);
 			assertEquals(
 					"org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java",
-					fh.getNewName());
+					fh.getNewPath());
 			assertEquals(1, fh.getHunks().size());
 		}
 
@@ -114,14 +114,14 @@ public void testError_GarbageBetweenFiles() throws IOException {
 			final FileHeader fh = p.getFiles().get(0);
 			assertEquals(
 					"org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java",
-					fh.getNewName());
+					fh.getNewPath());
 			assertEquals(1, fh.getHunks().size());
 		}
 		{
 			final FileHeader fh = p.getFiles().get(1);
 			assertEquals(
 					"org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java",
-					fh.getNewName());
+					fh.getNewPath());
 			assertEquals(1, fh.getHunks().size());
 		}
 
@@ -139,7 +139,7 @@ public void testError_GitBinaryNoForwardHunk() throws IOException {
 		{
 			final FileHeader fh = p.getFiles().get(0);
 			assertEquals("org.spearce.egit.ui/icons/toolbar/fetchd.png", fh
-					.getNewName());
+					.getNewPath());
 			assertSame(FileHeader.PatchType.GIT_BINARY, fh.getPatchType());
 			assertTrue(fh.getHunks().isEmpty());
 			assertNull(fh.getForwardBinaryHunk());
@@ -147,7 +147,7 @@ public void testError_GitBinaryNoForwardHunk() throws IOException {
 		{
 			final FileHeader fh = p.getFiles().get(1);
 			assertEquals("org.spearce.egit.ui/icons/toolbar/fetche.png", fh
-					.getNewName());
+					.getNewPath());
 			assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType());
 			assertTrue(fh.getHunks().isEmpty());
 			assertNull(fh.getForwardBinaryHunk());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java
index 52d6e27..dd76251 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java
@@ -68,11 +68,11 @@ public void testParse_ConfigCaseInsensitive() throws IOException {
 
 		assertEquals(
 				"org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java",
-				fRepositoryConfigTest.getNewName());
+				fRepositoryConfigTest.getNewPath());
 
 		assertEquals(
 				"org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java",
-				fRepositoryConfig.getNewName());
+				fRepositoryConfig.getNewPath());
 
 		assertEquals(572, fRepositoryConfigTest.startOffset);
 		assertEquals(1490, fRepositoryConfig.startOffset);
@@ -168,7 +168,7 @@ public void testParse_NoBinary() throws IOException {
 			assertEquals("0000000", fh.getOldId().name());
 			assertSame(FileMode.MISSING, fh.getOldMode());
 			assertSame(FileMode.REGULAR_FILE, fh.getNewMode());
-			assertTrue(fh.getNewName().startsWith(
+			assertTrue(fh.getNewPath().startsWith(
 					"org.spearce.egit.ui/icons/toolbar/"));
 			assertSame(FileHeader.PatchType.BINARY, fh.getPatchType());
 			assertTrue(fh.getHunks().isEmpty());
@@ -179,7 +179,7 @@ public void testParse_NoBinary() throws IOException {
 		}
 
 		final FileHeader fh = p.getFiles().get(4);
-		assertEquals("org.spearce.egit.ui/plugin.xml", fh.getNewName());
+		assertEquals("org.spearce.egit.ui/plugin.xml", fh.getNewPath());
 		assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType());
 		assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType());
 		assertFalse(fh.hasMetaDataChanges());
@@ -203,7 +203,7 @@ public void testParse_GitBinaryLiteral() throws IOException {
 			assertNotNull(fh.getNewId());
 			assertEquals(ObjectId.zeroId().name(), fh.getOldId().name());
 			assertSame(FileMode.REGULAR_FILE, fh.getNewMode());
-			assertTrue(fh.getNewName().startsWith(
+			assertTrue(fh.getNewPath().startsWith(
 					"org.spearce.egit.ui/icons/toolbar/"));
 			assertSame(FileHeader.PatchType.GIT_BINARY, fh.getPatchType());
 			assertTrue(fh.getHunks().isEmpty());
@@ -224,7 +224,7 @@ public void testParse_GitBinaryLiteral() throws IOException {
 		}
 
 		final FileHeader fh = p.getFiles().get(4);
-		assertEquals("org.spearce.egit.ui/plugin.xml", fh.getNewName());
+		assertEquals("org.spearce.egit.ui/plugin.xml", fh.getNewPath());
 		assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType());
 		assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType());
 		assertFalse(fh.hasMetaDataChanges());
@@ -241,7 +241,7 @@ public void testParse_GitBinaryDelta() throws IOException {
 		assertTrue(p.getErrors().isEmpty());
 
 		final FileHeader fh = p.getFiles().get(0);
-		assertTrue(fh.getNewName().startsWith("zero.bin"));
+		assertTrue(fh.getNewPath().startsWith("zero.bin"));
 		assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType());
 		assertSame(FileHeader.PatchType.GIT_BINARY, fh.getPatchType());
 		assertSame(FileMode.REGULAR_FILE, fh.getNewMode());
@@ -279,7 +279,7 @@ public void testParse_FixNoNewline() throws IOException {
 
 		final FileHeader f = p.getFiles().get(0);
 
-		assertEquals("a", f.getNewName());
+		assertEquals("a", f.getNewPath());
 		assertEquals(252, f.startOffset);
 
 		assertEquals("2e65efe", f.getOldId().name());
@@ -313,7 +313,7 @@ public void testParse_AddNoNewline() throws IOException {
 
 		final FileHeader f = p.getFiles().get(0);
 
-		assertEquals("a", f.getNewName());
+		assertEquals("a", f.getNewPath());
 		assertEquals(256, f.startOffset);
 
 		assertEquals("f2ad6c7", f.getOldId().name());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
index 6405232..9473fe6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
@@ -47,18 +47,19 @@
 
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryTestCase;
 
 /** Support for tests of the {@link RevWalk} class. */
 public abstract class RevWalkTestCase extends RepositoryTestCase {
-	private TestRepository util;
+	private TestRepository<Repository> util;
 
 	protected RevWalk rw;
 
 	@Override
 	public void setUp() throws Exception {
 		super.setUp();
-		util = new TestRepository(db, createRevWalk());
+		util = new TestRepository<Repository>(db, createRevWalk());
 		rw = util.getRevWalk();
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java
similarity index 90%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java
index 69430ed..c8f2aad 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java
@@ -42,7 +42,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
@@ -53,8 +53,17 @@
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryTestCase;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.pack.PackWriter;
 
 public class ConcurrentRepackTest extends RepositoryTestCase {
 	public void setUp() throws Exception {
@@ -125,10 +134,11 @@ public void testObjectMovedWithinPack()
 		// within the pack has been modified.
 		//
 		final RevObject o2 = writeBlob(eden, "o2");
-		final PackWriter pw = new PackWriter(eden, NullProgressMonitor.INSTANCE);
+		final PackWriter pw = new PackWriter(eden);
 		pw.addObject(o2);
 		pw.addObject(o1);
 		write(out1, pw);
+		pw.release();
 
 		// Try the old name, then the new name. The old name should cause the
 		// pack to reload when it opens and the index and pack mismatch.
@@ -148,7 +158,7 @@ public void testObjectMovedToNewPack2()
 		final File[] out1 = pack(eden, o1);
 		assertEquals(o1.name(), parse(o1).name());
 
-		final ObjectLoader load1 = db.openBlob(o1);
+		final ObjectLoader load1 = db.open(o1, Constants.OBJ_BLOB);
 		assertNotNull(load1);
 
 		final RevObject o2 = writeBlob(eden, "o2");
@@ -163,7 +173,7 @@ public void testObjectMovedToNewPack2()
 		// earlier still resolve the object, even though its underlying
 		// pack is gone, but the object still exists.
 		//
-		final ObjectLoader load2 = db.openBlob(o1);
+		final ObjectLoader load2 = db.open(o1, Constants.OBJ_BLOB);
 		assertNotNull(load2);
 		assertNotSame(load1, load2);
 
@@ -189,7 +199,7 @@ private RevObject parse(final AnyObjectId id)
 
 	private File[] pack(final Repository src, final RevObject... list)
 			throws IOException {
-		final PackWriter pw = new PackWriter(src, NullProgressMonitor.INSTANCE);
+		final PackWriter pw = new PackWriter(src);
 		for (final RevObject o : list) {
 			pw.addObject(o);
 		}
@@ -199,17 +209,19 @@ private RevObject parse(final AnyObjectId id)
 		final File idxFile = fullPackFileName(name, ".idx");
 		final File[] files = new File[] { packFile, idxFile };
 		write(files, pw);
+		pw.release();
 		return files;
 	}
 
 	private static void write(final File[] files, final PackWriter pw)
 			throws IOException {
 		final long begin = files[0].getParentFile().lastModified();
+		NullProgressMonitor m = NullProgressMonitor.INSTANCE;
 		OutputStream out;
 
 		out = new BufferedOutputStream(new FileOutputStream(files[0]));
 		try {
-			pw.writePack(out);
+			pw.writePack(m, m, out);
 		} finally {
 			out.close();
 		}
@@ -245,7 +257,7 @@ private static void touch(final long begin, final File dir) {
 	}
 
 	private File fullPackFileName(final ObjectId name, final String suffix) {
-		final File packdir = new File(db.getObjectsDirectory(), "pack");
+		final File packdir = new File(db.getObjectDatabase().getDirectory(), "pack");
 		return new File(packdir, "pack-" + name.name() + suffix);
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackFileTest.java
new file mode 100644
index 0000000..1a40b8e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackFileTest.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.zip.Deflater;
+
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.junit.TestRng;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectStream;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.storage.pack.DeltaEncoder;
+import org.eclipse.jgit.transport.IndexPack;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.TemporaryBuffer;
+
+public class PackFileTest extends LocalDiskRepositoryTestCase {
+	private TestRng rng;
+
+	private FileRepository repo;
+
+	private TestRepository<FileRepository> tr;
+
+	private WindowCursor wc;
+
+	protected void setUp() throws Exception {
+		super.setUp();
+		rng = new TestRng(getName());
+		repo = createBareRepository();
+		tr = new TestRepository<FileRepository>(repo);
+		wc = (WindowCursor) repo.newObjectReader();
+	}
+
+	protected void tearDown() throws Exception {
+		if (wc != null)
+			wc.release();
+		super.tearDown();
+	}
+
+	public void testWhole_SmallObject() throws Exception {
+		final int type = Constants.OBJ_BLOB;
+		byte[] data = rng.nextBytes(300);
+		RevBlob id = tr.blob(data);
+		tr.branch("master").commit().add("A", id).create();
+		tr.packAndPrune();
+		assertTrue("has blob", wc.has(id));
+
+		ObjectLoader ol = wc.open(id);
+		assertNotNull("created loader", ol);
+		assertEquals(type, ol.getType());
+		assertEquals(data.length, ol.getSize());
+		assertFalse("is not large", ol.isLarge());
+		assertTrue("same content", Arrays.equals(data, ol.getCachedBytes()));
+
+		ObjectStream in = ol.openStream();
+		assertNotNull("have stream", in);
+		assertEquals(type, in.getType());
+		assertEquals(data.length, in.getSize());
+		byte[] data2 = new byte[data.length];
+		IO.readFully(in, data2, 0, data.length);
+		assertTrue("same content", Arrays.equals(data2, data));
+		assertEquals("stream at EOF", -1, in.read());
+		in.close();
+	}
+
+	public void testWhole_LargeObject() throws Exception {
+		final int type = Constants.OBJ_BLOB;
+		byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5);
+		RevBlob id = tr.blob(data);
+		tr.branch("master").commit().add("A", id).create();
+		tr.packAndPrune();
+		assertTrue("has blob", wc.has(id));
+
+		ObjectLoader ol = wc.open(id);
+		assertNotNull("created loader", ol);
+		assertEquals(type, ol.getType());
+		assertEquals(data.length, ol.getSize());
+		assertTrue("is large", ol.isLarge());
+		try {
+			ol.getCachedBytes();
+			fail("Should have thrown LargeObjectException");
+		} catch (LargeObjectException tooBig) {
+			assertEquals(id.name(), tooBig.getMessage());
+		}
+
+		ObjectStream in = ol.openStream();
+		assertNotNull("have stream", in);
+		assertEquals(type, in.getType());
+		assertEquals(data.length, in.getSize());
+		byte[] data2 = new byte[data.length];
+		IO.readFully(in, data2, 0, data.length);
+		assertTrue("same content", Arrays.equals(data2, data));
+		assertEquals("stream at EOF", -1, in.read());
+		in.close();
+	}
+
+	public void testDelta_SmallObjectChain() throws Exception {
+		ObjectInserter.Formatter fmt = new ObjectInserter.Formatter();
+		byte[] data0 = new byte[512];
+		Arrays.fill(data0, (byte) 0xf3);
+		ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0);
+
+		TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024);
+		packHeader(pack, 4);
+		objectHeader(pack, Constants.OBJ_BLOB, data0.length);
+		deflate(pack, data0);
+
+		byte[] data1 = clone(0x01, data0);
+		byte[] delta1 = delta(data0, data1);
+		ObjectId id1 = fmt.idFor(Constants.OBJ_BLOB, data1);
+		objectHeader(pack, Constants.OBJ_REF_DELTA, delta1.length);
+		id0.copyRawTo(pack);
+		deflate(pack, delta1);
+
+		byte[] data2 = clone(0x02, data1);
+		byte[] delta2 = delta(data1, data2);
+		ObjectId id2 = fmt.idFor(Constants.OBJ_BLOB, data2);
+		objectHeader(pack, Constants.OBJ_REF_DELTA, delta2.length);
+		id1.copyRawTo(pack);
+		deflate(pack, delta2);
+
+		byte[] data3 = clone(0x03, data2);
+		byte[] delta3 = delta(data2, data3);
+		ObjectId id3 = fmt.idFor(Constants.OBJ_BLOB, data3);
+		objectHeader(pack, Constants.OBJ_REF_DELTA, delta3.length);
+		id2.copyRawTo(pack);
+		deflate(pack, delta3);
+
+		digest(pack);
+		final byte[] raw = pack.toByteArray();
+		IndexPack ip = IndexPack.create(repo, new ByteArrayInputStream(raw));
+		ip.setFixThin(true);
+		ip.index(NullProgressMonitor.INSTANCE);
+		ip.renameAndOpenPack();
+
+		assertTrue("has blob", wc.has(id3));
+
+		ObjectLoader ol = wc.open(id3);
+		assertNotNull("created loader", ol);
+		assertEquals(Constants.OBJ_BLOB, ol.getType());
+		assertEquals(data3.length, ol.getSize());
+		assertFalse("is large", ol.isLarge());
+		assertNotNull(ol.getCachedBytes());
+		assertTrue(Arrays.equals(data3, ol.getCachedBytes()));
+
+		ObjectStream in = ol.openStream();
+		assertNotNull("have stream", in);
+		assertEquals(Constants.OBJ_BLOB, in.getType());
+		assertEquals(data3.length, in.getSize());
+		byte[] act = new byte[data3.length];
+		IO.readFully(in, act, 0, data3.length);
+		assertTrue("same content", Arrays.equals(act, data3));
+		assertEquals("stream at EOF", -1, in.read());
+		in.close();
+	}
+
+	public void testDelta_LargeObjectChain() throws Exception {
+		ObjectInserter.Formatter fmt = new ObjectInserter.Formatter();
+		byte[] data0 = new byte[ObjectLoader.STREAM_THRESHOLD + 5];
+		Arrays.fill(data0, (byte) 0xf3);
+		ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0);
+
+		TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024);
+		packHeader(pack, 4);
+		objectHeader(pack, Constants.OBJ_BLOB, data0.length);
+		deflate(pack, data0);
+
+		byte[] data1 = clone(0x01, data0);
+		byte[] delta1 = delta(data0, data1);
+		ObjectId id1 = fmt.idFor(Constants.OBJ_BLOB, data1);
+		objectHeader(pack, Constants.OBJ_REF_DELTA, delta1.length);
+		id0.copyRawTo(pack);
+		deflate(pack, delta1);
+
+		byte[] data2 = clone(0x02, data1);
+		byte[] delta2 = delta(data1, data2);
+		ObjectId id2 = fmt.idFor(Constants.OBJ_BLOB, data2);
+		objectHeader(pack, Constants.OBJ_REF_DELTA, delta2.length);
+		id1.copyRawTo(pack);
+		deflate(pack, delta2);
+
+		byte[] data3 = clone(0x03, data2);
+		byte[] delta3 = delta(data2, data3);
+		ObjectId id3 = fmt.idFor(Constants.OBJ_BLOB, data3);
+		objectHeader(pack, Constants.OBJ_REF_DELTA, delta3.length);
+		id2.copyRawTo(pack);
+		deflate(pack, delta3);
+
+		digest(pack);
+		final byte[] raw = pack.toByteArray();
+		IndexPack ip = IndexPack.create(repo, new ByteArrayInputStream(raw));
+		ip.setFixThin(true);
+		ip.index(NullProgressMonitor.INSTANCE);
+		ip.renameAndOpenPack();
+
+		assertTrue("has blob", wc.has(id3));
+
+		ObjectLoader ol = wc.open(id3);
+		assertNotNull("created loader", ol);
+		assertEquals(Constants.OBJ_BLOB, ol.getType());
+		assertEquals(data3.length, ol.getSize());
+		assertTrue("is large", ol.isLarge());
+		try {
+			ol.getCachedBytes();
+			fail("Should have thrown LargeObjectException");
+		} catch (LargeObjectException tooBig) {
+			assertEquals(id3.name(), tooBig.getMessage());
+		}
+
+		ObjectStream in = ol.openStream();
+		assertNotNull("have stream", in);
+		assertEquals(Constants.OBJ_BLOB, in.getType());
+		assertEquals(data3.length, in.getSize());
+		byte[] act = new byte[data3.length];
+		IO.readFully(in, act, 0, data3.length);
+		assertTrue("same content", Arrays.equals(act, data3));
+		assertEquals("stream at EOF", -1, in.read());
+		in.close();
+	}
+
+	public void testDelta_LargeInstructionStream() throws Exception {
+		ObjectInserter.Formatter fmt = new ObjectInserter.Formatter();
+		byte[] data0 = new byte[32];
+		Arrays.fill(data0, (byte) 0xf3);
+		ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0);
+
+		byte[] data3 = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5);
+		ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+		DeltaEncoder de = new DeltaEncoder(tmp, data0.length, data3.length);
+		de.insert(data3, 0, data3.length);
+		byte[] delta3 = tmp.toByteArray();
+		assertTrue(delta3.length > ObjectLoader.STREAM_THRESHOLD);
+
+		TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024);
+		packHeader(pack, 2);
+		objectHeader(pack, Constants.OBJ_BLOB, data0.length);
+		deflate(pack, data0);
+
+		ObjectId id3 = fmt.idFor(Constants.OBJ_BLOB, data3);
+		objectHeader(pack, Constants.OBJ_REF_DELTA, delta3.length);
+		id0.copyRawTo(pack);
+		deflate(pack, delta3);
+
+		digest(pack);
+		final byte[] raw = pack.toByteArray();
+		IndexPack ip = IndexPack.create(repo, new ByteArrayInputStream(raw));
+		ip.setFixThin(true);
+		ip.index(NullProgressMonitor.INSTANCE);
+		ip.renameAndOpenPack();
+
+		assertTrue("has blob", wc.has(id3));
+
+		ObjectLoader ol = wc.open(id3);
+		assertNotNull("created loader", ol);
+		assertEquals(Constants.OBJ_BLOB, ol.getType());
+		assertEquals(data3.length, ol.getSize());
+		assertTrue("is large", ol.isLarge());
+		try {
+			ol.getCachedBytes();
+			fail("Should have thrown LargeObjectException");
+		} catch (LargeObjectException tooBig) {
+			assertEquals(id3.name(), tooBig.getMessage());
+		}
+
+		ObjectStream in = ol.openStream();
+		assertNotNull("have stream", in);
+		assertEquals(Constants.OBJ_BLOB, in.getType());
+		assertEquals(data3.length, in.getSize());
+		byte[] act = new byte[data3.length];
+		IO.readFully(in, act, 0, data3.length);
+		assertTrue("same content", Arrays.equals(act, data3));
+		assertEquals("stream at EOF", -1, in.read());
+		in.close();
+	}
+
+	private byte[] clone(int first, byte[] base) {
+		byte[] r = new byte[base.length];
+		System.arraycopy(base, 1, r, 1, r.length - 1);
+		r[0] = (byte) first;
+		return r;
+	}
+
+	private byte[] delta(byte[] base, byte[] dest) throws IOException {
+		ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+		DeltaEncoder de = new DeltaEncoder(tmp, base.length, dest.length);
+		de.insert(dest, 0, 1);
+		de.copy(1, base.length - 1);
+		return tmp.toByteArray();
+	}
+
+	private void packHeader(TemporaryBuffer.Heap pack, int cnt)
+			throws IOException {
+		final byte[] hdr = new byte[8];
+		NB.encodeInt32(hdr, 0, 2);
+		NB.encodeInt32(hdr, 4, cnt);
+		pack.write(Constants.PACK_SIGNATURE);
+		pack.write(hdr, 0, 8);
+	}
+
+	private void objectHeader(TemporaryBuffer.Heap pack, int type, int sz)
+			throws IOException {
+		byte[] buf = new byte[8];
+		int nextLength = sz >>> 4;
+		buf[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (sz & 0x0F));
+		sz = nextLength;
+		int n = 1;
+		while (sz > 0) {
+			nextLength >>>= 7;
+			buf[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (sz & 0x7F));
+			sz = nextLength;
+		}
+		pack.write(buf, 0, n);
+	}
+
+	private void deflate(TemporaryBuffer.Heap pack, final byte[] content)
+			throws IOException {
+		final Deflater deflater = new Deflater();
+		final byte[] buf = new byte[128];
+		deflater.setInput(content, 0, content.length);
+		deflater.finish();
+		do {
+			final int n = deflater.deflate(buf, 0, buf.length);
+			if (n > 0)
+				pack.write(buf, 0, n);
+		} while (!deflater.finished());
+		deflater.end();
+	}
+
+	private void digest(TemporaryBuffer.Heap buf) throws IOException {
+		MessageDigest md = Constants.newMessageDigest();
+		md.update(buf.toByteArray());
+		buf.write(md.digest());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexTestCase.java
similarity index 97%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexTestCase.java
index d1c5c5e..9884142 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexTestCase.java
@@ -41,14 +41,15 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.PackIndex.MutableEntry;
+import org.eclipse.jgit.lib.RepositoryTestCase;
+import org.eclipse.jgit.storage.file.PackIndex.MutableEntry;
 
 public abstract class PackIndexTestCase extends RepositoryTestCase {
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV1Test.java
similarity index 97%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV1Test.java
index f3082fb..303eeff 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV1Test.java
@@ -43,11 +43,12 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.util.JGitTestUtil;
 
 public class PackIndexV1Test extends PackIndexTestCase {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV2Test.java
similarity index 97%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV2Test.java
index c5669f9..2d3ec7b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV2Test.java
@@ -43,11 +43,12 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.util.JGitTestUtil;
 
 public class PackIndexV2Test extends PackIndexTestCase {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackReverseIndexTest.java
similarity index 96%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackReverseIndexTest.java
index 19b7058..07a40a4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackReverseIndexTest.java
@@ -42,10 +42,11 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.lib.PackIndex.MutableEntry;
+import org.eclipse.jgit.lib.RepositoryTestCase;
+import org.eclipse.jgit.storage.file.PackIndex.MutableEntry;
 import org.eclipse.jgit.util.JGitTestUtil;
 
 public class PackReverseIndexTest extends RepositoryTestCase {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java
similarity index 90%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java
index 76b663a..5685cca 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java
@@ -41,7 +41,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -59,9 +59,15 @@
 import java.util.List;
 
 import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.PackIndex.MutableEntry;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.SampleDataRepositoryTestCase;
+import org.eclipse.jgit.lib.TextProgressMonitor;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.PackIndex.MutableEntry;
+import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.storage.pack.PackWriter;
 import org.eclipse.jgit.transport.IndexPack;
 import org.eclipse.jgit.util.JGitTestUtil;
 
@@ -73,6 +79,8 @@ public class PackWriterTest extends SampleDataRepositoryTestCase {
 	private static final List<RevObject> EMPTY_LIST_REVS = Collections
 			.<RevObject> emptyList();
 
+	private PackConfig config;
+
 	private PackWriter writer;
 
 	private ByteArrayOutputStream os;
@@ -91,16 +99,23 @@ public void setUp() throws Exception {
 		packBase = new File(trash, "tmp_pack");
 		packFile = new File(trash, "tmp_pack.pack");
 		indexFile = new File(trash, "tmp_pack.idx");
-		writer = new PackWriter(db, new TextProgressMonitor());
+		config = new PackConfig(db);
+	}
+
+	public void tearDown() throws Exception {
+		if (writer != null)
+			writer.release();
+		super.tearDown();
 	}
 
 	/**
 	 * Test constructor for exceptions, default settings, initialization.
 	 */
 	public void testContructor() {
+		writer = new PackWriter(config, db.newObjectReader());
 		assertEquals(false, writer.isDeltaBaseAsOffset());
-		assertEquals(true, writer.isReuseDeltas());
-		assertEquals(true, writer.isReuseObjects());
+		assertEquals(true, config.isReuseDeltas());
+		assertEquals(true, config.isReuseObjects());
 		assertEquals(0, writer.getObjectsNumber());
 	}
 
@@ -108,13 +123,17 @@ public void testContructor() {
 	 * Change default settings and verify them.
 	 */
 	public void testModifySettings() {
-		writer.setDeltaBaseAsOffset(true);
-		writer.setReuseDeltas(false);
-		writer.setReuseObjects(false);
+		config.setReuseDeltas(false);
+		config.setReuseObjects(false);
+		config.setDeltaBaseAsOffset(false);
+		assertEquals(false, config.isReuseDeltas());
+		assertEquals(false, config.isReuseObjects());
+		assertEquals(false, config.isDeltaBaseAsOffset());
 
+		writer = new PackWriter(config, db.newObjectReader());
+		writer.setDeltaBaseAsOffset(true);
 		assertEquals(true, writer.isDeltaBaseAsOffset());
-		assertEquals(false, writer.isReuseDeltas());
-		assertEquals(false, writer.isReuseObjects());
+		assertEquals(false, config.isDeltaBaseAsOffset());
 	}
 
 	/**
@@ -183,7 +202,7 @@ public void testIgnoreNonExistingObjects() throws IOException {
 	 * @throws IOException
 	 */
 	public void testWritePack1() throws IOException {
-		writer.setReuseDeltas(false);
+		config.setReuseDeltas(false);
 		writeVerifyPack1();
 	}
 
@@ -194,8 +213,8 @@ public void testWritePack1() throws IOException {
 	 * @throws IOException
 	 */
 	public void testWritePack1NoObjectReuse() throws IOException {
-		writer.setReuseDeltas(false);
-		writer.setReuseObjects(false);
+		config.setReuseDeltas(false);
+		config.setReuseObjects(false);
 		writeVerifyPack1();
 	}
 
@@ -226,7 +245,7 @@ public void testWritePack2DeltasReuseRefs() throws IOException {
 	 * @throws IOException
 	 */
 	public void testWritePack2DeltasReuseOffsets() throws IOException {
-		writer.setDeltaBaseAsOffset(true);
+		config.setDeltaBaseAsOffset(true);
 		writeVerifyPack2(true);
 	}
 
@@ -238,7 +257,7 @@ public void testWritePack2DeltasReuseOffsets() throws IOException {
 	 * @throws IOException
 	 */
 	public void testWritePack2DeltasCRC32Copy() throws IOException {
-		final File packDir = new File(db.getObjectsDirectory(), "pack");
+		final File packDir = new File(db.getObjectDatabase().getDirectory(), "pack");
 		final File crc32Pack = new File(packDir,
 				"pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack");
 		final File crc32Idx = new File(packDir,
@@ -260,7 +279,7 @@ public void testWritePack2DeltasCRC32Copy() throws IOException {
 	 *
 	 */
 	public void testWritePack3() throws MissingObjectException, IOException {
-		writer.setReuseDeltas(false);
+		config.setReuseDeltas(false);
 		final ObjectId forcedOrder[] = new ObjectId[] {
 				ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
 				ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"),
@@ -358,7 +377,7 @@ public void testWritePack4SizeThinVsNoThin() throws Exception {
 	}
 
 	public void testWriteIndex() throws Exception {
-		writer.setIndexVersion(2);
+		config.setIndexVersion(2);
 		writeVerifyPack4(false);
 
 		// Validate that IndexPack came up with the right CRC32 value.
@@ -414,7 +433,7 @@ private void writeVerifyPack1() throws IOException {
 	}
 
 	private void writeVerifyPack2(boolean deltaReuse) throws IOException {
-		writer.setReuseDeltas(deltaReuse);
+		config.setReuseDeltas(deltaReuse);
 		final LinkedList<ObjectId> interestings = new LinkedList<ObjectId>();
 		interestings.add(ObjectId
 				.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
@@ -476,17 +495,23 @@ private void createVerifyOpenPack(final Collection<ObjectId> interestings,
 			final Collection<ObjectId> uninterestings, final boolean thin,
 			final boolean ignoreMissingUninteresting)
 			throws MissingObjectException, IOException {
+		NullProgressMonitor m = NullProgressMonitor.INSTANCE;
+		writer = new PackWriter(config, db.newObjectReader());
 		writer.setThin(thin);
 		writer.setIgnoreMissingUninteresting(ignoreMissingUninteresting);
-		writer.preparePack(interestings, uninterestings);
-		writer.writePack(os);
+		writer.preparePack(m, interestings, uninterestings);
+		writer.writePack(m, m, os);
+		writer.release();
 		verifyOpenPack(thin);
 	}
 
 	private void createVerifyOpenPack(final Iterator<RevObject> objectSource)
 			throws MissingObjectException, IOException {
+		NullProgressMonitor m = NullProgressMonitor.INSTANCE;
+		writer = new PackWriter(config, db.newObjectReader());
 		writer.preparePack(objectSource);
-		writer.writePack(os);
+		writer.writePack(m, m, os);
+		writer.release();
 		verifyOpenPack(false);
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java
similarity index 99%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java
index a281290..6e98541 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java
@@ -41,7 +41,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import static org.eclipse.jgit.lib.Constants.HEAD;
 import static org.eclipse.jgit.lib.Constants.R_HEADS;
@@ -55,6 +55,10 @@
 
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTag;
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefUpdateTest.java
similarity index 95%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefUpdateTest.java
index 8a9bb52..8717008 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefUpdateTest.java
@@ -43,7 +43,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.io.IOException;
@@ -51,9 +51,22 @@
 import java.util.Map;
 import java.util.Map.Entry;
 
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SampleDataRepositoryTestCase;
 import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.storage.file.LockFile;
+import org.eclipse.jgit.storage.file.RefDirectory;
+import org.eclipse.jgit.storage.file.RefDirectoryUpdate;
+import org.eclipse.jgit.storage.file.ReflogReader;
 
 public class RefUpdateTest extends SampleDataRepositoryTestCase {
 
@@ -103,14 +116,14 @@ public void testNoCacheObjectIdSubclass() throws IOException {
 		assertNotSame(newid, r.getObjectId());
 		assertSame(ObjectId.class, r.getObjectId().getClass());
 		assertEquals(newid.copy(), r.getObjectId());
-		List<org.eclipse.jgit.lib.ReflogReader.Entry> reverseEntries1 = db.getReflogReader("refs/heads/abc").getReverseEntries();
-		org.eclipse.jgit.lib.ReflogReader.Entry entry1 = reverseEntries1.get(0);
+		List<org.eclipse.jgit.storage.file.ReflogReader.Entry> reverseEntries1 = db.getReflogReader("refs/heads/abc").getReverseEntries();
+		org.eclipse.jgit.storage.file.ReflogReader.Entry entry1 = reverseEntries1.get(0);
 		assertEquals(1, reverseEntries1.size());
 		assertEquals(ObjectId.zeroId(), entry1.getOldId());
 		assertEquals(r.getObjectId(), entry1.getNewId());
 		assertEquals(new PersonIdent(db).toString(),  entry1.getWho().toString());
 		assertEquals("", entry1.getComment());
-		List<org.eclipse.jgit.lib.ReflogReader.Entry> reverseEntries2 = db.getReflogReader("HEAD").getReverseEntries();
+		List<org.eclipse.jgit.storage.file.ReflogReader.Entry> reverseEntries2 = db.getReflogReader("HEAD").getReverseEntries();
 		assertEquals(0, reverseEntries2.size());
 	}
 
@@ -326,7 +339,7 @@ public void testUpdateRefDetached() throws Exception {
 		// the branch HEAD referred to is left untouched
 		assertEquals(pid, db.resolve("refs/heads/master"));
 		ReflogReader reflogReader = new  ReflogReader(db, "HEAD");
-		org.eclipse.jgit.lib.ReflogReader.Entry e = reflogReader.getReverseEntries().get(0);
+		org.eclipse.jgit.storage.file.ReflogReader.Entry e = reflogReader.getReverseEntries().get(0);
 		assertEquals(pid, e.getOldId());
 		assertEquals(ppid, e.getNewId());
 		assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
@@ -355,7 +368,7 @@ public void testUpdateRefDetachedUnbornHead() throws Exception {
 		// the branch HEAD referred to is left untouched
 		assertNull(db.resolve("refs/heads/unborn"));
 		ReflogReader reflogReader = new  ReflogReader(db, "HEAD");
-		org.eclipse.jgit.lib.ReflogReader.Entry e = reflogReader.getReverseEntries().get(0);
+		org.eclipse.jgit.storage.file.ReflogReader.Entry e = reflogReader.getReverseEntries().get(0);
 		assertEquals(ObjectId.zeroId(), e.getOldId());
 		assertEquals(ppid, e.getNewId());
 		assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
@@ -542,13 +555,15 @@ public void testUpdateRefLockFailureLocked() throws IOException {
 		ObjectId pid = db.resolve("refs/heads/master^");
 		RefUpdate updateRef = db.updateRef("refs/heads/master");
 		updateRef.setNewObjectId(pid);
-		LockFile lockFile1 = new LockFile(new File(db.getDirectory(),"refs/heads/master"));
+		LockFile lockFile1 = new LockFile(new File(db.getDirectory(),
+				"refs/heads/master"), db.getFS());
 		try {
 			assertTrue(lockFile1.lock()); // precondition to test
 			Result update = updateRef.update();
 			assertEquals(Result.LOCK_FAILURE, update);
 			assertEquals(opid, db.resolve("refs/heads/master"));
-			LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master"));
+			LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master"),
+					db.getFS());
 			assertFalse(lockFile2.lock()); // was locked, still is
 		} finally {
 			lockFile1.unlock();
@@ -664,7 +679,7 @@ public void testRenameBranchAlsoInPack() throws IOException {
 
 		// Create new Repository instance, to reread caches and make sure our
 		// assumptions are persistent.
-		Repository ndb = new Repository(db.getDirectory());
+		Repository ndb = new FileRepository(db.getDirectory());
 		assertEquals(rb2, ndb.resolve("refs/heads/new/name"));
 		assertNull(ndb.resolve("refs/heads/b"));
 	}
@@ -677,16 +692,17 @@ public void tryRenameWhenLocked(String toLock, String fromName,
 		ObjectId oldHeadId = db.resolve(Constants.HEAD);
 		writeReflog(db, oldfromId, oldfromId, "Just a message",
 				fromName);
-		List<org.eclipse.jgit.lib.ReflogReader.Entry> oldFromLog = db
+		List<org.eclipse.jgit.storage.file.ReflogReader.Entry> oldFromLog = db
 				.getReflogReader(fromName).getReverseEntries();
-		List<org.eclipse.jgit.lib.ReflogReader.Entry> oldHeadLog = oldHeadId != null ? db
+		List<org.eclipse.jgit.storage.file.ReflogReader.Entry> oldHeadLog = oldHeadId != null ? db
 				.getReflogReader(Constants.HEAD).getReverseEntries() : null;
 
 		assertTrue("internal check, we have a log", new File(db.getDirectory(),
 				"logs/" + fromName).exists());
 
 		// "someone" has branch X locked
-		LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock));
+		LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock),
+				db.getFS());
 		try {
 			assertTrue(lockFile.lock());
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ReflogReaderTest.java
similarity index 97%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ReflogReaderTest.java
index 6144851..1d268a4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ReflogReaderTest.java
@@ -42,7 +42,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -51,7 +51,10 @@
 import java.text.SimpleDateFormat;
 import java.util.List;
 
-import org.eclipse.jgit.lib.ReflogReader.Entry;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.SampleDataRepositoryTestCase;
+import org.eclipse.jgit.storage.file.ReflogReader.Entry;
 
 public class ReflogReaderTest extends SampleDataRepositoryTestCase {
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java
similarity index 73%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java
index 6e5e005..d28dd39 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java
@@ -42,12 +42,18 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.io.IOException;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.NoWorkTreeException;
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
 
 /**
  * Tests for setting up the working directory when creating a Repository
@@ -57,12 +63,12 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase {
 	public void testIsBare_CreateRepositoryFromArbitraryGitDir()
 			throws Exception {
 		File gitDir = getFile("workdir");
-		assertTrue(new Repository(gitDir).isBare());
+		assertTrue(new FileRepository(gitDir).isBare());
 	}
 
 	public void testNotBare_CreateRepositoryFromDotGitGitDir() throws Exception {
 		File gitDir = getFile("workdir", Constants.DOT_GIT);
-		Repository repo = new Repository(gitDir);
+		Repository repo = new FileRepository(gitDir);
 		assertFalse(repo.isBare());
 		assertWorkdirPath(repo, "workdir");
 		assertGitdirPath(repo, "workdir", Constants.DOT_GIT);
@@ -71,14 +77,14 @@ public void testNotBare_CreateRepositoryFromDotGitGitDir() throws Exception {
 	public void testWorkdirIsParentDir_CreateRepositoryFromDotGitGitDir()
 			throws Exception {
 		File gitDir = getFile("workdir", Constants.DOT_GIT);
-		Repository repo = new Repository(gitDir);
-		String workdir = repo.getWorkDir().getName();
+		Repository repo = new FileRepository(gitDir);
+		String workdir = repo.getWorkTree().getName();
 		assertEquals(workdir, "workdir");
 	}
 
 	public void testNotBare_CreateRepositoryFromWorkDirOnly() throws Exception {
 		File workdir = getFile("workdir", "repo");
-		Repository repo = new Repository(null, workdir);
+		FileRepository repo = new FileRepositoryBuilder().setWorkTree(workdir).build();
 		assertFalse(repo.isBare());
 		assertWorkdirPath(repo, "workdir", "repo");
 		assertGitdirPath(repo, "workdir", "repo", Constants.DOT_GIT);
@@ -87,7 +93,7 @@ public void testNotBare_CreateRepositoryFromWorkDirOnly() throws Exception {
 	public void testWorkdirIsDotGit_CreateRepositoryFromWorkDirOnly()
 			throws Exception {
 		File workdir = getFile("workdir", "repo");
-		Repository repo = new Repository(null, workdir);
+		FileRepository repo = new FileRepositoryBuilder().setWorkTree(workdir).build();
 		assertGitdirPath(repo, "workdir", "repo", Constants.DOT_GIT);
 	}
 
@@ -96,7 +102,7 @@ public void testNotBare_CreateRepositoryFromGitDirOnlyWithWorktreeConfig()
 		File gitDir = getFile("workdir", "repoWithConfig");
 		File workTree = getFile("workdir", "treeRoot");
 		setWorkTree(gitDir, workTree);
-		Repository repo = new Repository(gitDir, null);
+		FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build();
 		assertFalse(repo.isBare());
 		assertWorkdirPath(repo, "workdir", "treeRoot");
 		assertGitdirPath(repo, "workdir", "repoWithConfig");
@@ -106,7 +112,7 @@ public void testBare_CreateRepositoryFromGitDirOnlyWithBareConfigTrue()
 			throws Exception {
 		File gitDir = getFile("workdir", "repoWithConfig");
 		setBare(gitDir, true);
-		Repository repo = new Repository(gitDir, null);
+		FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build();
 		assertTrue(repo.isBare());
 	}
 
@@ -114,7 +120,7 @@ public void testWorkdirIsParent_CreateRepositoryFromGitDirOnlyWithBareConfigFals
 			throws Exception {
 		File gitDir = getFile("workdir", "repoWithBareConfigTrue", "child");
 		setBare(gitDir, false);
-		Repository repo = new Repository(gitDir, null);
+		FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build();
 		assertWorkdirPath(repo, "workdir", "repoWithBareConfigTrue");
 	}
 
@@ -122,27 +128,18 @@ public void testNotBare_CreateRepositoryFromGitDirOnlyWithBareConfigFalse()
 			throws Exception {
 		File gitDir = getFile("workdir", "repoWithBareConfigFalse", "child");
 		setBare(gitDir, false);
-		Repository repo = new Repository(gitDir, null);
+		FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build();
 		assertFalse(repo.isBare());
 		assertWorkdirPath(repo, "workdir", "repoWithBareConfigFalse");
 		assertGitdirPath(repo, "workdir", "repoWithBareConfigFalse", "child");
 	}
 
-	public void testNotBare_MakeBareUnbareBySetWorkdir() throws Exception {
-		File gitDir = getFile("gitDir");
-		Repository repo = new Repository(gitDir);
-		repo.setWorkDir(getFile("workingDir"));
-		assertFalse(repo.isBare());
-		assertWorkdirPath(repo, "workingDir");
-		assertGitdirPath(repo, "gitDir");
-	}
-
 	public void testExceptionThrown_BareRepoGetWorkDir() throws Exception {
 		File gitDir = getFile("workdir");
 		try {
-			new Repository(gitDir).getWorkDir();
-			fail("Expected IllegalStateException missing");
-		} catch (IllegalStateException e) {
+			new FileRepository(gitDir).getWorkTree();
+			fail("Expected NoWorkTreeException missing");
+		} catch (NoWorkTreeException e) {
 			// expected
 		}
 	}
@@ -150,9 +147,9 @@ public void testExceptionThrown_BareRepoGetWorkDir() throws Exception {
 	public void testExceptionThrown_BareRepoGetIndex() throws Exception {
 		File gitDir = getFile("workdir");
 		try {
-			new Repository(gitDir).getIndex();
-			fail("Expected IllegalStateException missing");
-		} catch (IllegalStateException e) {
+			new FileRepository(gitDir).getIndex();
+			fail("Expected NoWorkTreeException missing");
+		} catch (NoWorkTreeException e) {
 			// expected
 		}
 	}
@@ -160,9 +157,9 @@ public void testExceptionThrown_BareRepoGetIndex() throws Exception {
 	public void testExceptionThrown_BareRepoGetIndexFile() throws Exception {
 		File gitDir = getFile("workdir");
 		try {
-			new Repository(gitDir).getIndexFile();
-			fail("Expected Exception missing");
-		} catch (IllegalStateException e) {
+			new FileRepository(gitDir).getIndexFile();
+			fail("Expected NoWorkTreeException missing");
+		} catch (NoWorkTreeException e) {
 			// expected
 		}
 	}
@@ -176,20 +173,29 @@ private File getFile(String... pathComponents) {
 		return result;
 	}
 
-	private void setBare(File gitDir, boolean bare) throws IOException {
-		Repository repo = new Repository(gitDir, null);
-		repo.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+	private void setBare(File gitDir, boolean bare) throws IOException,
+			ConfigInvalidException {
+		FileBasedConfig cfg = configFor(gitDir);
+		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
 				ConfigConstants.CONFIG_KEY_BARE, bare);
-		repo.getConfig().save();
+		cfg.save();
 	}
 
-	private void setWorkTree(File gitDir, File workTree) throws IOException {
-		Repository repo = new Repository(gitDir, null);
-		repo.getConfig()
-				.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
-						ConfigConstants.CONFIG_KEY_WORKTREE,
-						workTree.getAbsolutePath());
-		repo.getConfig().save();
+	private void setWorkTree(File gitDir, File workTree) throws IOException,
+			ConfigInvalidException {
+		String path = workTree.getAbsolutePath();
+		FileBasedConfig cfg = configFor(gitDir);
+		cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_WORKTREE, path);
+		cfg.save();
+	}
+
+	private FileBasedConfig configFor(File gitDir) throws IOException,
+			ConfigInvalidException {
+		File configPath = new File(gitDir, "config");
+		FileBasedConfig cfg = new FileBasedConfig(configPath, FS.DETECTED);
+		cfg.load();
+		return cfg;
 	}
 
 	private void assertGitdirPath(Repository repo, String... expected)
@@ -202,7 +208,7 @@ private void assertGitdirPath(Repository repo, String... expected)
 	private void assertWorkdirPath(Repository repo, String... expected)
 			throws IOException {
 		File exp = getFile(expected).getCanonicalFile();
-		File act = repo.getWorkDir().getCanonicalFile();
+		File act = repo.getWorkTree().getCanonicalFile();
 		assertEquals("Wrong working Directory", exp, act);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java
similarity index 89%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java
index ce8a79e..ecabedd 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java
@@ -43,7 +43,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -53,7 +53,23 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 
+import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Commit;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileTreeEntry;
+import org.eclipse.jgit.lib.ObjectDatabase;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SampleDataRepositoryTestCase;
+import org.eclipse.jgit.lib.Tag;
+import org.eclipse.jgit.lib.Tree;
+import org.eclipse.jgit.lib.TreeEntry;
+import org.eclipse.jgit.lib.WriteTree;
 
 public class T0003_Basic extends SampleDataRepositoryTestCase {
 	public void test001_Initalize() {
@@ -80,11 +96,11 @@ public void test001_Initalize() {
 
 	public void test000_openRepoBadArgs() throws IOException {
 		try {
-			new Repository(null, null);
+			new FileRepositoryBuilder().build();
 			fail("Must pass either GIT_DIR or GIT_WORK_TREE");
 		} catch (IllegalArgumentException e) {
 			assertEquals(
-					"Either GIT_DIR or GIT_WORK_TREE must be passed to Repository constructor",
+					JGitText.get().eitherGitDirOrWorkTreeRequired,
 					e.getMessage());
 		}
 	}
@@ -97,16 +113,16 @@ public void test000_openRepoBadArgs() throws IOException {
 	 */
 	public void test000_openrepo_default_gitDirSet() throws IOException {
 		File repo1Parent = new File(trash.getParentFile(), "r1");
-		Repository repo1initial = new Repository(new File(repo1Parent, Constants.DOT_GIT));
+		Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT));
 		repo1initial.create();
 		repo1initial.close();
 
 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
-		Repository r = new Repository(theDir, null);
+		FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build();
 		assertEqualsPath(theDir, r.getDirectory());
-		assertEqualsPath(repo1Parent, r.getWorkDir());
+		assertEqualsPath(repo1Parent, r.getWorkTree());
 		assertEqualsPath(new File(theDir, "index"), r.getIndexFile());
-		assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory());
+		assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory());
 	}
 
 	/**
@@ -117,16 +133,17 @@ public void test000_openrepo_default_gitDirSet() throws IOException {
 	 */
 	public void test000_openrepo_default_gitDirAndWorkTreeSet() throws IOException {
 		File repo1Parent = new File(trash.getParentFile(), "r1");
-		Repository repo1initial = new Repository(new File(repo1Parent, Constants.DOT_GIT));
+		Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT));
 		repo1initial.create();
 		repo1initial.close();
 
 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
-		Repository r = new Repository(theDir, repo1Parent.getParentFile());
+		FileRepository r = new FileRepositoryBuilder().setGitDir(theDir)
+				.setWorkTree(repo1Parent.getParentFile()).build();
 		assertEqualsPath(theDir, r.getDirectory());
-		assertEqualsPath(repo1Parent.getParentFile(), r.getWorkDir());
+		assertEqualsPath(repo1Parent.getParentFile(), r.getWorkTree());
 		assertEqualsPath(new File(theDir, "index"), r.getIndexFile());
-		assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory());
+		assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory());
 	}
 
 	/**
@@ -137,16 +154,16 @@ public void test000_openrepo_default_gitDirAndWorkTreeSet() throws IOException {
 	 */
 	public void test000_openrepo_default_workDirSet() throws IOException {
 		File repo1Parent = new File(trash.getParentFile(), "r1");
-		Repository repo1initial = new Repository(new File(repo1Parent, Constants.DOT_GIT));
+		Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT));
 		repo1initial.create();
 		repo1initial.close();
 
 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
-		Repository r = new Repository(null, repo1Parent);
+		FileRepository r = new FileRepositoryBuilder().setWorkTree(repo1Parent).build();
 		assertEqualsPath(theDir, r.getDirectory());
-		assertEqualsPath(repo1Parent, r.getWorkDir());
+		assertEqualsPath(repo1Parent, r.getWorkTree());
 		assertEqualsPath(new File(theDir, "index"), r.getIndexFile());
-		assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory());
+		assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory());
 	}
 
 	/**
@@ -159,7 +176,7 @@ public void test000_openrepo_default_absolute_workdirconfig()
 		File repo1Parent = new File(trash.getParentFile(), "r1");
 		File workdir = new File(trash.getParentFile(), "rw");
 		workdir.mkdir();
-		Repository repo1initial = new Repository(new File(repo1Parent, Constants.DOT_GIT));
+		FileRepository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT));
 		repo1initial.create();
 		repo1initial.getConfig().setString("core", null, "worktree",
 				workdir.getAbsolutePath());
@@ -167,11 +184,11 @@ public void test000_openrepo_default_absolute_workdirconfig()
 		repo1initial.close();
 
 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
-		Repository r = new Repository(theDir, null);
+		FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build();
 		assertEqualsPath(theDir, r.getDirectory());
-		assertEqualsPath(workdir, r.getWorkDir());
+		assertEqualsPath(workdir, r.getWorkTree());
 		assertEqualsPath(new File(theDir, "index"), r.getIndexFile());
-		assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory());
+		assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory());
 	}
 
 	/**
@@ -184,7 +201,7 @@ public void test000_openrepo_default_relative_workdirconfig()
 		File repo1Parent = new File(trash.getParentFile(), "r1");
 		File workdir = new File(trash.getParentFile(), "rw");
 		workdir.mkdir();
-		Repository repo1initial = new Repository(new File(repo1Parent, Constants.DOT_GIT));
+		FileRepository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT));
 		repo1initial.create();
 		repo1initial.getConfig()
 				.setString("core", null, "worktree", "../../rw");
@@ -192,11 +209,11 @@ public void test000_openrepo_default_relative_workdirconfig()
 		repo1initial.close();
 
 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
-		Repository r = new Repository(theDir, null);
+		FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build();
 		assertEqualsPath(theDir, r.getDirectory());
-		assertEqualsPath(workdir, r.getWorkDir());
+		assertEqualsPath(workdir, r.getWorkTree());
 		assertEqualsPath(new File(theDir, "index"), r.getIndexFile());
-		assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory());
+		assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory());
 	}
 
 	/**
@@ -210,18 +227,21 @@ public void test000_openrepo_alternate_index_file_and_objdirs()
 		File repo1Parent = new File(trash.getParentFile(), "r1");
 		File indexFile = new File(trash, "idx");
 		File objDir = new File(trash, "../obj");
-		File[] altObjDirs = new File[] { db.getObjectsDirectory() };
-		Repository repo1initial = new Repository(new File(repo1Parent, Constants.DOT_GIT));
+		File altObjDir = db.getObjectDatabase().getDirectory();
+		Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT));
 		repo1initial.create();
 		repo1initial.close();
 
 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
-		Repository r = new Repository(theDir, null, objDir, altObjDirs,
-				indexFile);
+		FileRepository r = new FileRepositoryBuilder() //
+				.setGitDir(theDir).setObjectDirectory(objDir) //
+				.addAlternateObjectDirectory(altObjDir) //
+				.setIndexFile(indexFile) //
+				.build();
 		assertEqualsPath(theDir, r.getDirectory());
-		assertEqualsPath(theDir.getParentFile(), r.getWorkDir());
+		assertEqualsPath(theDir.getParentFile(), r.getWorkTree());
 		assertEqualsPath(indexFile, r.getIndexFile());
-		assertEqualsPath(objDir, r.getObjectsDirectory());
+		assertEqualsPath(objDir, r.getObjectDatabase().getDirectory());
 		assertNotNull(r.mapCommit("6db9c2ebf75590eef973081736730a9ea169a0c4"));
 		// Must close or the default repo pack files created by this test gets
 		// locked via the alternate object directories on Windows.
@@ -283,7 +303,7 @@ public void test003_WriteShouldBeEmptyTree() throws IOException {
 	}
 
 	public void test005_ReadSimpleConfig() {
-		final RepositoryConfig c = db.getConfig();
+		final Config c = db.getConfig();
 		assertNotNull(c);
 		assertEquals("0", c.getString("core", null, "repositoryformatversion"));
 		assertEquals("0", c.getString("CoRe", null, "REPOSITORYFoRmAtVeRsIoN"));
@@ -294,8 +314,8 @@ public void test005_ReadSimpleConfig() {
 
 	public void test006_ReadUglyConfig() throws IOException,
 			ConfigInvalidException {
-		final RepositoryConfig c = db.getConfig();
 		final File cfg = new File(db.getDirectory(), "config");
+		final FileBasedConfig c = new FileBasedConfig(cfg, db.getFS());
 		final FileWriter pw = new FileWriter(cfg);
 		final String configStr = "  [core];comment\n\tfilemode = yes\n"
 				+ "[user]\n"
@@ -321,9 +341,9 @@ public void test006_ReadUglyConfig() throws IOException,
 	}
 
 	public void test007_Open() throws IOException {
-		final Repository db2 = new Repository(db.getDirectory());
+		final FileRepository db2 = new FileRepository(db.getDirectory());
 		assertEquals(db.getDirectory(), db2.getDirectory());
-		assertEquals(db.getObjectsDirectory(), db2.getObjectsDirectory());
+		assertEquals(db.getObjectDatabase().getDirectory(), db2.getObjectDatabase().getDirectory());
 		assertNotSame(db.getConfig(), db2.getConfig());
 	}
 
@@ -337,7 +357,7 @@ public void test008_FailOnWrongVersion() throws IOException {
 		pw.close();
 
 		try {
-			new Repository(db.getDirectory());
+			new FileRepository(db.getDirectory());
 			fail("incorrectly opened a bad repository");
 		} catch (IOException ioe) {
 			assertTrue(ioe.getMessage().indexOf("format") > 0);
@@ -345,11 +365,7 @@ public void test008_FailOnWrongVersion() throws IOException {
 		}
 	}
 
-	public void test009_CreateCommitOldFormat() throws IOException,
-			ConfigInvalidException {
-		writeTrashFile(".git/config", "[core]\n" + "legacyHeaders=1\n");
-		db.getConfig().load();
-
+	public void test009_CreateCommitOldFormat() throws IOException {
 		final Tree t = new Tree(db);
 		final FileTreeEntry f = t.addFile("i-am-a-file");
 		writeTrashFile(f.getName(), "and this is the data in me\n");
@@ -369,8 +385,10 @@ public void test009_CreateCommitOldFormat() throws IOException,
 		assertEquals(cmtid, c.getCommitId());
 
 		// Verify the commit we just wrote is in the correct format.
-		final XInputStream xis = new XInputStream(new FileInputStream(db
-				.toFile(cmtid)));
+		ObjectDatabase odb = db.getObjectDatabase();
+		assertTrue("is ObjectDirectory", odb instanceof ObjectDirectory);
+		final XInputStream xis = new XInputStream(new FileInputStream(
+				((ObjectDirectory) odb).fileFor(cmtid)));
 		try {
 			assertEquals(0x78, xis.readUInt8());
 			assertEquals(0x9c, xis.readUInt8());
@@ -724,10 +742,10 @@ public void test30_stripWorkDir() {
 		assertEquals("", Repository.stripWorkDir(relBase, relNonFile));
 		assertEquals("", Repository.stripWorkDir(absBase, absNonFile));
 
-		assertEquals("", Repository.stripWorkDir(db.getWorkDir(), db.getWorkDir()));
+		assertEquals("", Repository.stripWorkDir(db.getWorkTree(), db.getWorkTree()));
 
-		File file = new File(new File(db.getWorkDir(), "subdir"), "File.java");
-		assertEquals("subdir/File.java", Repository.stripWorkDir(db.getWorkDir(), file));
+		File file = new File(new File(db.getWorkTree(), "subdir"), "File.java");
+		assertEquals("subdir/File.java", Repository.stripWorkDir(db.getWorkTree(), file));
 
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java
similarity index 91%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java
index 336bba2..472d695 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java
@@ -44,11 +44,15 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.io.IOException;
 
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.SampleDataRepositoryTestCase;
 import org.eclipse.jgit.util.JGitTestUtil;
 
 public class T0004_PackReader extends SampleDataRepositoryTestCase {
@@ -59,15 +63,14 @@ public class T0004_PackReader extends SampleDataRepositoryTestCase {
 	public void test003_lookupCompressedObject() throws IOException {
 		final PackFile pr;
 		final ObjectId id;
-		final PackedObjectLoader or;
+		final ObjectLoader or;
 
 		id = ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327");
 		pr = new PackFile(TEST_IDX, TEST_PACK);
-		or = pr.get(new WindowCursor(), id);
+		or = pr.get(new WindowCursor(null), id);
 		assertNotNull(or);
 		assertEquals(Constants.OBJ_TREE, or.getType());
 		assertEquals(35, or.getSize());
-		assertEquals(7736, or.getObjectOffset());
 		pr.close();
 	}
 
@@ -76,11 +79,9 @@ public void test004_lookupDeltifiedObject() throws IOException {
 		final ObjectLoader or;
 
 		id = ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259");
-		or = db.openObject(id);
+		or = db.open(id);
 		assertNotNull(or);
-		assertTrue(or instanceof PackedObjectLoader);
 		assertEquals(Constants.OBJ_BLOB, or.getType());
 		assertEquals(18009, or.getSize());
-		assertEquals(516, ((PackedObjectLoader) or).getObjectOffset());
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/UnpackedObjectTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/UnpackedObjectTest.java
new file mode 100644
index 0000000..25dfe4c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/UnpackedObjectTest.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.zip.DeflaterOutputStream;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.TestRng;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectStream;
+import org.eclipse.jgit.util.IO;
+
+public class UnpackedObjectTest extends LocalDiskRepositoryTestCase {
+	private TestRng rng;
+
+	private FileRepository repo;
+
+	private WindowCursor wc;
+
+	protected void setUp() throws Exception {
+		super.setUp();
+		rng = new TestRng(getName());
+		repo = createBareRepository();
+		wc = (WindowCursor) repo.newObjectReader();
+	}
+
+	protected void tearDown() throws Exception {
+		if (wc != null)
+			wc.release();
+		super.tearDown();
+	}
+
+	public void testStandardFormat_SmallObject() throws Exception {
+		final int type = Constants.OBJ_BLOB;
+		byte[] data = rng.nextBytes(300);
+		byte[] gz = compressStandardFormat(type, data);
+		ObjectId id = ObjectId.zeroId();
+
+		ObjectLoader ol = UnpackedObject.open(new ByteArrayInputStream(gz),
+				path(id), id, wc);
+		assertNotNull("created loader", ol);
+		assertEquals(type, ol.getType());
+		assertEquals(data.length, ol.getSize());
+		assertFalse("is not large", ol.isLarge());
+		assertTrue("same content", Arrays.equals(data, ol.getCachedBytes()));
+
+		ObjectStream in = ol.openStream();
+		assertNotNull("have stream", in);
+		assertEquals(type, in.getType());
+		assertEquals(data.length, in.getSize());
+		byte[] data2 = new byte[data.length];
+		IO.readFully(in, data2, 0, data.length);
+		assertTrue("same content", Arrays.equals(data2, data));
+		assertEquals("stream at EOF", -1, in.read());
+		in.close();
+	}
+
+	public void testStandardFormat_LargeObject() throws Exception {
+		final int type = Constants.OBJ_BLOB;
+		byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5);
+		ObjectId id = new ObjectInserter.Formatter().idFor(type, data);
+		write(id, compressStandardFormat(type, data));
+
+		ObjectLoader ol;
+		{
+			FileInputStream fs = new FileInputStream(path(id));
+			try {
+				ol = UnpackedObject.open(fs, path(id), id, wc);
+			} finally {
+				fs.close();
+			}
+		}
+
+		assertNotNull("created loader", ol);
+		assertEquals(type, ol.getType());
+		assertEquals(data.length, ol.getSize());
+		assertTrue("is large", ol.isLarge());
+		try {
+			ol.getCachedBytes();
+			fail("Should have thrown LargeObjectException");
+		} catch (LargeObjectException tooBig) {
+			assertEquals(id.name(), tooBig.getMessage());
+		}
+
+		ObjectStream in = ol.openStream();
+		assertNotNull("have stream", in);
+		assertEquals(type, in.getType());
+		assertEquals(data.length, in.getSize());
+		byte[] data2 = new byte[data.length];
+		IO.readFully(in, data2, 0, data.length);
+		assertTrue("same content", Arrays.equals(data2, data));
+		assertEquals("stream at EOF", -1, in.read());
+		in.close();
+	}
+
+	public void testStandardFormat_NegativeSize() throws Exception {
+		ObjectId id = ObjectId.zeroId();
+		byte[] data = rng.nextBytes(300);
+
+		try {
+			byte[] gz = compressStandardFormat("blob", "-1", data);
+			UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectNegativeSize), coe
+					.getMessage());
+		}
+	}
+
+	public void testStandardFormat_InvalidType() throws Exception {
+		ObjectId id = ObjectId.zeroId();
+		byte[] data = rng.nextBytes(300);
+
+		try {
+			byte[] gz = compressStandardFormat("not.a.type", "1", data);
+			UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectInvalidType), coe
+					.getMessage());
+		}
+	}
+
+	public void testStandardFormat_NoHeader() throws Exception {
+		ObjectId id = ObjectId.zeroId();
+		byte[] data = {};
+
+		try {
+			byte[] gz = compressStandardFormat("", "", data);
+			UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectNoHeader), coe
+					.getMessage());
+		}
+	}
+
+	public void testStandardFormat_GarbageAfterSize() throws Exception {
+		ObjectId id = ObjectId.zeroId();
+		byte[] data = rng.nextBytes(300);
+
+		try {
+			byte[] gz = compressStandardFormat("blob", "1foo", data);
+			UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectGarbageAfterSize),
+					coe.getMessage());
+		}
+	}
+
+	public void testStandardFormat_SmallObject_CorruptZLibStream()
+			throws Exception {
+		ObjectId id = ObjectId.zeroId();
+		byte[] data = rng.nextBytes(300);
+
+		try {
+			byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data);
+			for (int i = 5; i < gz.length; i++)
+				gz[i] = 0;
+			UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectBadStream), coe
+					.getMessage());
+		}
+	}
+
+	public void testStandardFormat_SmallObject_TruncatedZLibStream()
+			throws Exception {
+		ObjectId id = ObjectId.zeroId();
+		byte[] data = rng.nextBytes(300);
+
+		try {
+			byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data);
+			byte[] tr = new byte[gz.length - 1];
+			System.arraycopy(gz, 0, tr, 0, tr.length);
+			UnpackedObject.open(new ByteArrayInputStream(tr), path(id), id, wc);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectBadStream), coe
+					.getMessage());
+		}
+	}
+
+	public void testStandardFormat_SmallObject_TrailingGarbage()
+			throws Exception {
+		ObjectId id = ObjectId.zeroId();
+		byte[] data = rng.nextBytes(300);
+
+		try {
+			byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data);
+			byte[] tr = new byte[gz.length + 1];
+			System.arraycopy(gz, 0, tr, 0, gz.length);
+			UnpackedObject.open(new ByteArrayInputStream(tr), path(id), id, wc);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectBadStream), coe
+					.getMessage());
+		}
+	}
+
+	public void testStandardFormat_LargeObject_CorruptZLibStream()
+			throws Exception {
+		final int type = Constants.OBJ_BLOB;
+		byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5);
+		ObjectId id = new ObjectInserter.Formatter().idFor(type, data);
+		byte[] gz = compressStandardFormat(type, data);
+		gz[gz.length - 1] = 0;
+		gz[gz.length - 2] = 0;
+
+		write(id, gz);
+
+		ObjectLoader ol;
+		{
+			FileInputStream fs = new FileInputStream(path(id));
+			try {
+				ol = UnpackedObject.open(fs, path(id), id, wc);
+			} finally {
+				fs.close();
+			}
+		}
+
+		try {
+			byte[] tmp = new byte[data.length];
+			InputStream in = ol.openStream();
+			try {
+				IO.readFully(in, tmp, 0, tmp.length);
+			} finally {
+				in.close();
+			}
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectBadStream), coe
+					.getMessage());
+		}
+	}
+
+	public void testStandardFormat_LargeObject_TruncatedZLibStream()
+			throws Exception {
+		final int type = Constants.OBJ_BLOB;
+		byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5);
+		ObjectId id = new ObjectInserter.Formatter().idFor(type, data);
+		byte[] gz = compressStandardFormat(type, data);
+		byte[] tr = new byte[gz.length - 1];
+		System.arraycopy(gz, 0, tr, 0, tr.length);
+
+		write(id, tr);
+
+		ObjectLoader ol;
+		{
+			FileInputStream fs = new FileInputStream(path(id));
+			try {
+				ol = UnpackedObject.open(fs, path(id), id, wc);
+			} finally {
+				fs.close();
+			}
+		}
+
+		byte[] tmp = new byte[data.length];
+		InputStream in = ol.openStream();
+		IO.readFully(in, tmp, 0, tmp.length);
+		try {
+			in.close();
+			fail("close did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectBadStream), coe
+					.getMessage());
+		}
+	}
+
+	public void testStandardFormat_LargeObject_TrailingGarbage()
+			throws Exception {
+		final int type = Constants.OBJ_BLOB;
+		byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5);
+		ObjectId id = new ObjectInserter.Formatter().idFor(type, data);
+		byte[] gz = compressStandardFormat(type, data);
+		byte[] tr = new byte[gz.length + 1];
+		System.arraycopy(gz, 0, tr, 0, gz.length);
+
+		write(id, tr);
+
+		ObjectLoader ol;
+		{
+			FileInputStream fs = new FileInputStream(path(id));
+			try {
+				ol = UnpackedObject.open(fs, path(id), id, wc);
+			} finally {
+				fs.close();
+			}
+		}
+
+		byte[] tmp = new byte[data.length];
+		InputStream in = ol.openStream();
+		IO.readFully(in, tmp, 0, tmp.length);
+		try {
+			in.close();
+			fail("close did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectBadStream), coe
+					.getMessage());
+		}
+	}
+
+	public void testPackFormat_SmallObject() throws Exception {
+		final int type = Constants.OBJ_BLOB;
+		byte[] data = rng.nextBytes(300);
+		byte[] gz = compressPackFormat(type, data);
+		ObjectId id = ObjectId.zeroId();
+
+		ObjectLoader ol = UnpackedObject.open(new ByteArrayInputStream(gz),
+				path(id), id, wc);
+		assertNotNull("created loader", ol);
+		assertEquals(type, ol.getType());
+		assertEquals(data.length, ol.getSize());
+		assertFalse("is not large", ol.isLarge());
+		assertTrue("same content", Arrays.equals(data, ol.getCachedBytes()));
+
+		ObjectStream in = ol.openStream();
+		assertNotNull("have stream", in);
+		assertEquals(type, in.getType());
+		assertEquals(data.length, in.getSize());
+		byte[] data2 = new byte[data.length];
+		IO.readFully(in, data2, 0, data.length);
+		assertTrue("same content", Arrays.equals(data, ol.getCachedBytes()));
+		in.close();
+	}
+
+	public void testPackFormat_LargeObject() throws Exception {
+		final int type = Constants.OBJ_BLOB;
+		byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5);
+		ObjectId id = new ObjectInserter.Formatter().idFor(type, data);
+		write(id, compressPackFormat(type, data));
+
+		ObjectLoader ol;
+		{
+			FileInputStream fs = new FileInputStream(path(id));
+			try {
+				ol = UnpackedObject.open(fs, path(id), id, wc);
+			} finally {
+				fs.close();
+			}
+		}
+
+		assertNotNull("created loader", ol);
+		assertEquals(type, ol.getType());
+		assertEquals(data.length, ol.getSize());
+		assertTrue("is large", ol.isLarge());
+		try {
+			ol.getCachedBytes();
+			fail("Should have thrown LargeObjectException");
+		} catch (LargeObjectException tooBig) {
+			assertEquals(id.name(), tooBig.getMessage());
+		}
+
+		ObjectStream in = ol.openStream();
+		assertNotNull("have stream", in);
+		assertEquals(type, in.getType());
+		assertEquals(data.length, in.getSize());
+		byte[] data2 = new byte[data.length];
+		IO.readFully(in, data2, 0, data.length);
+		assertTrue("same content", Arrays.equals(data2, data));
+		assertEquals("stream at EOF", -1, in.read());
+		in.close();
+	}
+
+	public void testPackFormat_DeltaNotAllowed() throws Exception {
+		ObjectId id = ObjectId.zeroId();
+		byte[] data = rng.nextBytes(300);
+
+		try {
+			byte[] gz = compressPackFormat(Constants.OBJ_OFS_DELTA, data);
+			UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectInvalidType), coe
+					.getMessage());
+		}
+
+		try {
+			byte[] gz = compressPackFormat(Constants.OBJ_REF_DELTA, data);
+			UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectInvalidType), coe
+					.getMessage());
+		}
+
+		try {
+			byte[] gz = compressPackFormat(Constants.OBJ_TYPE_5, data);
+			UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectInvalidType), coe
+					.getMessage());
+		}
+
+		try {
+			byte[] gz = compressPackFormat(Constants.OBJ_EXT, data);
+			UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException coe) {
+			assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
+					id.name(), JGitText.get().corruptObjectInvalidType), coe
+					.getMessage());
+		}
+	}
+
+	private byte[] compressStandardFormat(int type, byte[] data)
+			throws IOException {
+		String typeString = Constants.typeString(type);
+		String length = String.valueOf(data.length);
+		return compressStandardFormat(typeString, length, data);
+	}
+
+	private byte[] compressStandardFormat(String type, String length,
+			byte[] data) throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		DeflaterOutputStream d = new DeflaterOutputStream(out);
+		d.write(Constants.encodeASCII(type));
+		d.write(' ');
+		d.write(Constants.encodeASCII(length));
+		d.write(0);
+		d.write(data);
+		d.finish();
+		return out.toByteArray();
+	}
+
+	private byte[] compressPackFormat(int type, byte[] data) throws IOException {
+		byte[] hdr = new byte[64];
+		int rawLength = data.length;
+		int nextLength = rawLength >>> 4;
+		hdr[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (rawLength & 0x0F));
+		rawLength = nextLength;
+		int n = 1;
+		while (rawLength > 0) {
+			nextLength >>>= 7;
+			hdr[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (rawLength & 0x7F));
+			rawLength = nextLength;
+		}
+
+		final ByteArrayOutputStream out = new ByteArrayOutputStream();
+		out.write(hdr, 0, n);
+
+		DeflaterOutputStream d = new DeflaterOutputStream(out);
+		d.write(data);
+		d.finish();
+		return out.toByteArray();
+	}
+
+	private File path(ObjectId id) {
+		return repo.getObjectDatabase().fileFor(id);
+	}
+
+	private void write(ObjectId id, byte[] data) throws IOException {
+		File path = path(id);
+		path.getParentFile().mkdirs();
+		FileOutputStream out = new FileOutputStream(path);
+		try {
+			out.write(data);
+		} finally {
+			out.close();
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java
similarity index 92%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java
index 8ff022d..d8c6829 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java
@@ -41,7 +41,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.BufferedReader;
 import java.io.FileInputStream;
@@ -51,6 +51,10 @@
 import java.util.List;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.SampleDataRepositoryTestCase;
 import org.eclipse.jgit.util.JGitTestUtil;
 import org.eclipse.jgit.util.MutableInteger;
 
@@ -73,9 +77,9 @@ public void setUp() throws Exception {
 				final TestObject o = new TestObject();
 				o.id = ObjectId.fromString(parts[0]);
 				o.setType(parts[1]);
-				o.rawSize = Integer.parseInt(parts[2]);
+				// parts[2] is the inflate size
 				// parts[3] is the size-in-pack
-				o.offset = Long.parseLong(parts[4]);
+				// parts[4] is the offset in the pack
 				toLoad.add(o);
 			}
 		} finally {
@@ -122,12 +126,9 @@ private void checkLimits(final WindowCacheConfig cfg) {
 
 	private void doCacheTests() throws IOException {
 		for (final TestObject o : toLoad) {
-			final ObjectLoader or = db.openObject(o.id);
+			final ObjectLoader or = db.open(o.id, o.type);
 			assertNotNull(or);
-			assertTrue(or instanceof PackedObjectLoader);
 			assertEquals(o.type, or.getType());
-			assertEquals(o.rawSize, or.getRawSize());
-			assertEquals(o.offset, ((PackedObjectLoader) or).getObjectOffset());
 		}
 	}
 
@@ -136,10 +137,6 @@ private class TestObject {
 
 		int type;
 
-		int rawSize;
-
-		long offset;
-
 		void setType(final String typeStr) throws CorruptObjectException {
 			final byte[] typeRaw = Constants.encode(typeStr + " ");
 			final MutableInteger ptr = new MutableInteger();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheReconfigureTest.java
similarity index 97%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheReconfigureTest.java
index 9e093c8..e52b19d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheReconfigureTest.java
@@ -41,7 +41,9 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
+
+import org.eclipse.jgit.lib.RepositoryTestCase;
 
 public class WindowCacheReconfigureTest extends RepositoryTestCase {
 	public void testConfigureCache_PackedGitLimit_0() {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/XInputStream.java
similarity index 98%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/XInputStream.java
index eef32b9..9978c8e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/XInputStream.java
@@ -41,7 +41,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.BufferedInputStream;
 import java.io.EOFException;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaIndexTest.java
new file mode 100644
index 0000000..868ef88
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaIndexTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jgit.junit.TestRng;
+import org.eclipse.jgit.lib.Constants;
+
+public class DeltaIndexTest extends TestCase {
+	private TestRng rng;
+
+	private ByteArrayOutputStream actDeltaBuf;
+
+	private ByteArrayOutputStream expDeltaBuf;
+
+	private DeltaEncoder expDeltaEnc;
+
+	private byte[] src;
+
+	private byte[] dst;
+
+	private ByteArrayOutputStream dstBuf;
+
+	protected void setUp() throws Exception {
+		super.setUp();
+		rng = new TestRng(getName());
+		actDeltaBuf = new ByteArrayOutputStream();
+		expDeltaBuf = new ByteArrayOutputStream();
+		expDeltaEnc = new DeltaEncoder(expDeltaBuf, 0, 0);
+		dstBuf = new ByteArrayOutputStream();
+	}
+
+	public void testInsertWholeObject_Length12() throws IOException {
+		src = rng.nextBytes(12);
+		insert(src);
+		doTest();
+	}
+
+	public void testCopyWholeObject_Length128() throws IOException {
+		src = rng.nextBytes(128);
+		copy(0, 128);
+		doTest();
+	}
+
+	public void testCopyWholeObject_Length123() throws IOException {
+		src = rng.nextBytes(123);
+		copy(0, 123);
+		doTest();
+	}
+
+	public void testCopyZeros_Length128() throws IOException {
+		src = new byte[2048];
+		copy(0, src.length);
+		doTest();
+
+		// The index should be smaller than expected due to the chain
+		// being truncated. Without truncation we would expect to have
+		// more than 3584 bytes used.
+		//
+		assertEquals(2636, new DeltaIndex(src).getIndexSize());
+	}
+
+	public void testShuffleSegments() throws IOException {
+		src = rng.nextBytes(128);
+		copy(64, 64);
+		copy(0, 64);
+		doTest();
+	}
+
+	public void testInsertHeadMiddle() throws IOException {
+		src = rng.nextBytes(1024);
+		insert("foo");
+		copy(0, 512);
+		insert("yet more fooery");
+		copy(0, 512);
+		doTest();
+	}
+
+	public void testInsertTail() throws IOException {
+		src = rng.nextBytes(1024);
+		copy(0, 512);
+		insert("bar");
+		doTest();
+	}
+
+	public void testIndexSize() {
+		src = rng.nextBytes(1024);
+		DeltaIndex di = new DeltaIndex(src);
+		assertEquals(1860, di.getIndexSize());
+		assertEquals("DeltaIndex[2 KiB]", di.toString());
+	}
+
+	public void testLimitObjectSize_Length12InsertFails() throws IOException {
+		src = rng.nextBytes(12);
+		dst = src;
+
+		DeltaIndex di = new DeltaIndex(src);
+		assertFalse(di.encode(actDeltaBuf, dst, src.length));
+	}
+
+	public void testLimitObjectSize_Length130InsertFails() throws IOException {
+		src = rng.nextBytes(130);
+		dst = rng.nextBytes(130);
+
+		DeltaIndex di = new DeltaIndex(src);
+		assertFalse(di.encode(actDeltaBuf, dst, src.length));
+	}
+
+	public void testLimitObjectSize_Length130CopyOk() throws IOException {
+		src = rng.nextBytes(130);
+		copy(0, 130);
+		dst = dstBuf.toByteArray();
+
+		DeltaIndex di = new DeltaIndex(src);
+		assertTrue(di.encode(actDeltaBuf, dst, dst.length));
+
+		byte[] actDelta = actDeltaBuf.toByteArray();
+		byte[] expDelta = expDeltaBuf.toByteArray();
+
+		assertEquals(BinaryDelta.format(expDelta, false), //
+				BinaryDelta.format(actDelta, false));
+	}
+
+	public void testLimitObjectSize_Length130CopyFails() throws IOException {
+		src = rng.nextBytes(130);
+		copy(0, 130);
+		dst = dstBuf.toByteArray();
+
+		// The header requires 4 bytes for these objects, so a target length
+		// of 5 is bigger than the copy instruction and should cause an abort.
+		//
+		DeltaIndex di = new DeltaIndex(src);
+		assertFalse(di.encode(actDeltaBuf, dst, 5));
+		assertEquals(4, actDeltaBuf.size());
+	}
+
+	public void testLimitObjectSize_InsertFrontFails() throws IOException {
+		src = rng.nextBytes(130);
+		insert("eight");
+		copy(0, 130);
+		dst = dstBuf.toByteArray();
+
+		// The header requires 4 bytes for these objects, so a target length
+		// of 5 is bigger than the copy instruction and should cause an abort.
+		//
+		DeltaIndex di = new DeltaIndex(src);
+		assertFalse(di.encode(actDeltaBuf, dst, 5));
+		assertEquals(4, actDeltaBuf.size());
+	}
+
+	private void copy(int offset, int len) throws IOException {
+		dstBuf.write(src, offset, len);
+		expDeltaEnc.copy(offset, len);
+	}
+
+	private void insert(String text) throws IOException {
+		insert(Constants.encode(text));
+	}
+
+	private void insert(byte[] text) throws IOException {
+		dstBuf.write(text);
+		expDeltaEnc.insert(text);
+	}
+
+	private void doTest() throws IOException {
+		dst = dstBuf.toByteArray();
+
+		DeltaIndex di = new DeltaIndex(src);
+		di.encode(actDeltaBuf, dst);
+
+		byte[] actDelta = actDeltaBuf.toByteArray();
+		byte[] expDelta = expDeltaBuf.toByteArray();
+
+		assertEquals(BinaryDelta.format(expDelta, false), //
+				BinaryDelta.format(actDelta, false));
+
+		assertTrue("delta is not empty", actDelta.length > 0);
+		assertEquals(src.length, BinaryDelta.getBaseSize(actDelta));
+		assertEquals(dst.length, BinaryDelta.getResultSize(actDelta));
+		assertTrue(Arrays.equals(dst, BinaryDelta.apply(src, actDelta)));
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaStreamTest.java
new file mode 100644
index 0000000..9b34ad5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaStreamTest.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.junit.TestRng;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.util.IO;
+
+public class DeltaStreamTest extends TestCase {
+	private TestRng rng;
+
+	private ByteArrayOutputStream deltaBuf;
+
+	private DeltaEncoder deltaEnc;
+
+	private byte[] base;
+
+	private byte[] data;
+
+	private int dataPtr;
+
+	private byte[] delta;
+
+	protected void setUp() throws Exception {
+		super.setUp();
+		rng = new TestRng(getName());
+		deltaBuf = new ByteArrayOutputStream();
+	}
+
+	public void testCopy_SingleOp() throws IOException {
+		init((1 << 16) + 1, (1 << 8) + 1);
+		copy(0, data.length);
+		assertValidState();
+	}
+
+	public void testCopy_MaxSize() throws IOException {
+		int max = (0xff << 16) + (0xff << 8) + 0xff;
+		init(1 + max, max);
+		copy(1, max);
+		assertValidState();
+	}
+
+	public void testCopy_64k() throws IOException {
+		init(0x10000 + 2, 0x10000 + 1);
+		copy(1, 0x10000);
+		copy(0x10001, 1);
+		assertValidState();
+	}
+
+	public void testCopy_Gap() throws IOException {
+		init(256, 8);
+		copy(4, 4);
+		copy(128, 4);
+		assertValidState();
+	}
+
+	public void testCopy_OutOfOrder() throws IOException {
+		init((1 << 16) + 1, (1 << 16) + 1);
+		copy(1 << 8, 1 << 8);
+		copy(0, data.length - dataPtr);
+		assertValidState();
+	}
+
+	public void testInsert_SingleOp() throws IOException {
+		init((1 << 16) + 1, 2);
+		insert("hi");
+		assertValidState();
+	}
+
+	public void testInsertAndCopy() throws IOException {
+		init(8, 512);
+		insert(new byte[127]);
+		insert(new byte[127]);
+		insert(new byte[127]);
+		insert(new byte[125]);
+		copy(2, 6);
+		assertValidState();
+	}
+
+	public void testSkip() throws IOException {
+		init(32, 15);
+		copy(2, 2);
+		insert("ab");
+		insert("cd");
+		copy(4, 4);
+		copy(0, 2);
+		insert("efg");
+		assertValidState();
+
+		for (int p = 0; p < data.length; p++) {
+			byte[] act = new byte[data.length];
+			System.arraycopy(data, 0, act, 0, p);
+			DeltaStream in = open();
+			IO.skipFully(in, p);
+			assertEquals(data.length - p, in.read(act, p, data.length - p));
+			assertEquals(-1, in.read());
+			assertTrue("skipping " + p, Arrays.equals(data, act));
+		}
+
+		// Skip all the way to the end should still recognize EOF.
+		DeltaStream in = open();
+		IO.skipFully(in, data.length);
+		assertEquals(-1, in.read());
+		assertEquals(0, in.skip(1));
+
+		// Skip should not open the base as we move past it, but it
+		// will open when we need to start copying data from it.
+		final boolean[] opened = new boolean[1];
+		in = new DeltaStream(new ByteArrayInputStream(delta)) {
+			@Override
+			protected long getBaseSize() throws IOException {
+				return base.length;
+			}
+
+			@Override
+			protected InputStream openBase() throws IOException {
+				opened[0] = true;
+				return new ByteArrayInputStream(base);
+			}
+		};
+		IO.skipFully(in, 7);
+		assertFalse("not yet open", opened[0]);
+		assertEquals(data[7], in.read());
+		assertTrue("now open", opened[0]);
+	}
+
+	public void testIncorrectBaseSize() throws IOException {
+		init(4, 4);
+		copy(0, 4);
+		assertValidState();
+
+		DeltaStream in = new DeltaStream(new ByteArrayInputStream(delta)) {
+			@Override
+			protected long getBaseSize() throws IOException {
+				return 128;
+			}
+
+			@Override
+			protected InputStream openBase() throws IOException {
+				return new ByteArrayInputStream(base);
+			}
+		};
+		try {
+			in.read(new byte[4]);
+			fail("did not throw an exception");
+		} catch (CorruptObjectException e) {
+			assertEquals(JGitText.get().baseLengthIncorrect, e.getMessage());
+		}
+
+		in = new DeltaStream(new ByteArrayInputStream(delta)) {
+			@Override
+			protected long getBaseSize() throws IOException {
+				return 4;
+			}
+
+			@Override
+			protected InputStream openBase() throws IOException {
+				return new ByteArrayInputStream(new byte[0]);
+			}
+		};
+		try {
+			in.read(new byte[4]);
+			fail("did not throw an exception");
+		} catch (CorruptObjectException e) {
+			assertEquals(JGitText.get().baseLengthIncorrect, e.getMessage());
+		}
+	}
+
+	private void init(int baseSize, int dataSize) throws IOException {
+		base = rng.nextBytes(baseSize);
+		data = new byte[dataSize];
+		deltaEnc = new DeltaEncoder(deltaBuf, baseSize, dataSize);
+	}
+
+	private void copy(int offset, int len) throws IOException {
+		System.arraycopy(base, offset, data, dataPtr, len);
+		deltaEnc.copy(offset, len);
+		assertEquals(deltaBuf.size(), deltaEnc.getSize());
+		dataPtr += len;
+	}
+
+	private void insert(String text) throws IOException {
+		insert(Constants.encode(text));
+	}
+
+	private void insert(byte[] text) throws IOException {
+		System.arraycopy(text, 0, data, dataPtr, text.length);
+		deltaEnc.insert(text);
+		assertEquals(deltaBuf.size(), deltaEnc.getSize());
+		dataPtr += text.length;
+	}
+
+	private void assertValidState() throws IOException {
+		assertEquals("test filled example result", data.length, dataPtr);
+
+		delta = deltaBuf.toByteArray();
+		assertEquals(base.length, BinaryDelta.getBaseSize(delta));
+		assertEquals(data.length, BinaryDelta.getResultSize(delta));
+		assertTrue(Arrays.equals(data, BinaryDelta.apply(base, delta)));
+
+		byte[] act = new byte[data.length];
+		DeltaStream in = open();
+		assertEquals(data.length, in.getSize());
+		assertEquals(data.length, in.read(act));
+		assertEquals(-1, in.read());
+		assertTrue(Arrays.equals(data, act));
+	}
+
+	private DeltaStream open() throws IOException {
+		return new DeltaStream(new ByteArrayInputStream(delta)) {
+			@Override
+			protected long getBaseSize() throws IOException {
+				return base.length;
+			}
+
+			@Override
+			protected InputStream openBase() throws IOException {
+				return new ByteArrayInputStream(base);
+			}
+		};
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
index 2d6aa28..cc70562 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
@@ -148,12 +148,12 @@ private FetchResult fetchFromBundle(final Repository newRepo,
 			throws FileNotFoundException, IOException {
 		final BundleWriter bw;
 
-		bw = new BundleWriter(db, NullProgressMonitor.INSTANCE);
+		bw = new BundleWriter(db);
 		bw.include(name, ObjectId.fromString(anObjectToInclude));
 		if (assume != null)
 			bw.assume(assume);
 		final ByteArrayOutputStream out = new ByteArrayOutputStream();
-		bw.writeBundle(out);
+		bw.writeBundle(NullProgressMonitor.INSTANCE, out);
 		return out.toByteArray();
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java
index e18f741..110804f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java
@@ -58,10 +58,10 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PackFile;
 import org.eclipse.jgit.lib.RepositoryTestCase;
 import org.eclipse.jgit.lib.TextProgressMonitor;
 import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.storage.file.PackFile;
 import org.eclipse.jgit.util.JGitTestUtil;
 import org.eclipse.jgit.util.NB;
 import org.eclipse.jgit.util.TemporaryBuffer;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java
index 40c719f..b331f9c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java
@@ -56,7 +56,6 @@
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.ObjectDirectory;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.Ref;
@@ -64,6 +63,7 @@
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.storage.file.ObjectDirectory;
 import org.eclipse.jgit.util.NB;
 import org.eclipse.jgit.util.TemporaryBuffer;
 
@@ -176,7 +176,7 @@ public void testSuccess() throws Exception {
 		// Verify the only storage of b is our packed delta above.
 		//
 		ObjectDirectory od = (ObjectDirectory) src.getObjectDatabase();
-		assertTrue("has b", od.hasObject(b));
+		assertTrue("has b", src.hasObject(b));
 		assertFalse("b not loose", od.fileFor(b).exists());
 
 		// Now use b but in a different commit than what is hidden.
@@ -255,7 +255,7 @@ public void testCreateBranchAtHiddenCommitFails() throws Exception {
 	}
 
 	public void testUsingHiddenDeltaBaseFails() throws Exception {
-		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64);
+		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
 		packHeader(pack, 1);
 		pack.write((Constants.OBJ_REF_DELTA) << 4 | 4);
 		b.copyRawTo(pack);
@@ -292,18 +292,18 @@ public void testUsingHiddenDeltaBaseFails() throws Exception {
 	public void testUsingHiddenCommonBlobFails() throws Exception {
 		// Try to use the 'b' blob that is hidden.
 		//
-		TestRepository s = new TestRepository(src);
+		TestRepository<Repository> s = new TestRepository<Repository>(src);
 		RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create();
 
 		// But don't include it in the pack.
 		//
-		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64);
+		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
 		packHeader(pack, 2);
-		copy(pack, src.openObject(N));
-		copy(pack,src.openObject(s.parseBody(N).getTree()));
+		copy(pack, src.open(N));
+		copy(pack,src.open(s.parseBody(N).getTree()));
 		digest(pack);
 
-		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256);
+		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
 				+ "refs/heads/s" + '\0'
@@ -333,19 +333,19 @@ public void testUsingHiddenCommonBlobFails() throws Exception {
 	public void testUsingUnknownBlobFails() throws Exception {
 		// Try to use the 'n' blob that is not on the server.
 		//
-		TestRepository s = new TestRepository(src);
+		TestRepository<Repository> s = new TestRepository<Repository>(src);
 		RevBlob n = s.blob("n");
 		RevCommit N = s.commit().parent(B).add("q", n).create();
 
 		// But don't include it in the pack.
 		//
-		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64);
+		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
 		packHeader(pack, 2);
-		copy(pack, src.openObject(N));
-		copy(pack,src.openObject(s.parseBody(N).getTree()));
+		copy(pack, src.open(N));
+		copy(pack,src.open(s.parseBody(N).getTree()));
 		digest(pack);
 
-		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256);
+		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
 				+ "refs/heads/s" + '\0'
@@ -373,18 +373,18 @@ public void testUsingUnknownBlobFails() throws Exception {
 	}
 
 	public void testUsingUnknownTreeFails() throws Exception {
-		TestRepository s = new TestRepository(src);
+		TestRepository<Repository> s = new TestRepository<Repository>(src);
 		RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create();
 		RevTree t = s.parseBody(N).getTree();
 
 		// Don't include the tree in the pack.
 		//
-		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64);
+		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
 		packHeader(pack, 1);
-		copy(pack, src.openObject(N));
+		copy(pack, src.open(N));
 		digest(pack);
 
-		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256);
+		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
 				+ "refs/heads/s" + '\0'
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
index e351825..a6bdd88 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
@@ -48,7 +48,7 @@
 import java.util.Collection;
 import java.util.Collections;
 
-import org.eclipse.jgit.lib.RepositoryConfig;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.SampleDataRepositoryTestCase;
 
 public class TransportTest extends SampleDataRepositoryTestCase {
@@ -59,7 +59,7 @@ public class TransportTest extends SampleDataRepositoryTestCase {
 	@Override
 	public void setUp() throws Exception {
 		super.setUp();
-		final RepositoryConfig config = db.getConfig();
+		final Config config = db.getConfig();
 		remoteConfig = new RemoteConfig(config, "test");
 		remoteConfig.addURI(new URIish("http://everyones.loves.git/u/2"));
 		transport = null;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java
index e96445a..12c1148 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java
@@ -51,7 +51,7 @@
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.ObjectReader;
 
 
 public class AbstractTreeIteratorTest extends TestCase {
@@ -73,7 +73,7 @@ public FakeTreeIterator(String pathName, FileMode fileMode) {
 		}
 
 		@Override
-		public AbstractTreeIterator createSubtreeIterator(Repository repo)
+		public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
 				throws IncorrectObjectTypeException, IOException {
 			return null;
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java
index 111264b..1ea2dc6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.treewalk;
 
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.RepositoryTestCase;
 
 public class EmptyTreeIteratorTest extends RepositoryTestCase {
@@ -55,7 +56,8 @@ public void testAtEOF() throws Exception {
 
 	public void testCreateSubtreeIterator() throws Exception {
 		final EmptyTreeIterator etp = new EmptyTreeIterator();
-		final AbstractTreeIterator sub = etp.createSubtreeIterator(db);
+		final ObjectReader reader = db.newObjectReader();
+		final AbstractTreeIterator sub = etp.createSubtreeIterator(reader);
 		assertNotNull(sub);
 		assertTrue(sub.first());
 		assertTrue(sub.eof());
@@ -106,7 +108,8 @@ public void stopWalk() {
 				called[0] = true;
 			}
 		};
-		parent.createSubtreeIterator(db).stopWalk();
+		final ObjectReader reader = db.newObjectReader();
+		parent.createSubtreeIterator(reader).stopWalk();
 		assertTrue(called[0]);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
index eb08e49..f939c90 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
@@ -49,6 +49,7 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.RepositoryTestCase;
 import org.eclipse.jgit.util.RawParseUtils;
 
@@ -124,7 +125,8 @@ public void testSimpleIterate() throws Exception {
 		assertFalse(top.eof());
 		assertEquals(FileMode.TREE.getBits(), top.mode);
 
-		final AbstractTreeIterator sub = top.createSubtreeIterator(db);
+		final ObjectReader reader = db.newObjectReader();
+		final AbstractTreeIterator sub = top.createSubtreeIterator(reader);
 		assertTrue(sub instanceof FileTreeIterator);
 		final FileTreeIterator subfti = (FileTreeIterator) sub;
 		assertTrue(sub.first());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java
new file mode 100644
index 0000000..bb76d00
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.treewalk;
+
+import java.io.File;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
+
+/**
+ * A {@link FileTreeIterator} used in tests which allows to specify explicitly
+ * what will be returned by {@link #getEntryLastModified()}. This allows to
+ * write tests where certain files have to have the same modification time.
+ * <p>
+ * This iterator is configured by a list of strictly increasing long values
+ * t(0), t(1), ..., t(n). For each file with a modification between t(x) and
+ * t(x+1) [ t(x) <= time < t(x+1) ] this iterator will report t(x). For files
+ * with a modification time smaller t(0) a modification time of 0 is returned.
+ * For files with a modification time greater or equal t(n) t(n) will be
+ * returned.
+ * <p>
+ * This class was written especially to test racy-git problems
+ */
+public class FileTreeIteratorWithTimeControl extends FileTreeIterator {
+	private TreeSet<Long> modTimes;
+
+	public FileTreeIteratorWithTimeControl(FileTreeIterator p, Repository repo,
+			TreeSet<Long> modTimes) {
+		super(p, repo.getWorkTree(), repo.getFS());
+		this.modTimes = modTimes;
+	}
+
+	public FileTreeIteratorWithTimeControl(FileTreeIterator p, File f, FS fs,
+			TreeSet<Long> modTimes) {
+		super(p, f, fs);
+		this.modTimes = modTimes;
+	}
+
+	public FileTreeIteratorWithTimeControl(Repository repo,
+			TreeSet<Long> modTimes) {
+		super(repo);
+		this.modTimes = modTimes;
+	}
+
+	public FileTreeIteratorWithTimeControl(File f, FS fs,
+			TreeSet<Long> modTimes) {
+		super(f, fs);
+		this.modTimes = modTimes;
+	}
+
+	@Override
+	public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) {
+		return new FileTreeIteratorWithTimeControl(this,
+				((FileEntry) current()).file, fs, modTimes);
+	}
+
+	@Override
+	public long getEntryLastModified() {
+		if (modTimes == null)
+			return 0;
+		Long cutOff = Long.valueOf(super.getEntryLastModified() + 1);
+		SortedSet<Long> head = modTimes.headSet(cutOff);
+		return head.isEmpty() ? 0 : head.last().longValue();
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java
index 35298b8..e59b7c1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java
@@ -66,8 +66,8 @@ public class NameConflictTreeWalkTest extends RepositoryTestCase {
 	private static final FileMode EXECUTABLE_FILE = FileMode.EXECUTABLE_FILE;
 
 	public void testNoDF_NoGap() throws Exception {
-		final DirCache tree0 = DirCache.read(db);
-		final DirCache tree1 = DirCache.read(db);
+		final DirCache tree0 = db.readDirCache();
+		final DirCache tree1 = db.readDirCache();
 		{
 			final DirCacheBuilder b0 = tree0.builder();
 			final DirCacheBuilder b1 = tree1.builder();
@@ -97,8 +97,8 @@ public void testNoDF_NoGap() throws Exception {
 	}
 
 	public void testDF_NoGap() throws Exception {
-		final DirCache tree0 = DirCache.read(db);
-		final DirCache tree1 = DirCache.read(db);
+		final DirCache tree0 = db.readDirCache();
+		final DirCache tree1 = db.readDirCache();
 		{
 			final DirCacheBuilder b0 = tree0.builder();
 			final DirCacheBuilder b1 = tree1.builder();
@@ -120,16 +120,20 @@ public void testDF_NoGap() throws Exception {
 		tw.addTree(new DirCacheIterator(tree1));
 
 		assertModes("a", REGULAR_FILE, TREE, tw);
+		assertTrue(tw.isDirectoryFileConflict());
 		assertTrue(tw.isSubtree());
 		tw.enterSubtree();
 		assertModes("a/b", MISSING, REGULAR_FILE, tw);
+		assertTrue(tw.isDirectoryFileConflict());
 		assertModes("a.b", EXECUTABLE_FILE, MISSING, tw);
+		assertFalse(tw.isDirectoryFileConflict());
 		assertModes("a0b", SYMLINK, MISSING, tw);
+		assertFalse(tw.isDirectoryFileConflict());
 	}
 
 	public void testDF_GapByOne() throws Exception {
-		final DirCache tree0 = DirCache.read(db);
-		final DirCache tree1 = DirCache.read(db);
+		final DirCache tree0 = db.readDirCache();
+		final DirCache tree1 = db.readDirCache();
 		{
 			final DirCacheBuilder b0 = tree0.builder();
 			final DirCacheBuilder b1 = tree1.builder();
@@ -153,15 +157,19 @@ public void testDF_GapByOne() throws Exception {
 
 		assertModes("a", REGULAR_FILE, TREE, tw);
 		assertTrue(tw.isSubtree());
+		assertTrue(tw.isDirectoryFileConflict());
 		tw.enterSubtree();
 		assertModes("a/b", MISSING, REGULAR_FILE, tw);
+		assertTrue(tw.isDirectoryFileConflict());
 		assertModes("a.b", EXECUTABLE_FILE, EXECUTABLE_FILE, tw);
+		assertFalse(tw.isDirectoryFileConflict());
 		assertModes("a0b", SYMLINK, MISSING, tw);
+		assertFalse(tw.isDirectoryFileConflict());
 	}
 
 	public void testDF_SkipsSeenSubtree() throws Exception {
-		final DirCache tree0 = DirCache.read(db);
-		final DirCache tree1 = DirCache.read(db);
+		final DirCache tree0 = db.readDirCache();
+		final DirCache tree1 = db.readDirCache();
 		{
 			final DirCacheBuilder b0 = tree0.builder();
 			final DirCacheBuilder b1 = tree1.builder();
@@ -185,10 +193,57 @@ public void testDF_SkipsSeenSubtree() throws Exception {
 
 		assertModes("a", REGULAR_FILE, TREE, tw);
 		assertTrue(tw.isSubtree());
+		assertTrue(tw.isDirectoryFileConflict());
 		tw.enterSubtree();
 		assertModes("a/b", MISSING, REGULAR_FILE, tw);
+		assertTrue(tw.isDirectoryFileConflict());
 		assertModes("a.b", MISSING, EXECUTABLE_FILE, tw);
+		assertFalse(tw.isDirectoryFileConflict());
 		assertModes("a0b", SYMLINK, SYMLINK, tw);
+		assertFalse(tw.isDirectoryFileConflict());
+	}
+
+	public void testDF_DetectConflict() throws Exception {
+		final DirCache tree0 = db.readDirCache();
+		final DirCache tree1 = db.readDirCache();
+		{
+			final DirCacheBuilder b0 = tree0.builder();
+			final DirCacheBuilder b1 = tree1.builder();
+
+			b0.add(makeEntry("0", REGULAR_FILE));
+			b0.add(makeEntry("a", REGULAR_FILE));
+			b1.add(makeEntry("0", REGULAR_FILE));
+			b1.add(makeEntry("a.b", REGULAR_FILE));
+			b1.add(makeEntry("a/b", REGULAR_FILE));
+			b1.add(makeEntry("a/c/e", REGULAR_FILE));
+
+			b0.finish();
+			b1.finish();
+			assertEquals(2, tree0.getEntryCount());
+			assertEquals(4, tree1.getEntryCount());
+		}
+
+		final NameConflictTreeWalk tw = new NameConflictTreeWalk(db);
+		tw.reset();
+		tw.addTree(new DirCacheIterator(tree0));
+		tw.addTree(new DirCacheIterator(tree1));
+
+		assertModes("0", REGULAR_FILE, REGULAR_FILE, tw);
+		assertFalse(tw.isDirectoryFileConflict());
+		assertModes("a", REGULAR_FILE, TREE, tw);
+		assertTrue(tw.isSubtree());
+		assertTrue(tw.isDirectoryFileConflict());
+		tw.enterSubtree();
+		assertModes("a/b", MISSING, REGULAR_FILE, tw);
+		assertTrue(tw.isDirectoryFileConflict());
+		assertModes("a/c", MISSING, TREE, tw);
+		assertTrue(tw.isDirectoryFileConflict());
+		tw.enterSubtree();
+		assertModes("a/c/e", MISSING, REGULAR_FILE, tw);
+		assertTrue(tw.isDirectoryFileConflict());
+
+		assertModes("a.b", MISSING, REGULAR_FILE, tw);
+		assertFalse(tw.isDirectoryFileConflict());
 	}
 
 	private DirCacheEntry makeEntry(final String path, final FileMode mode)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java
index d136b8f..274df5b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java
@@ -86,7 +86,7 @@ public void testResetDoesNotAffectPostOrder() throws Exception {
 	}
 
 	public void testNoPostOrder() throws Exception {
-		final DirCache tree = DirCache.read(db);
+		final DirCache tree = db.readDirCache();
 		{
 			final DirCacheBuilder b = tree.builder();
 
@@ -115,7 +115,7 @@ public void testNoPostOrder() throws Exception {
 	}
 
 	public void testWithPostOrder_EnterSubtree() throws Exception {
-		final DirCache tree = DirCache.read(db);
+		final DirCache tree = db.readDirCache();
 		{
 			final DirCacheBuilder b = tree.builder();
 
@@ -150,7 +150,7 @@ public void testWithPostOrder_EnterSubtree() throws Exception {
 	}
 
 	public void testWithPostOrder_NoEnterSubtree() throws Exception {
-		final DirCache tree = DirCache.read(db);
+		final DirCache tree = db.readDirCache();
 		{
 			final DirCacheBuilder b = tree.builder();
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java
index 1aaefc4..302eada 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.treewalk.filter;
 
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
 import java.io.IOException;
 import java.util.LinkedList;
 import java.util.List;
@@ -52,17 +54,17 @@
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.RepositoryTestCase;
 import org.eclipse.jgit.treewalk.TreeWalk;
 
 public class PathSuffixFilterTestCase extends RepositoryTestCase {
 
 	public void testNonRecursiveFiltering() throws IOException {
-		final ObjectWriter ow = new ObjectWriter(db);
-		final ObjectId aSth = ow.writeBlob("a.sth".getBytes());
-		final ObjectId aTxt = ow.writeBlob("a.txt".getBytes());
-		final DirCache dc = DirCache.read(db);
+		final ObjectInserter odi = db.newObjectInserter();
+		final ObjectId aSth = odi.insert(OBJ_BLOB, "a.sth".getBytes());
+		final ObjectId aTxt = odi.insert(OBJ_BLOB, "a.txt".getBytes());
+		final DirCache dc = db.readDirCache();
 		final DirCacheBuilder builder = dc.builder();
 		final DirCacheEntry aSthEntry = new DirCacheEntry("a.sth");
 		aSthEntry.setFileMode(FileMode.REGULAR_FILE);
@@ -73,7 +75,8 @@ public void testNonRecursiveFiltering() throws IOException {
 		builder.add(aSthEntry);
 		builder.add(aTxtEntry);
 		builder.finish();
-		final ObjectId treeId = dc.writeTree(ow);
+		final ObjectId treeId = dc.writeTree(odi);
+		odi.flush();
 
 
 		final TreeWalk tw = new TreeWalk(db);
@@ -92,12 +95,12 @@ public void testNonRecursiveFiltering() throws IOException {
 	}
 
 	public void testRecursiveFiltering() throws IOException {
-		final ObjectWriter ow = new ObjectWriter(db);
-		final ObjectId aSth = ow.writeBlob("a.sth".getBytes());
-		final ObjectId aTxt = ow.writeBlob("a.txt".getBytes());
-		final ObjectId bSth = ow.writeBlob("b.sth".getBytes());
-		final ObjectId bTxt = ow.writeBlob("b.txt".getBytes());
-		final DirCache dc = DirCache.read(db);
+		final ObjectInserter odi = db.newObjectInserter();
+		final ObjectId aSth = odi.insert(OBJ_BLOB, "a.sth".getBytes());
+		final ObjectId aTxt = odi.insert(OBJ_BLOB, "a.txt".getBytes());
+		final ObjectId bSth = odi.insert(OBJ_BLOB, "b.sth".getBytes());
+		final ObjectId bTxt = odi.insert(OBJ_BLOB, "b.txt".getBytes());
+		final DirCache dc = db.readDirCache();
 		final DirCacheBuilder builder = dc.builder();
 		final DirCacheEntry aSthEntry = new DirCacheEntry("a.sth");
 		aSthEntry.setFileMode(FileMode.REGULAR_FILE);
@@ -116,7 +119,8 @@ public void testRecursiveFiltering() throws IOException {
 		builder.add(bSthEntry);
 		builder.add(bTxtEntry);
 		builder.finish();
-		final ObjectId treeId = dc.writeTree(ow);
+		final ObjectId treeId = dc.writeTree(odi);
+		odi.flush();
 
 
 		final TreeWalk tw = new TreeWalk(db);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
index 3b15846..a15cadf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
@@ -123,6 +123,13 @@ public void testHasChangeid() throws Exception {
 				call("has changeid\n\nBug: 33\nmore text\nSigned-off-by: me@you.too\nChange-Id: I0123456789012345678901234567890123456789\nAnd then some\n"));
 	}
 
+	public void testHasChangeidWithReplacement() throws Exception {
+		assertEquals(
+				"has changeid\n\nBug: 33\nmore text\nSigned-off-by: me@you.too\nChange-Id: I988d2d7a6f2c0578fccabd4ebd3cec0768bc7f9f\nAnd then some\n",
+				call("has changeid\n\nBug: 33\nmore text\nSigned-off-by: me@you.too\nChange-Id: I0123456789012345678901234567890123456789\nAnd then some\n",
+						true));
+	}
+
 	public void testOneliner() throws Exception {
 		assertEquals(
 				"oneliner\n\nChange-Id: I3a98091ce4470de88d52ae317fcd297e2339f063\n",
@@ -236,6 +243,47 @@ public void testChangeIdAlreadySet() throws Exception {
 				"Change-Id: I4f4e2e1e8568ddc1509baecb8c1270a1fb4b6da7\n");
 	}
 
+	public void testChangeIdAlreadySetWithReplacement() throws Exception {
+		// If a Change-Id is already present in the footer, the hook
+		// replaces the Change-Id with the new value..
+		//
+		assertEquals("a\n" + //
+				"\n" + //
+				"Change-Id: Ifa324efa85bfb3c8696a46a0f67fa70c35be5f5f\n",
+				call("a\n" + //
+						"\n" + //
+						"Change-Id: Iaeac9b4149291060228ef0154db2985a31111335\n",
+						true));
+		assertEquals("fix: this thing\n" + //
+				"\n" + //
+				"Change-Id: Ib63e4990a06412a3f24bd93bb160e98ac1bd412b\n",
+				call("fix: this thing\n" + //
+						"\n" + //
+						"Change-Id: I388bdaf52ed05b55e62a22d0a20d2c1ae0d33e7e\n",
+						true));
+		assertEquals("fix-a-widget: this thing\n" + //
+				"\n" + //
+				"Change-Id: If0444e4d0cabcf41b3d3b46b7e9a7a64a82117af\n",
+				call("fix-a-widget: this thing\n" + //
+						"\n" + //
+						"Change-Id: Id3bc5359d768a6400450283e12bdfb6cd135ea4b\n",
+						true));
+		assertEquals("FIX: this thing\n" + //
+				"\n" + //
+				"Change-Id: Iba5a3b2d5e5df46448f6daf362b6bfa775c6491d\n",
+				call("FIX: this thing\n" + //
+						"\n" + //
+						"Change-Id: I1b55098b5a2cce0b3f3da783dda50d5f79f873fa\n",
+						true));
+		assertEquals("Fix-A-Widget: this thing\n" + //
+				"\n" + //
+				"Change-Id: I2573d47c62c42429fbe424d70cfba931f8f87848\n",
+				call("Fix-A-Widget: this thing\n" + //
+				"\n" + //
+				"Change-Id: I4f4e2e1e8568ddc1509baecb8c1270a1fb4b6da7\n",
+				true));
+	}
+
 	public void testTimeAltersId() throws Exception {
 		assertEquals("a\n" + //
 				"\n" + //
@@ -513,11 +561,15 @@ private void hookDoesNotModify(final String in) throws Exception {
 	}
 
 	private String call(final String body) throws Exception {
+		return call(body, false);
+	}
+
+	private String call(final String body, boolean replaceExisting) throws Exception {
 		ObjectId computeChangeId = ChangeIdUtil.computeChangeId(treeId1,
 				parentId1, author, committer, body);
 		if (computeChangeId == null)
 			return body;
-		return ChangeIdUtil.insertId(body, computeChangeId);
+		return ChangeIdUtil.insertId(body, computeChangeId, replaceExisting);
 	}
 
 }
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 51d440e..a8f39d9 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -9,6 +9,7 @@
  org.eclipse.jgit.api;version="0.9.0",
  org.eclipse.jgit.diff;version="0.9.0",
  org.eclipse.jgit.dircache;version="0.9.0",
+ org.eclipse.jgit.events;version="0.9.0",
  org.eclipse.jgit.errors;version="0.9.0",
  org.eclipse.jgit.fnmatch;version="0.9.0",
  org.eclipse.jgit.ignore;version="0.9.0",
@@ -19,6 +20,8 @@
  org.eclipse.jgit.revplot;version="0.9.0",
  org.eclipse.jgit.revwalk;version="0.9.0",
  org.eclipse.jgit.revwalk.filter;version="0.9.0",
+ org.eclipse.jgit.storage.file;version="0.9.0",
+ org.eclipse.jgit.storage.pack;version="0.9.0",
  org.eclipse.jgit.transport;version="0.9.0",
  org.eclipse.jgit.treewalk;version="0.9.0",
  org.eclipse.jgit.treewalk.filter;version="0.9.0",
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
index ca1832e..a9878f8 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
@@ -127,7 +127,7 @@
 duplicateRef=Duplicate ref: {0}
 duplicateRemoteRefUpdateIsIllegal=Duplicate remote ref update is illegal. Affected remote name: {0}
 duplicateStagesNotAllowed=Duplicate stages not allowed
-eitherGIT_DIRorGIT_WORK_TREEmustBePassed=Either GIT_DIR or GIT_WORK_TREE must be passed to Repository constructor
+eitherGitDirOrWorkTreeRequired=One of setGitDir or setWorkTree must be called.
 emptyPathNotPermitted=Empty path not permitted.
 encryptionError=Encryption error: {0}
 endOfFileInEscape=End of file in escape
@@ -224,6 +224,7 @@
 mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads to be merged into HEAD
 mergeUsingStrategyResultedInDescription=Merge using strategy {0} resulted in: {1}. {2}
 missingAccesskey=Missing accesskey.
+missingDeltaBase=delta base
 missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch
 missingObject=Missing {0} {1}
 missingPrerequisiteCommits=missing prerequisite commits:
@@ -301,9 +302,12 @@
 remoteHungUpUnexpectedly=remote hung up unexpectedly
 remoteNameCantBeNull=Remote name can't be null.
 renamesAlreadyFound=Renames have already been found.
+renamesBreakingModifies=Breaking apart modified file pairs
 renamesFindingByContent=Finding renames by content similarity
 renamesFindingExact=Finding exact renames
+renamesRejoiningModifies=Rejoining modified file pairs
 repositoryAlreadyExists=Repository already exists: {0}
+repositoryConfigFileInvalid=Repository config file {0} invalid {1}
 repositoryIsRequired=Repository is required.
 repositoryNotFound=repository not found: {0}
 repositoryState_applyMailbox=Apply mailbox
@@ -331,6 +335,7 @@
 staleRevFlagsOn=Stale RevFlags on {0}
 startingReadStageWithoutWrittenRequestDataPendingIsNotSupported=Starting read stage without written request data pending is not supported
 statelessRPCRequiresOptionToBeEnabled=stateless RPC requires {0} to be enabled
+submodulesNotSupported=Submodules are not supported
 symlinkCannotBeWrittenAsTheLinkTarget=Symlink "{0}" cannot be written as the link target cannot be read from within Java.
 tSizeMustBeGreaterOrEqual1=tSize must be >= 1
 theFactoryMustNotBeNull=The factory must not be null
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
index 71e5e4a..461242c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
@@ -187,7 +187,7 @@ public static JGitText get() {
 	/***/ public String duplicateRef;
 	/***/ public String duplicateRemoteRefUpdateIsIllegal;
 	/***/ public String duplicateStagesNotAllowed;
-	/***/ public String eitherGIT_DIRorGIT_WORK_TREEmustBePassed;
+	/***/ public String eitherGitDirOrWorkTreeRequired;
 	/***/ public String emptyPathNotPermitted;
 	/***/ public String encryptionError;
 	/***/ public String endOfFileInEscape;
@@ -284,6 +284,7 @@ public static JGitText get() {
 	/***/ public String mergeStrategyDoesNotSupportHeads;
 	/***/ public String mergeUsingStrategyResultedInDescription;
 	/***/ public String missingAccesskey;
+	/***/ public String missingDeltaBase;
 	/***/ public String missingForwardImageInGITBinaryPatch;
 	/***/ public String missingObject;
 	/***/ public String missingPrerequisiteCommits;
@@ -360,9 +361,12 @@ public static JGitText get() {
 	/***/ public String remoteHungUpUnexpectedly;
 	/***/ public String remoteNameCantBeNull;
 	/***/ public String renamesAlreadyFound;
+	/***/ public String renamesBreakingModifies;
 	/***/ public String renamesFindingByContent;
 	/***/ public String renamesFindingExact;
+	/***/ public String renamesRejoiningModifies;
 	/***/ public String repositoryAlreadyExists;
+	/***/ public String repositoryConfigFileInvalid;
 	/***/ public String repositoryIsRequired;
 	/***/ public String repositoryNotFound;
 	/***/ public String repositoryState_applyMailbox;
@@ -390,6 +394,7 @@ public static JGitText get() {
 	/***/ public String staleRevFlagsOn;
 	/***/ public String startingReadStageWithoutWrittenRequestDataPendingIsNotSupported;
 	/***/ public String statelessRPCRequiresOptionToBeEnabled;
+	/***/ public String submodulesNotSupported;
 	/***/ public String symlinkCannotBeWrittenAsTheLinkTarget;
 	/***/ public String tSizeMustBeGreaterOrEqual1;
 	/***/ public String theFactoryMustNotBeNull;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
index 238d1d2..f7d4da4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -58,6 +58,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeIterator;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
 
 /**
@@ -73,6 +74,10 @@ public class AddCommand extends GitCommand<DirCache> {
 
 	private Collection<String> filepatterns;
 
+	private WorkingTreeIterator workingTreeIterator;
+
+	private boolean update = false;
+
 	/**
 	 *
 	 * @param repo
@@ -97,6 +102,16 @@ public AddCommand addFilepattern(String filepattern) {
 	}
 
 	/**
+	 * Allow clients to provide their own implementation of a FileTreeIterator
+	 * @param f
+	 * @return {@code this}
+	 */
+	public AddCommand setWorkingTreeIterator(WorkingTreeIterator f) {
+		workingTreeIterator = f;
+		return this;
+	}
+
+	/**
 	 * Executes the {@code Add} command. Each instance of this class should only
 	 * be used for one invocation of the command. Don't call this method twice
 	 * on an instance.
@@ -109,9 +124,12 @@ public DirCache call() throws NoFilepatternException {
 			throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired);
 		checkCallable();
 		DirCache dc = null;
+		boolean addAll = false;
+		if (filepatterns.contains("."))
+			addAll = true;
 
 		try {
-			dc = DirCache.lock(repo);
+			dc = repo.lockDirCache();
 			ObjectWriter ow = new ObjectWriter(repo);
 			DirCacheIterator c;
 
@@ -119,36 +137,43 @@ public DirCache call() throws NoFilepatternException {
 			final TreeWalk tw = new TreeWalk(repo);
 			tw.reset();
 			tw.addTree(new DirCacheBuildIterator(builder));
-			FileTreeIterator fileTreeIterator = new FileTreeIterator(
-					repo.getWorkDir(), repo.getFS());
-			tw.addTree(fileTreeIterator);
+			if (workingTreeIterator == null)
+				workingTreeIterator = new FileTreeIterator(repo);
+			tw.addTree(workingTreeIterator);
 			tw.setRecursive(true);
-			tw.setFilter(PathFilterGroup.createFromStrings(filepatterns));
+			if (!addAll)
+				tw.setFilter(PathFilterGroup.createFromStrings(filepatterns));
 
 			String lastAddedFile = null;
 
 			while (tw.next()) {
 				String path = tw.getPathString();
 
-				final File file = new File(repo.getWorkDir(), path);
+				final File file = new File(repo.getWorkTree(), path);
+				WorkingTreeIterator f = tw.getTree(1, WorkingTreeIterator.class);
+				if (tw.getTree(0, DirCacheIterator.class) == null &&
+						f != null && f.isEntryIgnored()) {
+					// file is not in index but is ignored, do nothing
+				}
 				// In case of an existing merge conflict the
 				// DirCacheBuildIterator iterates over all stages of
 				// this path, we however want to add only one
 				// new DirCacheEntry per path.
-				if (!(path.equals(lastAddedFile))) {
-					 FileTreeIterator f = tw.getTree(1, FileTreeIterator.class);
-					 if (f != null) { // the file exists
-						DirCacheEntry entry = new DirCacheEntry(path);
-						entry.setLength((int)f.getEntryLength());
-						entry.setLastModified(f.getEntryLastModified());
-						entry.setFileMode(f.getEntryFileMode());
-						entry.setObjectId(ow.writeBlob(file));
+				else if (!(path.equals(lastAddedFile))) {
+					if (!(update && tw.getTree(0, DirCacheIterator.class) == null)) {
+						if (f != null) { // the file exists
+							DirCacheEntry entry = new DirCacheEntry(path);
+							entry.setLength((int)f.getEntryLength());
+							entry.setLastModified(f.getEntryLastModified());
+							entry.setFileMode(f.getEntryFileMode());
+							entry.setObjectId(ow.writeBlob(file));
 
-						builder.add(entry);
-						lastAddedFile = path;
-					} else {
-						c = tw.getTree(0, DirCacheIterator.class);
-						builder.add(c.getDirCacheEntry());
+							builder.add(entry);
+							lastAddedFile = path;
+						} else if (!update){
+							c = tw.getTree(0, DirCacheIterator.class);
+							builder.add(c.getDirCacheEntry());
+						}
 					}
 				}
 			}
@@ -165,4 +190,29 @@ public DirCache call() throws NoFilepatternException {
 		return dc;
 	}
 
+	/**
+	 * @param update
+	 *            If set to true, the command only matches {@code filepattern}
+	 *            against already tracked files in the index rather than the
+	 *            working tree. That means that it will never stage new files,
+	 *            but that it will stage modified new contents of tracked files
+	 *            and that it will remove files from the index if the
+	 *            corresponding files in the working tree have been removed.
+	 *            In contrast to the git command line a {@code filepattern} must
+	 *            exist also if update is set to true as there is no
+	 *            concept of a working directory here.
+	 *
+	 * @return {@code this}
+	 */
+	public AddCommand setUpdate(boolean update) {
+		this.update = update;
+		return this;
+	}
+
+	/**
+	 * @return is the parameter update is set
+	 */
+	public boolean isUpdate() {
+		return update;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index eef952e..ae4b334 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -42,7 +42,6 @@
  */
 package org.eclipse.jgit.api;
 
-import java.io.File;
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.LinkedList;
@@ -54,13 +53,13 @@
 import org.eclipse.jgit.lib.Commit;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryState;
+import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 
@@ -80,6 +79,8 @@ public class CommitCommand extends GitCommand<RevCommit> {
 
 	private String message;
 
+	private boolean all;
+
 	/**
 	 * parents this commit should have. The current HEAD will be in this list
 	 * and also all commits mentioned in .git/MERGE_HEAD
@@ -128,6 +129,18 @@ public RevCommit call() throws NoHeadException, NoMessageException,
 		processOptions(state);
 
 		try {
+			if (all && !repo.isBare() && repo.getWorkTree() != null) {
+				Git git = new Git(repo);
+				try {
+					git.add()
+							.addFilepattern(".")
+							.setUpdate(true).call();
+				} catch (NoFilepatternException e) {
+					// should really not happen
+					throw new JGitInternalException(e.getMessage(), e);
+				}
+			}
+
 			Ref head = repo.getRef(Constants.HEAD);
 			if (head == null)
 				throw new NoHeadException(
@@ -139,54 +152,64 @@ public RevCommit call() throws NoHeadException, NoMessageException,
 				parents.add(0, headId);
 
 			// lock the index
-			DirCache index = DirCache.lock(repo);
+			DirCache index = repo.lockDirCache();
 			try {
-				ObjectWriter repoWriter = new ObjectWriter(repo);
+				ObjectInserter odi = repo.newObjectInserter();
+				try {
+					// Write the index as tree to the object database. This may
+					// fail for example when the index contains unmerged paths
+					// (unresolved conflicts)
+					ObjectId indexTreeId = index.writeTree(odi);
 
-				// Write the index as tree to the object database. This may fail
-				// for example when the index contains unmerged pathes
-				// (unresolved conflicts)
-				ObjectId indexTreeId = index.writeTree(repoWriter);
+					// Create a Commit object, populate it and write it
+					Commit commit = new Commit(repo);
+					commit.setCommitter(committer);
+					commit.setAuthor(author);
+					commit.setMessage(message);
 
-				// Create a Commit object, populate it and write it
-				Commit commit = new Commit(repo);
-				commit.setCommitter(committer);
-				commit.setAuthor(author);
-				commit.setMessage(message);
+					commit.setParentIds(parents.toArray(new ObjectId[] {}));
+					commit.setTreeId(indexTreeId);
+					ObjectId commitId = odi.insert(Constants.OBJ_COMMIT, odi
+							.format(commit));
+					odi.flush();
 
-				commit.setParentIds(parents.toArray(new ObjectId[]{}));
-				commit.setTreeId(indexTreeId);
-				ObjectId commitId = repoWriter.writeCommit(commit);
+					RevWalk revWalk = new RevWalk(repo);
+					try {
+						RevCommit revCommit = revWalk.parseCommit(commitId);
+						RefUpdate ru = repo.updateRef(Constants.HEAD);
+						ru.setNewObjectId(commitId);
+						ru.setRefLogMessage("commit : "
+								+ revCommit.getShortMessage(), false);
 
-				RevCommit revCommit = new RevWalk(repo).parseCommit(commitId);
-				RefUpdate ru = repo.updateRef(Constants.HEAD);
-				ru.setNewObjectId(commitId);
-				ru.setRefLogMessage("commit : " + revCommit.getShortMessage(),
-						false);
-
-				ru.setExpectedOldObjectId(headId);
-				Result rc = ru.update();
-				switch (rc) {
-				case NEW:
-				case FAST_FORWARD:
-					setCallable(false);
-					if (state == RepositoryState.MERGING_RESOLVED) {
-						// Commit was successful. Now delete the files
-						// used for merge commits
-						new File(repo.getDirectory(), Constants.MERGE_HEAD)
-								.delete();
-						new File(repo.getDirectory(), Constants.MERGE_MSG)
-								.delete();
+						ru.setExpectedOldObjectId(headId);
+						Result rc = ru.update();
+						switch (rc) {
+						case NEW:
+						case FAST_FORWARD: {
+							setCallable(false);
+							if (state == RepositoryState.MERGING_RESOLVED) {
+								// Commit was successful. Now delete the files
+								// used for merge commits
+								repo.writeMergeCommitMsg(null);
+								repo.writeMergeHeads(null);
+							}
+							return revCommit;
+						}
+						case REJECTED:
+						case LOCK_FAILURE:
+							throw new ConcurrentRefUpdateException(JGitText
+									.get().couldNotLockHEAD, ru.getRef(), rc);
+						default:
+							throw new JGitInternalException(MessageFormat
+									.format(JGitText.get().updatingRefFailed,
+											Constants.HEAD,
+											commitId.toString(), rc));
+						}
+					} finally {
+						revWalk.release();
 					}
-					return revCommit;
-				case REJECTED:
-				case LOCK_FAILURE:
-					throw new ConcurrentRefUpdateException(
-							JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
-				default:
-					throw new JGitInternalException(MessageFormat.format(
-							JGitText.get().updatingRefFailed
-							, Constants.HEAD, commitId.toString(), rc));
+				} finally {
+					odi.release();
 				}
 			} finally {
 				index.unlock();
@@ -344,4 +367,19 @@ public CommitCommand setAuthor(String name, String email) {
 	public PersonIdent getAuthor() {
 		return author;
 	}
+
+	/**
+	 * If set to true the Commit command automatically stages files that have
+	 * been modified and deleted, but new files you not known by the repository
+	 * are not affected. This corresponds to the parameter -a on the command
+	 * line.
+	 *
+	 * @param all
+	 * @return {@code this}
+	 */
+	public CommitCommand setAll(boolean all) {
+		this.all = all;
+		return this;
+	}
+
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ConcurrentRefUpdateException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ConcurrentRefUpdateException.java
index 6f681b6..2a5f7f9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ConcurrentRefUpdateException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ConcurrentRefUpdateException.java
@@ -47,7 +47,7 @@
  * Exception thrown when a command wants to update a ref but failed because
  * another process is accessing (or even also updating) the ref.
  *
- * @see RefUpdate.Result#LOCK_FAILURE
+ * @see org.eclipse.jgit.lib.RefUpdate.Result#LOCK_FAILURE
  */
 public class ConcurrentRefUpdateException extends GitAPIException {
 	private static final long serialVersionUID = 1L;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
index 414fac4..61a87b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
@@ -64,7 +64,7 @@
  * This is currently a very basic implementation which takes only one starting
  * revision as option.
  *
- * @TODO add more options (revision ranges, sorting, ...)
+ * TODO: add more options (revision ranges, sorting, ...)
  *
  * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-log.html"
  *      >Git documentation about Log</a>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
index 00a0309..972aa61 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
@@ -119,37 +119,41 @@ public MergeResult call() throws NoHeadException,
 
 			// Check for FAST_FORWARD, ALREADY_UP_TO_DATE
 			RevWalk revWalk = new RevWalk(repo);
-			RevCommit headCommit = revWalk.lookupCommit(head.getObjectId());
+			try {
+				RevCommit headCommit = revWalk.lookupCommit(head.getObjectId());
 
-			Ref ref = commits.get(0);
+				Ref ref = commits.get(0);
 
-			refLogMessage.append(ref.getName());
+				refLogMessage.append(ref.getName());
 
-			// handle annotated tags
-			ObjectId objectId = ref.getPeeledObjectId();
-			if (objectId == null)
-				objectId = ref.getObjectId();
+				// handle annotated tags
+				ObjectId objectId = ref.getPeeledObjectId();
+				if (objectId == null)
+					objectId = ref.getObjectId();
 
-			RevCommit srcCommit = revWalk.lookupCommit(objectId);
-			if (revWalk.isMergedInto(srcCommit, headCommit)) {
-				setCallable(false);
-				return new MergeResult(headCommit,
-						MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy);
-			} else if (revWalk.isMergedInto(headCommit, srcCommit)) {
-				// FAST_FORWARD detected: skip doing a real merge but only
-				// update HEAD
-				refLogMessage.append(": " + MergeStatus.FAST_FORWARD);
-				checkoutNewHead(revWalk, headCommit, srcCommit);
-				updateHead(refLogMessage, srcCommit, head.getObjectId());
-				setCallable(false);
-				return new MergeResult(srcCommit, MergeStatus.FAST_FORWARD,
-						mergeStrategy);
-			} else {
-				return new MergeResult(
-						headCommit,
-						MergeResult.MergeStatus.NOT_SUPPORTED,
-						mergeStrategy,
-						JGitText.get().onlyAlreadyUpToDateAndFastForwardMergesAreAvailable);
+				RevCommit srcCommit = revWalk.lookupCommit(objectId);
+				if (revWalk.isMergedInto(srcCommit, headCommit)) {
+					setCallable(false);
+					return new MergeResult(headCommit,
+							MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy);
+				} else if (revWalk.isMergedInto(headCommit, srcCommit)) {
+					// FAST_FORWARD detected: skip doing a real merge but only
+					// update HEAD
+					refLogMessage.append(": " + MergeStatus.FAST_FORWARD);
+					checkoutNewHead(revWalk, headCommit, srcCommit);
+					updateHead(refLogMessage, srcCommit, head.getObjectId());
+					setCallable(false);
+					return new MergeResult(srcCommit, MergeStatus.FAST_FORWARD,
+							mergeStrategy);
+				} else {
+					return new MergeResult(
+							headCommit,
+							MergeResult.MergeStatus.NOT_SUPPORTED,
+							mergeStrategy,
+							JGitText.get().onlyAlreadyUpToDateAndFastForwardMergesAreAvailable);
+				}
+			} finally {
+				revWalk.release();
 			}
 		} catch (IOException e) {
 			throw new JGitInternalException(
@@ -163,7 +167,7 @@ private void checkoutNewHead(RevWalk revWalk, RevCommit headCommit,
 			RevCommit newHeadCommit) throws IOException, CheckoutConflictException {
 		GitIndex index = repo.getIndex();
 
-		File workDir = repo.getWorkDir();
+		File workDir = repo.getWorkTree();
 		if (workDir != null) {
 			WorkDirCheckout workDirCheckout = new WorkDirCheckout(repo,
 					workDir, headCommit.asCommit(revWalk).getTree(), index,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
index 304e4cf..55ecc4e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
@@ -57,7 +57,8 @@
 
 /** A value class representing a change to a file */
 public class DiffEntry {
-	private static final AbbreviatedObjectId A_ZERO = AbbreviatedObjectId
+	/** Magical SHA1 used for file adds or deletes */
+	static final AbbreviatedObjectId A_ZERO = AbbreviatedObjectId
 			.fromObjectId(ObjectId.zeroId());
 
 	/** Magical file name used for file adds or deletes. */
@@ -111,15 +112,15 @@ public static List<DiffEntry> scan(TreeWalk walk) throws IOException {
 
 			entry.oldMode = walk.getFileMode(0);
 			entry.newMode = walk.getFileMode(1);
-			entry.newName = entry.oldName = walk.getPathString();
+			entry.newPath = entry.oldPath = walk.getPathString();
 
 			if (entry.oldMode == FileMode.MISSING) {
-				entry.oldName = DiffEntry.DEV_NULL;
+				entry.oldPath = DiffEntry.DEV_NULL;
 				entry.changeType = ChangeType.ADD;
 				r.add(entry);
 
 			} else if (entry.newMode == FileMode.MISSING) {
-				entry.newName = DiffEntry.DEV_NULL;
+				entry.newPath = DiffEntry.DEV_NULL;
 				entry.changeType = ChangeType.DELETE;
 				r.add(entry);
 
@@ -138,11 +139,11 @@ static DiffEntry add(String path, AnyObjectId id) {
 		DiffEntry e = new DiffEntry();
 		e.oldId = A_ZERO;
 		e.oldMode = FileMode.MISSING;
-		e.oldName = DEV_NULL;
+		e.oldPath = DEV_NULL;
 
 		e.newId = AbbreviatedObjectId.fromObjectId(id);
 		e.newMode = FileMode.REGULAR_FILE;
-		e.newName = path;
+		e.newPath = path;
 		e.changeType = ChangeType.ADD;
 		return e;
 	}
@@ -151,11 +152,11 @@ static DiffEntry delete(String path, AnyObjectId id) {
 		DiffEntry e = new DiffEntry();
 		e.oldId = AbbreviatedObjectId.fromObjectId(id);
 		e.oldMode = FileMode.REGULAR_FILE;
-		e.oldName = path;
+		e.oldPath = path;
 
 		e.newId = A_ZERO;
 		e.newMode = FileMode.MISSING;
-		e.newName = DEV_NULL;
+		e.newPath = DEV_NULL;
 		e.changeType = ChangeType.DELETE;
 		return e;
 	}
@@ -163,33 +164,42 @@ static DiffEntry delete(String path, AnyObjectId id) {
 	static DiffEntry modify(String path) {
 		DiffEntry e = new DiffEntry();
 		e.oldMode = FileMode.REGULAR_FILE;
-		e.oldName = path;
+		e.oldPath = path;
 
 		e.newMode = FileMode.REGULAR_FILE;
-		e.newName = path;
+		e.newPath = path;
 		e.changeType = ChangeType.MODIFY;
 		return e;
 	}
 
+	/**
+	 * Breaks apart a DiffEntry into two entries, one DELETE and one ADD.
+	 *
+	 * @param entry
+	 *            the DiffEntry to break apart.
+	 * @return a list containing two entries. Calling {@link #getChangeType()}
+	 *         on the first entry will return ChangeType.DELETE. Calling it on
+	 *         the second entry will return ChangeType.ADD.
+	 */
 	static List<DiffEntry> breakModify(DiffEntry entry) {
 		DiffEntry del = new DiffEntry();
 		del.oldId = entry.getOldId();
 		del.oldMode = entry.getOldMode();
-		del.oldName = entry.getOldName();
+		del.oldPath = entry.getOldPath();
 
 		del.newId = A_ZERO;
 		del.newMode = FileMode.MISSING;
-		del.newName = DiffEntry.DEV_NULL;
+		del.newPath = DiffEntry.DEV_NULL;
 		del.changeType = ChangeType.DELETE;
 
 		DiffEntry add = new DiffEntry();
 		add.oldId = A_ZERO;
 		add.oldMode = FileMode.MISSING;
-		add.oldName = DiffEntry.DEV_NULL;
+		add.oldPath = DiffEntry.DEV_NULL;
 
 		add.newId = entry.getNewId();
 		add.newMode = entry.getNewMode();
-		add.newName = entry.getNewName();
+		add.newPath = entry.getNewPath();
 		add.changeType = ChangeType.ADD;
 		return Arrays.asList(del, add);
 	}
@@ -200,11 +210,11 @@ static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst,
 
 		r.oldId = src.oldId;
 		r.oldMode = src.oldMode;
-		r.oldName = src.oldName;
+		r.oldPath = src.oldPath;
 
 		r.newId = dst.newId;
 		r.newMode = dst.newMode;
-		r.newName = dst.newName;
+		r.newPath = dst.newPath;
 
 		r.changeType = changeType;
 		r.score = score;
@@ -213,10 +223,10 @@ static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst,
 	}
 
 	/** File name of the old (pre-image). */
-	protected String oldName;
+	protected String oldPath;
 
 	/** File name of the new (post-image). */
-	protected String newName;
+	protected String newPath;
 
 	/** Old mode of the file, if described by the patch, else null. */
 	protected FileMode oldMode;
@@ -243,7 +253,7 @@ static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst,
 	 * of this patch:
 	 * <ul>
 	 * <li><i>file add</i>: always <code>/dev/null</code></li>
-	 * <li><i>file modify</i>: always {@link #getNewName()}</li>
+	 * <li><i>file modify</i>: always {@link #getNewPath()}</li>
 	 * <li><i>file delete</i>: always the file being deleted</li>
 	 * <li><i>file copy</i>: source file the copy originates from</li>
 	 * <li><i>file rename</i>: source file the rename originates from</li>
@@ -251,8 +261,8 @@ static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst,
 	 *
 	 * @return old name for this file.
 	 */
-	public String getOldName() {
-		return oldName;
+	public String getOldPath() {
+		return oldPath;
 	}
 
 	/**
@@ -262,7 +272,7 @@ public String getOldName() {
 	 * of this patch:
 	 * <ul>
 	 * <li><i>file add</i>: always the file being created</li>
-	 * <li><i>file modify</i>: always {@link #getOldName()}</li>
+	 * <li><i>file modify</i>: always {@link #getOldPath()}</li>
 	 * <li><i>file delete</i>: always <code>/dev/null</code></li>
 	 * <li><i>file copy</i>: destination file the copy ends up at</li>
 	 * <li><i>file rename</i>: destination file the rename ends up at/li>
@@ -270,8 +280,8 @@ public String getOldName() {
 	 *
 	 * @return new name for this file.
 	 */
-	public String getNewName() {
-		return newName;
+	public String getNewPath() {
+		return newPath;
 	}
 
 	/** @return the old file mode, if described in the patch */
@@ -284,14 +294,14 @@ public FileMode getNewMode() {
 		return newMode;
 	}
 
-	/** @return the type of change this patch makes on {@link #getNewName()} */
+	/** @return the type of change this patch makes on {@link #getNewPath()} */
 	public ChangeType getChangeType() {
 		return changeType;
 	}
 
 	/**
-	 * @return similarity score between {@link #getOldName()} and
-	 *         {@link #getNewName()} if {@link #getChangeType()} is
+	 * @return similarity score between {@link #getOldPath()} and
+	 *         {@link #getNewPath()} if {@link #getChangeType()} is
 	 *         {@link ChangeType#COPY} or {@link ChangeType#RENAME}.
 	 */
 	public int getScore() {
@@ -324,19 +334,19 @@ public String toString() {
 		buf.append(" ");
 		switch (changeType) {
 		case ADD:
-			buf.append(newName);
+			buf.append(newPath);
 			break;
 		case COPY:
-			buf.append(oldName + "->" + newName);
+			buf.append(oldPath + "->" + newPath);
 			break;
 		case DELETE:
-			buf.append(oldName);
+			buf.append(oldPath);
 			break;
 		case MODIFY:
-			buf.append(oldName);
+			buf.append(oldPath);
 			break;
 		case RENAME:
-			buf.append(oldName + "->" + newName);
+			buf.append(oldPath + "->" + newPath);
 			break;
 		}
 		buf.append("]");
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
index cdcc5e6..bb4a77c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -50,20 +50,24 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.List;
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.LargeObjectException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.patch.FileHeader;
 import org.eclipse.jgit.patch.HunkHeader;
 import org.eclipse.jgit.patch.FileHeader.PatchType;
+import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.QuotedString;
 import org.eclipse.jgit.util.io.DisabledOutputStream;
 
@@ -83,6 +87,8 @@ public class DiffFormatter {
 
 	private RawText.Factory rawTextFactory = RawText.FACTORY;
 
+	private long bigFileThreshold = 50 * 1024 * 1024;
+
 	/**
 	 * Create a new formatter with a default level of context.
 	 *
@@ -110,6 +116,9 @@ protected OutputStream getOutputStream() {
 	 */
 	public void setRepository(Repository repository) {
 		db = repository;
+
+		CoreConfig cfg = db.getConfig().get(CoreConfig.KEY);
+		bigFileThreshold = cfg.getStreamFileThreshold();
 	}
 
 	/**
@@ -158,6 +167,19 @@ public void setRawTextFactory(RawText.Factory type) {
 	}
 
 	/**
+	 * Set the maximum file size that should be considered for diff output.
+	 * <p>
+	 * Text files that are larger than this size will not have a difference
+	 * generated during output.
+	 *
+	 * @param bigFileThreshold
+	 *            the limit, in bytes.
+	 */
+	public void setBigFileThreshold(long bigFileThreshold) {
+		this.bigFileThreshold = bigFileThreshold;
+	}
+
+	/**
 	 * Flush the underlying output stream of this formatter.
 	 *
 	 * @throws IOException
@@ -224,8 +246,8 @@ private void writeGitLinkDiffText(OutputStream o, DiffEntry ent)
 
 	private void writeDiffHeader(OutputStream o, DiffEntry ent)
 			throws IOException {
-		String oldName = quotePath("a/" + ent.getOldName());
-		String newName = quotePath("b/" + ent.getNewName());
+		String oldName = quotePath("a/" + ent.getOldPath());
+		String newName = quotePath("b/" + ent.getNewPath());
 		o.write(encode("diff --git " + oldName + " " + newName + "\n"));
 
 		switch (ent.getChangeType()) {
@@ -245,10 +267,10 @@ private void writeDiffHeader(OutputStream o, DiffEntry ent)
 			o.write(encodeASCII("similarity index " + ent.getScore() + "%"));
 			o.write('\n');
 
-			o.write(encode("rename from " + quotePath(ent.getOldName())));
+			o.write(encode("rename from " + quotePath(ent.getOldPath())));
 			o.write('\n');
 
-			o.write(encode("rename to " + quotePath(ent.getNewName())));
+			o.write(encode("rename to " + quotePath(ent.getNewPath())));
 			o.write('\n');
 			break;
 
@@ -256,10 +278,10 @@ private void writeDiffHeader(OutputStream o, DiffEntry ent)
 			o.write(encodeASCII("similarity index " + ent.getScore() + "%"));
 			o.write('\n');
 
-			o.write(encode("copy from " + quotePath(ent.getOldName())));
+			o.write(encode("copy from " + quotePath(ent.getOldPath())));
 			o.write('\n');
 
-			o.write(encode("copy to " + quotePath(ent.getNewName())));
+			o.write(encode("copy to " + quotePath(ent.getNewPath())));
 			o.write('\n');
 
 			if (!ent.getOldMode().equals(ent.getNewMode())) {
@@ -268,6 +290,14 @@ private void writeDiffHeader(OutputStream o, DiffEntry ent)
 				o.write('\n');
 			}
 			break;
+		case MODIFY:
+			int score = ent.getScore();
+			if (0 < score && score <= 100) {
+				o.write(encodeASCII("dissimilarity index " + (100 - score)
+						+ "%"));
+				o.write('\n');
+			}
+			break;
 		}
 
 		switch (ent.getChangeType()) {
@@ -318,9 +348,32 @@ private static String quotePath(String name) {
 
 		if (db == null)
 			throw new IllegalStateException(JGitText.get().repositoryIsRequired);
+
 		if (id.isComplete()) {
-			ObjectLoader ldr = db.openObject(id.toObjectId());
-			return ldr.getCachedBytes();
+			ObjectLoader ldr = db.open(id.toObjectId());
+			if (!ldr.isLarge())
+				return ldr.getCachedBytes();
+
+			long sz = ldr.getSize();
+			if (sz < bigFileThreshold && sz < Integer.MAX_VALUE) {
+				byte[] buf;
+				try {
+					buf = new byte[(int) sz];
+				} catch (OutOfMemoryError noMemory) {
+					LargeObjectException e;
+
+					e = new LargeObjectException(id.toObjectId());
+					e.initCause(noMemory);
+					throw e;
+				}
+				InputStream in = ldr.openStream();
+				try {
+					IO.readFully(in, buf, 0, buf.length);
+				} finally {
+					in.close();
+				}
+				return buf;
+			}
 		}
 
 		return new byte[] {};
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
index ad37a73..9c1310a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
@@ -55,8 +55,10 @@
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Repository;
 
@@ -78,8 +80,8 @@ private String nameOf(DiffEntry ent) {
 			// the old name.
 			//
 			if (ent.changeType == ChangeType.DELETE)
-				return ent.oldName;
-			return ent.newName;
+				return ent.oldPath;
+			return ent.newPath;
 		}
 
 		private int sortOf(ChangeType changeType) {
@@ -98,7 +100,7 @@ private int sortOf(ChangeType changeType) {
 		}
 	};
 
-	private final List<DiffEntry> entries = new ArrayList<DiffEntry>();
+	private List<DiffEntry> entries = new ArrayList<DiffEntry>();
 
 	private List<DiffEntry> deleted = new ArrayList<DiffEntry>();
 
@@ -111,6 +113,13 @@ private int sortOf(ChangeType changeType) {
 	/** Similarity score required to pair an add/delete as a rename. */
 	private int renameScore = 60;
 
+	/**
+	 * Similarity score required to keep modified file pairs together. Any
+	 * modified file pairs with a similarity score below this will be broken
+	 * apart.
+	 */
+	private int breakScore = -1;
+
 	/** Limit in the number of files to consider for renames. */
 	private int renameLimit;
 
@@ -158,6 +167,29 @@ public void setRenameScore(int score) {
 		renameScore = score;
 	}
 
+	/**
+	 * @return the similarity score required to keep modified file pairs
+	 *         together. Any modify pairs that score below this will be broken
+	 *         apart into separate add/deletes. Values less than or equal to
+	 *         zero indicate that no modifies will be broken apart. Values over
+	 *         100 cause all modify pairs to be broken.
+	 */
+	public int getBreakScore() {
+		return breakScore;
+	}
+
+	/**
+	 * @param breakScore
+	 *            the similarity score required to keep modified file pairs
+	 *            together. Any modify pairs that score below this will be
+	 *            broken apart into separate add/deletes. Values less than or
+	 *            equal to zero indicate that no modifies will be broken apart.
+	 *            Values over 100 cause all modify pairs to be broken.
+	 */
+	public void setBreakScore(int breakScore) {
+		this.breakScore = breakScore;
+	}
+
 	/** @return limit on number of paths to perform inexact rename detection. */
 	public int getRenameLimit() {
 		return renameLimit;
@@ -218,10 +250,13 @@ public void addAll(Collection<DiffEntry> entriesToAdd) {
 				break;
 
 			case MODIFY:
-				if (sameType(entry.getOldMode(), entry.getNewMode()))
+				if (sameType(entry.getOldMode(), entry.getNewMode())) {
 					entries.add(entry);
-				else
-					entries.addAll(DiffEntry.breakModify(entry));
+				} else {
+					List<DiffEntry> tmp = DiffEntry.breakModify(entry);
+					deleted.add(tmp.get(0));
+					added.add(tmp.get(1));
+				}
 				break;
 
 			case COPY:
@@ -274,8 +309,15 @@ public List<DiffEntry> compute(ProgressMonitor pm) throws IOException {
 
 			if (pm == null)
 				pm = NullProgressMonitor.INSTANCE;
-			findExactRenames(pm);
-			findContentRenames(pm);
+			ObjectReader reader = repo.newObjectReader();
+			try {
+				breakModifies(reader, pm);
+				findExactRenames(pm);
+				findContentRenames(reader, pm);
+				rejoinModifies(pm);
+			} finally {
+				reader.release();
+			}
 
 			entries.addAll(added);
 			added = null;
@@ -288,7 +330,83 @@ public List<DiffEntry> compute(ProgressMonitor pm) throws IOException {
 		return Collections.unmodifiableList(entries);
 	}
 
-	private void findContentRenames(ProgressMonitor pm) throws IOException {
+	private void breakModifies(ObjectReader reader, ProgressMonitor pm)
+			throws IOException {
+		if (breakScore <= 0)
+			return;
+
+		ArrayList<DiffEntry> newEntries = new ArrayList<DiffEntry>(entries.size());
+
+		pm.beginTask(JGitText.get().renamesBreakingModifies, entries.size());
+
+		for (int i = 0; i < entries.size(); i++) {
+			DiffEntry e = entries.get(i);
+			if (e.getChangeType() == ChangeType.MODIFY) {
+				int score = calculateModifyScore(reader, e);
+				if (score < breakScore) {
+					List<DiffEntry> tmp = DiffEntry.breakModify(e);
+					DiffEntry del = tmp.get(0);
+					del.score = score;
+					deleted.add(del);
+					added.add(tmp.get(1));
+				} else {
+					newEntries.add(e);
+				}
+			} else {
+				newEntries.add(e);
+			}
+			pm.update(1);
+		}
+
+		entries = newEntries;
+	}
+
+	private void rejoinModifies(ProgressMonitor pm) {
+		HashMap<String, DiffEntry> nameMap = new HashMap<String, DiffEntry>();
+		ArrayList<DiffEntry> newAdded = new ArrayList<DiffEntry>(added.size());
+
+		pm.beginTask(JGitText.get().renamesRejoiningModifies, added.size()
+				+ deleted.size());
+
+		for (DiffEntry src : deleted) {
+			nameMap.put(src.oldPath, src);
+			pm.update(1);
+		}
+
+		for (DiffEntry dst : added) {
+			DiffEntry src = nameMap.remove(dst.newPath);
+			if (src != null) {
+				if (sameType(src.oldMode, dst.newMode)) {
+					entries.add(DiffEntry.pair(ChangeType.MODIFY, src, dst,
+							src.score));
+				} else {
+					nameMap.put(src.oldPath, src);
+					newAdded.add(dst);
+				}
+			} else {
+				newAdded.add(dst);
+			}
+			pm.update(1);
+		}
+
+		added = newAdded;
+		deleted = new ArrayList<DiffEntry>(nameMap.values());
+	}
+
+	private int calculateModifyScore(ObjectReader reader, DiffEntry d)
+			throws IOException {
+		SimilarityIndex src = new SimilarityIndex();
+		src.hash(reader.open(d.oldId.toObjectId(), Constants.OBJ_BLOB));
+		src.sort();
+
+		SimilarityIndex dst = new SimilarityIndex();
+		dst.hash(reader.open(d.newId.toObjectId(), Constants.OBJ_BLOB));
+		dst.sort();
+		return src.score(dst, 100);
+	}
+
+	private void findContentRenames(ObjectReader reader, ProgressMonitor pm)
+			throws IOException {
 		int cnt = Math.max(added.size(), deleted.size());
 		if (cnt == 0)
 			return;
@@ -296,7 +414,7 @@ private void findContentRenames(ProgressMonitor pm) throws IOException {
 		if (getRenameLimit() == 0 || cnt <= getRenameLimit()) {
 			SimilarityRenameDetector d;
 
-			d = new SimilarityRenameDetector(repo, deleted, added);
+			d = new SimilarityRenameDetector(reader, deleted, added);
 			d.setRenameScore(getRenameScore());
 			d.compute(pm);
 			deleted = d.getLeftOverSources();
@@ -391,10 +509,10 @@ private void findExactRenames(ProgressMonitor pm) {
 				long[] matrix = new long[dels.size() * adds.size()];
 				int mNext = 0;
 				for (int delIdx = 0; delIdx < dels.size(); delIdx++) {
-					String deletedName = dels.get(delIdx).oldName;
+					String deletedName = dels.get(delIdx).oldPath;
 
 					for (int addIdx = 0; addIdx < adds.size(); addIdx++) {
-						String addedName = adds.get(addIdx).newName;
+						String addedName = adds.get(addIdx).newPath;
 
 						int score = SimilarityRenameDetector.nameScore(addedName, deletedName);
 						matrix[mNext] = SimilarityRenameDetector.encode(score, delIdx, addIdx);
@@ -507,7 +625,7 @@ private HashMap<AbbreviatedObjectId, Object> populateMap(
 	}
 
 	private static String path(DiffEntry de) {
-		return de.changeType == ChangeType.DELETE ? de.oldName : de.newName;
+		return de.changeType == ChangeType.DELETE ? de.oldPath : de.newPath;
 	}
 
 	private static FileMode mode(DiffEntry de) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java
index b460d49..39bcebb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java
@@ -43,9 +43,14 @@
 
 package org.eclipse.jgit.diff;
 
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.Arrays;
 
+import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectStream;
 
 /**
  * Index structure of lines/blocks in one file.
@@ -107,10 +112,20 @@ void setFileSize(long size) {
 		fileSize = size;
 	}
 
-	void hash(ObjectLoader obj) {
-		byte[] raw = obj.getCachedBytes();
-		setFileSize(raw.length);
-		hash(raw, 0, raw.length);
+	void hash(ObjectLoader obj) throws MissingObjectException, IOException {
+		if (obj.isLarge()) {
+			ObjectStream in = obj.openStream();
+			try {
+				setFileSize(in.getSize());
+				hash(in, fileSize);
+			} finally {
+				in.close();
+			}
+		} else {
+			byte[] raw = obj.getCachedBytes();
+			setFileSize(raw.length);
+			hash(raw, 0, raw.length);
+		}
 	}
 
 	void hash(byte[] raw, int ptr, final int end) {
@@ -129,6 +144,35 @@ void hash(byte[] raw, int ptr, final int end) {
 		}
 	}
 
+	void hash(InputStream in, long remaining) throws IOException {
+		byte[] buf = new byte[4096];
+		int ptr = 0;
+		int cnt = 0;
+
+		while (0 < remaining) {
+			int hash = 5381;
+
+			// Hash one line, or one block, whichever occurs first.
+			int n = 0;
+			do {
+				if (ptr == cnt) {
+					ptr = 0;
+					cnt = in.read(buf, 0, buf.length);
+					if (cnt <= 0)
+						throw new EOFException();
+				}
+
+				n++;
+				int c = buf[ptr++] & 0xff;
+				if (c == '\n')
+					break;
+				hash = (hash << 5) ^ c;
+			} while (n < 64 && n < remaining);
+			add(hash, n);
+			remaining -= n;
+		}
+	}
+
 	/**
 	 * Sort the internal table so it can be used for efficient scoring.
 	 * <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
index e2115f0..643ac01 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
@@ -50,11 +50,12 @@
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.lib.Repository;
 
 class SimilarityRenameDetector {
 	/**
@@ -71,7 +72,7 @@ class SimilarityRenameDetector {
 
 	private static final int SCORE_SHIFT = 2 * BITS_PER_INDEX;
 
-	private final Repository repo;
+	private ObjectReader reader;
 
 	/**
 	 * All sources to consider for copies or renames.
@@ -111,9 +112,9 @@ class SimilarityRenameDetector {
 
 	private List<DiffEntry> out;
 
-	SimilarityRenameDetector(Repository repo, List<DiffEntry> srcs,
+	SimilarityRenameDetector(ObjectReader reader, List<DiffEntry> srcs,
 			List<DiffEntry> dsts) {
-		this.repo = repo;
+		this.reader = reader;
 		this.srcs = srcs;
 		this.dsts = dsts;
 	}
@@ -265,7 +266,7 @@ private int buildMatrix(ProgressMonitor pm) throws IOException {
 				// nameScore returns a value between 0 and 100, but we want it
 				// to be in the same range as the content score. This allows it
 				// to be dropped into the pretty formula for the final score.
-				int nameScore = nameScore(srcEnt.oldName, dstEnt.newName) * 100;
+				int nameScore = nameScore(srcEnt.oldPath, dstEnt.newPath) * 100;
 
 				int score = (contentScore * 99 + nameScore * 1) / 10000;
 
@@ -336,13 +337,13 @@ static int nameScore(String a, String b) {
 
 	private SimilarityIndex hash(ObjectId objectId) throws IOException {
 		SimilarityIndex r = new SimilarityIndex();
-		r.hash(repo.openObject(objectId));
+		r.hash(reader.open(objectId));
 		r.sort();
 		return r;
 	}
 
 	private long size(ObjectId objectId) throws IOException {
-		return repo.openObject(objectId).getSize();
+		return reader.getObjectSize(objectId, Constants.OBJ_BLOB);
 	}
 
 	private static int score(long value) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index 42fea48..60238c3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -64,10 +64,10 @@
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.UnmergedPathException;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.LockFile;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectWriter;
-import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.storage.file.LockFile;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.MutableInteger;
 import org.eclipse.jgit.util.NB;
@@ -130,7 +130,7 @@ static int cmp(final byte[] aPath, final int aLen, final byte[] bPath,
 	 *         memory).
 	 */
 	public static DirCache newInCore() {
-		return new DirCache(null);
+		return new DirCache(null, null);
 	}
 
 	/**
@@ -142,6 +142,9 @@ public static DirCache newInCore() {
 	 *
 	 * @param indexLocation
 	 *            location of the index file on disk.
+	 * @param fs
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
 	 * @return a cache representing the contents of the specified index file (if
 	 *         it exists) or an empty cache if the file does not exist.
 	 * @throws IOException
@@ -150,45 +153,26 @@ public static DirCache newInCore() {
 	 *             the index file is using a format or extension that this
 	 *             library does not support.
 	 */
-	public static DirCache read(final File indexLocation)
+	public static DirCache read(final File indexLocation, final FS fs)
 			throws CorruptObjectException, IOException {
-		final DirCache c = new DirCache(indexLocation);
+		final DirCache c = new DirCache(indexLocation, fs);
 		c.read();
 		return c;
 	}
 
 	/**
-	 * Create a new in-core index representation and read an index from disk.
-	 * <p>
-	 * The new index will be read before it is returned to the caller. Read
-	 * failures are reported as exceptions and therefore prevent the method from
-	 * returning a partially populated index.
-	 *
-	 * @param db
-	 *            repository the caller wants to read the default index of.
-	 * @return a cache representing the contents of the specified index file (if
-	 *         it exists) or an empty cache if the file does not exist.
-	 * @throws IOException
-	 *             the index file is present but could not be read.
-	 * @throws CorruptObjectException
-	 *             the index file is using a format or extension that this
-	 *             library does not support.
-	 */
-	public static DirCache read(final Repository db)
-			throws CorruptObjectException, IOException {
-		return read(new File(db.getDirectory(), "index"));
-	}
-
-	/**
 	 * Create a new in-core index representation, lock it, and read from disk.
 	 * <p>
 	 * The new index will be locked and then read before it is returned to the
 	 * caller. Read failures are reported as exceptions and therefore prevent
-	 * the method from returning a partially populated index.  On read failure,
+	 * the method from returning a partially populated index. On read failure,
 	 * the lock is released.
 	 *
 	 * @param indexLocation
 	 *            location of the index file on disk.
+	 * @param fs
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
 	 * @return a cache representing the contents of the specified index file (if
 	 *         it exists) or an empty cache if the file does not exist.
 	 * @throws IOException
@@ -198,9 +182,9 @@ public static DirCache read(final Repository db)
 	 *             the index file is using a format or extension that this
 	 *             library does not support.
 	 */
-	public static DirCache lock(final File indexLocation)
+	public static DirCache lock(final File indexLocation, final FS fs)
 			throws CorruptObjectException, IOException {
-		final DirCache c = new DirCache(indexLocation);
+		final DirCache c = new DirCache(indexLocation, fs);
 		if (!c.lock())
 			throw new IOException(MessageFormat.format(JGitText.get().cannotLock, indexLocation));
 
@@ -220,29 +204,6 @@ public static DirCache lock(final File indexLocation)
 		return c;
 	}
 
-	/**
-	 * Create a new in-core index representation, lock it, and read from disk.
-	 * <p>
-	 * The new index will be locked and then read before it is returned to the
-	 * caller. Read failures are reported as exceptions and therefore prevent
-	 * the method from returning a partially populated index.
-	 *
-	 * @param db
-	 *            repository the caller wants to read the default index of.
-	 * @return a cache representing the contents of the specified index file (if
-	 *         it exists) or an empty cache if the file does not exist.
-	 * @throws IOException
-	 *             the index file is present but could not be read, or the lock
-	 *             could not be obtained.
-	 * @throws CorruptObjectException
-	 *             the index file is using a format or extension that this
-	 *             library does not support.
-	 */
-	public static DirCache lock(final Repository db)
-			throws CorruptObjectException, IOException {
-		return lock(new File(db.getDirectory(), "index"));
-	}
-
 	/** Location of the current version of the index file. */
 	private final File liveFile;
 
@@ -261,6 +222,9 @@ public static DirCache lock(final Repository db)
 	/** Our active lock (if we hold it); null if we don't have it locked. */
 	private LockFile myLock;
 
+	/** file system abstraction **/
+	private final FS fs;
+
 	/**
 	 * Create a new in-core index representation.
 	 * <p>
@@ -269,9 +233,13 @@ public static DirCache lock(final Repository db)
 	 *
 	 * @param indexLocation
 	 *            location of the index file on disk.
+	 * @param fs
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
 	 */
-	public DirCache(final File indexLocation) {
+	public DirCache(final File indexLocation, final FS fs) {
 		liveFile = indexLocation;
+		this.fs = fs;
 		clear();
 	}
 
@@ -475,7 +443,7 @@ private static boolean is_DIRC(final byte[] hdr) {
 	public boolean lock() throws IOException {
 		if (liveFile == null)
 			throw new IOException(JGitText.get().dirCacheDoesNotHaveABackingFile);
-		final LockFile tmp = new LockFile(liveFile);
+		final LockFile tmp = new LockFile(liveFile, fs);
 		if (tmp.lock()) {
 			tmp.setNeedStatInformation(true);
 			myLock = tmp;
@@ -768,7 +736,9 @@ public DirCacheTree getCacheTree(final boolean build) {
 	 * Write all index trees to the object store, returning the root tree.
 	 *
 	 * @param ow
-	 *            the writer to use when serializing to the store.
+	 *            the writer to use when serializing to the store. The caller is
+	 *            responsible for flushing the inserter before trying to use the
+	 *            returned tree identity.
 	 * @return identity for the root tree.
 	 * @throws UnmergedPathException
 	 *             one or more paths contain higher-order stages (stage > 0),
@@ -779,7 +749,7 @@ public DirCacheTree getCacheTree(final boolean build) {
 	 * @throws IOException
 	 *             an unexpected error occurred writing to the object store.
 	 */
-	public ObjectId writeTree(final ObjectWriter ow)
+	public ObjectId writeTree(final ObjectInserter ow)
 			throws UnmergedPathException, IOException {
 		return getCacheTree(true).writeTree(sortedEntries, 0, 0, ow);
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
index 181192d..1eb95c4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
@@ -50,7 +50,7 @@
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
 
 /**
@@ -106,7 +106,7 @@ public DirCacheBuildIterator(final DirCacheBuilder dcb) {
 	}
 
 	@Override
-	public AbstractTreeIterator createSubtreeIterator(final Repository repo)
+	public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader)
 			throws IncorrectObjectTypeException, IOException {
 		if (currentSubtree == null)
 			throw new IncorrectObjectTypeException(getEntryObjectId(),
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
index e6b6197..5665002 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
@@ -50,8 +50,7 @@
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
 import org.eclipse.jgit.treewalk.TreeWalk;
@@ -149,11 +148,12 @@ public void keep(final int pos, int cnt) {
 	 *            as necessary.
 	 * @param stage
 	 *            stage of the entries when adding them.
-	 * @param db
-	 *            repository the tree(s) will be read from during recursive
+	 * @param reader
+	 *            reader the tree(s) will be read from during recursive
 	 *            traversal. This must be the same repository that the resulting
 	 *            DirCache would be written out to (or used in) otherwise the
 	 *            caller is simply asking for deferred MissingObjectExceptions.
+	 *            Caller is responsible for releasing this reader when done.
 	 * @param tree
 	 *            the tree to recursively add. This tree's contents will appear
 	 *            under <code>pathPrefix</code>. The ObjectId must be that of a
@@ -163,16 +163,11 @@ public void keep(final int pos, int cnt) {
 	 *             a tree cannot be read to iterate through its entries.
 	 */
 	public void addTree(final byte[] pathPrefix, final int stage,
-			final Repository db, final AnyObjectId tree) throws IOException {
-		final TreeWalk tw = new TreeWalk(db);
+			final ObjectReader reader, final AnyObjectId tree) throws IOException {
+		final TreeWalk tw = new TreeWalk(reader);
 		tw.reset();
-		final WindowCursor curs = new WindowCursor();
-		try {
-			tw.addTree(new CanonicalTreeParser(pathPrefix, db, tree
-					.toObjectId(), curs));
-		} finally {
-			curs.release();
-		}
+		tw.addTree(new CanonicalTreeParser(pathPrefix, reader, tree
+				.toObjectId()));
 		tw.setRecursive(true);
 		if (tw.next()) {
 			final DirCacheEntry newEntry = toEntry(stage, tw);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
index 7cb1472..909729d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -2,6 +2,7 @@
  * Copyright (C) 2008-2009, Google Inc.
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
+ * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -282,7 +283,7 @@ void write(final OutputStream os) throws IOException {
 	 *            nanoseconds component of the index's last modified time.
 	 * @return true if extra careful checks should be used.
 	 */
-	final boolean mightBeRacilyClean(final int smudge_s, final int smudge_ns) {
+	public final boolean mightBeRacilyClean(final int smudge_s, final int smudge_ns) {
 		// If the index has a modification time then it came from disk
 		// and was not generated from scratch in memory. In such cases
 		// the entry is 'racily clean' if the entry's cached modification
@@ -292,8 +293,6 @@ final boolean mightBeRacilyClean(final int smudge_s, final int smudge_ns) {
 		//
 		final int base = infoOffset + P_MTIME;
 		final int mtime = NB.decodeInt32(info, base);
-		if (smudge_s < mtime)
-			return true;
 		if (smudge_s == mtime)
 			return smudge_ns <= NB.decodeInt32(info, base + 4);
 		return false;
@@ -306,21 +305,30 @@ final boolean mightBeRacilyClean(final int smudge_s, final int smudge_ns) {
 	 * match the file in the working directory. Later git will be forced to
 	 * compare the file content to ensure the file matches the working tree.
 	 */
-	final void smudgeRacilyClean() {
-		// We don't use the same approach as C Git to smudge the entry,
-		// as we cannot compare the working tree file to our SHA-1 and
-		// thus cannot use the "size to 0" trick without accidentally
-		// thinking a zero length file is clean.
-		//
-		// Instead we force the mtime to the largest possible value, so
-		// it is certainly after the index's own modification time and
-		// on a future read will cause mightBeRacilyClean to say "yes!".
-		// It is also unlikely to match with the working tree file.
-		//
-		// I'll see you again before Jan 19, 2038, 03:14:07 AM GMT.
-		//
-		final int base = infoOffset + P_MTIME;
-		Arrays.fill(info, base, base + 8, (byte) 127);
+	public final void smudgeRacilyClean() {
+		// To mark an entry racily clean we set its length to 0 (like native git
+		// does). Entries which are not racily clean and have zero length can be
+		// distinguished from racily clean entries by checking P_OBJECTID
+		// against the SHA1 of empty content. When length is 0 and P_OBJECTID is
+		// different from SHA1 of empty content we know the entry is marked
+		// racily clean
+		final int base = infoOffset + P_SIZE;
+		Arrays.fill(info, base, base + 4, (byte) 0);
+	}
+
+	/**
+	 * Check whether this entry has been smudged or not
+	 * <p>
+	 * If a blob has length 0 we know his id see {@link Constants#EMPTY_BLOB_ID}. If an entry
+	 * has length 0 and an ID different from the one for empty blob we know this
+	 * entry was smudged.
+	 *
+	 * @return <code>true</code> if the entry is smudged, <code>false</code>
+	 *         otherwise
+	 */
+	public final boolean isSmudged() {
+		final int base = infoOffset + P_OBJECTID;
+		return (getLength() == 0) && (Constants.EMPTY_BLOB_ID.compareTo(info, base) != 0);
 	}
 
 	final byte[] idBuffer() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
index 9c47187..b4e2d2c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
@@ -50,7 +50,7 @@
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
 
@@ -125,7 +125,7 @@ public DirCacheIterator(final DirCache dc) {
 	}
 
 	@Override
-	public AbstractTreeIterator createSubtreeIterator(final Repository repo)
+	public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader)
 			throws IncorrectObjectTypeException, IOException {
 		if (currentSubtree == null)
 			throw new IncorrectObjectTypeException(getEntryObjectId(),
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
index 144b1a6..e04b797 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
@@ -57,7 +57,7 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.util.MutableInteger;
 import org.eclipse.jgit.util.RawParseUtils;
 
@@ -311,7 +311,7 @@ public String getPathString() {
 	 *             an unexpected error occurred writing to the object store.
 	 */
 	ObjectId writeTree(final DirCacheEntry[] cache, int cIdx,
-			final int pathOffset, final ObjectWriter ow)
+			final int pathOffset, final ObjectInserter ow)
 			throws UnmergedPathException, IOException {
 		if (id == null) {
 			final int endIdx = cIdx + entrySpan;
@@ -346,13 +346,13 @@ ObjectId writeTree(final DirCacheEntry[] cache, int cIdx,
 				entryIdx++;
 			}
 
-			id = ow.writeCanonicalTree(out.toByteArray());
+			id = ow.insert(Constants.OBJ_TREE, out.toByteArray());
 		}
 		return id;
 	}
 
 	private int computeSize(final DirCacheEntry[] cache, int cIdx,
-			final int pathOffset, final ObjectWriter ow)
+			final int pathOffset, final ObjectInserter ow)
 			throws UnmergedPathException, IOException {
 		final int endIdx = cIdx + entrySpan;
 		int childIdx = 0;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java
similarity index 73%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java
index e43c33a..d897c51 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,20 +41,27 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.errors;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
+import org.eclipse.jgit.lib.ObjectId;
 
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
+/** An object is too big to load into memory as a single byte array. */
+public class LargeObjectException extends RuntimeException {
+	private static final long serialVersionUID = 1L;
+
+	/** Create a large object exception, where the object isn't known. */
+	public LargeObjectException() {
+		// Do nothing.
 	}
 
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
+	/**
+	 * Create a large object exception, naming the object that is too big.
+	 *
+	 * @param id
+	 *            identity of the object that is too big to be loaded as a byte
+	 *            array in this JVM.
+	 */
+	public LargeObjectException(ObjectId id) {
+		super(id.name());
 	}
-
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoWorkTreeException.java
similarity index 78%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/errors/NoWorkTreeException.java
index e43c33a..f2980ef 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoWorkTreeException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,20 +41,19 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.errors;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.lib.Repository;
 
 /**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
+ * Indicates a {@link Repository} has no working directory, and is thus bare.
  */
-public class RepositoryAdapter implements RepositoryListener {
+public class NoWorkTreeException extends IllegalStateException {
+	private static final long serialVersionUID = 1L;
 
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
+	/** Creates an exception indicating there is no work tree for a repository. */
+	public NoWorkTreeException() {
+		super(JGitText.get().bareRepositoryNoWorkdirAndIndex);
 	}
-
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
-	}
-
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java
similarity index 77%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java
index e43c33a..e9e3f4d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,20 +41,21 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.errors;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
+import org.eclipse.jgit.storage.pack.ObjectToPack;
 
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
+/** A previously selected representation is no longer available. */
+public class StoredObjectRepresentationNotAvailableException extends Exception {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Construct an error for an object.
+	 *
+	 * @param otp
+	 *            the object whose current representation is no longer present.
+	 */
+	public StoredObjectRepresentationNotAvailableException(ObjectToPack otp) {
+		// Do nothing.
 	}
-
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
-	}
-
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java
similarity index 81%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java
index e43c33a..79598ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,20 +41,17 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.events;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
-
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
+/** Describes a change to one or more keys in the configuration. */
+public class ConfigChangedEvent extends RepositoryEvent<ConfigChangedListener> {
+	@Override
+	public Class<ConfigChangedListener> getListenerType() {
+		return ConfigChangedListener.class;
 	}
 
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
+	@Override
+	public void dispatch(ConfigChangedListener listener) {
+		listener.onConfigChanged(this);
 	}
-
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java
similarity index 83%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java
index e43c33a..322cf7f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java
@@ -41,20 +41,15 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.events;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
-
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
-	}
-
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
-	}
-
+/** Receives {@link ConfigChangedEvent}s. */
+public interface ConfigChangedListener extends RepositoryListener {
+	/**
+	 * Invoked when any change is made to the configuration.
+	 *
+	 * @param event
+	 *            information about the changes.
+	 */
+	void onConfigChanged(ConfigChangedEvent event);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java
similarity index 81%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java
index e43c33a..a54288e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,20 +41,17 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.events;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
-
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
+/** Describes a change to one or more paths in the index file. */
+public class IndexChangedEvent extends RepositoryEvent<IndexChangedListener> {
+	@Override
+	public Class<IndexChangedListener> getListenerType() {
+		return IndexChangedListener.class;
 	}
 
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
+	@Override
+	public void dispatch(IndexChangedListener listener) {
+		listener.onIndexChanged(this);
 	}
-
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.java
similarity index 83%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.java
index e43c33a..d41ef74 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.java
@@ -41,20 +41,15 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.events;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
-
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
-	}
-
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
-	}
-
+/** Receives {@link IndexChangedEvent}s. */
+public interface IndexChangedListener extends RepositoryListener {
+	/**
+	 * Invoked when any change is made to the index.
+	 *
+	 * @param event
+	 *            information about the changes.
+	 */
+	void onIndexChanged(IndexChangedEvent event);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java
similarity index 75%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java
index 495049c..ef90b22 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,30 +41,31 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.events;
 
-/**
- * This class passes information about changed refs to a
- * {@link RepositoryListener}
- *
- * Currently only a reference to the repository is passed.
- */
-public class RepositoryChangedEvent {
-	private final Repository repository;
+/** Tracks a previously registered {@link RepositoryListener}. */
+public class ListenerHandle {
+	private final ListenerList parent;
 
-	RepositoryChangedEvent(final Repository repository) {
-		this.repository = repository;
+	final Class<? extends RepositoryListener> type;
+
+	final RepositoryListener listener;
+
+	ListenerHandle(ListenerList parent,
+			Class<? extends RepositoryListener> type,
+			RepositoryListener listener) {
+		this.parent = parent;
+		this.type = type;
+		this.listener = listener;
 	}
 
-	/**
-	 * @return the repository that was changed
-	 */
-	public Repository getRepository() {
-		return repository;
+	/** Remove the listener and stop receiving events. */
+	public void remove() {
+		parent.remove(this);
 	}
 
 	@Override
 	public String toString() {
-		return "RepositoryChangedEvent[" + repository + "]";
+		return type.getSimpleName() + "[" + listener + "]";
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
new file mode 100644
index 0000000..6ac4b0f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.events;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/** Manages a thread-safe list of {@link RepositoryListener}s. */
+public class ListenerList {
+	private final ConcurrentMap<Class<? extends RepositoryListener>, CopyOnWriteArrayList<ListenerHandle>> lists = new ConcurrentHashMap<Class<? extends RepositoryListener>, CopyOnWriteArrayList<ListenerHandle>>();
+
+	/**
+	 * Register an IndexChangedListener.
+	 *
+	 * @param listener
+	 *            the listener implementation.
+	 * @return handle to later remove the listener.
+	 */
+	public ListenerHandle addIndexChangedListener(IndexChangedListener listener) {
+		return addListener(IndexChangedListener.class, listener);
+	}
+
+	/**
+	 * Register a RefsChangedListener.
+	 *
+	 * @param listener
+	 *            the listener implementation.
+	 * @return handle to later remove the listener.
+	 */
+	public ListenerHandle addRefsChangedListener(RefsChangedListener listener) {
+		return addListener(RefsChangedListener.class, listener);
+	}
+
+	/**
+	 * Register a ConfigChangedListener.
+	 *
+	 * @param listener
+	 *            the listener implementation.
+	 * @return handle to later remove the listener.
+	 */
+	public ListenerHandle addConfigChangedListener(
+			ConfigChangedListener listener) {
+		return addListener(ConfigChangedListener.class, listener);
+	}
+
+	/**
+	 * Add a listener to the list.
+	 *
+	 * @param <T>
+	 *            the type of listener being registered.
+	 * @param type
+	 *            type of listener being registered.
+	 * @param listener
+	 *            the listener instance.
+	 * @return a handle to later remove the registration, if desired.
+	 */
+	public <T extends RepositoryListener> ListenerHandle addListener(
+			Class<T> type, T listener) {
+		ListenerHandle handle = new ListenerHandle(this, type, listener);
+		add(handle);
+		return handle;
+	}
+
+	/**
+	 * Dispatch an event to all interested listeners.
+	 * <p>
+	 * Listeners are selected by the type of listener the event delivers to.
+	 *
+	 * @param event
+	 *            the event to deliver.
+	 */
+	@SuppressWarnings("unchecked")
+	public void dispatch(RepositoryEvent event) {
+		List<ListenerHandle> list = lists.get(event.getListenerType());
+		if (list != null) {
+			for (ListenerHandle handle : list)
+				event.dispatch(handle.listener);
+		}
+	}
+
+	private void add(ListenerHandle handle) {
+		List<ListenerHandle> list = lists.get(handle.type);
+		if (list == null) {
+			CopyOnWriteArrayList<ListenerHandle> newList;
+
+			newList = new CopyOnWriteArrayList<ListenerHandle>();
+			list = lists.putIfAbsent(handle.type, newList);
+			if (list == null)
+				list = newList;
+		}
+		list.add(handle);
+	}
+
+	void remove(ListenerHandle handle) {
+		List<ListenerHandle> list = lists.get(handle.type);
+		if (list != null)
+			list.remove(handle);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java
similarity index 81%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java
index e43c33a..36af3f8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,20 +41,17 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.events;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
-
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
+/** Describes a change to one or more references of a repository. */
+public class RefsChangedEvent extends RepositoryEvent<RefsChangedListener> {
+	@Override
+	public Class<RefsChangedListener> getListenerType() {
+		return RefsChangedListener.class;
 	}
 
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
+	@Override
+	public void dispatch(RefsChangedListener listener) {
+		listener.onRefsChanged(this);
 	}
-
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.java
similarity index 83%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.java
index e43c33a..9c0f4ed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.java
@@ -41,20 +41,15 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.events;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
-
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
-	}
-
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
-	}
-
+/** Receives {@link RefsChangedEvent}s. */
+public interface RefsChangedListener extends RepositoryListener {
+	/**
+	 * Invoked when any reference changes.
+	 *
+	 * @param event
+	 *            information about the changes.
+	 */
+	void onRefsChanged(RefsChangedEvent event);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java
similarity index 64%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java
index 495049c..ba1c81d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2010, Google Inc.
  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  * and other copyright owners as documented in the project's IP log.
  *
@@ -41,30 +42,54 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.events;
+
+import org.eclipse.jgit.lib.Repository;
 
 /**
- * This class passes information about changed refs to a
- * {@link RepositoryListener}
+ * Describes a modification made to a repository.
  *
- * Currently only a reference to the repository is passed.
+ * @param <T>
+ *            type of listener this event dispatches to.
  */
-public class RepositoryChangedEvent {
-	private final Repository repository;
-
-	RepositoryChangedEvent(final Repository repository) {
-		this.repository = repository;
-	}
+public abstract class RepositoryEvent<T extends RepositoryListener> {
+	private Repository repository;
 
 	/**
-	 * @return the repository that was changed
+	 * Set the repository this event occurred on.
+	 * <p>
+	 * This method should only be invoked once on each event object, and is
+	 * automatically set by {@link Repository#fireEvent(RepositoryEvent)}.
+	 *
+	 * @param r
+	 *            the repository.
 	 */
+	public void setRepository(Repository r) {
+		if (repository == null)
+			repository = r;
+	}
+
+	/** @return the repository that was changed. */
 	public Repository getRepository() {
 		return repository;
 	}
 
+	/** @return type of listener this event dispatches to. */
+	public abstract Class<T> getListenerType();
+
+	/**
+	 * Dispatch this event to the given listener.
+	 *
+	 * @param listener
+	 *            listener that wants this event.
+	 */
+	public abstract void dispatch(T listener);
+
 	@Override
 	public String toString() {
-		return "RepositoryChangedEvent[" + repository + "]";
+		String type = getClass().getSimpleName();
+		if (repository == null)
+			return type;
+		return type + "[" + repository + "]";
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryListener.java
similarity index 80%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryListener.java
index e43c33a..4f951e5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,20 +41,9 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.events;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
-
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
-	}
-
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
-	}
-
+/** A listener can register for event delivery. */
+public interface RepositoryListener {
+	// Empty marker interface; see extensions for actual methods.
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
index f29fa1e..575b9d6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
@@ -43,194 +43,104 @@
 package org.eclipse.jgit.ignore;
 
 import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Constants;
 
 /**
  * Represents a bundle of ignore rules inherited from a base directory.
- * Each IgnoreNode corresponds to one directory. Most IgnoreNodes will have
- * at most one source of ignore information -- its .gitignore file.
- * <br><br>
- * At the root of the repository, there may be an additional source of
- * ignore information (the exclude file)
- * <br><br>
- * It is recommended that implementers call the {@link #isIgnored(String)} method
- * rather than try to use the rules manually. The method will handle rule priority
- * automatically.
  *
+ * This class is not thread safe, it maintains state about the last match.
  */
 public class IgnoreNode {
-	//The base directory will be used to find the .gitignore file
-	private File baseDir;
-	//Only used for root node.
-	private File secondaryFile;
-	private ArrayList<IgnoreRule> rules;
-	//Indicates whether a match was made. Necessary to terminate early when a negation is encountered
-	private boolean matched;
-	//Indicates whether a match was made. Necessary to terminate early when a negation is encountered
-	private long lastModified;
+	/** Result from {@link IgnoreNode#isIgnored(String, boolean)}. */
+	public static enum MatchResult {
+		/** The file is not ignored, due to a rule saying its not ignored. */
+		NOT_IGNORED,
+
+		/** The file is ignored due to a rule in this node. */
+		IGNORED,
+
+		/** The ignore status is unknown, check inherited rules. */
+		CHECK_PARENT;
+	}
+
+	/** The rules that have been parsed into this node. */
+	private final List<IgnoreRule> rules;
+
+	/** Create an empty ignore node with no rules. */
+	public IgnoreNode() {
+		rules = new ArrayList<IgnoreRule>();
+	}
 
 	/**
-	 * Create a new ignore node based on the given directory. The node's
-	 * ignore file will be the .gitignore file in the directory (if any)
-	 * Rules contained within this node will only be applied to files
-	 * which are descendants of this directory.
+	 * Create an ignore node with given rules.
 	 *
-	 * @param baseDir
-	 * 			  base directory of this ignore node
-	 */
-	public IgnoreNode(File baseDir) {
-		this.baseDir = baseDir;
-		rules = new ArrayList<IgnoreRule>();
-		secondaryFile = null;
-		lastModified = 0l;
+	 * @param rules
+	 *            list of rules.
+	 **/
+	public IgnoreNode(List<IgnoreRule> rules) {
+		this.rules = rules;
 	}
 
 	/**
 	 * Parse files according to gitignore standards.
 	 *
+	 * @param in
+	 *            input stream holding the standard ignore format. The caller is
+	 *            responsible for closing the stream.
 	 * @throws IOException
-	 * 			  Error thrown when reading an ignore file.
+	 *             Error thrown when reading an ignore file.
 	 */
-	private void parse() throws IOException {
-		if (secondaryFile != null && secondaryFile.exists())
-			parse(secondaryFile);
-
-		parse(new File(baseDir.getAbsolutePath(), ".gitignore"));
-	}
-
-	private void parse(File targetFile) throws IOException {
-		if (!targetFile.exists())
-			return;
-
-		BufferedReader br = new BufferedReader(new FileReader(targetFile));
+	public void parse(InputStream in) throws IOException {
+		BufferedReader br = asReader(in);
 		String txt;
-		try {
-			while ((txt = br.readLine()) != null) {
-				txt = txt.trim();
-				if (txt.length() > 0 && !txt.startsWith("#"))
-					rules.add(new IgnoreRule(txt));
-			}
-		} finally {
-			br.close();
+		while ((txt = br.readLine()) != null) {
+			txt = txt.trim();
+			if (txt.length() > 0 && !txt.startsWith("#"))
+				rules.add(new IgnoreRule(txt));
 		}
 	}
 
-	/**
-	 * @return
-	 * 			  Base directory to which these rules apply, absolute path
-	 */
-	public File getBaseDir() {
-		return baseDir;
+	private static BufferedReader asReader(InputStream in) {
+		return new BufferedReader(new InputStreamReader(in, Constants.CHARSET));
 	}
 
-
-	/**
-	 *
-	 * @return
-	 * 			  List of all ignore rules held by this node
-	 */
-	public ArrayList<IgnoreRule> getRules() {
-		return rules;
+	/** @return list of all ignore rules held by this node. */
+	public List<IgnoreRule> getRules() {
+		return Collections.unmodifiableList(rules);
 	}
 
-
 	/**
+	 * Determine if an entry path matches an ignore rule.
 	 *
-	 * Returns whether or not a target is matched as being ignored by
-	 * any patterns in this directory.
-	 * <br>
-	 * Will return false if the file is not a descendant of this directory.
-	 * <br>
-	 *
-	 * @param target
-	 *			  Absolute path to the file. This makes stripping common path elements easier.
-	 * @return
-	 * 			  true if target is ignored, false if the target is explicitly not
-	 * 			  ignored or if no rules exist for the target.
-	 * @throws IOException
-	 * 			  Failed to parse rules
-	 *
+	 * @param entryPath
+	 *            the path to test. The path must be relative to this ignore
+	 *            node's own repository path, and in repository path format
+	 *            (uses '/' and not '\').
+	 * @param isDirectory
+	 *            true if the target item is a directory.
+	 * @return status of the path.
 	 */
-	public boolean isIgnored(String target) throws IOException {
-		matched = false;
-		File targetFile = new File(target);
-		String tar = baseDir.toURI().relativize(targetFile.toURI()).getPath();
-
-		if (tar.length() == target.length())
-			//target is not a derivative of baseDir, this node has no jurisdiction
-			return false;
-
-		if (rules.isEmpty()) {
-			//Either we haven't parsed yet, or the file is empty.
-			//Empty file should be very fast to parse
-			parse();
-		}
+	public MatchResult isIgnored(String entryPath, boolean isDirectory) {
 		if (rules.isEmpty())
-			return false;
+			return MatchResult.CHECK_PARENT;
 
-		/*
-		 * Boolean matched is necessary because we may have encountered
-		 * a negation ("!/test.c").
-		 */
-
-		int i;
-		//Parse rules in the reverse order that they were read
-		for (i = rules.size() -1; i > -1; i--) {
-			matched = rules.get(i).isMatch(tar, targetFile.isDirectory());
-			if (matched)
-				break;
+		// Parse rules in the reverse order that they were read
+		for (int i = rules.size() - 1; i > -1; i--) {
+			IgnoreRule rule = rules.get(i);
+			if (rule.isMatch(entryPath, isDirectory)) {
+				if (rule.getResult())
+					return MatchResult.IGNORED;
+				else
+					return MatchResult.NOT_IGNORED;
+			}
 		}
-
-		if (i > -1 && rules.get(i) != null)
-			return rules.get(i).getResult();
-
-		return false;
-	}
-
-	/**
-	 * @return
-	 * 			  True if the previous call to isIgnored resulted in a match,
-	 * 			  false otherwise.
-	 */
-	public boolean wasMatched() {
-		return matched;
-	}
-
-	/**
-	 * Adds another file as a source of ignore rules for this file. The
-	 * secondary file will have a lower priority than the first file, and
-	 * the parent directory of this node will be regarded as firstFile.getParent()
-	 *
-	 * @param f
-	 * 			Secondary source of gitignore information for this node
-	 */
-	public void addSecondarySource(File f) {
-		secondaryFile = f;
-	}
-
-	/**
-	 * Clear all rules in this node.
-	 */
-	public void clear() {
-		rules.clear();
-	}
-
-	/**
-	 * @param val
-	 * 			  Set the last modified time of this node.
-	 */
-	public void setLastModified(long val) {
-		lastModified = val;
-	}
-
-	/**
-	 * @return
-	 * 			  Last modified time of this node.
-	 */
-	public long getLastModified() {
-		return lastModified;
+		return MatchResult.CHECK_PARENT;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java
index 982ce06..ac3ec66 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java
@@ -91,10 +91,11 @@ private void setup() {
 			endIndex --;
 			dirOnly = true;
 		}
+		boolean hasSlash = pattern.contains("/");
 
 		pattern = pattern.substring(startIndex, endIndex);
 
-		if (!pattern.contains("/"))
+		if (!hasSlash)
 			nameOnly = true;
 		else if (!pattern.startsWith("/")) {
 			//Contains "/" but does not start with one
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/SimpleIgnoreCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/SimpleIgnoreCache.java
deleted file mode 100644
index be37a9a..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/SimpleIgnoreCache.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2010, Red Hat Inc.
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.eclipse.jgit.ignore;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-import java.util.HashMap;
-import java.util.HashSet;
-
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.treewalk.FileTreeIterator;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.util.FS;
-
-/**
- * A simple ignore cache. Stores ignore information on .gitignore and exclude files.
- * <br><br>
- * The cache can be initialized by calling {@link #initialize()} on a
- * target file.
- *
- * Inspiration from: Ferry Huberts
- */
-public class SimpleIgnoreCache {
-
-	/**
-	 * Map of ignore nodes, indexed by base directory. By convention, the
-	 * base directory string should NOT start or end with a "/". Use
-	 * {@link #relativize(File)} before appending nodes to the ignoreMap
-	 * <br>
-	 * e.g: path/to/directory is a valid String
-	 */
-	private HashMap<String, IgnoreNode> ignoreMap;
-
-	//Repository associated with this cache
-	private Repository repository;
-
-	//Base directory of this cache
-	private URI rootFileURI;
-
-	/**
-	 * Creates a base implementation of an ignore cache. This default implementation
-	 * will search for all .gitignore files in all children of the base directory,
-	 * and grab the exclude file from baseDir/.git/info/exclude.
-	 * <br><br>
-	 * Call {@link #initialize()} to fetch the ignore information relevant
-	 * to a target file.
-	 * @param repository
-	 * 			  Repository to associate this cache with. The cache's base directory will
-	 * 			  be set to this repository's GIT_DIR
-	 *
-	 */
-	public SimpleIgnoreCache(Repository repository) {
-		ignoreMap = new HashMap<String, IgnoreNode>();
-		this.repository = repository;
-		this.rootFileURI = repository.getWorkDir().toURI();
-	}
-
-	/**
-	 * Initializes the ignore map for the target file and all parents.
-	 * This will delete existing ignore information for all folders
-	 * on the partial initialization path. Will only function for files
-	 * that are children of the cache's basePath.
-	 * <br><br>
-	 * Note that this does not initialize the ignore rules. Ignore rules will
-	 * be parsed when needed during a call to {@link #isIgnored(String)}
-	 *
-	 * @throws IOException
-	 *            The tree could not be walked.
-	 */
-	public void initialize() throws IOException {
-		TreeWalk tw = new TreeWalk(repository);
-		tw.reset();
-		tw.addTree(new FileTreeIterator(repository.getWorkDir(), FS.DETECTED));
-		tw.setRecursive(true);
-
-		//Don't waste time trying to add iterators that already exist
-		HashSet<FileTreeIterator> toAdd = new HashSet<FileTreeIterator>();
-		while (tw.next()) {
-			FileTreeIterator t = tw.getTree(0, FileTreeIterator.class);
-			if (t.hasGitIgnore()) {
-				toAdd.add(t);
-				//TODO: Account for and test the removal of .gitignore files
-			}
-		}
-		for (FileTreeIterator t : toAdd)
-			addNodeFromTree(t);
-
-		//The base is special
-		//TODO: Test alternate locations for GIT_DIR
-		readRulesAtBase();
-	}
-
-	/**
-	 * Creates rules for .git/info/exclude and .gitignore to the base node.
-	 * It will overwrite the existing base ignore node. There will always
-	 * be a base ignore node, even if there is no .gitignore file
-	 */
-	private void readRulesAtBase() {
-		//Add .gitignore rules
-		String path =  new File(repository.getWorkDir(), ".gitignore").getAbsolutePath();
-		File f = new File(path);
-		IgnoreNode n = new IgnoreNode(f.getParentFile());
-
-		//Add exclude rules
-		//TODO: Get /info directory without string concat
-		path = new File(repository.getWorkDir(), ".git/info/exclude").getAbsolutePath();
-		f = new File(path);
-		if (f.canRead())
-			n.addSecondarySource(f);
-
-		ignoreMap.put("", n);
-	}
-
-	/**
-	 *	Adds a node located at the FileTreeIterator's root directory.
-	 *	<br>
-	 *	Will check for the presence of a .gitignore using {@link FileTreeIterator#hasGitIgnore()}.
-	 *	If no .gitignore file exists, nothing will be done.
-	 *  <br>
-	 *  Will check the last time of modification using {@link FileTreeIterator#hasGitIgnore()}.
-	 *  If a node already exists and the time stamp has not changed, do nothing.
-	 *	<br>
-	 *  Note: This can be extended later if necessary to AbstractTreeIterator by using
-	 *  byte[] path instead of File directory.
-	 *
-	 * @param t
-	 * 			  AbstractTreeIterator to check for ignore info. The name of the node
-	 * 			  should be .gitignore
-	 */
-	protected void addNodeFromTree(FileTreeIterator t) {
-		IgnoreNode n = ignoreMap.get(relativize(t.getDirectory()));
-		long time = t.getGitIgnoreLastModified();
-		if (n != null) {
-			if (n.getLastModified() == time)
-				//TODO: Test and optimize
-				return;
-		}
-		n = addIgnoreNode(t.getDirectory());
-		n.setLastModified(time);
-	}
-
-	/**
-	 * Maps the directory to an IgnoreNode, but does not initialize
-	 * the IgnoreNode. If a node already exists it will be emptied. Empty nodes
-	 * will be initialized when needed, see {@link #isIgnored(String)}
-	 *
-	 * @param dir
-	 * 			  directory to load rules from
-	 * @return
-	 * 			  true if set successfully, false if directory does not exist
-	 * 			  or if directory does not contain a .gitignore file.
-	 */
-	protected IgnoreNode addIgnoreNode(File dir) {
-		String relativeDir = relativize(dir);
-		IgnoreNode n = ignoreMap.get(relativeDir);
-		if (n != null)
-			n.clear();
-		else {
-			n = new IgnoreNode(dir);
-			ignoreMap.put(relativeDir, n);
-		}
-		return n;
-	}
-
-	/**
-	 * Returns the ignored status of the file based on the current state
-	 * of the ignore nodes. Ignore nodes will not be updated and new ignore
-	 * nodes will not be created.
-	 * <br><br>
-	 * Traverses from highest to lowest priority and quits as soon as a match
-	 * is made. If no match is made anywhere, the file is assumed
-	 * to be not ignored.
-	 *
-	 * @param file
-	 * 			  Path string relative to Repository.getWorkDir();
-	 * @return true
-	 * 			  True if file is ignored, false if the file matches a negation statement
-	 *            or if there are no rules pertaining to the file.
-	 * @throws IOException
-	 * 			  Failed to check ignore status
-	 */
-	public boolean isIgnored(String file) throws IOException{
-		String currentPriority = file;
-
-		boolean ignored = false;
-		String target = rootFileURI.getPath() + file;
-		while (currentPriority.length() > 1) {
-			currentPriority = getParent(currentPriority);
-			IgnoreNode n = ignoreMap.get(currentPriority);
-
-			if (n != null) {
-				ignored = n.isIgnored(target);
-
-				if (n.wasMatched()) {
-					if (ignored)
-						return ignored;
-					else
-						target = getParent(target);
-				}
-			}
-		}
-
-		return false;
-	}
-
-	/**
-	 * String manipulation to get the parent directory of the given path.
-	 * It may be more efficient to make a file and call File.getParent().
-	 * This function is only called in {@link #initialize}
-	 *
-	 * @param filePath
-	 * 			  Will seek parent directory for this path. Returns empty string
-	 * 			  if the filePath does not contain a File.separator
-	 * @return
-	 * 			  Parent of the filePath, or blank string if non-existent
-	 */
-	private String getParent(String filePath) {
-		int lastSlash = filePath.lastIndexOf("/");
-		if (filePath.length() > 0 && lastSlash != -1)
-			return filePath.substring(0, lastSlash);
-		else
-			//This line should be unreachable with the current partiallyInitialize
-			return "";
-	}
-
-	/**
-	 * @param relativePath
-	 * 			  Directory to find rules for, should be relative to the repository root
-	 * @return
-	 * 			  Ignore rules for given base directory, contained in an IgnoreNode
-	 */
-	public IgnoreNode getRules(String relativePath) {
-		return ignoreMap.get(relativePath);
-	}
-
-	/**
-	 * @return
-	 * 			  True if there are no ignore rules in this cache
-	 */
-	public boolean isEmpty() {
-		return ignoreMap.isEmpty();
-	}
-
-	/**
-	 * Clears the cache
-	 */
-	public void clear() {
-		ignoreMap.clear();
-	}
-
-	/**
-	 * Returns the relative path versus the repository root.
-	 *
-	 * @param directory
-	 * 			  Directory to find relative path for.
-	 * @return
-	 * 			  Relative path versus the repository root. This function will
-	 * 			  strip the last trailing "/" from its return string
-	 */
-	private String relativize(File directory) {
-		String retVal = rootFileURI.relativize(directory.toURI()).getPath();
-		if (retVal.endsWith("/"))
-			retVal = retVal.substring(0, retVal.length() - 1);
-		return retVal;
-	}
-
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java
deleted file mode 100644
index 40f1106..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com>
- * Copyright (C) 2009, Google Inc.
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-import java.util.Collection;
-
-/**
- * An ObjectDatabase of another {@link Repository}.
- * <p>
- * This {@code ObjectDatabase} wraps around another {@code Repository}'s object
- * database, providing its contents to the caller, and closing the Repository
- * when this database is closed. The primary user of this class is
- * {@link ObjectDirectory}, when the {@code info/alternates} file points at the
- * {@code objects/} directory of another repository.
- */
-public final class AlternateRepositoryDatabase extends ObjectDatabase {
-	private final Repository repository;
-
-	private final ObjectDatabase odb;
-
-	/**
-	 * @param alt
-	 *            the alternate repository to wrap and export.
-	 */
-	public AlternateRepositoryDatabase(final Repository alt) {
-		repository = alt;
-		odb = repository.getObjectDatabase();
-	}
-
-	/** @return the alternate repository objects are borrowed from. */
-	public Repository getRepository() {
-		return repository;
-	}
-
-	@Override
-	public void closeSelf() {
-		repository.close();
-	}
-
-	@Override
-	public void create() throws IOException {
-		repository.create();
-	}
-
-	@Override
-	public boolean exists() {
-		return odb.exists();
-	}
-
-	@Override
-	protected boolean hasObject1(final AnyObjectId objectId) {
-		return odb.hasObject1(objectId);
-	}
-
-	@Override
-	protected boolean tryAgain1() {
-		return odb.tryAgain1();
-	}
-
-	@Override
-	protected boolean hasObject2(final String objectName) {
-		return odb.hasObject2(objectName);
-	}
-
-	@Override
-	protected ObjectLoader openObject1(final WindowCursor curs,
-			final AnyObjectId objectId) throws IOException {
-		return odb.openObject1(curs, objectId);
-	}
-
-	@Override
-	protected ObjectLoader openObject2(final WindowCursor curs,
-			final String objectName, final AnyObjectId objectId)
-			throws IOException {
-		return odb.openObject2(curs, objectName, objectId);
-	}
-
-	@Override
-	void openObjectInAllPacks1(final Collection<PackedObjectLoader> out,
-			final WindowCursor curs, final AnyObjectId objectId)
-			throws IOException {
-		odb.openObjectInAllPacks1(out, curs, objectId);
-	}
-
-	@Override
-	protected ObjectDatabase[] loadAlternates() throws IOException {
-		return odb.getAlternates();
-	}
-
-	@Override
-	protected void closeAlternates(final ObjectDatabase[] alt) {
-		// Do nothing; these belong to odb to close, not us.
-	}
-
-	@Override
-	public ObjectDatabase newCachedDatabase() {
-		return odb.newCachedDatabase();
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
index 7d08f3d..ecaa82b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
@@ -113,7 +113,7 @@ public final int getFirstByte() {
 	 * @return < 0 if this id comes before other; 0 if this id is equal to
 	 *         other; > 0 if this id comes after other.
 	 */
-	public int compareTo(final ObjectId other) {
+	public int compareTo(final AnyObjectId other) {
 		if (this == other)
 			return 0;
 
@@ -139,7 +139,7 @@ public int compareTo(final ObjectId other) {
 	}
 
 	public int compareTo(final Object other) {
-		return compareTo(((ObjectId) other));
+		return compareTo(((AnyObjectId) other));
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
new file mode 100644
index 0000000..410c85f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -0,0 +1,616 @@
+package org.eclipse.jgit.lib;
+
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BARE;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WORKTREE;
+import static org.eclipse.jgit.lib.Constants.DOT_GIT;
+import static org.eclipse.jgit.lib.Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY;
+import static org.eclipse.jgit.lib.Constants.GIT_CEILING_DIRECTORIES_KEY;
+import static org.eclipse.jgit.lib.Constants.GIT_DIR_KEY;
+import static org.eclipse.jgit.lib.Constants.GIT_INDEX_KEY;
+import static org.eclipse.jgit.lib.Constants.GIT_OBJECT_DIRECTORY_KEY;
+import static org.eclipse.jgit.lib.Constants.GIT_WORK_TREE_KEY;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.SystemReader;
+
+/**
+ * Base builder to customize repository construction.
+ * <p>
+ * Repository implementations may subclass this builder in order to add custom
+ * repository detection methods.
+ *
+ * @param <B>
+ *            type of the repository builder.
+ * @param <R>
+ *            type of the repository that is constructed.
+ * @see RepositoryBuilder
+ * @see FileRepositoryBuilder
+ */
+public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Repository> {
+	private FS fs;
+
+	private File gitDir;
+
+	private File objectDirectory;
+
+	private List<File> alternateObjectDirectories;
+
+	private File indexFile;
+
+	private File workTree;
+
+	/** Directories limiting the search for a Git repository. */
+	private List<File> ceilingDirectories;
+
+	/** True only if the caller wants to force bare behavior. */
+	private boolean bare;
+
+	/** Configuration file of target repository, lazily loaded if required. */
+	private Config config;
+
+	/**
+	 * Set the file system abstraction needed by this repository.
+	 *
+	 * @param fs
+	 *            the abstraction.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B setFS(FS fs) {
+		this.fs = fs;
+		return self();
+	}
+
+	/** @return the file system abstraction, or null if not set. */
+	public FS getFS() {
+		return fs;
+	}
+
+	/**
+	 * Set the Git directory storing the repository metadata.
+	 * <p>
+	 * The meta directory stores the objects, references, and meta files like
+	 * {@code MERGE_HEAD}, or the index file. If {@code null} the path is
+	 * assumed to be {@code workTree/.git}.
+	 *
+	 * @param gitDir
+	 *            {@code GIT_DIR}, the repository meta directory.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B setGitDir(File gitDir) {
+		this.gitDir = gitDir;
+		this.config = null;
+		return self();
+	}
+
+	/** @return the meta data directory; null if not set. */
+	public File getGitDir() {
+		return gitDir;
+	}
+
+	/**
+	 * Set the directory storing the repository's objects.
+	 *
+	 * @param objectDirectory
+	 *            {@code GIT_OBJECT_DIRECTORY}, the directory where the
+	 *            repository's object files are stored.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B setObjectDirectory(File objectDirectory) {
+		this.objectDirectory = objectDirectory;
+		return self();
+	}
+
+	/** @return the object directory; null if not set. */
+	public File getObjectDirectory() {
+		return objectDirectory;
+	}
+
+	/**
+	 * Add an alternate object directory to the search list.
+	 * <p>
+	 * This setting handles one alternate directory at a time, and is provided
+	 * to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}.
+	 *
+	 * @param other
+	 *            another objects directory to search after the standard one.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B addAlternateObjectDirectory(File other) {
+		if (other != null) {
+			if (alternateObjectDirectories == null)
+				alternateObjectDirectories = new LinkedList<File>();
+			alternateObjectDirectories.add(other);
+		}
+		return self();
+	}
+
+	/**
+	 * Add alternate object directories to the search list.
+	 * <p>
+	 * This setting handles several alternate directories at once, and is
+	 * provided to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}.
+	 *
+	 * @param inList
+	 *            other object directories to search after the standard one. The
+	 *            collection's contents is copied to an internal list.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B addAlternateObjectDirectories(Collection<File> inList) {
+		if (inList != null) {
+			for (File path : inList)
+				addAlternateObjectDirectory(path);
+		}
+		return self();
+	}
+
+	/**
+	 * Add alternate object directories to the search list.
+	 * <p>
+	 * This setting handles several alternate directories at once, and is
+	 * provided to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}.
+	 *
+	 * @param inList
+	 *            other object directories to search after the standard one. The
+	 *            array's contents is copied to an internal list.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B addAlternateObjectDirectories(File[] inList) {
+		if (inList != null) {
+			for (File path : inList)
+				addAlternateObjectDirectory(path);
+		}
+		return self();
+	}
+
+	/** @return ordered array of alternate directories; null if non were set. */
+	public File[] getAlternateObjectDirectories() {
+		final List<File> alts = alternateObjectDirectories;
+		if (alts == null)
+			return null;
+		return alts.toArray(new File[alts.size()]);
+	}
+
+	/**
+	 * Force the repository to be treated as bare (have no working directory).
+	 * <p>
+	 * If bare the working directory aspects of the repository won't be
+	 * configured, and will not be accessible.
+	 *
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B setBare() {
+		setIndexFile(null);
+		setWorkTree(null);
+		bare = true;
+		return self();
+	}
+
+	/** @return true if this repository was forced bare by {@link #setBare()}. */
+	public boolean isBare() {
+		return bare;
+	}
+
+	/**
+	 * Set the top level directory of the working files.
+	 *
+	 * @param workTree
+	 *            {@code GIT_WORK_TREE}, the working directory of the checkout.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B setWorkTree(File workTree) {
+		this.workTree = workTree;
+		return self();
+	}
+
+	/** @return the work tree directory, or null if not set. */
+	public File getWorkTree() {
+		return workTree;
+	}
+
+	/**
+	 * Set the local index file that is caching checked out file status.
+	 * <p>
+	 * The location of the index file tracking the status information for each
+	 * checked out file in {@code workTree}. This may be null to assume the
+	 * default {@code gitDiir/index}.
+	 *
+	 * @param indexFile
+	 *            {@code GIT_INDEX_FILE}, the index file location.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B setIndexFile(File indexFile) {
+		this.indexFile = indexFile;
+		return self();
+	}
+
+	/** @return the index file location, or null if not set. */
+	public File getIndexFile() {
+		return indexFile;
+	}
+
+	/**
+	 * Read standard Git environment variables and configure from those.
+	 * <p>
+	 * This method tries to read the standard Git environment variables, such as
+	 * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder
+	 * instance. If an environment variable is set, it overrides the value
+	 * already set in this builder.
+	 *
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B readEnvironment() {
+		return readEnvironment(SystemReader.getInstance());
+	}
+
+	/**
+	 * Read standard Git environment variables and configure from those.
+	 * <p>
+	 * This method tries to read the standard Git environment variables, such as
+	 * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder
+	 * instance. If a property is already set in the builder, the environment
+	 * variable is not used.
+	 *
+	 * @param sr
+	 *            the SystemReader abstraction to access the environment.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B readEnvironment(SystemReader sr) {
+		if (getGitDir() == null) {
+			String val = sr.getenv(GIT_DIR_KEY);
+			if (val != null)
+				setGitDir(new File(val));
+		}
+
+		if (getObjectDirectory() == null) {
+			String val = sr.getenv(GIT_OBJECT_DIRECTORY_KEY);
+			if (val != null)
+				setObjectDirectory(new File(val));
+		}
+
+		if (getAlternateObjectDirectories() == null) {
+			String val = sr.getenv(GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY);
+			if (val != null) {
+				for (String path : val.split(File.pathSeparator))
+					addAlternateObjectDirectory(new File(path));
+			}
+		}
+
+		if (getWorkTree() == null) {
+			String val = sr.getenv(GIT_WORK_TREE_KEY);
+			if (val != null)
+				setWorkTree(new File(val));
+		}
+
+		if (getIndexFile() == null) {
+			String val = sr.getenv(GIT_INDEX_KEY);
+			if (val != null)
+				setIndexFile(new File(val));
+		}
+
+		if (ceilingDirectories == null) {
+			String val = sr.getenv(GIT_CEILING_DIRECTORIES_KEY);
+			if (val != null) {
+				for (String path : val.split(File.pathSeparator))
+					addCeilingDirectory(new File(path));
+			}
+		}
+
+		return self();
+	}
+
+	/**
+	 * Add a ceiling directory to the search limit list.
+	 * <p>
+	 * This setting handles one ceiling directory at a time, and is provided to
+	 * support {@code GIT_CEILING_DIRECTORIES}.
+	 *
+	 * @param root
+	 *            a path to stop searching at; its parent will not be searched.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B addCeilingDirectory(File root) {
+		if (root != null) {
+			if (ceilingDirectories == null)
+				ceilingDirectories = new LinkedList<File>();
+			ceilingDirectories.add(root);
+		}
+		return self();
+	}
+
+	/**
+	 * Add ceiling directories to the search list.
+	 * <p>
+	 * This setting handles several ceiling directories at once, and is provided
+	 * to support {@code GIT_CEILING_DIRECTORIES}.
+	 *
+	 * @param inList
+	 *            directory paths to stop searching at. The collection's
+	 *            contents is copied to an internal list.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B addCeilingDirectories(Collection<File> inList) {
+		if (inList != null) {
+			for (File path : inList)
+				addCeilingDirectory(path);
+		}
+		return self();
+	}
+
+	/**
+	 * Add ceiling directories to the search list.
+	 * <p>
+	 * This setting handles several ceiling directories at once, and is provided
+	 * to support {@code GIT_CEILING_DIRECTORIES}.
+	 *
+	 * @param inList
+	 *            directory paths to stop searching at. The array's contents is
+	 *            copied to an internal list.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B addCeilingDirectories(File[] inList) {
+		if (inList != null) {
+			for (File path : inList)
+				addCeilingDirectory(path);
+		}
+		return self();
+	}
+
+	/**
+	 * Configure {@code GIT_DIR} by searching up the file system.
+	 * <p>
+	 * Starts from the current working directory of the JVM and scans up through
+	 * the directory tree until a Git repository is found. Success can be
+	 * determined by checking for {@code getGitDir() != null}.
+	 * <p>
+	 * The search can be limited to specific spaces of the local filesystem by
+	 * {@link #addCeilingDirectory(File)}, or inheriting the list through a
+	 * prior call to {@link #readEnvironment()}.
+	 *
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B findGitDir() {
+		if (getGitDir() == null)
+			findGitDir(new File("").getAbsoluteFile());
+		return self();
+	}
+
+	/**
+	 * Configure {@code GIT_DIR} by searching up the file system.
+	 * <p>
+	 * Starts from the supplied directory path and scans up through the parent
+	 * directory tree until a Git repository is found. Success can be determined
+	 * by checking for {@code getGitDir() != null}.
+	 * <p>
+	 * The search can be limited to specific spaces of the local filesystem by
+	 * {@link #addCeilingDirectory(File)}, or inheriting the list through a
+	 * prior call to {@link #readEnvironment()}.
+	 *
+	 * @param current
+	 *            directory to begin searching in.
+	 * @return {@code this} (for chaining calls).
+	 */
+	public B findGitDir(File current) {
+		if (getGitDir() == null) {
+			FS tryFS = safeFS();
+			while (current != null) {
+				File dir = new File(current, DOT_GIT);
+				if (FileKey.isGitRepository(dir, tryFS)) {
+					setGitDir(dir);
+					break;
+				}
+
+				current = current.getParentFile();
+				if (current != null && ceilingDirectories.contains(current))
+					break;
+			}
+		}
+		return self();
+	}
+
+	/**
+	 * Guess and populate all parameters not already defined.
+	 * <p>
+	 * If an option was not set, the setup method will try to default the option
+	 * based on other options. If insufficient information is available, an
+	 * exception is thrown to the caller.
+	 *
+	 * @return {@code this}
+	 * @throws IllegalArgumentException
+	 *             insufficient parameters were set, or some parameters are
+	 *             incompatible with one another.
+	 * @throws IOException
+	 *             the repository could not be accessed to configure the rest of
+	 *             the builder's parameters.
+	 */
+	public B setup() throws IllegalArgumentException, IOException {
+		requireGitDirOrWorkTree();
+		setupGitDir();
+		setupWorkTree();
+		setupInternals();
+		return self();
+	}
+
+	/**
+	 * Create a repository matching the configuration in this builder.
+	 * <p>
+	 * If an option was not set, the build method will try to default the option
+	 * based on other options. If insufficient information is available, an
+	 * exception is thrown to the caller.
+	 *
+	 * @return a repository matching this configuration.
+	 * @throws IllegalArgumentException
+	 *             insufficient parameters were set.
+	 * @throws IOException
+	 *             the repository could not be accessed to configure the rest of
+	 *             the builder's parameters.
+	 */
+	@SuppressWarnings("unchecked")
+	public R build() throws IOException {
+		return (R) new FileRepository(setup());
+	}
+
+	/** Require either {@code gitDir} or {@code workTree} to be set. */
+	protected void requireGitDirOrWorkTree() {
+		if (getGitDir() == null && getWorkTree() == null)
+			throw new IllegalArgumentException(
+					JGitText.get().eitherGitDirOrWorkTreeRequired);
+	}
+
+	/**
+	 * Perform standard gitDir initialization.
+	 *
+	 * @throws IOException
+	 *             the repository could not be accessed
+	 */
+	protected void setupGitDir() throws IOException {
+		// No gitDir? Try to assume its under the workTree.
+		//
+		if (getGitDir() == null && getWorkTree() != null)
+			setGitDir(new File(getWorkTree(), DOT_GIT));
+	}
+
+	/**
+	 * Perform standard work-tree initialization.
+	 * <p>
+	 * This is a method typically invoked inside of {@link #setup()}, near the
+	 * end after the repository has been identified and its configuration is
+	 * available for inspection.
+	 *
+	 * @throws IOException
+	 *             the repository configuration could not be read.
+	 */
+	protected void setupWorkTree() throws IOException {
+		if (getFS() == null)
+			setFS(FS.DETECTED);
+
+		// If we aren't bare, we should have a work tree.
+		//
+		if (!isBare() && getWorkTree() == null)
+			setWorkTree(guessWorkTreeOrFail());
+
+		if (!isBare()) {
+			// If after guessing we're still not bare, we must have
+			// a metadata directory to hold the repository. Assume
+			// its at the work tree.
+			//
+			if (getGitDir() == null)
+				setGitDir(getWorkTree().getParentFile());
+			if (getIndexFile() == null)
+				setIndexFile(new File(getGitDir(), "index"));
+		}
+	}
+
+	/**
+	 * Configure the internal implementation details of the repository.
+	 *
+	 * @throws IOException
+	 *             the repository could not be accessed
+	 */
+	protected void setupInternals() throws IOException {
+		if (getObjectDirectory() == null && getGitDir() != null)
+			setObjectDirectory(safeFS().resolve(getGitDir(), "objects"));
+	}
+
+	/**
+	 * Get the cached repository configuration, loading if not yet available.
+	 *
+	 * @return the configuration of the repository.
+	 * @throws IOException
+	 *             the configuration is not available, or is badly formed.
+	 */
+	protected Config getConfig() throws IOException {
+		if (config == null)
+			config = loadConfig();
+		return config;
+	}
+
+	/**
+	 * Parse and load the repository specific configuration.
+	 * <p>
+	 * The default implementation reads {@code gitDir/config}, or returns an
+	 * empty configuration if gitDir was not set.
+	 *
+	 * @return the repository's configuration.
+	 * @throws IOException
+	 *             the configuration is not available.
+	 */
+	protected Config loadConfig() throws IOException {
+		if (getGitDir() != null) {
+			// We only want the repository's configuration file, and not
+			// the user file, as these parameters must be unique to this
+			// repository and not inherited from other files.
+			//
+			File path = safeFS().resolve(getGitDir(), "config");
+			FileBasedConfig cfg = new FileBasedConfig(path, safeFS());
+			try {
+				cfg.load();
+			} catch (ConfigInvalidException err) {
+				throw new IllegalArgumentException(MessageFormat.format(
+						JGitText.get().repositoryConfigFileInvalid, path
+								.getAbsolutePath(), err.getMessage()));
+			}
+			return cfg;
+		} else {
+			return new Config();
+		}
+	}
+
+	private File guessWorkTreeOrFail() throws IOException {
+		final Config cfg = getConfig();
+
+		// If set, core.worktree wins.
+		//
+		String path = cfg.getString(CONFIG_CORE_SECTION, null,
+				CONFIG_KEY_WORKTREE);
+		if (path != null)
+			return safeFS().resolve(getGitDir(), path);
+
+		// If core.bare is set, honor its value. Assume workTree is
+		// the parent directory of the repository.
+		//
+		if (cfg.getString(CONFIG_CORE_SECTION, null, CONFIG_KEY_BARE) != null) {
+			if (cfg.getBoolean(CONFIG_CORE_SECTION, CONFIG_KEY_BARE, true)) {
+				setBare();
+				return null;
+			}
+			return getGitDir().getParentFile();
+		}
+
+		if (getGitDir().getName().equals(DOT_GIT)) {
+			// No value for the "bare" flag, but gitDir is named ".git",
+			// use the parent of the directory
+			//
+			return getGitDir().getParentFile();
+		}
+
+		// We have to assume we are bare.
+		//
+		setBare();
+		return null;
+	}
+
+	/** @return the configured FS, or {@link FS#DETECTED}. */
+	protected FS safeFS() {
+		return getFS() != null ? getFS() : FS.DETECTED;
+	}
+
+	/** @return {@code this} */
+	@SuppressWarnings("unchecked")
+	protected final B self() {
+		return (B) this;
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java
deleted file mode 100644
index a59b335..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import org.eclipse.jgit.JGitText;
-
-/**
- * Recreate a stream from a base stream and a GIT pack delta.
- * <p>
- * This entire class is heavily cribbed from <code>patch-delta.c</code> in the
- * GIT project. The original delta patching code was written by Nicolas Pitre
- * (&lt;nico@cam.org&gt;).
- * </p>
- */
-public class BinaryDelta {
-
-	/**
-	 * Apply the changes defined by delta to the data in base, yielding a new
-	 * array of bytes.
-	 *
-	 * @param base
-	 *            some byte representing an object of some kind.
-	 * @param delta
-	 *            a git pack delta defining the transform from one version to
-	 *            another.
-	 * @return patched base
-	 */
-	public static final byte[] apply(final byte[] base, final byte[] delta) {
-		int deltaPtr = 0;
-
-		// Length of the base object (a variable length int).
-		//
-		int baseLen = 0;
-		int c, shift = 0;
-		do {
-			c = delta[deltaPtr++] & 0xff;
-			baseLen |= (c & 0x7f) << shift;
-			shift += 7;
-		} while ((c & 0x80) != 0);
-		if (base.length != baseLen)
-			throw new IllegalArgumentException(JGitText.get().baseLengthIncorrect);
-
-		// Length of the resulting object (a variable length int).
-		//
-		int resLen = 0;
-		shift = 0;
-		do {
-			c = delta[deltaPtr++] & 0xff;
-			resLen |= (c & 0x7f) << shift;
-			shift += 7;
-		} while ((c & 0x80) != 0);
-
-		final byte[] result = new byte[resLen];
-		int resultPtr = 0;
-		while (deltaPtr < delta.length) {
-			final int cmd = delta[deltaPtr++] & 0xff;
-			if ((cmd & 0x80) != 0) {
-				// Determine the segment of the base which should
-				// be copied into the output. The segment is given
-				// as an offset and a length.
-				//
-				int copyOffset = 0;
-				if ((cmd & 0x01) != 0)
-					copyOffset = delta[deltaPtr++] & 0xff;
-				if ((cmd & 0x02) != 0)
-					copyOffset |= (delta[deltaPtr++] & 0xff) << 8;
-				if ((cmd & 0x04) != 0)
-					copyOffset |= (delta[deltaPtr++] & 0xff) << 16;
-				if ((cmd & 0x08) != 0)
-					copyOffset |= (delta[deltaPtr++] & 0xff) << 24;
-
-				int copySize = 0;
-				if ((cmd & 0x10) != 0)
-					copySize = delta[deltaPtr++] & 0xff;
-				if ((cmd & 0x20) != 0)
-					copySize |= (delta[deltaPtr++] & 0xff) << 8;
-				if ((cmd & 0x40) != 0)
-					copySize |= (delta[deltaPtr++] & 0xff) << 16;
-				if (copySize == 0)
-					copySize = 0x10000;
-
-				System.arraycopy(base, copyOffset, result, resultPtr, copySize);
-				resultPtr += copySize;
-			} else if (cmd != 0) {
-				// Anything else the data is literal within the delta
-				// itself.
-				//
-				System.arraycopy(delta, deltaPtr, result, resultPtr, cmd);
-				deltaPtr += cmd;
-				resultPtr += cmd;
-			} else {
-				// cmd == 0 has been reserved for future encoding but
-				// for now its not acceptable.
-				//
-				throw new IllegalArgumentException(JGitText.get().unsupportedCommand0);
-			}
-		}
-
-		return result;
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
index b05942b..b56966f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
@@ -91,10 +91,8 @@ public BlobBasedConfig(Config base, final byte[] blob)
 	public BlobBasedConfig(Config base, final Repository r,
 			final ObjectId objectId) throws IOException, ConfigInvalidException {
 		super(base);
-		final ObjectLoader loader = r.openBlob(objectId);
-		if (loader == null)
-			throw new IOException(MessageFormat.format(JGitText.get().blobNotFound, objectId));
-		fromText(RawParseUtils.decode(loader.getBytes()));
+		ObjectLoader loader = r.open(objectId, Constants.OBJ_BLOB);
+		fromText(RawParseUtils.decode(loader.getCachedBytes()));
 	}
 
 	/**
@@ -122,10 +120,7 @@ public BlobBasedConfig(Config base, final Commit commit, final String path)
 		if (tree == null)
 			throw new FileNotFoundException(MessageFormat.format(JGitText.get().entryNotFoundByPath, path));
 		final ObjectId blobId = tree.getObjectId(0);
-		final ObjectLoader loader = tree.getRepository().openBlob(blobId);
-		if (loader == null)
-			throw new IOException(MessageFormat.format(JGitText.get().blobNotFoundForPath
-					, blobId, path));
-		fromText(RawParseUtils.decode(loader.getBytes()));
+		ObjectLoader loader = r.open(blobId,Constants.OBJ_BLOB);
+		fromText(RawParseUtils.decode(loader.getCachedBytes()));
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java
deleted file mode 100644
index 3dcea16..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com>
- * Copyright (C) 2010, JetBrains s.r.o.
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-import java.util.Collection;
-
-/**
- * {@link ObjectDatabase} wrapper providing temporary lookup caching.
- * <p>
- * The base class for {@code ObjectDatabase}s that wrap other database instances
- * and optimize querying for objects by caching some database dependent
- * information. Instances of this class (or any of its subclasses) can be
- * returned from the method {@link ObjectDatabase#newCachedDatabase()}. This
- * class can be used in scenarios where the database does not change, or when
- * changes in the database while some operation is in progress is an acceptable
- * risk.
- * <p>
- * The default implementation delegates all requests to the wrapped database.
- * The instance might be indirectly invalidated if the wrapped instance is
- * closed. Closing the delegating instance does not implies closing the wrapped
- * instance. For alternative databases, cached instances are used as well.
- */
-public class CachedObjectDatabase extends ObjectDatabase {
-	/**
-	 * The wrapped database instance
-	 */
-	protected final ObjectDatabase wrapped;
-
-	/**
-	 * Create the delegating database instance
-	 *
-	 * @param wrapped
-	 *            the wrapped object database
-	 */
-	public CachedObjectDatabase(ObjectDatabase wrapped) {
-		this.wrapped = wrapped;
-	}
-
-	@Override
-	protected boolean hasObject1(AnyObjectId objectId) {
-		return wrapped.hasObject1(objectId);
-	}
-
-	@Override
-	protected ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId)
-			throws IOException {
-		return wrapped.openObject1(curs, objectId);
-	}
-
-	@Override
-	protected boolean hasObject2(String objectName) {
-		return wrapped.hasObject2(objectName);
-	}
-
-	@Override
-	protected ObjectDatabase[] loadAlternates() throws IOException {
-		ObjectDatabase[] loaded = wrapped.getAlternates();
-		ObjectDatabase[] result = new ObjectDatabase[loaded.length];
-		for (int i = 0; i < loaded.length; i++) {
-			result[i] = loaded[i].newCachedDatabase();
-		}
-		return result;
-	}
-
-	@Override
-	protected ObjectLoader openObject2(WindowCursor curs, String objectName,
-			AnyObjectId objectId) throws IOException {
-		return wrapped.openObject2(curs, objectName, objectId);
-	}
-
-	@Override
-	void openObjectInAllPacks1(Collection<PackedObjectLoader> out,
-			WindowCursor curs, AnyObjectId objectId) throws IOException {
-		wrapped.openObjectInAllPacks1(out, curs, objectId);
-	}
-
-	@Override
-	protected boolean tryAgain1() {
-		return wrapped.tryAgain1();
-	}
-
-	@Override
-	public ObjectDatabase newCachedDatabase() {
-		// Note that "this" is not returned since subclasses might actually do something,
-		// on closeSelf() (for example closing database connections or open repositories).
-		// The situation might become even more tricky if we will consider alternates.
-		return wrapped.newCachedDatabase();
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java
deleted file mode 100644
index 3724f84..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com>
- * Copyright (C) 2010, JetBrains s.r.o.
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * The cached instance of an {@link ObjectDirectory}.
- * <p>
- * This class caches the list of loose objects in memory, so the file system is
- * not queried with stat calls.
- */
-public class CachedObjectDirectory extends CachedObjectDatabase {
-	/**
-	 * The set that contains unpacked objects identifiers, it is created when
-	 * the cached instance is created.
-	 */
-	private final ObjectIdSubclassMap<ObjectId> unpackedObjects = new ObjectIdSubclassMap<ObjectId>();
-
-	/**
-	 * The constructor
-	 *
-	 * @param wrapped
-	 *            the wrapped database
-	 */
-	public CachedObjectDirectory(ObjectDirectory wrapped) {
-		super(wrapped);
-		File objects = wrapped.getDirectory();
-		String[] fanout = objects.list();
-		if (fanout == null)
-			fanout = new String[0];
-		for (String d : fanout) {
-			if (d.length() != 2)
-				continue;
-			String[] entries = new File(objects, d).list();
-			if (entries == null)
-				continue;
-			for (String e : entries) {
-				if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
-					continue;
-				try {
-					unpackedObjects.add(ObjectId.fromString(d + e));
-				} catch (IllegalArgumentException notAnObject) {
-					// ignoring the file that does not represent loose object
-				}
-			}
-		}
-	}
-
-	@Override
-	protected ObjectLoader openObject2(WindowCursor curs, String objectName,
-			AnyObjectId objectId) throws IOException {
-		if (unpackedObjects.get(objectId) == null)
-			return null;
-		return super.openObject2(curs, objectName, objectId);
-	}
-
-	@Override
-	protected boolean hasObject1(AnyObjectId objectId) {
-		if (unpackedObjects.get(objectId) != null)
-			return true; // known to be loose
-		return super.hasObject1(objectId);
-	}
-
-	@Override
-	protected boolean hasObject2(String name) {
-		return false; // loose objects were tested by hasObject1
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java
index 66dd891..eeffb08 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java
@@ -339,7 +339,14 @@ public void setMessage(final String m) {
 	public void commit() throws IOException {
 		if (getCommitId() != null)
 			throw new IllegalStateException(MessageFormat.format(JGitText.get().commitAlreadyExists, getCommitId()));
-		setCommitId(new ObjectWriter(objdb).writeCommit(this));
+		ObjectInserter odi = objdb.newObjectInserter();
+		try {
+			ObjectId id = odi.insert(Constants.OBJ_COMMIT, odi.format(this));
+			odi.flush();
+			setCommitId(id);
+		} finally {
+			odi.release();
+		}
 	}
 
 	public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
index ccb2516..2e1ab9a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -221,6 +221,21 @@ public int getInt(final String section, String subsection,
 	 *
 	 * @param section
 	 *            section the key is grouped within.
+	 * @param name
+	 *            name of the key to get.
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @return an integer value from the configuration, or defaultValue.
+	 */
+	public long getLong(String section, String name, long defaultValue) {
+		return getLong(section, null, name, defaultValue);
+	}
+
+	/**
+	 * Obtain an integer value from the configuration.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
 	 * @param subsection
 	 *            subsection name, such a remote or branch name.
 	 * @param name
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index a5b3d95..2269655 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -527,6 +527,10 @@ public static int decodeTypeString(final AnyObjectId id,
 	/** name of the file containing the IDs of the parents of a merge commit */
 	public static final String MERGE_HEAD = "MERGE_HEAD";
 
+	/** objectid for the empty blob */
+	public static final ObjectId EMPTY_BLOB_ID = ObjectId
+			.fromString("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391");
+
 	private Constants() {
 		// Hide the default constructor
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
index 9080ec1..5ad7910 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
@@ -47,6 +47,7 @@
 package org.eclipse.jgit.lib;
 
 import static java.util.zip.Deflater.DEFAULT_COMPRESSION;
+import static org.eclipse.jgit.lib.ObjectLoader.STREAM_THRESHOLD;
 
 import org.eclipse.jgit.lib.Config.SectionParser;
 
@@ -67,14 +68,21 @@ public CoreConfig parse(final Config cfg) {
 
 	private final boolean logAllRefUpdates;
 
+	private final int streamFileThreshold;
+
 	private CoreConfig(final Config rc) {
 		compression = rc.getInt("core", "compression", DEFAULT_COMPRESSION);
 		packIndexVersion = rc.getInt("pack", "indexversion", 2);
 		logAllRefUpdates = rc.getBoolean("core", "logallrefupdates", true);
+
+		long maxMem = Runtime.getRuntime().maxMemory();
+		long sft = rc.getLong("core", null, "streamfilethreshold", STREAM_THRESHOLD);
+		sft = Math.min(sft, maxMem / 4); // don't use more than 1/4 of the heap
+		sft = Math.min(sft, Integer.MAX_VALUE); // cannot exceed array length
+		streamFileThreshold = (int) sft;
 	}
 
 	/**
-	 * @see ObjectWriter
 	 * @return The compression level to use when storing loose objects
 	 */
 	public int getCompression() {
@@ -95,4 +103,9 @@ public int getPackIndexVersion() {
 	public boolean isLogAllRefUpdates() {
 		return logAllRefUpdates;
 	}
+
+	/** @return the size threshold beyond which objects must be streamed. */
+	public int getStreamFileThreshold() {
+		return streamFileThreshold;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java
deleted file mode 100644
index d0e98a2..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2009, Google Inc.
- * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-
-import org.eclipse.jgit.JGitText;
-import org.eclipse.jgit.errors.CorruptObjectException;
-
-/** Reads a deltified object which uses an offset to find its base. */
-class DeltaOfsPackedObjectLoader extends DeltaPackedObjectLoader {
-	private final long deltaBase;
-
-	DeltaOfsPackedObjectLoader(final PackFile pr, final long objectOffset,
-			final int headerSz, final int deltaSz, final long base) {
-		super(pr, objectOffset, headerSz, deltaSz);
-		deltaBase = base;
-	}
-
-	protected PackedObjectLoader getBaseLoader(final WindowCursor curs)
-			throws IOException {
-		return pack.resolveBase(curs, deltaBase);
-	}
-
-	@Override
-	public int getRawType() {
-		return Constants.OBJ_OFS_DELTA;
-	}
-
-	@Override
-	public ObjectId getDeltaBase() throws IOException {
-		final ObjectId id = pack.findObjectForOffset(deltaBase);
-		if (id == null)
-			throw new CorruptObjectException(
-					JGitText.get().offsetWrittenDeltaBaseForObjectNotFoundInAPack);
-		return id;
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java
deleted file mode 100644
index bbc1c62..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.zip.DataFormatException;
-
-import org.eclipse.jgit.JGitText;
-import org.eclipse.jgit.errors.CorruptObjectException;
-
-/** Reader for a deltified object stored in a pack file. */
-abstract class DeltaPackedObjectLoader extends PackedObjectLoader {
-	private static final int OBJ_COMMIT = Constants.OBJ_COMMIT;
-
-	private final int deltaSize;
-
-	DeltaPackedObjectLoader(final PackFile pr, final long objectOffset,
-			final int headerSize, final int deltaSz) {
-		super(pr, objectOffset, headerSize);
-		objectType = -1;
-		deltaSize = deltaSz;
-	}
-
-	@Override
-	public void materialize(final WindowCursor curs) throws IOException {
-		if (cachedBytes != null) {
-			return;
-		}
-
-		if (objectType != OBJ_COMMIT) {
-			UnpackedObjectCache.Entry cache = pack.readCache(objectOffset);
-			if (cache != null) {
-				curs.release();
-				objectType = cache.type;
-				objectSize = cache.data.length;
-				cachedBytes = cache.data;
-				return;
-			}
-		}
-
-		try {
-			final PackedObjectLoader baseLoader = getBaseLoader(curs);
-			baseLoader.materialize(curs);
-			cachedBytes = BinaryDelta.apply(baseLoader.getCachedBytes(), pack
-					.decompress(objectOffset + headerSize, deltaSize, curs));
-			curs.release();
-			objectType = baseLoader.getType();
-			objectSize = cachedBytes.length;
-			if (objectType != OBJ_COMMIT)
-				pack.saveCache(objectOffset, cachedBytes, objectType);
-		} catch (DataFormatException dfe) {
-			final CorruptObjectException coe;
-			coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream,
-					objectOffset, pack.getPackFile()));
-			coe.initCause(dfe);
-			throw coe;
-		}
-	}
-
-	@Override
-	public long getRawSize() {
-		return deltaSize;
-	}
-
-	/**
-	 * @param curs
-	 *            temporary thread storage during data access.
-	 * @return the object loader for the base object
-	 * @throws IOException
-	 */
-	protected abstract PackedObjectLoader getBaseLoader(WindowCursor curs)
-			throws IOException;
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java
deleted file mode 100644
index 9f7589c..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-
-import org.eclipse.jgit.errors.MissingObjectException;
-
-/** Reads a deltified object which uses an {@link ObjectId} to find its base. */
-class DeltaRefPackedObjectLoader extends DeltaPackedObjectLoader {
-	private final ObjectId deltaBase;
-
-	DeltaRefPackedObjectLoader(final PackFile pr, final long objectOffset,
-			final int headerSz, final int deltaSz, final ObjectId base) {
-		super(pr, objectOffset, headerSz, deltaSz);
-		deltaBase = base;
-	}
-
-	protected PackedObjectLoader getBaseLoader(final WindowCursor curs)
-			throws IOException {
-		final PackedObjectLoader or = pack.get(curs, deltaBase);
-		if (or == null)
-			throw new MissingObjectException(deltaBase, "delta base");
-		return or;
-	}
-
-	@Override
-	public int getRawType() {
-		return Constants.OBJ_REF_DELTA;
-	}
-
-	@Override
-	public ObjectId getDeltaBase() throws IOException {
-		return deltaBase;
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java
index ed1b51d..7990956 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java
@@ -100,7 +100,7 @@ public void setExecutable(final boolean execute) {
 	 * @throws IOException
 	 */
 	public ObjectLoader openReader() throws IOException {
-		return getRepository().openBlob(getId());
+		return getRepository().open(getId(), Constants.OBJ_BLOB);
 	}
 
 	public void accept(final TreeVisitor tv, final int flags)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java
index defda14..bf293d1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java
@@ -71,6 +71,7 @@
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.events.IndexChangedEvent;
 import org.eclipse.jgit.util.RawParseUtils;
 
 /**
@@ -155,7 +156,7 @@ public boolean isChanged() {
 	public void rereadIfNecessary() throws IOException {
 		if (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) {
 			read();
-			db.fireIndexChanged();
+			db.fireEvent(new IndexChangedEvent());
 		}
 	}
 
@@ -297,17 +298,39 @@ public void write() throws IOException {
 			fc.write(buf);
 			fc.close();
 			fileOutputStream.close();
-			if (cacheFile.exists())
-				if (!cacheFile.delete())
-					throw new IOException(
-						JGitText.get().couldNotRenameDeleteOldIndex);
+			if (cacheFile.exists()) {
+				if (db.getFS().retryFailedLockFileCommit()) {
+					// file deletion fails on windows if another
+					// thread is reading the file concurrently
+					// So let's try 10 times...
+					boolean deleted = false;
+					for (int i = 0; i < 10; i++) {
+						if (cacheFile.delete()) {
+							deleted = true;
+							break;
+						}
+						try {
+							Thread.sleep(100);
+						} catch (InterruptedException e) {
+							// ignore
+						}
+					}
+					if (!deleted)
+						throw new IOException(
+								JGitText.get().couldNotRenameDeleteOldIndex);
+				} else {
+					if (!cacheFile.delete())
+						throw new IOException(
+								JGitText.get().couldNotRenameDeleteOldIndex);
+				}
+			}
 			if (!tmpIndex.renameTo(cacheFile))
 				throw new IOException(
 						JGitText.get().couldNotRenameTemporaryIndexFileToIndex);
 			changed = false;
 			statDirty = false;
 			lastCacheTime = cacheFile.lastModified();
-			db.fireIndexChanged();
+			db.fireEvent(new IndexChangedEvent());
 		} finally {
 			if (!lock.delete())
 				throw new IOException(
@@ -352,7 +375,7 @@ private boolean config_filemode() {
 		// to change this for testing.
 		if (filemode != null)
 			return filemode.booleanValue();
-		RepositoryConfig config = db.getConfig();
+		Config config = db.getConfig();
 		filemode = Boolean.valueOf(config.getBoolean("core", null, "filemode", true));
 		return filemode.booleanValue();
 	}
@@ -437,7 +460,7 @@ public class Entry {
 			uid = -1;
 			gid = -1;
 			try {
-				size = (int) db.openBlob(f.getId()).getSize();
+				size = (int) db.open(f.getId(), Constants.OBJ_BLOB).getSize();
 			} catch (IOException e) {
 				e.printStackTrace();
 				size = -1;
@@ -877,17 +900,16 @@ public void checkout(File wd) throws IOException {
 	 * @throws IOException
 	 */
 	public void checkoutEntry(File wd, Entry e) throws IOException {
-		ObjectLoader ol = db.openBlob(e.sha1);
-		byte[] bytes = ol.getBytes();
+		ObjectLoader ol = db.open(e.sha1, Constants.OBJ_BLOB);
 		File file = new File(wd, e.getName());
 		file.delete();
 		file.getParentFile().mkdirs();
-		FileChannel channel = new FileOutputStream(file).getChannel();
-		ByteBuffer buffer = ByteBuffer.wrap(bytes);
-		int j = channel.write(buffer);
-		if (j != bytes.length)
-			throw new IOException(MessageFormat.format(JGitText.get().couldNotWriteFile, file));
-		channel.close();
+		FileOutputStream dst = new FileOutputStream(file);
+		try {
+			ol.copyTo(dst);
+		} finally {
+			dst.close();
+		}
 		if (config_filemode() && File_hasExecute()) {
 			if (FileMode.EXECUTABLE_FILE.equals(e.mode)) {
 				if (!File_canExecute(file))
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java
deleted file mode 100644
index c866db5..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-/**
- * This class passes information about a changed Git index to a
- * {@link RepositoryListener}
- *
- * Currently only a reference to the repository is passed.
- */
-public class IndexChangedEvent extends RepositoryChangedEvent {
-	/**
-	 * Create an event describing index changes in a repository.
-	 *
-	 * @param repository
-	 *            the repository whose index (DirCache) recently changed.
-	 */
-	public IndexChangedEvent(final Repository repository) {
-		super(repository);
-	}
-
-	@Override
-	public String toString() {
-		return "IndexChangedEvent[" + getRepository() + "]";
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index 030b942..d4e6ac9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -44,97 +45,163 @@
 
 package org.eclipse.jgit.lib;
 
-import java.io.File;
 import java.io.IOException;
 import java.util.HashSet;
 
-import org.eclipse.jgit.lib.GitIndex.Entry;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
 
 /**
- * Compares the Index, a Tree, and the working directory
- *
- * @deprecated Use {@link org.eclipse.jgit.treewalk.TreeWalk} instead, with at
- * least the {@link org.eclipse.jgit.dircache.DirCacheIterator} and
- * {@link org.eclipse.jgit.treewalk.FileTreeIterator} iterators, and setting
- * the filter {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ANY_DIFF}.
+ * Compares the index, a tree, and the working directory
+ * Ignored files are not taken into account.
+ * The following information is retrieved:
+ * <li> added files
+ * <li> changed files
+ * <li> removed files
+ * <li> missing files
+ * <li> modified files
+ * <li> untracked files
  */
-@Deprecated
 public class IndexDiff {
-	private GitIndex index;
-	private Tree tree;
+
+	private final static int TREE = 0;
+
+	private final static int INDEX = 1;
+
+	private final static int WORKDIR = 2;
+
+	private final Repository repository;
+
+	private final RevTree tree;
+
+	private final WorkingTreeIterator initialWorkingTreeIterator;
+
+	private HashSet<String> added = new HashSet<String>();
+
+	private HashSet<String> changed = new HashSet<String>();
+
+	private HashSet<String> removed = new HashSet<String>();
+
+	private HashSet<String> missing = new HashSet<String>();
+
+	private HashSet<String> modified = new HashSet<String>();
+
+	private HashSet<String> untracked = new HashSet<String>();
 
 	/**
-	 * Construct an indexdiff for diffing the workdir against
-	 * the index.
+	 * Construct an IndexDiff
 	 *
 	 * @param repository
+	 * @param revstr
+	 *            symbolic name e.g. HEAD
+	 * @param workingTreeIterator
+	 *            iterator for working directory
 	 * @throws IOException
 	 */
-	public IndexDiff(Repository repository) throws IOException {
-		this.tree = repository.mapTree(Constants.HEAD);
-		this.index = repository.getIndex();
+	public IndexDiff(Repository repository, String revstr,
+			WorkingTreeIterator workingTreeIterator) throws IOException {
+		this.repository = repository;
+		ObjectId objectId = repository.resolve(revstr);
+		tree = new RevWalk(repository).parseTree(objectId);
+		this.initialWorkingTreeIterator = workingTreeIterator;
 	}
 
 	/**
-	 * Construct an indexdiff for diffing the workdir against both
-	 * the index and a tree.
+	 * Construct an Indexdiff
 	 *
-	 * @param tree
-	 * @param index
+	 * @param repository
+	 * @param objectId
+	 *            tree id
+	 * @param workingTreeIterator
+	 *            iterator for working directory
+	 * @throws IOException
 	 */
-	public IndexDiff(Tree tree, GitIndex index) {
-		this.tree = tree;
-		this.index = index;
+	public IndexDiff(Repository repository, ObjectId objectId,
+			WorkingTreeIterator workingTreeIterator) throws IOException {
+		this.repository = repository;
+		tree = new RevWalk(repository).parseTree(objectId);
+		this.initialWorkingTreeIterator = workingTreeIterator;
 	}
 
-	boolean anyChanges = false;
-
 	/**
 	 * Run the diff operation. Until this is called, all lists will be empty
+	 *
 	 * @return if anything is different between index, tree, and workdir
 	 * @throws IOException
 	 */
 	public boolean diff() throws IOException {
-		final File root = index.getRepository().getWorkDir();
-		new IndexTreeWalker(index, tree, root, new AbstractIndexTreeVisitor() {
-			public void visitEntry(TreeEntry treeEntry, Entry indexEntry, File file) {
-				if (treeEntry == null) {
-					added.add(indexEntry.getName());
-					anyChanges = true;
-				} else if (indexEntry == null) {
-					if (!(treeEntry instanceof Tree))
-						removed.add(treeEntry.getFullName());
-					anyChanges = true;
+		boolean changesExist = false;
+		DirCache dirCache = repository.readDirCache();
+		TreeWalk treeWalk = new TreeWalk(repository);
+		treeWalk.reset();
+		treeWalk.setRecursive(true);
+		// add the trees (tree, dirchache, workdir)
+		treeWalk.addTree(tree);
+		treeWalk.addTree(new DirCacheIterator(dirCache));
+		treeWalk.addTree(initialWorkingTreeIterator);
+		treeWalk.setFilter(TreeFilter.ANY_DIFF);
+		while (treeWalk.next()) {
+			AbstractTreeIterator treeIterator = treeWalk.getTree(TREE,
+					AbstractTreeIterator.class);
+			DirCacheIterator dirCacheIterator = treeWalk.getTree(INDEX,
+					DirCacheIterator.class);
+			WorkingTreeIterator workingTreeIterator = treeWalk.getTree(WORKDIR,
+					WorkingTreeIterator.class);
+			FileMode fileModeTree = treeWalk.getFileMode(TREE);
+
+			if (treeIterator != null) {
+				if (dirCacheIterator != null) {
+					if (!treeIterator.getEntryObjectId().equals(
+							dirCacheIterator.getEntryObjectId())) {
+						// in repo, in index, content diff => changed
+						changed.add(dirCacheIterator.getEntryPathString());
+						changesExist = true;
+					}
 				} else {
-					if (!treeEntry.getId().equals(indexEntry.getObjectId())) {
-						changed.add(indexEntry.getName());
-						anyChanges = true;
+					// in repo, not in index => removed
+					if (!fileModeTree.equals(FileMode.TYPE_TREE)) {
+						removed.add(treeIterator.getEntryPathString());
+						changesExist = true;
 					}
 				}
-
-				if (indexEntry != null) {
-					if (!file.exists()) {
-						missing.add(indexEntry.getName());
-						anyChanges = true;
-					} else {
-						if (indexEntry.isModified(root, true)) {
-							modified.add(indexEntry.getName());
-							anyChanges = true;
-						}
+			} else {
+				if (dirCacheIterator != null) {
+					// not in repo, in index => added
+					added.add(dirCacheIterator.getEntryPathString());
+					changesExist = true;
+				} else {
+					// not in repo, not in index => untracked
+					if (workingTreeIterator != null
+							&& !workingTreeIterator.isEntryIgnored()) {
+						untracked.add(workingTreeIterator.getEntryPathString());
+						changesExist = true;
 					}
 				}
 			}
-		}).walk();
 
-		return anyChanges;
+			if (dirCacheIterator != null) {
+				if (workingTreeIterator == null) {
+					// in index, not in workdir => missing
+					missing.add(dirCacheIterator.getEntryPathString());
+					changesExist = true;
+				} else {
+					if (!dirCacheIterator.idEqual(workingTreeIterator)) {
+						// in index, in workdir, content differs => modified
+						modified.add(dirCacheIterator.getEntryPathString());
+						changesExist = true;
+					}
+				}
+			}
+		}
+		return changesExist;
 	}
 
-	HashSet<String> added = new HashSet<String>();
-	HashSet<String> changed = new HashSet<String>();
-	HashSet<String> removed = new HashSet<String>();
-	HashSet<String> missing = new HashSet<String>();
-	HashSet<String> modified = new HashSet<String>();
-
 	/**
 	 * @return list of files added to the index, not in the tree
 	 */
@@ -169,4 +236,11 @@ public HashSet<String> getMissing() {
 	public HashSet<String> getModified() {
 		return modified;
 	}
+
+	/**
+	 * @return list of files on modified on disk relative to the index
+	 */
+	public HashSet<String> getUntracked() {
+		return untracked;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
index 7eac79f..15d118c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
@@ -44,31 +44,20 @@
 package org.eclipse.jgit.lib;
 
 import java.io.IOException;
-import java.util.Collection;
-import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
 
 /**
  * Abstraction of arbitrary object storage.
  * <p>
  * An object database stores one or more Git objects, indexed by their unique
- * {@link ObjectId}. Optionally an object database can reference one or more
- * alternates; other ObjectDatabase instances that are searched in addition to
- * the current database.
- * <p>
- * Databases are usually divided into two halves: a half that is considered to
- * be fast to search, and a half that is considered to be slow to search. When
- * alternates are present the fast half is fully searched (recursively through
- * all alternates) before the slow half is considered.
+ * {@link ObjectId}.
  */
 public abstract class ObjectDatabase {
-	/** Constant indicating no alternate databases exist. */
-	protected static final ObjectDatabase[] NO_ALTERNATES = {};
-
-	private final AtomicReference<ObjectDatabase[]> alternates;
-
 	/** Initialize a new database instance for access. */
 	protected ObjectDatabase() {
-		alternates = new AtomicReference<ObjectDatabase[]>();
+		// Protected to force extension.
 	}
 
 	/**
@@ -92,292 +81,101 @@ public void create() throws IOException {
 	}
 
 	/**
-	 * Close any resources held by this database and its active alternates.
+	 * Create a new {@code ObjectInserter} to insert new objects.
+	 * <p>
+	 * The returned inserter is not itself thread-safe, but multiple concurrent
+	 * inserter instances created from the same {@code ObjectDatabase} must be
+	 * thread-safe.
+	 *
+	 * @return writer the caller can use to create objects in this database.
 	 */
-	public final void close() {
-		closeSelf();
-		closeAlternates();
-	}
+	public abstract ObjectInserter newInserter();
 
 	/**
-	 * Close any resources held by this database only; ignoring alternates.
+	 * Create a new {@code ObjectReader} to read existing objects.
 	 * <p>
-	 * To fully close this database and its referenced alternates, the caller
-	 * should instead invoke {@link #close()}.
+	 * The returned reader is not itself thread-safe, but multiple concurrent
+	 * reader instances created from the same {@code ObjectDatabase} must be
+	 * thread-safe.
+	 *
+	 * @return reader the caller can use to load objects from this database.
 	 */
-	public void closeSelf() {
-		// Assume no action is required.
-	}
+	public abstract ObjectReader newReader();
 
-	/** Fully close all loaded alternates and clear the alternate list. */
-	public final void closeAlternates() {
-		ObjectDatabase[] alt = alternates.get();
-		if (alt != null) {
-			alternates.set(null);
-			closeAlternates(alt);
-		}
-	}
+	/**
+	 * Close any resources held by this database.
+	 */
+	public abstract void close();
 
 	/**
 	 * Does the requested object exist in this database?
 	 * <p>
-	 * Alternates (if present) are searched automatically.
-	 *
-	 * @param objectId
-	 *            identity of the object to test for existence of.
-	 * @return true if the specified object is stored in this database, or any
-	 *         of the alternate databases.
-	 */
-	public final boolean hasObject(final AnyObjectId objectId) {
-		return hasObjectImpl1(objectId) || hasObjectImpl2(objectId.name());
-	}
-
-	private final boolean hasObjectImpl1(final AnyObjectId objectId) {
-		if (hasObject1(objectId)) {
-			return true;
-		}
-		for (final ObjectDatabase alt : getAlternates()) {
-			if (alt.hasObjectImpl1(objectId)) {
-				return true;
-			}
-		}
-		return tryAgain1() && hasObject1(objectId);
-	}
-
-	private final boolean hasObjectImpl2(final String objectId) {
-		if (hasObject2(objectId)) {
-			return true;
-		}
-		for (final ObjectDatabase alt : getAlternates()) {
-			if (alt.hasObjectImpl2(objectId)) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	/**
-	 * Fast half of {@link #hasObject(AnyObjectId)}.
+	 * This is a one-shot call interface which may be faster than allocating a
+	 * {@link #newReader()} to perform the lookup.
 	 *
 	 * @param objectId
 	 *            identity of the object to test for existence of.
 	 * @return true if the specified object is stored in this database.
+	 * @throws IOException
+	 *             the object store cannot be accessed.
 	 */
-	protected abstract boolean hasObject1(AnyObjectId objectId);
-
-	/**
-	 * Slow half of {@link #hasObject(AnyObjectId)}.
-	 *
-	 * @param objectName
-	 *            identity of the object to test for existence of.
-	 * @return true if the specified object is stored in this database.
-	 */
-	protected boolean hasObject2(String objectName) {
-		// Assume the search took place during hasObject1.
-		return false;
+	public boolean has(final AnyObjectId objectId) throws IOException {
+		final ObjectReader or = newReader();
+		try {
+			return or.has(objectId);
+		} finally {
+			or.release();
+		}
 	}
 
 	/**
 	 * Open an object from this database.
 	 * <p>
-	 * Alternates (if present) are searched automatically.
+	 * This is a one-shot call interface which may be faster than allocating a
+	 * {@link #newReader()} to perform the lookup.
 	 *
-	 * @param curs
-	 *            temporary working space associated with the calling thread.
 	 * @param objectId
 	 *            identity of the object to open.
-	 * @return a {@link ObjectLoader} for accessing the data of the named
-	 *         object, or null if the object does not exist.
+	 * @return a {@link ObjectLoader} for accessing the object.
+	 * @throws MissingObjectException
+	 *             the object does not exist.
 	 * @throws IOException
+	 *             the object store cannot be accessed.
 	 */
-	public final ObjectLoader openObject(final WindowCursor curs,
-			final AnyObjectId objectId) throws IOException {
-		ObjectLoader ldr;
-
-		ldr = openObjectImpl1(curs, objectId);
-		if (ldr != null) {
-			return ldr;
-		}
-
-		ldr = openObjectImpl2(curs, objectId.name(), objectId);
-		if (ldr != null) {
-			return ldr;
-		}
-		return null;
-	}
-
-	private ObjectLoader openObjectImpl1(final WindowCursor curs,
-			final AnyObjectId objectId) throws IOException {
-		ObjectLoader ldr;
-
-		ldr = openObject1(curs, objectId);
-		if (ldr != null) {
-			return ldr;
-		}
-		for (final ObjectDatabase alt : getAlternates()) {
-			ldr = alt.openObjectImpl1(curs, objectId);
-			if (ldr != null) {
-				return ldr;
-			}
-		}
-		if (tryAgain1()) {
-			ldr = openObject1(curs, objectId);
-			if (ldr != null) {
-				return ldr;
-			}
-		}
-		return null;
-	}
-
-	private ObjectLoader openObjectImpl2(final WindowCursor curs,
-			final String objectName, final AnyObjectId objectId)
+	public ObjectLoader open(final AnyObjectId objectId)
 			throws IOException {
-		ObjectLoader ldr;
-
-		ldr = openObject2(curs, objectName, objectId);
-		if (ldr != null) {
-			return ldr;
-		}
-		for (final ObjectDatabase alt : getAlternates()) {
-			ldr = alt.openObjectImpl2(curs, objectName, objectId);
-			if (ldr != null) {
-				return ldr;
-			}
-		}
-		return null;
+		return open(objectId, ObjectReader.OBJ_ANY);
 	}
 
 	/**
-	 * Fast half of {@link #openObject(WindowCursor, AnyObjectId)}.
+	 * Open an object from this database.
+	 * <p>
+	 * This is a one-shot call interface which may be faster than allocating a
+	 * {@link #newReader()} to perform the lookup.
 	 *
-	 * @param curs
-	 *            temporary working space associated with the calling thread.
 	 * @param objectId
 	 *            identity of the object to open.
-	 * @return a {@link ObjectLoader} for accessing the data of the named
-	 *         object, or null if the object does not exist.
+	 * @param typeHint
+	 *            hint about the type of object being requested;
+	 *            {@link ObjectReader#OBJ_ANY} if the object type is not known,
+	 *            or does not matter to the caller.
+	 * @return a {@link ObjectLoader} for accessing the object.
+	 * @throws MissingObjectException
+	 *             the object does not exist.
+	 * @throws IncorrectObjectTypeException
+	 *             typeHint was not OBJ_ANY, and the object's actual type does
+	 *             not match typeHint.
 	 * @throws IOException
+	 *             the object store cannot be accessed.
 	 */
-	protected abstract ObjectLoader openObject1(WindowCursor curs,
-			AnyObjectId objectId) throws IOException;
-
-	/**
-	 * Slow half of {@link #openObject(WindowCursor, AnyObjectId)}.
-	 *
-	 * @param curs
-	 *            temporary working space associated with the calling thread.
-	 * @param objectName
-	 *            name of the object to open.
-	 * @param objectId
-	 *            identity of the object to open.
-	 * @return a {@link ObjectLoader} for accessing the data of the named
-	 *         object, or null if the object does not exist.
-	 * @throws IOException
-	 */
-	protected ObjectLoader openObject2(WindowCursor curs, String objectName,
-			AnyObjectId objectId) throws IOException {
-		// Assume the search took place during openObject1.
-		return null;
-	}
-
-	/**
-	 * Open the object from all packs containing it.
-	 * <p>
-	 * If any alternates are present, their packs are also considered.
-	 *
-	 * @param out
-	 *            result collection of loaders for this object, filled with
-	 *            loaders from all packs containing specified object
-	 * @param curs
-	 *            temporary working space associated with the calling thread.
-	 * @param objectId
-	 *            id of object to search for
-	 * @throws IOException
-	 */
-	final void openObjectInAllPacks(final Collection<PackedObjectLoader> out,
-			final WindowCursor curs, final AnyObjectId objectId)
-			throws IOException {
-		openObjectInAllPacks1(out, curs, objectId);
-		for (final ObjectDatabase alt : getAlternates()) {
-			alt.openObjectInAllPacks1(out, curs, objectId);
-		}
-	}
-
-	/**
-	 * Open the object from all packs containing it.
-	 *
-	 * @param out
-	 *            result collection of loaders for this object, filled with
-	 *            loaders from all packs containing specified object
-	 * @param curs
-	 *            temporary working space associated with the calling thread.
-	 * @param objectId
-	 *            id of object to search for
-	 * @throws IOException
-	 */
-	void openObjectInAllPacks1(Collection<PackedObjectLoader> out,
-			WindowCursor curs, AnyObjectId objectId) throws IOException {
-		// Assume no pack support
-	}
-
-	/**
-	 * @return true if the fast-half search should be tried again.
-	 */
-	protected boolean tryAgain1() {
-		return false;
-	}
-
-	/**
-	 * Get the alternate databases known to this database.
-	 *
-	 * @return the alternate list. Never null, but may be an empty array.
-	 */
-	public final ObjectDatabase[] getAlternates() {
-		ObjectDatabase[] r = alternates.get();
-		if (r == null) {
-			synchronized (alternates) {
-				r = alternates.get();
-				if (r == null) {
-					try {
-						r = loadAlternates();
-					} catch (IOException e) {
-						r = NO_ALTERNATES;
-					}
-					alternates.set(r);
-				}
-			}
-		}
-		return r;
-	}
-
-	/**
-	 * Load the list of alternate databases into memory.
-	 * <p>
-	 * This method is invoked by {@link #getAlternates()} if the alternate list
-	 * has not yet been populated, or if {@link #closeAlternates()} has been
-	 * called on this instance and the alternate list is needed again.
-	 * <p>
-	 * If the alternate array is empty, implementors should consider using the
-	 * constant {@link #NO_ALTERNATES}.
-	 *
-	 * @return the alternate list for this database.
-	 * @throws IOException
-	 *             the alternate list could not be accessed. The empty alternate
-	 *             array {@link #NO_ALTERNATES} will be assumed by the caller.
-	 */
-	protected ObjectDatabase[] loadAlternates() throws IOException {
-		return NO_ALTERNATES;
-	}
-
-	/**
-	 * Close the list of alternates returned by {@link #loadAlternates()}.
-	 *
-	 * @param alt
-	 *            the alternate list, from {@link #loadAlternates()}.
-	 */
-	protected void closeAlternates(ObjectDatabase[] alt) {
-		for (final ObjectDatabase d : alt) {
-			d.close();
+	public ObjectLoader open(AnyObjectId objectId, int typeHint)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException {
+		final ObjectReader or = newReader();
+		try {
+			return or.open(objectId, typeHint);
+		} finally {
+			or.release();
 		}
 	}
 
@@ -387,9 +185,8 @@ protected void closeAlternates(ObjectDatabase[] alt) {
 	 * done after instance creation might fail to be noticed.
 	 *
 	 * @return new cached database instance
-	 * @see CachedObjectDatabase
 	 */
 	public ObjectDatabase newCachedDatabase() {
-		return new CachedObjectDatabase(this);
+		return this;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
new file mode 100644
index 0000000..fd99d39
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.ObjectWritingException;
+
+/**
+ * Inserts objects into an existing {@code ObjectDatabase}.
+ * <p>
+ * An inserter is not thread-safe. Individual threads should each obtain their
+ * own unique inserter instance, or must arrange for locking at a higher level
+ * to ensure the inserter is in use by no more than one thread at a time.
+ * <p>
+ * Objects written by an inserter may not be immediately visible for reading
+ * after the insert method completes. Callers must invoke either
+ * {@link #release()} or {@link #flush()} prior to updating references or
+ * otherwise making the returned ObjectIds visible to other code.
+ */
+public abstract class ObjectInserter {
+	private static final byte[] htree = Constants.encodeASCII("tree");
+
+	private static final byte[] hparent = Constants.encodeASCII("parent");
+
+	private static final byte[] hauthor = Constants.encodeASCII("author");
+
+	private static final byte[] hcommitter = Constants.encodeASCII("committer");
+
+	private static final byte[] hencoding = Constants.encodeASCII("encoding");
+
+	/** An inserter that can be used for formatting and id generation only. */
+	public static class Formatter extends ObjectInserter {
+		@Override
+		public ObjectId insert(int objectType, long length, InputStream in)
+				throws IOException {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void flush() throws IOException {
+			// Do nothing.
+		}
+
+		@Override
+		public void release() {
+			// Do nothing.
+		}
+	}
+
+	/** Digest to compute the name of an object. */
+	private final MessageDigest digest;
+
+	/** Temporary working buffer for streaming data through. */
+	private byte[] tempBuffer;
+
+	/** Create a new inserter for a database. */
+	protected ObjectInserter() {
+		digest = Constants.newMessageDigest();
+	}
+
+	/** @return a temporary byte array for use by the caller. */
+	protected byte[] buffer() {
+		if (tempBuffer == null)
+			tempBuffer = new byte[8192];
+		return tempBuffer;
+	}
+
+	/** @return digest to help compute an ObjectId */
+	protected MessageDigest digest() {
+		digest.reset();
+		return digest;
+	}
+
+	/**
+	 * Compute the name of an object, without inserting it.
+	 *
+	 * @param type
+	 *            type code of the object to store.
+	 * @param data
+	 *            complete content of the object.
+	 * @return the name of the object.
+	 */
+	public ObjectId idFor(int type, byte[] data) {
+		return idFor(type, data, 0, data.length);
+	}
+
+	/**
+	 * Compute the name of an object, without inserting it.
+	 *
+	 * @param type
+	 *            type code of the object to store.
+	 * @param data
+	 *            complete content of the object.
+	 * @param off
+	 *            first position within {@code data}.
+	 * @param len
+	 *            number of bytes to copy from {@code data}.
+	 * @return the name of the object.
+	 */
+	public ObjectId idFor(int type, byte[] data, int off, int len) {
+		MessageDigest md = digest();
+		md.update(Constants.encodedTypeString(type));
+		md.update((byte) ' ');
+		md.update(Constants.encodeASCII(len));
+		md.update((byte) 0);
+		md.update(data, off, len);
+		return ObjectId.fromRaw(md.digest());
+	}
+
+	/**
+	 * Compute the name of an object, without inserting it.
+	 *
+	 * @param objectType
+	 *            type code of the object to store.
+	 * @param length
+	 *            number of bytes to scan from {@code in}.
+	 * @param in
+	 *            stream providing the object content. The caller is responsible
+	 *            for closing the stream.
+	 * @return the name of the object.
+	 * @throws IOException
+	 *             the source stream could not be read.
+	 */
+	public ObjectId idFor(int objectType, long length, InputStream in)
+			throws IOException {
+		MessageDigest md = digest();
+		md.update(Constants.encodedTypeString(objectType));
+		md.update((byte) ' ');
+		md.update(Constants.encodeASCII(length));
+		md.update((byte) 0);
+		byte[] buf = buffer();
+		while (length > 0) {
+			int n = in.read(buf, 0, (int) Math.min(length, buf.length));
+			if (n < 0)
+				throw new EOFException("Unexpected end of input");
+			md.update(buf, 0, n);
+			length -= n;
+		}
+		return ObjectId.fromRaw(md.digest());
+	}
+
+	/**
+	 * Insert a single object into the store, returning its unique name.
+	 *
+	 * @param type
+	 *            type code of the object to store.
+	 * @param data
+	 *            complete content of the object.
+	 * @return the name of the object.
+	 * @throws IOException
+	 *             the object could not be stored.
+	 */
+	public ObjectId insert(final int type, final byte[] data)
+			throws IOException {
+		return insert(type, data, 0, data.length);
+	}
+
+	/**
+	 * Insert a single object into the store, returning its unique name.
+	 *
+	 * @param type
+	 *            type code of the object to store.
+	 * @param data
+	 *            complete content of the object.
+	 * @param off
+	 *            first position within {@code data}.
+	 * @param len
+	 *            number of bytes to copy from {@code data}.
+	 * @return the name of the object.
+	 * @throws IOException
+	 *             the object could not be stored.
+	 */
+	public ObjectId insert(int type, byte[] data, int off, int len)
+			throws IOException {
+		return insert(type, len, new ByteArrayInputStream(data, off, len));
+	}
+
+	/**
+	 * Insert a single object into the store, returning its unique name.
+	 *
+	 * @param objectType
+	 *            type code of the object to store.
+	 * @param length
+	 *            number of bytes to copy from {@code in}.
+	 * @param in
+	 *            stream providing the object content. The caller is responsible
+	 *            for closing the stream.
+	 * @return the name of the object.
+	 * @throws IOException
+	 *             the object could not be stored, or the source stream could
+	 *             not be read.
+	 */
+	public abstract ObjectId insert(int objectType, long length, InputStream in)
+			throws IOException;
+
+	/**
+	 * Make all inserted objects visible.
+	 * <p>
+	 * The flush may take some period of time to make the objects available to
+	 * other threads.
+	 *
+	 * @throws IOException
+	 *             the flush could not be completed; objects inserted thus far
+	 *             are in an indeterminate state.
+	 */
+	public abstract void flush() throws IOException;
+
+	/**
+	 * Release any resources used by this inserter.
+	 * <p>
+	 * An inserter that has been released can be used again, but may need to be
+	 * released after the subsequent usage.
+	 */
+	public abstract void release();
+
+	/**
+	 * Format a Tree in canonical format.
+	 *
+	 * @param tree
+	 *            the tree object to format
+	 * @return canonical encoding of the tree object.
+	 * @throws IOException
+	 *             the tree cannot be loaded, or its not in a writable state.
+	 */
+	public final byte[] format(Tree tree) throws IOException {
+		ByteArrayOutputStream o = new ByteArrayOutputStream();
+		for (TreeEntry e : tree.members()) {
+			ObjectId id = e.getId();
+			if (id == null)
+				throw new ObjectWritingException(MessageFormat.format(JGitText
+						.get().objectAtPathDoesNotHaveId, e.getFullName()));
+
+			e.getMode().copyTo(o);
+			o.write(' ');
+			o.write(e.getNameUTF8());
+			o.write(0);
+			id.copyRawTo(o);
+		}
+		return o.toByteArray();
+	}
+
+	/**
+	 * Format a Commit in canonical format.
+	 *
+	 * @param commit
+	 *            the commit object to format
+	 * @return canonical encoding of the commit object.
+	 * @throws UnsupportedEncodingException
+	 *             the commit's chosen encoding isn't supported on this JVM.
+	 */
+	public final byte[] format(Commit commit)
+			throws UnsupportedEncodingException {
+		String encoding = commit.getEncoding();
+		if (encoding == null)
+			encoding = Constants.CHARACTER_ENCODING;
+
+		ByteArrayOutputStream os = new ByteArrayOutputStream();
+		OutputStreamWriter w = new OutputStreamWriter(os, encoding);
+		try {
+			os.write(htree);
+			os.write(' ');
+			commit.getTreeId().copyTo(os);
+			os.write('\n');
+
+			ObjectId[] ps = commit.getParentIds();
+			for (int i = 0; i < ps.length; ++i) {
+				os.write(hparent);
+				os.write(' ');
+				ps[i].copyTo(os);
+				os.write('\n');
+			}
+
+			os.write(hauthor);
+			os.write(' ');
+			w.write(commit.getAuthor().toExternalString());
+			w.flush();
+			os.write('\n');
+
+			os.write(hcommitter);
+			os.write(' ');
+			w.write(commit.getCommitter().toExternalString());
+			w.flush();
+			os.write('\n');
+
+			if (!encoding.equals(Constants.CHARACTER_ENCODING)) {
+				os.write(hencoding);
+				os.write(' ');
+				os.write(Constants.encodeASCII(encoding));
+				os.write('\n');
+			}
+
+			os.write('\n');
+			w.write(commit.getMessage());
+			w.flush();
+		} catch (IOException err) {
+			// This should never occur, the only way to get it above is
+			// for the ByteArrayOutputStream to throw, but it doesn't.
+			//
+			throw new RuntimeException(err);
+		}
+		return os.toByteArray();
+	}
+
+	/**
+	 * Format a Tag in canonical format.
+	 *
+	 * @param tag
+	 *            the tag object to format
+	 * @return canonical encoding of the tag object.
+	 */
+	public final byte[] format(Tag tag) {
+		ByteArrayOutputStream os = new ByteArrayOutputStream();
+		OutputStreamWriter w = new OutputStreamWriter(os, Constants.CHARSET);
+		try {
+			w.write("object ");
+			tag.getObjId().copyTo(w);
+			w.write('\n');
+
+			w.write("type ");
+			w.write(tag.getType());
+			w.write("\n");
+
+			w.write("tag ");
+			w.write(tag.getTag());
+			w.write("\n");
+
+			w.write("tagger ");
+			w.write(tag.getAuthor().toExternalString());
+			w.write('\n');
+
+			w.write('\n');
+			w.write(tag.getMessage());
+			w.close();
+		} catch (IOException err) {
+			// This should never occur, the only way to get it above is
+			// for the ByteArrayOutputStream to throw, but it doesn't.
+			//
+			throw new RuntimeException(err);
+		}
+		return os.toByteArray();
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java
index 4839a1c..e19bfc4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java
@@ -47,6 +47,12 @@
 
 package org.eclipse.jgit.lib;
 
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
 
 /**
  * Base class for a set of loaders for different representations of Git objects.
@@ -54,6 +60,14 @@
  */
 public abstract class ObjectLoader {
 	/**
+	 * Default setting for the large object threshold.
+	 * <p>
+	 * Objects larger than this size must be accessed as a stream through the
+	 * loader's {@link #openStream()} method.
+	 */
+	public static final int STREAM_THRESHOLD = 1024 * 1024;
+
+	/**
 	 * @return Git in pack object type, see {@link Constants}.
 	 */
 	public abstract int getType();
@@ -64,14 +78,32 @@ public abstract class ObjectLoader {
 	public abstract long getSize();
 
 	/**
+	 * @return true if this object is too large to obtain as a byte array.
+	 *         Objects over a certain threshold should be accessed only by their
+	 *         {@link #openStream()} to prevent overflowing the JVM heap.
+	 */
+	public boolean isLarge() {
+		try {
+			getCachedBytes();
+			return false;
+		} catch (LargeObjectException tooBig) {
+			return true;
+		}
+	}
+
+	/**
 	 * Obtain a copy of the bytes of this object.
 	 * <p>
 	 * Unlike {@link #getCachedBytes()} this method returns an array that might
 	 * be modified by the caller.
 	 *
 	 * @return the bytes of this object.
+	 * @throws LargeObjectException
+	 *             if the object won't fit into a byte array, because
+	 *             {@link #isLarge()} returns true. Callers should use
+	 *             {@link #openStream()} instead to access the contents.
 	 */
-	public final byte[] getBytes() {
+	public final byte[] getBytes() throws LargeObjectException {
 		final byte[] data = getCachedBytes();
 		final byte[] copy = new byte[data.length];
 		System.arraycopy(data, 0, copy, 0, data.length);
@@ -87,19 +119,120 @@ public abstract class ObjectLoader {
 	 * Changes (if made) will affect the cache but not the repository itself.
 	 *
 	 * @return the cached bytes of this object. Do not modify it.
+	 * @throws LargeObjectException
+	 *             if the object won't fit into a byte array, because
+	 *             {@link #isLarge()} returns true. Callers should use
+	 *             {@link #openStream()} instead to access the contents.
 	 */
-	public abstract byte[] getCachedBytes();
+	public abstract byte[] getCachedBytes() throws LargeObjectException;
 
 	/**
-	 * @return raw object type from object header, as stored in storage (pack,
-	 *         loose file). This may be different from {@link #getType()} result
-	 *         for packs (see {@link Constants}).
+	 * Obtain an input stream to read this object's data.
+	 *
+	 * @return a stream of this object's data. Caller must close the stream when
+	 *         through with it. The returned stream is buffered with a
+	 *         reasonable buffer size.
+	 * @throws MissingObjectException
+	 *             the object no longer exists.
+	 * @throws IOException
+	 *             the object store cannot be accessed.
 	 */
-	public abstract int getRawType();
+	public abstract ObjectStream openStream() throws MissingObjectException,
+			IOException;
 
 	/**
-	 * @return raw size of object from object header (pack, loose file).
-	 *         Interpretation of this value depends on {@link #getRawType()}.
+	 * Copy this object to the output stream.
+	 * <p>
+	 * For some object store implementations, this method may be more efficient
+	 * than reading from {@link #openStream()} into a temporary byte array, then
+	 * writing to the destination stream.
+	 * <p>
+	 * The default implementation of this method is to copy with a temporary
+	 * byte array for large objects, or to pass through the cached byte array
+	 * for small objects.
+	 *
+	 * @param out
+	 *            stream to receive the complete copy of this object's data.
+	 *            Caller is responsible for flushing or closing this stream
+	 *            after this method returns.
+	 * @throws MissingObjectException
+	 *             the object no longer exists.
+	 * @throws IOException
+	 *             the object store cannot be accessed, or the stream cannot be
+	 *             written to.
 	 */
-	public abstract long getRawSize();
+	public void copyTo(OutputStream out) throws MissingObjectException,
+			IOException {
+		if (isLarge()) {
+			ObjectStream in = openStream();
+			try {
+				byte[] tmp = new byte[1024];
+				long copied = 0;
+				for (;;) {
+					int n = in.read(tmp);
+					if (n < 0)
+						break;
+					out.write(tmp, 0, n);
+					copied += n;
+				}
+				if (copied != getSize())
+					throw new EOFException();
+			} finally {
+				in.close();
+			}
+		} else {
+			out.write(getCachedBytes());
+		}
+	}
+
+	/**
+	 * Simple loader around the cached byte array.
+	 * <p>
+	 * ObjectReader implementations can use this stream type when the object's
+	 * content is small enough to be accessed as a single byte array.
+	 */
+	public static class SmallObject extends ObjectLoader {
+		private final int type;
+
+		private final byte[] data;
+
+		/**
+		 * Construct a small object loader.
+		 *
+		 * @param type
+		 *            type of the object.
+		 * @param data
+		 *            the object's data array. This array will be returned as-is
+		 *            for the {@link #getCachedBytes()} method.
+		 */
+		public SmallObject(int type, byte[] data) {
+			this.type = type;
+			this.data = data;
+		}
+
+		@Override
+		public int getType() {
+			return type;
+		}
+
+		@Override
+		public long getSize() {
+			return getCachedBytes().length;
+		}
+
+		@Override
+		public boolean isLarge() {
+			return false;
+		}
+
+		@Override
+		public byte[] getCachedBytes() {
+			return data;
+		}
+
+		@Override
+		public ObjectStream openStream() {
+			return new ObjectStream.SmallStream(this);
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
new file mode 100644
index 0000000..ae70638
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.storage.pack.ObjectReuseAsIs;
+
+/**
+ * Reads an {@link ObjectDatabase} for a single thread.
+ * <p>
+ * Readers that can support efficient reuse of pack encoded objects should also
+ * implement the companion interface {@link ObjectReuseAsIs}.
+ */
+public abstract class ObjectReader {
+	/** Type hint indicating the caller doesn't know the type. */
+	protected static final int OBJ_ANY = -1;
+
+	/**
+	 * Construct a new reader from the same data.
+	 * <p>
+	 * Applications can use this method to build a new reader from the same data
+	 * source, but for an different thread.
+	 *
+	 * @return a brand new reader, using the same data source.
+	 */
+	public abstract ObjectReader newReader();
+
+	/**
+	 * Does the requested object exist in this database?
+	 *
+	 * @param objectId
+	 *            identity of the object to test for existence of.
+	 * @return true if the specified object is stored in this database.
+	 * @throws IOException
+	 *             the object store cannot be accessed.
+	 */
+	public boolean has(AnyObjectId objectId) throws IOException {
+		return has(objectId, OBJ_ANY);
+	}
+
+	/**
+	 * Does the requested object exist in this database?
+	 *
+	 * @param objectId
+	 *            identity of the object to test for existence of.
+	 * @param typeHint
+	 *            hint about the type of object being requested;
+	 *            {@link #OBJ_ANY} if the object type is not known, or does not
+	 *            matter to the caller.
+	 * @return true if the specified object is stored in this database.
+	 * @throws IncorrectObjectTypeException
+	 *             typeHint was not OBJ_ANY, and the object's actual type does
+	 *             not match typeHint.
+	 * @throws IOException
+	 *             the object store cannot be accessed.
+	 */
+	public boolean has(AnyObjectId objectId, int typeHint) throws IOException {
+		try {
+			open(objectId, typeHint);
+			return true;
+		} catch (MissingObjectException notFound) {
+			return false;
+		}
+	}
+
+	/**
+	 * Open an object from this database.
+	 *
+	 * @param objectId
+	 *            identity of the object to open.
+	 * @return a {@link ObjectLoader} for accessing the object.
+	 * @throws MissingObjectException
+	 *             the object does not exist.
+	 * @throws IOException
+	 *             the object store cannot be accessed.
+	 */
+	public ObjectLoader open(AnyObjectId objectId)
+			throws MissingObjectException, IOException {
+		return open(objectId, OBJ_ANY);
+	}
+
+	/**
+	 * Open an object from this database.
+	 *
+	 * @param objectId
+	 *            identity of the object to open.
+	 * @param typeHint
+	 *            hint about the type of object being requested;
+	 *            {@link #OBJ_ANY} if the object type is not known, or does not
+	 *            matter to the caller.
+	 * @return a {@link ObjectLoader} for accessing the object.
+	 * @throws MissingObjectException
+	 *             the object does not exist.
+	 * @throws IncorrectObjectTypeException
+	 *             typeHint was not OBJ_ANY, and the object's actual type does
+	 *             not match typeHint.
+	 * @throws IOException
+	 *             the object store cannot be accessed.
+	 */
+	public abstract ObjectLoader open(AnyObjectId objectId, int typeHint)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException;
+
+	/**
+	 * Get only the size of an object.
+	 * <p>
+	 * The default implementation of this method opens an ObjectLoader.
+	 * Databases are encouraged to override this if a faster access method is
+	 * available to them.
+	 *
+	 * @param objectId
+	 *            identity of the object to open.
+	 * @param typeHint
+	 *            hint about the type of object being requested;
+	 *            {@link #OBJ_ANY} if the object type is not known, or does not
+	 *            matter to the caller.
+	 * @return size of object in bytes.
+	 * @throws MissingObjectException
+	 *             the object does not exist.
+	 * @throws IncorrectObjectTypeException
+	 *             typeHint was not OBJ_ANY, and the object's actual type does
+	 *             not match typeHint.
+	 * @throws IOException
+	 *             the object store cannot be accessed.
+	 */
+	public long getObjectSize(AnyObjectId objectId, int typeHint)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException {
+		return open(objectId, typeHint).getSize();
+	}
+
+	/**
+	 * Release any resources used by this reader.
+	 * <p>
+	 * A reader that has been released can be used again, but may need to be
+	 * released after the subsequent usage.
+	 */
+	public void release() {
+		// Do nothing.
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java
new file mode 100644
index 0000000..86d6643
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Stream of data coming from an object loaded by {@link ObjectLoader}. */
+public abstract class ObjectStream extends InputStream {
+	/** @return Git object type, see {@link Constants}. */
+	public abstract int getType();
+
+	/** @return total size of object in bytes */
+	public abstract long getSize();
+
+	/**
+	 * Simple stream around the cached byte array created by a loader.
+	 * <p>
+	 * ObjectLoader implementations can use this stream type when the object's
+	 * content is small enough to be accessed as a single byte array, but the
+	 * application has still requested it in stream format.
+	 */
+	public static class SmallStream extends ObjectStream {
+		private final int type;
+
+		private final byte[] data;
+
+		private int ptr;
+
+		private int mark;
+
+		/**
+		 * Create the stream from an existing loader's cached bytes.
+		 *
+		 * @param loader
+		 *            the loader.
+		 */
+		public SmallStream(ObjectLoader loader) {
+			this.type = loader.getType();
+			this.data = loader.getCachedBytes();
+		}
+
+		@Override
+		public int getType() {
+			return type;
+		}
+
+		@Override
+		public long getSize() {
+			return data.length;
+		}
+
+		@Override
+		public int available() {
+			return data.length - ptr;
+		}
+
+		@Override
+		public long skip(long n) {
+			int s = (int) Math.min(available(), Math.max(0, n));
+			ptr += s;
+			return s;
+		}
+
+		@Override
+		public int read() {
+			if (ptr == data.length)
+				return -1;
+			return data[ptr++] & 0xff;
+		}
+
+		@Override
+		public int read(byte[] b, int off, int len) {
+			if (ptr == data.length)
+				return -1;
+			int n = Math.min(available(), len);
+			System.arraycopy(data, ptr, b, off, n);
+			ptr += n;
+			return n;
+		}
+
+		@Override
+		public boolean markSupported() {
+			return true;
+		}
+
+		@Override
+		public void mark(int readlimit) {
+			mark = ptr;
+		}
+
+		@Override
+		public void reset() {
+			ptr = mark;
+		}
+	}
+
+	/**
+	 * Simple filter stream around another stream.
+	 * <p>
+	 * ObjectLoader implementations can use this stream type when the object's
+	 * content is available from a standard InputStream.
+	 */
+	public static class Filter extends ObjectStream {
+		private final int type;
+
+		private final long size;
+
+		private final InputStream in;
+
+		/**
+		 * Create a filter stream for an object.
+		 *
+		 * @param type
+		 *            the type of the object.
+		 * @param size
+		 *            total size of the object, in bytes.
+		 * @param in
+		 *            stream the object's raw data is available from. This
+		 *            stream should be buffered with some reasonable amount of
+		 *            buffering.
+		 */
+		public Filter(int type, long size, InputStream in) {
+			this.type = type;
+			this.size = size;
+			this.in = in;
+		}
+
+		@Override
+		public int getType() {
+			return type;
+		}
+
+		@Override
+		public long getSize() {
+			return size;
+		}
+
+		@Override
+		public int available() throws IOException {
+			return in.available();
+		}
+
+		@Override
+		public long skip(long n) throws IOException {
+			return in.skip(n);
+		}
+
+		@Override
+		public int read() throws IOException {
+			return in.read();
+		}
+
+		@Override
+		public int read(byte[] b, int off, int len) throws IOException {
+			return in.read(b, off, len);
+		}
+
+		@Override
+		public boolean markSupported() {
+			return in.markSupported();
+		}
+
+		@Override
+		public void mark(int readlimit) {
+			in.mark(readlimit);
+		}
+
+		@Override
+		public void reset() throws IOException {
+			in.reset();
+		}
+
+		@Override
+		public void close() throws IOException {
+			in.close();
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java
index 20147ed..ce91efb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2009, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -44,61 +45,49 @@
 
 package org.eclipse.jgit.lib;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
+
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.security.MessageDigest;
-import java.text.MessageFormat;
-import java.util.zip.Deflater;
-import java.util.zip.DeflaterOutputStream;
-
-import org.eclipse.jgit.JGitText;
-import org.eclipse.jgit.errors.ObjectWritingException;
 
 /**
  * A class for writing loose objects.
+ *
+ * @deprecated Use {@link Repository#newObjectInserter()}.
  */
 public class ObjectWriter {
-	private static final byte[] htree = Constants.encodeASCII("tree");
-
-	private static final byte[] hparent = Constants.encodeASCII("parent");
-
-	private static final byte[] hauthor = Constants.encodeASCII("author");
-
-	private static final byte[] hcommitter = Constants.encodeASCII("committer");
-
-	private static final byte[] hencoding = Constants.encodeASCII("encoding");
-
-	private final Repository r;
-
-	private final byte[] buf;
-
-	private final MessageDigest md;
+	private final ObjectInserter inserter;
 
 	/**
 	 * Construct an Object writer for the specified repository
+	 *
 	 * @param d
 	 */
 	public ObjectWriter(final Repository d) {
-		r = d;
-		buf = new byte[8192];
-		md = Constants.newMessageDigest();
+		inserter = d.newObjectInserter();
 	}
 
 	/**
 	 * Write a blob with the specified data
 	 *
-	 * @param b bytes of the blob
+	 * @param b
+	 *            bytes of the blob
 	 * @return SHA-1 of the blob
 	 * @throws IOException
 	 */
 	public ObjectId writeBlob(final byte[] b) throws IOException {
-		return writeBlob(b.length, new ByteArrayInputStream(b));
+		try {
+			ObjectId id = inserter.insert(OBJ_BLOB, b);
+			inserter.flush();
+			return id;
+		} finally {
+			inserter.release();
+		}
 	}
 
 	/**
@@ -130,174 +119,101 @@ public ObjectId writeBlob(final File f) throws IOException {
 	 */
 	public ObjectId writeBlob(final long len, final InputStream is)
 			throws IOException {
-		return writeObject(Constants.OBJ_BLOB, len, is, true);
+		try {
+			ObjectId id = inserter.insert(OBJ_BLOB, len, is);
+			inserter.flush();
+			return id;
+		} finally {
+			inserter.release();
+		}
 	}
 
 	/**
 	 * Write a Tree to the object database.
 	 *
-	 * @param t
+	 * @param tree
 	 *            Tree
 	 * @return SHA-1 of the tree
 	 * @throws IOException
 	 */
-	public ObjectId writeTree(final Tree t) throws IOException {
-		final ByteArrayOutputStream o = new ByteArrayOutputStream();
-		final TreeEntry[] items = t.members();
-		for (int k = 0; k < items.length; k++) {
-			final TreeEntry e = items[k];
-			final ObjectId id = e.getId();
-
-			if (id == null)
-				throw new ObjectWritingException(MessageFormat.format(
-						JGitText.get().objectAtPathDoesNotHaveId, e.getFullName()));
-
-			e.getMode().copyTo(o);
-			o.write(' ');
-			o.write(e.getNameUTF8());
-			o.write(0);
-			id.copyRawTo(o);
+	public ObjectId writeTree(Tree tree) throws IOException {
+		try {
+			ObjectId id = inserter.insert(OBJ_TREE, inserter.format(tree));
+			inserter.flush();
+			return id;
+		} finally {
+			inserter.release();
 		}
-		return writeCanonicalTree(o.toByteArray());
 	}
 
 	/**
 	 * Write a canonical tree to the object database.
 	 *
-	 * @param b
+	 * @param treeData
 	 *            the canonical encoding of the tree object.
 	 * @return SHA-1 of the tree
 	 * @throws IOException
 	 */
-	public ObjectId writeCanonicalTree(final byte[] b) throws IOException {
-		return writeTree(b.length, new ByteArrayInputStream(b));
-	}
-
-	private ObjectId writeTree(final long len, final InputStream is)
-			throws IOException {
-		return writeObject(Constants.OBJ_TREE, len, is, true);
+	public ObjectId writeCanonicalTree(byte[] treeData) throws IOException {
+		try {
+			ObjectId id = inserter.insert(OBJ_TREE, treeData);
+			inserter.flush();
+			return id;
+		} finally {
+			inserter.release();
+		}
 	}
 
 	/**
 	 * Write a Commit to the object database
 	 *
-	 * @param c
+	 * @param commit
 	 *            Commit to store
 	 * @return SHA-1 of the commit
 	 * @throws IOException
 	 */
-	public ObjectId writeCommit(final Commit c) throws IOException {
-		final ByteArrayOutputStream os = new ByteArrayOutputStream();
-		String encoding = c.getEncoding();
-		if (encoding == null)
-			encoding = Constants.CHARACTER_ENCODING;
-		final OutputStreamWriter w = new OutputStreamWriter(os, encoding);
-
-		os.write(htree);
-		os.write(' ');
-		c.getTreeId().copyTo(os);
-		os.write('\n');
-
-		ObjectId[] ps = c.getParentIds();
-		for (int i=0; i<ps.length; ++i) {
-			os.write(hparent);
-			os.write(' ');
-			ps[i].copyTo(os);
-			os.write('\n');
+	public ObjectId writeCommit(Commit commit) throws IOException {
+		try {
+			ObjectId id = inserter.insert(OBJ_COMMIT, inserter.format(commit));
+			inserter.flush();
+			return id;
+		} finally {
+			inserter.release();
 		}
-
-		os.write(hauthor);
-		os.write(' ');
-		w.write(c.getAuthor().toExternalString());
-		w.flush();
-		os.write('\n');
-
-		os.write(hcommitter);
-		os.write(' ');
-		w.write(c.getCommitter().toExternalString());
-		w.flush();
-		os.write('\n');
-
-		if (!encoding.equals(Constants.CHARACTER_ENCODING)) {
-			os.write(hencoding);
-			os.write(' ');
-			os.write(Constants.encodeASCII(encoding));
-			os.write('\n');
-		}
-
-		os.write('\n');
-		w.write(c.getMessage());
-		w.flush();
-
-		return writeCommit(os.toByteArray());
-	}
-
-	private ObjectId writeTag(final byte[] b) throws IOException {
-		return writeTag(b.length, new ByteArrayInputStream(b));
 	}
 
 	/**
 	 * Write an annotated Tag to the object database
 	 *
-	 * @param c
+	 * @param tag
 	 *            Tag
 	 * @return SHA-1 of the tag
 	 * @throws IOException
 	 */
-	public ObjectId writeTag(final Tag c) throws IOException {
-		final ByteArrayOutputStream os = new ByteArrayOutputStream();
-		final OutputStreamWriter w = new OutputStreamWriter(os,
-				Constants.CHARSET);
-
-		w.write("object ");
-		c.getObjId().copyTo(w);
-		w.write('\n');
-
-		w.write("type ");
-		w.write(c.getType());
-		w.write("\n");
-
-		w.write("tag ");
-		w.write(c.getTag());
-		w.write("\n");
-
-		w.write("tagger ");
-		w.write(c.getAuthor().toExternalString());
-		w.write('\n');
-
-		w.write('\n');
-		w.write(c.getMessage());
-		w.close();
-
-		return writeTag(os.toByteArray());
-	}
-
-	private ObjectId writeCommit(final byte[] b) throws IOException {
-		return writeCommit(b.length, new ByteArrayInputStream(b));
-	}
-
-	private ObjectId writeCommit(final long len, final InputStream is)
-			throws IOException {
-		return writeObject(Constants.OBJ_COMMIT, len, is, true);
-	}
-
-	private ObjectId writeTag(final long len, final InputStream is)
-		throws IOException {
-		return writeObject(Constants.OBJ_TAG, len, is, true);
+	public ObjectId writeTag(Tag tag) throws IOException {
+		try {
+			ObjectId id = inserter.insert(OBJ_TAG, inserter.format(tag));
+			inserter.flush();
+			return id;
+		} finally {
+			inserter.release();
+		}
 	}
 
 	/**
 	 * Compute the SHA-1 of a blob without creating an object. This is for
 	 * figuring out if we already have a blob or not.
 	 *
-	 * @param len number of bytes to consume
-	 * @param is stream for read blob data from
+	 * @param len
+	 *            number of bytes to consume
+	 * @param is
+	 *            stream for read blob data from
 	 * @return SHA-1 of a looked for blob
 	 * @throws IOException
 	 */
-	public ObjectId computeBlobSha1(final long len, final InputStream is)
+	public ObjectId computeBlobSha1(long len, InputStream is)
 			throws IOException {
-		return writeObject(Constants.OBJ_BLOB, len, is, false);
+		return computeObjectSha1(OBJ_BLOB, len, is);
 	}
 
 	/**
@@ -313,119 +229,12 @@ public ObjectId computeBlobSha1(final long len, final InputStream is)
 	 * @return SHA-1 of data combined with type information
 	 * @throws IOException
 	 */
-	public ObjectId computeObjectSha1(final int type, final long len, final InputStream is)
+	public ObjectId computeObjectSha1(int type, long len, InputStream is)
 			throws IOException {
-		return writeObject(type, len, is, false);
-	}
-
-	ObjectId writeObject(final int type, long len, final InputStream is,
-			boolean store) throws IOException {
-		final File t;
-		final DeflaterOutputStream deflateStream;
-		final FileOutputStream fileStream;
-		ObjectId id = null;
-		Deflater def = null;
-
-		if (store) {
-			t = File.createTempFile("noz", null, r.getObjectsDirectory());
-			fileStream = new FileOutputStream(t);
-		} else {
-			t = null;
-			fileStream = null;
-		}
-
-		md.reset();
-		if (store) {
-			def = new Deflater(r.getConfig().getCore().getCompression());
-			deflateStream = new DeflaterOutputStream(fileStream, def);
-		} else
-			deflateStream = null;
-
 		try {
-			byte[] header;
-			int n;
-
-			header = Constants.encodedTypeString(type);
-			md.update(header);
-			if (deflateStream != null)
-				deflateStream.write(header);
-
-			md.update((byte) ' ');
-			if (deflateStream != null)
-				deflateStream.write((byte) ' ');
-
-			header = Constants.encodeASCII(len);
-			md.update(header);
-			if (deflateStream != null)
-				deflateStream.write(header);
-
-			md.update((byte) 0);
-			if (deflateStream != null)
-				deflateStream.write((byte) 0);
-
-			while (len > 0
-					&& (n = is.read(buf, 0, (int) Math.min(len, buf.length))) > 0) {
-				md.update(buf, 0, n);
-				if (deflateStream != null)
-					deflateStream.write(buf, 0, n);
-				len -= n;
-			}
-
-			if (len != 0)
-				throw new IOException("Input did not match supplied length. "
-						+ len + " bytes are missing.");
-
-			if (deflateStream != null ) {
-				deflateStream.close();
-				if (t != null)
-					t.setReadOnly();
-			}
-
-			id = ObjectId.fromRaw(md.digest());
+			return inserter.idFor(type, len, is);
 		} finally {
-			if (id == null && deflateStream != null) {
-				try {
-					deflateStream.close();
-				} finally {
-					t.delete();
-				}
-			}
-			if (def != null) {
-				def.end();
-			}
+			inserter.release();
 		}
-
-		if (t == null)
-			return id;
-
-		if (r.hasObject(id)) {
-			// Object is already in the repository so remove
-			// the temporary file.
-			//
-			t.delete();
-		} else {
-			final File o = r.toFile(id);
-			if (!t.renameTo(o)) {
-				// Maybe the directory doesn't exist yet as the object
-				// directories are always lazily created. Note that we
-				// try the rename first as the directory likely does exist.
-				//
-				o.getParentFile().mkdir();
-				if (!t.renameTo(o)) {
-					if (!r.hasObject(id)) {
-						// The object failed to be renamed into its proper
-						// location and it doesn't exist in the repository
-						// either. We really don't know what went wrong, so
-						// fail.
-						//
-						t.delete();
-						throw new ObjectWritingException("Unable to"
-								+ " create new object: " + o);
-					}
-				}
-			}
-		}
-
-		return id;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java
deleted file mode 100644
index 829832e..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.EOFException;
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel.MapMode;
-import java.text.MessageFormat;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.zip.CRC32;
-import java.util.zip.CheckedOutputStream;
-import java.util.zip.DataFormatException;
-
-import org.eclipse.jgit.JGitText;
-import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.errors.PackInvalidException;
-import org.eclipse.jgit.errors.PackMismatchException;
-import org.eclipse.jgit.util.NB;
-import org.eclipse.jgit.util.RawParseUtils;
-
-/**
- * A Git version 2 pack file representation. A pack file contains Git objects in
- * delta packed format yielding high compression of lots of object where some
- * objects are similar.
- */
-public class PackFile implements Iterable<PackIndex.MutableEntry> {
-	/** Sorts PackFiles to be most recently created to least recently created. */
-	public static Comparator<PackFile> SORT = new Comparator<PackFile>() {
-		public int compare(final PackFile a, final PackFile b) {
-			return b.packLastModified - a.packLastModified;
-		}
-	};
-
-	private final File idxFile;
-
-	private final File packFile;
-
-	final int hash;
-
-	private RandomAccessFile fd;
-
-	/** Serializes reads performed against {@link #fd}. */
-	private final Object readLock = new Object();
-
-	long length;
-
-	private int activeWindows;
-
-	private int activeCopyRawData;
-
-	private int packLastModified;
-
-	private volatile boolean invalid;
-
-	private byte[] packChecksum;
-
-	private PackIndex loadedIdx;
-
-	private PackReverseIndex reverseIdx;
-
-	/**
-	 * Construct a reader for an existing, pre-indexed packfile.
-	 *
-	 * @param idxFile
-	 *            path of the <code>.idx</code> file listing the contents.
-	 * @param packFile
-	 *            path of the <code>.pack</code> file holding the data.
-	 */
-	public PackFile(final File idxFile, final File packFile) {
-		this.idxFile = idxFile;
-		this.packFile = packFile;
-		this.packLastModified = (int) (packFile.lastModified() >> 10);
-
-		// Multiply by 31 here so we can more directly combine with another
-		// value in WindowCache.hash(), without doing the multiply there.
-		//
-		hash = System.identityHashCode(this) * 31;
-		length = Long.MAX_VALUE;
-	}
-
-	private synchronized PackIndex idx() throws IOException {
-		if (loadedIdx == null) {
-			if (invalid)
-				throw new PackInvalidException(packFile);
-
-			try {
-				final PackIndex idx = PackIndex.open(idxFile);
-
-				if (packChecksum == null)
-					packChecksum = idx.packChecksum;
-				else if (!Arrays.equals(packChecksum, idx.packChecksum))
-					throw new PackMismatchException(JGitText.get().packChecksumMismatch);
-
-				loadedIdx = idx;
-			} catch (IOException e) {
-				invalid = true;
-				throw e;
-			}
-		}
-		return loadedIdx;
-	}
-
-	final PackedObjectLoader resolveBase(final WindowCursor curs, final long ofs)
-			throws IOException {
-		return reader(curs, ofs);
-	}
-
-	/** @return the File object which locates this pack on disk. */
-	public File getPackFile() {
-		return packFile;
-	}
-
-	/**
-	 * Determine if an object is contained within the pack file.
-	 * <p>
-	 * For performance reasons only the index file is searched; the main pack
-	 * content is ignored entirely.
-	 * </p>
-	 *
-	 * @param id
-	 *            the object to look for. Must not be null.
-	 * @return true if the object is in this pack; false otherwise.
-	 * @throws IOException
-	 *             the index file cannot be loaded into memory.
-	 */
-	public boolean hasObject(final AnyObjectId id) throws IOException {
-		return idx().hasObject(id);
-	}
-
-	/**
-	 * Get an object from this pack.
-	 *
-	 * @param curs
-	 *            temporary working space associated with the calling thread.
-	 * @param id
-	 *            the object to obtain from the pack. Must not be null.
-	 * @return the object loader for the requested object if it is contained in
-	 *         this pack; null if the object was not found.
-	 * @throws IOException
-	 *             the pack file or the index could not be read.
-	 */
-	public PackedObjectLoader get(final WindowCursor curs, final AnyObjectId id)
-			throws IOException {
-		final long offset = idx().findOffset(id);
-		return 0 < offset ? reader(curs, offset) : null;
-	}
-
-	/**
-	 * Close the resources utilized by this repository
-	 */
-	public void close() {
-		UnpackedObjectCache.purge(this);
-		WindowCache.purge(this);
-		synchronized (this) {
-			loadedIdx = null;
-			reverseIdx = null;
-		}
-	}
-
-	/**
-	 * Provide iterator over entries in associated pack index, that should also
-	 * exist in this pack file. Objects returned by such iterator are mutable
-	 * during iteration.
-	 * <p>
-	 * Iterator returns objects in SHA-1 lexicographical order.
-	 * </p>
-	 *
-	 * @return iterator over entries of associated pack index
-	 *
-	 * @see PackIndex#iterator()
-	 */
-	public Iterator<PackIndex.MutableEntry> iterator() {
-		try {
-			return idx().iterator();
-		} catch (IOException e) {
-			return Collections.<PackIndex.MutableEntry> emptyList().iterator();
-		}
-	}
-
-	/**
-	 * Obtain the total number of objects available in this pack. This method
-	 * relies on pack index, giving number of effectively available objects.
-	 *
-	 * @return number of objects in index of this pack, likewise in this pack
-	 * @throws IOException
-	 *             the index file cannot be loaded into memory.
-	 */
-	long getObjectCount() throws IOException {
-		return idx().getObjectCount();
-	}
-
-	/**
-	 * Search for object id with the specified start offset in associated pack
-	 * (reverse) index.
-	 *
-	 * @param offset
-	 *            start offset of object to find
-	 * @return object id for this offset, or null if no object was found
-	 * @throws IOException
-	 *             the index file cannot be loaded into memory.
-	 */
-	ObjectId findObjectForOffset(final long offset) throws IOException {
-		return getReverseIdx().findObject(offset);
-	}
-
-	final UnpackedObjectCache.Entry readCache(final long position) {
-		return UnpackedObjectCache.get(this, position);
-	}
-
-	final void saveCache(final long position, final byte[] data, final int type) {
-		UnpackedObjectCache.store(this, position, data, type);
-	}
-
-	final byte[] decompress(final long position, final int totalSize,
-			final WindowCursor curs) throws DataFormatException, IOException {
-		final byte[] dstbuf = new byte[totalSize];
-		if (curs.inflate(this, position, dstbuf, 0) != totalSize)
-			throw new EOFException(MessageFormat.format(JGitText.get().shortCompressedStreamAt, position));
-		return dstbuf;
-	}
-
-	final void copyRawData(final PackedObjectLoader loader,
-			final OutputStream out, final byte buf[], final WindowCursor curs)
-			throws IOException {
-		final long objectOffset = loader.objectOffset;
-		final long dataOffset = objectOffset + loader.headerSize;
-		final long sz = findEndOffset(objectOffset) - dataOffset;
-		final PackIndex idx = idx();
-
-		if (idx.hasCRC32Support()) {
-			final CRC32 crc = new CRC32();
-			int headerCnt = loader.headerSize;
-			while (headerCnt > 0) {
-				final int toRead = Math.min(headerCnt, buf.length);
-				readFully(objectOffset, buf, 0, toRead, curs);
-				crc.update(buf, 0, toRead);
-				headerCnt -= toRead;
-			}
-			final CheckedOutputStream crcOut = new CheckedOutputStream(out, crc);
-			copyToStream(dataOffset, buf, sz, crcOut, curs);
-			final long computed = crc.getValue();
-
-			final ObjectId id = findObjectForOffset(objectOffset);
-			final long expected = idx.findCRC32(id);
-			if (computed != expected)
-				throw new CorruptObjectException(MessageFormat.format(
-						JGitText.get().objectAtHasBadZlibStream, objectOffset, getPackFile()));
-		} else {
-			try {
-				curs.inflateVerify(this, dataOffset);
-			} catch (DataFormatException dfe) {
-				final CorruptObjectException coe;
-				coe = new CorruptObjectException(MessageFormat.format(
-						JGitText.get().objectAtHasBadZlibStream, objectOffset, getPackFile()));
-				coe.initCause(dfe);
-				throw coe;
-			}
-			copyToStream(dataOffset, buf, sz, out, curs);
-		}
-	}
-
-	boolean supportsFastCopyRawData() throws IOException {
-		return idx().hasCRC32Support();
-	}
-
-	boolean invalid() {
-		return invalid;
-	}
-
-	private void readFully(final long position, final byte[] dstbuf,
-			int dstoff, final int cnt, final WindowCursor curs)
-			throws IOException {
-		if (curs.copy(this, position, dstbuf, dstoff, cnt) != cnt)
-			throw new EOFException();
-	}
-
-	private void copyToStream(long position, final byte[] buf, long cnt,
-			final OutputStream out, final WindowCursor curs)
-			throws IOException, EOFException {
-		while (cnt > 0) {
-			final int toRead = (int) Math.min(cnt, buf.length);
-			readFully(position, buf, 0, toRead, curs);
-			position += toRead;
-			cnt -= toRead;
-			out.write(buf, 0, toRead);
-		}
-	}
-
-	synchronized void beginCopyRawData() throws IOException {
-		if (++activeCopyRawData == 1 && activeWindows == 0)
-			doOpen();
-	}
-
-	synchronized void endCopyRawData() {
-		if (--activeCopyRawData == 0 && activeWindows == 0)
-			doClose();
-	}
-
-	synchronized boolean beginWindowCache() throws IOException {
-		if (++activeWindows == 1) {
-			if (activeCopyRawData == 0)
-				doOpen();
-			return true;
-		}
-		return false;
-	}
-
-	synchronized boolean endWindowCache() {
-		final boolean r = --activeWindows == 0;
-		if (r && activeCopyRawData == 0)
-			doClose();
-		return r;
-	}
-
-	private void doOpen() throws IOException {
-		try {
-			if (invalid)
-				throw new PackInvalidException(packFile);
-			synchronized (readLock) {
-				fd = new RandomAccessFile(packFile, "r");
-				length = fd.length();
-				onOpenPack();
-			}
-		} catch (IOException ioe) {
-			openFail();
-			throw ioe;
-		} catch (RuntimeException re) {
-			openFail();
-			throw re;
-		} catch (Error re) {
-			openFail();
-			throw re;
-		}
-	}
-
-	private void openFail() {
-		activeWindows = 0;
-		activeCopyRawData = 0;
-		invalid = true;
-		doClose();
-	}
-
-	private void doClose() {
-		synchronized (readLock) {
-			if (fd != null) {
-				try {
-					fd.close();
-				} catch (IOException err) {
-					// Ignore a close event. We had it open only for reading.
-					// There should not be errors related to network buffers
-					// not flushed, etc.
-				}
-				fd = null;
-			}
-		}
-	}
-
-	ByteArrayWindow read(final long pos, int size) throws IOException {
-		synchronized (readLock) {
-			if (length < pos + size)
-				size = (int) (length - pos);
-			final byte[] buf = new byte[size];
-			fd.seek(pos);
-			fd.readFully(buf, 0, size);
-			return new ByteArrayWindow(this, pos, buf);
-		}
-	}
-
-	ByteWindow mmap(final long pos, int size) throws IOException {
-		synchronized (readLock) {
-			if (length < pos + size)
-				size = (int) (length - pos);
-
-			MappedByteBuffer map;
-			try {
-				map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
-			} catch (IOException ioe1) {
-				// The most likely reason this failed is the JVM has run out
-				// of virtual memory. We need to discard quickly, and try to
-				// force the GC to finalize and release any existing mappings.
-				//
-				System.gc();
-				System.runFinalization();
-				map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
-			}
-
-			if (map.hasArray())
-				return new ByteArrayWindow(this, pos, map.array());
-			return new ByteBufferWindow(this, pos, map);
-		}
-	}
-
-	private void onOpenPack() throws IOException {
-		final PackIndex idx = idx();
-		final byte[] buf = new byte[20];
-
-		fd.seek(0);
-		fd.readFully(buf, 0, 12);
-		if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4)
-			throw new IOException(JGitText.get().notAPACKFile);
-		final long vers = NB.decodeUInt32(buf, 4);
-		final long packCnt = NB.decodeUInt32(buf, 8);
-		if (vers != 2 && vers != 3)
-			throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackVersion, vers));
-
-		if (packCnt != idx.getObjectCount())
-			throw new PackMismatchException(MessageFormat.format(
-					JGitText.get().packObjectCountMismatch, packCnt, idx.getObjectCount(), getPackFile()));
-
-		fd.seek(length - 20);
-		fd.read(buf, 0, 20);
-		if (!Arrays.equals(buf, packChecksum))
-			throw new PackMismatchException(MessageFormat.format(
-					JGitText.get().packObjectCountMismatch
-					, ObjectId.fromRaw(buf).name()
-					, ObjectId.fromRaw(idx.packChecksum).name()
-					, getPackFile()));
-	}
-
-	private PackedObjectLoader reader(final WindowCursor curs,
-			final long objOffset) throws IOException {
-		int p = 0;
-		final byte[] ib = curs.tempId;
-		readFully(objOffset, ib, 0, 20, curs);
-		int c = ib[p++] & 0xff;
-		final int typeCode = (c >> 4) & 7;
-		long dataSize = c & 15;
-		int shift = 4;
-		while ((c & 0x80) != 0) {
-			c = ib[p++] & 0xff;
-			dataSize += (c & 0x7f) << shift;
-			shift += 7;
-		}
-
-		switch (typeCode) {
-		case Constants.OBJ_COMMIT:
-		case Constants.OBJ_TREE:
-		case Constants.OBJ_BLOB:
-		case Constants.OBJ_TAG:
-			return new WholePackedObjectLoader(this, objOffset, p, typeCode,
-					(int) dataSize);
-
-		case Constants.OBJ_OFS_DELTA: {
-			c = ib[p++] & 0xff;
-			long ofs = c & 127;
-			while ((c & 128) != 0) {
-				ofs += 1;
-				c = ib[p++] & 0xff;
-				ofs <<= 7;
-				ofs += (c & 127);
-			}
-			return new DeltaOfsPackedObjectLoader(this, objOffset, p,
-					(int) dataSize, objOffset - ofs);
-		}
-		case Constants.OBJ_REF_DELTA: {
-			readFully(objOffset + p, ib, 0, 20, curs);
-			return new DeltaRefPackedObjectLoader(this, objOffset, p + 20,
-					(int) dataSize, ObjectId.fromRaw(ib));
-		}
-		default:
-			throw new IOException(MessageFormat.format(JGitText.get().unknownObjectType, typeCode));
-		}
-	}
-
-	private long findEndOffset(final long startOffset)
-			throws IOException, CorruptObjectException {
-		final long maxOffset = length - 20;
-		return getReverseIdx().findNextOffset(startOffset, maxOffset);
-	}
-
-	private synchronized PackReverseIndex getReverseIdx() throws IOException {
-		if (reverseIdx == null)
-			reverseIdx = new PackReverseIndex(idx());
-		return reverseIdx;
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java
deleted file mode 100644
index a348f1e..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.MessageDigest;
-import java.util.zip.CRC32;
-
-/** Custom output stream to support {@link PackWriter}. */
-final class PackOutputStream extends OutputStream {
-	private final OutputStream out;
-
-	private final CRC32 crc = new CRC32();
-
-	private final MessageDigest md = Constants.newMessageDigest();
-
-	private long count;
-
-	PackOutputStream(final OutputStream out) {
-		this.out = out;
-	}
-
-	@Override
-	public void write(final int b) throws IOException {
-		out.write(b);
-		crc.update(b);
-		md.update((byte) b);
-		count++;
-	}
-
-	@Override
-	public void write(final byte[] b, final int off, final int len)
-			throws IOException {
-		out.write(b, off, len);
-		crc.update(b, off, len);
-		md.update(b, off, len);
-		count += len;
-	}
-
-	@Override
-	public void flush() throws IOException {
-		out.flush();
-	}
-
-	/** @return total number of bytes written since stream start. */
-	long length() {
-		return count;
-	}
-
-	/** @return obtain the current CRC32 register. */
-	int getCRC32() {
-		return (int) crc.getValue();
-	}
-
-	/** Reinitialize the CRC32 register for a new region. */
-	void resetCRC32() {
-		crc.reset();
-	}
-
-	/** @return obtain the current SHA-1 digest. */
-	byte[] getDigest() {
-		return md.digest();
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java
deleted file mode 100644
index 48f41a5..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java
+++ /dev/null
@@ -1,1040 +0,0 @@
-/*
- * Copyright (C) 2008-2010, Google Inc.
- * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.MessageDigest;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.zip.Deflater;
-
-import org.eclipse.jgit.JGitText;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.revwalk.ObjectWalk;
-import org.eclipse.jgit.revwalk.RevFlag;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevSort;
-import org.eclipse.jgit.transport.PackedObjectInfo;
-import org.eclipse.jgit.util.NB;
-
-/**
- * <p>
- * PackWriter class is responsible for generating pack files from specified set
- * of objects from repository. This implementation produce pack files in format
- * version 2.
- * </p>
- * <p>
- * Source of objects may be specified in two ways:
- * <ul>
- * <li>(usually) by providing sets of interesting and uninteresting objects in
- * repository - all interesting objects and their ancestors except uninteresting
- * objects and their ancestors will be included in pack, or</li>
- * <li>by providing iterator of {@link RevObject} specifying exact list and
- * order of objects in pack</li>
- * </ul>
- * Typical usage consists of creating instance intended for some pack,
- * configuring options, preparing the list of objects by calling
- * {@link #preparePack(Iterator)} or
- * {@link #preparePack(Collection, Collection)}, and finally
- * producing the stream with {@link #writePack(OutputStream)}.
- * </p>
- * <p>
- * Class provide set of configurable options and {@link ProgressMonitor}
- * support, as operations may take a long time for big repositories. Deltas
- * searching algorithm is <b>NOT IMPLEMENTED</b> yet - this implementation
- * relies only on deltas and objects reuse.
- * </p>
- * <p>
- * This class is not thread safe, it is intended to be used in one thread, with
- * one instance per created pack. Subsequent calls to writePack result in
- * undefined behavior.
- * </p>
- */
-public class PackWriter {
-	/**
-	 * Title of {@link ProgressMonitor} task used during counting objects to
-	 * pack.
-	 *
-	 * @see #preparePack(Collection, Collection)
-	 */
-	public static final String COUNTING_OBJECTS_PROGRESS = JGitText.get().countingObjects;
-
-	/**
-	 * Title of {@link ProgressMonitor} task used during searching for objects
-	 * reuse or delta reuse.
-	 *
-	 * @see #writePack(OutputStream)
-	 */
-	public static final String SEARCHING_REUSE_PROGRESS = JGitText.get().compressingObjects;
-
-	/**
-	 * Title of {@link ProgressMonitor} task used during writing out pack
-	 * (objects)
-	 *
-	 * @see #writePack(OutputStream)
-	 */
-	public static final String WRITING_OBJECTS_PROGRESS = JGitText.get().writingObjects;
-
-	/**
-	 * Default value of deltas reuse option.
-	 *
-	 * @see #setReuseDeltas(boolean)
-	 */
-	public static final boolean DEFAULT_REUSE_DELTAS = true;
-
-	/**
-	 * Default value of objects reuse option.
-	 *
-	 * @see #setReuseObjects(boolean)
-	 */
-	public static final boolean DEFAULT_REUSE_OBJECTS = true;
-
-	/**
-	 * Default value of delta base as offset option.
-	 *
-	 * @see #setDeltaBaseAsOffset(boolean)
-	 */
-	public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET = false;
-
-	/**
-	 * Default value of maximum delta chain depth.
-	 *
-	 * @see #setMaxDeltaDepth(int)
-	 */
-	public static final int DEFAULT_MAX_DELTA_DEPTH = 50;
-
-	private static final int PACK_VERSION_GENERATED = 2;
-
-	@SuppressWarnings("unchecked")
-	private final List<ObjectToPack> objectsLists[] = new List[Constants.OBJ_TAG + 1];
-	{
-		objectsLists[0] = Collections.<ObjectToPack> emptyList();
-		objectsLists[Constants.OBJ_COMMIT] = new ArrayList<ObjectToPack>();
-		objectsLists[Constants.OBJ_TREE] = new ArrayList<ObjectToPack>();
-		objectsLists[Constants.OBJ_BLOB] = new ArrayList<ObjectToPack>();
-		objectsLists[Constants.OBJ_TAG] = new ArrayList<ObjectToPack>();
-	}
-
-	private final ObjectIdSubclassMap<ObjectToPack> objectsMap = new ObjectIdSubclassMap<ObjectToPack>();
-
-	// edge objects for thin packs
-	private final ObjectIdSubclassMap<ObjectId> edgeObjects = new ObjectIdSubclassMap<ObjectId>();
-
-	private final Repository db;
-
-	private PackOutputStream out;
-
-	private final Deflater deflater;
-
-	private ProgressMonitor initMonitor;
-
-	private ProgressMonitor writeMonitor;
-
-	private final byte[] buf = new byte[16384]; // 16 KB
-
-	private final WindowCursor windowCursor = new WindowCursor();
-
-	private List<ObjectToPack> sortedByName;
-
-	private byte packcsum[];
-
-	private boolean reuseDeltas = DEFAULT_REUSE_DELTAS;
-
-	private boolean reuseObjects = DEFAULT_REUSE_OBJECTS;
-
-	private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET;
-
-	private int maxDeltaDepth = DEFAULT_MAX_DELTA_DEPTH;
-
-	private int outputVersion;
-
-	private boolean thin;
-
-	private boolean ignoreMissingUninteresting = true;
-
-	/**
-	 * Create writer for specified repository.
-	 * <p>
-	 * Objects for packing are specified in {@link #preparePack(Iterator)} or
-	 * {@link #preparePack(Collection, Collection)}.
-	 *
-	 * @param repo
-	 *            repository where objects are stored.
-	 * @param monitor
-	 *            operations progress monitor, used within
-	 *            {@link #preparePack(Iterator)},
-	 *            {@link #preparePack(Collection, Collection)}
-	 *            , or {@link #writePack(OutputStream)}.
-	 */
-	public PackWriter(final Repository repo, final ProgressMonitor monitor) {
-		this(repo, monitor, monitor);
-	}
-
-	/**
-	 * Create writer for specified repository.
-	 * <p>
-	 * Objects for packing are specified in {@link #preparePack(Iterator)} or
-	 * {@link #preparePack(Collection, Collection)}.
-	 *
-	 * @param repo
-	 *            repository where objects are stored.
-	 * @param imonitor
-	 *            operations progress monitor, used within
-	 *            {@link #preparePack(Iterator)},
-	 *            {@link #preparePack(Collection, Collection)}
-	 * @param wmonitor
-	 *            operations progress monitor, used within
-	 *            {@link #writePack(OutputStream)}.
-	 */
-	public PackWriter(final Repository repo, final ProgressMonitor imonitor,
-			final ProgressMonitor wmonitor) {
-		this.db = repo;
-		initMonitor = imonitor == null ? NullProgressMonitor.INSTANCE : imonitor;
-		writeMonitor = wmonitor == null ? NullProgressMonitor.INSTANCE : wmonitor;
-		this.deflater = new Deflater(db.getConfig().getCore().getCompression());
-		outputVersion = repo.getConfig().getCore().getPackIndexVersion();
-	}
-
-	/**
-	 * Check whether object is configured to reuse deltas existing in
-	 * repository.
-	 * <p>
-	 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
-	 * </p>
-	 *
-	 * @return true if object is configured to reuse deltas; false otherwise.
-	 */
-	public boolean isReuseDeltas() {
-		return reuseDeltas;
-	}
-
-	/**
-	 * Set reuse deltas configuration option for this writer. When enabled,
-	 * writer will search for delta representation of object in repository and
-	 * use it if possible. Normally, only deltas with base to another object
-	 * existing in set of objects to pack will be used. Exception is however
-	 * thin-pack (see
-	 * {@link #preparePack(Collection, Collection)} and
-	 * {@link #preparePack(Iterator)}) where base object must exist on other
-	 * side machine.
-	 * <p>
-	 * When raw delta data is directly copied from a pack file, checksum is
-	 * computed to verify data.
-	 * </p>
-	 * <p>
-	 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
-	 * </p>
-	 *
-	 * @param reuseDeltas
-	 *            boolean indicating whether or not try to reuse deltas.
-	 */
-	public void setReuseDeltas(boolean reuseDeltas) {
-		this.reuseDeltas = reuseDeltas;
-	}
-
-	/**
-	 * Checks whether object is configured to reuse existing objects
-	 * representation in repository.
-	 * <p>
-	 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
-	 * </p>
-	 *
-	 * @return true if writer is configured to reuse objects representation from
-	 *         pack; false otherwise.
-	 */
-	public boolean isReuseObjects() {
-		return reuseObjects;
-	}
-
-	/**
-	 * Set reuse objects configuration option for this writer. If enabled,
-	 * writer searches for representation in a pack file. If possible,
-	 * compressed data is directly copied from such a pack file. Data checksum
-	 * is verified.
-	 * <p>
-	 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
-	 * </p>
-	 *
-	 * @param reuseObjects
-	 *            boolean indicating whether or not writer should reuse existing
-	 *            objects representation.
-	 */
-	public void setReuseObjects(boolean reuseObjects) {
-		this.reuseObjects = reuseObjects;
-	}
-
-	/**
-	 * Check whether writer can store delta base as an offset (new style
-	 * reducing pack size) or should store it as an object id (legacy style,
-	 * compatible with old readers).
-	 * <p>
-	 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
-	 * </p>
-	 *
-	 * @return true if delta base is stored as an offset; false if it is stored
-	 *         as an object id.
-	 */
-	public boolean isDeltaBaseAsOffset() {
-		return deltaBaseAsOffset;
-	}
-
-	/**
-	 * Set writer delta base format. Delta base can be written as an offset in a
-	 * pack file (new approach reducing file size) or as an object id (legacy
-	 * approach, compatible with old readers).
-	 * <p>
-	 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
-	 * </p>
-	 *
-	 * @param deltaBaseAsOffset
-	 *            boolean indicating whether delta base can be stored as an
-	 *            offset.
-	 */
-	public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) {
-		this.deltaBaseAsOffset = deltaBaseAsOffset;
-	}
-
-	/**
-	 * Get maximum depth of delta chain set up for this writer. Generated chains
-	 * are not longer than this value.
-	 * <p>
-	 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
-	 * </p>
-	 *
-	 * @return maximum delta chain depth.
-	 */
-	public int getMaxDeltaDepth() {
-		return maxDeltaDepth;
-	}
-
-	/**
-	 * Set up maximum depth of delta chain for this writer. Generated chains are
-	 * not longer than this value. Too low value causes low compression level,
-	 * while too big makes unpacking (reading) longer.
-	 * <p>
-	 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
-	 * </p>
-	 *
-	 * @param maxDeltaDepth
-	 *            maximum delta chain depth.
-	 */
-	public void setMaxDeltaDepth(int maxDeltaDepth) {
-		this.maxDeltaDepth = maxDeltaDepth;
-	}
-
-	/** @return true if this writer is producing a thin pack. */
-	public boolean isThin() {
-		return thin;
-	}
-
-	/**
-	 * @param packthin
-	 *            a boolean indicating whether writer may pack objects with
-	 *            delta base object not within set of objects to pack, but
-	 *            belonging to party repository (uninteresting/boundary) as
-	 *            determined by set; this kind of pack is used only for
-	 *            transport; true - to produce thin pack, false - otherwise.
-	 */
-	public void setThin(final boolean packthin) {
-		thin = packthin;
-	}
-
-	/**
-	 * @return true to ignore objects that are uninteresting and also not found
-	 *         on local disk; false to throw a {@link MissingObjectException}
-	 *         out of {@link #preparePack(Collection, Collection)} if an
-	 *         uninteresting object is not in the source repository. By default,
-	 *         true, permitting gracefully ignoring of uninteresting objects.
-	 */
-	public boolean isIgnoreMissingUninteresting() {
-		return ignoreMissingUninteresting;
-	}
-
-	/**
-	 * @param ignore
-	 *            true if writer should ignore non existing uninteresting
-	 *            objects during construction set of objects to pack; false
-	 *            otherwise - non existing uninteresting objects may cause
-	 *            {@link MissingObjectException}
-	 */
-	public void setIgnoreMissingUninteresting(final boolean ignore) {
-		ignoreMissingUninteresting = ignore;
-	}
-
-	/**
-	 * Set the pack index file format version this instance will create.
-	 *
-	 * @param version
-	 *            the version to write. The special version 0 designates the
-	 *            oldest (most compatible) format available for the objects.
-	 * @see PackIndexWriter
-	 */
-	public void setIndexVersion(final int version) {
-		outputVersion = version;
-	}
-
-	/**
-	 * Returns objects number in a pack file that was created by this writer.
-	 *
-	 * @return number of objects in pack.
-	 */
-	public int getObjectsNumber() {
-		return objectsMap.size();
-	}
-
-	/**
-	 * Prepare the list of objects to be written to the pack stream.
-	 * <p>
-	 * Iterator <b>exactly</b> determines which objects are included in a pack
-	 * and order they appear in pack (except that objects order by type is not
-	 * needed at input). This order should conform general rules of ordering
-	 * objects in git - by recency and path (type and delta-base first is
-	 * internally secured) and responsibility for guaranteeing this order is on
-	 * a caller side. Iterator must return each id of object to write exactly
-	 * once.
-	 * </p>
-	 * <p>
-	 * When iterator returns object that has {@link RevFlag#UNINTERESTING} flag,
-	 * this object won't be included in an output pack. Instead, it is recorded
-	 * as edge-object (known to remote repository) for thin-pack. In such a case
-	 * writer may pack objects with delta base object not within set of objects
-	 * to pack, but belonging to party repository - those marked with
-	 * {@link RevFlag#UNINTERESTING} flag. This type of pack is used only for
-	 * transport.
-	 * </p>
-	 *
-	 * @param objectsSource
-	 *            iterator of object to store in a pack; order of objects within
-	 *            each type is important, ordering by type is not needed;
-	 *            allowed types for objects are {@link Constants#OBJ_COMMIT},
-	 *            {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and
-	 *            {@link Constants#OBJ_TAG}; objects returned by iterator may
-	 *            be later reused by caller as object id and type are internally
-	 *            copied in each iteration; if object returned by iterator has
-	 *            {@link RevFlag#UNINTERESTING} flag set, it won't be included
-	 *            in a pack, but is considered as edge-object for thin-pack.
-	 * @throws IOException
-	 *             when some I/O problem occur during reading objects.
-	 */
-	public void preparePack(final Iterator<RevObject> objectsSource)
-			throws IOException {
-		while (objectsSource.hasNext()) {
-			addObject(objectsSource.next());
-		}
-	}
-
-	/**
-	 * Prepare the list of objects to be written to the pack stream.
-	 * <p>
-	 * Basing on these 2 sets, another set of objects to put in a pack file is
-	 * created: this set consists of all objects reachable (ancestors) from
-	 * interesting objects, except uninteresting objects and their ancestors.
-	 * This method uses class {@link ObjectWalk} extensively to find out that
-	 * appropriate set of output objects and their optimal order in output pack.
-	 * Order is consistent with general git in-pack rules: sort by object type,
-	 * recency, path and delta-base first.
-	 * </p>
-	 *
-	 * @param interestingObjects
-	 *            collection of objects to be marked as interesting (start
-	 *            points of graph traversal).
-	 * @param uninterestingObjects
-	 *            collection of objects to be marked as uninteresting (end
-	 *            points of graph traversal).
-	 * @throws IOException
-	 *             when some I/O problem occur during reading objects.
-	 */
-	public void preparePack(
-			final Collection<? extends ObjectId> interestingObjects,
-			final Collection<? extends ObjectId> uninterestingObjects)
-			throws IOException {
-		ObjectWalk walker = setUpWalker(interestingObjects,
-				uninterestingObjects);
-		findObjectsToPack(walker);
-	}
-
-	/**
-	 * Determine if the pack file will contain the requested object.
-	 *
-	 * @param id
-	 *            the object to test the existence of.
-	 * @return true if the object will appear in the output pack file.
-	 */
-	public boolean willInclude(final AnyObjectId id) {
-		return objectsMap.get(id) != null;
-	}
-
-	/**
-	 * Computes SHA-1 of lexicographically sorted objects ids written in this
-	 * pack, as used to name a pack file in repository.
-	 *
-	 * @return ObjectId representing SHA-1 name of a pack that was created.
-	 */
-	public ObjectId computeName() {
-		final MessageDigest md = Constants.newMessageDigest();
-		for (ObjectToPack otp : sortByName()) {
-			otp.copyRawTo(buf, 0);
-			md.update(buf, 0, Constants.OBJECT_ID_LENGTH);
-		}
-		return ObjectId.fromRaw(md.digest());
-	}
-
-	/**
-	 * Create an index file to match the pack file just written.
-	 * <p>
-	 * This method can only be invoked after {@link #preparePack(Iterator)} or
-	 * {@link #preparePack(Collection, Collection)} has been
-	 * invoked and completed successfully. Writing a corresponding index is an
-	 * optional feature that not all pack users may require.
-	 *
-	 * @param indexStream
-	 *            output for the index data. Caller is responsible for closing
-	 *            this stream.
-	 * @throws IOException
-	 *             the index data could not be written to the supplied stream.
-	 */
-	public void writeIndex(final OutputStream indexStream) throws IOException {
-		final List<ObjectToPack> list = sortByName();
-		final PackIndexWriter iw;
-		if (outputVersion <= 0)
-			iw = PackIndexWriter.createOldestPossible(indexStream, list);
-		else
-			iw = PackIndexWriter.createVersion(indexStream, outputVersion);
-		iw.write(list, packcsum);
-	}
-
-	private List<ObjectToPack> sortByName() {
-		if (sortedByName == null) {
-			sortedByName = new ArrayList<ObjectToPack>(objectsMap.size());
-			for (List<ObjectToPack> list : objectsLists) {
-				for (ObjectToPack otp : list)
-					sortedByName.add(otp);
-			}
-			Collections.sort(sortedByName);
-		}
-		return sortedByName;
-	}
-
-	/**
-	 * Write the prepared pack to the supplied stream.
-	 * <p>
-	 * At first, this method collects and sorts objects to pack, then deltas
-	 * search is performed if set up accordingly, finally pack stream is
-	 * written. {@link ProgressMonitor} tasks {@value #SEARCHING_REUSE_PROGRESS}
-	 * (only if reuseDeltas or reuseObjects is enabled) and
-	 * {@value #WRITING_OBJECTS_PROGRESS} are updated during packing.
-	 * </p>
-	 * <p>
-	 * All reused objects data checksum (Adler32/CRC32) is computed and
-	 * validated against existing checksum.
-	 * </p>
-	 *
-	 * @param packStream
-	 *            output stream of pack data. The stream should be buffered by
-	 *            the caller. The caller is responsible for closing the stream.
-	 * @throws IOException
-	 *             an error occurred reading a local object's data to include in
-	 *             the pack, or writing compressed object data to the output
-	 *             stream.
-	 */
-	public void writePack(OutputStream packStream) throws IOException {
-		if (reuseDeltas || reuseObjects)
-			searchForReuse();
-
-		out = new PackOutputStream(packStream);
-
-		writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber());
-		writeHeader();
-		writeObjects();
-		writeChecksum();
-
-		windowCursor.release();
-		writeMonitor.endTask();
-	}
-
-	private void searchForReuse() throws IOException {
-		initMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber());
-		final Collection<PackedObjectLoader> reuseLoaders = new ArrayList<PackedObjectLoader>();
-		for (List<ObjectToPack> list : objectsLists) {
-			for (ObjectToPack otp : list) {
-				if (initMonitor.isCancelled())
-					throw new IOException(
-							JGitText.get().packingCancelledDuringObjectsWriting);
-				reuseLoaders.clear();
-				searchForReuse(reuseLoaders, otp);
-				initMonitor.update(1);
-			}
-		}
-
-		initMonitor.endTask();
-	}
-
-	private void searchForReuse(
-			final Collection<PackedObjectLoader> reuseLoaders,
-			final ObjectToPack otp) throws IOException {
-		db.openObjectInAllPacks(otp, reuseLoaders, windowCursor);
-		if (reuseDeltas) {
-			selectDeltaReuseForObject(otp, reuseLoaders);
-		}
-		// delta reuse is preferred over object reuse
-		if (reuseObjects && !otp.isCopyable()) {
-			selectObjectReuseForObject(otp, reuseLoaders);
-		}
-	}
-
-	private void selectDeltaReuseForObject(final ObjectToPack otp,
-			final Collection<PackedObjectLoader> loaders) throws IOException {
-		PackedObjectLoader bestLoader = null;
-		ObjectId bestBase = null;
-
-		for (PackedObjectLoader loader : loaders) {
-			ObjectId idBase = loader.getDeltaBase();
-			if (idBase == null)
-				continue;
-			ObjectToPack otpBase = objectsMap.get(idBase);
-
-			// only if base is in set of objects to write or thin-pack's edge
-			if ((otpBase != null || (thin && edgeObjects.get(idBase) != null))
-			// select smallest possible delta if > 1 available
-					&& isBetterDeltaReuseLoader(bestLoader, loader)) {
-				bestLoader = loader;
-				bestBase = (otpBase != null ? otpBase : idBase);
-			}
-		}
-
-		if (bestLoader != null) {
-			otp.setCopyFromPack(bestLoader);
-			otp.setDeltaBase(bestBase);
-		}
-	}
-
-	private static boolean isBetterDeltaReuseLoader(
-			PackedObjectLoader currentLoader, PackedObjectLoader loader)
-			throws IOException {
-		if (currentLoader == null)
-			return true;
-		if (loader.getRawSize() < currentLoader.getRawSize())
-			return true;
-		return (loader.getRawSize() == currentLoader.getRawSize()
-				&& loader.supportsFastCopyRawData() && !currentLoader
-				.supportsFastCopyRawData());
-	}
-
-	private void selectObjectReuseForObject(final ObjectToPack otp,
-			final Collection<PackedObjectLoader> loaders) {
-		for (final PackedObjectLoader loader : loaders) {
-			if (loader instanceof WholePackedObjectLoader) {
-				otp.setCopyFromPack(loader);
-				return;
-			}
-		}
-	}
-
-	private void writeHeader() throws IOException {
-		System.arraycopy(Constants.PACK_SIGNATURE, 0, buf, 0, 4);
-		NB.encodeInt32(buf, 4, PACK_VERSION_GENERATED);
-		NB.encodeInt32(buf, 8, getObjectsNumber());
-		out.write(buf, 0, 12);
-	}
-
-	private void writeObjects() throws IOException {
-		for (List<ObjectToPack> list : objectsLists) {
-			for (ObjectToPack otp : list) {
-				if (writeMonitor.isCancelled())
-					throw new IOException(
-							JGitText.get().packingCancelledDuringObjectsWriting);
-				if (!otp.isWritten())
-					writeObject(otp);
-			}
-		}
-	}
-
-	private void writeObject(final ObjectToPack otp) throws IOException {
-		otp.markWantWrite();
-		if (otp.isDeltaRepresentation()) {
-			ObjectToPack deltaBase = otp.getDeltaBase();
-			assert deltaBase != null || thin;
-			if (deltaBase != null && !deltaBase.isWritten()) {
-				if (deltaBase.wantWrite()) {
-					otp.clearDeltaBase(); // cycle detected
-					otp.clearSourcePack();
-				} else {
-					writeObject(deltaBase);
-				}
-			}
-		}
-
-		assert !otp.isWritten();
-
-		out.resetCRC32();
-		otp.setOffset(out.length());
-
-		final PackedObjectLoader reuse = open(otp);
-		if (reuse != null) {
-			try {
-				if (otp.isDeltaRepresentation())
-					writeDeltaObjectHeader(otp, reuse);
-				else
-					writeObjectHeader(otp.getType(), reuse.getSize());
-				reuse.copyRawData(out, buf, windowCursor);
-			} finally {
-				reuse.endCopyRawData();
-			}
-		} else if (otp.isDeltaRepresentation()) {
-			throw new IOException(JGitText.get().creatingDeltasIsNotImplemented);
-		} else {
-			writeWholeObjectDeflate(otp);
-		}
-		otp.setCRC(out.getCRC32());
-
-		writeMonitor.update(1);
-	}
-
-	private PackedObjectLoader open(final ObjectToPack otp) throws IOException {
-		while (otp.isCopyable()) {
-			try {
-				PackedObjectLoader reuse = otp.getCopyLoader(windowCursor);
-				reuse.beginCopyRawData();
-				return reuse;
-			} catch (IOException err) {
-				// The pack we found the object in originally is gone, or
-				// it has been overwritten with a different layout.
-				//
-				otp.clearDeltaBase();
-				otp.clearSourcePack();
-				searchForReuse(new ArrayList<PackedObjectLoader>(), otp);
-				continue;
-			}
-		}
-		return null;
-	}
-
-	private void writeWholeObjectDeflate(final ObjectToPack otp)
-			throws IOException {
-		final ObjectLoader loader = db.openObject(windowCursor, otp);
-		final byte[] data = loader.getCachedBytes();
-		writeObjectHeader(otp.getType(), data.length);
-		deflater.reset();
-		deflater.setInput(data, 0, data.length);
-		deflater.finish();
-		do {
-			final int n = deflater.deflate(buf, 0, buf.length);
-			if (n > 0)
-				out.write(buf, 0, n);
-		} while (!deflater.finished());
-	}
-
-	private void writeDeltaObjectHeader(final ObjectToPack otp,
-			final PackedObjectLoader reuse) throws IOException {
-		if (deltaBaseAsOffset && otp.getDeltaBase() != null) {
-			writeObjectHeader(Constants.OBJ_OFS_DELTA, reuse.getRawSize());
-
-			final ObjectToPack deltaBase = otp.getDeltaBase();
-			long offsetDiff = otp.getOffset() - deltaBase.getOffset();
-			int pos = buf.length - 1;
-			buf[pos] = (byte) (offsetDiff & 0x7F);
-			while ((offsetDiff >>= 7) > 0) {
-				buf[--pos] = (byte) (0x80 | (--offsetDiff & 0x7F));
-			}
-
-			out.write(buf, pos, buf.length - pos);
-		} else {
-			writeObjectHeader(Constants.OBJ_REF_DELTA, reuse.getRawSize());
-			otp.getDeltaBaseId().copyRawTo(buf, 0);
-			out.write(buf, 0, Constants.OBJECT_ID_LENGTH);
-		}
-	}
-
-	private void writeObjectHeader(final int objectType, long dataLength)
-			throws IOException {
-		long nextLength = dataLength >>> 4;
-		int size = 0;
-		buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00)
-				| (objectType << 4) | (dataLength & 0x0F));
-		dataLength = nextLength;
-		while (dataLength > 0) {
-			nextLength >>>= 7;
-			buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F));
-			dataLength = nextLength;
-		}
-		out.write(buf, 0, size);
-	}
-
-	private void writeChecksum() throws IOException {
-		packcsum = out.getDigest();
-		out.write(packcsum);
-	}
-
-	private ObjectWalk setUpWalker(
-			final Collection<? extends ObjectId> interestingObjects,
-			final Collection<? extends ObjectId> uninterestingObjects)
-			throws MissingObjectException, IOException,
-			IncorrectObjectTypeException {
-		final ObjectWalk walker = new ObjectWalk(db);
-		walker.setRetainBody(false);
-		walker.sort(RevSort.TOPO);
-		walker.sort(RevSort.COMMIT_TIME_DESC, true);
-		if (thin)
-			walker.sort(RevSort.BOUNDARY, true);
-
-		for (ObjectId id : interestingObjects) {
-			RevObject o = walker.parseAny(id);
-			walker.markStart(o);
-		}
-		if (uninterestingObjects != null) {
-			for (ObjectId id : uninterestingObjects) {
-				final RevObject o;
-				try {
-					o = walker.parseAny(id);
-				} catch (MissingObjectException x) {
-					if (ignoreMissingUninteresting)
-						continue;
-					throw x;
-				}
-				walker.markUninteresting(o);
-			}
-		}
-		return walker;
-	}
-
-	private void findObjectsToPack(final ObjectWalk walker)
-			throws MissingObjectException, IncorrectObjectTypeException,
-			IOException {
-		initMonitor.beginTask(COUNTING_OBJECTS_PROGRESS,
-				ProgressMonitor.UNKNOWN);
-		RevObject o;
-
-		while ((o = walker.next()) != null) {
-			addObject(o);
-			initMonitor.update(1);
-		}
-		while ((o = walker.nextObject()) != null) {
-			addObject(o);
-			initMonitor.update(1);
-		}
-		initMonitor.endTask();
-	}
-
-	/**
-	 * Include one object to the output file.
-	 * <p>
-	 * Objects are written in the order they are added. If the same object is
-	 * added twice, it may be written twice, creating a larger than necessary
-	 * file.
-	 *
-	 * @param object
-	 *            the object to add.
-	 * @throws IncorrectObjectTypeException
-	 *             the object is an unsupported type.
-	 */
-	public void addObject(final RevObject object)
-			throws IncorrectObjectTypeException {
-		if (object.has(RevFlag.UNINTERESTING)) {
-			edgeObjects.add(object);
-			thin = true;
-			return;
-		}
-
-		final ObjectToPack otp = new ObjectToPack(object, object.getType());
-		try {
-			objectsLists[object.getType()].add(otp);
-		} catch (ArrayIndexOutOfBoundsException x) {
-			throw new IncorrectObjectTypeException(object,
-					JGitText.get().incorrectObjectType_COMMITnorTREEnorBLOBnorTAG);
-		} catch (UnsupportedOperationException x) {
-			// index pointing to "dummy" empty list
-			throw new IncorrectObjectTypeException(object,
-					JGitText.get().incorrectObjectType_COMMITnorTREEnorBLOBnorTAG);
-		}
-		objectsMap.add(otp);
-	}
-
-	/**
-	 * Class holding information about object that is going to be packed by
-	 * {@link PackWriter}. Information include object representation in a
-	 * pack-file and object status.
-	 *
-	 */
-	static class ObjectToPack extends PackedObjectInfo {
-		/** Other object being packed that this will delta against. */
-		private ObjectId deltaBase;
-
-		/** Pack to reuse compressed data from, otherwise null. */
-		private PackFile copyFromPack;
-
-		/** Offset of the object's header in {@link #copyFromPack}. */
-		private long copyOffset;
-
-		/**
-		 * Bit field, from bit 0 to bit 31:
-		 * <ul>
-		 * <li>1 bit: wantWrite</li>
-		 * <li>3 bits: type</li>
-		 * <li>28 bits: deltaDepth</li>
-		 * </ul>
-		 */
-		private int flags;
-
-		/**
-		 * Construct object for specified object id. <br/> By default object is
-		 * marked as not written and non-delta packed (as a whole object).
-		 *
-		 * @param src
-		 *            object id of object for packing
-		 * @param type
-		 *            real type code of the object, not its in-pack type.
-		 */
-		ObjectToPack(AnyObjectId src, final int type) {
-			super(src);
-			flags |= type << 1;
-		}
-
-		/**
-		 * @return delta base object id if object is going to be packed in delta
-		 *         representation; null otherwise - if going to be packed as a
-		 *         whole object.
-		 */
-		ObjectId getDeltaBaseId() {
-			return deltaBase;
-		}
-
-		/**
-		 * @return delta base object to pack if object is going to be packed in
-		 *         delta representation and delta is specified as object to
-		 *         pack; null otherwise - if going to be packed as a whole
-		 *         object or delta base is specified only as id.
-		 */
-		ObjectToPack getDeltaBase() {
-			if (deltaBase instanceof ObjectToPack)
-				return (ObjectToPack) deltaBase;
-			return null;
-		}
-
-		/**
-		 * Set delta base for the object. Delta base set by this method is used
-		 * by {@link PackWriter} to write object - determines its representation
-		 * in a created pack.
-		 *
-		 * @param deltaBase
-		 *            delta base object or null if object should be packed as a
-		 *            whole object.
-		 *
-		 */
-		void setDeltaBase(ObjectId deltaBase) {
-			this.deltaBase = deltaBase;
-		}
-
-		void clearDeltaBase() {
-			this.deltaBase = null;
-		}
-
-		/**
-		 * @return true if object is going to be written as delta; false
-		 *         otherwise.
-		 */
-		boolean isDeltaRepresentation() {
-			return deltaBase != null;
-		}
-
-		/**
-		 * Check if object is already written in a pack. This information is
-		 * used to achieve delta-base precedence in a pack file.
-		 *
-		 * @return true if object is already written; false otherwise.
-		 */
-		boolean isWritten() {
-			return getOffset() != 0;
-		}
-
-		boolean isCopyable() {
-			return copyFromPack != null;
-		}
-
-		PackedObjectLoader getCopyLoader(WindowCursor curs) throws IOException {
-			return copyFromPack.resolveBase(curs, copyOffset);
-		}
-
-		void setCopyFromPack(PackedObjectLoader loader) {
-			this.copyFromPack = loader.pack;
-			this.copyOffset = loader.objectOffset;
-		}
-
-		void clearSourcePack() {
-			copyFromPack = null;
-		}
-
-		int getType() {
-			return (flags>>1) & 0x7;
-		}
-
-		int getDeltaDepth() {
-			return flags >>> 4;
-		}
-
-		void updateDeltaDepth() {
-			final int d;
-			if (deltaBase instanceof ObjectToPack)
-				d = ((ObjectToPack) deltaBase).getDeltaDepth() + 1;
-			else if (deltaBase != null)
-				d = 1;
-			else
-				d = 0;
-			flags = (d << 4) | flags & 0x15;
-		}
-
-		boolean wantWrite() {
-			return (flags & 1) == 1;
-		}
-
-		void markWantWrite() {
-			flags |= 1;
-		}
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java
deleted file mode 100644
index 026b008..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2009, Google Inc.
- * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * Base class for a set of object loader classes for packed objects.
- */
-abstract class PackedObjectLoader extends ObjectLoader {
-	protected final PackFile pack;
-
-	/** Position of the first byte of the object's header. */
-	protected final long objectOffset;
-
-	/** Bytes used to express the object header, including delta reference. */
-	protected final int headerSize;
-
-	protected int objectType;
-
-	protected int objectSize;
-
-	protected byte[] cachedBytes;
-
-	PackedObjectLoader(final PackFile pr, final long objectOffset,
-			final int headerSize) {
-		pack = pr;
-		this.objectOffset = objectOffset;
-		this.headerSize = headerSize;
-	}
-
-	/**
-	 * Force this object to be loaded into memory and pinned in this loader.
-	 * <p>
-	 * Once materialized, subsequent get operations for the following methods
-	 * will always succeed without raising an exception, as all information is
-	 * pinned in memory by this loader instance.
-	 * <ul>
-	 * <li>{@link #getType()}</li>
-	 * <li>{@link #getSize()}</li>
-	 * <li>{@link #getBytes()}, {@link #getCachedBytes}</li>
-	 * <li>{@link #getRawSize()}</li>
-	 * <li>{@link #getRawType()}</li>
-	 * </ul>
-	 *
-	 * @param curs
-	 *            temporary thread storage during data access.
-	 * @throws IOException
-	 *             the object cannot be read.
-	 */
-	public abstract void materialize(WindowCursor curs) throws IOException;
-
-	public final int getType() {
-		return objectType;
-	}
-
-	public final long getSize() {
-		return objectSize;
-	}
-
-	@Override
-	public final byte[] getCachedBytes() {
-		return cachedBytes;
-	}
-
-	/**
-	 * @return offset of object header within pack file
-	 */
-	public final long getObjectOffset() {
-		return objectOffset;
-	}
-
-	/**
-	 * Peg the pack file open to support data copying.
-	 * <p>
-	 * Applications trying to copy raw pack data should ensure the pack stays
-	 * open and available throughout the entire copy. To do that use:
-	 *
-	 * <pre>
-	 * loader.beginCopyRawData();
-	 * try {
-	 * 	loader.copyRawData(out, tmpbuf, curs);
-	 * } finally {
-	 * 	loader.endCopyRawData();
-	 * }
-	 * </pre>
-	 *
-	 * @throws IOException
-	 *             this loader contains stale information and cannot be used.
-	 *             The most likely cause is the underlying pack file has been
-	 *             deleted, and the object has moved to another pack file.
-	 */
-	public void beginCopyRawData() throws IOException {
-		pack.beginCopyRawData();
-	}
-
-	/**
-	 * Copy raw object representation from storage to provided output stream.
-	 * <p>
-	 * Copied data doesn't include object header. User must provide temporary
-	 * buffer used during copying by underlying I/O layer.
-	 * </p>
-	 *
-	 * @param out
-	 *            output stream when data is copied. No buffering is guaranteed.
-	 * @param buf
-	 *            temporary buffer used during copying. Recommended size is at
-	 *            least few kB.
-	 * @param curs
-	 *            temporary thread storage during data access.
-	 * @throws IOException
-	 *             when the object cannot be read.
-	 * @see #beginCopyRawData()
-	 */
-	public void copyRawData(OutputStream out, byte buf[], WindowCursor curs)
-			throws IOException {
-		pack.copyRawData(this, out, buf, curs);
-	}
-
-	/** Release resources after {@link #beginCopyRawData()}. */
-	public void endCopyRawData() {
-		pack.endCopyRawData();
-	}
-
-	/**
-	 * @return true if this loader is capable of fast raw-data copying basing on
-	 *         compressed data checksum; false if raw-data copying needs
-	 *         uncompressing and compressing data
-	 * @throws IOException
-	 *             the index file format cannot be determined.
-	 */
-	public boolean supportsFastCopyRawData() throws IOException {
-		return pack.supportsFastCopyRawData();
-	}
-
-	/**
-	 * @return id of delta base object for this object representation. null if
-	 *         object is not stored as delta.
-	 * @throws IOException
-	 *             when delta base cannot read.
-	 */
-	public abstract ObjectId getDeltaBase() throws IOException;
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
index 522f847..0406684 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
@@ -77,7 +77,7 @@ public class PersonIdent {
 	 * @param repo
 	 */
 	public PersonIdent(final Repository repo) {
-		final RepositoryConfig config = repo.getConfig();
+		final UserConfig config = repo.getConfig().get(UserConfig.KEY);
 		name = config.getCommitterName();
 		emailAddress = config.getCommitterEmail();
 		when = SystemReader.getInstance().getCurrentTime();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
index e04a587..e6f8933 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -440,7 +440,12 @@ public Result forceUpdate() throws IOException {
 	 *             an unexpected IO error occurred while writing changes.
 	 */
 	public Result update() throws IOException {
-		return update(new RevWalk(getRepository()));
+		RevWalk rw = new RevWalk(getRepository());
+		try {
+			return update(rw);
+		} finally {
+			rw.release();
+		}
 	}
 
 	/**
@@ -485,7 +490,12 @@ Result execute(Result status) throws IOException {
 	 * @throws IOException
 	 */
 	public Result delete() throws IOException {
-		return delete(new RevWalk(getRepository()));
+		RevWalk rw = new RevWalk(getRepository());
+		try {
+			return delete(rw);
+		} finally {
+			rw.release();
+		}
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
index f273824..4acb3ec 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
@@ -51,6 +51,7 @@
 import java.util.Collection;
 import java.util.Map;
 
+import org.eclipse.jgit.storage.file.RefDirectory;
 import org.eclipse.jgit.util.RefList;
 import org.eclipse.jgit.util.RefMap;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java
deleted file mode 100644
index 705c613..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-/**
- * This class passes information about a changed Git index to a
- * {@link RepositoryListener}
- *
- * Currently only a reference to the repository is passed.
- */
-public class RefsChangedEvent extends RepositoryChangedEvent {
-	/**
-	 * Create an event describing reference changes in a repository.
-	 *
-	 * @param repository
-	 *            the repository whose references recently changed.
-	 */
-	public RefsChangedEvent(final Repository repository) {
-		super(repository);
-	}
-
-	@Override
-	public String toString() {
-		return "RefsChangedEvent[" + getRepository() + "]";
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index 62e1578..27fdf68 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -46,12 +46,11 @@
 
 package org.eclipse.jgit.lib;
 
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -59,279 +58,103 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.Vector;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.NoWorkTreeException;
 import org.eclipse.jgit.errors.RevisionSyntaxException;
+import org.eclipse.jgit.events.ListenerList;
+import org.eclipse.jgit.events.RepositoryEvent;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.ReflogReader;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
-import org.eclipse.jgit.util.SystemReader;
 
 /**
- * Represents a Git repository. A repository holds all objects and refs used for
- * managing source code (could by any type of file, but source code is what
- * SCM's are typically used for).
- *
- * In Git terms all data is stored in GIT_DIR, typically a directory called
- * .git. A work tree is maintained unless the repository is a bare repository.
- * Typically the .git directory is located at the root of the work dir.
- *
- * <ul>
- * <li>GIT_DIR
- * 	<ul>
- * 		<li>objects/ - objects</li>
- * 		<li>refs/ - tags and heads</li>
- * 		<li>config - configuration</li>
- * 		<li>info/ - more configurations</li>
- * 	</ul>
- * </li>
- * </ul>
+ * Represents a Git repository.
+ * <p>
+ * A repository holds all objects and refs used for managing source code (could
+ * be any type of file, but source code is what SCM's are typically used for).
  * <p>
  * This class is thread-safe.
- * <p>
- * This implementation only handles a subtly undocumented subset of git features.
- *
  */
-public class Repository {
+public abstract class Repository {
+	private static final ListenerList globalListeners = new ListenerList();
+
+	/** @return the global listener list observing all events in this JVM. */
+	public static ListenerList getGlobalListenerList() {
+		return globalListeners;
+	}
+
 	private final AtomicInteger useCnt = new AtomicInteger(1);
 
+	/** Metadata directory holding the repository's critical files. */
 	private final File gitDir;
 
+	/** File abstraction used to resolve paths. */
 	private final FS fs;
 
-	private final FileBasedConfig userConfig;
-
-	private final RepositoryConfig config;
-
-	private final RefDatabase refs;
-
-	private final ObjectDirectory objectDatabase;
-
 	private GitIndex index;
 
-	private final List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe
-	static private final List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe
+	private final ListenerList myListeners = new ListenerList();
 
-	private File workDir;
+	/** If not bare, the top level directory of the working files. */
+	private final File workTree;
 
-	private File indexFile;
+	/** If not bare, the index file caching the working file states. */
+	private final File indexFile;
 
 	/**
-	 * Construct a representation of a Git repository.
+	 * Initialize a new repository instance.
 	 *
-	 * The work tree, object directory, alternate object directories and index
-	 * file locations are deduced from the given git directory and the default
-	 * rules.
-	 *
-	 * @param d
-	 *            GIT_DIR (the location of the repository metadata).
-	 * @throws IOException
-	 *             the repository appears to already exist but cannot be
-	 *             accessed.
+	 * @param options
+	 *            options to configure the repository.
 	 */
-	public Repository(final File d) throws IOException {
-		this(d, null, null, null, null); // go figure it out
+	protected Repository(final BaseRepositoryBuilder options) {
+		gitDir = options.getGitDir();
+		fs = options.getFS();
+		workTree = options.getWorkTree();
+		indexFile = options.getIndexFile();
+	}
+
+	/** @return listeners observing only events on this repository. */
+	public ListenerList getListenerList() {
+		return myListeners;
 	}
 
 	/**
-	 * Construct a representation of a Git repository.
+	 * Fire an event to all registered listeners.
+	 * <p>
+	 * The source repository of the event is automatically set to this
+	 * repository, before the event is delivered to any listeners.
 	 *
-	 * The work tree, object directory, alternate object directories and index
-	 * file locations are deduced from the given git directory and the default
-	 * rules.
-	 *
-	 * @param d
-	 *            GIT_DIR (the location of the repository metadata). May be
-	 *            null work workTree is set
-	 * @param workTree
-	 *            GIT_WORK_TREE (the root of the checkout). May be null for
-	 *            default value.
-	 * @throws IOException
-	 *             the repository appears to already exist but cannot be
-	 *             accessed.
+	 * @param event
+	 *            the event to deliver.
 	 */
-	public Repository(final File d, final File workTree) throws IOException {
-		this(d, workTree, null, null, null); // go figure it out
+	public void fireEvent(RepositoryEvent<?> event) {
+		event.setRepository(this);
+		myListeners.dispatch(event);
+		globalListeners.dispatch(event);
 	}
 
 	/**
-	 * Construct a representation of a Git repository using the given parameters
-	 * possibly overriding default conventions.
-	 *
-	 * @param d
-	 *            GIT_DIR (the location of the repository metadata). May be null
-	 *            for default value in which case it depends on GIT_WORK_TREE.
-	 * @param workTree
-	 *            GIT_WORK_TREE (the root of the checkout). May be null for
-	 *            default value if GIT_DIR is provided.
-	 * @param objectDir
-	 *            GIT_OBJECT_DIRECTORY (where objects and are stored). May be
-	 *            null for default value. Relative names ares resolved against
-	 *            GIT_WORK_TREE.
-	 * @param alternateObjectDir
-	 *            GIT_ALTERNATE_OBJECT_DIRECTORIES (where more objects are read
-	 *            from). May be null for default value. Relative names ares
-	 *            resolved against GIT_WORK_TREE.
-	 * @param indexFile
-	 *            GIT_INDEX_FILE (the location of the index file). May be null
-	 *            for default value. Relative names ares resolved against
-	 *            GIT_WORK_TREE.
-	 * @throws IOException
-	 *             the repository appears to already exist but cannot be
-	 *             accessed.
-	 */
-	public Repository(final File d, final File workTree, final File objectDir,
-			final File[] alternateObjectDir, final File indexFile) throws IOException {
-		this(d, workTree, objectDir, alternateObjectDir, indexFile, FS.DETECTED);
-	}
-
-	/**
-	 * Construct a representation of a Git repository using the given parameters
-	 * possibly overriding default conventions.
-	 *
-	 * @param d
-	 *            GIT_DIR (the location of the repository metadata). May be null
-	 *            for default value in which case it depends on GIT_WORK_TREE.
-	 * @param workTree
-	 *            GIT_WORK_TREE (the root of the checkout). May be null for
-	 *            default value if GIT_DIR is provided.
-	 * @param objectDir
-	 *            GIT_OBJECT_DIRECTORY (where objects and are stored). May be
-	 *            null for default value. Relative names ares resolved against
-	 *            GIT_WORK_TREE.
-	 * @param alternateObjectDir
-	 *            GIT_ALTERNATE_OBJECT_DIRECTORIES (where more objects are read
-	 *            from). May be null for default value. Relative names ares
-	 *            resolved against GIT_WORK_TREE.
-	 * @param indexFile
-	 *            GIT_INDEX_FILE (the location of the index file). May be null
-	 *            for default value. Relative names ares resolved against
-	 *            GIT_WORK_TREE.
-	 * @param fs
-	 *            the file system abstraction which will be necessary to
-	 *            perform certain file system operations.
-	 * @throws IOException
-	 *             the repository appears to already exist but cannot be
-	 *             accessed.
-	 */
-	public Repository(final File d, final File workTree, final File objectDir,
-			final File[] alternateObjectDir, final File indexFile, FS fs)
-			throws IOException {
-
-		if (workTree != null) {
-			workDir = workTree;
-			if (d == null)
-				gitDir = new File(workTree, Constants.DOT_GIT);
-			else
-				gitDir = d;
-		} else {
-			if (d != null)
-				gitDir = d;
-			else
-				throw new IllegalArgumentException(
-						JGitText.get().eitherGIT_DIRorGIT_WORK_TREEmustBePassed);
-		}
-
-		this.fs = fs;
-
-		userConfig = SystemReader.getInstance().openUserConfig(fs);
-		config = new RepositoryConfig(userConfig, fs.resolve(gitDir, "config"));
-
-		loadUserConfig();
-		loadConfig();
-
-		if (workDir == null) {
-			// if the working directory was not provided explicitly,
-			// we need to decide if this is a "bare" repository or not
-			// first, we check the working tree configuration
-			String workTreeConfig = getConfig().getString(
-					ConfigConstants.CONFIG_CORE_SECTION, null,
-					ConfigConstants.CONFIG_KEY_WORKTREE);
-			if (workTreeConfig != null) {
-				// the working tree configuration wins
-				workDir = fs.resolve(d, workTreeConfig);
-			} else if (getConfig().getString(
-					ConfigConstants.CONFIG_CORE_SECTION, null,
-					ConfigConstants.CONFIG_KEY_BARE) != null) {
-				// we have asserted that a value for the "bare" flag was set
-				if (!getConfig().getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
-						ConfigConstants.CONFIG_KEY_BARE, true))
-					// the "bare" flag is false -> use the parent of the
-					// meta data directory
-					workDir = gitDir.getParentFile();
-				else
-					// the "bare" flag is true
-					workDir = null;
-			} else if (Constants.DOT_GIT.equals(gitDir.getName())) {
-				// no value for the "bare" flag, but the meta data directory
-				// is named ".git" -> use the parent of the meta data directory
-				workDir = gitDir.getParentFile();
-			} else {
-				workDir = null;
-			}
-		}
-
-		refs = new RefDirectory(this);
-		if (objectDir != null)
-			objectDatabase = new ObjectDirectory(fs.resolve(objectDir, ""),
-					alternateObjectDir, fs);
-		else
-			objectDatabase = new ObjectDirectory(fs.resolve(gitDir, "objects"),
-					alternateObjectDir, fs);
-
-		if (indexFile != null)
-			this.indexFile = indexFile;
-		else
-			this.indexFile = new File(gitDir, "index");
-
-		if (objectDatabase.exists()) {
-			final String repositoryFormatVersion = getConfig().getString(
-					ConfigConstants.CONFIG_CORE_SECTION, null,
-					ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION);
-			if (!"0".equals(repositoryFormatVersion)) {
-				throw new IOException(MessageFormat.format(
-						JGitText.get().unknownRepositoryFormat2,
-						repositoryFormatVersion));
-			}
-		}
-	}
-
-	private void loadUserConfig() throws IOException {
-		try {
-			userConfig.load();
-		} catch (ConfigInvalidException e1) {
-			IOException e2 = new IOException(MessageFormat.format(JGitText
-					.get().userConfigFileInvalid, userConfig.getFile()
-					.getAbsolutePath(), e1));
-			e2.initCause(e1);
-			throw e2;
-		}
-	}
-
-	private void loadConfig() throws IOException {
-		try {
-			config.load();
-		} catch (ConfigInvalidException e1) {
-			IOException e2 = new IOException(JGitText.get().unknownRepositoryFormat);
-			e2.initCause(e1);
-			throw e2;
-		}
-	}
-
-
-	/**
-	 * Create a new Git repository initializing the necessary files and
-	 * directories. Repository with working tree is created using this method.
+	 * Create a new Git repository.
+	 * <p>
+	 * Repository with working tree is created using this method. This method is
+	 * the same as {@code create(false)}.
 	 *
 	 * @throws IOException
 	 * @see #create(boolean)
 	 */
-	public synchronized void create() throws IOException {
+	public void create() throws IOException {
 		create(false);
 	}
 
@@ -340,44 +163,14 @@ public synchronized void create() throws IOException {
 	 * directories.
 	 *
 	 * @param bare
-	 *            if true, a bare repository is created.
-	 *
+	 *            if true, a bare repository (a repository without a working
+	 *            directory) is created.
 	 * @throws IOException
 	 *             in case of IO problem
 	 */
-	public void create(boolean bare) throws IOException {
-		final RepositoryConfig cfg = getConfig();
-		if (cfg.getFile().exists()) {
-			throw new IllegalStateException(MessageFormat.format(
-					JGitText.get().repositoryAlreadyExists, gitDir));
-		}
-		gitDir.mkdirs();
-		refs.create();
-		objectDatabase.create();
+	public abstract void create(boolean bare) throws IOException;
 
-		new File(gitDir, "branches").mkdir();
-
-		RefUpdate head = updateRef(Constants.HEAD);
-		head.disableRefLog();
-		head.link(Constants.R_HEADS + Constants.MASTER);
-
-		cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null,
-				ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
-		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
-				ConfigConstants.CONFIG_KEY_FILEMODE, true);
-		if (bare)
-			cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
-					ConfigConstants.CONFIG_KEY_BARE, true);
-		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
-				ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare);
-		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
-				ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
-		cfg.save();
-	}
-
-	/**
-	 * @return GIT_DIR
-	 */
+	/** @return local metadata directory; null if repository isn't local. */
 	public File getDirectory() {
 		return gitDir;
 	}
@@ -385,42 +178,30 @@ public File getDirectory() {
 	/**
 	 * @return the directory containing the objects owned by this repository.
 	 */
-	public File getObjectsDirectory() {
-		return objectDatabase.getDirectory();
-	}
+	public abstract File getObjectsDirectory();
 
 	/**
 	 * @return the object database which stores this repository's data.
 	 */
-	public ObjectDatabase getObjectDatabase() {
-		return objectDatabase;
+	public abstract ObjectDatabase getObjectDatabase();
+
+	/** @return a new inserter to create objects in {@link #getObjectDatabase()} */
+	public ObjectInserter newObjectInserter() {
+		return getObjectDatabase().newInserter();
+	}
+
+	/** @return a new inserter to create objects in {@link #getObjectDatabase()} */
+	public ObjectReader newObjectReader() {
+		return getObjectDatabase().newReader();
 	}
 
 	/** @return the reference database which stores the reference namespace. */
-	public RefDatabase getRefDatabase() {
-		return refs;
-	}
+	public abstract RefDatabase getRefDatabase();
 
 	/**
 	 * @return the configuration of this repository
 	 */
-	public RepositoryConfig getConfig() {
-		if (userConfig.isOutdated()) {
-			try {
-				loadUserConfig();
-			} catch (IOException e) {
-				throw new RuntimeException(e);
-			}
-		}
-		if (config.isOutdated()) {
-				try {
-					loadConfig();
-				} catch (IOException e) {
-					throw new RuntimeException(e);
-				}
-		}
-		return config;
-	}
+	public abstract StoredConfig getConfig();
 
 	/**
 	 * @return the used file system abstraction
@@ -430,116 +211,63 @@ public FS getFS() {
 	}
 
 	/**
-	 * Construct a filename where the loose object having a specified SHA-1
-	 * should be stored. If the object is stored in a shared repository the path
-	 * to the alternative repo will be returned. If the object is not yet store
-	 * a usable path in this repo will be returned. It is assumed that callers
-	 * will look for objects in a pack first.
-	 *
-	 * @param objectId
-	 * @return suggested file name
-	 */
-	public File toFile(final AnyObjectId objectId) {
-		return objectDatabase.fileFor(objectId);
-	}
-
-	/**
 	 * @param objectId
 	 * @return true if the specified object is stored in this repo or any of the
 	 *         known shared repositories.
 	 */
-	public boolean hasObject(final AnyObjectId objectId) {
-		return objectDatabase.hasObject(objectId);
-	}
-
-	/**
-	 * @param id
-	 *            SHA-1 of an object.
-	 *
-	 * @return a {@link ObjectLoader} for accessing the data of the named
-	 *         object, or null if the object does not exist.
-	 * @throws IOException
-	 */
-	public ObjectLoader openObject(final AnyObjectId id)
-			throws IOException {
-		final WindowCursor wc = new WindowCursor();
+	public boolean hasObject(AnyObjectId objectId) {
 		try {
-			return openObject(wc, id);
-		} finally {
-			wc.release();
+			return getObjectDatabase().has(objectId);
+		} catch (IOException e) {
+			// Legacy API, assume error means "no"
+			return false;
 		}
 	}
 
 	/**
-	 * @param curs
-	 *            temporary working space associated with the calling thread.
-	 * @param id
-	 *            SHA-1 of an object.
-	 *
-	 * @return a {@link ObjectLoader} for accessing the data of the named
-	 *         object, or null if the object does not exist.
-	 * @throws IOException
-	 */
-	public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
-			throws IOException {
-		return objectDatabase.openObject(curs, id);
-	}
-
-	/**
-	 * Open object in all packs containing specified object.
+	 * Open an object from this repository.
+	 * <p>
+	 * This is a one-shot call interface which may be faster than allocating a
+	 * {@link #newObjectReader()} to perform the lookup.
 	 *
 	 * @param objectId
-	 *            id of object to search for
-	 * @param curs
-	 *            temporary working space associated with the calling thread.
-	 * @return collection of loaders for this object, from all packs containing
-	 *         this object
+	 *            identity of the object to open.
+	 * @return a {@link ObjectLoader} for accessing the object.
+	 * @throws MissingObjectException
+	 *             the object does not exist.
 	 * @throws IOException
+	 *             the object store cannot be accessed.
 	 */
-	public Collection<PackedObjectLoader> openObjectInAllPacks(
-			final AnyObjectId objectId, final WindowCursor curs)
-			throws IOException {
-		Collection<PackedObjectLoader> result = new LinkedList<PackedObjectLoader>();
-		openObjectInAllPacks(objectId, result, curs);
-		return result;
+	public ObjectLoader open(final AnyObjectId objectId)
+			throws MissingObjectException, IOException {
+		return getObjectDatabase().open(objectId);
 	}
 
 	/**
-	 * Open object in all packs containing specified object.
+	 * Open an object from this repository.
+	 * <p>
+	 * This is a one-shot call interface which may be faster than allocating a
+	 * {@link #newObjectReader()} to perform the lookup.
 	 *
 	 * @param objectId
-	 *            id of object to search for
-	 * @param resultLoaders
-	 *            result collection of loaders for this object, filled with
-	 *            loaders from all packs containing specified object
-	 * @param curs
-	 *            temporary working space associated with the calling thread.
+	 *            identity of the object to open.
+	 * @param typeHint
+	 *            hint about the type of object being requested;
+	 *            {@link ObjectReader#OBJ_ANY} if the object type is not known,
+	 *            or does not matter to the caller.
+	 * @return a {@link ObjectLoader} for accessing the object.
+	 * @throws MissingObjectException
+	 *             the object does not exist.
+	 * @throws IncorrectObjectTypeException
+	 *             typeHint was not OBJ_ANY, and the object's actual type does
+	 *             not match typeHint.
 	 * @throws IOException
+	 *             the object store cannot be accessed.
 	 */
-	void openObjectInAllPacks(final AnyObjectId objectId,
-			final Collection<PackedObjectLoader> resultLoaders,
-			final WindowCursor curs) throws IOException {
-		objectDatabase.openObjectInAllPacks(resultLoaders, curs, objectId);
-	}
-
-	/**
-	 * @param id
-	 *            SHA'1 of a blob
-	 * @return an {@link ObjectLoader} for accessing the data of a named blob
-	 * @throws IOException
-	 */
-	public ObjectLoader openBlob(final ObjectId id) throws IOException {
-		return openObject(id);
-	}
-
-	/**
-	 * @param id
-	 *            SHA'1 of a tree
-	 * @return an {@link ObjectLoader} for accessing the data of a named tree
-	 * @throws IOException
-	 */
-	public ObjectLoader openTree(final ObjectId id) throws IOException {
-		return openObject(id);
+	public ObjectLoader open(AnyObjectId objectId, int typeHint)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException {
+		return getObjectDatabase().open(objectId, typeHint);
 	}
 
 	/**
@@ -572,23 +300,26 @@ public Commit mapCommit(final String revstr) throws IOException {
 	 * @deprecated Use {@link org.eclipse.jgit.revwalk.RevWalk#parseCommit(AnyObjectId)},
 	 *  or {@link org.eclipse.jgit.revwalk.RevWalk#parseTag(AnyObjectId)}.
 	 *  To read a tree, use {@link org.eclipse.jgit.treewalk.TreeWalk#addTree(AnyObjectId)}.
-	 *  To read a blob, open it with {@link #openObject(AnyObjectId)}.
+	 *  To read a blob, open it with {@link #open(AnyObjectId)}.
 	 */
 	@Deprecated
 	public Object mapObject(final ObjectId id, final String refName) throws IOException {
-		final ObjectLoader or = openObject(id);
-		if (or == null)
+		final ObjectLoader or;
+		try {
+			or = open(id);
+		} catch (MissingObjectException notFound) {
 			return null;
-		final byte[] raw = or.getBytes();
+		}
+		final byte[] raw = or.getCachedBytes();
 		switch (or.getType()) {
 		case Constants.OBJ_TREE:
-			return makeTree(id, raw);
+			return new Tree(this, id, raw);
 
 		case Constants.OBJ_COMMIT:
-			return makeCommit(id, raw);
+			return new Commit(this, id, raw);
 
 		case Constants.OBJ_TAG:
-			return makeTag(id, refName, raw);
+			return new Tag(this, id, refName, raw);
 
 		case Constants.OBJ_BLOB:
 			return raw;
@@ -608,18 +339,13 @@ public Object mapObject(final ObjectId id, final String refName) throws IOExcept
 	 */
 	@Deprecated
 	public Commit mapCommit(final ObjectId id) throws IOException {
-		final ObjectLoader or = openObject(id);
-		if (or == null)
+		final ObjectLoader or;
+		try {
+			or = open(id, Constants.OBJ_COMMIT);
+		} catch (MissingObjectException notFound) {
 			return null;
-		final byte[] raw = or.getBytes();
-		if (Constants.OBJ_COMMIT == or.getType())
-			return new Commit(this, id, raw);
-		throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
-	}
-
-	private Commit makeCommit(final ObjectId id, final byte[] raw) {
-		Commit ret = new Commit(this, id, raw);
-		return ret;
+		}
+		return new Commit(this, id, or.getCachedBytes());
 	}
 
 	/**
@@ -650,10 +376,13 @@ public Tree mapTree(final String revstr) throws IOException {
 	 */
 	@Deprecated
 	public Tree mapTree(final ObjectId id) throws IOException {
-		final ObjectLoader or = openObject(id);
-		if (or == null)
+		final ObjectLoader or;
+		try {
+			or = open(id);
+		} catch (MissingObjectException notFound) {
 			return null;
-		final byte[] raw = or.getBytes();
+		}
+		final byte[] raw = or.getCachedBytes();
 		switch (or.getType()) {
 		case Constants.OBJ_TREE:
 			return new Tree(this, id, raw);
@@ -666,16 +395,6 @@ public Tree mapTree(final ObjectId id) throws IOException {
 		}
 	}
 
-	private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
-		Tree ret = new Tree(this, id, raw);
-		return ret;
-	}
-
-	private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
-		Tag ret = new Tag(this, id, refName, raw);
-		return ret;
-	}
-
 	/**
 	 * Access a tag by symbolic name.
 	 *
@@ -701,12 +420,14 @@ public Tag mapTag(String revstr) throws IOException {
 	 */
 	@Deprecated
 	public Tag mapTag(final String refName, final ObjectId id) throws IOException {
-		final ObjectLoader or = openObject(id);
-		if (or == null)
+		final ObjectLoader or;
+		try {
+			or = open(id);
+		} catch (MissingObjectException notFound) {
 			return null;
-		final byte[] raw = or.getBytes();
-		if (Constants.OBJ_TAG == or.getType())
-			return new Tag(this, id, refName, raw);
+		}
+		if (or.getType() == Constants.OBJ_TAG)
+			return new Tag(this, id, refName, or.getCachedBytes());
 		return new Tag(this, id, refName, null);
 	}
 
@@ -741,7 +462,7 @@ public RefUpdate updateRef(final String ref) throws IOException {
 	 *             to the base ref, as the symbolic ref could not be read.
 	 */
 	public RefUpdate updateRef(final String ref, final boolean detach) throws IOException {
-		return refs.newUpdate(ref, detach);
+		return getRefDatabase().newUpdate(ref, detach);
 	}
 
 	/**
@@ -757,7 +478,7 @@ public RefUpdate updateRef(final String ref, final boolean detach) throws IOExce
 	 *
 	 */
 	public RefRename renameRef(final String fromRef, final String toRef) throws IOException {
-		return refs.newRename(fromRef, toRef);
+		return getRefDatabase().newRename(fromRef, toRef);
 	}
 
 	/**
@@ -765,36 +486,45 @@ public RefRename renameRef(final String fromRef, final String toRef) throws IOEx
 	 *
 	 * Currently supported is combinations of these.
 	 * <ul>
-	 *  <li>SHA-1 - a SHA-1</li>
-	 *  <li>refs/... - a ref name</li>
-	 *  <li>ref^n - nth parent reference</li>
-	 *  <li>ref~n - distance via parent reference</li>
-	 *  <li>ref@{n} - nth version of ref</li>
-	 *  <li>ref^{tree} - tree references by ref</li>
-	 *  <li>ref^{commit} - commit references by ref</li>
+	 * <li>SHA-1 - a SHA-1</li>
+	 * <li>refs/... - a ref name</li>
+	 * <li>ref^n - nth parent reference</li>
+	 * <li>ref~n - distance via parent reference</li>
+	 * <li>ref@{n} - nth version of ref</li>
+	 * <li>ref^{tree} - tree references by ref</li>
+	 * <li>ref^{commit} - commit references by ref</li>
 	 * </ul>
 	 *
-	 * Not supported is
+	 * Not supported is:
 	 * <ul>
 	 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
 	 * <li>abbreviated SHA-1's</li>
 	 * </ul>
 	 *
-	 * @param revstr A git object references expression
+	 * @param revstr
+	 *            A git object references expression
 	 * @return an ObjectId or null if revstr can't be resolved to any ObjectId
-	 * @throws IOException on serious errors
+	 * @throws IOException
+	 *             on serious errors
 	 */
 	public ObjectId resolve(final String revstr) throws IOException {
+		RevWalk rw = new RevWalk(this);
+		try {
+			return resolve(rw, revstr);
+		} finally {
+			rw.release();
+		}
+	}
+
+	private ObjectId resolve(final RevWalk rw, final String revstr) throws IOException {
 		char[] rev = revstr.toCharArray();
-		Object ref = null;
-		ObjectId refId = null;
+		RevObject ref = null;
 		for (int i = 0; i < rev.length; ++i) {
 			switch (rev[i]) {
 			case '^':
-				if (refId == null) {
-					String refstr = new String(rev,0,i);
-					refId = resolveSimple(refstr);
-					if (refId == null)
+				if (ref == null) {
+					ref = parseSimple(rw, new String(rev, 0, i));
+					if (ref == null)
 						return null;
 				}
 				if (i + 1 < rev.length) {
@@ -810,19 +540,12 @@ public ObjectId resolve(final String revstr) throws IOException {
 					case '8':
 					case '9':
 						int j;
-						ref = mapObject(refId, null);
-						while (ref instanceof Tag) {
-							Tag tag = (Tag)ref;
-							refId = tag.getObjId();
-							ref = mapObject(refId, null);
-						}
-						if (!(ref instanceof Commit))
-							throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
-						for (j=i+1; j<rev.length; ++j) {
+						ref = rw.parseCommit(ref);
+						for (j = i + 1; j < rev.length; ++j) {
 							if (!Character.isDigit(rev[j]))
 								break;
 						}
-						String parentnum = new String(rev, i+1, j-i-1);
+						String parentnum = new String(rev, i + 1, j - i - 1);
 						int pnum;
 						try {
 							pnum = Integer.parseInt(parentnum);
@@ -832,123 +555,83 @@ public ObjectId resolve(final String revstr) throws IOException {
 									revstr);
 						}
 						if (pnum != 0) {
-							final ObjectId parents[] = ((Commit) ref)
-									.getParentIds();
-							if (pnum > parents.length)
-								refId = null;
+							RevCommit commit = (RevCommit) ref;
+							if (pnum > commit.getParentCount())
+								ref = null;
 							else
-								refId = parents[pnum - 1];
+								ref = commit.getParent(pnum - 1);
 						}
 						i = j - 1;
 						break;
 					case '{':
 						int k;
 						String item = null;
-						for (k=i+2; k<rev.length; ++k) {
+						for (k = i + 2; k < rev.length; ++k) {
 							if (rev[k] == '}') {
-								item = new String(rev, i+2, k-i-2);
+								item = new String(rev, i + 2, k - i - 2);
 								break;
 							}
 						}
 						i = k;
 						if (item != null)
 							if (item.equals("tree")) {
-								ref = mapObject(refId, null);
-								while (ref instanceof Tag) {
-									Tag t = (Tag)ref;
-									refId = t.getObjId();
-									ref = mapObject(refId, null);
-								}
-								if (ref instanceof Treeish)
-									refId = ((Treeish)ref).getTreeId();
-								else
-									throw new IncorrectObjectTypeException(refId,  Constants.TYPE_TREE);
-							}
-							else if (item.equals("commit")) {
-								ref = mapObject(refId, null);
-								while (ref instanceof Tag) {
-									Tag t = (Tag)ref;
-									refId = t.getObjId();
-									ref = mapObject(refId, null);
-								}
-								if (!(ref instanceof Commit))
-									throw new IncorrectObjectTypeException(refId,  Constants.TYPE_COMMIT);
-							}
-							else if (item.equals("blob")) {
-								ref = mapObject(refId, null);
-								while (ref instanceof Tag) {
-									Tag t = (Tag)ref;
-									refId = t.getObjId();
-									ref = mapObject(refId, null);
-								}
-								if (!(ref instanceof byte[]))
-									throw new IncorrectObjectTypeException(refId,  Constants.TYPE_BLOB);
-							}
-							else if (item.equals("")) {
-								ref = mapObject(refId, null);
-								while (ref instanceof Tag) {
-									Tag t = (Tag)ref;
-									refId = t.getObjId();
-									ref = mapObject(refId, null);
-								}
-							}
-							else
+								ref = rw.parseTree(ref);
+							} else if (item.equals("commit")) {
+								ref = rw.parseCommit(ref);
+							} else if (item.equals("blob")) {
+								ref = rw.peel(ref);
+								if (!(ref instanceof RevBlob))
+									throw new IncorrectObjectTypeException(ref,
+											Constants.TYPE_BLOB);
+							} else if (item.equals("")) {
+								ref = rw.peel(ref);
+							} else
 								throw new RevisionSyntaxException(revstr);
 						else
 							throw new RevisionSyntaxException(revstr);
 						break;
 					default:
-						ref = mapObject(refId, null);
-						if (ref instanceof Commit) {
-							final ObjectId parents[] = ((Commit) ref)
-									.getParentIds();
-							if (parents.length == 0)
-								refId = null;
+						ref = rw.parseAny(ref);
+						if (ref instanceof RevCommit) {
+							RevCommit commit = ((RevCommit) ref);
+							if (commit.getParentCount() == 0)
+								ref = null;
 							else
-								refId = parents[0];
+								ref = commit.getParent(0);
 						} else
-							throw new IncorrectObjectTypeException(refId,  Constants.TYPE_COMMIT);
+							throw new IncorrectObjectTypeException(ref,
+									Constants.TYPE_COMMIT);
 
 					}
 				} else {
-					ref = mapObject(refId, null);
-					while (ref instanceof Tag) {
-						Tag tag = (Tag)ref;
-						refId = tag.getObjId();
-						ref = mapObject(refId, null);
-					}
-					if (ref instanceof Commit) {
-						final ObjectId parents[] = ((Commit) ref)
-								.getParentIds();
-						if (parents.length == 0)
-							refId = null;
+					ref = rw.peel(ref);
+					if (ref instanceof RevCommit) {
+						RevCommit commit = ((RevCommit) ref);
+						if (commit.getParentCount() == 0)
+							ref = null;
 						else
-							refId = parents[0];
+							ref = commit.getParent(0);
 					} else
-						throw new IncorrectObjectTypeException(refId,  Constants.TYPE_COMMIT);
+						throw new IncorrectObjectTypeException(ref,
+								Constants.TYPE_COMMIT);
 				}
 				break;
 			case '~':
 				if (ref == null) {
-					String refstr = new String(rev,0,i);
-					refId = resolveSimple(refstr);
-					if (refId == null)
+					ref = parseSimple(rw, new String(rev, 0, i));
+					if (ref == null)
 						return null;
-					ref = mapObject(refId, null);
 				}
-				while (ref instanceof Tag) {
-					Tag tag = (Tag)ref;
-					refId = tag.getObjId();
-					ref = mapObject(refId, null);
-				}
-				if (!(ref instanceof Commit))
-					throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
+				ref = rw.peel(ref);
+				if (!(ref instanceof RevCommit))
+					throw new IncorrectObjectTypeException(ref,
+							Constants.TYPE_COMMIT);
 				int l;
 				for (l = i + 1; l < rev.length; ++l) {
 					if (!Character.isDigit(rev[l]))
 						break;
 				}
-				String distnum = new String(rev, i+1, l-i-1);
+				String distnum = new String(rev, i + 1, l - i - 1);
 				int dist;
 				try {
 					dist = Integer.parseInt(distnum);
@@ -957,13 +640,14 @@ else if (item.equals("")) {
 							JGitText.get().invalidAncestryLength, revstr);
 				}
 				while (dist > 0) {
-					final ObjectId[] parents = ((Commit) ref).getParentIds();
-					if (parents.length == 0) {
-						refId = null;
+					RevCommit commit = (RevCommit) ref;
+					if (commit.getParentCount() == 0) {
+						ref = null;
 						break;
 					}
-					refId = parents[0];
-					ref = mapCommit(refId);
+					commit = commit.getParent(0);
+					rw.parseHeaders(commit);
+					ref = commit;
 					--dist;
 				}
 				i = l - 1;
@@ -971,30 +655,35 @@ else if (item.equals("")) {
 			case '@':
 				int m;
 				String time = null;
-				for (m=i+2; m<rev.length; ++m) {
+				for (m = i + 2; m < rev.length; ++m) {
 					if (rev[m] == '}') {
-						time = new String(rev, i+2, m-i-2);
+						time = new String(rev, i + 2, m - i - 2);
 						break;
 					}
 				}
 				if (time != null)
-					throw new RevisionSyntaxException(JGitText.get().reflogsNotYetSupportedByRevisionParser, revstr);
+					throw new RevisionSyntaxException(
+							JGitText.get().reflogsNotYetSupportedByRevisionParser,
+							revstr);
 				i = m - 1;
 				break;
 			default:
-				if (refId != null)
+				if (ref != null)
 					throw new RevisionSyntaxException(revstr);
 			}
 		}
-		if (refId == null)
-			refId = resolveSimple(revstr);
-		return refId;
+		return ref != null ? ref.copy() : resolveSimple(revstr);
+	}
+
+	private RevObject parseSimple(RevWalk rw, String revstr) throws IOException {
+		ObjectId id = resolveSimple(revstr);
+		return id != null ? rw.parseAny(id) : null;
 	}
 
 	private ObjectId resolveSimple(final String revstr) throws IOException {
 		if (ObjectId.isId(revstr))
 			return ObjectId.fromString(revstr);
-		final Ref r = refs.getRef(revstr);
+		final Ref r = getRefDatabase().getRef(revstr);
 		return r != null ? r.getObjectId() : null;
 	}
 
@@ -1003,17 +692,24 @@ public void incrementOpen() {
 		useCnt.incrementAndGet();
 	}
 
-	/**
-	 * Close all resources used by this repository
-	 */
+	/** Decrement the use count, and maybe close resources. */
 	public void close() {
 		if (useCnt.decrementAndGet() == 0) {
-			objectDatabase.close();
-			refs.close();
+			doClose();
 		}
 	}
 
 	/**
+	 * Invoked when the use count drops to zero during {@link #close()}.
+	 * <p>
+	 * The default implementation closes the object and ref databases.
+	 */
+	protected void doClose() {
+		getObjectDatabase().close();
+		getRefDatabase().close();
+	}
+
+	/**
 	 * Add a single existing pack to the list of available pack files.
 	 *
 	 * @param pack
@@ -1024,12 +720,16 @@ public void close() {
 	 *             index file could not be opened, read, or is not recognized as
 	 *             a Git pack file index.
 	 */
-	public void openPack(final File pack, final File idx) throws IOException {
-		objectDatabase.openPack(pack, idx);
-	}
+	public abstract void openPack(File pack, File idx) throws IOException;
 
 	public String toString() {
-		return "Repository[" + getDirectory() + "]";
+		String desc;
+		if (getDirectory() != null)
+			desc = getDirectory().getPath();
+		else
+			desc = getClass().getSimpleName() + "-"
+					+ System.identityHashCode(this);
+		return "Repository[" + desc + "]";
 	}
 
 	/**
@@ -1078,6 +778,20 @@ public String getBranch() throws IOException {
 	}
 
 	/**
+	 * Objects known to exist but not expressed by {@link #getAllRefs()}.
+	 * <p>
+	 * When a repository borrows objects from another repository, it can
+	 * advertise that it safely has that other repository's references, without
+	 * exposing any other details about the other repository.  This may help
+	 * a client trying to push changes avoid pushing more than it needs to.
+	 *
+	 * @return unmodifiable collection of other known objects.
+	 */
+	public Set<ObjectId> getAdditionalHaves() {
+		return Collections.emptySet();
+	}
+
+	/**
 	 * Get a ref by name.
 	 *
 	 * @param name
@@ -1088,7 +802,7 @@ public String getBranch() throws IOException {
 	 * @throws IOException
 	 */
 	public Ref getRef(final String name) throws IOException {
-		return refs.getRef(name);
+		return getRefDatabase().getRef(name);
 	}
 
 	/**
@@ -1096,7 +810,7 @@ public Ref getRef(final String name) throws IOException {
 	 */
 	public Map<String, Ref> getAllRefs() {
 		try {
-			return refs.getRefs(RefDatabase.ALL);
+			return getRefDatabase().getRefs(RefDatabase.ALL);
 		} catch (IOException e) {
 			return new HashMap<String, Ref>();
 		}
@@ -1109,7 +823,7 @@ public Map<String, Ref> getAllRefs() {
 	 */
 	public Map<String, Ref> getTags() {
 		try {
-			return refs.getRefs(Constants.R_TAGS);
+			return getRefDatabase().getRefs(Constants.R_TAGS);
 		} catch (IOException e) {
 			return new HashMap<String, Ref>();
 		}
@@ -1130,7 +844,7 @@ public Map<String, Ref> getTags() {
 	 */
 	public Ref peel(final Ref ref) {
 		try {
-			return refs.peel(ref);
+			return getRefDatabase().peel(ref);
 		} catch (IOException e) {
 			// Historical accident; if the reference cannot be peeled due
 			// to some sort of repository access problem we claim that the
@@ -1170,13 +884,13 @@ public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
 	 *         {@link Repository}
 	 * @throws IOException
 	 *             if the index can not be read
-	 * @throws IllegalStateException
-	 *             if this is bare (see {@link #isBare()})
+	 * @throws NoWorkTreeException
+	 *             if this is bare, which implies it has no working directory.
+	 *             See {@link #isBare()}.
 	 */
-	public GitIndex getIndex() throws IOException, IllegalStateException {
+	public GitIndex getIndex() throws IOException, NoWorkTreeException {
 		if (isBare())
-			throw new IllegalStateException(
-					JGitText.get().bareRepositoryNoWorkdirAndIndex);
+			throw new NoWorkTreeException();
 		if (index == null) {
 			index = new GitIndex(this);
 			index.read();
@@ -1188,16 +902,63 @@ public GitIndex getIndex() throws IOException, IllegalStateException {
 
 	/**
 	 * @return the index file location
-	 * @throws IllegalStateException
-	 *             if this is bare (see {@link #isBare()})
+	 * @throws NoWorkTreeException
+	 *             if this is bare, which implies it has no working directory.
+	 *             See {@link #isBare()}.
 	 */
-	public File getIndexFile() throws IllegalStateException {
+	public File getIndexFile() throws NoWorkTreeException {
 		if (isBare())
-			throw new IllegalStateException(
-					JGitText.get().bareRepositoryNoWorkdirAndIndex);
+			throw new NoWorkTreeException();
 		return indexFile;
 	}
 
+	/**
+	 * Create a new in-core index representation and read an index from disk.
+	 * <p>
+	 * The new index will be read before it is returned to the caller. Read
+	 * failures are reported as exceptions and therefore prevent the method from
+	 * returning a partially populated index.
+	 *
+	 * @return a cache representing the contents of the specified index file (if
+	 *         it exists) or an empty cache if the file does not exist.
+	 * @throws NoWorkTreeException
+	 *             if this is bare, which implies it has no working directory.
+	 *             See {@link #isBare()}.
+	 * @throws IOException
+	 *             the index file is present but could not be read.
+	 * @throws CorruptObjectException
+	 *             the index file is using a format or extension that this
+	 *             library does not support.
+	 */
+	public DirCache readDirCache() throws NoWorkTreeException,
+			CorruptObjectException, IOException {
+		return DirCache.read(getIndexFile(), getFS());
+	}
+
+	/**
+	 * Create a new in-core index representation, lock it, and read from disk.
+	 * <p>
+	 * The new index will be locked and then read before it is returned to the
+	 * caller. Read failures are reported as exceptions and therefore prevent
+	 * the method from returning a partially populated index.
+	 *
+	 * @return a cache representing the contents of the specified index file (if
+	 *         it exists) or an empty cache if the file does not exist.
+	 * @throws NoWorkTreeException
+	 *             if this is bare, which implies it has no working directory.
+	 *             See {@link #isBare()}.
+	 * @throws IOException
+	 *             the index file is present but could not be read, or the lock
+	 *             could not be obtained.
+	 * @throws CorruptObjectException
+	 *             the index file is using a format or extension that this
+	 *             library does not support.
+	 */
+	public DirCache lockDirCache() throws NoWorkTreeException,
+			CorruptObjectException, IOException {
+		return DirCache.lock(getIndexFile(), getFS());
+	}
+
 	static byte[] gitInternalSlash(byte[] bytes) {
 		if (File.separatorChar == '/')
 			return bytes;
@@ -1211,10 +972,13 @@ public File getIndexFile() throws IllegalStateException {
 	 * @return an important state
 	 */
 	public RepositoryState getRepositoryState() {
+		if (isBare() || getDirectory() == null)
+			return RepositoryState.BARE;
+
 		// Pre Git-1.6 logic
-		if (new File(getWorkDir(), ".dotest").exists())
+		if (new File(getWorkTree(), ".dotest").exists())
 			return RepositoryState.REBASING;
-		if (new File(gitDir,".dotest-merge").exists())
+		if (new File(getDirectory(), ".dotest-merge").exists())
 			return RepositoryState.REBASING_INTERACTIVE;
 
 		// From 1.6 onwards
@@ -1231,10 +995,10 @@ public RepositoryState getRepositoryState() {
 			return RepositoryState.REBASING_MERGE;
 
 		// Both versions
-		if (new File(gitDir, "MERGE_HEAD").exists()) {
+		if (new File(getDirectory(), "MERGE_HEAD").exists()) {
 			// we are merging - now check whether we have unmerged paths
 			try {
-				if (!DirCache.read(this).hasUnmergedPaths()) {
+				if (!readDirCache().hasUnmergedPaths()) {
 					// no unmerged paths -> return the MERGING_RESOLVED state
 					return RepositoryState.MERGING_RESOLVED;
 				}
@@ -1247,7 +1011,7 @@ public RepositoryState getRepositoryState() {
 			return RepositoryState.MERGING;
 		}
 
-		if (new File(gitDir,"BISECT_LOG").exists())
+		if (new File(getDirectory(), "BISECT_LOG").exists())
 			return RepositoryState.BISECTING;
 
 		return RepositoryState.SAFE;
@@ -1334,96 +1098,23 @@ public static String stripWorkDir(File workDir, File file) {
 	}
 
 	/**
-	 * @return the "bare"-ness of this Repository
+	 * @return true if this is bare, which implies it has no working directory.
 	 */
 	public boolean isBare() {
-		return workDir == null;
+		return workTree == null;
 	}
 
 	/**
-	 * @return the workdir file, i.e. where the files are checked out
-	 * @throws IllegalStateException
-	 *             if the repository is "bare"
+	 * @return the root directory of the working tree, where files are checked
+	 *         out for viewing and editing.
+	 * @throws NoWorkTreeException
+	 *             if this is bare, which implies it has no working directory.
+	 *             See {@link #isBare()}.
 	 */
-	public File getWorkDir() throws IllegalStateException {
+	public File getWorkTree() throws NoWorkTreeException {
 		if (isBare())
-			throw new IllegalStateException(
-					JGitText.get().bareRepositoryNoWorkdirAndIndex);
-		return workDir;
-	}
-
-	/**
-	 * Override default workdir
-	 *
-	 * @param workTree
-	 *            the work tree directory
-	 */
-	public void setWorkDir(File workTree) {
-		this.workDir = workTree;
-	}
-
-	/**
-	 * Register a {@link RepositoryListener} which will be notified
-	 * when ref changes are detected.
-	 *
-	 * @param l
-	 */
-	public void addRepositoryChangedListener(final RepositoryListener l) {
-		listeners.add(l);
-	}
-
-	/**
-	 * Remove a registered {@link RepositoryListener}
-	 * @param l
-	 */
-	public void removeRepositoryChangedListener(final RepositoryListener l) {
-		listeners.remove(l);
-	}
-
-	/**
-	 * Register a global {@link RepositoryListener} which will be notified
-	 * when a ref changes in any repository are detected.
-	 *
-	 * @param l
-	 */
-	public static void addAnyRepositoryChangedListener(final RepositoryListener l) {
-		allListeners.add(l);
-	}
-
-	/**
-	 * Remove a globally registered {@link RepositoryListener}
-	 * @param l
-	 */
-	public static void removeAnyRepositoryChangedListener(final RepositoryListener l) {
-		allListeners.remove(l);
-	}
-
-	void fireRefsChanged() {
-		final RefsChangedEvent event = new RefsChangedEvent(this);
-		List<RepositoryListener> all;
-		synchronized (listeners) {
-			all = new ArrayList<RepositoryListener>(listeners);
-		}
-		synchronized (allListeners) {
-			all.addAll(allListeners);
-		}
-		for (final RepositoryListener l : all) {
-			l.refsChanged(event);
-		}
-	}
-
-	void fireIndexChanged() {
-		final IndexChangedEvent event = new IndexChangedEvent(this);
-		List<RepositoryListener> all;
-		synchronized (listeners) {
-			all = new ArrayList<RepositoryListener>(listeners);
-		}
-		synchronized (allListeners) {
-			all.addAll(allListeners);
-		}
-		for (final RepositoryListener l : all) {
-			l.indexChanged(event);
-		}
+			throw new NoWorkTreeException();
+		return workTree;
 	}
 
 	/**
@@ -1431,11 +1122,7 @@ void fireIndexChanged() {
 	 *
 	 * @throws IOException
 	 */
-	public void scanForRepoChanges() throws IOException {
-		getAllRefs(); // This will look for changes to refs
-		if (!isBare())
-			getIndex(); // This will detect changes in the index
-	}
+	public abstract void scanForRepoChanges() throws IOException;
 
 	/**
 	 * @param refName
@@ -1458,12 +1145,8 @@ public String shortenRefName(String refName) {
 	 *         named ref does not exist.
 	 * @throws IOException the ref could not be accessed.
 	 */
-	public ReflogReader getReflogReader(String refName) throws IOException {
-		Ref ref = getRef(refName);
-		if (ref != null)
-			return new ReflogReader(this, ref.getName());
-		return null;
-	}
+	public abstract ReflogReader getReflogReader(String refName)
+			throws IOException;
 
 	/**
 	 * Return the information stored in the file $GIT_DIR/MERGE_MSG. In this
@@ -1473,11 +1156,17 @@ public ReflogReader getReflogReader(String refName) throws IOException {
 	 * @return a String containing the content of the MERGE_MSG file or
 	 *         {@code null} if this file doesn't exist
 	 * @throws IOException
+	 * @throws NoWorkTreeException
+	 *             if this is bare, which implies it has no working directory.
+	 *             See {@link #isBare()}.
 	 */
-	public String readMergeCommitMsg() throws IOException {
-		File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG);
+	public String readMergeCommitMsg() throws IOException, NoWorkTreeException {
+		if (isBare() || getDirectory() == null)
+			throw new NoWorkTreeException();
+
+		File mergeMsgFile = new File(getDirectory(), Constants.MERGE_MSG);
 		try {
-			return new String(IO.readFully(mergeMsgFile));
+			return RawParseUtils.decode(IO.readFully(mergeMsgFile));
 		} catch (FileNotFoundException e) {
 			// MERGE_MSG file has disappeared in the meantime
 			// ignore it
@@ -1486,6 +1175,32 @@ public String readMergeCommitMsg() throws IOException {
 	}
 
 	/**
+	 * Write new content to the file $GIT_DIR/MERGE_MSG. In this file operations
+	 * triggering a merge will store a template for the commit message of the
+	 * merge commit. If <code>null</code> is specified as message the file will
+	 * be deleted
+	 *
+	 * @param msg
+	 *            the message which should be written or <code>null</code> to
+	 *            delete the file
+	 *
+	 * @throws IOException
+	 */
+	public void writeMergeCommitMsg(String msg) throws IOException {
+		File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG);
+		if (msg != null) {
+			FileOutputStream fos = new FileOutputStream(mergeMsgFile);
+			try {
+				fos.write(msg.getBytes(Constants.CHARACTER_ENCODING));
+			} finally {
+				fos.close();
+			}
+		} else {
+			mergeMsgFile.delete();
+		}
+	}
+
+	/**
 	 * Return the information stored in the file $GIT_DIR/MERGE_HEAD. In this
 	 * file operations triggering a merge will store the IDs of all heads which
 	 * should be merged together with HEAD.
@@ -1494,18 +1209,24 @@ public String readMergeCommitMsg() throws IOException {
 	 *         file or {@code null} if this file doesn't exist. Also if the file
 	 *         exists but is empty {@code null} will be returned
 	 * @throws IOException
+	 * @throws NoWorkTreeException
+	 *             if this is bare, which implies it has no working directory.
+	 *             See {@link #isBare()}.
 	 */
-	public List<ObjectId> readMergeHeads() throws IOException {
-		File mergeHeadFile = new File(gitDir, Constants.MERGE_HEAD);
+	public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException {
+		if (isBare() || getDirectory() == null)
+			throw new NoWorkTreeException();
+
+		File mergeHeadFile = new File(getDirectory(), Constants.MERGE_HEAD);
 		byte[] raw;
 		try {
 			raw = IO.readFully(mergeHeadFile);
 		} catch (FileNotFoundException notFound) {
-			return new LinkedList<ObjectId>();
+			return null;
 		}
 
 		if (raw.length == 0)
-			throw new IOException("MERGE_HEAD file empty: " + mergeHeadFile);
+			return null;
 
 		LinkedList<ObjectId> heads = new LinkedList<ObjectId>();
 		for (int p = 0; p < raw.length;) {
@@ -1515,4 +1236,33 @@ public List<ObjectId> readMergeHeads() throws IOException {
 		}
 		return heads;
 	}
+
+	/**
+	 * Write new merge-heads into $GIT_DIR/MERGE_HEAD. In this file operations
+	 * triggering a merge will store the IDs of all heads which should be merged
+	 * together with HEAD. If <code>null</code> is specified as list of commits
+	 * the file will be deleted
+	 *
+	 * @param heads
+	 *            a list of {@link Commit}s which IDs should be written to
+	 *            $GIT_DIR/MERGE_HEAD or <code>null</code> to delete the file
+	 * @throws IOException
+	 */
+	public void writeMergeHeads(List<ObjectId> heads) throws IOException {
+		File mergeHeadFile = new File(gitDir, Constants.MERGE_HEAD);
+		if (heads != null) {
+			BufferedOutputStream bos = new BufferedOutputStream(
+					new FileOutputStream(mergeHeadFile));
+			try {
+				for (ObjectId id : heads) {
+					id.copyTo(bos);
+					bos.write('\n');
+				}
+			} finally {
+				bos.close();
+			}
+		} else {
+			mergeHeadFile.delete();
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java
similarity index 66%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java
index e43c33a..f9185e8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -43,18 +43,32 @@
 
 package org.eclipse.jgit.lib;
 
+import java.io.File;
+
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+
 /**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
+ * Base class to support constructing a {@link Repository}.
+ * <p>
+ * Applications must set one of {@link #setGitDir(File)} or
+ * {@link #setWorkTree(File)}, or use {@link #readEnvironment()} or
+ * {@link #findGitDir()} in order to configure the minimum property set
+ * necessary to open a repository.
+ * <p>
+ * Single repository applications trying to be compatible with other Git
+ * implementations are encouraged to use a model such as:
+ *
+ * <pre>
+ * new RepositoryBuilder() //
+ * 		.setGitDir(gitDirArgument) // --git-dir if supplied, no-op if null
+ * 		.readEnviroment() // scan environment GIT_* variables
+ * 		.findGitDir() // scan up the file system tree
+ * 		.build()
+ * </pre>
+ *
+ * @see FileRepositoryBuilder
  */
-public class RepositoryAdapter implements RepositoryListener {
-
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
-	}
-
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
-	}
-
+public class RepositoryBuilder extends
+		BaseRepositoryBuilder<RepositoryBuilder, Repository> {
+	// Empty implementation, everything is inherited.
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
index 0b0260a..dc5eae5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
@@ -52,6 +52,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.storage.file.FileRepository;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
@@ -120,7 +121,10 @@ public static Repository open(final Key location, final boolean mustExist)
 	 *            repository to register.
 	 */
 	public static void register(final Repository db) {
-		cache.registerRepository(FileKey.exact(db.getDirectory(), db.getFS()), db);
+		if (db.getDirectory() != null) {
+			FileKey key = FileKey.exact(db.getDirectory(), db.getFS());
+			cache.registerRepository(key, db);
+		}
 	}
 
 	/**
@@ -133,7 +137,10 @@ public static void register(final Repository db) {
 	 *            repository to unregister.
 	 */
 	public static void close(final Repository db) {
-		cache.unregisterRepository(FileKey.exact(db.getDirectory(), db.getFS()));
+		if (db.getDirectory() != null) {
+			FileKey key = FileKey.exact(db.getDirectory(), db.getFS());
+			cache.unregisterRepository(key);
+		}
 	}
 
 	/** Unregister all repositories from the cache. */
@@ -313,7 +320,7 @@ public final File getFile() {
 		public Repository open(final boolean mustExist) throws IOException {
 			if (mustExist && !isGitRepository(path, fs))
 				throw new RepositoryNotFoundException(path);
-			return new Repository(path);
+			return new FileRepository(path);
 		}
 
 		@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java
deleted file mode 100644
index 805975a..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
- * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2009, JetBrains s.r.o.
- * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com>
- * Copyright (C) 2009, Yann Simon <yann.simon.fr@gmail.com>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.File;
-
-/**
- * An object representing the Git config file.
- *
- * This can be either the repository specific file or the user global
- * file depending on how it is instantiated.
- */
-public class RepositoryConfig extends FileBasedConfig {
-	/** Section name for a branch configuration. */
-	public static final String BRANCH_SECTION = "branch";
-
-	/**
-	 * Create a Git configuration file reader/writer/cache for a specific file.
-	 *
-	 * @param base
-	 *            configuration that provides default values if this file does
-	 *            not set/override a particular key. Often this is the user's
-	 *            global configuration file, or the system level configuration.
-	 * @param cfgLocation
-	 *            path of the file to load (or save).
-	 */
-	public RepositoryConfig(final Config base, final File cfgLocation) {
-		super(base, cfgLocation);
-	}
-
-	/**
-	 * @return Core configuration values
-	 */
-	public CoreConfig getCore() {
-		return get(CoreConfig.KEY);
-	}
-
-	/**
-	 * @return transfer, fetch and receive configuration values
-	 */
-	public TransferConfig getTransfer() {
-		return get(TransferConfig.KEY);
-	}
-
-	/** @return standard user configuration data */
-	public UserConfig getUserConfig() {
-		return get(UserConfig.KEY);
-	}
-
-	/**
-	 * @return the author name as defined in the git variables
-	 *         and configurations. If no name could be found, try
-	 *         to use the system user name instead.
-	 */
-	public String getAuthorName() {
-		return getUserConfig().getAuthorName();
-	}
-
-	/**
-	 * @return the committer name as defined in the git variables
-	 *         and configurations. If no name could be found, try
-	 *         to use the system user name instead.
-	 */
-	public String getCommitterName() {
-		return getUserConfig().getCommitterName();
-	}
-
-	/**
-	 * @return the author email as defined in git variables and
-	 *         configurations. If no email could be found, try to
-	 *         propose one default with the user name and the
-	 *         host name.
-	 */
-	public String getAuthorEmail() {
-		return getUserConfig().getAuthorEmail();
-	}
-
-	/**
-	 * @return the committer email as defined in git variables and
-	 *         configurations. If no email could be found, try to
-	 *         propose one default with the user name and the
-	 *         host name.
-	 */
-	public String getCommitterEmail() {
-		return getUserConfig().getCommitterEmail();
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java
deleted file mode 100644
index 0473093..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-/**
- * A RepositoryListener gets notification about changes in refs or repository.
- * <p>
- * It currently does <em>not</em> get notification about which items are
- * changed.
- */
-public interface RepositoryListener {
-	/**
-	 * Invoked when a ref changes
-	 *
-	 * @param e
-	 *            information about the changes.
-	 */
-	void refsChanged(RefsChangedEvent e);
-
-	/**
-	 * Invoked when the index changes
-	 *
-	 * @param e
-	 *            information about the changes.
-	 */
-	void indexChanged(IndexChangedEvent e);
-
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java
index 2cf5225..0a59906 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java
@@ -55,6 +55,14 @@
  * on the state are the only supported means of deciding what to do.
  */
 public enum RepositoryState {
+	/** Has no work tree and cannot be used for normal editing. */
+	BARE {
+		public boolean canCheckout() { return false; }
+		public boolean canResetHead() { return false; }
+		public boolean canCommit() { return false; }
+		public String getDescription() { return "Bare"; }
+	},
+
 	/**
 	 * A safe state for working normally
 	 * */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java
similarity index 61%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java
index de8e3fa..f904120 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -43,48 +43,48 @@
 
 package org.eclipse.jgit.lib;
 
-import java.io.File;
 import java.io.IOException;
 
-/** Keeps track of a {@link PackFile}'s associated <code>.keep</code> file. */
-public class PackLock {
-	private final File keepFile;
+import org.eclipse.jgit.errors.ConfigInvalidException;
 
-	/**
-	 * Create a new lock for a pack file.
-	 *
-	 * @param packFile
-	 *            location of the <code>pack-*.pack</code> file.
-	 */
-	public PackLock(final File packFile) {
-		final File p = packFile.getParentFile();
-		final String n = packFile.getName();
-		keepFile = new File(p, n.substring(0, n.length() - 5) + ".keep");
+/**
+ * Persistent configuration that can be stored and loaded from a location.
+ */
+public abstract class StoredConfig extends Config {
+	/** Create a configuration with no default fallback. */
+	public StoredConfig() {
+		super();
 	}
 
 	/**
-	 * Create the <code>pack-*.keep</code> file, with the given message.
+	 * Create an empty configuration with a fallback for missing keys.
 	 *
-	 * @param msg
-	 *            message to store in the file.
-	 * @return true if the keep file was successfully written; false otherwise.
+	 * @param defaultConfig
+	 *            the base configuration to be consulted when a key is missing
+	 *            from this configuration instance.
+	 */
+	public StoredConfig(Config defaultConfig) {
+		super(defaultConfig);
+	}
+
+	/**
+	 * Load the configuration from the persistent store.
+	 * <p>
+	 * If the configuration does not exist, this configuration is cleared, and
+	 * thus behaves the same as though the backing store exists, but is empty.
+	 *
 	 * @throws IOException
-	 *             the keep file could not be written.
+	 *             the configuration could not be read (but does exist).
+	 * @throws ConfigInvalidException
+	 *             the configuration is not properly formatted.
 	 */
-	public boolean lock(String msg) throws IOException {
-		if (msg == null)
-			return false;
-		if (!msg.endsWith("\n"))
-			msg += "\n";
-		final LockFile lf = new LockFile(keepFile);
-		if (!lf.lock())
-			return false;
-		lf.write(Constants.encode(msg));
-		return lf.commit();
-	}
+	public abstract void load() throws IOException, ConfigInvalidException;
 
-	/** Remove the <code>.keep</code> file that holds this pack in place. */
-	public void unlock() {
-		keepFile.delete();
-	}
+	/**
+	 * Save the configuration to the persistent store.
+	 *
+	 * @throws IOException
+	 *             the configuration could not be written.
+	 */
+	public abstract void save() throws IOException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java
index 5b3531e..25a06c9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java
@@ -203,9 +203,14 @@ public void tag() throws IOException {
 		final RefUpdate ru;
 
 		if (tagger!=null || message!=null || type!=null) {
-			ObjectId tagid = new ObjectWriter(objdb).writeTag(this);
-			setTagId(tagid);
-			id = tagid;
+			ObjectInserter odi = objdb.newObjectInserter();
+			try {
+				id = odi.insert(Constants.OBJ_TAG, odi.format(this));
+				odi.flush();
+				setTagId(id);
+			} finally {
+				odi.release();
+			}
 		} else {
 			id = objId;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java
similarity index 62%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java
index e43c33a..9708bb2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -43,18 +43,69 @@
 
 package org.eclipse.jgit.lib;
 
+import java.util.concurrent.locks.ReentrantLock;
+
 /**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
+ * Wrapper around the general {@link ProgressMonitor} to make it thread safe.
  */
-public class RepositoryAdapter implements RepositoryListener {
+public class ThreadSafeProgressMonitor implements ProgressMonitor {
+	private final ProgressMonitor pm;
 
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
+	private final ReentrantLock lock;
+
+	/**
+	 * Wrap a ProgressMonitor to be thread safe.
+	 *
+	 * @param pm
+	 *            the underlying monitor to receive events.
+	 */
+	public ThreadSafeProgressMonitor(ProgressMonitor pm) {
+		this.pm = pm;
+		this.lock = new ReentrantLock();
 	}
 
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
+	public void start(int totalTasks) {
+		lock.lock();
+		try {
+			pm.start(totalTasks);
+		} finally {
+			lock.unlock();
+		}
 	}
 
+	public void beginTask(String title, int totalWork) {
+		lock.lock();
+		try {
+			pm.beginTask(title, totalWork);
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	public void update(int completed) {
+		lock.lock();
+		try {
+			pm.update(completed);
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	public boolean isCancelled() {
+		lock.lock();
+		try {
+			return pm.isCancelled();
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	public void endTask() {
+		lock.lock();
+		try {
+			pm.endTask();
+		} finally {
+			lock.unlock();
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java
index 0872c96..d68b9f6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java
@@ -537,10 +537,8 @@ public void accept(final TreeVisitor tv, final int flags)
 
 	private void ensureLoaded() throws IOException, MissingObjectException {
 		if (!isLoaded()) {
-			final ObjectLoader or = db.openTree(getId());
-			if (or == null)
-				throw new MissingObjectException(getId(), Constants.TYPE_TREE);
-			readTree(or.getBytes());
+			ObjectLoader ldr = db.open(getId(), Constants.OBJ_TREE);
+			readTree(ldr.getCachedBytes());
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java
deleted file mode 100644
index cd2eb38..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.zip.DataFormatException;
-import java.util.zip.Inflater;
-
-import org.eclipse.jgit.JGitText;
-import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.util.IO;
-import org.eclipse.jgit.util.MutableInteger;
-import org.eclipse.jgit.util.RawParseUtils;
-
-/**
- * Loose object loader. This class loads an object not stored in a pack.
- */
-public class UnpackedObjectLoader extends ObjectLoader {
-	private final int objectType;
-
-	private final int objectSize;
-
-	private final byte[] bytes;
-
-	/**
-	 * Construct an ObjectLoader to read from the file.
-	 *
-	 * @param path
-	 *            location of the loose object to read.
-	 * @param id
-	 *            expected identity of the object being loaded, if known.
-	 * @throws FileNotFoundException
-	 *             the loose object file does not exist.
-	 * @throws IOException
-	 *             the loose object file exists, but is corrupt.
-	 */
-	public UnpackedObjectLoader(final File path, final AnyObjectId id)
-			throws IOException {
-		this(IO.readFully(path), id);
-	}
-
-	/**
-	 * Construct an ObjectLoader from a loose object's compressed form.
-	 *
-	 * @param compressed
-	 *            entire content of the loose object file.
-	 * @throws CorruptObjectException
-	 *             The compressed data supplied does not match the format for a
-	 *             valid loose object.
-	 */
-	public UnpackedObjectLoader(final byte[] compressed)
-			throws CorruptObjectException {
-		this(compressed, null);
-	}
-
-	private UnpackedObjectLoader(final byte[] compressed, final AnyObjectId id)
-			throws CorruptObjectException {
-		// Try to determine if this is a legacy format loose object or
-		// a new style loose object. The legacy format was completely
-		// compressed with zlib so the first byte must be 0x78 (15-bit
-		// window size, deflated) and the first 16 bit word must be
-		// evenly divisible by 31. Otherwise its a new style loose
-		// object.
-		//
-		final Inflater inflater = InflaterCache.get();
-		try {
-			final int fb = compressed[0] & 0xff;
-			if (fb == 0x78 && (((fb << 8) | compressed[1] & 0xff) % 31) == 0) {
-				inflater.setInput(compressed);
-				final byte[] hdr = new byte[64];
-				int avail = 0;
-				while (!inflater.finished() && avail < hdr.length)
-					try {
-						int uncompressed = inflater.inflate(hdr, avail,
-								hdr.length - avail);
-						if (uncompressed == 0) {
-							throw new CorruptObjectException(id,
-									JGitText.get().corruptObjectBadStreamCorruptHeader);
-						}
-						avail += uncompressed;
-					} catch (DataFormatException dfe) {
-						final CorruptObjectException coe;
-						coe = new CorruptObjectException(id, JGitText.get().corruptObjectBadStream);
-						coe.initCause(dfe);
-						throw coe;
-					}
-				if (avail < 5)
-					throw new CorruptObjectException(id, JGitText.get().corruptObjectNoHeader);
-
-				final MutableInteger p = new MutableInteger();
-				objectType = Constants.decodeTypeString(id, hdr, (byte) ' ', p);
-				objectSize = RawParseUtils.parseBase10(hdr, p.value, p);
-				if (objectSize < 0)
-					throw new CorruptObjectException(id, JGitText.get().corruptObjectNegativeSize);
-				if (hdr[p.value++] != 0)
-					throw new CorruptObjectException(id, JGitText.get().corruptObjectGarbageAfterSize);
-				bytes = new byte[objectSize];
-				if (p.value < avail)
-					System.arraycopy(hdr, p.value, bytes, 0, avail - p.value);
-				decompress(id, inflater, avail - p.value);
-			} else {
-				int p = 0;
-				int c = compressed[p++] & 0xff;
-				final int typeCode = (c >> 4) & 7;
-				int size = c & 15;
-				int shift = 4;
-				while ((c & 0x80) != 0) {
-					c = compressed[p++] & 0xff;
-					size += (c & 0x7f) << shift;
-					shift += 7;
-				}
-
-				switch (typeCode) {
-				case Constants.OBJ_COMMIT:
-				case Constants.OBJ_TREE:
-				case Constants.OBJ_BLOB:
-				case Constants.OBJ_TAG:
-					objectType = typeCode;
-					break;
-				default:
-					throw new CorruptObjectException(id, JGitText.get().corruptObjectInvalidType);
-				}
-
-				objectSize = size;
-				bytes = new byte[objectSize];
-				inflater.setInput(compressed, p, compressed.length - p);
-				decompress(id, inflater, 0);
-			}
-		} finally {
-			InflaterCache.release(inflater);
-		}
-	}
-
-	private void decompress(final AnyObjectId id, final Inflater inf, int p)
-			throws CorruptObjectException {
-		try {
-			while (!inf.finished()) {
-				int uncompressed = inf.inflate(bytes, p, objectSize - p);
-				p += uncompressed;
-				if (uncompressed == 0 && !inf.finished()) {
-					throw new CorruptObjectException(id,
-							JGitText.get().corruptObjectBadStreamCorruptHeader);
-				}
-			}
-		} catch (DataFormatException dfe) {
-			final CorruptObjectException coe;
-			coe = new CorruptObjectException(id, JGitText.get().corruptObjectBadStream);
-			coe.initCause(dfe);
-			throw coe;
-		}
-		if (p != objectSize)
-			throw new CorruptObjectException(id, JGitText.get().corruptObjectIncorrectLength);
-	}
-
-	@Override
-	public int getType() {
-		return objectType;
-	}
-
-	@Override
-	public long getSize() {
-		return objectSize;
-	}
-
-	@Override
-	public byte[] getCachedBytes() {
-		return bytes;
-	}
-
-	@Override
-	public int getRawType() {
-		return objectType;
-	}
-
-	@Override
-	public long getRawSize() {
-		return objectSize;
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java
deleted file mode 100644
index fcfa573..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.zip.DataFormatException;
-
-import org.eclipse.jgit.JGitText;
-import org.eclipse.jgit.errors.CorruptObjectException;
-
-/** Reader for a non-delta (just deflated) object in a pack file. */
-class WholePackedObjectLoader extends PackedObjectLoader {
-	private static final int OBJ_COMMIT = Constants.OBJ_COMMIT;
-
-	WholePackedObjectLoader(final PackFile pr, final long objectOffset,
-			final int headerSize, final int type, final int size) {
-		super(pr, objectOffset, headerSize);
-		objectType = type;
-		objectSize = size;
-	}
-
-	@Override
-	public void materialize(final WindowCursor curs) throws IOException {
-		if (cachedBytes != null) {
-			return;
-		}
-
-		if (objectType != OBJ_COMMIT) {
-			UnpackedObjectCache.Entry cache = pack.readCache(objectOffset);
-			if (cache != null) {
-				curs.release();
-				cachedBytes = cache.data;
-				return;
-			}
-		}
-
-		try {
-			cachedBytes = pack.decompress(objectOffset + headerSize,
-					objectSize, curs);
-			curs.release();
-			if (objectType != OBJ_COMMIT)
-				pack.saveCache(objectOffset, cachedBytes, objectType);
-		} catch (DataFormatException dfe) {
-			final CorruptObjectException coe;
-			coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream,
-					objectOffset, pack.getPackFile()));
-			coe.initCause(dfe);
-			throw coe;
-		}
-	}
-
-	@Override
-	public int getRawType() {
-		return objectType;
-	}
-
-	@Override
-	public long getRawSize() {
-		return objectSize;
-	}
-
-	@Override
-	public ObjectId getDeltaBase() {
-		return null;
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java
index ef3d784..beab61a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java
@@ -165,6 +165,10 @@ private void checkoutTwoTrees() throws FileNotFoundException, IOException {
 	private void checkoutOutIndexNoHead() throws IOException {
 		new IndexTreeWalker(index, merge, root, new AbstractIndexTreeVisitor() {
 			public void visitEntry(TreeEntry m, Entry i, File f) throws IOException {
+				// TODO remove this once we support submodules
+				if (f.getName().equals(".gitmodules"))
+					throw new UnsupportedOperationException(
+							JGitText.get().submodulesNotSupported);
 				if (m == null) {
 					index.remove(root, f);
 					return;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java
index 1127b89..72857ff 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java
@@ -87,7 +87,7 @@ public enum ConflictState {
 	 *
 	 * @param sequenceIndex
 	 *            determines to which sequence this chunks belongs to. Same as
-	 *            in {@link MergeResult#add(int, int, int, ConflictState)}
+	 *            in {@link org.eclipse.jgit.merge.MergeResult#add}
 	 * @param begin
 	 *            the first element from the specified sequence which should be
 	 *            included in the merge result. Indexes start with 0.
@@ -109,8 +109,7 @@ protected MergeChunk(int sequenceIndex, int begin, int end,
 
 	/**
 	 * @return the index of the sequence to which sequence this chunks belongs
-	 *         to. Same as in
-	 *         {@link MergeResult#add(int, int, int, ConflictState)}
+	 *         to. Same as in {@link org.eclipse.jgit.merge.MergeResult#add}
 	 */
 	public int getSequenceIndex() {
 		return sequenceIndex;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
index 38af20f..68d60c0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
@@ -51,9 +51,9 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTree;
@@ -70,10 +70,13 @@ public abstract class Merger {
 	/** The repository this merger operates on. */
 	protected final Repository db;
 
+	/** Reader to support {@link #walk} and other object loading. */
+	protected final ObjectReader reader;
+
 	/** A RevWalk for computing merge bases, or listing incoming commits. */
 	protected final RevWalk walk;
 
-	private ObjectWriter writer;
+	private ObjectInserter inserter;
 
 	/** The original objects supplied in the merge; this can be any tree-ish. */
 	protected RevObject[] sourceObjects;
@@ -92,7 +95,8 @@ public abstract class Merger {
 	 */
 	protected Merger(final Repository local) {
 		db = local;
-		walk = new RevWalk(db);
+		reader = db.newObjectReader();
+		walk = new RevWalk(reader);
 	}
 
 	/**
@@ -105,10 +109,10 @@ public Repository getRepository() {
 	/**
 	 * @return an object writer to create objects in {@link #getRepository()}.
 	 */
-	public ObjectWriter getObjectWriter() {
-		if (writer == null)
-			writer = new ObjectWriter(getRepository());
-		return writer;
+	public ObjectInserter getObjectInserter() {
+		if (inserter == null)
+			inserter = getRepository().newObjectInserter();
+		return inserter;
 	}
 
 	/**
@@ -148,7 +152,13 @@ public boolean merge(final AnyObjectId[] tips) throws IOException {
 		for (int i = 0; i < sourceObjects.length; i++)
 			sourceTrees[i] = walk.parseTree(sourceObjects[i]);
 
-		return mergeImpl();
+		try {
+			return mergeImpl();
+		} finally {
+			if (inserter != null)
+				inserter.release();
+			reader.release();
+		}
 	}
 
 	/**
@@ -202,12 +212,7 @@ protected AbstractTreeIterator mergeBase(final int aIdx, final int bIdx)
 	 */
 	protected AbstractTreeIterator openTree(final AnyObjectId treeId)
 			throws IncorrectObjectTypeException, IOException {
-		final WindowCursor curs = new WindowCursor();
-		try {
-			return new CanonicalTreeParser(null, db, treeId, curs);
-		} finally {
-			curs.release();
-		}
+		return new CanonicalTreeParser(null, reader, treeId);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java
index 6cd2445..29342a7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java
@@ -51,6 +51,7 @@
 import org.eclipse.jgit.errors.UnmergedPathException;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
 import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
@@ -99,7 +100,7 @@ private static class InCoreMerger extends ThreeWayMerger {
 
 		InCoreMerger(final Repository local) {
 			super(local);
-			tw = new NameConflictTreeWalk(db);
+			tw = new NameConflictTreeWalk(reader);
 			cache = DirCache.newInCore();
 		}
 
@@ -152,7 +153,9 @@ else if (tw.isSubtree()) {
 			if (hasConflict)
 				return false;
 			try {
-				resultTree = cache.writeTree(getObjectWriter());
+				ObjectInserter odi = getObjectInserter();
+				resultTree = cache.writeTree(odi);
+				odi.flush();
 				return true;
 			} catch (UnmergedPathException upe) {
 				resultTree = null;
@@ -168,7 +171,7 @@ private void add(final int tree, final int stage) throws IOException {
 			final AbstractTreeIterator i = getTree(tree);
 			if (i != null) {
 				if (FileMode.TREE.equals(tw.getRawMode(tree))) {
-					builder.addTree(tw.getRawPath(), stage, db, tw
+					builder.addTree(tw.getRawPath(), stage, reader, tw
 							.getObjectId(tree));
 				} else {
 					final DirCacheEntry e;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
index 0c24fc6..eae1040 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
@@ -385,12 +385,12 @@ int parseGitFileName(int ptr, final int end) {
 					if (buf[sp - 2] != '"') {
 						return eol;
 					}
-					oldName = QuotedString.GIT_PATH.dequote(buf, bol, sp - 1);
-					oldName = p1(oldName);
+					oldPath = QuotedString.GIT_PATH.dequote(buf, bol, sp - 1);
+					oldPath = p1(oldPath);
 				} else {
-					oldName = decode(Constants.CHARSET, buf, aStart, sp - 1);
+					oldPath = decode(Constants.CHARSET, buf, aStart, sp - 1);
 				}
-				newName = oldName;
+				newPath = oldPath;
 				return eol;
 			}
 
@@ -431,27 +431,27 @@ int parseGitHeaders(int ptr, final int end) {
 				parseNewFileMode(ptr, eol);
 
 			} else if (match(buf, ptr, COPY_FROM) >= 0) {
-				oldName = parseName(oldName, ptr + COPY_FROM.length, eol);
+				oldPath = parseName(oldPath, ptr + COPY_FROM.length, eol);
 				changeType = ChangeType.COPY;
 
 			} else if (match(buf, ptr, COPY_TO) >= 0) {
-				newName = parseName(newName, ptr + COPY_TO.length, eol);
+				newPath = parseName(newPath, ptr + COPY_TO.length, eol);
 				changeType = ChangeType.COPY;
 
 			} else if (match(buf, ptr, RENAME_OLD) >= 0) {
-				oldName = parseName(oldName, ptr + RENAME_OLD.length, eol);
+				oldPath = parseName(oldPath, ptr + RENAME_OLD.length, eol);
 				changeType = ChangeType.RENAME;
 
 			} else if (match(buf, ptr, RENAME_NEW) >= 0) {
-				newName = parseName(newName, ptr + RENAME_NEW.length, eol);
+				newPath = parseName(newPath, ptr + RENAME_NEW.length, eol);
 				changeType = ChangeType.RENAME;
 
 			} else if (match(buf, ptr, RENAME_FROM) >= 0) {
-				oldName = parseName(oldName, ptr + RENAME_FROM.length, eol);
+				oldPath = parseName(oldPath, ptr + RENAME_FROM.length, eol);
 				changeType = ChangeType.RENAME;
 
 			} else if (match(buf, ptr, RENAME_TO) >= 0) {
-				newName = parseName(newName, ptr + RENAME_TO.length, eol);
+				newPath = parseName(newPath, ptr + RENAME_TO.length, eol);
 				changeType = ChangeType.RENAME;
 
 			} else if (match(buf, ptr, SIMILARITY_INDEX) >= 0) {
@@ -474,14 +474,14 @@ int parseGitHeaders(int ptr, final int end) {
 	}
 
 	void parseOldName(int ptr, final int eol) {
-		oldName = p1(parseName(oldName, ptr + OLD_NAME.length, eol));
-		if (oldName == DEV_NULL)
+		oldPath = p1(parseName(oldPath, ptr + OLD_NAME.length, eol));
+		if (oldPath == DEV_NULL)
 			changeType = ChangeType.ADD;
 	}
 
 	void parseNewName(int ptr, final int eol) {
-		newName = p1(parseName(newName, ptr + NEW_NAME.length, eol));
-		if (newName == DEV_NULL)
+		newPath = p1(parseName(newPath, ptr + NEW_NAME.length, eol));
+		if (newPath == DEV_NULL)
 			changeType = ChangeType.DELETE;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java
index a068943..dc9e032 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java
@@ -65,19 +65,16 @@ public class PlotCommit<L extends PlotLane> extends RevCommit {
 
 	PlotCommit[] children;
 
-	final Ref[] refs;
+	Ref[] refs;
 
 	/**
 	 * Create a new commit.
 	 *
 	 * @param id
 	 *            the identity of this commit.
-	 * @param tags
-	 *            the tags associated with this commit, null for no tags
 	 */
-	protected PlotCommit(final AnyObjectId id, final Ref[] tags) {
+	protected PlotCommit(final AnyObjectId id) {
 		super(id);
-		this.refs = tags;
 		passingLanes = NO_LANES;
 		children = NO_CHILDREN;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
index 6b4ed80..c69e66c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
@@ -52,13 +52,16 @@
 import java.util.Set;
 
 import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.Commit;
+import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.Tag;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevWalk;
 
 /** Specialized RevWalk for visualization of a commit graph. */
@@ -93,14 +96,19 @@ public void sort(final RevSort s, final boolean use) {
 
 	@Override
 	protected RevCommit createCommit(final AnyObjectId id) {
-		return new PlotCommit(id, getTags(id));
+		return new PlotCommit(id);
 	}
 
-	/**
-	 * @param commitId
-	 * @return return the list of knows tags referring to this commit
-	 */
-	protected Ref[] getTags(final AnyObjectId commitId) {
+	@Override
+	public RevCommit next() throws MissingObjectException,
+			IncorrectObjectTypeException, IOException {
+		PlotCommit<?> pc = (PlotCommit) super.next();
+		if (pc != null)
+			pc.refs = getTags(pc);
+		return pc;
+	}
+
+	private Ref[] getTags(final AnyObjectId commitId) {
 		Collection<Ref> list = reverseRefMap.get(commitId);
 		Ref[] tags;
 		if (list == null)
@@ -115,8 +123,8 @@ protected RevCommit createCommit(final AnyObjectId id) {
 	class PlotRefComparator implements Comparator<Ref> {
 		public int compare(Ref o1, Ref o2) {
 			try {
-				Object obj1 = getRepository().mapObject(o1.getObjectId(), o1.getName());
-				Object obj2 = getRepository().mapObject(o2.getObjectId(), o2.getName());
+				RevObject obj1 = parseAny(o1.getObjectId());
+				RevObject obj2 = parseAny(o2.getObjectId());
 				long t1 = timeof(obj1);
 				long t2 = timeof(obj2);
 				if (t1 > t2)
@@ -129,11 +137,15 @@ public int compare(Ref o1, Ref o2) {
 				return 0;
 			}
 		}
-		long timeof(Object o) {
-			if (o instanceof Commit)
-				return ((Commit)o).getCommitter().getWhen().getTime();
-			if (o instanceof Tag)
-				return ((Tag)o).getTagger().getWhen().getTime();
+
+		long timeof(RevObject o) {
+			if (o instanceof RevCommit)
+				return ((RevCommit) o).getCommitTime();
+			if (o instanceof RevTag) {
+				RevTag tag = (RevTag) o;
+				PersonIdent who = tag.getTaggerIdent();
+				return who != null ? who.getWhen().getTime() : 0;
+			}
 			return 0;
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
index edb8837..76510ce 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
@@ -137,7 +137,7 @@ RevCommit next() throws MissingObjectException,
 		for (;;) {
 			final RevCommit c = pending.next();
 			if (c == null) {
-				walker.curs.release();
+				walker.reader.release();
 				return null;
 			}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
index 11d4001..a6ecfe2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
@@ -53,6 +53,7 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
 
@@ -97,7 +98,19 @@ public class ObjectWalk extends RevWalk {
 	 *            the repository the walker will obtain data from.
 	 */
 	public ObjectWalk(final Repository repo) {
-		super(repo);
+		this(repo.newObjectReader());
+	}
+
+	/**
+	 * Create a new revision and object walker for a given repository.
+	 *
+	 * @param or
+	 *            the reader the walker will obtain data from. The reader should
+	 *            be released by the caller when the walker is no longer
+	 *            required.
+	 */
+	public ObjectWalk(ObjectReader or) {
+		super(or);
 		pendingObjects = new BlockObjQueue();
 		treeWalk = new CanonicalTreeParser();
 	}
@@ -294,14 +307,14 @@ public RevObject nextObject() throws MissingObjectException,
 				continue;
 			if (o instanceof RevTree) {
 				currentTree = (RevTree) o;
-				treeWalk = treeWalk.resetRoot(db, currentTree, curs);
+				treeWalk = treeWalk.resetRoot(reader, currentTree);
 			}
 			return o;
 		}
 	}
 
 	private CanonicalTreeParser enter(RevObject tree) throws IOException {
-		CanonicalTreeParser p = treeWalk.createSubtreeIterator0(db, tree, curs);
+		CanonicalTreeParser p = treeWalk.createSubtreeIterator0(reader, tree);
 		if (p.eof()) {
 			// We can't tolerate the subtree being an empty tree, as
 			// that will break us out early before we visit all names.
@@ -349,7 +362,7 @@ public void checkConnectivity() throws MissingObjectException,
 			final RevObject o = nextObject();
 			if (o == null)
 				break;
-			if (o instanceof RevBlob && !db.hasObject(o))
+			if (o instanceof RevBlob && !reader.has(o))
 				throw new MissingObjectException(o, Constants.TYPE_BLOB);
 		}
 	}
@@ -371,6 +384,18 @@ public String getPathString() {
 		return last != null ? treeWalk.getEntryPathString() : null;
 	}
 
+	/**
+	 * Get the current object's path hash code.
+	 * <p>
+	 * This method computes a hash code on the fly for this path, the hash is
+	 * suitable to cluster objects that may have similar paths together.
+	 *
+	 * @return path hash code; any integer may be returned.
+	 */
+	public int getPathHashCode() {
+		return last != null ? treeWalk.getEntryPathHashCode() : 0;
+	}
+
 	@Override
 	public void dispose() {
 		super.dispose();
@@ -403,7 +428,7 @@ private void markTreeUninteresting(final RevTree tree)
 			return;
 		tree.flags |= UNINTERESTING;
 
-		treeWalk = treeWalk.resetRoot(db, tree, curs);
+		treeWalk = treeWalk.resetRoot(reader, tree);
 		while (!treeWalk.eof()) {
 			final FileMode mode = treeWalk.getEntryFileMode();
 			final int sType = mode.getObjectType();
@@ -419,7 +444,7 @@ private void markTreeUninteresting(final RevTree tree)
 				final RevTree t = lookupTree(idBuffer);
 				if ((t.flags & UNINTERESTING) == 0) {
 					t.flags |= UNINTERESTING;
-					treeWalk = treeWalk.createSubtreeIterator0(db, t, curs);
+					treeWalk = treeWalk.createSubtreeIterator0(reader, t);
 					continue;
 				}
 				break;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java
index e723bce..0e2bb98 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java
@@ -128,7 +128,7 @@ RevCommit next() throws MissingObjectException,
 			for (;;) {
 				final RevCommit c = pending.next();
 				if (c == null) {
-					walker.curs.release();
+					walker.reader.release();
 					return null;
 				}
 
@@ -174,7 +174,7 @@ else if (canDispose)
 					c.disposeBody();
 			}
 		} catch (StopWalkException swe) {
-			walker.curs.release();
+			walker.reader.release();
 			pending.clear();
 			return null;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
index 2d96bbf..84cc704 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -214,7 +214,8 @@ public final int getCommitTime() {
 	 * @return parsed commit.
 	 */
 	public final Commit asCommit(final RevWalk walk) {
-		return new Commit(walk.db, this, buffer);
+		// TODO(spearce) Remove repository when this method dies.
+		return new Commit(walk.repository, this, buffer);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java
index 5dde43b..a19f4d8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java
@@ -51,7 +51,6 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectLoader;
 
 /** Base object type accessed during revision walking. */
 public abstract class RevObject extends ObjectId {
@@ -78,13 +77,7 @@ void parseBody(final RevWalk walk) throws MissingObjectException,
 	final byte[] loadCanonical(final RevWalk walk) throws IOException,
 			MissingObjectException, IncorrectObjectTypeException,
 			CorruptObjectException {
-		final ObjectLoader ldr = walk.db.openObject(walk.curs, this);
-		if (ldr == null)
-			throw new MissingObjectException(this, getType());
-		final byte[] data = ldr.getCachedBytes();
-		if (getType() != ldr.getType())
-			throw new IncorrectObjectTypeException(this, getType());
-		return data;
+		return walk.reader.open(this, getType()).getCachedBytes();
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
index d2a665e..a04ea71 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
@@ -194,7 +194,7 @@ public final String getShortMessage() {
 	 * @return parsed tag.
 	 */
 	public Tag asTag(final RevWalk walk) {
-		return new Tag(walk.db, this, tagName, buffer);
+		return new Tag(walk.repository, this, tagName, buffer);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
index a8c67c6..7406bb6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -62,7 +62,7 @@
 import org.eclipse.jgit.lib.ObjectIdSubclassMap;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.revwalk.filter.RevFilter;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
 
@@ -157,9 +157,10 @@ public class RevWalk implements Iterable<RevCommit> {
 
 	private static final int APP_FLAGS = -1 & ~((1 << RESERVED_FLAGS) - 1);
 
-	final Repository db;
+	/** Exists <b>ONLY</b> to support legacy Tag and Commit objects. */
+	final Repository repository;
 
-	final WindowCursor curs;
+	final ObjectReader reader;
 
 	final MutableObjectId idBuffer;
 
@@ -189,11 +190,29 @@ public class RevWalk implements Iterable<RevCommit> {
 	 * Create a new revision walker for a given repository.
 	 *
 	 * @param repo
-	 *            the repository the walker will obtain data from.
+	 *            the repository the walker will obtain data from. An
+	 *            ObjectReader will be created by the walker, and must be
+	 *            released by the caller.
 	 */
 	public RevWalk(final Repository repo) {
-		db = repo;
-		curs = new WindowCursor();
+		this(repo, repo.newObjectReader());
+	}
+
+	/**
+	 * Create a new revision walker for a given repository.
+	 *
+	 * @param or
+	 *            the reader the walker will obtain data from. The reader should
+	 *            be released by the caller when the walker is no longer
+	 *            required.
+	 */
+	public RevWalk(ObjectReader or) {
+		this(null, or);
+	}
+
+	private RevWalk(final Repository repo, final ObjectReader or) {
+		repository = repo;
+		reader = or;
 		idBuffer = new MutableObjectId();
 		objects = new ObjectIdSubclassMap<RevObject>();
 		roots = new ArrayList<RevCommit>();
@@ -205,13 +224,19 @@ public RevWalk(final Repository repo) {
 		retainBody = true;
 	}
 
+	/** @return the reader this walker is using to load objects. */
+	public ObjectReader getObjectReader() {
+		return reader;
+	}
+
 	/**
-	 * Get the repository this walker loads objects from.
-	 *
-	 * @return the repository this walker was created to read.
+	 * Release any resources used by this walker's reader.
+	 * <p>
+	 * A walker that has been released can be used again, but may need to be
+	 * released after the subsequent usage.
 	 */
-	public Repository getRepository() {
-		return db;
+	public void release() {
+		reader.release();
 	}
 
 	/**
@@ -678,11 +703,7 @@ public RevObject lookupAny(final AnyObjectId id, final int type) {
 	public RevCommit parseCommit(final AnyObjectId id)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
-		RevObject c = parseAny(id);
-		while (c instanceof RevTag) {
-			c = ((RevTag) c).getObject();
-			parseHeaders(c);
-		}
+		RevObject c = peel(parseAny(id));
 		if (!(c instanceof RevCommit))
 			throw new IncorrectObjectTypeException(id.toObjectId(),
 					Constants.TYPE_COMMIT);
@@ -709,11 +730,7 @@ public RevCommit parseCommit(final AnyObjectId id)
 	public RevTree parseTree(final AnyObjectId id)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
-		RevObject c = parseAny(id);
-		while (c instanceof RevTag) {
-			c = ((RevTag) c).getObject();
-			parseHeaders(c);
-		}
+		RevObject c = peel(parseAny(id));
 
 		final RevTree t;
 		if (c instanceof RevCommit)
@@ -773,15 +790,12 @@ public RevObject parseAny(final AnyObjectId id)
 			throws MissingObjectException, IOException {
 		RevObject r = objects.get(id);
 		if (r == null) {
-			final ObjectLoader ldr = db.openObject(curs, id);
-			if (ldr == null)
-				throw new MissingObjectException(id.toObjectId(), "unknown");
-			final byte[] data = ldr.getCachedBytes();
+			final ObjectLoader ldr = reader.open(id);
 			final int type = ldr.getType();
 			switch (type) {
 			case Constants.OBJ_COMMIT: {
 				final RevCommit c = createCommit(id);
-				c.parseCanonical(this, data);
+				c.parseCanonical(this, ldr.getCachedBytes());
 				r = c;
 				break;
 			}
@@ -797,7 +811,7 @@ public RevObject parseAny(final AnyObjectId id)
 			}
 			case Constants.OBJ_TAG: {
 				final RevTag t = new RevTag(id);
-				t.parseCanonical(this, data);
+				t.parseCanonical(this, ldr.getCachedBytes());
 				r = t;
 				break;
 			}
@@ -848,6 +862,29 @@ public void parseBody(final RevObject obj)
 	}
 
 	/**
+	 * Peel back annotated tags until a non-tag object is found.
+	 *
+	 * @param obj
+	 *            the starting object.
+	 * @return If {@code obj} is not an annotated tag, {@code obj}. Otherwise
+	 *         the first non-tag object that {@code obj} references. The
+	 *         returned object's headers have been parsed.
+	 * @throws MissingObjectException
+	 *             a referenced object cannot be found.
+	 * @throws IOException
+	 *             a pack file or loose object could not be read.
+	 */
+	public RevObject peel(RevObject obj) throws MissingObjectException,
+			IOException {
+		while (obj instanceof RevTag) {
+			parseHeaders(obj);
+			obj = ((RevTag) obj).getObject();
+		}
+		parseHeaders(obj);
+		return obj;
+	}
+
+	/**
 	 * Create a new flag for application use during walking.
 	 * <p>
 	 * Applications are only assured to be able to create 24 unique flags on any
@@ -1023,7 +1060,7 @@ protected void reset(int retainFlags) {
 			}
 		}
 
-		curs.release();
+		reader.release();
 		roots.clear();
 		queue = new DateRevQueue();
 		pending = new StartGenerator(this);
@@ -1038,11 +1075,12 @@ protected void reset(int retainFlags) {
 	 * All RevFlag instances are also invalidated, and must not be reused.
 	 */
 	public void dispose() {
+		reader.release();
 		freeFlags = APP_FLAGS;
 		delayFreeFlags = 0;
 		carryFlags = UNINTERESTING;
 		objects.clear();
-		curs.release();
+		reader.release();
 		roots.clear();
 		queue = new DateRevQueue();
 		pending = new StartGenerator(this);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java
index 4c5a2a7..41cfcf8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java
@@ -82,11 +82,11 @@ class RewriteTreeFilter extends RevFilter {
 
 	private final TreeWalk pathFilter;
 
-	private final Repository repo;
+	private final Repository repository;
 
 	RewriteTreeFilter(final RevWalk walker, final TreeFilter t) {
-		repo = walker.db;
-		pathFilter = new TreeWalk(repo);
+		repository = walker.repository;
+		pathFilter = new TreeWalk(walker.reader);
 		pathFilter.setFilter(t);
 		pathFilter.setRecursive(t.shouldBeRecursive());
 	}
@@ -239,14 +239,14 @@ private void updateFollowFilter(ObjectId[] trees)
 		tw.reset(trees);
 
 		List<DiffEntry> files = DiffEntry.scan(tw);
-		RenameDetector rd = new RenameDetector(repo);
+		RenameDetector rd = new RenameDetector(repository);
 		rd.addAll(files);
 		files = rd.compute();
 
 		TreeFilter newFilter = oldFilter;
 		for (DiffEntry ent : files) {
-			if (isRename(ent) && ent.getNewName().equals(oldFilter.getPath())) {
-				newFilter = FollowFilter.create(ent.getOldName());
+			if (isRename(ent) && ent.getNewPath().equals(oldFilter.getPath())) {
+				newFilter = FollowFilter.create(ent.getOldPath());
 				break;
 			}
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java
similarity index 76%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java
index 8042610..457c8dc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java
@@ -43,8 +43,11 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.CRC32;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
 
@@ -67,31 +70,25 @@ protected int copy(final int p, final byte[] b, final int o, int n) {
 	}
 
 	@Override
-	protected int inflate(final int pos, final byte[] b, int o,
-			final Inflater inf) throws DataFormatException {
-		while (!inf.finished()) {
-			if (inf.needsInput()) {
-				inf.setInput(array, pos, array.length - pos);
-				break;
-			}
-			o += inf.inflate(b, o, b.length - o);
-		}
-		while (!inf.finished() && !inf.needsInput())
-			o += inf.inflate(b, o, b.length - o);
-		return o;
+	protected int setInput(final int pos, final Inflater inf)
+			throws DataFormatException {
+		int n = array.length - pos;
+		inf.setInput(array, pos, n);
+		return n;
 	}
 
-	@Override
-	protected void inflateVerify(final int pos, final Inflater inf)
+	void crc32(CRC32 out, long pos, int cnt) {
+		out.update(array, (int) (pos - start), cnt);
+	}
+
+	void write(OutputStream out, long pos, int cnt) throws IOException {
+		out.write(array, (int) (pos - start), cnt);
+	}
+
+	void check(Inflater inf, byte[] tmp, long pos, int cnt)
 			throws DataFormatException {
-		while (!inf.finished()) {
-			if (inf.needsInput()) {
-				inf.setInput(array, pos, array.length - pos);
-				break;
-			}
-			inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length);
-		}
-		while (!inf.finished() && !inf.needsInput())
-			inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length);
+		inf.setInput(array, (int) (pos - start), cnt);
+		while (inf.inflate(tmp, 0, tmp.length) > 0)
+			continue;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java
similarity index 72%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java
index 1b29934..29a0159 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java
@@ -43,7 +43,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.nio.ByteBuffer;
 import java.util.zip.DataFormatException;
@@ -72,39 +72,13 @@ protected int copy(final int p, final byte[] b, final int o, int n) {
 	}
 
 	@Override
-	protected int inflate(final int pos, final byte[] b, int o,
-			final Inflater inf) throws DataFormatException {
-		final byte[] tmp = new byte[512];
-		final ByteBuffer s = buffer.slice();
-		s.position(pos);
-		while (s.remaining() > 0 && !inf.finished()) {
-			if (inf.needsInput()) {
-				final int n = Math.min(s.remaining(), tmp.length);
-				s.get(tmp, 0, n);
-				inf.setInput(tmp, 0, n);
-			}
-			o += inf.inflate(b, o, b.length - o);
-		}
-		while (!inf.finished() && !inf.needsInput())
-			o += inf.inflate(b, o, b.length - o);
-		return o;
-	}
-
-	@Override
-	protected void inflateVerify(final int pos, final Inflater inf)
+	protected int setInput(final int pos, final Inflater inf)
 			throws DataFormatException {
-		final byte[] tmp = new byte[512];
 		final ByteBuffer s = buffer.slice();
 		s.position(pos);
-		while (s.remaining() > 0 && !inf.finished()) {
-			if (inf.needsInput()) {
-				final int n = Math.min(s.remaining(), tmp.length);
-				s.get(tmp, 0, n);
-				inf.setInput(tmp, 0, n);
-			}
-			inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length);
-		}
-		while (!inf.finished() && !inf.needsInput())
-			inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length);
+		final byte[] tmp = new byte[Math.min(s.remaining(), 512)];
+		s.get(tmp, 0, tmp.length);
+		inf.setInput(tmp, 0, tmp.length);
+		return tmp.length;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java
similarity index 62%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java
index cbef421..f92efb4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java
@@ -42,7 +42,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
@@ -117,69 +117,10 @@ final int copy(long pos, byte[] dstbuf, int dstoff, int cnt) {
 	 */
 	protected abstract int copy(int pos, byte[] dstbuf, int dstoff, int cnt);
 
-	/**
-	 * Pump bytes into the supplied inflater as input.
-	 *
-	 * @param pos
-	 *            offset within the file to start supplying input from.
-	 * @param dstbuf
-	 *            destination buffer the inflater should output decompressed
-	 *            data to.
-	 * @param dstoff
-	 *            current offset within <code>dstbuf</code> to inflate into.
-	 * @param inf
-	 *            the inflater to feed input to. The caller is responsible for
-	 *            initializing the inflater as multiple windows may need to
-	 *            supply data to the same inflater to completely decompress
-	 *            something.
-	 * @return updated <code>dstoff</code> based on the number of bytes
-	 *         successfully copied into <code>dstbuf</code> by
-	 *         <code>inf</code>. If the inflater is not yet finished then
-	 *         another window's data must still be supplied as input to finish
-	 *         decompression.
-	 * @throws DataFormatException
-	 *             the inflater encountered an invalid chunk of data. Data
-	 *             stream corruption is likely.
-	 */
-	final int inflate(long pos, byte[] dstbuf, int dstoff, Inflater inf)
-			throws DataFormatException {
-		return inflate((int) (pos - start), dstbuf, dstoff, inf);
+	final int setInput(long pos, Inflater inf) throws DataFormatException {
+		return setInput((int) (pos - start), inf);
 	}
 
-	/**
-	 * Pump bytes into the supplied inflater as input.
-	 *
-	 * @param pos
-	 *            offset within the window to start supplying input from.
-	 * @param dstbuf
-	 *            destination buffer the inflater should output decompressed
-	 *            data to.
-	 * @param dstoff
-	 *            current offset within <code>dstbuf</code> to inflate into.
-	 * @param inf
-	 *            the inflater to feed input to. The caller is responsible for
-	 *            initializing the inflater as multiple windows may need to
-	 *            supply data to the same inflater to completely decompress
-	 *            something.
-	 * @return updated <code>dstoff</code> based on the number of bytes
-	 *         successfully copied into <code>dstbuf</code> by
-	 *         <code>inf</code>. If the inflater is not yet finished then
-	 *         another window's data must still be supplied as input to finish
-	 *         decompression.
-	 * @throws DataFormatException
-	 *             the inflater encountered an invalid chunk of data. Data
-	 *             stream corruption is likely.
-	 */
-	protected abstract int inflate(int pos, byte[] dstbuf, int dstoff,
-			Inflater inf) throws DataFormatException;
-
-	protected static final byte[] verifyGarbageBuffer = new byte[2048];
-
-	final void inflateVerify(final long pos, final Inflater inf)
-			throws DataFormatException {
-		inflateVerify((int) (pos - start), inf);
-	}
-
-	protected abstract void inflateVerify(int pos, Inflater inf)
+	protected abstract int setInput(int pos, Inflater inf)
 			throws DataFormatException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java
new file mode 100644
index 0000000..8ea0b85
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2010, JetBrains s.r.o.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectDatabase;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSubclassMap;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.storage.pack.ObjectToPack;
+import org.eclipse.jgit.storage.pack.PackWriter;
+
+/**
+ * The cached instance of an {@link ObjectDirectory}.
+ * <p>
+ * This class caches the list of loose objects in memory, so the file system is
+ * not queried with stat calls.
+ */
+class CachedObjectDirectory extends FileObjectDatabase {
+	/**
+	 * The set that contains unpacked objects identifiers, it is created when
+	 * the cached instance is created.
+	 */
+	private final ObjectIdSubclassMap<ObjectId> unpackedObjects = new ObjectIdSubclassMap<ObjectId>();
+
+	private final ObjectDirectory wrapped;
+
+	private AlternateHandle[] alts;
+
+	/**
+	 * The constructor
+	 *
+	 * @param wrapped
+	 *            the wrapped database
+	 */
+	CachedObjectDirectory(ObjectDirectory wrapped) {
+		this.wrapped = wrapped;
+
+		File objects = wrapped.getDirectory();
+		String[] fanout = objects.list();
+		if (fanout == null)
+			fanout = new String[0];
+		for (String d : fanout) {
+			if (d.length() != 2)
+				continue;
+			String[] entries = new File(objects, d).list();
+			if (entries == null)
+				continue;
+			for (String e : entries) {
+				if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
+					continue;
+				try {
+					unpackedObjects.add(ObjectId.fromString(d + e));
+				} catch (IllegalArgumentException notAnObject) {
+					// ignoring the file that does not represent loose object
+				}
+			}
+		}
+	}
+
+	@Override
+	public void close() {
+		// Don't close anything.
+	}
+
+	@Override
+	public ObjectInserter newInserter() {
+		return wrapped.newInserter();
+	}
+
+	@Override
+	public ObjectDatabase newCachedDatabase() {
+		return this;
+	}
+
+	@Override
+	FileObjectDatabase newCachedFileObjectDatabase() {
+		return this;
+	}
+
+	@Override
+	File getDirectory() {
+		return wrapped.getDirectory();
+	}
+
+	@Override
+	AlternateHandle[] myAlternates() {
+		if (alts == null) {
+			AlternateHandle[] src = wrapped.myAlternates();
+			alts = new AlternateHandle[src.length];
+			for (int i = 0; i < alts.length; i++) {
+				FileObjectDatabase s = src[i].db;
+				alts[i] = new AlternateHandle(s.newCachedFileObjectDatabase());
+			}
+		}
+		return alts;
+	}
+
+	@Override
+	boolean tryAgain1() {
+		return wrapped.tryAgain1();
+	}
+
+	@Override
+	public boolean has(final AnyObjectId objectId) {
+		return hasObjectImpl1(objectId);
+	}
+
+	@Override
+	boolean hasObject1(AnyObjectId objectId) {
+		return unpackedObjects.contains(objectId)
+				|| wrapped.hasObject1(objectId);
+	}
+
+	@Override
+	ObjectLoader openObject(final WindowCursor curs,
+			final AnyObjectId objectId) throws IOException {
+		return openObjectImpl1(curs, objectId);
+	}
+
+	@Override
+	ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId)
+			throws IOException {
+		if (unpackedObjects.contains(objectId))
+			return wrapped.openObject2(curs, objectId.name(), objectId);
+		return wrapped.openObject1(curs, objectId);
+	}
+
+	@Override
+	boolean hasObject2(String objectId) {
+		// This method should never be invoked.
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	ObjectLoader openObject2(WindowCursor curs, String objectName,
+			AnyObjectId objectId) throws IOException {
+		// This method should never be invoked.
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	long getObjectSize1(WindowCursor curs, AnyObjectId objectId) throws IOException {
+		if (unpackedObjects.contains(objectId))
+			return wrapped.getObjectSize2(curs, objectId.name(), objectId);
+		return wrapped.getObjectSize1(curs, objectId);
+	}
+
+	@Override
+	long getObjectSize2(WindowCursor curs, String objectName, AnyObjectId objectId)
+			throws IOException {
+		// This method should never be invoked.
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
+			WindowCursor curs) throws IOException {
+		wrapped.selectObjectRepresentation(packer, otp, curs);
+	}
+
+	@Override
+	int getStreamFileThreshold() {
+		return wrapped.getStreamFileThreshold();
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
similarity index 87%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
index eb00917..8ffbe80 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
@@ -47,7 +47,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -56,24 +56,32 @@
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
 
 /**
  * The configuration file that is stored in the file of the file system.
  */
-public class FileBasedConfig extends Config {
+public class FileBasedConfig extends StoredConfig {
 	private final File configFile;
 	private volatile long lastModified;
+	private final FS fs;
 
 	/**
 	 * Create a configuration with no default fallback.
 	 *
 	 * @param cfgLocation
 	 *            the location of the configuration file on the file system
+	 * @param fs
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
 	 */
-	public FileBasedConfig(File cfgLocation) {
-		this(null, cfgLocation);
+	public FileBasedConfig(File cfgLocation, FS fs) {
+		this(null, cfgLocation, fs);
 	}
 
 	/**
@@ -83,10 +91,14 @@ public FileBasedConfig(File cfgLocation) {
 	 *            the base configuration file
 	 * @param cfgLocation
 	 *            the location of the configuration file on the file system
+	 * @param fs
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
 	 */
-	public FileBasedConfig(Config base, File cfgLocation) {
+	public FileBasedConfig(Config base, File cfgLocation, FS fs) {
 		super(base);
 		configFile = cfgLocation;
+		this.fs = fs;
 	}
 
 	/** @return location of the configuration file on disk */
@@ -105,6 +117,7 @@ public final File getFile() {
 	 * @throws ConfigInvalidException
 	 *             the file is not a properly formatted configuration file.
 	 */
+	@Override
 	public void load() throws IOException, ConfigInvalidException {
 		lastModified = getFile().lastModified();
 		try {
@@ -134,7 +147,7 @@ public void load() throws IOException, ConfigInvalidException {
 	 */
 	public void save() throws IOException {
 		final byte[] out = Constants.encode(toText());
-		final LockFile lf = new LockFile(getFile());
+		final LockFile lf = new LockFile(getFile(), fs);
 		if (!lf.lock())
 			throw new IOException(MessageFormat.format(JGitText.get().cannotLockFile, getFile()));
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java
new file mode 100644
index 0000000..250c7ca
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectDatabase;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.storage.pack.ObjectToPack;
+import org.eclipse.jgit.storage.pack.PackWriter;
+
+abstract class FileObjectDatabase extends ObjectDatabase {
+	@Override
+	public ObjectReader newReader() {
+		return new WindowCursor(this);
+	}
+
+	/**
+	 * Does the requested object exist in this database?
+	 * <p>
+	 * Alternates (if present) are searched automatically.
+	 *
+	 * @param objectId
+	 *            identity of the object to test for existence of.
+	 * @return true if the specified object is stored in this database, or any
+	 *         of the alternate databases.
+	 */
+	public boolean has(final AnyObjectId objectId) {
+		return hasObjectImpl1(objectId) || hasObjectImpl2(objectId.name());
+	}
+
+	final boolean hasObjectImpl1(final AnyObjectId objectId) {
+		if (hasObject1(objectId))
+			return true;
+
+		for (final AlternateHandle alt : myAlternates()) {
+			if (alt.db.hasObjectImpl1(objectId))
+				return true;
+		}
+
+		return tryAgain1() && hasObject1(objectId);
+	}
+
+	final boolean hasObjectImpl2(final String objectId) {
+		if (hasObject2(objectId))
+			return true;
+
+		for (final AlternateHandle alt : myAlternates()) {
+			if (alt.db.hasObjectImpl2(objectId))
+				return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Open an object from this database.
+	 * <p>
+	 * Alternates (if present) are searched automatically.
+	 *
+	 * @param curs
+	 *            temporary working space associated with the calling thread.
+	 * @param objectId
+	 *            identity of the object to open.
+	 * @return a {@link ObjectLoader} for accessing the data of the named
+	 *         object, or null if the object does not exist.
+	 * @throws IOException
+	 */
+	ObjectLoader openObject(final WindowCursor curs, final AnyObjectId objectId)
+			throws IOException {
+		ObjectLoader ldr;
+
+		ldr = openObjectImpl1(curs, objectId);
+		if (ldr != null)
+			return ldr;
+
+		ldr = openObjectImpl2(curs, objectId.name(), objectId);
+		if (ldr != null)
+			return ldr;
+
+		return null;
+	}
+
+	final ObjectLoader openObjectImpl1(final WindowCursor curs,
+			final AnyObjectId objectId) throws IOException {
+		ObjectLoader ldr;
+
+		ldr = openObject1(curs, objectId);
+		if (ldr != null)
+			return ldr;
+
+		for (final AlternateHandle alt : myAlternates()) {
+			ldr = alt.db.openObjectImpl1(curs, objectId);
+			if (ldr != null)
+				return ldr;
+		}
+
+		if (tryAgain1()) {
+			ldr = openObject1(curs, objectId);
+			if (ldr != null)
+				return ldr;
+		}
+
+		return null;
+	}
+
+	final ObjectLoader openObjectImpl2(final WindowCursor curs,
+			final String objectName, final AnyObjectId objectId)
+			throws IOException {
+		ObjectLoader ldr;
+
+		ldr = openObject2(curs, objectName, objectId);
+		if (ldr != null)
+			return ldr;
+
+		for (final AlternateHandle alt : myAlternates()) {
+			ldr = alt.db.openObjectImpl2(curs, objectName, objectId);
+			if (ldr != null)
+				return ldr;
+		}
+
+		return null;
+	}
+
+	long getObjectSize(WindowCursor curs, AnyObjectId objectId)
+			throws IOException {
+		long sz = getObjectSizeImpl1(curs, objectId);
+		if (0 <= sz)
+			return sz;
+		return getObjectSizeImpl2(curs, objectId.name(), objectId);
+	}
+
+	final long getObjectSizeImpl1(final WindowCursor curs,
+			final AnyObjectId objectId) throws IOException {
+		long sz;
+
+		sz = getObjectSize1(curs, objectId);
+		if (0 <= sz)
+			return sz;
+
+		for (final AlternateHandle alt : myAlternates()) {
+			sz = alt.db.getObjectSizeImpl1(curs, objectId);
+			if (0 <= sz)
+				return sz;
+		}
+
+		if (tryAgain1()) {
+			sz = getObjectSize1(curs, objectId);
+			if (0 <= sz)
+				return sz;
+		}
+
+		return -1;
+	}
+
+	final long getObjectSizeImpl2(final WindowCursor curs,
+			final String objectName, final AnyObjectId objectId)
+			throws IOException {
+		long sz;
+
+		sz = getObjectSize2(curs, objectName, objectId);
+		if (0 <= sz)
+			return sz;
+
+		for (final AlternateHandle alt : myAlternates()) {
+			sz = alt.db.getObjectSizeImpl2(curs, objectName, objectId);
+			if (0 <= sz)
+				return sz;
+		}
+
+		return -1;
+	}
+
+	abstract void selectObjectRepresentation(PackWriter packer,
+			ObjectToPack otp, WindowCursor curs) throws IOException;
+
+	abstract File getDirectory();
+
+	abstract AlternateHandle[] myAlternates();
+
+	abstract boolean tryAgain1();
+
+	abstract boolean hasObject1(AnyObjectId objectId);
+
+	abstract boolean hasObject2(String objectId);
+
+	abstract ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId)
+			throws IOException;
+
+	abstract ObjectLoader openObject2(WindowCursor curs, String objectName,
+			AnyObjectId objectId) throws IOException;
+
+	abstract long getObjectSize1(WindowCursor curs, AnyObjectId objectId)
+			throws IOException;
+
+	abstract long getObjectSize2(WindowCursor curs, String objectName,
+			AnyObjectId objectId) throws IOException;
+
+	abstract FileObjectDatabase newCachedFileObjectDatabase();
+
+	abstract int getStreamFileThreshold();
+
+	static class AlternateHandle {
+		final FileObjectDatabase db;
+
+		AlternateHandle(FileObjectDatabase db) {
+			this.db = db;
+		}
+
+		void close() {
+			db.close();
+		}
+	}
+
+	static class AlternateRepository extends AlternateHandle {
+		final FileRepository repository;
+
+		AlternateRepository(FileRepository r) {
+			super(r.getObjectDatabase());
+			repository = r;
+		}
+
+		void close() {
+			repository.close();
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java
new file mode 100644
index 0000000..69cce71
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2008-2010, Google Inc.
+ * Copyright (C) 2006-2010, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.BaseRepositoryBuilder;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileObjectDatabase.AlternateHandle;
+import org.eclipse.jgit.storage.file.FileObjectDatabase.AlternateRepository;
+import org.eclipse.jgit.util.SystemReader;
+
+/**
+ * Represents a Git repository. A repository holds all objects and refs used for
+ * managing source code (could by any type of file, but source code is what
+ * SCM's are typically used for).
+ *
+ * In Git terms all data is stored in GIT_DIR, typically a directory called
+ * .git. A work tree is maintained unless the repository is a bare repository.
+ * Typically the .git directory is located at the root of the work dir.
+ *
+ * <ul>
+ * <li>GIT_DIR
+ * 	<ul>
+ * 		<li>objects/ - objects</li>
+ * 		<li>refs/ - tags and heads</li>
+ * 		<li>config - configuration</li>
+ * 		<li>info/ - more configurations</li>
+ * 	</ul>
+ * </li>
+ * </ul>
+ * <p>
+ * This class is thread-safe.
+ * <p>
+ * This implementation only handles a subtly undocumented subset of git features.
+ *
+ */
+public class FileRepository extends Repository {
+	private final FileBasedConfig userConfig;
+
+	private final FileBasedConfig repoConfig;
+
+	private final RefDatabase refs;
+
+	private final ObjectDirectory objectDatabase;
+
+	/**
+	 * Construct a representation of a Git repository.
+	 * <p>
+	 * The work tree, object directory, alternate object directories and index
+	 * file locations are deduced from the given git directory and the default
+	 * rules by running {@link FileRepositoryBuilder}. This constructor is the
+	 * same as saying:
+	 *
+	 * <pre>
+	 * new FileRepositoryBuilder().setGitDir(gitDir).build()
+	 * </pre>
+	 *
+	 * @param gitDir
+	 *            GIT_DIR (the location of the repository metadata).
+	 * @throws IOException
+	 *             the repository appears to already exist but cannot be
+	 *             accessed.
+	 * @see FileRepositoryBuilder
+	 */
+	public FileRepository(final File gitDir) throws IOException {
+		this(new FileRepositoryBuilder().setGitDir(gitDir).setup());
+	}
+
+	/**
+	 * Create a repository using the local file system.
+	 *
+	 * @param options
+	 *            description of the repository's important paths.
+	 * @throws IOException
+	 *             the user configuration file or repository configuration file
+	 *             cannot be accessed.
+	 */
+	public FileRepository(final BaseRepositoryBuilder options) throws IOException {
+		super(options);
+
+		userConfig = SystemReader.getInstance().openUserConfig(getFS());
+		repoConfig = new FileBasedConfig(userConfig, //
+				getFS().resolve(getDirectory(), "config"), //
+				getFS());
+
+		loadUserConfig();
+		loadRepoConfig();
+
+		refs = new RefDirectory(this);
+		objectDatabase = new ObjectDirectory(repoConfig, //
+				options.getObjectDirectory(), //
+				options.getAlternateObjectDirectories(), //
+				getFS());
+		getListenerList().addConfigChangedListener(objectDatabase);
+
+		if (objectDatabase.exists()) {
+			final String repositoryFormatVersion = getConfig().getString(
+					ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION);
+			if (!"0".equals(repositoryFormatVersion)) {
+				throw new IOException(MessageFormat.format(
+						JGitText.get().unknownRepositoryFormat2,
+						repositoryFormatVersion));
+			}
+		}
+	}
+
+	private void loadUserConfig() throws IOException {
+		try {
+			userConfig.load();
+		} catch (ConfigInvalidException e1) {
+			IOException e2 = new IOException(MessageFormat.format(JGitText
+					.get().userConfigFileInvalid, userConfig.getFile()
+					.getAbsolutePath(), e1));
+			e2.initCause(e1);
+			throw e2;
+		}
+	}
+
+	private void loadRepoConfig() throws IOException {
+		try {
+			repoConfig.load();
+		} catch (ConfigInvalidException e1) {
+			IOException e2 = new IOException(JGitText.get().unknownRepositoryFormat);
+			e2.initCause(e1);
+			throw e2;
+		}
+	}
+
+	/**
+	 * Create a new Git repository initializing the necessary files and
+	 * directories.
+	 *
+	 * @param bare
+	 *            if true, a bare repository is created.
+	 *
+	 * @throws IOException
+	 *             in case of IO problem
+	 */
+	public void create(boolean bare) throws IOException {
+		final FileBasedConfig cfg = getConfig();
+		if (cfg.getFile().exists()) {
+			throw new IllegalStateException(MessageFormat.format(
+					JGitText.get().repositoryAlreadyExists, getDirectory()));
+		}
+		getDirectory().mkdirs();
+		refs.create();
+		objectDatabase.create();
+
+		new File(getDirectory(), "branches").mkdir();
+
+		RefUpdate head = updateRef(Constants.HEAD);
+		head.disableRefLog();
+		head.link(Constants.R_HEADS + Constants.MASTER);
+
+		cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
+		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_FILEMODE, true);
+		if (bare)
+			cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_BARE, true);
+		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare);
+		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+		cfg.save();
+	}
+
+	/**
+	 * @return the directory containing the objects owned by this repository.
+	 */
+	public File getObjectsDirectory() {
+		return objectDatabase.getDirectory();
+	}
+
+	/**
+	 * @return the object database which stores this repository's data.
+	 */
+	public ObjectDirectory getObjectDatabase() {
+		return objectDatabase;
+	}
+
+	/** @return the reference database which stores the reference namespace. */
+	public RefDatabase getRefDatabase() {
+		return refs;
+	}
+
+	/**
+	 * @return the configuration of this repository
+	 */
+	public FileBasedConfig getConfig() {
+		if (userConfig.isOutdated()) {
+			try {
+				loadUserConfig();
+			} catch (IOException e) {
+				throw new RuntimeException(e);
+			}
+		}
+		if (repoConfig.isOutdated()) {
+				try {
+					loadRepoConfig();
+				} catch (IOException e) {
+					throw new RuntimeException(e);
+				}
+		}
+		return repoConfig;
+	}
+
+	/**
+	 * Objects known to exist but not expressed by {@link #getAllRefs()}.
+	 * <p>
+	 * When a repository borrows objects from another repository, it can
+	 * advertise that it safely has that other repository's references, without
+	 * exposing any other details about the other repository.  This may help
+	 * a client trying to push changes avoid pushing more than it needs to.
+	 *
+	 * @return unmodifiable collection of other known objects.
+	 */
+	public Set<ObjectId> getAdditionalHaves() {
+		HashSet<ObjectId> r = new HashSet<ObjectId>();
+		for (AlternateHandle d : objectDatabase. myAlternates()) {
+			if (d instanceof AlternateRepository) {
+				Repository repo;
+
+				repo = ((AlternateRepository) d).repository;
+				for (Ref ref : repo.getAllRefs().values())
+					r.add(ref.getObjectId());
+				r.addAll(repo.getAdditionalHaves());
+			}
+		}
+		return r;
+	}
+
+	/**
+	 * Add a single existing pack to the list of available pack files.
+	 *
+	 * @param pack
+	 *            path of the pack file to open.
+	 * @param idx
+	 *            path of the corresponding index file.
+	 * @throws IOException
+	 *             index file could not be opened, read, or is not recognized as
+	 *             a Git pack file index.
+	 */
+	public void openPack(final File pack, final File idx) throws IOException {
+		objectDatabase.openPack(pack, idx);
+	}
+
+	/**
+	 * Force a scan for changed refs.
+	 *
+	 * @throws IOException
+	 */
+	public void scanForRepoChanges() throws IOException {
+		getAllRefs(); // This will look for changes to refs
+		if (!isBare())
+			getIndex(); // This will detect changes in the index
+	}
+
+	/**
+	 * @param refName
+	 * @return a {@link ReflogReader} for the supplied refname, or null if the
+	 *         named ref does not exist.
+	 * @throws IOException the ref could not be accessed.
+	 */
+	public ReflogReader getReflogReader(String refName) throws IOException {
+		Ref ref = getRef(refName);
+		if (ref != null)
+			return new ReflogReader(this, ref.getName());
+		return null;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java
new file mode 100644
index 0000000..31d3e99
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.BaseRepositoryBuilder;
+
+/**
+ * Constructs a {@link FileRepository}.
+ * <p>
+ * Applications must set one of {@link #setGitDir(File)} or
+ * {@link #setWorkTree(File)}, or use {@link #readEnvironment()} or
+ * {@link #findGitDir()} in order to configure the minimum property set
+ * necessary to open a repository.
+ * <p>
+ * Single repository applications trying to be compatible with other Git
+ * implementations are encouraged to use a model such as:
+ *
+ * <pre>
+ * new FileRepositoryBuilder() //
+ * 		.setGitDir(gitDirArgument) // --git-dir if supplied, no-op if null
+ * 		.readEnviroment() // scan environment GIT_* variables
+ * 		.findGitDir() // scan up the file system tree
+ * 		.build()
+ * </pre>
+ */
+public class FileRepositoryBuilder extends
+		BaseRepositoryBuilder<FileRepositoryBuilder, FileRepository> {
+	/**
+	 * Create a repository matching the configuration in this builder.
+	 * <p>
+	 * If an option was not set, the build method will try to default the option
+	 * based on other options. If insufficient information is available, an
+	 * exception is thrown to the caller.
+	 *
+	 * @return a repository matching this configuration.
+	 * @throws IllegalArgumentException
+	 *             insufficient parameters were set.
+	 * @throws IOException
+	 *             the repository could not be accessed to configure the rest of
+	 *             the builder's parameters.
+	 */
+	@Override
+	public FileRepository build() throws IOException {
+		return new FileRepository(setup());
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java
new file mode 100644
index 0000000..53a0e61
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.DataFormatException;
+import java.util.zip.InflaterInputStream;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectStream;
+import org.eclipse.jgit.storage.pack.BinaryDelta;
+import org.eclipse.jgit.storage.pack.DeltaStream;
+
+class LargePackedDeltaObject extends ObjectLoader {
+	private static final long SIZE_UNKNOWN = -1;
+
+	private int type;
+
+	private long size;
+
+	private final long objectOffset;
+
+	private final long baseOffset;
+
+	private final int headerLength;
+
+	private final PackFile pack;
+
+	private final FileObjectDatabase db;
+
+	LargePackedDeltaObject(long objectOffset,
+			long baseOffset, int headerLength, PackFile pack,
+			FileObjectDatabase db) {
+		this.type = Constants.OBJ_BAD;
+		this.size = SIZE_UNKNOWN;
+		this.objectOffset = objectOffset;
+		this.baseOffset = baseOffset;
+		this.headerLength = headerLength;
+		this.pack = pack;
+		this.db = db;
+	}
+
+	@Override
+	public int getType() {
+		if (type == Constants.OBJ_BAD) {
+			WindowCursor wc = new WindowCursor(db);
+			try {
+				type = pack.getObjectType(wc, objectOffset);
+			} catch (IOException packGone) {
+				// If the pack file cannot be pinned into the cursor, it
+				// probably was repacked recently. Go find the object
+				// again and get the type from that location instead.
+				//
+				try {
+					type = wc.open(getObjectId()).getType();
+				} catch (IOException packGone2) {
+					// "He's dead, Jim." We just can't discover the type
+					// and the interface isn't supposed to be lazy here.
+					// Report an invalid type code instead, callers will
+					// wind up bailing out with an error at some point.
+				}
+			} finally {
+				wc.release();
+			}
+		}
+		return type;
+	}
+
+	@Override
+	public long getSize() {
+		if (size == SIZE_UNKNOWN) {
+			WindowCursor wc = new WindowCursor(db);
+			try {
+				byte[] b = pack.getDeltaHeader(wc, objectOffset + headerLength);
+				size = BinaryDelta.getResultSize(b);
+			} catch (DataFormatException objectCorrupt) {
+				// The zlib stream for the delta is corrupt. We probably
+				// cannot access the object. Keep the size negative and
+				// report that bogus result to the caller.
+			} catch (IOException packGone) {
+				// If the pack file cannot be pinned into the cursor, it
+				// probably was repacked recently. Go find the object
+				// again and get the size from that location instead.
+				//
+				try {
+					size = wc.open(getObjectId()).getSize();
+				} catch (IOException packGone2) {
+					// "He's dead, Jim." We just can't discover the size
+					// and the interface isn't supposed to be lazy here.
+					// Report an invalid type code instead, callers will
+					// wind up bailing out with an error at some point.
+				}
+			} finally {
+				wc.release();
+			}
+		}
+		return size;
+	}
+
+	@Override
+	public boolean isLarge() {
+		return true;
+	}
+
+	@Override
+	public byte[] getCachedBytes() throws LargeObjectException {
+		try {
+			throw new LargeObjectException(getObjectId());
+		} catch (IOException cannotObtainId) {
+			throw new LargeObjectException();
+		}
+	}
+
+	@Override
+	public ObjectStream openStream() throws MissingObjectException, IOException {
+		final WindowCursor wc = new WindowCursor(db);
+		InputStream in = open(wc);
+		in = new BufferedInputStream(in, 8192);
+		return new ObjectStream.Filter(getType(), size, in) {
+			@Override
+			public void close() throws IOException {
+				wc.release();
+				super.close();
+			}
+		};
+	}
+
+	private InputStream open(final WindowCursor wc)
+			throws MissingObjectException, IOException,
+			IncorrectObjectTypeException {
+		InputStream delta;
+		try {
+			delta = new PackInputStream(pack, objectOffset + headerLength, wc);
+		} catch (IOException packGone) {
+			// If the pack file cannot be pinned into the cursor, it
+			// probably was repacked recently. Go find the object
+			// again and open the stream from that location instead.
+			//
+			return wc.open(getObjectId()).openStream();
+		}
+		delta = new InflaterInputStream(delta);
+
+		final ObjectLoader base = pack.load(wc, baseOffset);
+		DeltaStream ds = new DeltaStream(delta) {
+			private long baseSize = SIZE_UNKNOWN;
+
+			@Override
+			protected InputStream openBase() throws IOException {
+				InputStream in;
+				if (base instanceof LargePackedDeltaObject)
+					in = ((LargePackedDeltaObject) base).open(wc);
+				else
+					in = base.openStream();
+				if (baseSize == SIZE_UNKNOWN) {
+					if (in instanceof DeltaStream)
+						baseSize = ((DeltaStream) in).getSize();
+					else if (in instanceof ObjectStream)
+						baseSize = ((ObjectStream) in).getSize();
+				}
+				return in;
+			}
+
+			@Override
+			protected long getBaseSize() throws IOException {
+				if (baseSize == SIZE_UNKNOWN) {
+					// This code path should never be used as DeltaStream
+					// is supposed to open the stream first, which would
+					// initialize the size for us directly from the stream.
+					baseSize = base.getSize();
+				}
+				return baseSize;
+			}
+		};
+		if (size == SIZE_UNKNOWN)
+			size = ds.getSize();
+		return ds;
+	}
+
+	private ObjectId getObjectId() throws IOException {
+		return pack.findObjectForOffset(objectOffset);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedWholeObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedWholeObject.java
new file mode 100644
index 0000000..9f5b804
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedWholeObject.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.InflaterInputStream;
+
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectStream;
+
+class LargePackedWholeObject extends ObjectLoader {
+	private final int type;
+
+	private final long size;
+
+	private final long objectOffset;
+
+	private final int headerLength;
+
+	private final PackFile pack;
+
+	private final FileObjectDatabase db;
+
+	LargePackedWholeObject(int type, long size, long objectOffset,
+			int headerLength, PackFile pack, FileObjectDatabase db) {
+		this.type = type;
+		this.size = size;
+		this.objectOffset = objectOffset;
+		this.headerLength = headerLength;
+		this.pack = pack;
+		this.db = db;
+	}
+
+	@Override
+	public int getType() {
+		return type;
+	}
+
+	@Override
+	public long getSize() {
+		return size;
+	}
+
+	@Override
+	public boolean isLarge() {
+		return true;
+	}
+
+	@Override
+	public byte[] getCachedBytes() throws LargeObjectException {
+		try {
+			throw new LargeObjectException(getObjectId());
+		} catch (IOException cannotObtainId) {
+			throw new LargeObjectException();
+		}
+	}
+
+	@Override
+	public ObjectStream openStream() throws MissingObjectException, IOException {
+		WindowCursor wc = new WindowCursor(db);
+		InputStream in;
+		try {
+			in = new PackInputStream(pack, objectOffset + headerLength, wc);
+		} catch (IOException packGone) {
+			// If the pack file cannot be pinned into the cursor, it
+			// probably was repacked recently. Go find the object
+			// again and open the stream from that location instead.
+			//
+			return wc.open(getObjectId(), type).openStream();
+		}
+
+		in = new BufferedInputStream( //
+				new InflaterInputStream( //
+						in, //
+						wc.inflater(), //
+						8192), //
+				8192);
+		return new ObjectStream.Filter(type, size, in);
+	}
+
+	private ObjectId getObjectId() throws IOException {
+		return pack.findObjectForOffset(objectOffset);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java
new file mode 100644
index 0000000..08bb8e6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.storage.pack.StoredObjectRepresentation;
+
+class LocalObjectRepresentation extends StoredObjectRepresentation {
+	static LocalObjectRepresentation newWhole(PackFile f, long p, long length) {
+		LocalObjectRepresentation r = new LocalObjectRepresentation() {
+			@Override
+			public int getFormat() {
+				return PACK_WHOLE;
+			}
+		};
+		r.pack = f;
+		r.offset = p;
+		r.length = length;
+		return r;
+	}
+
+	static LocalObjectRepresentation newDelta(PackFile f, long p, long n,
+			ObjectId base) {
+		LocalObjectRepresentation r = new Delta();
+		r.pack = f;
+		r.offset = p;
+		r.length = n;
+		r.baseId = base;
+		return r;
+	}
+
+	static LocalObjectRepresentation newDelta(PackFile f, long p, long n,
+			long base) {
+		LocalObjectRepresentation r = new Delta();
+		r.pack = f;
+		r.offset = p;
+		r.length = n;
+		r.baseOffset = base;
+		return r;
+	}
+
+	PackFile pack;
+
+	long offset;
+
+	long length;
+
+	private long baseOffset;
+
+	private ObjectId baseId;
+
+	@Override
+	public int getWeight() {
+		return (int) Math.min(length, Integer.MAX_VALUE);
+	}
+
+	@Override
+	public ObjectId getDeltaBase() {
+		if (baseId == null && getFormat() == PACK_DELTA) {
+			try {
+				baseId = pack.findObjectForOffset(baseOffset);
+			} catch (IOException error) {
+				return null;
+			}
+		}
+		return baseId;
+	}
+
+	private static final class Delta extends LocalObjectRepresentation {
+		@Override
+		public int getFormat() {
+			return PACK_DELTA;
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java
similarity index 68%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java
index 495049c..c7ef2c9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,30 +41,38 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
-/**
- * This class passes information about changed refs to a
- * {@link RepositoryListener}
- *
- * Currently only a reference to the repository is passed.
- */
-public class RepositoryChangedEvent {
-	private final Repository repository;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.storage.pack.ObjectToPack;
+import org.eclipse.jgit.storage.pack.StoredObjectRepresentation;
 
-	RepositoryChangedEvent(final Repository repository) {
-		this.repository = repository;
-	}
+/** {@link ObjectToPack} for {@link ObjectDirectory}. */
+class LocalObjectToPack extends ObjectToPack {
+	/** Pack to reuse compressed data from, otherwise null. */
+	PackFile pack;
 
-	/**
-	 * @return the repository that was changed
-	 */
-	public Repository getRepository() {
-		return repository;
+	/** Offset of the object's header in {@link #pack}. */
+	long offset;
+
+	/** Length of the data section of the object. */
+	long length;
+
+	LocalObjectToPack(RevObject obj) {
+		super(obj);
 	}
 
 	@Override
-	public String toString() {
-		return "RepositoryChangedEvent[" + repository + "]";
+	protected void clearReuseAsIs() {
+		super.clearReuseAsIs();
+		pack = null;
+	}
+
+	@Override
+	public void select(StoredObjectRepresentation ref) {
+		LocalObjectRepresentation ptr = (LocalObjectRepresentation) ref;
+		this.pack = ptr.pack;
+		this.offset = ptr.offset;
+		this.length = ptr.length;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java
similarity index 93%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java
index 13f158d..e8bc3e2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java
@@ -42,7 +42,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
@@ -57,6 +57,9 @@
 import java.text.MessageFormat;
 
 import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.FS;
 
 /**
  * Git style file locking and replacement.
@@ -92,15 +95,21 @@ public boolean accept(File dir, String name) {
 
 	private long commitLastModified;
 
+	private final FS fs;
+
 	/**
 	 * Create a new lock for any file.
 	 *
 	 * @param f
 	 *            the file that will be locked.
+	 * @param fs
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
 	 */
-	public LockFile(final File f) {
+	public LockFile(final File f, FS fs) {
 		ref = f;
 		lck = new File(ref.getParentFile(), ref.getName() + SUFFIX);
+		this.fs = fs;
 	}
 
 	/**
@@ -391,13 +400,32 @@ public boolean commit() {
 		saveStatInformation();
 		if (lck.renameTo(ref))
 			return true;
-		if (!ref.exists() || ref.delete())
+		if (!ref.exists() || deleteRef())
 			if (lck.renameTo(ref))
 				return true;
 		unlock();
 		return false;
 	}
 
+	private boolean deleteRef() {
+		if (!fs.retryFailedLockFileCommit())
+			return ref.delete();
+
+		// File deletion fails on windows if another thread is
+		// concurrently reading the same file. So try a few times.
+		//
+		for (int attempts = 0; attempts < 10; attempts++) {
+			if (ref.delete())
+				return true;
+			try {
+				Thread.sleep(100);
+			} catch (InterruptedException e) {
+				return false;
+			}
+		}
+		return false;
+	}
+
 	private void saveStatInformation() {
 		if (needStatInformation)
 			commitLastModified = lck.lastModified();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java
similarity index 73%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java
index 9a5bcdb..6fe4fd7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java
@@ -41,10 +41,11 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
@@ -62,7 +63,19 @@
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.errors.PackMismatchException;
+import org.eclipse.jgit.events.ConfigChangedEvent;
+import org.eclipse.jgit.events.ConfigChangedListener;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.ObjectDatabase;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.RepositoryCache;
 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.storage.pack.ObjectToPack;
+import org.eclipse.jgit.storage.pack.PackWriter;
 import org.eclipse.jgit.util.FS;
 
 /**
@@ -72,10 +85,23 @@
  * where objects are stored loose by hashing them into directories by their
  * {@link ObjectId}, or are stored in compressed containers known as
  * {@link PackFile}s.
+ * <p>
+ * Optionally an object database can reference one or more alternates; other
+ * ObjectDatabase instances that are searched in addition to the current
+ * database.
+ * <p>
+ * Databases are divided into two halves: a half that is considered to be fast
+ * to search (the {@code PackFile}s), and a half that is considered to be slow
+ * to search (loose objects). When alternates are present the fast half is fully
+ * searched (recursively through all alternates) before the slow half is
+ * considered.
  */
-public class ObjectDirectory extends ObjectDatabase {
+public class ObjectDirectory extends FileObjectDatabase implements
+		ConfigChangedListener {
 	private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]);
 
+	private final Config config;
+
 	private final File objects;
 
 	private final File infoDirectory;
@@ -86,29 +112,53 @@ public class ObjectDirectory extends ObjectDatabase {
 
 	private final AtomicReference<PackList> packList;
 
-	private final File[] alternateObjectDir;
-
 	private final FS fs;
 
+	private final AtomicReference<AlternateHandle[]> alternates;
+
+	private int streamFileThreshold;
+
 	/**
 	 * Initialize a reference to an on-disk object directory.
 	 *
+	 * @param cfg
+	 *            configuration this directory consults for write settings.
 	 * @param dir
 	 *            the location of the <code>objects</code> directory.
-	 * @param alternateObjectDir
+	 * @param alternatePaths
 	 *            a list of alternate object directories
 	 * @param fs
-	 *            the file system abstraction which will be necessary to
-	 *            perform certain file system operations.
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
+	 * @throws IOException
+	 *             an alternate object cannot be opened.
 	 */
-	public ObjectDirectory(final File dir, File[] alternateObjectDir, FS fs) {
+	public ObjectDirectory(final Config cfg, final File dir,
+			File[] alternatePaths, FS fs) throws IOException {
+		config = cfg;
 		objects = dir;
-		this.alternateObjectDir = alternateObjectDir;
 		infoDirectory = new File(objects, "info");
 		packDirectory = new File(objects, "pack");
 		alternatesFile = new File(infoDirectory, "alternates");
 		packList = new AtomicReference<PackList>(NO_PACKS);
 		this.fs = fs;
+
+		alternates = new AtomicReference<AlternateHandle[]>();
+		if (alternatePaths != null) {
+			AlternateHandle[] alt;
+
+			alt = new AlternateHandle[alternatePaths.length];
+			for (int i = 0; i < alternatePaths.length; i++)
+				alt[i] = openAlternate(alternatePaths[i]);
+			alternates.set(alt);
+		}
+
+		onConfigChanged(new ConfigChangedEvent());
+	}
+
+	public void onConfigChanged(ConfigChangedEvent event) {
+		CoreConfig core = config.get(CoreConfig.KEY);
+		streamFileThreshold = core.getStreamFileThreshold();
 	}
 
 	/**
@@ -131,11 +181,24 @@ public void create() throws IOException {
 	}
 
 	@Override
-	public void closeSelf() {
+	public ObjectInserter newInserter() {
+		return new ObjectDirectoryInserter(this, config);
+	}
+
+	@Override
+	public void close() {
 		final PackList packs = packList.get();
 		packList.set(NO_PACKS);
 		for (final PackFile p : packs.packs)
 			p.close();
+
+		// Fully close all loaded alternates and clear the alternate list.
+		AlternateHandle[] alt = alternates.get();
+		if (alt != null) {
+			alternates.set(null);
+			for(final AlternateHandle od : alt)
+				od.close();
+		}
 	}
 
 	/**
@@ -199,8 +262,7 @@ public String toString() {
 		return "ObjectDirectory[" + getDirectory() + "]";
 	}
 
-	@Override
-	protected boolean hasObject1(final AnyObjectId objectId) {
+	boolean hasObject1(final AnyObjectId objectId) {
 		for (final PackFile p : packList.get().packs) {
 			try {
 				if (p.hasObject(objectId)) {
@@ -218,18 +280,15 @@ protected boolean hasObject1(final AnyObjectId objectId) {
 		return false;
 	}
 
-	@Override
-	protected ObjectLoader openObject1(final WindowCursor curs,
+	ObjectLoader openObject1(final WindowCursor curs,
 			final AnyObjectId objectId) throws IOException {
 		PackList pList = packList.get();
 		SEARCH: for (;;) {
 			for (final PackFile p : pList.packs) {
 				try {
-					final PackedObjectLoader ldr = p.get(curs, objectId);
-					if (ldr != null) {
-						ldr.materialize(curs);
+					final ObjectLoader ldr = p.get(curs, objectId);
+					if (ldr != null)
 						return ldr;
-					}
 				} catch (PackMismatchException e) {
 					// Pack was modified; refresh the entire pack list.
 					//
@@ -245,18 +304,56 @@ protected ObjectLoader openObject1(final WindowCursor curs,
 		}
 	}
 
-	@Override
-	void openObjectInAllPacks1(final Collection<PackedObjectLoader> out,
-			final WindowCursor curs, final AnyObjectId objectId)
+	long getObjectSize1(final WindowCursor curs, final AnyObjectId objectId)
 			throws IOException {
 		PackList pList = packList.get();
 		SEARCH: for (;;) {
 			for (final PackFile p : pList.packs) {
 				try {
-					final PackedObjectLoader ldr = p.get(curs, objectId);
-					if (ldr != null) {
-						out.add(ldr);
-					}
+					long sz = p.getObjectSize(curs, objectId);
+					if (0 <= sz)
+						return sz;
+				} catch (PackMismatchException e) {
+					// Pack was modified; refresh the entire pack list.
+					//
+					pList = scanPacks(pList);
+					continue SEARCH;
+				} catch (IOException e) {
+					// Assume the pack is corrupted.
+					//
+					removePack(p);
+				}
+			}
+			return -1;
+		}
+	}
+
+	@Override
+	long getObjectSize2(WindowCursor curs, String objectName,
+			AnyObjectId objectId) throws IOException {
+		try {
+			File path = fileFor(objectName);
+			FileInputStream in = new FileInputStream(path);
+			try {
+				return UnpackedObject.getSize(in, objectId, curs);
+			} finally {
+				in.close();
+			}
+		} catch (FileNotFoundException noFile) {
+			return -1;
+		}
+	}
+
+	@Override
+	void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
+			WindowCursor curs) throws IOException {
+		PackList pList = packList.get();
+		SEARCH: for (;;) {
+			for (final PackFile p : pList.packs) {
+				try {
+					LocalObjectRepresentation rep = p.representation(curs, otp);
+					if (rep != null)
+						packer.select(otp, rep);
 				} catch (PackMismatchException e) {
 					// Pack was modified; refresh the entire pack list.
 					//
@@ -270,26 +367,32 @@ void openObjectInAllPacks1(final Collection<PackedObjectLoader> out,
 			}
 			break SEARCH;
 		}
+
+		for (AlternateHandle h : myAlternates())
+			h.db.selectObjectRepresentation(packer, otp, curs);
 	}
 
-	@Override
-	protected boolean hasObject2(final String objectName) {
+	boolean hasObject2(final String objectName) {
 		return fileFor(objectName).exists();
 	}
 
-	@Override
-	protected ObjectLoader openObject2(final WindowCursor curs,
+	ObjectLoader openObject2(final WindowCursor curs,
 			final String objectName, final AnyObjectId objectId)
 			throws IOException {
 		try {
-			return new UnpackedObjectLoader(fileFor(objectName), objectId);
+			File path = fileFor(objectName);
+			FileInputStream in = new FileInputStream(path);
+			try {
+				return UnpackedObject.open(in, path, objectId, curs);
+			} finally {
+				in.close();
+			}
 		} catch (FileNotFoundException noFile) {
 			return null;
 		}
 	}
 
-	@Override
-	protected boolean tryAgain1() {
+	boolean tryAgain1() {
 		final PackList old = packList.get();
 		if (old.tryAgain(packDirectory.lastModified()))
 			return old != scanPacks(old);
@@ -459,29 +562,36 @@ private Set<String> listPackDirectory() {
 		return nameSet;
 	}
 
-	@Override
-	protected ObjectDatabase[] loadAlternates() throws IOException {
-		final List<ObjectDatabase> l = new ArrayList<ObjectDatabase>(4);
-		if (alternateObjectDir != null) {
-			for (File d : alternateObjectDir) {
-				l.add(openAlternate(d));
-			}
-		} else {
-			final BufferedReader br = open(alternatesFile);
-			try {
-				String line;
-				while ((line = br.readLine()) != null) {
-					l.add(openAlternate(line));
+	AlternateHandle[] myAlternates() {
+		AlternateHandle[] alt = alternates.get();
+		if (alt == null) {
+			synchronized (alternates) {
+				alt = alternates.get();
+				if (alt == null) {
+					try {
+						alt = loadAlternates();
+					} catch (IOException e) {
+						alt = new AlternateHandle[0];
+					}
+					alternates.set(alt);
 				}
-			} finally {
-				br.close();
 			}
 		}
+		return alt;
+	}
 
-		if (l.isEmpty()) {
-			return NO_ALTERNATES;
+	private AlternateHandle[] loadAlternates() throws IOException {
+		final List<AlternateHandle> l = new ArrayList<AlternateHandle>(4);
+		final BufferedReader br = open(alternatesFile);
+		try {
+			String line;
+			while ((line = br.readLine()) != null) {
+				l.add(openAlternate(line));
+			}
+		} finally {
+			br.close();
 		}
-		return l.toArray(new ObjectDatabase[l.size()]);
+		return l.toArray(new AlternateHandle[l.size()]);
 	}
 
 	private static BufferedReader open(final File f)
@@ -489,19 +599,22 @@ private static BufferedReader open(final File f)
 		return new BufferedReader(new FileReader(f));
 	}
 
-	private ObjectDatabase openAlternate(final String location)
+	private AlternateHandle openAlternate(final String location)
 			throws IOException {
 		final File objdir = fs.resolve(objects, location);
 		return openAlternate(objdir);
 	}
 
-	private ObjectDatabase openAlternate(File objdir) throws IOException {
+	private AlternateHandle openAlternate(File objdir) throws IOException {
 		final File parent = objdir.getParentFile();
 		if (FileKey.isGitRepository(parent, fs)) {
-			final Repository db = RepositoryCache.open(FileKey.exact(parent, fs));
-			return new AlternateRepositoryDatabase(db);
+			FileKey key = FileKey.exact(parent, fs);
+			FileRepository db = (FileRepository) RepositoryCache.open(key);
+			return new AlternateRepository(db);
 		}
-		return new ObjectDirectory(objdir, null, fs);
+
+		ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs);
+		return new AlternateHandle(db);
 	}
 
 	private static final class PackList {
@@ -566,6 +679,15 @@ boolean tryAgain(final long currLastModified) {
 
 	@Override
 	public ObjectDatabase newCachedDatabase() {
+		return newCachedFileObjectDatabase();
+	}
+
+	FileObjectDatabase newCachedFileObjectDatabase() {
 		return new CachedObjectDirectory(this);
 	}
+
+	@Override
+	int getStreamFileThreshold() {
+		return streamFileThreshold;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java
new file mode 100644
index 0000000..5016679
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import org.eclipse.jgit.errors.ObjectWritingException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+
+/** Creates loose objects in a {@link ObjectDirectory}. */
+class ObjectDirectoryInserter extends ObjectInserter {
+	private final ObjectDirectory db;
+
+	private final Config config;
+
+	private Deflater deflate;
+
+	ObjectDirectoryInserter(final ObjectDirectory dest, final Config cfg) {
+		db = dest;
+		config = cfg;
+	}
+
+	@Override
+	public ObjectId insert(final int type, long len, final InputStream is)
+			throws IOException {
+		final MessageDigest md = digest();
+		final File tmp = toTemp(md, type, len, is);
+		final ObjectId id = ObjectId.fromRaw(md.digest());
+		if (db.has(id)) {
+			// Object is already in the repository, remove temporary file.
+			//
+			tmp.delete();
+			return id;
+		}
+
+		final File dst = db.fileFor(id);
+		if (tmp.renameTo(dst))
+			return id;
+
+		// Maybe the directory doesn't exist yet as the object
+		// directories are always lazily created. Note that we
+		// try the rename first as the directory likely does exist.
+		//
+		dst.getParentFile().mkdir();
+		if (tmp.renameTo(dst))
+			return id;
+
+		if (db.has(id)) {
+			tmp.delete();
+			return id;
+		}
+
+		// The object failed to be renamed into its proper
+		// location and it doesn't exist in the repository
+		// either. We really don't know what went wrong, so
+		// fail.
+		//
+		tmp.delete();
+		throw new ObjectWritingException("Unable to create new object: " + dst);
+	}
+
+	@Override
+	public void flush() throws IOException {
+		// Do nothing. Objects are immediately visible.
+	}
+
+	@Override
+	public void release() {
+		if (deflate != null) {
+			try {
+				deflate.end();
+			} finally {
+				deflate = null;
+			}
+		}
+	}
+
+	private File toTemp(final MessageDigest md, final int type, long len,
+			final InputStream is) throws IOException, FileNotFoundException,
+			Error {
+		boolean delete = true;
+		File tmp = File.createTempFile("noz", null, db.getDirectory());
+		try {
+			DigestOutputStream dOut = new DigestOutputStream(
+					compress(new FileOutputStream(tmp)), md);
+			try {
+				dOut.write(Constants.encodedTypeString(type));
+				dOut.write((byte) ' ');
+				dOut.write(Constants.encodeASCII(len));
+				dOut.write((byte) 0);
+
+				final byte[] buf = buffer();
+				while (len > 0) {
+					int n = is.read(buf, 0, (int) Math.min(len, buf.length));
+					if (n <= 0)
+						throw shortInput(len);
+					dOut.write(buf, 0, n);
+					len -= n;
+				}
+			} finally {
+				dOut.close();
+			}
+
+			tmp.setReadOnly();
+			delete = false;
+			return tmp;
+		} finally {
+			if (delete)
+				tmp.delete();
+		}
+	}
+
+	private DeflaterOutputStream compress(final OutputStream out) {
+		if (deflate == null)
+			deflate = new Deflater(config.get(CoreConfig.KEY).getCompression());
+		else
+			deflate.reset();
+		return new DeflaterOutputStream(out, deflate);
+	}
+
+	private static EOFException shortInput(long missing) {
+		return new EOFException("Input did not match supplied length. "
+				+ missing + " bytes are missing.");
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java
new file mode 100644
index 0000000..e74a7c0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java
@@ -0,0 +1,920 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel.MapMode;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.zip.CRC32;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.PackInvalidException;
+import org.eclipse.jgit.errors.PackMismatchException;
+import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.storage.pack.BinaryDelta;
+import org.eclipse.jgit.storage.pack.ObjectToPack;
+import org.eclipse.jgit.storage.pack.PackOutputStream;
+import org.eclipse.jgit.util.LongList;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * A Git version 2 pack file representation. A pack file contains Git objects in
+ * delta packed format yielding high compression of lots of object where some
+ * objects are similar.
+ */
+public class PackFile implements Iterable<PackIndex.MutableEntry> {
+	/** Sorts PackFiles to be most recently created to least recently created. */
+	public static Comparator<PackFile> SORT = new Comparator<PackFile>() {
+		public int compare(final PackFile a, final PackFile b) {
+			return b.packLastModified - a.packLastModified;
+		}
+	};
+
+	private final File idxFile;
+
+	private final File packFile;
+
+	final int hash;
+
+	private RandomAccessFile fd;
+
+	/** Serializes reads performed against {@link #fd}. */
+	private final Object readLock = new Object();
+
+	long length;
+
+	private int activeWindows;
+
+	private int activeCopyRawData;
+
+	private int packLastModified;
+
+	private volatile boolean invalid;
+
+	private byte[] packChecksum;
+
+	private PackIndex loadedIdx;
+
+	private PackReverseIndex reverseIdx;
+
+	/**
+	 * Objects we have tried to read, and discovered to be corrupt.
+	 * <p>
+	 * The list is allocated after the first corruption is found, and filled in
+	 * as more entries are discovered. Typically this list is never used, as
+	 * pack files do not usually contain corrupt objects.
+	 */
+	private volatile LongList corruptObjects;
+
+	/**
+	 * Construct a reader for an existing, pre-indexed packfile.
+	 *
+	 * @param idxFile
+	 *            path of the <code>.idx</code> file listing the contents.
+	 * @param packFile
+	 *            path of the <code>.pack</code> file holding the data.
+	 */
+	public PackFile(final File idxFile, final File packFile) {
+		this.idxFile = idxFile;
+		this.packFile = packFile;
+		this.packLastModified = (int) (packFile.lastModified() >> 10);
+
+		// Multiply by 31 here so we can more directly combine with another
+		// value in WindowCache.hash(), without doing the multiply there.
+		//
+		hash = System.identityHashCode(this) * 31;
+		length = Long.MAX_VALUE;
+	}
+
+	private synchronized PackIndex idx() throws IOException {
+		if (loadedIdx == null) {
+			if (invalid)
+				throw new PackInvalidException(packFile);
+
+			try {
+				final PackIndex idx = PackIndex.open(idxFile);
+
+				if (packChecksum == null)
+					packChecksum = idx.packChecksum;
+				else if (!Arrays.equals(packChecksum, idx.packChecksum))
+					throw new PackMismatchException(JGitText.get().packChecksumMismatch);
+
+				loadedIdx = idx;
+			} catch (IOException e) {
+				invalid = true;
+				throw e;
+			}
+		}
+		return loadedIdx;
+	}
+
+	/** @return the File object which locates this pack on disk. */
+	public File getPackFile() {
+		return packFile;
+	}
+
+	/**
+	 * Determine if an object is contained within the pack file.
+	 * <p>
+	 * For performance reasons only the index file is searched; the main pack
+	 * content is ignored entirely.
+	 * </p>
+	 *
+	 * @param id
+	 *            the object to look for. Must not be null.
+	 * @return true if the object is in this pack; false otherwise.
+	 * @throws IOException
+	 *             the index file cannot be loaded into memory.
+	 */
+	public boolean hasObject(final AnyObjectId id) throws IOException {
+		final long offset = idx().findOffset(id);
+		return 0 < offset && !isCorrupt(offset);
+	}
+
+	/**
+	 * Get an object from this pack.
+	 *
+	 * @param curs
+	 *            temporary working space associated with the calling thread.
+	 * @param id
+	 *            the object to obtain from the pack. Must not be null.
+	 * @return the object loader for the requested object if it is contained in
+	 *         this pack; null if the object was not found.
+	 * @throws IOException
+	 *             the pack file or the index could not be read.
+	 */
+	ObjectLoader get(final WindowCursor curs, final AnyObjectId id)
+			throws IOException {
+		final long offset = idx().findOffset(id);
+		return 0 < offset && !isCorrupt(offset) ? load(curs, offset) : null;
+	}
+
+	/**
+	 * Close the resources utilized by this repository
+	 */
+	public void close() {
+		UnpackedObjectCache.purge(this);
+		WindowCache.purge(this);
+		synchronized (this) {
+			loadedIdx = null;
+			reverseIdx = null;
+		}
+	}
+
+	/**
+	 * Provide iterator over entries in associated pack index, that should also
+	 * exist in this pack file. Objects returned by such iterator are mutable
+	 * during iteration.
+	 * <p>
+	 * Iterator returns objects in SHA-1 lexicographical order.
+	 * </p>
+	 *
+	 * @return iterator over entries of associated pack index
+	 *
+	 * @see PackIndex#iterator()
+	 */
+	public Iterator<PackIndex.MutableEntry> iterator() {
+		try {
+			return idx().iterator();
+		} catch (IOException e) {
+			return Collections.<PackIndex.MutableEntry> emptyList().iterator();
+		}
+	}
+
+	/**
+	 * Obtain the total number of objects available in this pack. This method
+	 * relies on pack index, giving number of effectively available objects.
+	 *
+	 * @return number of objects in index of this pack, likewise in this pack
+	 * @throws IOException
+	 *             the index file cannot be loaded into memory.
+	 */
+	long getObjectCount() throws IOException {
+		return idx().getObjectCount();
+	}
+
+	/**
+	 * Search for object id with the specified start offset in associated pack
+	 * (reverse) index.
+	 *
+	 * @param offset
+	 *            start offset of object to find
+	 * @return object id for this offset, or null if no object was found
+	 * @throws IOException
+	 *             the index file cannot be loaded into memory.
+	 */
+	ObjectId findObjectForOffset(final long offset) throws IOException {
+		return getReverseIdx().findObject(offset);
+	}
+
+	private final UnpackedObjectCache.Entry readCache(final long position) {
+		return UnpackedObjectCache.get(this, position);
+	}
+
+	private final void saveCache(final long position, final byte[] data, final int type) {
+		UnpackedObjectCache.store(this, position, data, type);
+	}
+
+	private final byte[] decompress(final long position, final long totalSize,
+			final WindowCursor curs) throws IOException, DataFormatException {
+		final byte[] dstbuf = new byte[(int) totalSize];
+		if (curs.inflate(this, position, dstbuf, 0) != totalSize)
+			throw new EOFException(MessageFormat.format(JGitText.get().shortCompressedStreamAt, position));
+		return dstbuf;
+	}
+
+	final void copyAsIs(PackOutputStream out, LocalObjectToPack src,
+			WindowCursor curs) throws IOException,
+			StoredObjectRepresentationNotAvailableException {
+		beginCopyAsIs(src);
+		try {
+			copyAsIs2(out, src, curs);
+		} finally {
+			endCopyAsIs();
+		}
+	}
+
+	private void copyAsIs2(PackOutputStream out, LocalObjectToPack src,
+			WindowCursor curs) throws IOException,
+			StoredObjectRepresentationNotAvailableException {
+		final CRC32 crc1 = new CRC32();
+		final CRC32 crc2 = new CRC32();
+		final byte[] buf = out.getCopyBuffer();
+
+		// Rip apart the header so we can discover the size.
+		//
+		readFully(src.offset, buf, 0, 20, curs);
+		int c = buf[0] & 0xff;
+		final int typeCode = (c >> 4) & 7;
+		long inflatedLength = c & 15;
+		int shift = 4;
+		int headerCnt = 1;
+		while ((c & 0x80) != 0) {
+			c = buf[headerCnt++] & 0xff;
+			inflatedLength += (c & 0x7f) << shift;
+			shift += 7;
+		}
+
+		if (typeCode == Constants.OBJ_OFS_DELTA) {
+			do {
+				c = buf[headerCnt++] & 0xff;
+			} while ((c & 128) != 0);
+			crc1.update(buf, 0, headerCnt);
+			crc2.update(buf, 0, headerCnt);
+		} else if (typeCode == Constants.OBJ_REF_DELTA) {
+			crc1.update(buf, 0, headerCnt);
+			crc2.update(buf, 0, headerCnt);
+
+			readFully(src.offset + headerCnt, buf, 0, 20, curs);
+			crc1.update(buf, 0, 20);
+			crc2.update(buf, 0, headerCnt);
+			headerCnt += 20;
+		} else {
+			crc1.update(buf, 0, headerCnt);
+			crc2.update(buf, 0, headerCnt);
+		}
+
+		final long dataOffset = src.offset + headerCnt;
+		final long dataLength = src.length;
+		final long expectedCRC;
+		final ByteArrayWindow quickCopy;
+
+		// Verify the object isn't corrupt before sending. If it is,
+		// we report it missing instead.
+		//
+		try {
+			quickCopy = curs.quickCopy(this, dataOffset, dataLength);
+
+			if (idx().hasCRC32Support()) {
+				// Index has the CRC32 code cached, validate the object.
+				//
+				expectedCRC = idx().findCRC32(src);
+				if (quickCopy != null) {
+					quickCopy.crc32(crc1, dataOffset, (int) dataLength);
+				} else {
+					long pos = dataOffset;
+					long cnt = dataLength;
+					while (cnt > 0) {
+						final int n = (int) Math.min(cnt, buf.length);
+						readFully(pos, buf, 0, n, curs);
+						crc1.update(buf, 0, n);
+						pos += n;
+						cnt -= n;
+					}
+				}
+				if (crc1.getValue() != expectedCRC) {
+					setCorrupt(src.offset);
+					throw new CorruptObjectException(MessageFormat.format(
+							JGitText.get().objectAtHasBadZlibStream,
+							src.offset, getPackFile()));
+				}
+			} else {
+				// We don't have a CRC32 code in the index, so compute it
+				// now while inflating the raw data to get zlib to tell us
+				// whether or not the data is safe.
+				//
+				Inflater inf = curs.inflater();
+				byte[] tmp = new byte[1024];
+				if (quickCopy != null) {
+					quickCopy.check(inf, tmp, dataOffset, (int) dataLength);
+				} else {
+					long pos = dataOffset;
+					long cnt = dataLength;
+					while (cnt > 0) {
+						final int n = (int) Math.min(cnt, buf.length);
+						readFully(pos, buf, 0, n, curs);
+						crc1.update(buf, 0, n);
+						inf.setInput(buf, 0, n);
+						while (inf.inflate(tmp, 0, tmp.length) > 0)
+							continue;
+						pos += n;
+						cnt -= n;
+					}
+				}
+				if (!inf.finished() || inf.getBytesRead() != dataLength) {
+					setCorrupt(src.offset);
+					throw new EOFException(MessageFormat.format(
+							JGitText.get().shortCompressedStreamAt,
+							src.offset));
+				}
+				expectedCRC = crc1.getValue();
+			}
+		} catch (DataFormatException dataFormat) {
+			setCorrupt(src.offset);
+
+			CorruptObjectException corruptObject = new CorruptObjectException(
+					MessageFormat.format(
+							JGitText.get().objectAtHasBadZlibStream,
+							src.offset, getPackFile()));
+			corruptObject.initCause(dataFormat);
+
+			StoredObjectRepresentationNotAvailableException gone;
+			gone = new StoredObjectRepresentationNotAvailableException(src);
+			gone.initCause(corruptObject);
+			throw gone;
+
+		} catch (IOException ioError) {
+			StoredObjectRepresentationNotAvailableException gone;
+			gone = new StoredObjectRepresentationNotAvailableException(src);
+			gone.initCause(ioError);
+			throw gone;
+		}
+
+		if (quickCopy != null) {
+			// The entire object fits into a single byte array window slice,
+			// and we have it pinned.  Write this out without copying.
+			//
+			out.writeHeader(src, inflatedLength);
+			quickCopy.write(out, dataOffset, (int) dataLength);
+
+		} else if (dataLength <= buf.length) {
+			// Tiny optimization: Lots of objects are very small deltas or
+			// deflated commits that are likely to fit in the copy buffer.
+			//
+			out.writeHeader(src, inflatedLength);
+			out.write(buf, 0, (int) dataLength);
+		} else {
+			// Now we are committed to sending the object. As we spool it out,
+			// check its CRC32 code to make sure there wasn't corruption between
+			// the verification we did above, and us actually outputting it.
+			//
+			out.writeHeader(src, inflatedLength);
+			long pos = dataOffset;
+			long cnt = dataLength;
+			while (cnt > 0) {
+				final int n = (int) Math.min(cnt, buf.length);
+				readFully(pos, buf, 0, n, curs);
+				crc2.update(buf, 0, n);
+				out.write(buf, 0, n);
+				pos += n;
+				cnt -= n;
+			}
+			if (crc2.getValue() != expectedCRC) {
+				throw new CorruptObjectException(MessageFormat.format(JGitText
+						.get().objectAtHasBadZlibStream, src.offset,
+						getPackFile()));
+			}
+		}
+	}
+
+	boolean invalid() {
+		return invalid;
+	}
+
+	private void readFully(final long position, final byte[] dstbuf,
+			int dstoff, final int cnt, final WindowCursor curs)
+			throws IOException {
+		if (curs.copy(this, position, dstbuf, dstoff, cnt) != cnt)
+			throw new EOFException();
+	}
+
+	private synchronized void beginCopyAsIs(ObjectToPack otp)
+			throws StoredObjectRepresentationNotAvailableException {
+		if (++activeCopyRawData == 1 && activeWindows == 0) {
+			try {
+				doOpen();
+			} catch (IOException thisPackNotValid) {
+				StoredObjectRepresentationNotAvailableException gone;
+
+				gone = new StoredObjectRepresentationNotAvailableException(otp);
+				gone.initCause(thisPackNotValid);
+				throw gone;
+			}
+		}
+	}
+
+	private synchronized void endCopyAsIs() {
+		if (--activeCopyRawData == 0 && activeWindows == 0)
+			doClose();
+	}
+
+	synchronized boolean beginWindowCache() throws IOException {
+		if (++activeWindows == 1) {
+			if (activeCopyRawData == 0)
+				doOpen();
+			return true;
+		}
+		return false;
+	}
+
+	synchronized boolean endWindowCache() {
+		final boolean r = --activeWindows == 0;
+		if (r && activeCopyRawData == 0)
+			doClose();
+		return r;
+	}
+
+	private void doOpen() throws IOException {
+		try {
+			if (invalid)
+				throw new PackInvalidException(packFile);
+			synchronized (readLock) {
+				fd = new RandomAccessFile(packFile, "r");
+				length = fd.length();
+				onOpenPack();
+			}
+		} catch (IOException ioe) {
+			openFail();
+			throw ioe;
+		} catch (RuntimeException re) {
+			openFail();
+			throw re;
+		} catch (Error re) {
+			openFail();
+			throw re;
+		}
+	}
+
+	private void openFail() {
+		activeWindows = 0;
+		activeCopyRawData = 0;
+		invalid = true;
+		doClose();
+	}
+
+	private void doClose() {
+		synchronized (readLock) {
+			if (fd != null) {
+				try {
+					fd.close();
+				} catch (IOException err) {
+					// Ignore a close event. We had it open only for reading.
+					// There should not be errors related to network buffers
+					// not flushed, etc.
+				}
+				fd = null;
+			}
+		}
+	}
+
+	ByteArrayWindow read(final long pos, int size) throws IOException {
+		synchronized (readLock) {
+			if (length < pos + size)
+				size = (int) (length - pos);
+			final byte[] buf = new byte[size];
+			fd.seek(pos);
+			fd.readFully(buf, 0, size);
+			return new ByteArrayWindow(this, pos, buf);
+		}
+	}
+
+	ByteWindow mmap(final long pos, int size) throws IOException {
+		synchronized (readLock) {
+			if (length < pos + size)
+				size = (int) (length - pos);
+
+			MappedByteBuffer map;
+			try {
+				map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
+			} catch (IOException ioe1) {
+				// The most likely reason this failed is the JVM has run out
+				// of virtual memory. We need to discard quickly, and try to
+				// force the GC to finalize and release any existing mappings.
+				//
+				System.gc();
+				System.runFinalization();
+				map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
+			}
+
+			if (map.hasArray())
+				return new ByteArrayWindow(this, pos, map.array());
+			return new ByteBufferWindow(this, pos, map);
+		}
+	}
+
+	private void onOpenPack() throws IOException {
+		final PackIndex idx = idx();
+		final byte[] buf = new byte[20];
+
+		fd.seek(0);
+		fd.readFully(buf, 0, 12);
+		if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4)
+			throw new IOException(JGitText.get().notAPACKFile);
+		final long vers = NB.decodeUInt32(buf, 4);
+		final long packCnt = NB.decodeUInt32(buf, 8);
+		if (vers != 2 && vers != 3)
+			throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackVersion, vers));
+
+		if (packCnt != idx.getObjectCount())
+			throw new PackMismatchException(MessageFormat.format(
+					JGitText.get().packObjectCountMismatch, packCnt, idx.getObjectCount(), getPackFile()));
+
+		fd.seek(length - 20);
+		fd.read(buf, 0, 20);
+		if (!Arrays.equals(buf, packChecksum))
+			throw new PackMismatchException(MessageFormat.format(
+					JGitText.get().packObjectCountMismatch
+					, ObjectId.fromRaw(buf).name()
+					, ObjectId.fromRaw(idx.packChecksum).name()
+					, getPackFile()));
+	}
+
+	ObjectLoader load(final WindowCursor curs, final long pos)
+			throws IOException {
+		final byte[] ib = curs.tempId;
+		readFully(pos, ib, 0, 20, curs);
+		int c = ib[0] & 0xff;
+		final int type = (c >> 4) & 7;
+		long sz = c & 15;
+		int shift = 4;
+		int p = 1;
+		while ((c & 0x80) != 0) {
+			c = ib[p++] & 0xff;
+			sz += (c & 0x7f) << shift;
+			shift += 7;
+		}
+
+		try {
+			switch (type) {
+			case Constants.OBJ_COMMIT:
+			case Constants.OBJ_TREE:
+			case Constants.OBJ_BLOB:
+			case Constants.OBJ_TAG: {
+				if (sz < curs.getStreamFileThreshold()) {
+					byte[] data = decompress(pos + p, sz, curs);
+					return new ObjectLoader.SmallObject(type, data);
+				}
+				return new LargePackedWholeObject(type, sz, pos, p, this, curs.db);
+			}
+
+			case Constants.OBJ_OFS_DELTA: {
+				c = ib[p++] & 0xff;
+				long ofs = c & 127;
+				while ((c & 128) != 0) {
+					ofs += 1;
+					c = ib[p++] & 0xff;
+					ofs <<= 7;
+					ofs += (c & 127);
+				}
+				return loadDelta(pos, p, sz, pos - ofs, curs);
+			}
+
+			case Constants.OBJ_REF_DELTA: {
+				readFully(pos + p, ib, 0, 20, curs);
+				long ofs = findDeltaBase(ObjectId.fromRaw(ib));
+				return loadDelta(pos, p + 20, sz, ofs, curs);
+			}
+
+			default:
+				throw new IOException(MessageFormat.format(
+						JGitText.get().unknownObjectType, type));
+			}
+		} catch (DataFormatException dfe) {
+			CorruptObjectException coe = new CorruptObjectException(
+					MessageFormat.format(
+							JGitText.get().objectAtHasBadZlibStream, pos,
+							getPackFile()));
+			coe.initCause(dfe);
+			throw coe;
+		}
+	}
+
+	private long findDeltaBase(ObjectId baseId) throws IOException,
+			MissingObjectException {
+		long ofs = idx().findOffset(baseId);
+		if (ofs < 0)
+			throw new MissingObjectException(baseId,
+					JGitText.get().missingDeltaBase);
+		return ofs;
+	}
+
+	private ObjectLoader loadDelta(long posSelf, int hdrLen, long sz,
+			long posBase, WindowCursor curs) throws IOException,
+			DataFormatException {
+		if (curs.getStreamFileThreshold() <= sz) {
+			// The delta instruction stream itself is pretty big, and
+			// that implies the resulting object is going to be massive.
+			// Use only the large delta format here.
+			//
+			return new LargePackedDeltaObject(posSelf, posBase, hdrLen, //
+					this, curs.db);
+		}
+
+		byte[] data;
+		int type;
+
+		UnpackedObjectCache.Entry e = readCache(posBase);
+		if (e != null) {
+			data = e.data;
+			type = e.type;
+		} else {
+			ObjectLoader p = load(curs, posBase);
+			if (p.isLarge()) {
+				// The base itself is large. We have to produce a large
+				// delta stream as we don't want to build the whole base.
+				//
+				return new LargePackedDeltaObject(posSelf, posBase, hdrLen,
+						this, curs.db);
+			}
+			data = p.getCachedBytes();
+			type = p.getType();
+			saveCache(posBase, data, type);
+		}
+
+		// At this point we have the base, and its small, and the delta
+		// stream also is small, so the result object cannot be more than
+		// 2x our small size. This occurs if the delta instructions were
+		// "copy entire base, literal insert entire delta". Go with the
+		// faster small object style at this point.
+		//
+		data = BinaryDelta.apply(data, decompress(posSelf + hdrLen, sz, curs));
+		return new ObjectLoader.SmallObject(type, data);
+	}
+
+	byte[] getDeltaHeader(WindowCursor wc, long pos)
+			throws IOException, DataFormatException {
+		// The delta stream starts as two variable length integers. If we
+		// assume they are 64 bits each, we need 16 bytes to encode them,
+		// plus 2 extra bytes for the variable length overhead. So 18 is
+		// the longest delta instruction header.
+		//
+		final byte[] hdr = new byte[18];
+		wc.inflate(this, pos, hdr, 0);
+		return hdr;
+	}
+
+	int getObjectType(final WindowCursor curs, long pos) throws IOException {
+		final byte[] ib = curs.tempId;
+		for (;;) {
+			readFully(pos, ib, 0, 20, curs);
+			int c = ib[0] & 0xff;
+			final int type = (c >> 4) & 7;
+			int shift = 4;
+			int p = 1;
+			while ((c & 0x80) != 0) {
+				c = ib[p++] & 0xff;
+				shift += 7;
+			}
+
+			switch (type) {
+			case Constants.OBJ_COMMIT:
+			case Constants.OBJ_TREE:
+			case Constants.OBJ_BLOB:
+			case Constants.OBJ_TAG:
+				return type;
+
+			case Constants.OBJ_OFS_DELTA: {
+				c = ib[p++] & 0xff;
+				long ofs = c & 127;
+				while ((c & 128) != 0) {
+					ofs += 1;
+					c = ib[p++] & 0xff;
+					ofs <<= 7;
+					ofs += (c & 127);
+				}
+				pos = pos - ofs;
+				continue;
+			}
+
+			case Constants.OBJ_REF_DELTA: {
+				readFully(pos + p, ib, 0, 20, curs);
+				pos = findDeltaBase(ObjectId.fromRaw(ib));
+				continue;
+			}
+
+			default:
+				throw new IOException(MessageFormat.format(
+						JGitText.get().unknownObjectType, type));
+			}
+		}
+	}
+
+	long getObjectSize(final WindowCursor curs, final AnyObjectId id)
+			throws IOException {
+		final long offset = idx().findOffset(id);
+		return 0 < offset ? getObjectSize(curs, offset) : -1;
+	}
+
+	long getObjectSize(final WindowCursor curs, final long pos)
+			throws IOException {
+		final byte[] ib = curs.tempId;
+		readFully(pos, ib, 0, 20, curs);
+		int c = ib[0] & 0xff;
+		final int type = (c >> 4) & 7;
+		long sz = c & 15;
+		int shift = 4;
+		int p = 1;
+		while ((c & 0x80) != 0) {
+			c = ib[p++] & 0xff;
+			sz += (c & 0x7f) << shift;
+			shift += 7;
+		}
+
+		long deltaAt;
+		switch (type) {
+		case Constants.OBJ_COMMIT:
+		case Constants.OBJ_TREE:
+		case Constants.OBJ_BLOB:
+		case Constants.OBJ_TAG:
+			return sz;
+
+		case Constants.OBJ_OFS_DELTA:
+			c = ib[p++] & 0xff;
+			while ((c & 128) != 0)
+				c = ib[p++] & 0xff;
+			deltaAt = pos + p;
+			break;
+
+		case Constants.OBJ_REF_DELTA:
+			deltaAt = pos + p + 20;
+			break;
+
+		default:
+			throw new IOException(MessageFormat.format(
+					JGitText.get().unknownObjectType, type));
+		}
+
+		try {
+			return BinaryDelta.getResultSize(getDeltaHeader(curs, deltaAt));
+		} catch (DataFormatException e) {
+			throw new CorruptObjectException(MessageFormat.format(JGitText
+					.get().objectAtHasBadZlibStream, pos, getPackFile()));
+		}
+	}
+
+	LocalObjectRepresentation representation(final WindowCursor curs,
+			final AnyObjectId objectId) throws IOException {
+		final long pos = idx().findOffset(objectId);
+		if (pos < 0)
+			return null;
+
+		final byte[] ib = curs.tempId;
+		readFully(pos, ib, 0, 20, curs);
+		int c = ib[0] & 0xff;
+		int p = 1;
+		final int typeCode = (c >> 4) & 7;
+		while ((c & 0x80) != 0)
+			c = ib[p++] & 0xff;
+
+		long len = (findEndOffset(pos) - pos);
+		switch (typeCode) {
+		case Constants.OBJ_COMMIT:
+		case Constants.OBJ_TREE:
+		case Constants.OBJ_BLOB:
+		case Constants.OBJ_TAG:
+			return LocalObjectRepresentation.newWhole(this, pos, len - p);
+
+		case Constants.OBJ_OFS_DELTA: {
+			c = ib[p++] & 0xff;
+			long ofs = c & 127;
+			while ((c & 128) != 0) {
+				ofs += 1;
+				c = ib[p++] & 0xff;
+				ofs <<= 7;
+				ofs += (c & 127);
+			}
+			ofs = pos - ofs;
+			return LocalObjectRepresentation.newDelta(this, pos, len - p, ofs);
+		}
+
+		case Constants.OBJ_REF_DELTA: {
+			len -= p;
+			len -= Constants.OBJECT_ID_LENGTH;
+			readFully(pos + p, ib, 0, 20, curs);
+			ObjectId id = ObjectId.fromRaw(ib);
+			return LocalObjectRepresentation.newDelta(this, pos, len, id);
+		}
+
+		default:
+			throw new IOException(MessageFormat.format(
+					JGitText.get().unknownObjectType, typeCode));
+		}
+	}
+
+	private long findEndOffset(final long startOffset)
+			throws IOException, CorruptObjectException {
+		final long maxOffset = length - 20;
+		return getReverseIdx().findNextOffset(startOffset, maxOffset);
+	}
+
+	private synchronized PackReverseIndex getReverseIdx() throws IOException {
+		if (reverseIdx == null)
+			reverseIdx = new PackReverseIndex(idx());
+		return reverseIdx;
+	}
+
+	private boolean isCorrupt(long offset) {
+		LongList list = corruptObjects;
+		if (list == null)
+			return false;
+		synchronized (list) {
+			return list.contains(offset);
+		}
+	}
+
+	private void setCorrupt(long offset) {
+		LongList list = corruptObjects;
+		if (list == null) {
+			synchronized (readLock) {
+				list = corruptObjects;
+				if (list == null) {
+					list = new LongList();
+					corruptObjects = list;
+				}
+			}
+		}
+		synchronized (list) {
+			list.add(offset);
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java
similarity index 98%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java
index 13985e7..62d1c9d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java
@@ -42,7 +42,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -53,6 +53,9 @@
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.NB;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java
similarity index 97%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java
index bb7cd8b..3b68edc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java
@@ -44,7 +44,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -53,6 +53,9 @@
 import java.util.NoSuchElementException;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.NB;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java
similarity index 97%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java
index 128b2df..cef7cc4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java
@@ -41,7 +41,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -51,6 +51,9 @@
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.NB;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java
similarity index 98%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java
index 4d2714b..6bd73ad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java
@@ -42,7 +42,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.BufferedOutputStream;
 import java.io.IOException;
@@ -52,6 +52,8 @@
 import java.util.List;
 
 import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.transport.PackedObjectInfo;
 import org.eclipse.jgit.util.NB;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java
similarity index 98%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java
index eb44b3a..722ab0e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java
@@ -42,7 +42,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.IOException;
 import java.io.OutputStream;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java
similarity index 98%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java
index b6ac7b8..21ebd1c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java
@@ -41,7 +41,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.IOException;
 import java.io.OutputStream;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackInputStream.java
similarity index 69%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackInputStream.java
index e43c33a..5425eed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackInputStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,20 +41,45 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
+import java.io.IOException;
+import java.io.InputStream;
 
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
+class PackInputStream extends InputStream {
+	private final WindowCursor wc;
+
+	private final PackFile pack;
+
+	private long pos;
+
+	PackInputStream(PackFile pack, long pos, WindowCursor wc)
+			throws IOException {
+		this.pack = pack;
+		this.pos = pos;
+		this.wc = wc;
+
+		// Pin the first window, to ensure the pack is open and valid.
+		//
+		wc.pin(pack, pos);
 	}
 
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
+	@Override
+	public int read(byte[] b, int off, int len) throws IOException {
+		int n = wc.copy(pack, pos, b, off, len);
+		pos += n;
+		return n;
 	}
 
-}
+	@Override
+	public int read() throws IOException {
+		byte[] buf = new byte[1];
+		int n = read(buf, 0, 1);
+		return n == 1 ? buf[0] & 0xff : -1;
+	}
+
+	@Override
+	public void close() {
+		wc.release();
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java
similarity index 89%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java
index de8e3fa..dd08dfb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java
@@ -41,25 +41,32 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.io.IOException;
 
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.util.FS;
+
 /** Keeps track of a {@link PackFile}'s associated <code>.keep</code> file. */
 public class PackLock {
 	private final File keepFile;
+	private final FS fs;
 
 	/**
 	 * Create a new lock for a pack file.
 	 *
 	 * @param packFile
 	 *            location of the <code>pack-*.pack</code> file.
+	 * @param fs
+	 *            the filesystem abstraction used by the repository.
 	 */
-	public PackLock(final File packFile) {
+	public PackLock(final File packFile, final FS fs) {
 		final File p = packFile.getParentFile();
 		final String n = packFile.getName();
 		keepFile = new File(p, n.substring(0, n.length() - 5) + ".keep");
+		this.fs = fs;
 	}
 
 	/**
@@ -76,7 +83,7 @@ public boolean lock(String msg) throws IOException {
 			return false;
 		if (!msg.endsWith("\n"))
 			msg += "\n";
-		final LockFile lf = new LockFile(keepFile);
+		final LockFile lf = new LockFile(keepFile, fs);
 		if (!lf.lock())
 			return false;
 		lf.write(Constants.encode(msg));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java
similarity index 97%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java
index f4f57ae..96abaee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java
@@ -41,14 +41,15 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.text.MessageFormat;
 import java.util.Arrays;
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.lib.PackIndex.MutableEntry;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.storage.file.PackIndex.MutableEntry;
 
 /**
  * <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java
similarity index 95%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java
index 302b63b..b22b14a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java
@@ -44,7 +44,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import static org.eclipse.jgit.lib.Constants.CHARSET;
 import static org.eclipse.jgit.lib.Constants.HEAD;
@@ -74,7 +74,21 @@
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.ObjectWritingException;
+import org.eclipse.jgit.events.RefsChangedEvent;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefComparator;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefWriter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -109,7 +123,7 @@ public class RefDirectory extends RefDatabase {
 	/** If in the header, denotes the file has peeled data. */
 	public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$
 
-	private final Repository parent;
+	private final FileRepository parent;
 
 	private final File gitDir;
 
@@ -150,7 +164,7 @@ public class RefDirectory extends RefDatabase {
 	 */
 	private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
 
-	RefDirectory(final Repository db) {
+	RefDirectory(final FileRepository db) {
 		final FS fs = db.getFS();
 		parent = db;
 		gitDir = db.getDirectory();
@@ -405,20 +419,7 @@ public Ref peel(final Ref ref) throws IOException {
 		if (leaf.isPeeled() || leaf.getObjectId() == null)
 			return ref;
 
-		RevWalk rw = new RevWalk(getRepository());
-		RevObject obj = rw.parseAny(leaf.getObjectId());
-		ObjectIdRef newLeaf;
-		if (obj instanceof RevTag) {
-			do {
-				obj = rw.parseAny(((RevTag) obj).getObject());
-			} while (obj instanceof RevTag);
-
-			newLeaf = new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf
-					.getName(), leaf.getObjectId(), obj.copy());
-		} else {
-			newLeaf = new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf
-					.getName(), leaf.getObjectId());
-		}
+		ObjectIdRef newLeaf = doPeel(leaf);
 
 		// Try to remember this peeling in the cache, so we don't have to do
 		// it again in the future, but only if the reference is unchanged.
@@ -435,6 +436,23 @@ public Ref peel(final Ref ref) throws IOException {
 		return recreate(ref, newLeaf);
 	}
 
+	private ObjectIdRef doPeel(final Ref leaf) throws MissingObjectException,
+			IOException {
+		RevWalk rw = new RevWalk(getRepository());
+		try {
+			RevObject obj = rw.parseAny(leaf.getObjectId());
+			if (obj instanceof RevTag) {
+				return new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf
+						.getName(), leaf.getObjectId(), rw.peel(obj).copy());
+			} else {
+				return new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf
+						.getName(), leaf.getObjectId());
+			}
+		} finally {
+			rw.release();
+		}
+	}
+
 	private static Ref recreate(final Ref old, final ObjectIdRef leaf) {
 		if (old.isSymbolic()) {
 			Ref dst = recreate(old.getTarget(), leaf);
@@ -494,7 +512,8 @@ void delete(RefDirectoryUpdate update) throws IOException {
 		// we don't miss an edit made externally.
 		final PackedRefList packed = getPackedRefs();
 		if (packed.contains(name)) {
-			LockFile lck = new LockFile(packedRefsFile);
+			LockFile lck = new LockFile(packedRefsFile,
+					update.getRepository().getFS());
 			if (!lck.lock())
 				throw new IOException(MessageFormat.format(
 					JGitText.get().cannotLockFile, packedRefsFile));
@@ -590,7 +609,7 @@ else if (log.isFile())
 	}
 
 	private boolean isLogAllRefUpdates() {
-		return parent.getConfig().getCore().isLogAllRefUpdates();
+		return parent.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates();
 	}
 
 	private boolean shouldAutoCreateLog(final String refName) {
@@ -833,7 +852,7 @@ private void fireRefsChanged() {
 		final int last = lastNotifiedModCnt.get();
 		final int curr = modCnt.get();
 		if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr))
-			parent.fireRefsChanged();
+			parent.fireEvent(new RefsChangedEvent());
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java
similarity index 96%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java
index fec00d9..4f3efe3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java
@@ -42,11 +42,15 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.io.IOException;
 
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.revwalk.RevWalk;
 
@@ -88,10 +92,10 @@ protected Result doRename() throws IOException {
 		if (source.getRef().isSymbolic())
 			return Result.IO_FAILURE; // not supported
 
-		final RevWalk rw = new RevWalk(refdb.getRepository());
 		objId = source.getOldObjectId();
 		updateHEAD = needToUpdateHEAD();
 		tmp = refdb.newTemporaryUpdate();
+		final RevWalk rw = new RevWalk(refdb.getRepository());
 		try {
 			// First backup the source so its never unreachable.
 			tmp.setNewObjectId(objId);
@@ -173,6 +177,7 @@ protected Result doRename() throws IOException {
 			} catch (IOException err) {
 				refdb.fileFor(tmp.getName()).delete();
 			}
+			rw.release();
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java
similarity index 94%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java
index 447be10..a9f0548 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java
@@ -42,12 +42,16 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import static org.eclipse.jgit.lib.Constants.encode;
 
 import java.io.IOException;
 
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+
 /** Updates any reference stored by {@link RefDirectory}. */
 class RefDirectoryUpdate extends RefUpdate {
 	private final RefDirectory database;
@@ -75,7 +79,7 @@ protected boolean tryLock(boolean deref) throws IOException {
 		if (deref)
 			dst = dst.getLeaf();
 		String name = dst.getName();
-		lock = new LockFile(database.fileFor(name));
+		lock = new LockFile(database.fileFor(name), getRepository().getFS());
 		if (lock.lock()) {
 			dst = database.getRef(name);
 			setOldObjectId(dst != null ? dst.getObjectId() : null);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java
similarity index 96%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java
index 4c5503f..7521430 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java
@@ -42,7 +42,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -52,6 +52,10 @@
 import java.util.List;
 
 import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java
new file mode 100644
index 0000000..78e7b10
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipException;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.InflaterCache;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectStream;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.MutableInteger;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Loose object loader. This class loads an object not stored in a pack.
+ */
+public class UnpackedObject {
+	private static final int BUFFER_SIZE = 8192;
+
+	/**
+	 * Parse an object from the unpacked object format.
+	 *
+	 * @param raw
+	 *            complete contents of the compressed object.
+	 * @param id
+	 *            expected ObjectId of the object, used only for error reporting
+	 *            in exceptions.
+	 * @return loader to read the inflated contents.
+	 * @throws IOException
+	 *             the object cannot be parsed.
+	 */
+	public static ObjectLoader parse(byte[] raw, AnyObjectId id)
+			throws IOException {
+		WindowCursor wc = new WindowCursor(null);
+		try {
+			return open(new ByteArrayInputStream(raw), null, id, wc);
+		} finally {
+			wc.release();
+		}
+	}
+
+	static ObjectLoader open(InputStream in, File path, AnyObjectId id,
+			WindowCursor wc) throws IOException {
+		try {
+			in = buffer(in);
+			in.mark(20);
+			final byte[] hdr = new byte[64];
+			IO.readFully(in, hdr, 0, 2);
+
+			if (isStandardFormat(hdr)) {
+				in.reset();
+				Inflater inf = wc.inflater();
+				InputStream zIn = inflate(in, inf);
+				int avail = readSome(zIn, hdr, 0, 64);
+				if (avail < 5)
+					throw new CorruptObjectException(id,
+							JGitText.get().corruptObjectNoHeader);
+
+				final MutableInteger p = new MutableInteger();
+				int type = Constants.decodeTypeString(id, hdr, (byte) ' ', p);
+				long size = RawParseUtils.parseLongBase10(hdr, p.value, p);
+				if (size < 0)
+					throw new CorruptObjectException(id,
+							JGitText.get().corruptObjectNegativeSize);
+				if (hdr[p.value++] != 0)
+					throw new CorruptObjectException(id,
+							JGitText.get().corruptObjectGarbageAfterSize);
+				if (path == null && Integer.MAX_VALUE < size)
+					throw new LargeObjectException(id.copy());
+				if (size < wc.getStreamFileThreshold() || path == null) {
+					byte[] data = new byte[(int) size];
+					int n = avail - p.value;
+					if (n > 0)
+						System.arraycopy(hdr, p.value, data, 0, n);
+					IO.readFully(zIn, data, n, data.length - n);
+					checkValidEndOfStream(in, inf, id, hdr);
+					return new ObjectLoader.SmallObject(type, data);
+				}
+				return new LargeObject(type, size, path, id, wc.db);
+
+			} else {
+				readSome(in, hdr, 2, 18);
+				int c = hdr[0] & 0xff;
+				int type = (c >> 4) & 7;
+				long size = c & 15;
+				int shift = 4;
+				int p = 1;
+				while ((c & 0x80) != 0) {
+					c = hdr[p++] & 0xff;
+					size += (c & 0x7f) << shift;
+					shift += 7;
+				}
+
+				switch (type) {
+				case Constants.OBJ_COMMIT:
+				case Constants.OBJ_TREE:
+				case Constants.OBJ_BLOB:
+				case Constants.OBJ_TAG:
+					// Acceptable types for a loose object.
+					break;
+				default:
+					throw new CorruptObjectException(id,
+							JGitText.get().corruptObjectInvalidType);
+				}
+
+				if (path == null && Integer.MAX_VALUE < size)
+					throw new LargeObjectException(id.copy());
+				if (size < wc.getStreamFileThreshold() || path == null) {
+					in.reset();
+					IO.skipFully(in, p);
+					Inflater inf = wc.inflater();
+					InputStream zIn = inflate(in, inf);
+					byte[] data = new byte[(int) size];
+					IO.readFully(zIn, data, 0, data.length);
+					checkValidEndOfStream(in, inf, id, hdr);
+					return new ObjectLoader.SmallObject(type, data);
+				}
+				return new LargeObject(type, size, path, id, wc.db);
+			}
+		} catch (ZipException badStream) {
+			throw new CorruptObjectException(id,
+					JGitText.get().corruptObjectBadStream);
+		}
+	}
+
+	static long getSize(InputStream in, AnyObjectId id, WindowCursor wc)
+			throws IOException {
+		try {
+			in = buffer(in);
+			in.mark(20);
+			final byte[] hdr = new byte[64];
+			IO.readFully(in, hdr, 0, 2);
+
+			if (isStandardFormat(hdr)) {
+				in.reset();
+				Inflater inf = wc.inflater();
+				InputStream zIn = inflate(in, inf);
+				int avail = readSome(zIn, hdr, 0, 64);
+				if (avail < 5)
+					throw new CorruptObjectException(id,
+							JGitText.get().corruptObjectNoHeader);
+
+				final MutableInteger p = new MutableInteger();
+				Constants.decodeTypeString(id, hdr, (byte) ' ', p);
+				long size = RawParseUtils.parseLongBase10(hdr, p.value, p);
+				if (size < 0)
+					throw new CorruptObjectException(id,
+							JGitText.get().corruptObjectNegativeSize);
+				return size;
+
+			} else {
+				readSome(in, hdr, 2, 18);
+				int c = hdr[0] & 0xff;
+				long size = c & 15;
+				int shift = 4;
+				int p = 1;
+				while ((c & 0x80) != 0) {
+					c = hdr[p++] & 0xff;
+					size += (c & 0x7f) << shift;
+					shift += 7;
+				}
+				return size;
+			}
+		} catch (ZipException badStream) {
+			throw new CorruptObjectException(id,
+					JGitText.get().corruptObjectBadStream);
+		}
+	}
+
+	private static void checkValidEndOfStream(InputStream in, Inflater inf,
+			AnyObjectId id, final byte[] buf) throws IOException,
+			CorruptObjectException {
+		for (;;) {
+			int r;
+			try {
+				r = inf.inflate(buf);
+			} catch (DataFormatException e) {
+				throw new CorruptObjectException(id,
+						JGitText.get().corruptObjectBadStream);
+			}
+			if (r != 0)
+				throw new CorruptObjectException(id,
+						JGitText.get().corruptObjectIncorrectLength);
+
+			if (inf.finished()) {
+				if (inf.getRemaining() != 0 || in.read() != -1)
+					throw new CorruptObjectException(id,
+							JGitText.get().corruptObjectBadStream);
+				break;
+			}
+
+			if (!inf.needsInput())
+				throw new CorruptObjectException(id,
+						JGitText.get().corruptObjectBadStream);
+
+			r = in.read(buf);
+			if (r <= 0)
+				throw new CorruptObjectException(id,
+						JGitText.get().corruptObjectBadStream);
+			inf.setInput(buf, 0, r);
+		}
+	}
+
+	private static boolean isStandardFormat(final byte[] hdr) {
+		// Try to determine if this is a standard format loose object or
+		// a pack style loose object. The standard format is completely
+		// compressed with zlib so the first byte must be 0x78 (15-bit
+		// window size, deflated) and the first 16 bit word must be
+		// evenly divisible by 31. Otherwise its a pack style object.
+		//
+		final int fb = hdr[0] & 0xff;
+		return fb == 0x78 && (((fb << 8) | hdr[1] & 0xff) % 31) == 0;
+	}
+
+	private static InputStream inflate(final InputStream in, final long size,
+			final ObjectId id) {
+		final Inflater inf = InflaterCache.get();
+		return new InflaterInputStream(in, inf) {
+			private long remaining = size;
+
+			@Override
+			public int read(byte[] b, int off, int cnt) throws IOException {
+				try {
+					int r = super.read(b, off, cnt);
+					if (r > 0)
+						remaining -= r;
+					return r;
+				} catch (ZipException badStream) {
+					throw new CorruptObjectException(id,
+							JGitText.get().corruptObjectBadStream);
+				}
+			}
+
+			@Override
+			public void close() throws IOException {
+				try {
+					if (remaining <= 0)
+						checkValidEndOfStream(in, inf, id, new byte[64]);
+				} finally {
+					InflaterCache.release(inf);
+					super.close();
+				}
+			}
+		};
+	}
+
+	private static InflaterInputStream inflate(InputStream in, Inflater inf) {
+		return new InflaterInputStream(in, inf, BUFFER_SIZE);
+	}
+
+	private static BufferedInputStream buffer(InputStream in) {
+		return new BufferedInputStream(in, BUFFER_SIZE);
+	}
+
+	private static int readSome(InputStream in, final byte[] hdr, int off,
+			int cnt) throws IOException {
+		int avail = 0;
+		while (0 < cnt) {
+			int n = in.read(hdr, off, cnt);
+			if (n < 0)
+				break;
+			avail += n;
+			cnt -= n;
+		}
+		return avail;
+	}
+
+	private static final class LargeObject extends ObjectLoader {
+		private final int type;
+
+		private final long size;
+
+		private final File path;
+
+		private final ObjectId id;
+
+		private final FileObjectDatabase source;
+
+		private LargeObject(int type, long size, File path, AnyObjectId id,
+				FileObjectDatabase db) {
+			this.type = type;
+			this.size = size;
+			this.path = path;
+			this.id = id.copy();
+			this.source = db;
+		}
+
+		@Override
+		public int getType() {
+			return type;
+		}
+
+		@Override
+		public long getSize() {
+			return size;
+		}
+
+		@Override
+		public boolean isLarge() {
+			return true;
+		}
+
+		@Override
+		public byte[] getCachedBytes() throws LargeObjectException {
+			throw new LargeObjectException(id);
+		}
+
+		@Override
+		public ObjectStream openStream() throws MissingObjectException,
+				IOException {
+			InputStream in;
+			try {
+				in = buffer(new FileInputStream(path));
+			} catch (FileNotFoundException gone) {
+				// If the loose file no longer exists, it may have been
+				// moved into a pack file in the mean time. Try again
+				// to locate the object.
+				//
+				return source.open(id, type).openStream();
+			}
+
+			boolean ok = false;
+			try {
+				final byte[] hdr = new byte[64];
+				in.mark(20);
+				IO.readFully(in, hdr, 0, 2);
+
+				if (isStandardFormat(hdr)) {
+					in.reset();
+					in = buffer(inflate(in, size, id));
+					while (0 < in.read())
+						continue;
+				} else {
+					readSome(in, hdr, 2, 18);
+					int c = hdr[0] & 0xff;
+					int p = 1;
+					while ((c & 0x80) != 0)
+						c = hdr[p++] & 0xff;
+
+					in.reset();
+					IO.skipFully(in, p);
+					in = buffer(inflate(in, size, id));
+				}
+
+				ok = true;
+				return new ObjectStream.Filter(type, size, in);
+			} finally {
+				if (!ok)
+					in.close();
+			}
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java
similarity index 98%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java
index 3cef482..92f4824 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java
@@ -41,7 +41,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.lang.ref.SoftReference;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java
similarity index 99%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java
index a44a30e..39633ee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java
@@ -42,7 +42,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.IOException;
 import java.lang.ref.ReferenceQueue;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java
similarity index 98%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java
index 2d8aef3..48d7018 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java
@@ -41,7 +41,9 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
+
+import org.eclipse.jgit.lib.Config;
 
 /** Configuration parameters for {@link WindowCache}. */
 public class WindowCacheConfig {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java
similarity index 60%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java
index 968c92e..5376d07 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java
@@ -42,14 +42,28 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.file;
 
 import java.io.IOException;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
 
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.InflaterCache;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.storage.pack.ObjectReuseAsIs;
+import org.eclipse.jgit.storage.pack.ObjectToPack;
+import org.eclipse.jgit.storage.pack.PackOutputStream;
+import org.eclipse.jgit.storage.pack.PackWriter;
+
 /** Active handle to a ByteWindow. */
-public final class WindowCursor {
+final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
 	/** Temporary buffer large enough for at least one raw object id. */
 	final byte[] tempId = new byte[Constants.OBJECT_ID_LENGTH];
 
@@ -57,6 +71,62 @@ public final class WindowCursor {
 
 	private ByteWindow window;
 
+	final FileObjectDatabase db;
+
+	WindowCursor(FileObjectDatabase db) {
+		this.db = db;
+	}
+
+	@Override
+	public ObjectReader newReader() {
+		return new WindowCursor(db);
+	}
+
+	public boolean has(AnyObjectId objectId) throws IOException {
+		return db.has(objectId);
+	}
+
+	public ObjectLoader open(AnyObjectId objectId, int typeHint)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException {
+		final ObjectLoader ldr = db.openObject(this, objectId);
+		if (ldr == null) {
+			if (typeHint == OBJ_ANY)
+				throw new MissingObjectException(objectId.copy(), "unknown");
+			throw new MissingObjectException(objectId.copy(), typeHint);
+		}
+		if (typeHint != OBJ_ANY && ldr.getType() != typeHint)
+			throw new IncorrectObjectTypeException(objectId.copy(), typeHint);
+		return ldr;
+	}
+
+	public long getObjectSize(AnyObjectId objectId, int typeHint)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException {
+		long sz = db.getObjectSize(this, objectId);
+		if (sz < 0) {
+			if (typeHint == OBJ_ANY)
+				throw new MissingObjectException(objectId.copy(), "unknown");
+			throw new MissingObjectException(objectId.copy(), typeHint);
+		}
+		return sz;
+	}
+
+	public LocalObjectToPack newObjectToPack(RevObject obj) {
+		return new LocalObjectToPack(obj);
+	}
+
+	public void selectObjectRepresentation(PackWriter packer, ObjectToPack otp)
+			throws IOException, MissingObjectException {
+		db.selectObjectRepresentation(packer, otp, this);
+	}
+
+	public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp)
+			throws IOException, StoredObjectRepresentationNotAvailableException {
+		LocalObjectToPack src = (LocalObjectToPack) otp;
+		src.pack.copyAsIs(out, src, this);
+	}
+
 	/**
 	 * Copy bytes from the window to a caller supplied buffer.
 	 *
@@ -73,8 +143,8 @@ public final class WindowCursor {
 	 *            bytes remaining in the window starting at offset
 	 *            <code>pos</code>.
 	 * @return number of bytes actually copied; this may be less than
-	 *         <code>cnt</code> if <code>cnt</code> exceeded the number of
-	 *         bytes available.
+	 *         <code>cnt</code> if <code>cnt</code> exceeded the number of bytes
+	 *         available.
 	 * @throws IOException
 	 *             this cursor does not match the provider or id and the proper
 	 *             window could not be acquired through the provider's cache.
@@ -94,7 +164,7 @@ int copy(final PackFile pack, long position, final byte[] dstbuf,
 	}
 
 	/**
-	 * Pump bytes into the supplied inflater as input.
+	 * Inflate a region of the pack starting at {@code position}.
 	 *
 	 * @param pack
 	 *            the file the desired window is stored within.
@@ -117,25 +187,36 @@ int copy(final PackFile pack, long position, final byte[] dstbuf,
 	int inflate(final PackFile pack, long position, final byte[] dstbuf,
 			int dstoff) throws IOException, DataFormatException {
 		prepareInflater();
-		for (;;) {
-			pin(pack, position);
-			dstoff = window.inflate(position, dstbuf, dstoff, inf);
-			if (inf.finished())
-				return dstoff;
-			position = window.end;
-		}
+		pin(pack, position);
+		position += window.setInput(position, inf);
+		do {
+			int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff);
+			if (n == 0) {
+				if (inf.needsInput()) {
+					pin(pack, position);
+					position += window.setInput(position, inf);
+				} else if (inf.finished())
+					return dstoff;
+				else
+					throw new DataFormatException();
+			}
+			dstoff += n;
+		} while (dstoff < dstbuf.length);
+		return dstoff;
 	}
 
-	void inflateVerify(final PackFile pack, long position)
-			throws IOException, DataFormatException {
+	ByteArrayWindow quickCopy(PackFile p, long pos, long cnt)
+			throws IOException {
+		pin(p, pos);
+		if (window instanceof ByteArrayWindow
+				&& window.contains(p, pos + (cnt - 1)))
+			return (ByteArrayWindow) window;
+		return null;
+	}
+
+	Inflater inflater() {
 		prepareInflater();
-		for (;;) {
-			pin(pack, position);
-			window.inflateVerify(position, inf);
-			if (inf.finished())
-				return;
-			position = window.end;
-		}
+		return inf;
 	}
 
 	private void prepareInflater() {
@@ -145,7 +226,7 @@ private void prepareInflater() {
 			inf.reset();
 	}
 
-	private void pin(final PackFile pack, final long position)
+	void pin(final PackFile pack, final long position)
 			throws IOException {
 		final ByteWindow w = window;
 		if (w == null || !w.contains(pack, position)) {
@@ -159,6 +240,12 @@ private void pin(final PackFile pack, final long position)
 		}
 	}
 
+	int getStreamFileThreshold() {
+		if (db == null)
+			return ObjectLoader.STREAM_THRESHOLD;
+		return db.getStreamFileThreshold();
+	}
+
 	/** Release the current window cursor. */
 	public void release() {
 		window = null;
@@ -168,14 +255,4 @@ public void release() {
 			inf = null;
 		}
 	}
-
-	/**
-	 * @param curs cursor to release; may be null.
-	 * @return always null.
-	 */
-	public static WindowCursor release(final WindowCursor curs) {
-		if (curs != null)
-			curs.release();
-		return null;
-	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java
new file mode 100644
index 0000000..1d433d7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.util.QuotedString;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Recreate a stream from a base stream and a GIT pack delta.
+ * <p>
+ * This entire class is heavily cribbed from <code>patch-delta.c</code> in the
+ * GIT project. The original delta patching code was written by Nicolas Pitre
+ * (&lt;nico@cam.org&gt;).
+ * </p>
+ */
+public class BinaryDelta {
+	/**
+	 * Length of the base object in the delta stream.
+	 *
+	 * @param delta
+	 *            the delta stream, or at least the header of it.
+	 * @return the base object's size.
+	 */
+	public static long getBaseSize(final byte[] delta) {
+		int p = 0;
+		long baseLen = 0;
+		int c, shift = 0;
+		do {
+			c = delta[p++] & 0xff;
+			baseLen |= (c & 0x7f) << shift;
+			shift += 7;
+		} while ((c & 0x80) != 0);
+		return baseLen;
+	}
+
+	/**
+	 * Length of the resulting object in the delta stream.
+	 *
+	 * @param delta
+	 *            the delta stream, or at least the header of it.
+	 * @return the resulting object's size.
+	 */
+	public static long getResultSize(final byte[] delta) {
+		int p = 0;
+
+		// Skip length of the base object.
+		//
+		int c;
+		do {
+			c = delta[p++] & 0xff;
+		} while ((c & 0x80) != 0);
+
+		long resLen = 0;
+		int shift = 0;
+		do {
+			c = delta[p++] & 0xff;
+			resLen |= (c & 0x7f) << shift;
+			shift += 7;
+		} while ((c & 0x80) != 0);
+		return resLen;
+	}
+
+	/**
+	 * Apply the changes defined by delta to the data in base, yielding a new
+	 * array of bytes.
+	 *
+	 * @param base
+	 *            some byte representing an object of some kind.
+	 * @param delta
+	 *            a git pack delta defining the transform from one version to
+	 *            another.
+	 * @return patched base
+	 */
+	public static final byte[] apply(final byte[] base, final byte[] delta) {
+		int deltaPtr = 0;
+
+		// Length of the base object (a variable length int).
+		//
+		int baseLen = 0;
+		int c, shift = 0;
+		do {
+			c = delta[deltaPtr++] & 0xff;
+			baseLen |= (c & 0x7f) << shift;
+			shift += 7;
+		} while ((c & 0x80) != 0);
+		if (base.length != baseLen)
+			throw new IllegalArgumentException(
+					JGitText.get().baseLengthIncorrect);
+
+		// Length of the resulting object (a variable length int).
+		//
+		int resLen = 0;
+		shift = 0;
+		do {
+			c = delta[deltaPtr++] & 0xff;
+			resLen |= (c & 0x7f) << shift;
+			shift += 7;
+		} while ((c & 0x80) != 0);
+
+		final byte[] result = new byte[resLen];
+		int resultPtr = 0;
+		while (deltaPtr < delta.length) {
+			final int cmd = delta[deltaPtr++] & 0xff;
+			if ((cmd & 0x80) != 0) {
+				// Determine the segment of the base which should
+				// be copied into the output. The segment is given
+				// as an offset and a length.
+				//
+				int copyOffset = 0;
+				if ((cmd & 0x01) != 0)
+					copyOffset = delta[deltaPtr++] & 0xff;
+				if ((cmd & 0x02) != 0)
+					copyOffset |= (delta[deltaPtr++] & 0xff) << 8;
+				if ((cmd & 0x04) != 0)
+					copyOffset |= (delta[deltaPtr++] & 0xff) << 16;
+				if ((cmd & 0x08) != 0)
+					copyOffset |= (delta[deltaPtr++] & 0xff) << 24;
+
+				int copySize = 0;
+				if ((cmd & 0x10) != 0)
+					copySize = delta[deltaPtr++] & 0xff;
+				if ((cmd & 0x20) != 0)
+					copySize |= (delta[deltaPtr++] & 0xff) << 8;
+				if ((cmd & 0x40) != 0)
+					copySize |= (delta[deltaPtr++] & 0xff) << 16;
+				if (copySize == 0)
+					copySize = 0x10000;
+
+				System.arraycopy(base, copyOffset, result, resultPtr, copySize);
+				resultPtr += copySize;
+			} else if (cmd != 0) {
+				// Anything else the data is literal within the delta
+				// itself.
+				//
+				System.arraycopy(delta, deltaPtr, result, resultPtr, cmd);
+				deltaPtr += cmd;
+				resultPtr += cmd;
+			} else {
+				// cmd == 0 has been reserved for future encoding but
+				// for now its not acceptable.
+				//
+				throw new IllegalArgumentException(
+						JGitText.get().unsupportedCommand0);
+			}
+		}
+
+		return result;
+	}
+
+	/**
+	 * Format this delta as a human readable string.
+	 *
+	 * @param delta
+	 *            the delta instruction sequence to format.
+	 * @return the formatted delta.
+	 */
+	public static String format(byte[] delta) {
+		return format(delta, true);
+	}
+
+	/**
+	 * Format this delta as a human readable string.
+	 *
+	 * @param delta
+	 *            the delta instruction sequence to format.
+	 * @param includeHeader
+	 *            true if the header (base size and result size) should be
+	 *            included in the formatting.
+	 * @return the formatted delta.
+	 */
+	public static String format(byte[] delta, boolean includeHeader) {
+		StringBuilder r = new StringBuilder();
+		int deltaPtr = 0;
+
+		long baseLen = 0;
+		int c, shift = 0;
+		do {
+			c = delta[deltaPtr++] & 0xff;
+			baseLen |= (c & 0x7f) << shift;
+			shift += 7;
+		} while ((c & 0x80) != 0);
+
+		long resLen = 0;
+		shift = 0;
+		do {
+			c = delta[deltaPtr++] & 0xff;
+			resLen |= (c & 0x7f) << shift;
+			shift += 7;
+		} while ((c & 0x80) != 0);
+
+		if (includeHeader)
+			r.append("DELTA( BASE=" + baseLen + " RESULT=" + resLen + " )\n");
+
+		while (deltaPtr < delta.length) {
+			final int cmd = delta[deltaPtr++] & 0xff;
+			if ((cmd & 0x80) != 0) {
+				// Determine the segment of the base which should
+				// be copied into the output. The segment is given
+				// as an offset and a length.
+				//
+				int copyOffset = 0;
+				if ((cmd & 0x01) != 0)
+					copyOffset = delta[deltaPtr++] & 0xff;
+				if ((cmd & 0x02) != 0)
+					copyOffset |= (delta[deltaPtr++] & 0xff) << 8;
+				if ((cmd & 0x04) != 0)
+					copyOffset |= (delta[deltaPtr++] & 0xff) << 16;
+				if ((cmd & 0x08) != 0)
+					copyOffset |= (delta[deltaPtr++] & 0xff) << 24;
+
+				int copySize = 0;
+				if ((cmd & 0x10) != 0)
+					copySize = delta[deltaPtr++] & 0xff;
+				if ((cmd & 0x20) != 0)
+					copySize |= (delta[deltaPtr++] & 0xff) << 8;
+				if ((cmd & 0x40) != 0)
+					copySize |= (delta[deltaPtr++] & 0xff) << 16;
+				if (copySize == 0)
+					copySize = 0x10000;
+
+				r.append("  COPY  (" + copyOffset + ", " + copySize + ")\n");
+
+			} else if (cmd != 0) {
+				// Anything else the data is literal within the delta
+				// itself.
+				//
+				r.append("  INSERT(");
+				r.append(QuotedString.GIT_PATH.quote(RawParseUtils.decode(
+						delta, deltaPtr, deltaPtr + cmd)));
+				r.append(")\n");
+				deltaPtr += cmd;
+			} else {
+				// cmd == 0 has been reserved for future encoding but
+				// for now its not acceptable.
+				//
+				throw new IllegalArgumentException(
+						JGitText.get().unsupportedCommand0);
+			}
+		}
+
+		return r.toString();
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java
new file mode 100644
index 0000000..93eab19
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+
+class DeltaCache {
+	private final long size;
+
+	private final int entryLimit;
+
+	private final ReferenceQueue<byte[]> queue;
+
+	private long used;
+
+	DeltaCache(PackConfig pc) {
+		size = pc.getDeltaCacheSize();
+		entryLimit = pc.getDeltaCacheLimit();
+		queue = new ReferenceQueue<byte[]>();
+	}
+
+	boolean canCache(int length, ObjectToPack src, ObjectToPack res) {
+		// If the cache would overflow, don't store.
+		//
+		if (0 < size && size < used + length) {
+			checkForGarbageCollectedObjects();
+			if (0 < size && size < used + length)
+				return false;
+		}
+
+		if (length < entryLimit) {
+			used += length;
+			return true;
+		}
+
+		// If the combined source files are multiple megabytes but the delta
+		// is on the order of a kilobyte or two, this was likely costly to
+		// construct. Cache it anyway, even though its over the limit.
+		//
+		if (length >> 10 < (src.getWeight() >> 20) + (res.getWeight() >> 21)) {
+			used += length;
+			return true;
+		}
+
+		return false;
+	}
+
+	void credit(int reservedSize) {
+		used -= reservedSize;
+	}
+
+	Ref cache(byte[] data, int actLen, int reservedSize) {
+		// The caller may have had to allocate more space than is
+		// required. If we are about to waste anything, shrink it.
+		//
+		data = resize(data, actLen);
+
+		// When we reserved space for this item we did it for the
+		// inflated size of the delta, but we were just given the
+		// compressed version. Adjust the cache cost to match.
+		//
+		if (reservedSize != data.length) {
+			used -= reservedSize;
+			used += data.length;
+		}
+		return new Ref(data, queue);
+	}
+
+	byte[] resize(byte[] data, int actLen) {
+		if (data.length != actLen) {
+			byte[] nbuf = new byte[actLen];
+			System.arraycopy(data, 0, nbuf, 0, actLen);
+			data = nbuf;
+		}
+		return data;
+	}
+
+	private void checkForGarbageCollectedObjects() {
+		Ref r;
+		while ((r = (Ref) queue.poll()) != null)
+			used -= r.cost;
+	}
+
+	static class Ref extends SoftReference<byte[]> {
+		final int cost;
+
+		Ref(byte[] array, ReferenceQueue<byte[]> queue) {
+			super(array, queue);
+			cost = array.length;
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java
new file mode 100644
index 0000000..204030b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.lib.Constants;
+
+/** Encodes an instruction stream for {@link BinaryDelta}. */
+public class DeltaEncoder {
+	/**
+	 * Maximum number of bytes to be copied in pack v2 format.
+	 * <p>
+	 * Historical limitations have this at 64k, even though current delta
+	 * decoders recognize larger copy instructions.
+	 */
+	private static final int MAX_V2_COPY = 0x10000;
+
+	/*
+	 * Maximum number of bytes to be copied in pack v3 format.
+	 *
+	 * Current delta decoders can recognize a copy instruction with a count that
+	 * is this large, but the historical limitation of {@link MAX_V2_COPY} is
+	 * still used.
+	 */
+	// private static final int MAX_V3_COPY = (0xff << 16) | (0xff << 8) | 0xff;
+
+	/** Maximum number of bytes used by a copy instruction. */
+	private static final int MAX_COPY_CMD_SIZE = 8;
+
+	/** Maximum length that an an insert command can encode at once. */
+	private static final int MAX_INSERT_DATA_SIZE = 127;
+
+	private final OutputStream out;
+
+	private final byte[] buf = new byte[MAX_COPY_CMD_SIZE * 4];
+
+	private final int limit;
+
+	private int size;
+
+	/**
+	 * Create an encoder with no upper bound on the instruction stream size.
+	 *
+	 * @param out
+	 *            buffer to store the instructions written.
+	 * @param baseSize
+	 *            size of the base object, in bytes.
+	 * @param resultSize
+	 *            size of the resulting object, after applying this instruction
+	 *            stream to the base object, in bytes.
+	 * @throws IOException
+	 *             the output buffer cannot store the instruction stream's
+	 *             header with the size fields.
+	 */
+	public DeltaEncoder(OutputStream out, long baseSize, long resultSize)
+			throws IOException {
+		this(out, baseSize, resultSize, 0);
+	}
+
+	/**
+	 * Create an encoder with an upper limit on the instruction size.
+	 *
+	 * @param out
+	 *            buffer to store the instructions written.
+	 * @param baseSize
+	 *            size of the base object, in bytes.
+	 * @param resultSize
+	 *            size of the resulting object, after applying this instruction
+	 *            stream to the base object, in bytes.
+	 * @param limit
+	 *            maximum number of bytes to write to the out buffer declaring
+	 *            the stream is over limit and should be discarded. May be 0 to
+	 *            specify an infinite limit.
+	 * @throws IOException
+	 *             the output buffer cannot store the instruction stream's
+	 *             header with the size fields.
+	 */
+	public DeltaEncoder(OutputStream out, long baseSize, long resultSize,
+			int limit) throws IOException {
+		this.out = out;
+		this.limit = limit;
+		writeVarint(baseSize);
+		writeVarint(resultSize);
+	}
+
+	private void writeVarint(long sz) throws IOException {
+		int p = 0;
+		while (sz >= 0x80) {
+			buf[p++] = (byte) (0x80 | (((int) sz) & 0x7f));
+			sz >>>= 7;
+		}
+		buf[p++] = (byte) (((int) sz) & 0x7f);
+		size += p;
+		if (limit <= 0 || size < limit)
+			out.write(buf, 0, p);
+	}
+
+	/** @return current size of the delta stream, in bytes. */
+	public int getSize() {
+		return size;
+	}
+
+	/**
+	 * Insert a literal string of text, in UTF-8 encoding.
+	 *
+	 * @param text
+	 *            the string to insert.
+	 * @return true if the insert fits within the limit; false if the insert
+	 *         would cause the instruction stream to exceed the limit.
+	 * @throws IOException
+	 *             the instruction buffer can't store the instructions.
+	 */
+	public boolean insert(String text) throws IOException {
+		return insert(Constants.encode(text));
+	}
+
+	/**
+	 * Insert a literal binary sequence.
+	 *
+	 * @param text
+	 *            the binary to insert.
+	 * @return true if the insert fits within the limit; false if the insert
+	 *         would cause the instruction stream to exceed the limit.
+	 * @throws IOException
+	 *             the instruction buffer can't store the instructions.
+	 */
+	public boolean insert(byte[] text) throws IOException {
+		return insert(text, 0, text.length);
+	}
+
+	/**
+	 * Insert a literal binary sequence.
+	 *
+	 * @param text
+	 *            the binary to insert.
+	 * @param off
+	 *            offset within {@code text} to start copying from.
+	 * @param cnt
+	 *            number of bytes to insert.
+	 * @return true if the insert fits within the limit; false if the insert
+	 *         would cause the instruction stream to exceed the limit.
+	 * @throws IOException
+	 *             the instruction buffer can't store the instructions.
+	 */
+	public boolean insert(byte[] text, int off, int cnt)
+			throws IOException {
+		if (cnt <= 0)
+			return true;
+		if (0 < limit) {
+			int hdrs = cnt / MAX_INSERT_DATA_SIZE;
+			if (cnt % MAX_INSERT_DATA_SIZE != 0)
+				hdrs++;
+			if (limit < size + hdrs + cnt)
+				return false;
+		}
+		do {
+			int n = Math.min(MAX_INSERT_DATA_SIZE, cnt);
+			out.write((byte) n);
+			out.write(text, off, n);
+			off += n;
+			cnt -= n;
+			size += 1 + n;
+		} while (0 < cnt);
+		return true;
+	}
+
+	/**
+	 * Create a copy instruction to copy from the base object.
+	 *
+	 * @param offset
+	 *            position in the base object to copy from. This is absolute,
+	 *            from the beginning of the base.
+	 * @param cnt
+	 *            number of bytes to copy.
+	 * @return true if the copy fits within the limit; false if the copy
+	 *         would cause the instruction stream to exceed the limit.
+	 * @throws IOException
+	 *             the instruction buffer cannot store the instructions.
+	 */
+	public boolean copy(long offset, int cnt) throws IOException {
+		if (cnt == 0)
+			return true;
+
+		int p = 0;
+
+		// We cannot encode more than MAX_V2_COPY bytes in a single
+		// command, so encode that much and start a new command.
+		// This limit is imposed by the pack file format rules.
+		//
+		while (MAX_V2_COPY < cnt) {
+			p = encodeCopy(p, offset, MAX_V2_COPY);
+			offset += MAX_V2_COPY;
+			cnt -= MAX_V2_COPY;
+
+			if (buf.length < p + MAX_COPY_CMD_SIZE) {
+				if (0 < limit && limit < size + p)
+					return false;
+				out.write(buf, 0, p);
+				size += p;
+				p = 0;
+			}
+		}
+
+		p = encodeCopy(p, offset, cnt);
+		if (0 < limit && limit < size + p)
+			return false;
+		out.write(buf, 0, p);
+		size += p;
+		return true;
+	}
+
+	private int encodeCopy(int p, long offset, int cnt) {
+		int cmd = 0x80;
+		final int cmdPtr = p++; // save room for the command
+
+		if ((offset & 0xff) != 0) {
+			cmd |= 0x01;
+			buf[p++] = (byte) (offset & 0xff);
+		}
+		if ((offset & (0xff << 8)) != 0) {
+			cmd |= 0x02;
+			buf[p++] = (byte) ((offset >>> 8) & 0xff);
+		}
+		if ((offset & (0xff << 16)) != 0) {
+			cmd |= 0x04;
+			buf[p++] = (byte) ((offset >>> 16) & 0xff);
+		}
+		if ((offset & (0xff << 24)) != 0) {
+			cmd |= 0x08;
+			buf[p++] = (byte) ((offset >>> 24) & 0xff);
+		}
+
+		if (cnt != MAX_V2_COPY) {
+			if ((cnt & 0xff) != 0) {
+				cmd |= 0x10;
+				buf[p++] = (byte) (cnt & 0xff);
+			}
+			if ((cnt & (0xff << 8)) != 0) {
+				cmd |= 0x20;
+				buf[p++] = (byte) ((cnt >>> 8) & 0xff);
+			}
+			if ((cnt & (0xff << 16)) != 0) {
+				cmd |= 0x40;
+				buf[p++] = (byte) ((cnt >>> 16) & 0xff);
+			}
+		}
+
+		buf[cmdPtr] = (byte) cmd;
+		return p;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndex.java
new file mode 100644
index 0000000..e548cc9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndex.java
@@ -0,0 +1,586 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Index of blocks in a source file.
+ * <p>
+ * The index can be passed a result buffer, and output an instruction sequence
+ * that transforms the source buffer used by the index into the result buffer.
+ * The instruction sequence can be executed by {@link BinaryDelta} or
+ * {@link DeltaStream} to recreate the result buffer.
+ * <p>
+ * An index stores the entire contents of the source buffer, but also a table of
+ * block identities mapped to locations where the block appears in the source
+ * buffer. The mapping table uses 12 bytes for every 16 bytes of source buffer,
+ * and is therefore ~75% of the source buffer size. The overall index is ~1.75x
+ * the size of the source buffer. This relationship holds for any JVM, as only a
+ * constant number of objects are allocated per index. Callers can use the
+ * method {@link #getIndexSize()} to obtain a reasonably accurate estimate of
+ * the complete heap space used by this index.
+ * <p>
+ * A {@code DeltaIndex} is thread-safe. Concurrent threads can use the same
+ * index to encode delta instructions for different result buffers.
+ */
+public class DeltaIndex {
+	/** Number of bytes in a block. */
+	static final int BLKSZ = 16; // must be 16, see unrolled loop in hashBlock
+
+	/**
+	 * Estimate the size of an index for a given source.
+	 * <p>
+	 * This is roughly a worst-case estimate. The actual index may be smaller.
+	 *
+	 * @param sourceLength
+	 *            length of the source, in bytes.
+	 * @return estimated size. Approximately {@code 1.75 * sourceLength}.
+	 */
+	public static long estimateIndexSize(int sourceLength) {
+		return sourceLength + (sourceLength * 3 / 4);
+	}
+
+	/**
+	 * Maximum number of positions to consider for a given content hash.
+	 * <p>
+	 * All positions with the same content hash are stored into a single chain.
+	 * The chain size is capped to ensure delta encoding stays linear time at
+	 * O(len_src + len_dst) rather than quadratic at O(len_src * len_dst).
+	 */
+	private static final int MAX_CHAIN_LENGTH = 64;
+
+	/** Original source file that we indexed. */
+	private final byte[] src;
+
+	/**
+	 * Pointers into the {@link #entries} table, indexed by block hash.
+	 * <p>
+	 * A block hash is masked with {@link #tableMask} to become the array index
+	 * of this table. The value stored here is the first index within
+	 * {@link #entries} that starts the consecutive list of blocks with that
+	 * same masked hash. If there are no matching blocks, 0 is stored instead.
+	 * <p>
+	 * Note that this table is always a power of 2 in size, to support fast
+	 * normalization of a block hash into an array index.
+	 */
+	private final int[] table;
+
+	/**
+	 * Pairs of block hash value and {@link #src} offsets.
+	 * <p>
+	 * The very first entry in this table at index 0 is always empty, this is to
+	 * allow fast evaluation when {@link #table} has no values under any given
+	 * slot. Remaining entries are pairs of integers, with the upper 32 bits
+	 * holding the block hash and the lower 32 bits holding the source offset.
+	 */
+	private final long[] entries;
+
+	/** Mask to make block hashes into an array index for {@link #table}. */
+	private final int tableMask;
+
+	/**
+	 * Construct an index from the source file.
+	 *
+	 * @param sourceBuffer
+	 *            the source file's raw contents. The buffer will be held by the
+	 *            index instance to facilitate matching, and therefore must not
+	 *            be modified by the caller.
+	 */
+	public DeltaIndex(byte[] sourceBuffer) {
+		src = sourceBuffer;
+
+		DeltaIndexScanner scan = new DeltaIndexScanner(src, src.length);
+
+		// Reuse the same table the scanner made. We will replace the
+		// values at each position, but we want the same-length array.
+		//
+		table = scan.table;
+		tableMask = scan.tableMask;
+
+		// Because entry index 0 means there are no entries for the
+		// slot in the table, we have to allocate one extra position.
+		//
+		entries = new long[1 + countEntries(scan)];
+		copyEntries(scan);
+	}
+
+	private int countEntries(DeltaIndexScanner scan) {
+		// Figure out exactly how many entries we need. As we do the
+		// enumeration truncate any delta chains longer than what we
+		// are willing to scan during encode. This keeps the encode
+		// logic linear in the size of the input rather than quadratic.
+		//
+		int cnt = 0;
+		for (int i = 0; i < table.length; i++) {
+			int h = table[i];
+			if (h == 0)
+				continue;
+
+			int len = 0;
+			do {
+				if (++len == MAX_CHAIN_LENGTH) {
+					scan.next[h] = 0;
+					break;
+				}
+				h = scan.next[h];
+			} while (h != 0);
+			cnt += len;
+		}
+		return cnt;
+	}
+
+	private void copyEntries(DeltaIndexScanner scan) {
+		// Rebuild the entries list from the scanner, positioning all
+		// blocks in the same hash chain next to each other. We can
+		// then later discard the next list, along with the scanner.
+		//
+		int next = 1;
+		for (int i = 0; i < table.length; i++) {
+			int h = table[i];
+			if (h == 0)
+				continue;
+
+			table[i] = next;
+			do {
+				entries[next++] = scan.entries[h];
+				h = scan.next[h];
+			} while (h != 0);
+		}
+	}
+
+	/** @return size of the source buffer this index has scanned. */
+	public long getSourceSize() {
+		return src.length;
+	}
+
+	/**
+	 * Get an estimate of the memory required by this index.
+	 *
+	 * @return an approximation of the number of bytes used by this index in
+	 *         memory. The size includes the cached source buffer size from
+	 *         {@link #getSourceSize()}, as well as a rough approximation of JVM
+	 *         object overheads.
+	 */
+	public long getIndexSize() {
+		long sz = 8 /* object header */;
+		sz += 4 /* fields */* 4 /* guessed size per field */;
+		sz += sizeOf(src);
+		sz += sizeOf(table);
+		sz += sizeOf(entries);
+		return sz;
+	}
+
+	private static long sizeOf(byte[] b) {
+		return sizeOfArray(1, b.length);
+	}
+
+	private static long sizeOf(int[] b) {
+		return sizeOfArray(4, b.length);
+	}
+
+	private static long sizeOf(long[] b) {
+		return sizeOfArray(8, b.length);
+	}
+
+	private static int sizeOfArray(int entSize, int len) {
+		return 12 /* estimated array header size */+ (len * entSize);
+	}
+
+	/**
+	 * Generate a delta sequence to recreate the result buffer.
+	 * <p>
+	 * There is no limit on the size of the delta sequence created. This is the
+	 * same as {@code encode(out, res, 0)}.
+	 *
+	 * @param out
+	 *            stream to receive the delta instructions that can transform
+	 *            this index's source buffer into {@code res}. This stream
+	 *            should be buffered, as instructions are written directly to it
+	 *            in small bursts.
+	 * @param res
+	 *            the desired result buffer. The generated instructions will
+	 *            recreate this buffer when applied to the source buffer stored
+	 *            within this index.
+	 * @throws IOException
+	 *             the output stream refused to write the instructions.
+	 */
+	public void encode(OutputStream out, byte[] res) throws IOException {
+		encode(out, res, 0 /* no limit */);
+	}
+
+	/**
+	 * Generate a delta sequence to recreate the result buffer.
+	 *
+	 * @param out
+	 *            stream to receive the delta instructions that can transform
+	 *            this index's source buffer into {@code res}. This stream
+	 *            should be buffered, as instructions are written directly to it
+	 *            in small bursts. If the caller might need to discard the
+	 *            instructions (such as when deltaSizeLimit would be exceeded)
+	 *            the caller is responsible for discarding or rewinding the
+	 *            stream when this method returns false.
+	 * @param res
+	 *            the desired result buffer. The generated instructions will
+	 *            recreate this buffer when applied to the source buffer stored
+	 *            within this index.
+	 * @param deltaSizeLimit
+	 *            maximum number of bytes that the delta instructions can
+	 *            occupy. If the generated instructions would be longer than
+	 *            this amount, this method returns false. If 0, there is no
+	 *            limit on the length of delta created.
+	 * @return true if the delta is smaller than deltaSizeLimit; false if the
+	 *         encoder aborted because the encoded delta instructions would be
+	 *         longer than deltaSizeLimit bytes.
+	 * @throws IOException
+	 *             the output stream refused to write the instructions.
+	 */
+	public boolean encode(OutputStream out, byte[] res, int deltaSizeLimit)
+			throws IOException {
+		final int end = res.length;
+		final DeltaEncoder enc = newEncoder(out, end, deltaSizeLimit);
+
+		// If either input is smaller than one full block, we simply punt
+		// and construct a delta as a literal. This implies that any file
+		// smaller than our block size is never delta encoded as the delta
+		// will always be larger than the file itself would be.
+		//
+		if (end < BLKSZ || table.length == 0)
+			return enc.insert(res);
+
+		// Bootstrap the scan by constructing a hash for the first block
+		// in the input.
+		//
+		int blkPtr = 0;
+		int blkEnd = BLKSZ;
+		int hash = hashBlock(res, 0);
+
+		int resPtr = 0;
+		while (blkEnd < end) {
+			final int tableIdx = hash & tableMask;
+			int entryIdx = table[tableIdx];
+			if (entryIdx == 0) {
+				// No matching blocks, slide forward one byte.
+				//
+				hash = step(hash, res[blkPtr++], res[blkEnd++]);
+				continue;
+			}
+
+			// For every possible location of the current block, try to
+			// extend the match out to the longest common substring.
+			//
+			int bestLen = -1;
+			int bestPtr = -1;
+			int bestNeg = 0;
+			do {
+				long ent = entries[entryIdx++];
+				if (keyOf(ent) == hash) {
+					int neg = 0;
+					if (resPtr < blkPtr) {
+						// If we need to do an insertion, check to see if
+						// moving the starting point of the copy backwards
+						// will allow us to shorten the insert. Our hash
+						// may not have allowed us to identify this area.
+						// Since it is quite fast to perform a negative
+						// scan, try to stretch backwards too.
+						//
+						neg = blkPtr - resPtr;
+						neg = negmatch(res, blkPtr, src, valOf(ent), neg);
+					}
+
+					int len = neg + fwdmatch(res, blkPtr, src, valOf(ent));
+					if (bestLen < len) {
+						bestLen = len;
+						bestPtr = valOf(ent);
+						bestNeg = neg;
+					}
+				} else if ((keyOf(ent) & tableMask) != tableIdx)
+					break;
+			} while (bestLen < 4096 && entryIdx < entries.length);
+
+			if (bestLen < BLKSZ) {
+				// All of the locations were false positives, or the copy
+				// is shorter than a block. In the latter case this won't
+				// give us a very great copy instruction, so delay and try
+				// at the next byte.
+				//
+				hash = step(hash, res[blkPtr++], res[blkEnd++]);
+				continue;
+			}
+
+			blkPtr -= bestNeg;
+
+			if (resPtr < blkPtr) {
+				// There are bytes between the last instruction we made
+				// and the current block pointer. None of these matched
+				// during the earlier iteration so insert them directly
+				// into the instruction stream.
+				//
+				int cnt = blkPtr - resPtr;
+				if (!enc.insert(res, resPtr, cnt))
+					return false;
+			}
+
+			if (!enc.copy(bestPtr - bestNeg, bestLen))
+				return false;
+
+			blkPtr += bestLen;
+			resPtr = blkPtr;
+			blkEnd = blkPtr + BLKSZ;
+
+			// If we don't have a full block available to us, abort now.
+			//
+			if (end <= blkEnd)
+				break;
+
+			// Start a new hash of the block after the copy region.
+			//
+			hash = hashBlock(res, blkPtr);
+		}
+
+		if (resPtr < end) {
+			// There were bytes at the end which didn't match, or maybe
+			// didn't make a full block. Insert whatever is left over.
+			//
+			int cnt = end - resPtr;
+			return enc.insert(res, resPtr, cnt);
+		}
+		return true;
+	}
+
+	private DeltaEncoder newEncoder(OutputStream out, long resSize, int limit)
+			throws IOException {
+		return new DeltaEncoder(out, getSourceSize(), resSize, limit);
+	}
+
+	private static int fwdmatch(byte[] res, int resPtr, byte[] src, int srcPtr) {
+		int start = resPtr;
+		for (; resPtr < res.length && srcPtr < src.length; resPtr++, srcPtr++) {
+			if (res[resPtr] != src[srcPtr])
+				break;
+		}
+		return resPtr - start;
+	}
+
+	private static int negmatch(byte[] res, int resPtr, byte[] src, int srcPtr,
+			int limit) {
+		if (srcPtr == 0)
+			return 0;
+
+		resPtr--;
+		srcPtr--;
+		int start = resPtr;
+		do {
+			if (res[resPtr] != src[srcPtr])
+				break;
+			resPtr--;
+			srcPtr--;
+		} while (0 <= srcPtr && 0 < --limit);
+		return start - resPtr;
+	}
+
+	public String toString() {
+		String[] units = { "bytes", "KiB", "MiB", "GiB" };
+		long sz = getIndexSize();
+		int u = 0;
+		while (1024 <= sz && u < units.length - 1) {
+			int rem = (int) (sz % 1024);
+			sz /= 1024;
+			if (rem != 0)
+				sz++;
+			u++;
+		}
+		return "DeltaIndex[" + sz + " " + units[u] + "]";
+	}
+
+	static int hashBlock(byte[] raw, int ptr) {
+		int hash;
+
+		// The first 4 steps collapse out into a 4 byte big-endian decode,
+		// with a larger right shift as we combined shift lefts together.
+		//
+		hash = ((raw[ptr] & 0xff) << 24) //
+				| ((raw[ptr + 1] & 0xff) << 16) //
+				| ((raw[ptr + 2] & 0xff) << 8) //
+				| (raw[ptr + 3] & 0xff);
+		hash ^= T[hash >>> 31];
+
+		hash = ((hash << 8) | (raw[ptr + 4] & 0xff)) ^ T[hash >>> 23];
+		hash = ((hash << 8) | (raw[ptr + 5] & 0xff)) ^ T[hash >>> 23];
+		hash = ((hash << 8) | (raw[ptr + 6] & 0xff)) ^ T[hash >>> 23];
+		hash = ((hash << 8) | (raw[ptr + 7] & 0xff)) ^ T[hash >>> 23];
+
+		hash = ((hash << 8) | (raw[ptr + 8] & 0xff)) ^ T[hash >>> 23];
+		hash = ((hash << 8) | (raw[ptr + 9] & 0xff)) ^ T[hash >>> 23];
+		hash = ((hash << 8) | (raw[ptr + 10] & 0xff)) ^ T[hash >>> 23];
+		hash = ((hash << 8) | (raw[ptr + 11] & 0xff)) ^ T[hash >>> 23];
+
+		hash = ((hash << 8) | (raw[ptr + 12] & 0xff)) ^ T[hash >>> 23];
+		hash = ((hash << 8) | (raw[ptr + 13] & 0xff)) ^ T[hash >>> 23];
+		hash = ((hash << 8) | (raw[ptr + 14] & 0xff)) ^ T[hash >>> 23];
+		hash = ((hash << 8) | (raw[ptr + 15] & 0xff)) ^ T[hash >>> 23];
+
+		return hash;
+	}
+
+	private static int step(int hash, byte toRemove, byte toAdd) {
+		hash ^= U[toRemove & 0xff];
+		return ((hash << 8) | (toAdd & 0xff)) ^ T[hash >>> 23];
+	}
+
+	private static int keyOf(long ent) {
+		return (int) (ent >>> 32);
+	}
+
+	private static int valOf(long ent) {
+		return (int) ent;
+	}
+
+	private static final int[] T = { 0x00000000, 0xd4c6b32d, 0x7d4bd577,
+			0xa98d665a, 0x2e5119c3, 0xfa97aaee, 0x531accb4, 0x87dc7f99,
+			0x5ca23386, 0x886480ab, 0x21e9e6f1, 0xf52f55dc, 0x72f32a45,
+			0xa6359968, 0x0fb8ff32, 0xdb7e4c1f, 0x6d82d421, 0xb944670c,
+			0x10c90156, 0xc40fb27b, 0x43d3cde2, 0x97157ecf, 0x3e981895,
+			0xea5eabb8, 0x3120e7a7, 0xe5e6548a, 0x4c6b32d0, 0x98ad81fd,
+			0x1f71fe64, 0xcbb74d49, 0x623a2b13, 0xb6fc983e, 0x0fc31b6f,
+			0xdb05a842, 0x7288ce18, 0xa64e7d35, 0x219202ac, 0xf554b181,
+			0x5cd9d7db, 0x881f64f6, 0x536128e9, 0x87a79bc4, 0x2e2afd9e,
+			0xfaec4eb3, 0x7d30312a, 0xa9f68207, 0x007be45d, 0xd4bd5770,
+			0x6241cf4e, 0xb6877c63, 0x1f0a1a39, 0xcbcca914, 0x4c10d68d,
+			0x98d665a0, 0x315b03fa, 0xe59db0d7, 0x3ee3fcc8, 0xea254fe5,
+			0x43a829bf, 0x976e9a92, 0x10b2e50b, 0xc4745626, 0x6df9307c,
+			0xb93f8351, 0x1f8636de, 0xcb4085f3, 0x62cde3a9, 0xb60b5084,
+			0x31d72f1d, 0xe5119c30, 0x4c9cfa6a, 0x985a4947, 0x43240558,
+			0x97e2b675, 0x3e6fd02f, 0xeaa96302, 0x6d751c9b, 0xb9b3afb6,
+			0x103ec9ec, 0xc4f87ac1, 0x7204e2ff, 0xa6c251d2, 0x0f4f3788,
+			0xdb8984a5, 0x5c55fb3c, 0x88934811, 0x211e2e4b, 0xf5d89d66,
+			0x2ea6d179, 0xfa606254, 0x53ed040e, 0x872bb723, 0x00f7c8ba,
+			0xd4317b97, 0x7dbc1dcd, 0xa97aaee0, 0x10452db1, 0xc4839e9c,
+			0x6d0ef8c6, 0xb9c84beb, 0x3e143472, 0xead2875f, 0x435fe105,
+			0x97995228, 0x4ce71e37, 0x9821ad1a, 0x31accb40, 0xe56a786d,
+			0x62b607f4, 0xb670b4d9, 0x1ffdd283, 0xcb3b61ae, 0x7dc7f990,
+			0xa9014abd, 0x008c2ce7, 0xd44a9fca, 0x5396e053, 0x8750537e,
+			0x2edd3524, 0xfa1b8609, 0x2165ca16, 0xf5a3793b, 0x5c2e1f61,
+			0x88e8ac4c, 0x0f34d3d5, 0xdbf260f8, 0x727f06a2, 0xa6b9b58f,
+			0x3f0c6dbc, 0xebcade91, 0x4247b8cb, 0x96810be6, 0x115d747f,
+			0xc59bc752, 0x6c16a108, 0xb8d01225, 0x63ae5e3a, 0xb768ed17,
+			0x1ee58b4d, 0xca233860, 0x4dff47f9, 0x9939f4d4, 0x30b4928e,
+			0xe47221a3, 0x528eb99d, 0x86480ab0, 0x2fc56cea, 0xfb03dfc7,
+			0x7cdfa05e, 0xa8191373, 0x01947529, 0xd552c604, 0x0e2c8a1b,
+			0xdaea3936, 0x73675f6c, 0xa7a1ec41, 0x207d93d8, 0xf4bb20f5,
+			0x5d3646af, 0x89f0f582, 0x30cf76d3, 0xe409c5fe, 0x4d84a3a4,
+			0x99421089, 0x1e9e6f10, 0xca58dc3d, 0x63d5ba67, 0xb713094a,
+			0x6c6d4555, 0xb8abf678, 0x11269022, 0xc5e0230f, 0x423c5c96,
+			0x96faefbb, 0x3f7789e1, 0xebb13acc, 0x5d4da2f2, 0x898b11df,
+			0x20067785, 0xf4c0c4a8, 0x731cbb31, 0xa7da081c, 0x0e576e46,
+			0xda91dd6b, 0x01ef9174, 0xd5292259, 0x7ca44403, 0xa862f72e,
+			0x2fbe88b7, 0xfb783b9a, 0x52f55dc0, 0x8633eeed, 0x208a5b62,
+			0xf44ce84f, 0x5dc18e15, 0x89073d38, 0x0edb42a1, 0xda1df18c,
+			0x739097d6, 0xa75624fb, 0x7c2868e4, 0xa8eedbc9, 0x0163bd93,
+			0xd5a50ebe, 0x52797127, 0x86bfc20a, 0x2f32a450, 0xfbf4177d,
+			0x4d088f43, 0x99ce3c6e, 0x30435a34, 0xe485e919, 0x63599680,
+			0xb79f25ad, 0x1e1243f7, 0xcad4f0da, 0x11aabcc5, 0xc56c0fe8,
+			0x6ce169b2, 0xb827da9f, 0x3ffba506, 0xeb3d162b, 0x42b07071,
+			0x9676c35c, 0x2f49400d, 0xfb8ff320, 0x5202957a, 0x86c42657,
+			0x011859ce, 0xd5deeae3, 0x7c538cb9, 0xa8953f94, 0x73eb738b,
+			0xa72dc0a6, 0x0ea0a6fc, 0xda6615d1, 0x5dba6a48, 0x897cd965,
+			0x20f1bf3f, 0xf4370c12, 0x42cb942c, 0x960d2701, 0x3f80415b,
+			0xeb46f276, 0x6c9a8def, 0xb85c3ec2, 0x11d15898, 0xc517ebb5,
+			0x1e69a7aa, 0xcaaf1487, 0x632272dd, 0xb7e4c1f0, 0x3038be69,
+			0xe4fe0d44, 0x4d736b1e, 0x99b5d833 };
+
+	private static final int[] U = { 0x00000000, 0x12c6e90f, 0x258dd21e,
+			0x374b3b11, 0x4b1ba43c, 0x59dd4d33, 0x6e967622, 0x7c509f2d,
+			0x42f1fb55, 0x5037125a, 0x677c294b, 0x75bac044, 0x09ea5f69,
+			0x1b2cb666, 0x2c678d77, 0x3ea16478, 0x51254587, 0x43e3ac88,
+			0x74a89799, 0x666e7e96, 0x1a3ee1bb, 0x08f808b4, 0x3fb333a5,
+			0x2d75daaa, 0x13d4bed2, 0x011257dd, 0x36596ccc, 0x249f85c3,
+			0x58cf1aee, 0x4a09f3e1, 0x7d42c8f0, 0x6f8421ff, 0x768c3823,
+			0x644ad12c, 0x5301ea3d, 0x41c70332, 0x3d979c1f, 0x2f517510,
+			0x181a4e01, 0x0adca70e, 0x347dc376, 0x26bb2a79, 0x11f01168,
+			0x0336f867, 0x7f66674a, 0x6da08e45, 0x5aebb554, 0x482d5c5b,
+			0x27a97da4, 0x356f94ab, 0x0224afba, 0x10e246b5, 0x6cb2d998,
+			0x7e743097, 0x493f0b86, 0x5bf9e289, 0x655886f1, 0x779e6ffe,
+			0x40d554ef, 0x5213bde0, 0x2e4322cd, 0x3c85cbc2, 0x0bcef0d3,
+			0x190819dc, 0x39dec36b, 0x2b182a64, 0x1c531175, 0x0e95f87a,
+			0x72c56757, 0x60038e58, 0x5748b549, 0x458e5c46, 0x7b2f383e,
+			0x69e9d131, 0x5ea2ea20, 0x4c64032f, 0x30349c02, 0x22f2750d,
+			0x15b94e1c, 0x077fa713, 0x68fb86ec, 0x7a3d6fe3, 0x4d7654f2,
+			0x5fb0bdfd, 0x23e022d0, 0x3126cbdf, 0x066df0ce, 0x14ab19c1,
+			0x2a0a7db9, 0x38cc94b6, 0x0f87afa7, 0x1d4146a8, 0x6111d985,
+			0x73d7308a, 0x449c0b9b, 0x565ae294, 0x4f52fb48, 0x5d941247,
+			0x6adf2956, 0x7819c059, 0x04495f74, 0x168fb67b, 0x21c48d6a,
+			0x33026465, 0x0da3001d, 0x1f65e912, 0x282ed203, 0x3ae83b0c,
+			0x46b8a421, 0x547e4d2e, 0x6335763f, 0x71f39f30, 0x1e77becf,
+			0x0cb157c0, 0x3bfa6cd1, 0x293c85de, 0x556c1af3, 0x47aaf3fc,
+			0x70e1c8ed, 0x622721e2, 0x5c86459a, 0x4e40ac95, 0x790b9784,
+			0x6bcd7e8b, 0x179de1a6, 0x055b08a9, 0x321033b8, 0x20d6dab7,
+			0x73bd86d6, 0x617b6fd9, 0x563054c8, 0x44f6bdc7, 0x38a622ea,
+			0x2a60cbe5, 0x1d2bf0f4, 0x0fed19fb, 0x314c7d83, 0x238a948c,
+			0x14c1af9d, 0x06074692, 0x7a57d9bf, 0x689130b0, 0x5fda0ba1,
+			0x4d1ce2ae, 0x2298c351, 0x305e2a5e, 0x0715114f, 0x15d3f840,
+			0x6983676d, 0x7b458e62, 0x4c0eb573, 0x5ec85c7c, 0x60693804,
+			0x72afd10b, 0x45e4ea1a, 0x57220315, 0x2b729c38, 0x39b47537,
+			0x0eff4e26, 0x1c39a729, 0x0531bef5, 0x17f757fa, 0x20bc6ceb,
+			0x327a85e4, 0x4e2a1ac9, 0x5cecf3c6, 0x6ba7c8d7, 0x796121d8,
+			0x47c045a0, 0x5506acaf, 0x624d97be, 0x708b7eb1, 0x0cdbe19c,
+			0x1e1d0893, 0x29563382, 0x3b90da8d, 0x5414fb72, 0x46d2127d,
+			0x7199296c, 0x635fc063, 0x1f0f5f4e, 0x0dc9b641, 0x3a828d50,
+			0x2844645f, 0x16e50027, 0x0423e928, 0x3368d239, 0x21ae3b36,
+			0x5dfea41b, 0x4f384d14, 0x78737605, 0x6ab59f0a, 0x4a6345bd,
+			0x58a5acb2, 0x6fee97a3, 0x7d287eac, 0x0178e181, 0x13be088e,
+			0x24f5339f, 0x3633da90, 0x0892bee8, 0x1a5457e7, 0x2d1f6cf6,
+			0x3fd985f9, 0x43891ad4, 0x514ff3db, 0x6604c8ca, 0x74c221c5,
+			0x1b46003a, 0x0980e935, 0x3ecbd224, 0x2c0d3b2b, 0x505da406,
+			0x429b4d09, 0x75d07618, 0x67169f17, 0x59b7fb6f, 0x4b711260,
+			0x7c3a2971, 0x6efcc07e, 0x12ac5f53, 0x006ab65c, 0x37218d4d,
+			0x25e76442, 0x3cef7d9e, 0x2e299491, 0x1962af80, 0x0ba4468f,
+			0x77f4d9a2, 0x653230ad, 0x52790bbc, 0x40bfe2b3, 0x7e1e86cb,
+			0x6cd86fc4, 0x5b9354d5, 0x4955bdda, 0x350522f7, 0x27c3cbf8,
+			0x1088f0e9, 0x024e19e6, 0x6dca3819, 0x7f0cd116, 0x4847ea07,
+			0x5a810308, 0x26d19c25, 0x3417752a, 0x035c4e3b, 0x119aa734,
+			0x2f3bc34c, 0x3dfd2a43, 0x0ab61152, 0x1870f85d, 0x64206770,
+			0x76e68e7f, 0x41adb56e, 0x536b5c61 };
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndexScanner.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndexScanner.java
new file mode 100644
index 0000000..d30690d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndexScanner.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+/**
+ * Supports {@link DeltaIndex} by performing a partial scan of the content.
+ */
+class DeltaIndexScanner {
+	final int[] table;
+
+	// To save memory the buckets for hash chains are stored in correlated
+	// arrays. This permits us to get 3 values per entry, without paying
+	// the penalty for an object header on each entry.
+
+	final long[] entries;
+
+	final int[] next;
+
+	final int tableMask;
+
+	private int entryCnt;
+
+	DeltaIndexScanner(byte[] raw, int len) {
+		// Clip the length so it falls on a block boundary. We won't
+		// bother to scan the final partial block.
+		//
+		len -= (len % DeltaIndex.BLKSZ);
+
+		final int worstCaseBlockCnt = len / DeltaIndex.BLKSZ;
+		if (worstCaseBlockCnt < 1) {
+			table = new int[] {};
+			tableMask = 0;
+
+			entries = new long[] {};
+			next = new int[] {};
+
+		} else {
+			table = new int[tableSize(worstCaseBlockCnt)];
+			tableMask = table.length - 1;
+
+			// As we insert blocks we preincrement so that 0 is never a
+			// valid entry. Therefore we have to allocate one extra space.
+			//
+			entries = new long[1 + worstCaseBlockCnt];
+			next = new int[entries.length];
+
+			scan(raw, len);
+		}
+	}
+
+	private void scan(byte[] raw, final int end) {
+		// We scan the input backwards, and always insert onto the
+		// front of the chain. This ensures that chains will have lower
+		// offsets at the front of the chain, allowing us to prefer the
+		// earlier match rather than the later match.
+		//
+		int lastHash = 0;
+		int ptr = end - DeltaIndex.BLKSZ;
+		do {
+			final int key = DeltaIndex.hashBlock(raw, ptr);
+			final int tIdx = key & tableMask;
+
+			final int head = table[tIdx];
+			if (head != 0 && lastHash == key) {
+				// Two consecutive blocks have the same content hash,
+				// prefer the earlier block because we want to use the
+				// longest sequence we can during encoding.
+				//
+				entries[head] = (((long) key) << 32) | ptr;
+			} else {
+				final int eIdx = ++entryCnt;
+				entries[eIdx] = (((long) key) << 32) | ptr;
+				next[eIdx] = head;
+				table[tIdx] = eIdx;
+			}
+
+			lastHash = key;
+			ptr -= DeltaIndex.BLKSZ;
+		} while (0 <= ptr);
+	}
+
+	private static int tableSize(final int worstCaseBlockCnt) {
+		int shift = 32 - Integer.numberOfLeadingZeros(worstCaseBlockCnt);
+		int sz = 1 << (shift - 1);
+		if (sz < worstCaseBlockCnt)
+			sz <<= 1;
+		return sz;
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaStream.java
new file mode 100644
index 0000000..6f479eb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaStream.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.util.IO;
+
+/**
+ * Inflates a delta in an incremental way.
+ * <p>
+ * Implementations must provide a means to access a stream for the base object.
+ * This stream may be accessed multiple times, in order to randomly position it
+ * to match the copy instructions. A {@code DeltaStream} performs an efficient
+ * skip by only moving through the delta stream, making restarts of stacked
+ * deltas reasonably efficient.
+ */
+public abstract class DeltaStream extends InputStream {
+	private static final int CMD_COPY = 0;
+
+	private static final int CMD_INSERT = 1;
+
+	private static final int CMD_EOF = 2;
+
+	private final InputStream deltaStream;
+
+	private long baseSize;
+
+	private long resultSize;
+
+	private final byte[] cmdbuf = new byte[512];
+
+	private int cmdptr;
+
+	private int cmdcnt;
+
+	/** Stream to read from the base object. */
+	private InputStream baseStream;
+
+	/** Current position within {@link #baseStream}. */
+	private long baseOffset;
+
+	private int curcmd;
+
+	/** If {@code curcmd == CMD_COPY}, position the base has to be at. */
+	private long copyOffset;
+
+	/** Total number of bytes in this current command. */
+	private int copySize;
+
+	/**
+	 * Construct a delta application stream, reading instructions.
+	 *
+	 * @param deltaStream
+	 *            the stream to read delta instructions from.
+	 * @throws IOException
+	 *             the delta instruction stream cannot be read, or is
+	 *             inconsistent with the the base object information.
+	 */
+	public DeltaStream(final InputStream deltaStream) throws IOException {
+		this.deltaStream = deltaStream;
+		if (!fill(cmdbuf.length))
+			throw new EOFException();
+
+		// Length of the base object.
+		//
+		int c, shift = 0;
+		do {
+			c = cmdbuf[cmdptr++] & 0xff;
+			baseSize |= (c & 0x7f) << shift;
+			shift += 7;
+		} while ((c & 0x80) != 0);
+
+		// Length of the resulting object.
+		//
+		shift = 0;
+		do {
+			c = cmdbuf[cmdptr++] & 0xff;
+			resultSize |= (c & 0x7f) << shift;
+			shift += 7;
+		} while ((c & 0x80) != 0);
+
+		curcmd = next();
+	}
+
+	/**
+	 * Open the base stream.
+	 * <p>
+	 * The {@code DeltaStream} may close and reopen the base stream multiple
+	 * times if copy instructions use offsets out of order. This can occur if a
+	 * large block in the file was moved from near the top, to near the bottom.
+	 * In such cases the reopened stream is skipped to the target offset, so
+	 * {@code skip(long)} should be as efficient as possible.
+	 *
+	 * @return stream to read from the base object. This stream should not be
+	 *         buffered (or should be only minimally buffered), and does not
+	 *         need to support mark/reset.
+	 * @throws IOException
+	 *             the base object cannot be opened for reading.
+	 */
+	protected abstract InputStream openBase() throws IOException;
+
+	/**
+	 * @return length of the base object, in bytes.
+	 * @throws IOException
+	 *             the length of the base cannot be determined.
+	 */
+	protected abstract long getBaseSize() throws IOException;
+
+	/** @return total size of this stream, in bytes. */
+	public long getSize() {
+		return resultSize;
+	}
+
+	@Override
+	public int read() throws IOException {
+		byte[] buf = new byte[1];
+		int n = read(buf, 0, 1);
+		return n == 1 ? buf[0] & 0xff : -1;
+	}
+
+	@Override
+	public void close() throws IOException {
+		deltaStream.close();
+		if (baseStream != null)
+			baseStream.close();
+	}
+
+	@Override
+	public long skip(long len) throws IOException {
+		long act = 0;
+		while (0 < len) {
+			long n = Math.min(len, copySize);
+			switch (curcmd) {
+			case CMD_COPY:
+				copyOffset += n;
+				break;
+
+			case CMD_INSERT:
+				cmdptr += n;
+				break;
+
+			case CMD_EOF:
+				return act;
+			default:
+				throw new CorruptObjectException(
+						JGitText.get().unsupportedCommand0);
+			}
+
+			act += n;
+			len -= n;
+			copySize -= n;
+			if (copySize == 0)
+				curcmd = next();
+		}
+		return act;
+	}
+
+	@Override
+	public int read(byte[] buf, int off, int len) throws IOException {
+		int act = 0;
+		while (0 < len) {
+			int n = Math.min(len, copySize);
+			switch (curcmd) {
+			case CMD_COPY:
+				seekBase();
+				n = baseStream.read(buf, off, n);
+				if (n < 0)
+					throw new CorruptObjectException(
+							JGitText.get().baseLengthIncorrect);
+				baseOffset += n;
+				break;
+
+			case CMD_INSERT:
+				System.arraycopy(cmdbuf, cmdptr, buf, off, n);
+				cmdptr += n;
+				break;
+
+			case CMD_EOF:
+				return 0 < act ? act : -1;
+			default:
+				throw new CorruptObjectException(
+						JGitText.get().unsupportedCommand0);
+			}
+
+			act += n;
+			off += n;
+			len -= n;
+			copySize -= n;
+			if (copySize == 0)
+				curcmd = next();
+		}
+		return act;
+	}
+
+	private boolean fill(final int need) throws IOException {
+		int n = have();
+		if (need < n)
+			return true;
+		if (n == 0) {
+			cmdptr = 0;
+			cmdcnt = 0;
+		} else if (cmdbuf.length - cmdptr < need) {
+			// There isn't room for the entire worst-case copy command,
+			// so shift the array down to make sure we can use the entire
+			// command without having it span across the end of the array.
+			//
+			System.arraycopy(cmdbuf, cmdptr, cmdbuf, 0, n);
+			cmdptr = 0;
+			cmdcnt = n;
+		}
+
+		do {
+			n = deltaStream.read(cmdbuf, cmdcnt, cmdbuf.length - cmdcnt);
+			if (n < 0)
+				return 0 < have();
+			cmdcnt += n;
+		} while (cmdcnt < cmdbuf.length);
+		return true;
+	}
+
+	private int next() throws IOException {
+		if (!fill(8))
+			return CMD_EOF;
+
+		final int cmd = cmdbuf[cmdptr++] & 0xff;
+		if ((cmd & 0x80) != 0) {
+			// Determine the segment of the base which should
+			// be copied into the output. The segment is given
+			// as an offset and a length.
+			//
+			copyOffset = 0;
+			if ((cmd & 0x01) != 0)
+				copyOffset = cmdbuf[cmdptr++] & 0xff;
+			if ((cmd & 0x02) != 0)
+				copyOffset |= (cmdbuf[cmdptr++] & 0xff) << 8;
+			if ((cmd & 0x04) != 0)
+				copyOffset |= (cmdbuf[cmdptr++] & 0xff) << 16;
+			if ((cmd & 0x08) != 0)
+				copyOffset |= (cmdbuf[cmdptr++] & 0xff) << 24;
+
+			copySize = 0;
+			if ((cmd & 0x10) != 0)
+				copySize = cmdbuf[cmdptr++] & 0xff;
+			if ((cmd & 0x20) != 0)
+				copySize |= (cmdbuf[cmdptr++] & 0xff) << 8;
+			if ((cmd & 0x40) != 0)
+				copySize |= (cmdbuf[cmdptr++] & 0xff) << 16;
+			if (copySize == 0)
+				copySize = 0x10000;
+			return CMD_COPY;
+
+		} else if (cmd != 0) {
+			// Anything else the data is literal within the delta
+			// itself. Page the entire thing into the cmdbuf, if
+			// its not already there.
+			//
+			fill(cmd);
+			copySize = cmd;
+			return CMD_INSERT;
+
+		} else {
+			// cmd == 0 has been reserved for future encoding but
+			// for now its not acceptable.
+			//
+			throw new CorruptObjectException(JGitText.get().unsupportedCommand0);
+		}
+	}
+
+	private int have() {
+		return cmdcnt - cmdptr;
+	}
+
+	private void seekBase() throws IOException {
+		if (baseStream == null) {
+			baseStream = openBase();
+			if (getBaseSize() != baseSize)
+				throw new CorruptObjectException(
+						JGitText.get().baseLengthIncorrect);
+			IO.skipFully(baseStream, copyOffset);
+			baseOffset = copyOffset;
+
+		} else if (baseOffset < copyOffset) {
+			IO.skipFully(baseStream, copyOffset - baseOffset);
+			baseOffset = copyOffset;
+
+		} else if (baseOffset > copyOffset) {
+			baseStream.close();
+			baseStream = openBase();
+			IO.skipFully(baseStream, copyOffset);
+			baseOffset = copyOffset;
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaTask.java
similarity index 64%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaTask.java
index e43c33a..5e551e9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaTask.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,20 +41,48 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.pack;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
+import java.util.concurrent.Callable;
 
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ProgressMonitor;
+
+final class DeltaTask implements Callable<Object> {
+	private final PackConfig config;
+
+	private final ObjectReader templateReader;
+
+	private final DeltaCache dc;
+
+	private final ProgressMonitor pm;
+
+	private final int batchSize;
+
+	private final int start;
+
+	private final ObjectToPack[] list;
+
+	DeltaTask(PackConfig config, ObjectReader reader, DeltaCache dc,
+			ProgressMonitor pm, int batchSize, int start, ObjectToPack[] list) {
+		this.config = config;
+		this.templateReader = reader;
+		this.dc = dc;
+		this.pm = pm;
+		this.batchSize = batchSize;
+		this.start = start;
+		this.list = list;
 	}
 
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
+	public Object call() throws Exception {
+		final ObjectReader or = templateReader.newReader();
+		try {
+			DeltaWindow dw;
+			dw = new DeltaWindow(config, dc, or);
+			dw.search(pm, list, start, batchSize);
+		} finally {
+			or.release();
+		}
+		return null;
 	}
-
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java
new file mode 100644
index 0000000..c961056
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java
@@ -0,0 +1,511 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.Deflater;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.util.TemporaryBuffer;
+
+class DeltaWindow {
+	private static final int NEXT_RES = 0;
+
+	private static final int NEXT_SRC = 1;
+
+	private final PackConfig config;
+
+	private final DeltaCache deltaCache;
+
+	private final ObjectReader reader;
+
+	private final DeltaWindowEntry[] window;
+
+	/** Maximum number of bytes to admit to the window at once. */
+	private final long maxMemory;
+
+	/** Maximum depth we should create for any delta chain. */
+	private final int maxDepth;
+
+	/** Amount of memory we have loaded right now. */
+	private long loaded;
+
+	// The object we are currently considering needs a lot of state:
+
+	/** Position of {@link #res} within {@link #window} array. */
+	private int resSlot;
+
+	/**
+	 * Maximum delta chain depth the current object can have.
+	 * <p>
+	 * This can be smaller than {@link #maxDepth}.
+	 */
+	private int resMaxDepth;
+
+	/** Window entry of the object we are currently considering. */
+	private DeltaWindowEntry res;
+
+	/** If we have a delta for {@link #res}, this is the shortest found yet. */
+	private TemporaryBuffer.Heap bestDelta;
+
+	/** If we have {@link #bestDelta}, the window position it was created by. */
+	private int bestSlot;
+
+	/** Used to compress cached deltas. */
+	private Deflater deflater;
+
+	DeltaWindow(PackConfig pc, DeltaCache dc, ObjectReader or) {
+		config = pc;
+		deltaCache = dc;
+		reader = or;
+
+		// C Git increases the window size supplied by the user by 1.
+		// We don't know why it does this, but if the user asks for
+		// window=10, it actually processes with window=11. Because
+		// the window size has the largest direct impact on the final
+		// pack file size, we match this odd behavior here to give us
+		// a better chance of producing a similar sized pack as C Git.
+		//
+		// We would prefer to directly honor the user's request since
+		// PackWriter has a minimum of 2 for the window size, but then
+		// users might complain that JGit is creating a bigger pack file.
+		//
+		window = new DeltaWindowEntry[config.getDeltaSearchWindowSize() + 1];
+		for (int i = 0; i < window.length; i++)
+			window[i] = new DeltaWindowEntry();
+
+		maxMemory = config.getDeltaSearchMemoryLimit();
+		maxDepth = config.getMaxDeltaDepth();
+	}
+
+	void search(ProgressMonitor monitor, ObjectToPack[] toSearch, int off,
+			int cnt) throws IOException {
+		try {
+			for (int end = off + cnt; off < end; off++) {
+				monitor.update(1);
+
+				res = window[resSlot];
+				if (0 < maxMemory) {
+					clear(res);
+					int tail = next(resSlot);
+					final long need = estimateSize(toSearch[off]);
+					while (maxMemory < loaded + need && tail != resSlot) {
+						clear(window[tail]);
+						tail = next(tail);
+					}
+				}
+				res.set(toSearch[off]);
+
+				if (res.object.isDoNotDelta()) {
+					// PackWriter marked edge objects with the
+					// do-not-delta flag. They are the only ones
+					// that appear in toSearch with it set, but
+					// we don't actually want to make a delta for
+					// them, just need to push them into the window
+					// so they can be read by other objects.
+					//
+					keepInWindow();
+				} else {
+					// Search for a delta for the current window slot.
+					//
+					search();
+				}
+			}
+		} finally {
+			if (deflater != null)
+				deflater.end();
+		}
+	}
+
+	private static long estimateSize(ObjectToPack ent) {
+		return DeltaIndex.estimateIndexSize(ent.getWeight());
+	}
+
+	private void clear(DeltaWindowEntry ent) {
+		if (ent.index != null)
+			loaded -= ent.index.getIndexSize();
+		else if (res.buffer != null)
+			loaded -= ent.buffer.length;
+		ent.set(null);
+	}
+
+	private void search() throws IOException {
+		// TODO(spearce) If the object is used as a base for other
+		// objects in this pack we should limit the depth we create
+		// for ourselves to be the remainder of our longest dependent
+		// chain and the configured maximum depth. This can happen
+		// when the dependents are being reused out a pack, but we
+		// cannot be because we are near the edge of a thin pack.
+		//
+		resMaxDepth = maxDepth;
+
+		// Loop through the window backwards, considering every entry.
+		// This lets us look at the bigger objects that came before.
+		//
+		for (int srcSlot = prior(resSlot); srcSlot != resSlot; srcSlot = prior(srcSlot)) {
+			DeltaWindowEntry src = window[srcSlot];
+			if (src.empty())
+				break;
+			if (delta(src, srcSlot) == NEXT_RES) {
+				bestDelta = null;
+				return;
+			}
+		}
+
+		// We couldn't find a suitable delta for this object, but it may
+		// still be able to act as a base for another one.
+		//
+		if (bestDelta == null) {
+			keepInWindow();
+			return;
+		}
+
+		// Select this best matching delta as the base for the object.
+		//
+		ObjectToPack srcObj = window[bestSlot].object;
+		ObjectToPack resObj = res.object;
+		if (srcObj.isDoNotDelta()) {
+			// The source (the delta base) is an edge object outside of the
+			// pack. Its part of the common base set that the peer already
+			// has on hand, so we don't want to send it. We have to store
+			// an ObjectId and *NOT* an ObjectToPack for the base to ensure
+			// the base isn't included in the outgoing pack file.
+			//
+			resObj.setDeltaBase(srcObj.copy());
+		} else {
+			// The base is part of the pack we are sending, so it should be
+			// a direct pointer to the base.
+			//
+			resObj.setDeltaBase(srcObj);
+		}
+		resObj.setDeltaDepth(srcObj.getDeltaDepth() + 1);
+		resObj.clearReuseAsIs();
+		cacheDelta(srcObj, resObj);
+
+		// Discard the cached best result, otherwise it leaks.
+		//
+		bestDelta = null;
+
+		// If this should be the end of a chain, don't keep
+		// it in the window. Just move on to the next object.
+		//
+		if (resObj.getDeltaDepth() == maxDepth)
+			return;
+
+		shuffleBaseUpInPriority();
+		keepInWindow();
+	}
+
+	private int delta(final DeltaWindowEntry src, final int srcSlot)
+			throws IOException {
+		// Objects must use only the same type as their delta base.
+		// If we are looking at something where that isn't true we
+		// have exhausted everything of the correct type and should
+		// move on to the next thing to examine.
+		//
+		if (src.type() != res.type()) {
+			keepInWindow();
+			return NEXT_RES;
+		}
+
+		// Only consider a source with a short enough delta chain.
+		if (src.depth() > resMaxDepth)
+			return NEXT_SRC;
+
+		// Estimate a reasonable upper limit on delta size.
+		int msz = deltaSizeLimit(res, resMaxDepth, src);
+		if (msz <= 8)
+			return NEXT_SRC;
+
+		// If we have to insert a lot to make this work, find another.
+		if (res.size() - src.size() > msz)
+			return NEXT_SRC;
+
+		// If the sizes are radically different, this is a bad pairing.
+		if (res.size() < src.size() / 16)
+			return NEXT_SRC;
+
+		DeltaIndex srcIndex;
+		try {
+			srcIndex = index(src);
+		} catch (LargeObjectException tooBig) {
+			// If the source is too big to work on, skip it.
+			dropFromWindow(srcSlot);
+			return NEXT_SRC;
+		} catch (IOException notAvailable) {
+			if (src.object.isDoNotDelta()) {
+				// This is an edge that is suddenly not available.
+				dropFromWindow(srcSlot);
+				return NEXT_SRC;
+			} else {
+				throw notAvailable;
+			}
+		}
+
+		byte[] resBuf;
+		try {
+			resBuf = buffer(res);
+		} catch (LargeObjectException tooBig) {
+			// If its too big, move on to another item.
+			return NEXT_RES;
+		}
+
+		// If we already have a delta for the current object, abort
+		// encoding early if this new pairing produces a larger delta.
+		if (bestDelta != null && bestDelta.length() < msz)
+			msz = (int) bestDelta.length();
+
+		TemporaryBuffer.Heap delta = new TemporaryBuffer.Heap(msz);
+		try {
+			if (!srcIndex.encode(delta, resBuf, msz))
+				return NEXT_SRC;
+		} catch (IOException deltaTooBig) {
+			// This only happens when the heap overflows our limit.
+			return NEXT_SRC;
+		}
+
+		if (isBetterDelta(src, delta)) {
+			bestDelta = delta;
+			bestSlot = srcSlot;
+		}
+
+		return NEXT_SRC;
+	}
+
+	private void cacheDelta(ObjectToPack srcObj, ObjectToPack resObj) {
+		if (Integer.MAX_VALUE < bestDelta.length())
+			return;
+
+		int rawsz = (int) bestDelta.length();
+		if (deltaCache.canCache(rawsz, srcObj, resObj)) {
+			try {
+				byte[] zbuf = new byte[deflateBound(rawsz)];
+
+				ZipStream zs = new ZipStream(deflater(), zbuf);
+				bestDelta.writeTo(zs, null);
+				bestDelta = null;
+				int len = zs.finish();
+
+				resObj.setCachedDelta(deltaCache.cache(zbuf, len, rawsz));
+				resObj.setCachedSize(rawsz);
+			} catch (IOException err) {
+				deltaCache.credit(rawsz);
+			} catch (OutOfMemoryError err) {
+				deltaCache.credit(rawsz);
+			}
+		}
+	}
+
+	private static int deflateBound(int insz) {
+		return insz + ((insz + 7) >> 3) + ((insz + 63) >> 6) + 11;
+	}
+
+	private void shuffleBaseUpInPriority() {
+		// Shuffle the entire window so that the best match we just used
+		// is at our current index, and our current object is at the index
+		// before it. Slide any entries in between to make space.
+		//
+		window[resSlot] = window[bestSlot];
+
+		DeltaWindowEntry next = res;
+		int slot = prior(resSlot);
+		for (; slot != bestSlot; slot = prior(slot)) {
+			DeltaWindowEntry e = window[slot];
+			window[slot] = next;
+			next = e;
+		}
+		window[slot] = next;
+	}
+
+	private void keepInWindow() {
+		resSlot = next(resSlot);
+	}
+
+	private int next(int slot) {
+		if (++slot == window.length)
+			return 0;
+		return slot;
+	}
+
+	private int prior(int slot) {
+		if (slot == 0)
+			return window.length - 1;
+		return slot - 1;
+	}
+
+	private void dropFromWindow(@SuppressWarnings("unused") int srcSlot) {
+		// We should drop the current source entry from the window,
+		// it is somehow invalid for us to work with.
+	}
+
+	private boolean isBetterDelta(DeltaWindowEntry src,
+			TemporaryBuffer.Heap resDelta) {
+		if (bestDelta == null)
+			return true;
+
+		// If both delta sequences are the same length, use the one
+		// that has a shorter delta chain since it would be faster
+		// to access during reads.
+		//
+		if (resDelta.length() == bestDelta.length())
+			return src.depth() < window[bestSlot].depth();
+
+		return resDelta.length() < bestDelta.length();
+	}
+
+	private static int deltaSizeLimit(DeltaWindowEntry res, int maxDepth,
+			DeltaWindowEntry src) {
+		// Ideally the delta is at least 50% of the original size,
+		// but we also want to account for delta header overhead in
+		// the pack file (to point to the delta base) so subtract off
+		// some of those header bytes from the limit.
+		//
+		final int limit = res.size() / 2 - 20;
+
+		// Distribute the delta limit over the entire chain length.
+		// This is weighted such that deeper items in the chain must
+		// be even smaller than if they were earlier in the chain, as
+		// they cost significantly more to unpack due to the increased
+		// number of recursive unpack calls.
+		//
+		final int remainingDepth = maxDepth - src.depth();
+		return (limit * remainingDepth) / maxDepth;
+	}
+
+	private DeltaIndex index(DeltaWindowEntry ent)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException, LargeObjectException {
+		DeltaIndex idx = ent.index;
+		if (idx == null) {
+			try {
+				idx = new DeltaIndex(buffer(ent));
+			} catch (OutOfMemoryError noMemory) {
+				LargeObjectException e = new LargeObjectException(ent.object);
+				e.initCause(noMemory);
+				throw e;
+			}
+			if (0 < maxMemory)
+				loaded += idx.getIndexSize() - idx.getSourceSize();
+			ent.index = idx;
+		}
+		return idx;
+	}
+
+	private byte[] buffer(DeltaWindowEntry ent) throws MissingObjectException,
+			IncorrectObjectTypeException, IOException, LargeObjectException {
+		byte[] buf = ent.buffer;
+		if (buf == null) {
+			buf = PackWriter.buffer(config, reader, ent.object);
+			if (0 < maxMemory)
+				loaded += buf.length;
+			ent.buffer = buf;
+		}
+		return buf;
+	}
+
+	private Deflater deflater() {
+		if (deflater == null)
+			deflater = new Deflater(config.getCompressionLevel());
+		else
+			deflater.reset();
+		return deflater;
+	}
+
+	static final class ZipStream extends OutputStream {
+		private final Deflater deflater;
+
+		private final byte[] zbuf;
+
+		private int outPtr;
+
+		ZipStream(Deflater deflater, byte[] zbuf) {
+			this.deflater = deflater;
+			this.zbuf = zbuf;
+		}
+
+		int finish() throws IOException {
+			deflater.finish();
+			for (;;) {
+				if (outPtr == zbuf.length)
+					throw new EOFException();
+
+				int n = deflater.deflate(zbuf, outPtr, zbuf.length - outPtr);
+				if (n == 0) {
+					if (deflater.finished())
+						return outPtr;
+					throw new IOException();
+				}
+				outPtr += n;
+			}
+		}
+
+		@Override
+		public void write(byte[] b, int off, int len) throws IOException {
+			deflater.setInput(b, off, len);
+			for (;;) {
+				if (outPtr == zbuf.length)
+					throw new EOFException();
+
+				int n = deflater.deflate(zbuf, outPtr, zbuf.length - outPtr);
+				if (n == 0) {
+					if (deflater.needsInput())
+						break;
+					throw new IOException();
+				}
+				outPtr += n;
+			}
+		}
+
+		@Override
+		public void write(int b) throws IOException {
+			throw new UnsupportedOperationException();
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindowEntry.java
similarity index 69%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindowEntry.java
index e43c33a..0f1e632 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindowEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,20 +41,40 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.pack;
 
-/**
- * A default {@link RepositoryListener} that does nothing except invoke an
- * optional general method for any repository change.
- */
-public class RepositoryAdapter implements RepositoryListener {
+class DeltaWindowEntry {
+	ObjectToPack object;
 
-	public void indexChanged(final IndexChangedEvent e) {
-		// Empty
+	/** Complete contents of this object. Lazily loaded. */
+	byte[] buffer;
+
+	/** Index of this object's content, to encode other deltas. Lazily loaded. */
+	DeltaIndex index;
+
+	void set(ObjectToPack object) {
+		this.object = object;
+		this.index = null;
+		this.buffer = null;
 	}
 
-	public void refsChanged(final RefsChangedEvent e) {
-		// Empty
+	/** @return current delta chain depth of this object. */
+	int depth() {
+		return object.getDeltaDepth();
 	}
 
+	/** @return type of the object in this window entry. */
+	int type() {
+		return object.getType();
+	}
+
+	/** @return estimated unpacked size of the object, in bytes . */
+	int size() {
+		return object.getWeight();
+	}
+
+	/** @return true if there is no object stored in this entry. */
+	boolean empty() {
+		return object == null;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectReuseAsIs.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectReuseAsIs.java
new file mode 100644
index 0000000..a815e93
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectReuseAsIs.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.revwalk.RevObject;
+
+/**
+ * Extension of {@link ObjectReader} that supports reusing objects in packs.
+ * <p>
+ * {@code ObjectReader} implementations may also optionally implement this
+ * interface to support {@link PackWriter} with a means of copying an object
+ * that is already in pack encoding format directly into the output stream,
+ * without incurring decompression and recompression overheads.
+ */
+public interface ObjectReuseAsIs {
+	/**
+	 * Allocate a new {@code PackWriter} state structure for an object.
+	 * <p>
+	 * {@link PackWriter} allocates these objects to keep track of the
+	 * per-object state, and how to load the objects efficiently into the
+	 * generated stream. Implementers may subclass this type with additional
+	 * object state, such as to remember what file and offset contains the
+	 * object's pack encoded data.
+	 *
+	 * @param obj
+	 *            identity of the object that will be packed. The object's
+	 *            parsed status is undefined here. Implementers must not rely on
+	 *            the object being parsed.
+	 * @return a new instance for this object.
+	 */
+	public ObjectToPack newObjectToPack(RevObject obj);
+
+	/**
+	 * Select the best object representation for a packer.
+	 * <p>
+	 * Implementations should iterate through all available representations of
+	 * an object, and pass them in turn to the PackWriter though
+	 * {@link PackWriter#select(ObjectToPack, StoredObjectRepresentation)} so
+	 * the writer can select the most suitable representation to reuse into the
+	 * output stream.
+	 *
+	 * @param packer
+	 *            the packer that will write the object in the near future.
+	 * @param otp
+	 *            the object to pack.
+	 * @throws MissingObjectException
+	 *             there is no representation available for the object, as it is
+	 *             no longer in the repository. Packing will abort.
+	 * @throws IOException
+	 *             the repository cannot be accessed. Packing will abort.
+	 */
+	public void selectObjectRepresentation(PackWriter packer, ObjectToPack otp)
+			throws IOException, MissingObjectException;
+
+	/**
+	 * Output a previously selected representation.
+	 * <p>
+	 * {@code PackWriter} invokes this method only if a representation
+	 * previously given to it by {@code selectObjectRepresentation} was chosen
+	 * for reuse into the output stream. The {@code otp} argument is an instance
+	 * created by this reader's own {@code newObjectToPack}, and the
+	 * representation data saved within it also originated from this reader.
+	 * <p>
+	 * Implementors must write the object header before copying the raw data to
+	 * the output stream. The typical implementation is like:
+	 *
+	 * <pre>
+	 * MyToPack mtp = (MyToPack) otp;
+	 * byte[] raw = validate(mtp); // throw SORNAE here, if at all
+	 * out.writeHeader(mtp, mtp.inflatedSize);
+	 * out.write(raw);
+	 * </pre>
+	 *
+	 * @param out
+	 *            stream the object should be written to.
+	 * @param otp
+	 *            the object's saved representation information.
+	 * @throws StoredObjectRepresentationNotAvailableException
+	 *             the previously selected representation is no longer
+	 *             available. If thrown before {@code out.writeHeader} the pack
+	 *             writer will try to find another representation, and write
+	 *             that one instead. If throw after {@code out.writeHeader},
+	 *             packing will abort.
+	 * @throws IOException
+	 *             the stream's write method threw an exception. Packing will
+	 *             abort.
+	 */
+	public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp)
+			throws IOException, StoredObjectRepresentationNotAvailableException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java
new file mode 100644
index 0000000..70188a3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2008-2010, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.transport.PackedObjectInfo;
+
+/**
+ * Per-object state used by {@link PackWriter}.
+ * <p>
+ * {@code PackWriter} uses this class to track the things it needs to include in
+ * the newly generated pack file, and how to efficiently obtain the raw data for
+ * each object as they are written to the output stream.
+ */
+public class ObjectToPack extends PackedObjectInfo {
+	private static final int WANT_WRITE = 1 << 0;
+
+	private static final int REUSE_AS_IS = 1 << 1;
+
+	private static final int DO_NOT_DELTA = 1 << 2;
+
+	private static final int TYPE_SHIFT = 5;
+
+	private static final int DELTA_SHIFT = 8;
+
+	private static final int NON_DELTA_MASK = 0xff;
+
+	/** Other object being packed that this will delta against. */
+	private ObjectId deltaBase;
+
+	/**
+	 * Bit field, from bit 0 to bit 31:
+	 * <ul>
+	 * <li>1 bit: wantWrite</li>
+	 * <li>1 bit: canReuseAsIs</li>
+	 * <li>1 bit: doNotDelta</li>
+	 * <li>2 bits: unused</li>
+	 * <li>3 bits: type</li>
+	 * <li>--</li>
+	 * <li>24 bits: deltaDepth</li>
+	 * </ul>
+	 */
+	private int flags;
+
+	/** Hash of the object's tree path. */
+	private int pathHash;
+
+	/** If present, deflated delta instruction stream for this object. */
+	private DeltaCache.Ref cachedDelta;
+
+	/**
+	 * Construct for the specified object id.
+	 *
+	 * @param src
+	 *            object id of object for packing
+	 * @param type
+	 *            real type code of the object, not its in-pack type.
+	 */
+	public ObjectToPack(AnyObjectId src, final int type) {
+		super(src);
+		flags = type << TYPE_SHIFT;
+	}
+
+	/**
+	 * Construct for the specified object.
+	 *
+	 * @param obj
+	 *            identity of the object that will be packed. The object's
+	 *            parsed status is undefined here. Implementers must not rely on
+	 *            the object being parsed.
+	 */
+	public ObjectToPack(RevObject obj) {
+		this(obj, obj.getType());
+	}
+
+	/**
+	 * @return delta base object id if object is going to be packed in delta
+	 *         representation; null otherwise - if going to be packed as a
+	 *         whole object.
+	 */
+	ObjectId getDeltaBaseId() {
+		return deltaBase;
+	}
+
+	/**
+	 * @return delta base object to pack if object is going to be packed in
+	 *         delta representation and delta is specified as object to
+	 *         pack; null otherwise - if going to be packed as a whole
+	 *         object or delta base is specified only as id.
+	 */
+	ObjectToPack getDeltaBase() {
+		if (deltaBase instanceof ObjectToPack)
+			return (ObjectToPack) deltaBase;
+		return null;
+	}
+
+	/**
+	 * Set delta base for the object. Delta base set by this method is used
+	 * by {@link PackWriter} to write object - determines its representation
+	 * in a created pack.
+	 *
+	 * @param deltaBase
+	 *            delta base object or null if object should be packed as a
+	 *            whole object.
+	 *
+	 */
+	void setDeltaBase(ObjectId deltaBase) {
+		this.deltaBase = deltaBase;
+	}
+
+	void setCachedDelta(DeltaCache.Ref data){
+		cachedDelta = data;
+	}
+
+	DeltaCache.Ref popCachedDelta() {
+		DeltaCache.Ref r = cachedDelta;
+		if (r != null)
+			cachedDelta = null;
+		return r;
+	}
+
+	void clearDeltaBase() {
+		this.deltaBase = null;
+
+		if (cachedDelta != null) {
+			cachedDelta.clear();
+			cachedDelta.enqueue();
+			cachedDelta = null;
+		}
+	}
+
+	/**
+	 * @return true if object is going to be written as delta; false
+	 *         otherwise.
+	 */
+	boolean isDeltaRepresentation() {
+		return deltaBase != null;
+	}
+
+	/**
+	 * Check if object is already written in a pack. This information is
+	 * used to achieve delta-base precedence in a pack file.
+	 *
+	 * @return true if object is already written; false otherwise.
+	 */
+	boolean isWritten() {
+		return getOffset() != 0;
+	}
+
+	int getType() {
+		return (flags >> TYPE_SHIFT) & 0x7;
+	}
+
+	int getDeltaDepth() {
+		return flags >>> DELTA_SHIFT;
+	}
+
+	void setDeltaDepth(int d) {
+		flags = (d << DELTA_SHIFT) | (flags & NON_DELTA_MASK);
+	}
+
+	boolean wantWrite() {
+		return (flags & WANT_WRITE) != 0;
+	}
+
+	void markWantWrite() {
+		flags |= WANT_WRITE;
+	}
+
+	boolean isReuseAsIs() {
+		return (flags & REUSE_AS_IS) != 0;
+	}
+
+	void setReuseAsIs() {
+		flags |= REUSE_AS_IS;
+	}
+
+	/**
+	 * Forget the reuse information previously stored.
+	 * <p>
+	 * Implementations may subclass this method, but they must also invoke the
+	 * super version with {@code super.clearReuseAsIs()} to ensure the flag is
+	 * properly cleared for the writer.
+	 */
+	protected void clearReuseAsIs() {
+		flags &= ~REUSE_AS_IS;
+	}
+
+	boolean isDoNotDelta() {
+		return (flags & DO_NOT_DELTA) != 0;
+	}
+
+	void setDoNotDelta(boolean noDelta) {
+		if (noDelta)
+			flags |= DO_NOT_DELTA;
+		else
+			flags &= ~DO_NOT_DELTA;
+	}
+
+	int getFormat() {
+		if (isReuseAsIs()) {
+			if (isDeltaRepresentation())
+				return StoredObjectRepresentation.PACK_DELTA;
+			return StoredObjectRepresentation.PACK_WHOLE;
+		}
+		return StoredObjectRepresentation.FORMAT_OTHER;
+	}
+
+	// Overload weight into CRC since we don't need them at the same time.
+	int getWeight() {
+		return getCRC();
+	}
+
+	void setWeight(int weight) {
+		setCRC(weight);
+	}
+
+	int getPathHash() {
+		return pathHash;
+	}
+
+	void setPathHash(int hc) {
+		pathHash = hc;
+	}
+
+	int getCachedSize() {
+		return pathHash;
+	}
+
+	void setCachedSize(int sz) {
+		pathHash = sz;
+	}
+
+	/**
+	 * Remember a specific representation for reuse at a later time.
+	 * <p>
+	 * Implementers should remember the representation chosen, so it can be
+	 * reused at a later time. {@link PackWriter} may invoke this method
+	 * multiple times for the same object, each time saving the current best
+	 * representation found.
+	 *
+	 * @param ref
+	 *            the object representation.
+	 */
+	public void select(StoredObjectRepresentation ref) {
+		// Empty by default.
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder buf = new StringBuilder();
+		buf.append("ObjectToPack[");
+		buf.append(Constants.typeString(getType()));
+		buf.append(" ");
+		buf.append(name());
+		if (wantWrite())
+			buf.append(" wantWrite");
+		if (isReuseAsIs())
+			buf.append(" reuseAsIs");
+		if (isDoNotDelta())
+			buf.append(" doNotDelta");
+		if (getDeltaDepth() > 0)
+			buf.append(" depth=" + getDeltaDepth());
+		if (isDeltaRepresentation()) {
+			if (getDeltaBase() != null)
+				buf.append(" base=inpack:" + getDeltaBase().name());
+			else
+				buf.append(" base=edge:" + getDeltaBaseId().name());
+		}
+		if (isWritten())
+			buf.append(" offset=" + getOffset());
+		buf.append("]");
+		return buf.toString();
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
new file mode 100644
index 0000000..9bda76d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 2008-2010, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import java.util.concurrent.Executor;
+import java.util.zip.Deflater;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.PackIndexWriter;
+
+/**
+ * Configuration used by a {@link PackWriter} when constructing the stream.
+ *
+ * A configuration may be modified once created, but should not be modified
+ * while it is being used by a PackWriter. If a configuration is not modified it
+ * is safe to share the same configuration instance between multiple concurrent
+ * threads executing different PackWriters.
+ */
+public class PackConfig {
+	/**
+	 * Default value of deltas reuse option: {@value}
+	 *
+	 * @see #setReuseDeltas(boolean)
+	 */
+	public static final boolean DEFAULT_REUSE_DELTAS = true;
+
+	/**
+	 * Default value of objects reuse option: {@value}
+	 *
+	 * @see #setReuseObjects(boolean)
+	 */
+	public static final boolean DEFAULT_REUSE_OBJECTS = true;
+
+	/**
+	 * Default value of delta compress option: {@value}
+	 *
+	 * @see #setDeltaCompress(boolean)
+	 */
+	public static final boolean DEFAULT_DELTA_COMPRESS = true;
+
+	/**
+	 * Default value of delta base as offset option: {@value}
+	 *
+	 * @see #setDeltaBaseAsOffset(boolean)
+	 */
+	public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET = false;
+
+	/**
+	 * Default value of maximum delta chain depth: {@value}
+	 *
+	 * @see #setMaxDeltaDepth(int)
+	 */
+	public static final int DEFAULT_MAX_DELTA_DEPTH = 50;
+
+	/**
+	 * Default window size during packing: {@value}
+	 *
+	 * @see #setDeltaSearchWindowSize(int)
+	 */
+	public static final int DEFAULT_DELTA_SEARCH_WINDOW_SIZE = 10;
+
+	/**
+	 * Default big file threshold: {@value}
+	 *
+	 * @see #setBigFileThreshold(long)
+	 */
+	public static final long DEFAULT_BIG_FILE_THRESHOLD = 50 * 1024 * 1024;
+
+	/**
+	 * Default delta cache size: {@value}
+	 *
+	 * @see #setDeltaCacheSize(long)
+	 */
+	public static final long DEFAULT_DELTA_CACHE_SIZE = 50 * 1024 * 1024;
+
+	/**
+	 * Default delta cache limit: {@value}
+	 *
+	 * @see #setDeltaCacheLimit(int)
+	 */
+	public static final int DEFAULT_DELTA_CACHE_LIMIT = 100;
+
+	/**
+	 * Default index version: {@value}
+	 *
+	 * @see #setIndexVersion(int)
+	 */
+	public static final int DEFAULT_INDEX_VERSION = 2;
+
+
+	private int compressionLevel = Deflater.DEFAULT_COMPRESSION;
+
+	private boolean reuseDeltas = DEFAULT_REUSE_DELTAS;
+
+	private boolean reuseObjects = DEFAULT_REUSE_OBJECTS;
+
+	private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET;
+
+	private boolean deltaCompress = DEFAULT_DELTA_COMPRESS;
+
+	private int maxDeltaDepth = DEFAULT_MAX_DELTA_DEPTH;
+
+	private int deltaSearchWindowSize = DEFAULT_DELTA_SEARCH_WINDOW_SIZE;
+
+	private long deltaSearchMemoryLimit;
+
+	private long deltaCacheSize = DEFAULT_DELTA_CACHE_SIZE;
+
+	private int deltaCacheLimit = DEFAULT_DELTA_CACHE_LIMIT;
+
+	private long bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD;
+
+	private int threads;
+
+	private Executor executor;
+
+	private int indexVersion = DEFAULT_INDEX_VERSION;
+
+
+	/** Create a default configuration. */
+	public PackConfig() {
+		// Fields are initialized to defaults.
+	}
+
+	/**
+	 * Create a configuration honoring the repository's settings.
+	 *
+	 * @param db
+	 *            the repository to read settings from. The repository is not
+	 *            retained by the new configuration, instead its settings are
+	 *            copied during the constructor.
+	 */
+	public PackConfig(Repository db) {
+		fromConfig(db.getConfig());
+	}
+
+	/**
+	 * Create a configuration honoring settings in a {@link Config}.
+	 *
+	 * @param cfg
+	 *            the source to read settings from. The source is not retained
+	 *            by the new configuration, instead its settings are copied
+	 *            during the constructor.
+	 */
+	public PackConfig(Config cfg) {
+		fromConfig(cfg);
+	}
+
+	/**
+	 * Check whether to reuse deltas existing in repository.
+	 *
+	 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
+	 *
+	 * @return true if object is configured to reuse deltas; false otherwise.
+	 */
+	public boolean isReuseDeltas() {
+		return reuseDeltas;
+	}
+
+	/**
+	 * Set reuse deltas configuration option for the writer.
+	 *
+	 * When enabled, writer will search for delta representation of object in
+	 * repository and use it if possible. Normally, only deltas with base to
+	 * another object existing in set of objects to pack will be used. The
+	 * exception however is thin-packs where the base object may exist on the
+	 * other side.
+	 *
+	 * When raw delta data is directly copied from a pack file, its checksum is
+	 * computed to verify the data is not corrupt.
+	 *
+	 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
+	 *
+	 * @param reuseDeltas
+	 *            boolean indicating whether or not try to reuse deltas.
+	 */
+	public void setReuseDeltas(boolean reuseDeltas) {
+		this.reuseDeltas = reuseDeltas;
+	}
+
+	/**
+	 * Checks whether to reuse existing objects representation in repository.
+	 *
+	 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
+	 *
+	 * @return true if writer is configured to reuse objects representation from
+	 *         pack; false otherwise.
+	 */
+	public boolean isReuseObjects() {
+		return reuseObjects;
+	}
+
+	/**
+	 * Set reuse objects configuration option for the writer.
+	 *
+	 * If enabled, writer searches for compressed representation in a pack file.
+	 * If possible, compressed data is directly copied from such a pack file.
+	 * Data checksum is verified.
+	 *
+	 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
+	 *
+	 * @param reuseObjects
+	 *            boolean indicating whether or not writer should reuse existing
+	 *            objects representation.
+	 */
+	public void setReuseObjects(boolean reuseObjects) {
+		this.reuseObjects = reuseObjects;
+	}
+
+	/**
+	 * True if writer can use offsets to point to a delta base.
+	 *
+	 * If true the writer may choose to use an offset to point to a delta base
+	 * in the same pack, this is a newer style of reference that saves space.
+	 * False if the writer has to use the older (and more compatible style) of
+	 * storing the full ObjectId of the delta base.
+	 *
+	 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
+	 *
+	 * @return true if delta base is stored as an offset; false if it is stored
+	 *         as an ObjectId.
+	 */
+	public boolean isDeltaBaseAsOffset() {
+		return deltaBaseAsOffset;
+	}
+
+	/**
+	 * Set writer delta base format.
+	 *
+	 * Delta base can be written as an offset in a pack file (new approach
+	 * reducing file size) or as an object id (legacy approach, compatible with
+	 * old readers).
+	 *
+	 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
+	 *
+	 * @param deltaBaseAsOffset
+	 *            boolean indicating whether delta base can be stored as an
+	 *            offset.
+	 */
+	public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) {
+		this.deltaBaseAsOffset = deltaBaseAsOffset;
+	}
+
+	/**
+	 * Check whether the writer will create new deltas on the fly.
+	 *
+	 * Default setting: {@value #DEFAULT_DELTA_COMPRESS}
+	 *
+	 * @return true if the writer will create a new delta when either
+	 *         {@link #isReuseDeltas()} is false, or no suitable delta is
+	 *         available for reuse.
+	 */
+	public boolean isDeltaCompress() {
+		return deltaCompress;
+	}
+
+	/**
+	 * Set whether or not the writer will create new deltas on the fly.
+	 *
+	 * Default setting: {@value #DEFAULT_DELTA_COMPRESS}
+	 *
+	 * @param deltaCompress
+	 *            true to create deltas when {@link #isReuseDeltas()} is false,
+	 *            or when a suitable delta isn't available for reuse. Set to
+	 *            false to write whole objects instead.
+	 */
+	public void setDeltaCompress(boolean deltaCompress) {
+		this.deltaCompress = deltaCompress;
+	}
+
+	/**
+	 * Get maximum depth of delta chain set up for the writer.
+	 *
+	 * Generated chains are not longer than this value.
+	 *
+	 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
+	 *
+	 * @return maximum delta chain depth.
+	 */
+	public int getMaxDeltaDepth() {
+		return maxDeltaDepth;
+	}
+
+	/**
+	 * Set up maximum depth of delta chain for the writer.
+	 *
+	 * Generated chains are not longer than this value. Too low value causes low
+	 * compression level, while too big makes unpacking (reading) longer.
+	 *
+	 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
+	 *
+	 * @param maxDeltaDepth
+	 *            maximum delta chain depth.
+	 */
+	public void setMaxDeltaDepth(int maxDeltaDepth) {
+		this.maxDeltaDepth = maxDeltaDepth;
+	}
+
+	/**
+	 * Get the number of objects to try when looking for a delta base.
+	 *
+	 * This limit is per thread, if 4 threads are used the actual memory used
+	 * will be 4 times this value.
+	 *
+	 * Default setting: {@value #DEFAULT_DELTA_SEARCH_WINDOW_SIZE}
+	 *
+	 * @return the object count to be searched.
+	 */
+	public int getDeltaSearchWindowSize() {
+		return deltaSearchWindowSize;
+	}
+
+	/**
+	 * Set the number of objects considered when searching for a delta base.
+	 *
+	 * Default setting: {@value #DEFAULT_DELTA_SEARCH_WINDOW_SIZE}
+	 *
+	 * @param objectCount
+	 *            number of objects to search at once. Must be at least 2.
+	 */
+	public void setDeltaSearchWindowSize(int objectCount) {
+		if (objectCount <= 2)
+			setDeltaCompress(false);
+		else
+			deltaSearchWindowSize = objectCount;
+	}
+
+	/**
+	 * Get maximum number of bytes to put into the delta search window.
+	 *
+	 * Default setting is 0, for an unlimited amount of memory usage. Actual
+	 * memory used is the lower limit of either this setting, or the sum of
+	 * space used by at most {@link #getDeltaSearchWindowSize()} objects.
+	 *
+	 * This limit is per thread, if 4 threads are used the actual memory limit
+	 * will be 4 times this value.
+	 *
+	 * @return the memory limit.
+	 */
+	public long getDeltaSearchMemoryLimit() {
+		return deltaSearchMemoryLimit;
+	}
+
+	/**
+	 * Set the maximum number of bytes to put into the delta search window.
+	 *
+	 * Default setting is 0, for an unlimited amount of memory usage. If the
+	 * memory limit is reached before {@link #getDeltaSearchWindowSize()} the
+	 * window size is temporarily lowered.
+	 *
+	 * @param memoryLimit
+	 *            Maximum number of bytes to load at once, 0 for unlimited.
+	 */
+	public void setDeltaSearchMemoryLimit(long memoryLimit) {
+		deltaSearchMemoryLimit = memoryLimit;
+	}
+
+	/**
+	 * Get the size of the in-memory delta cache.
+	 *
+	 * This limit is for the entire writer, even if multiple threads are used.
+	 *
+	 * Default setting: {@value #DEFAULT_DELTA_CACHE_SIZE}
+	 *
+	 * @return maximum number of bytes worth of delta data to cache in memory.
+	 *         If 0 the cache is infinite in size (up to the JVM heap limit
+	 *         anyway). A very tiny size such as 1 indicates the cache is
+	 *         effectively disabled.
+	 */
+	public long getDeltaCacheSize() {
+		return deltaCacheSize;
+	}
+
+	/**
+	 * Set the maximum number of bytes of delta data to cache.
+	 *
+	 * During delta search, up to this many bytes worth of small or hard to
+	 * compute deltas will be stored in memory. This cache speeds up writing by
+	 * allowing the cached entry to simply be dumped to the output stream.
+	 *
+	 * Default setting: {@value #DEFAULT_DELTA_CACHE_SIZE}
+	 *
+	 * @param size
+	 *            number of bytes to cache. Set to 0 to enable an infinite
+	 *            cache, set to 1 (an impossible size for any delta) to disable
+	 *            the cache.
+	 */
+	public void setDeltaCacheSize(long size) {
+		deltaCacheSize = size;
+	}
+
+	/**
+	 * Maximum size in bytes of a delta to cache.
+	 *
+	 * Default setting: {@value #DEFAULT_DELTA_CACHE_LIMIT}
+	 *
+	 * @return maximum size (in bytes) of a delta that should be cached.
+	 */
+	public int getDeltaCacheLimit() {
+		return deltaCacheLimit;
+	}
+
+	/**
+	 * Set the maximum size of a delta that should be cached.
+	 *
+	 * During delta search, any delta smaller than this size will be cached, up
+	 * to the {@link #getDeltaCacheSize()} maximum limit. This speeds up writing
+	 * by allowing these cached deltas to be output as-is.
+	 *
+	 * Default setting: {@value #DEFAULT_DELTA_CACHE_LIMIT}
+	 *
+	 * @param size
+	 *            maximum size (in bytes) of a delta to be cached.
+	 */
+	public void setDeltaCacheLimit(int size) {
+		deltaCacheLimit = size;
+	}
+
+	/**
+	 * Get the maximum file size that will be delta compressed.
+	 *
+	 * Files bigger than this setting will not be delta compressed, as they are
+	 * more than likely already highly compressed binary data files that do not
+	 * delta compress well, such as MPEG videos.
+	 *
+	 * Default setting: {@value #DEFAULT_BIG_FILE_THRESHOLD}
+	 *
+	 * @return the configured big file threshold.
+	 */
+	public long getBigFileThreshold() {
+		return bigFileThreshold;
+	}
+
+	/**
+	 * Set the maximum file size that should be considered for deltas.
+	 *
+	 * Default setting: {@value #DEFAULT_BIG_FILE_THRESHOLD}
+	 *
+	 * @param bigFileThreshold
+	 *            the limit, in bytes.
+	 */
+	public void setBigFileThreshold(long bigFileThreshold) {
+		this.bigFileThreshold = bigFileThreshold;
+	}
+
+	/**
+	 * Get the compression level applied to objects in the pack.
+	 *
+	 * Default setting: {@value java.util.zip.Deflater#DEFAULT_COMPRESSION}
+	 *
+	 * @return current compression level, see {@link java.util.zip.Deflater}.
+	 */
+	public int getCompressionLevel() {
+		return compressionLevel;
+	}
+
+	/**
+	 * Set the compression level applied to objects in the pack.
+	 *
+	 * Default setting: {@value java.util.zip.Deflater#DEFAULT_COMPRESSION}
+	 *
+	 * @param level
+	 *            compression level, must be a valid level recognized by the
+	 *            {@link java.util.zip.Deflater} class.
+	 */
+	public void setCompressionLevel(int level) {
+		compressionLevel = level;
+	}
+
+	/**
+	 * Get the number of threads used during delta compression.
+	 *
+	 * Default setting: 0 (auto-detect processors)
+	 *
+	 * @return number of threads used for delta compression. 0 will auto-detect
+	 *         the threads to the number of available processors.
+	 */
+	public int getThreads() {
+		return threads;
+	}
+
+	/**
+	 * Set the number of threads to use for delta compression.
+	 *
+	 * During delta compression, if there are enough objects to be considered
+	 * the writer will start up concurrent threads and allow them to compress
+	 * different sections of the repository concurrently.
+	 *
+	 * An application thread pool can be set by {@link #setExecutor(Executor)}.
+	 * If not set a temporary pool will be created by the writer, and torn down
+	 * automatically when compression is over.
+	 *
+	 * Default setting: 0 (auto-detect processors)
+	 *
+	 * @param threads
+	 *            number of threads to use. If <= 0 the number of available
+	 *            processors for this JVM is used.
+	 */
+	public void setThreads(int threads) {
+		this.threads = threads;
+	}
+
+	/** @return the preferred thread pool to execute delta search on. */
+	public Executor getExecutor() {
+		return executor;
+	}
+
+	/**
+	 * Set the executor to use when using threads.
+	 *
+	 * During delta compression if the executor is non-null jobs will be queued
+	 * up on it to perform delta compression in parallel. Aside from setting the
+	 * executor, the caller must set {@link #setThreads(int)} to enable threaded
+	 * delta search.
+	 *
+	 * @param executor
+	 *            executor to use for threads. Set to null to create a temporary
+	 *            executor just for the writer.
+	 */
+	public void setExecutor(Executor executor) {
+		this.executor = executor;
+	}
+
+	/**
+	 * Get the pack index file format version this instance creates.
+	 *
+	 * Default setting: {@value #DEFAULT_INDEX_VERSION}
+	 *
+	 * @return the index version, the special version 0 designates the oldest
+	 *         (most compatible) format available for the objects.
+	 * @see PackIndexWriter
+	 */
+	public int getIndexVersion() {
+		return indexVersion;
+	}
+
+	/**
+	 * Set the pack index file format version this instance will create.
+	 *
+	 * Default setting: {@value #DEFAULT_INDEX_VERSION}
+	 *
+	 * @param version
+	 *            the version to write. The special version 0 designates the
+	 *            oldest (most compatible) format available for the objects.
+	 * @see PackIndexWriter
+	 */
+	public void setIndexVersion(final int version) {
+		indexVersion = version;
+	}
+
+	/**
+	 * Update properties by setting fields from the configuration.
+	 *
+	 * If a property's corresponding variable is not defined in the supplied
+	 * configuration, then it is left unmodified.
+	 *
+	 * @param rc
+	 *            configuration to read properties from.
+	 */
+	public void fromConfig(final Config rc) {
+		setMaxDeltaDepth(rc.getInt("pack", "depth", getMaxDeltaDepth()));
+		setDeltaSearchWindowSize(rc.getInt("pack", "window", getDeltaSearchWindowSize()));
+		setDeltaSearchMemoryLimit(rc.getLong("pack", "windowmemory", getDeltaSearchMemoryLimit()));
+		setDeltaCacheSize(rc.getLong("pack", "deltacachesize", getDeltaCacheSize()));
+		setDeltaCacheLimit(rc.getInt("pack", "deltacachelimit", getDeltaCacheLimit()));
+		setCompressionLevel(rc.getInt("pack", "compression",
+				rc.getInt("core", "compression", getCompressionLevel())));
+		setIndexVersion(rc.getInt("pack", "indexversion", getIndexVersion()));
+		setBigFileThreshold(rc.getLong("core", "bigfilethreshold", getBigFileThreshold()));
+		setThreads(rc.getInt("pack", "threads", getThreads()));
+
+		// These variables aren't standardized
+		//
+		setReuseDeltas(rc.getBoolean("pack", "reusedeltas", isReuseDeltas()));
+		setReuseObjects(rc.getBoolean("pack", "reuseobjects", isReuseObjects()));
+		setDeltaCompress(rc.getBoolean("pack", "deltacompression", isDeltaCompress()));
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java
new file mode 100644
index 0000000..92e1a19
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.util.zip.CRC32;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.util.NB;
+
+/** Custom output stream to support {@link PackWriter}. */
+public final class PackOutputStream extends OutputStream {
+	private final ProgressMonitor writeMonitor;
+
+	private final OutputStream out;
+
+	private final boolean ofsDelta;
+
+	private final CRC32 crc = new CRC32();
+
+	private final MessageDigest md = Constants.newMessageDigest();
+
+	private long count;
+
+	private byte[] headerBuffer = new byte[32];
+
+	private byte[] copyBuffer;
+
+	/**
+	 * Initialize a pack output stream.
+	 * <p>
+	 * This constructor is exposed to support debugging the JGit library only.
+	 * Application or storage level code should not create a PackOutputStream,
+	 * instead use {@link PackWriter}, and let the writer create the stream.
+	 *
+	 * @param writeMonitor
+	 *            monitor to update on object output progress.
+	 * @param out
+	 *            target stream to receive all object contents.
+	 * @param pw
+	 *            packer that is going to perform the output.
+	 */
+	public PackOutputStream(final ProgressMonitor writeMonitor,
+			final OutputStream out, final PackWriter pw) {
+		this.writeMonitor = writeMonitor;
+		this.out = out;
+		this.ofsDelta = pw.isDeltaBaseAsOffset();
+	}
+
+	@Override
+	public void write(final int b) throws IOException {
+		count++;
+		out.write(b);
+		crc.update(b);
+		md.update((byte) b);
+	}
+
+	@Override
+	public void write(final byte[] b, final int off, final int len)
+			throws IOException {
+		count += len;
+		out.write(b, off, len);
+		crc.update(b, off, len);
+		md.update(b, off, len);
+	}
+
+	@Override
+	public void flush() throws IOException {
+		out.flush();
+	}
+
+	void writeFileHeader(int version, int objectCount) throws IOException {
+		System.arraycopy(Constants.PACK_SIGNATURE, 0, headerBuffer, 0, 4);
+		NB.encodeInt32(headerBuffer, 4, version);
+		NB.encodeInt32(headerBuffer, 8, objectCount);
+		write(headerBuffer, 0, 12);
+	}
+
+	/**
+	 * Commits the object header onto the stream.
+	 * <p>
+	 * Once the header has been written, the object representation must be fully
+	 * output, or packing must abort abnormally.
+	 *
+	 * @param otp
+	 *            the object to pack. Header information is obtained.
+	 * @param rawLength
+	 *            number of bytes of the inflated content. For an object that is
+	 *            in whole object format, this is the same as the object size.
+	 *            For an object that is in a delta format, this is the size of
+	 *            the inflated delta instruction stream.
+	 * @throws IOException
+	 *             the underlying stream refused to accept the header.
+	 */
+	public void writeHeader(ObjectToPack otp, long rawLength)
+			throws IOException {
+		if (otp.isDeltaRepresentation()) {
+			if (ofsDelta) {
+				ObjectToPack baseInPack = otp.getDeltaBase();
+				if (baseInPack != null && baseInPack.isWritten()) {
+					final long start = count;
+					int n = encodeTypeSize(Constants.OBJ_OFS_DELTA, rawLength);
+					write(headerBuffer, 0, n);
+
+					long offsetDiff = start - baseInPack.getOffset();
+					n = headerBuffer.length - 1;
+					headerBuffer[n] = (byte) (offsetDiff & 0x7F);
+					while ((offsetDiff >>= 7) > 0)
+						headerBuffer[--n] = (byte) (0x80 | (--offsetDiff & 0x7F));
+					write(headerBuffer, n, headerBuffer.length - n);
+					return;
+				}
+			}
+
+			int n = encodeTypeSize(Constants.OBJ_REF_DELTA, rawLength);
+			otp.getDeltaBaseId().copyRawTo(headerBuffer, n);
+			write(headerBuffer, 0, n + Constants.OBJECT_ID_LENGTH);
+		} else {
+			int n = encodeTypeSize(otp.getType(), rawLength);
+			write(headerBuffer, 0, n);
+		}
+	}
+
+	private int encodeTypeSize(int type, long rawLength) {
+		long nextLength = rawLength >>> 4;
+		headerBuffer[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00)
+				| (type << 4) | (rawLength & 0x0F));
+		rawLength = nextLength;
+		int n = 1;
+		while (rawLength > 0) {
+			nextLength >>>= 7;
+			headerBuffer[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (rawLength & 0x7F));
+			rawLength = nextLength;
+		}
+		return n;
+	}
+
+	/** @return a temporary buffer writers can use to copy data with. */
+	public byte[] getCopyBuffer() {
+		if (copyBuffer == null)
+			copyBuffer = new byte[16 * 1024];
+		return copyBuffer;
+	}
+
+	void endObject() {
+		writeMonitor.update(1);
+	}
+
+	/** @return total number of bytes written since stream start. */
+	long length() {
+		return count;
+	}
+
+	/** @return obtain the current CRC32 register. */
+	int getCRC32() {
+		return (int) crc.getValue();
+	}
+
+	/** Reinitialize the CRC32 register for a new region. */
+	void resetCRC32() {
+		crc.reset();
+	}
+
+	/** @return obtain the current SHA-1 digest. */
+	byte[] getDigest() {
+		return md.digest();
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java
new file mode 100644
index 0000000..df5594c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java
@@ -0,0 +1,1106 @@
+/*
+ * Copyright (C) 2008-2010, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import static org.eclipse.jgit.storage.pack.StoredObjectRepresentation.PACK_DELTA;
+import static org.eclipse.jgit.storage.pack.StoredObjectRepresentation.PACK_WHOLE;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSubclassMap;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.ThreadSafeProgressMonitor;
+import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.storage.file.PackIndexWriter;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.TemporaryBuffer;
+
+/**
+ * <p>
+ * PackWriter class is responsible for generating pack files from specified set
+ * of objects from repository. This implementation produce pack files in format
+ * version 2.
+ * </p>
+ * <p>
+ * Source of objects may be specified in two ways:
+ * <ul>
+ * <li>(usually) by providing sets of interesting and uninteresting objects in
+ * repository - all interesting objects and their ancestors except uninteresting
+ * objects and their ancestors will be included in pack, or</li>
+ * <li>by providing iterator of {@link RevObject} specifying exact list and
+ * order of objects in pack</li>
+ * </ul>
+ * Typical usage consists of creating instance intended for some pack,
+ * configuring options, preparing the list of objects by calling
+ * {@link #preparePack(Iterator)} or
+ * {@link #preparePack(ProgressMonitor, Collection, Collection)}, and finally
+ * producing the stream with {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}.
+ * </p>
+ * <p>
+ * Class provide set of configurable options and {@link ProgressMonitor}
+ * support, as operations may take a long time for big repositories. Deltas
+ * searching algorithm is <b>NOT IMPLEMENTED</b> yet - this implementation
+ * relies only on deltas and objects reuse.
+ * </p>
+ * <p>
+ * This class is not thread safe, it is intended to be used in one thread, with
+ * one instance per created pack. Subsequent calls to writePack result in
+ * undefined behavior.
+ * </p>
+ */
+public class PackWriter {
+	private static final int PACK_VERSION_GENERATED = 2;
+
+	@SuppressWarnings("unchecked")
+	private final List<ObjectToPack> objectsLists[] = new List[Constants.OBJ_TAG + 1];
+	{
+		objectsLists[0] = Collections.<ObjectToPack> emptyList();
+		objectsLists[Constants.OBJ_COMMIT] = new ArrayList<ObjectToPack>();
+		objectsLists[Constants.OBJ_TREE] = new ArrayList<ObjectToPack>();
+		objectsLists[Constants.OBJ_BLOB] = new ArrayList<ObjectToPack>();
+		objectsLists[Constants.OBJ_TAG] = new ArrayList<ObjectToPack>();
+	}
+
+	private final ObjectIdSubclassMap<ObjectToPack> objectsMap = new ObjectIdSubclassMap<ObjectToPack>();
+
+	// edge objects for thin packs
+	private final ObjectIdSubclassMap<ObjectToPack> edgeObjects = new ObjectIdSubclassMap<ObjectToPack>();
+
+	private Deflater myDeflater;
+
+	private final ObjectReader reader;
+
+	/** {@link #reader} recast to the reuse interface, if it supports it. */
+	private final ObjectReuseAsIs reuseSupport;
+
+	private final PackConfig config;
+
+	private List<ObjectToPack> sortedByName;
+
+	private byte packcsum[];
+
+	private boolean deltaBaseAsOffset;
+
+	private boolean reuseDeltas;
+
+	private boolean thin;
+
+	private boolean ignoreMissingUninteresting = true;
+
+	/**
+	 * Create writer for specified repository.
+	 * <p>
+	 * Objects for packing are specified in {@link #preparePack(Iterator)} or
+	 * {@link #preparePack(ProgressMonitor, Collection, Collection)}.
+	 *
+	 * @param repo
+	 *            repository where objects are stored.
+	 */
+	public PackWriter(final Repository repo) {
+		this(repo, repo.newObjectReader());
+	}
+
+	/**
+	 * Create a writer to load objects from the specified reader.
+	 * <p>
+	 * Objects for packing are specified in {@link #preparePack(Iterator)} or
+	 * {@link #preparePack(ProgressMonitor, Collection, Collection)}.
+	 *
+	 * @param reader
+	 *            reader to read from the repository with.
+	 */
+	public PackWriter(final ObjectReader reader) {
+		this(new PackConfig(), reader);
+	}
+
+	/**
+	 * Create writer for specified repository.
+	 * <p>
+	 * Objects for packing are specified in {@link #preparePack(Iterator)} or
+	 * {@link #preparePack(ProgressMonitor, Collection, Collection)}.
+	 *
+	 * @param repo
+	 *            repository where objects are stored.
+	 * @param reader
+	 *            reader to read from the repository with.
+	 */
+	public PackWriter(final Repository repo, final ObjectReader reader) {
+		this(new PackConfig(repo), reader);
+	}
+
+	/**
+	 * Create writer with a specified configuration.
+	 * <p>
+	 * Objects for packing are specified in {@link #preparePack(Iterator)} or
+	 * {@link #preparePack(ProgressMonitor, Collection, Collection)}.
+	 *
+	 * @param config
+	 *            configuration for the pack writer.
+	 * @param reader
+	 *            reader to read from the repository with.
+	 */
+	public PackWriter(final PackConfig config, final ObjectReader reader) {
+		this.config = config;
+		this.reader = reader;
+		if (reader instanceof ObjectReuseAsIs)
+			reuseSupport = ((ObjectReuseAsIs) reader);
+		else
+			reuseSupport = null;
+
+		deltaBaseAsOffset = config.isDeltaBaseAsOffset();
+		reuseDeltas = config.isReuseDeltas();
+	}
+
+	/**
+	 * Check whether writer can store delta base as an offset (new style
+	 * reducing pack size) or should store it as an object id (legacy style,
+	 * compatible with old readers).
+	 *
+	 * Default setting: {@value PackConfig#DEFAULT_DELTA_BASE_AS_OFFSET}
+	 *
+	 * @return true if delta base is stored as an offset; false if it is stored
+	 *         as an object id.
+	 */
+	public boolean isDeltaBaseAsOffset() {
+		return deltaBaseAsOffset;
+	}
+
+	/**
+	 * Set writer delta base format. Delta base can be written as an offset in a
+	 * pack file (new approach reducing file size) or as an object id (legacy
+	 * approach, compatible with old readers).
+	 *
+	 * Default setting: {@value PackConfig#DEFAULT_DELTA_BASE_AS_OFFSET}
+	 *
+	 * @param deltaBaseAsOffset
+	 *            boolean indicating whether delta base can be stored as an
+	 *            offset.
+	 */
+	public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) {
+		this.deltaBaseAsOffset = deltaBaseAsOffset;
+	}
+
+	/** @return true if this writer is producing a thin pack. */
+	public boolean isThin() {
+		return thin;
+	}
+
+	/**
+	 * @param packthin
+	 *            a boolean indicating whether writer may pack objects with
+	 *            delta base object not within set of objects to pack, but
+	 *            belonging to party repository (uninteresting/boundary) as
+	 *            determined by set; this kind of pack is used only for
+	 *            transport; true - to produce thin pack, false - otherwise.
+	 */
+	public void setThin(final boolean packthin) {
+		thin = packthin;
+	}
+
+	/**
+	 * @return true to ignore objects that are uninteresting and also not found
+	 *         on local disk; false to throw a {@link MissingObjectException}
+	 *         out of {@link #preparePack(ProgressMonitor, Collection, Collection)} if an
+	 *         uninteresting object is not in the source repository. By default,
+	 *         true, permitting gracefully ignoring of uninteresting objects.
+	 */
+	public boolean isIgnoreMissingUninteresting() {
+		return ignoreMissingUninteresting;
+	}
+
+	/**
+	 * @param ignore
+	 *            true if writer should ignore non existing uninteresting
+	 *            objects during construction set of objects to pack; false
+	 *            otherwise - non existing uninteresting objects may cause
+	 *            {@link MissingObjectException}
+	 */
+	public void setIgnoreMissingUninteresting(final boolean ignore) {
+		ignoreMissingUninteresting = ignore;
+	}
+
+	/**
+	 * Returns objects number in a pack file that was created by this writer.
+	 *
+	 * @return number of objects in pack.
+	 */
+	public int getObjectsNumber() {
+		return objectsMap.size();
+	}
+
+	/**
+	 * Prepare the list of objects to be written to the pack stream.
+	 * <p>
+	 * Iterator <b>exactly</b> determines which objects are included in a pack
+	 * and order they appear in pack (except that objects order by type is not
+	 * needed at input). This order should conform general rules of ordering
+	 * objects in git - by recency and path (type and delta-base first is
+	 * internally secured) and responsibility for guaranteeing this order is on
+	 * a caller side. Iterator must return each id of object to write exactly
+	 * once.
+	 * </p>
+	 * <p>
+	 * When iterator returns object that has {@link RevFlag#UNINTERESTING} flag,
+	 * this object won't be included in an output pack. Instead, it is recorded
+	 * as edge-object (known to remote repository) for thin-pack. In such a case
+	 * writer may pack objects with delta base object not within set of objects
+	 * to pack, but belonging to party repository - those marked with
+	 * {@link RevFlag#UNINTERESTING} flag. This type of pack is used only for
+	 * transport.
+	 * </p>
+	 *
+	 * @param objectsSource
+	 *            iterator of object to store in a pack; order of objects within
+	 *            each type is important, ordering by type is not needed;
+	 *            allowed types for objects are {@link Constants#OBJ_COMMIT},
+	 *            {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and
+	 *            {@link Constants#OBJ_TAG}; objects returned by iterator may
+	 *            be later reused by caller as object id and type are internally
+	 *            copied in each iteration; if object returned by iterator has
+	 *            {@link RevFlag#UNINTERESTING} flag set, it won't be included
+	 *            in a pack, but is considered as edge-object for thin-pack.
+	 * @throws IOException
+	 *             when some I/O problem occur during reading objects.
+	 */
+	public void preparePack(final Iterator<RevObject> objectsSource)
+			throws IOException {
+		while (objectsSource.hasNext()) {
+			addObject(objectsSource.next());
+		}
+	}
+
+	/**
+	 * Prepare the list of objects to be written to the pack stream.
+	 * <p>
+	 * Basing on these 2 sets, another set of objects to put in a pack file is
+	 * created: this set consists of all objects reachable (ancestors) from
+	 * interesting objects, except uninteresting objects and their ancestors.
+	 * This method uses class {@link ObjectWalk} extensively to find out that
+	 * appropriate set of output objects and their optimal order in output pack.
+	 * Order is consistent with general git in-pack rules: sort by object type,
+	 * recency, path and delta-base first.
+	 * </p>
+	 *
+	 * @param countingMonitor
+	 *            progress during object enumeration.
+	 * @param interestingObjects
+	 *            collection of objects to be marked as interesting (start
+	 *            points of graph traversal).
+	 * @param uninterestingObjects
+	 *            collection of objects to be marked as uninteresting (end
+	 *            points of graph traversal).
+	 * @throws IOException
+	 *             when some I/O problem occur during reading objects.
+	 */
+	public void preparePack(ProgressMonitor countingMonitor,
+			final Collection<? extends ObjectId> interestingObjects,
+			final Collection<? extends ObjectId> uninterestingObjects)
+			throws IOException {
+		if (countingMonitor == null)
+			countingMonitor = NullProgressMonitor.INSTANCE;
+		ObjectWalk walker = setUpWalker(interestingObjects,
+				uninterestingObjects);
+		findObjectsToPack(countingMonitor, walker);
+	}
+
+	/**
+	 * Determine if the pack file will contain the requested object.
+	 *
+	 * @param id
+	 *            the object to test the existence of.
+	 * @return true if the object will appear in the output pack file.
+	 */
+	public boolean willInclude(final AnyObjectId id) {
+		return objectsMap.get(id) != null;
+	}
+
+	/**
+	 * Computes SHA-1 of lexicographically sorted objects ids written in this
+	 * pack, as used to name a pack file in repository.
+	 *
+	 * @return ObjectId representing SHA-1 name of a pack that was created.
+	 */
+	public ObjectId computeName() {
+		final byte[] buf = new byte[Constants.OBJECT_ID_LENGTH];
+		final MessageDigest md = Constants.newMessageDigest();
+		for (ObjectToPack otp : sortByName()) {
+			otp.copyRawTo(buf, 0);
+			md.update(buf, 0, Constants.OBJECT_ID_LENGTH);
+		}
+		return ObjectId.fromRaw(md.digest());
+	}
+
+	/**
+	 * Create an index file to match the pack file just written.
+	 * <p>
+	 * This method can only be invoked after {@link #preparePack(Iterator)} or
+	 * {@link #preparePack(ProgressMonitor, Collection, Collection)} has been
+	 * invoked and completed successfully. Writing a corresponding index is an
+	 * optional feature that not all pack users may require.
+	 *
+	 * @param indexStream
+	 *            output for the index data. Caller is responsible for closing
+	 *            this stream.
+	 * @throws IOException
+	 *             the index data could not be written to the supplied stream.
+	 */
+	public void writeIndex(final OutputStream indexStream) throws IOException {
+		final List<ObjectToPack> list = sortByName();
+		final PackIndexWriter iw;
+		int indexVersion = config.getIndexVersion();
+		if (indexVersion <= 0)
+			iw = PackIndexWriter.createOldestPossible(indexStream, list);
+		else
+			iw = PackIndexWriter.createVersion(indexStream, indexVersion);
+		iw.write(list, packcsum);
+	}
+
+	private List<ObjectToPack> sortByName() {
+		if (sortedByName == null) {
+			sortedByName = new ArrayList<ObjectToPack>(objectsMap.size());
+			for (List<ObjectToPack> list : objectsLists) {
+				for (ObjectToPack otp : list)
+					sortedByName.add(otp);
+			}
+			Collections.sort(sortedByName);
+		}
+		return sortedByName;
+	}
+
+	/**
+	 * Write the prepared pack to the supplied stream.
+	 * <p>
+	 * At first, this method collects and sorts objects to pack, then deltas
+	 * search is performed if set up accordingly, finally pack stream is
+	 * written.
+	 * </p>
+	 * <p>
+	 * All reused objects data checksum (Adler32/CRC32) is computed and
+	 * validated against existing checksum.
+	 * </p>
+	 *
+	 * @param compressMonitor
+	 *            progress monitor to report object compression work.
+	 * @param writeMonitor
+	 *            progress monitor to report the number of objects written.
+	 * @param packStream
+	 *            output stream of pack data. The stream should be buffered by
+	 *            the caller. The caller is responsible for closing the stream.
+	 * @throws IOException
+	 *             an error occurred reading a local object's data to include in
+	 *             the pack, or writing compressed object data to the output
+	 *             stream.
+	 */
+	public void writePack(ProgressMonitor compressMonitor,
+			ProgressMonitor writeMonitor, OutputStream packStream)
+			throws IOException {
+		if (compressMonitor == null)
+			compressMonitor = NullProgressMonitor.INSTANCE;
+		if (writeMonitor == null)
+			writeMonitor = NullProgressMonitor.INSTANCE;
+
+		if ((reuseDeltas || config.isReuseObjects()) && reuseSupport != null)
+			searchForReuse();
+		if (config.isDeltaCompress())
+			searchForDeltas(compressMonitor);
+
+		final PackOutputStream out = new PackOutputStream(writeMonitor,
+				packStream, this);
+
+		int objCnt = getObjectsNumber();
+		writeMonitor.beginTask(JGitText.get().writingObjects, objCnt);
+		out.writeFileHeader(PACK_VERSION_GENERATED, objCnt);
+		writeObjects(writeMonitor, out);
+		writeChecksum(out);
+
+		reader.release();
+		writeMonitor.endTask();
+	}
+
+	/** Release all resources used by this writer. */
+	public void release() {
+		reader.release();
+		if (myDeflater != null) {
+			myDeflater.end();
+			myDeflater = null;
+		}
+	}
+
+	private void searchForReuse() throws IOException {
+		for (List<ObjectToPack> list : objectsLists) {
+			for (ObjectToPack otp : list)
+				reuseSupport.selectObjectRepresentation(this, otp);
+		}
+	}
+
+	private void searchForDeltas(ProgressMonitor monitor)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException {
+		// Commits and annotated tags tend to have too many differences to
+		// really benefit from delta compression. Consequently just don't
+		// bother examining those types here.
+		//
+		ObjectToPack[] list = new ObjectToPack[
+				  objectsLists[Constants.OBJ_TREE].size()
+				+ objectsLists[Constants.OBJ_BLOB].size()
+				+ edgeObjects.size()];
+		int cnt = 0;
+		cnt = findObjectsNeedingDelta(list, cnt, Constants.OBJ_TREE);
+		cnt = findObjectsNeedingDelta(list, cnt, Constants.OBJ_BLOB);
+		if (cnt == 0)
+			return;
+
+		// Queue up any edge objects that we might delta against.  We won't
+		// be sending these as we assume the other side has them, but we need
+		// them in the search phase below.
+		//
+		for (ObjectToPack eo : edgeObjects) {
+			try {
+				if (loadSize(eo))
+					list[cnt++] = eo;
+			} catch (IOException notAvailable) {
+				// Skip this object. Since we aren't going to write it out
+				// the only consequence of it being unavailable to us is we
+				// may produce a larger data stream than we could have.
+				//
+				if (!ignoreMissingUninteresting)
+					throw notAvailable;
+			}
+		}
+
+		monitor.beginTask(JGitText.get().compressingObjects, cnt);
+
+		// Sort the objects by path hash so like files are near each other,
+		// and then by size descending so that bigger files are first. This
+		// applies "Linus' Law" which states that newer files tend to be the
+		// bigger ones, because source files grow and hardly ever shrink.
+		//
+		Arrays.sort(list, 0, cnt, new Comparator<ObjectToPack>() {
+			public int compare(ObjectToPack a, ObjectToPack b) {
+				int cmp = a.getType() - b.getType();
+				if (cmp == 0)
+					cmp = (a.getPathHash() >>> 1) - (b.getPathHash() >>> 1);
+				if (cmp == 0)
+					cmp = (a.getPathHash() & 1) - (b.getPathHash() & 1);
+				if (cmp == 0)
+					cmp = b.getWeight() - a.getWeight();
+				return cmp;
+			}
+		});
+		searchForDeltas(monitor, list, cnt);
+		monitor.endTask();
+	}
+
+	private int findObjectsNeedingDelta(ObjectToPack[] list, int cnt, int type)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException {
+		for (ObjectToPack otp : objectsLists[type]) {
+			if (otp.isDoNotDelta()) // delta is disabled for this path
+				continue;
+			if (otp.isDeltaRepresentation()) // already reusing a delta
+				continue;
+			if (loadSize(otp))
+				list[cnt++] = otp;
+		}
+		return cnt;
+	}
+
+	private boolean loadSize(ObjectToPack e) throws MissingObjectException,
+			IncorrectObjectTypeException, IOException {
+		long sz = reader.getObjectSize(e, e.getType());
+
+		// If its too big for us to handle, skip over it.
+		//
+		if (config.getBigFileThreshold() <= sz || Integer.MAX_VALUE <= sz)
+			return false;
+
+		// If its too tiny for the delta compression to work, skip it.
+		//
+		if (sz <= DeltaIndex.BLKSZ)
+			return false;
+
+		e.setWeight((int) sz);
+		return true;
+	}
+
+	private void searchForDeltas(final ProgressMonitor monitor,
+			final ObjectToPack[] list, final int cnt)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			LargeObjectException, IOException {
+		int threads = config.getThreads();
+		if (threads == 0)
+			threads = Runtime.getRuntime().availableProcessors();
+
+		if (threads <= 1 || cnt <= 2 * config.getDeltaSearchWindowSize()) {
+			DeltaCache dc = new DeltaCache(config);
+			DeltaWindow dw = new DeltaWindow(config, dc, reader);
+			dw.search(monitor, list, 0, cnt);
+			return;
+		}
+
+		final DeltaCache dc = new ThreadSafeDeltaCache(config);
+		final ProgressMonitor pm = new ThreadSafeProgressMonitor(monitor);
+
+		// Guess at the size of batch we want. Because we don't really
+		// have a way for a thread to steal work from another thread if
+		// it ends early, we over partition slightly so the work units
+		// are a bit smaller.
+		//
+		int estSize = cnt / (threads * 2);
+		if (estSize < 2 * config.getDeltaSearchWindowSize())
+			estSize = 2 * config.getDeltaSearchWindowSize();
+
+		final List<DeltaTask> myTasks = new ArrayList<DeltaTask>(threads * 2);
+		for (int i = 0; i < cnt;) {
+			final int start = i;
+			final int batchSize;
+
+			if (cnt - i < estSize) {
+				// If we don't have enough to fill the remaining block,
+				// schedule what is left over as a single block.
+				//
+				batchSize = cnt - i;
+			} else {
+				// Try to split the block at the end of a path.
+				//
+				int end = start + estSize;
+				while (end < cnt) {
+					ObjectToPack a = list[end - 1];
+					ObjectToPack b = list[end];
+					if (a.getPathHash() == b.getPathHash())
+						end++;
+					else
+						break;
+				}
+				batchSize = end - start;
+			}
+			i += batchSize;
+			myTasks.add(new DeltaTask(config, reader, dc, pm, batchSize, start, list));
+		}
+
+		final Executor executor = config.getExecutor();
+		final List<Throwable> errors = Collections
+				.synchronizedList(new ArrayList<Throwable>());
+		if (executor instanceof ExecutorService) {
+			// Caller supplied us a service, use it directly.
+			//
+			runTasks((ExecutorService) executor, myTasks, errors);
+
+		} else if (executor == null) {
+			// Caller didn't give us a way to run the tasks, spawn up a
+			// temporary thread pool and make sure it tears down cleanly.
+			//
+			ExecutorService pool = Executors.newFixedThreadPool(threads);
+			try {
+				runTasks(pool, myTasks, errors);
+			} finally {
+				pool.shutdown();
+				for (;;) {
+					try {
+						if (pool.awaitTermination(60, TimeUnit.SECONDS))
+							break;
+					} catch (InterruptedException e) {
+						throw new IOException(
+								JGitText.get().packingCancelledDuringObjectsWriting);
+					}
+				}
+			}
+		} else {
+			// The caller gave us an executor, but it might not do
+			// asynchronous execution.  Wrap everything and hope it
+			// can schedule these for us.
+			//
+			final CountDownLatch done = new CountDownLatch(myTasks.size());
+			for (final DeltaTask task : myTasks) {
+				executor.execute(new Runnable() {
+					public void run() {
+						try {
+							task.call();
+						} catch (Throwable failure) {
+							errors.add(failure);
+						} finally {
+							done.countDown();
+						}
+					}
+				});
+			}
+			try {
+				done.await();
+			} catch (InterruptedException ie) {
+				// We can't abort the other tasks as we have no handle.
+				// Cross our fingers and just break out anyway.
+				//
+				throw new IOException(
+						JGitText.get().packingCancelledDuringObjectsWriting);
+			}
+		}
+
+		// If any task threw an error, try to report it back as
+		// though we weren't using a threaded search algorithm.
+		//
+		if (!errors.isEmpty()) {
+			Throwable err = errors.get(0);
+			if (err instanceof Error)
+				throw (Error) err;
+			if (err instanceof RuntimeException)
+				throw (RuntimeException) err;
+			if (err instanceof IOException)
+				throw (IOException) err;
+
+			IOException fail = new IOException(err.getMessage());
+			fail.initCause(err);
+			throw fail;
+		}
+	}
+
+	private void runTasks(ExecutorService pool, List<DeltaTask> tasks,
+			List<Throwable> errors) throws IOException {
+		List<Future<?>> futures = new ArrayList<Future<?>>(tasks.size());
+		for (DeltaTask task : tasks)
+			futures.add(pool.submit(task));
+
+		try {
+			for (Future<?> f : futures) {
+				try {
+					f.get();
+				} catch (ExecutionException failed) {
+					errors.add(failed.getCause());
+				}
+			}
+		} catch (InterruptedException ie) {
+			for (Future<?> f : futures)
+				f.cancel(true);
+			throw new IOException(
+					JGitText.get().packingCancelledDuringObjectsWriting);
+		}
+	}
+
+	private void writeObjects(ProgressMonitor writeMonitor, PackOutputStream out)
+			throws IOException {
+		for (List<ObjectToPack> list : objectsLists) {
+			for (ObjectToPack otp : list) {
+				if (writeMonitor.isCancelled())
+					throw new IOException(
+							JGitText.get().packingCancelledDuringObjectsWriting);
+				if (!otp.isWritten())
+					writeObject(out, otp);
+			}
+		}
+	}
+
+	private void writeObject(PackOutputStream out, final ObjectToPack otp)
+			throws IOException {
+		if (otp.isWritten())
+			return; // We shouldn't be here.
+
+		otp.markWantWrite();
+		if (otp.isDeltaRepresentation())
+			writeBaseFirst(out, otp);
+
+		out.resetCRC32();
+		otp.setOffset(out.length());
+
+		while (otp.isReuseAsIs()) {
+			try {
+				reuseSupport.copyObjectAsIs(out, otp);
+				out.endObject();
+				otp.setCRC(out.getCRC32());
+				return;
+			} catch (StoredObjectRepresentationNotAvailableException gone) {
+				if (otp.getOffset() == out.length()) {
+					redoSearchForReuse(otp);
+					continue;
+				} else {
+					// Object writing already started, we cannot recover.
+					//
+					CorruptObjectException coe;
+					coe = new CorruptObjectException(otp, "");
+					coe.initCause(gone);
+					throw coe;
+				}
+			}
+		}
+
+		// If we reached here, reuse wasn't possible.
+		//
+		if (otp.isDeltaRepresentation())
+			writeDeltaObjectDeflate(out, otp);
+		else
+			writeWholeObjectDeflate(out, otp);
+		out.endObject();
+		otp.setCRC(out.getCRC32());
+	}
+
+	private void writeBaseFirst(PackOutputStream out, final ObjectToPack otp)
+			throws IOException {
+		ObjectToPack baseInPack = otp.getDeltaBase();
+		if (baseInPack != null) {
+			if (!baseInPack.isWritten()) {
+				if (baseInPack.wantWrite()) {
+					// There is a cycle. Our caller is trying to write the
+					// object we want as a base, and called us. Turn off
+					// delta reuse so we can find another form.
+					//
+					reuseDeltas = false;
+					redoSearchForReuse(otp);
+					reuseDeltas = true;
+				} else {
+					writeObject(out, baseInPack);
+				}
+			}
+		} else if (!thin) {
+			// This should never occur, the base isn't in the pack and
+			// the pack isn't allowed to reference base outside objects.
+			// Write the object as a whole form, even if that is slow.
+			//
+			otp.clearDeltaBase();
+			otp.clearReuseAsIs();
+		}
+	}
+
+	private void redoSearchForReuse(final ObjectToPack otp) throws IOException,
+			MissingObjectException {
+		otp.clearDeltaBase();
+		otp.clearReuseAsIs();
+		reuseSupport.selectObjectRepresentation(this, otp);
+	}
+
+	private void writeWholeObjectDeflate(PackOutputStream out,
+			final ObjectToPack otp) throws IOException {
+		final Deflater deflater = deflater();
+		final ObjectLoader ldr = reader.open(otp, otp.getType());
+
+		out.writeHeader(otp, ldr.getSize());
+
+		deflater.reset();
+		DeflaterOutputStream dst = new DeflaterOutputStream(out, deflater);
+		ldr.copyTo(dst);
+		dst.finish();
+	}
+
+	private void writeDeltaObjectDeflate(PackOutputStream out,
+			final ObjectToPack otp) throws IOException {
+		DeltaCache.Ref ref = otp.popCachedDelta();
+		if (ref != null) {
+			byte[] zbuf = ref.get();
+			if (zbuf != null) {
+				out.writeHeader(otp, otp.getCachedSize());
+				out.write(zbuf);
+				return;
+			}
+		}
+
+		TemporaryBuffer.Heap delta = delta(otp);
+		out.writeHeader(otp, delta.length());
+
+		Deflater deflater = deflater();
+		deflater.reset();
+		DeflaterOutputStream dst = new DeflaterOutputStream(out, deflater);
+		delta.writeTo(dst, null);
+		dst.finish();
+	}
+
+	private TemporaryBuffer.Heap delta(final ObjectToPack otp)
+			throws IOException {
+		DeltaIndex index = new DeltaIndex(buffer(otp.getDeltaBaseId()));
+		byte[] res = buffer(otp);
+
+		// We never would have proposed this pair if the delta would be
+		// larger than the unpacked version of the object. So using it
+		// as our buffer limit is valid: we will never reach it.
+		//
+		TemporaryBuffer.Heap delta = new TemporaryBuffer.Heap(res.length);
+		index.encode(delta, res);
+		return delta;
+	}
+
+	private byte[] buffer(AnyObjectId objId) throws IOException {
+		return buffer(config, reader, objId);
+	}
+
+	static byte[] buffer(PackConfig config, ObjectReader or, AnyObjectId objId)
+			throws IOException {
+		ObjectLoader ldr = or.open(objId);
+		if (!ldr.isLarge())
+			return ldr.getCachedBytes();
+
+		// PackWriter should have already pruned objects that
+		// are above the big file threshold, so our chances of
+		// the object being below it are very good. We really
+		// shouldn't be here, unless the implementation is odd.
+
+		// If it really is too big to work with, abort out now.
+		//
+		long sz = ldr.getSize();
+		if (config.getBigFileThreshold() <= sz || Integer.MAX_VALUE < sz)
+			throw new LargeObjectException(objId.copy());
+
+		// Its considered to be large by the loader, but we really
+		// want it in byte array format. Try to make it happen.
+		//
+		byte[] buf;
+		try {
+			buf = new byte[(int) sz];
+		} catch (OutOfMemoryError noMemory) {
+			LargeObjectException e;
+
+			e = new LargeObjectException(objId.copy());
+			e.initCause(noMemory);
+			throw e;
+		}
+		InputStream in = ldr.openStream();
+		try {
+			IO.readFully(in, buf, 0, buf.length);
+		} finally {
+			in.close();
+		}
+		return buf;
+	}
+
+	private Deflater deflater() {
+		if (myDeflater == null)
+			myDeflater = new Deflater(config.getCompressionLevel());
+		return myDeflater;
+	}
+
+	private void writeChecksum(PackOutputStream out) throws IOException {
+		packcsum = out.getDigest();
+		out.write(packcsum);
+	}
+
+	private ObjectWalk setUpWalker(
+			final Collection<? extends ObjectId> interestingObjects,
+			final Collection<? extends ObjectId> uninterestingObjects)
+			throws MissingObjectException, IOException,
+			IncorrectObjectTypeException {
+		final ObjectWalk walker = new ObjectWalk(reader);
+		walker.setRetainBody(false);
+		walker.sort(RevSort.COMMIT_TIME_DESC);
+		if (thin)
+			walker.sort(RevSort.BOUNDARY, true);
+
+		for (ObjectId id : interestingObjects) {
+			RevObject o = walker.parseAny(id);
+			walker.markStart(o);
+		}
+		if (uninterestingObjects != null) {
+			for (ObjectId id : uninterestingObjects) {
+				final RevObject o;
+				try {
+					o = walker.parseAny(id);
+				} catch (MissingObjectException x) {
+					if (ignoreMissingUninteresting)
+						continue;
+					throw x;
+				}
+				walker.markUninteresting(o);
+			}
+		}
+		return walker;
+	}
+
+	private void findObjectsToPack(final ProgressMonitor countingMonitor,
+			final ObjectWalk walker) throws MissingObjectException,
+			IncorrectObjectTypeException,			IOException {
+		countingMonitor.beginTask(JGitText.get().countingObjects,
+				ProgressMonitor.UNKNOWN);
+		RevObject o;
+
+		while ((o = walker.next()) != null) {
+			addObject(o, 0);
+			countingMonitor.update(1);
+		}
+		while ((o = walker.nextObject()) != null) {
+			addObject(o, walker.getPathHashCode());
+			countingMonitor.update(1);
+		}
+		countingMonitor.endTask();
+	}
+
+	/**
+	 * Include one object to the output file.
+	 * <p>
+	 * Objects are written in the order they are added. If the same object is
+	 * added twice, it may be written twice, creating a larger than necessary
+	 * file.
+	 *
+	 * @param object
+	 *            the object to add.
+	 * @throws IncorrectObjectTypeException
+	 *             the object is an unsupported type.
+	 */
+	public void addObject(final RevObject object)
+			throws IncorrectObjectTypeException {
+		addObject(object, 0);
+	}
+
+	private void addObject(final RevObject object, final int pathHashCode)
+			throws IncorrectObjectTypeException {
+		if (object.has(RevFlag.UNINTERESTING)) {
+			switch (object.getType()) {
+			case Constants.OBJ_TREE:
+			case Constants.OBJ_BLOB:
+				ObjectToPack otp = new ObjectToPack(object);
+				otp.setPathHash(pathHashCode);
+				otp.setDoNotDelta(true);
+				edgeObjects.add(otp);
+				thin = true;
+				break;
+			}
+			return;
+		}
+
+		final ObjectToPack otp;
+		if (reuseSupport != null)
+			otp = reuseSupport.newObjectToPack(object);
+		else
+			otp = new ObjectToPack(object);
+		otp.setPathHash(pathHashCode);
+
+		try {
+			objectsLists[object.getType()].add(otp);
+		} catch (ArrayIndexOutOfBoundsException x) {
+			throw new IncorrectObjectTypeException(object,
+					JGitText.get().incorrectObjectType_COMMITnorTREEnorBLOBnorTAG);
+		} catch (UnsupportedOperationException x) {
+			// index pointing to "dummy" empty list
+			throw new IncorrectObjectTypeException(object,
+					JGitText.get().incorrectObjectType_COMMITnorTREEnorBLOBnorTAG);
+		}
+		objectsMap.add(otp);
+	}
+
+	/**
+	 * Select an object representation for this writer.
+	 * <p>
+	 * An {@link ObjectReader} implementation should invoke this method once for
+	 * each representation available for an object, to allow the writer to find
+	 * the most suitable one for the output.
+	 *
+	 * @param otp
+	 *            the object being packed.
+	 * @param next
+	 *            the next available representation from the repository.
+	 */
+	public void select(ObjectToPack otp, StoredObjectRepresentation next) {
+		int nFmt = next.getFormat();
+		int nWeight;
+		if (otp.isReuseAsIs()) {
+			// We've already chosen to reuse a packed form, if next
+			// cannot beat that break out early.
+			//
+			if (PACK_WHOLE < nFmt)
+				return; // next isn't packed
+			else if (PACK_DELTA < nFmt && otp.isDeltaRepresentation())
+				return; // next isn't a delta, but we are
+
+			nWeight = next.getWeight();
+			if (otp.getWeight() <= nWeight)
+				return; // next would be bigger
+		} else
+			nWeight = next.getWeight();
+
+		if (nFmt == PACK_DELTA && reuseDeltas) {
+			ObjectId baseId = next.getDeltaBase();
+			ObjectToPack ptr = objectsMap.get(baseId);
+			if (ptr != null) {
+				otp.setDeltaBase(ptr);
+				otp.setReuseAsIs();
+				otp.setWeight(nWeight);
+			} else if (thin && edgeObjects.contains(baseId)) {
+				otp.setDeltaBase(baseId);
+				otp.setReuseAsIs();
+				otp.setWeight(nWeight);
+			} else {
+				otp.clearDeltaBase();
+				otp.clearReuseAsIs();
+			}
+		} else if (nFmt == PACK_WHOLE && config.isReuseObjects()) {
+			otp.clearDeltaBase();
+			otp.setReuseAsIs();
+			otp.setWeight(nWeight);
+		} else {
+			otp.clearDeltaBase();
+			otp.clearReuseAsIs();
+		}
+
+		otp.select(next);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/StoredObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/StoredObjectRepresentation.java
new file mode 100644
index 0000000..334ea5e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/StoredObjectRepresentation.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.pack;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * An object representation {@link PackWriter} can consider for packing.
+ */
+public class StoredObjectRepresentation {
+	/** Special unknown value for {@link #getWeight()}. */
+	public static final int WEIGHT_UNKNOWN = Integer.MAX_VALUE;
+
+	/** Stored in pack format, as a delta to another object. */
+	public static final int PACK_DELTA = 0;
+
+	/** Stored in pack format, without delta. */
+	public static final int PACK_WHOLE = 1;
+
+	/** Only available after inflating to canonical format. */
+	public static final int FORMAT_OTHER = 2;
+
+	/**
+	 * @return relative size of this object's packed form. The special value
+	 *         {@link #WEIGHT_UNKNOWN} can be returned to indicate the
+	 *         implementation doesn't know, or cannot supply the weight up
+	 *         front.
+	 */
+	public int getWeight() {
+		return WEIGHT_UNKNOWN;
+	}
+
+	/**
+	 * @return true if this is a delta against another object and this is stored
+	 *         in pack delta format.
+	 */
+	public int getFormat() {
+		return FORMAT_OTHER;
+	}
+
+	/**
+	 * @return identity of the object this delta applies to in order to recover
+	 *         the original object content. This method should only be called if
+	 *         {@link #getFormat()} returned {@link #PACK_DELTA}.
+	 */
+	public ObjectId getDeltaBase() {
+		return null;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ThreadSafeDeltaCache.java
similarity index 70%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ThreadSafeDeltaCache.java
index 495049c..2492a05 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ThreadSafeDeltaCache.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,30 +41,46 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.storage.pack;
 
-/**
- * This class passes information about changed refs to a
- * {@link RepositoryListener}
- *
- * Currently only a reference to the repository is passed.
- */
-public class RepositoryChangedEvent {
-	private final Repository repository;
+import java.util.concurrent.locks.ReentrantLock;
 
-	RepositoryChangedEvent(final Repository repository) {
-		this.repository = repository;
-	}
+class ThreadSafeDeltaCache extends DeltaCache {
+	private final ReentrantLock lock;
 
-	/**
-	 * @return the repository that was changed
-	 */
-	public Repository getRepository() {
-		return repository;
+	ThreadSafeDeltaCache(PackConfig pc) {
+		super(pc);
+		lock = new ReentrantLock();
 	}
 
 	@Override
-	public String toString() {
-		return "RepositoryChangedEvent[" + repository + "]";
+	boolean canCache(int length, ObjectToPack src, ObjectToPack res) {
+		lock.lock();
+		try {
+			return super.canCache(length, src, res);
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	@Override
+	void credit(int reservedSize) {
+		lock.lock();
+		try {
+			super.credit(reservedSize);
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	@Override
+	Ref cache(byte[] data, int actLen, int reservedSize) {
+		data = resize(data, actLen);
+		lock.lock();
+		try {
+			return super.cache(data, actLen, reservedSize);
+		} finally {
+			lock.unlock();
+		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
index 2819ae2..af18f18 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -61,7 +61,6 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PackLock;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Config.SectionParser;
@@ -73,6 +72,7 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
 import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.storage.file.PackLock;
 import org.eclipse.jgit.transport.PacketLineIn.AckNackResult;
 import org.eclipse.jgit.util.TemporaryBuffer;
 
@@ -270,6 +270,12 @@ protected void doFetch(final ProgressMonitor monitor,
 		}
 	}
 
+	@Override
+	public void close() {
+		walk.release();
+		super.close();
+	}
+
 	private int maxTimeWanted(final Collection<Ref> wants) {
 		int maxTime = 0;
 		for (final Ref r : wants) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
index 44ccd2d..0838f29 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -48,6 +48,7 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 import org.eclipse.jgit.JGitText;
@@ -56,9 +57,9 @@
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PackWriter;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.storage.pack.PackWriter;
 import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
 
 /**
@@ -226,25 +227,30 @@ private String enableCapabilities(final ProgressMonitor monitor) {
 
 	private void writePack(final Map<String, RemoteRefUpdate> refUpdates,
 			final ProgressMonitor monitor) throws IOException {
-		final PackWriter writer = new PackWriter(local, monitor);
-		final ArrayList<ObjectId> remoteObjects = new ArrayList<ObjectId>(
-				getRefs().size());
-		final ArrayList<ObjectId> newObjects = new ArrayList<ObjectId>(
-				refUpdates.size());
+		List<ObjectId> remoteObjects = new ArrayList<ObjectId>(getRefs().size());
+		List<ObjectId> newObjects = new ArrayList<ObjectId>(refUpdates.size());
 
-		for (final Ref r : getRefs())
-			remoteObjects.add(r.getObjectId());
-		remoteObjects.addAll(additionalHaves);
-		for (final RemoteRefUpdate r : refUpdates.values()) {
-			if (!ObjectId.zeroId().equals(r.getNewObjectId()))
-				newObjects.add(r.getNewObjectId());
+		final long start;
+		final PackWriter writer = new PackWriter(transport.getPackConfig(),
+				local.newObjectReader());
+		try {
+
+			for (final Ref r : getRefs())
+				remoteObjects.add(r.getObjectId());
+			remoteObjects.addAll(additionalHaves);
+			for (final RemoteRefUpdate r : refUpdates.values()) {
+				if (!ObjectId.zeroId().equals(r.getNewObjectId()))
+					newObjects.add(r.getNewObjectId());
+			}
+
+			writer.setThin(thinPack);
+			writer.setDeltaBaseAsOffset(capableOfsDelta);
+			writer.preparePack(monitor, newObjects, remoteObjects);
+			start = System.currentTimeMillis();
+			writer.writePack(monitor, monitor, out);
+		} finally {
+			writer.release();
 		}
-
-		writer.setThin(thinPack);
-		writer.setDeltaBaseAsOffset(capableOfsDelta);
-		writer.preparePack(newObjects, remoteObjects);
-		final long start = System.currentTimeMillis();
-		writer.writePack(out);
 		out.flush();
 		packTransferTime = System.currentTimeMillis() - start;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
index 3b97dfc..126acab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
@@ -68,13 +68,13 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.PackLock;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevFlag;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.PackLock;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
 
@@ -213,57 +213,65 @@ private void verifyPrerequisites() throws TransportException {
 			return;
 
 		final RevWalk rw = new RevWalk(transport.local);
-		final RevFlag PREREQ = rw.newFlag("PREREQ");
-		final RevFlag SEEN = rw.newFlag("SEEN");
-
-		final Map<ObjectId, String> missing = new HashMap<ObjectId, String>();
-		final List<RevObject> commits = new ArrayList<RevObject>();
-		for (final Map.Entry<ObjectId, String> e : prereqs.entrySet()) {
-			ObjectId p = e.getKey();
-			try {
-				final RevCommit c = rw.parseCommit(p);
-				if (!c.has(PREREQ)) {
-					c.add(PREREQ);
-					commits.add(c);
-				}
-			} catch (MissingObjectException notFound) {
-				missing.put(p, e.getValue());
-			} catch (IOException err) {
-				throw new TransportException(transport.uri
-						, MessageFormat.format(JGitText.get().cannotReadCommit, p.name()), err);
-			}
-		}
-		if (!missing.isEmpty())
-			throw new MissingBundlePrerequisiteException(transport.uri, missing);
-
-		for (final Ref r : transport.local.getAllRefs().values()) {
-			try {
-				rw.markStart(rw.parseCommit(r.getObjectId()));
-			} catch (IOException readError) {
-				// If we cannot read the value of the ref skip it.
-			}
-		}
-
-		int remaining = commits.size();
 		try {
-			RevCommit c;
-			while ((c = rw.next()) != null) {
-				if (c.has(PREREQ)) {
-					c.add(SEEN);
-					if (--remaining == 0)
-						break;
+			final RevFlag PREREQ = rw.newFlag("PREREQ");
+			final RevFlag SEEN = rw.newFlag("SEEN");
+
+			final Map<ObjectId, String> missing = new HashMap<ObjectId, String>();
+			final List<RevObject> commits = new ArrayList<RevObject>();
+			for (final Map.Entry<ObjectId, String> e : prereqs.entrySet()) {
+				ObjectId p = e.getKey();
+				try {
+					final RevCommit c = rw.parseCommit(p);
+					if (!c.has(PREREQ)) {
+						c.add(PREREQ);
+						commits.add(c);
+					}
+				} catch (MissingObjectException notFound) {
+					missing.put(p, e.getValue());
+				} catch (IOException err) {
+					throw new TransportException(transport.uri, MessageFormat
+							.format(JGitText.get().cannotReadCommit, p.name()),
+							err);
 				}
 			}
-		} catch (IOException err) {
-			throw new TransportException(transport.uri, JGitText.get().cannotReadObject, err);
-		}
+			if (!missing.isEmpty())
+				throw new MissingBundlePrerequisiteException(transport.uri,
+						missing);
 
-		if (remaining > 0) {
-			for (final RevObject o : commits) {
-				if (!o.has(SEEN))
-					missing.put(o, prereqs.get(o));
+			for (final Ref r : transport.local.getAllRefs().values()) {
+				try {
+					rw.markStart(rw.parseCommit(r.getObjectId()));
+				} catch (IOException readError) {
+					// If we cannot read the value of the ref skip it.
+				}
 			}
-			throw new MissingBundlePrerequisiteException(transport.uri, missing);
+
+			int remaining = commits.size();
+			try {
+				RevCommit c;
+				while ((c = rw.next()) != null) {
+					if (c.has(PREREQ)) {
+						c.add(SEEN);
+						if (--remaining == 0)
+							break;
+					}
+				}
+			} catch (IOException err) {
+				throw new TransportException(transport.uri,
+						JGitText.get().cannotReadObject, err);
+			}
+
+			if (remaining > 0) {
+				for (final RevObject o : commits) {
+					if (!o.has(SEEN))
+						missing.put(o, prereqs.get(o));
+				}
+				throw new MissingBundlePrerequisiteException(transport.uri,
+						missing);
+			}
+		} finally {
+			rw.release();
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
index 7e91557..b513412 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
@@ -57,11 +57,12 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PackWriter;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.storage.pack.PackWriter;
 
 /**
  * Creates a Git bundle file, for sneaker-net transport to another system.
@@ -81,27 +82,38 @@
  * overall bundle size.
  */
 public class BundleWriter {
-	private final PackWriter packWriter;
+	private final Repository db;
 
 	private final Map<String, ObjectId> include;
 
 	private final Set<RevCommit> assume;
 
+	private PackConfig packConfig;
+
 	/**
 	 * Create a writer for a bundle.
 	 *
 	 * @param repo
 	 *            repository where objects are stored.
-	 * @param monitor
-	 *            operations progress monitor.
 	 */
-	public BundleWriter(final Repository repo, final ProgressMonitor monitor) {
-		packWriter = new PackWriter(repo, monitor);
+	public BundleWriter(final Repository repo) {
+		db = repo;
 		include = new TreeMap<String, ObjectId>();
 		assume = new HashSet<RevCommit>();
 	}
 
 	/**
+	 * Set the configuration used by the pack generator.
+	 *
+	 * @param pc
+	 *            configuration controlling packing parameters. If null the
+	 *            source repository's settings will be used.
+	 */
+	public void setPackConfig(PackConfig pc) {
+		this.packConfig = pc;
+	}
+
+	/**
 	 * Include an object (and everything reachable from it) in the bundle.
 	 *
 	 * @param name
@@ -155,6 +167,8 @@ public void assume(final RevCommit c) {
 	 * <p>
 	 * This method can only be called once per BundleWriter instance.
 	 *
+	 * @param monitor
+	 *            progress monitor to report bundle writing status to.
 	 * @param os
 	 *            the stream the bundle is written to. The stream should be
 	 *            buffered by the caller. The caller is responsible for closing
@@ -164,38 +178,47 @@ public void assume(final RevCommit c) {
 	 *             the bundle, or writing compressed object data to the output
 	 *             stream.
 	 */
-	public void writeBundle(OutputStream os) throws IOException {
-		final HashSet<ObjectId> inc = new HashSet<ObjectId>();
-		final HashSet<ObjectId> exc = new HashSet<ObjectId>();
-		inc.addAll(include.values());
-		for (final RevCommit r : assume)
-			exc.add(r.getId());
-		packWriter.setThin(exc.size() > 0);
-		packWriter.preparePack(inc, exc);
+	public void writeBundle(ProgressMonitor monitor, OutputStream os)
+			throws IOException {
+		PackConfig pc = packConfig;
+		if (pc == null)
+			pc = new PackConfig(db);
+		PackWriter packWriter = new PackWriter(pc, db.newObjectReader());
+		try {
+			final HashSet<ObjectId> inc = new HashSet<ObjectId>();
+			final HashSet<ObjectId> exc = new HashSet<ObjectId>();
+			inc.addAll(include.values());
+			for (final RevCommit r : assume)
+				exc.add(r.getId());
+			packWriter.setThin(exc.size() > 0);
+			packWriter.preparePack(monitor, inc, exc);
 
-		final Writer w = new OutputStreamWriter(os, Constants.CHARSET);
-		w.write(TransportBundle.V2_BUNDLE_SIGNATURE);
-		w.write('\n');
+			final Writer w = new OutputStreamWriter(os, Constants.CHARSET);
+			w.write(TransportBundle.V2_BUNDLE_SIGNATURE);
+			w.write('\n');
 
-		final char[] tmp = new char[Constants.OBJECT_ID_STRING_LENGTH];
-		for (final RevCommit a : assume) {
-			w.write('-');
-			a.copyTo(tmp, w);
-			if (a.getRawBuffer() != null) {
-				w.write(' ');
-				w.write(a.getShortMessage());
+			final char[] tmp = new char[Constants.OBJECT_ID_STRING_LENGTH];
+			for (final RevCommit a : assume) {
+				w.write('-');
+				a.copyTo(tmp, w);
+				if (a.getRawBuffer() != null) {
+					w.write(' ');
+					w.write(a.getShortMessage());
+				}
+				w.write('\n');
 			}
-			w.write('\n');
-		}
-		for (final Map.Entry<String, ObjectId> e : include.entrySet()) {
-			e.getValue().copyTo(tmp, w);
-			w.write(' ');
-			w.write(e.getKey());
-			w.write('\n');
-		}
+			for (final Map.Entry<String, ObjectId> e : include.entrySet()) {
+				e.getValue().copyTo(tmp, w);
+				w.write(' ');
+				w.write(e.getKey());
+				w.write('\n');
+			}
 
-		w.write('\n');
-		w.flush();
-		packWriter.writePack(os);
+			w.write('\n');
+			w.flush();
+			packWriter.writePack(monitor, monitor, os);
+		} finally {
+			packWriter.release();
+		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
index aa2e252..0bc5fb3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
@@ -63,6 +63,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryCache;
 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.storage.pack.PackConfig;
 import org.eclipse.jgit.util.FS;
 
 /** Basic daemon for the anonymous <code>git://</code> transport protocol. */
@@ -90,6 +91,8 @@ public class Daemon {
 
 	private int timeout;
 
+	private PackConfig packConfig;
+
 	/** Configure a daemon to listen on any available network port. */
 	public Daemon() {
 		this(null);
@@ -120,6 +123,7 @@ protected void execute(final DaemonClient dc,
 						final UploadPack rp = new UploadPack(db);
 						final InputStream in = dc.getInputStream();
 						rp.setTimeout(Daemon.this.getTimeout());
+						rp.setPackConfig(Daemon.this.packConfig);
 						rp.upload(in, dc.getOutputStream(), null);
 					}
 				}, new DaemonService("receive-pack", "receivepack") {
@@ -243,6 +247,17 @@ public void setTimeout(final int seconds) {
 	}
 
 	/**
+	 * Set the configuration used by the pack generator.
+	 *
+	 * @param pc
+	 *            configuration controlling packing parameters. If null the
+	 *            source repository's settings will be used.
+	 */
+	public void setPackConfig(PackConfig pc) {
+		this.packConfig = pc;
+	}
+
+	/**
 	 * Start this daemon on a background thread.
 	 *
 	 * @throws IOException
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java
index 50c0866..9dc54da 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java
@@ -51,9 +51,9 @@
 
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PackLock;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.storage.file.PackLock;
 
 /**
  * Lists known refs from the remote and copies objects of selected refs.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
index fc203f6..f747616 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -63,14 +63,14 @@
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.LockFile;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PackLock;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.ObjectWalk;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.LockFile;
+import org.eclipse.jgit.storage.file.PackLock;
 
 class FetchProcess {
 	/** Transport we will fetch over. */
@@ -176,16 +176,21 @@ else if (tagopt == TagOpt.FETCH_TAGS)
 		}
 
 		final RevWalk walk = new RevWalk(transport.local);
-		if (transport.isRemoveDeletedRefs())
-			deleteStaleTrackingRefs(result, walk);
-		for (TrackingRefUpdate u : localUpdates) {
-			try {
-				u.update(walk);
-				result.add(u);
-			} catch (IOException err) {
-				throw new TransportException(MessageFormat.format(
-						JGitText.get().failureUpdatingTrackingRef, u.getLocalName(), err.getMessage()), err);
+		try {
+			if (transport.isRemoveDeletedRefs())
+				deleteStaleTrackingRefs(result, walk);
+			for (TrackingRefUpdate u : localUpdates) {
+				try {
+					u.update(walk);
+					result.add(u);
+				} catch (IOException err) {
+					throw new TransportException(MessageFormat.format(JGitText
+							.get().failureUpdatingTrackingRef,
+							u.getLocalName(), err.getMessage()), err);
+				}
 			}
+		} finally {
+			walk.release();
 		}
 
 		if (!fetchHeadUpdates.isEmpty()) {
@@ -271,8 +276,11 @@ private void removeFetchHeadRecord(final ObjectId want) {
 	}
 
 	private void updateFETCH_HEAD(final FetchResult result) throws IOException {
-		final LockFile lock = new LockFile(new File(transport.local
-				.getDirectory(), "FETCH_HEAD"));
+		File meta = transport.local.getDirectory();
+		if (meta == null)
+			return;
+		final LockFile lock = new LockFile(new File(meta, "FETCH_HEAD"),
+				transport.local.getFS());
 		try {
 			if (lock.lock()) {
 				final Writer w = new OutputStreamWriter(lock.getOutputStream());
@@ -294,11 +302,15 @@ private void updateFETCH_HEAD(final FetchResult result) throws IOException {
 	private boolean askForIsComplete() throws TransportException {
 		try {
 			final ObjectWalk ow = new ObjectWalk(transport.local);
-			for (final ObjectId want : askFor.keySet())
-				ow.markStart(ow.parseAny(want));
-			for (final Ref ref : transport.local.getAllRefs().values())
-				ow.markUninteresting(ow.parseAny(ref.getObjectId()));
-			ow.checkConnectivity();
+			try {
+				for (final ObjectId want : askFor.keySet())
+					ow.markStart(ow.parseAny(want));
+				for (final Ref ref : transport.local.getAllRefs().values())
+					ow.markUninteresting(ow.parseAny(ref.getObjectId()));
+				ow.checkConnectivity();
+			} finally {
+				ow.release();
+			}
 			return true;
 		} catch (MissingObjectException e) {
 			return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java
index 491227d..2daa105 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java
@@ -65,8 +65,8 @@
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.BinaryDelta;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig;
 import org.eclipse.jgit.lib.InflaterCache;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectChecker;
@@ -74,11 +74,12 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdSubclassMap;
 import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.PackIndexWriter;
-import org.eclipse.jgit.lib.PackLock;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.storage.file.PackIndexWriter;
+import org.eclipse.jgit.storage.file.PackLock;
+import org.eclipse.jgit.storage.pack.BinaryDelta;
 import org.eclipse.jgit.util.NB;
 
 /** Indexes Git pack files for local use. */
@@ -127,7 +128,8 @@ public static IndexPack create(final Repository db, final InputStream is)
 
 		base = new File(objdir, n.substring(0, n.length() - suffix.length()));
 		final IndexPack ip = new IndexPack(db, is, base);
-		ip.setIndexVersion(db.getConfig().getCore().getPackIndexVersion());
+		ip.setIndexVersion(db.getConfig().get(CoreConfig.KEY)
+				.getPackIndexVersion());
 		return ip;
 	}
 
@@ -224,7 +226,7 @@ private static enum Source {
 	/** If {@link #fixThin} this is the last byte of the original checksum. */
 	private long originalEOF;
 
-	private WindowCursor readCurs;
+	private ObjectReader readCurs;
 
 	/**
 	 * Create a new pack indexer utility.
@@ -243,7 +245,7 @@ public IndexPack(final Repository db, final InputStream src,
 		objectDatabase = db.getObjectDatabase().newCachedDatabase();
 		in = src;
 		inflater = InflaterCache.get();
-		readCurs = new WindowCursor();
+		readCurs = objectDatabase.newReader();
 		buf = new byte[BUFFER_SIZE];
 		skipBuffer = new byte[512];
 		objectDigest = Constants.newMessageDigest();
@@ -436,12 +438,18 @@ public void index(final ProgressMonitor progress) throws IOException {
 
 			} finally {
 				try {
+					if (readCurs != null)
+						readCurs.release();
+				} finally {
+					readCurs = null;
+				}
+
+				try {
 					InflaterCache.release(inflater);
 				} finally {
 					inflater = null;
 					objectDatabase.close();
 				}
-				readCurs = WindowCursor.release(readCurs);
 
 				progress.endTask();
 				if (packOut != null)
@@ -598,8 +606,10 @@ private void fixThinPack(final ProgressMonitor progress) throws IOException {
 				continue;
 			if (needBaseObjectIds)
 				baseObjectIds.add(baseId);
-			final ObjectLoader ldr = repo.openObject(readCurs, baseId);
-			if (ldr == null) {
+			final ObjectLoader ldr;
+			try {
+				ldr = readCurs.open(baseId);
+			} catch (MissingObjectException notFound) {
 				missing.add(baseId);
 				continue;
 			}
@@ -858,12 +868,16 @@ private void verifySafeObject(final AnyObjectId id, final int type,
 			}
 		}
 
-		final ObjectLoader ldr = objectDatabase.openObject(readCurs, id);
-		if (ldr != null) {
+		try {
+			final ObjectLoader ldr = readCurs.open(id, type);
 			final byte[] existingData = ldr.getCachedBytes();
-			if (ldr.getType() != type || !Arrays.equals(data, existingData)) {
+			if (!Arrays.equals(data, existingData)) {
 				throw new IOException(MessageFormat.format(JGitText.get().collisionOn, id.name()));
 			}
+		} catch (MissingObjectException notLocal) {
+			// This is OK, we don't have a copy of the object locally
+			// but the API throws when we try to read it as usually its
+			// an error to read something that doesn't exist.
 		}
 	}
 
@@ -1087,7 +1101,7 @@ public PackLock renameAndOpenPack(final String lockMessage)
 		final File packDir = new File(repo.getObjectsDirectory(), "pack");
 		final File finalPack = new File(packDir, "pack-" + name + ".pack");
 		final File finalIdx = new File(packDir, "pack-" + name + ".idx");
-		final PackLock keep = new PackLock(finalPack);
+		final PackLock keep = new PackLock(finalPack, repo.getFS());
 
 		if (!packDir.exists() && !packDir.mkdir() && !packDir.exists()) {
 			// The objects/pack directory isn't present, and we are unable
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
index 02497cb..6cd796a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -122,32 +122,38 @@ class PushProcess {
 	 */
 	PushResult execute(final ProgressMonitor monitor)
 			throws NotSupportedException, TransportException {
-		monitor.beginTask(PROGRESS_OPENING_CONNECTION, ProgressMonitor.UNKNOWN);
-
-		final PushResult res = new PushResult();
-		connection = transport.openPush();
 		try {
-			res.setAdvertisedRefs(transport.getURI(), connection.getRefsMap());
-			res.setRemoteUpdates(toPush);
-			monitor.endTask();
+			monitor.beginTask(PROGRESS_OPENING_CONNECTION,
+					ProgressMonitor.UNKNOWN);
 
-			final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates();
-			if (transport.isDryRun())
-				modifyUpdatesForDryRun();
-			else if (!preprocessed.isEmpty())
-				connection.push(monitor, preprocessed);
+			final PushResult res = new PushResult();
+			connection = transport.openPush();
+			try {
+				res.setAdvertisedRefs(transport.getURI(), connection
+						.getRefsMap());
+				res.setRemoteUpdates(toPush);
+				monitor.endTask();
+
+				final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates();
+				if (transport.isDryRun())
+					modifyUpdatesForDryRun();
+				else if (!preprocessed.isEmpty())
+					connection.push(monitor, preprocessed);
+			} finally {
+				connection.close();
+				res.addMessages(connection.getMessages());
+			}
+			if (!transport.isDryRun())
+				updateTrackingRefs();
+			for (final RemoteRefUpdate rru : toPush.values()) {
+				final TrackingRefUpdate tru = rru.getTrackingRefUpdate();
+				if (tru != null)
+					res.add(tru);
+			}
+			return res;
 		} finally {
-			connection.close();
-			res.addMessages(connection.getMessages());
+			walker.release();
 		}
-		if (!transport.isDryRun())
-			updateTrackingRefs();
-		for (final RemoteRefUpdate rru : toPush.values()) {
-			final TrackingRefUpdate tru = rru.getTrackingRefUpdate();
-			if (tru != null)
-				res.add(tru);
-		}
-		return res;
 	}
 
 	private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
index e42b7fe..6b0a9b6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -73,7 +73,6 @@
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdSubclassMap;
-import org.eclipse.jgit.lib.PackLock;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
@@ -84,9 +83,9 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevFlag;
 import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.PackLock;
 import org.eclipse.jgit.transport.ReceiveCommand.Result;
 import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
 import org.eclipse.jgit.util.io.InterruptTimer;
@@ -575,6 +574,7 @@ public void receive(final InputStream input, final OutputStream output,
 
 			service();
 		} finally {
+			walk.release();
 			try {
 				if (pckOut != null)
 					pckOut.flush();
@@ -697,7 +697,7 @@ public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException {
 		adv.send(refs);
 		if (head != null && !head.isSymbolic())
 			adv.advertiseHave(head.getObjectId());
-		adv.includeAdditionalHaves();
+		adv.includeAdditionalHaves(db);
 		if (adv.isEmpty())
 			adv.advertiseId(ObjectId.zeroId(), "capabilities^{}");
 		adv.end();
@@ -818,8 +818,7 @@ private void checkConnectivity() throws IOException {
 			ow.markUninteresting(o);
 
 			if (checkReferencedIsReachable && !baseObjects.isEmpty()) {
-				while (o instanceof RevTag)
-					o = ((RevTag) o).getObject();
+				o = ow.peel(o);
 				if (o instanceof RevCommit)
 					o = ((RevCommit) o).getTree();
 				if (o instanceof RevTree)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
index 694a2e0..df0afe7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -49,10 +49,9 @@
 import java.util.Set;
 import java.util.SortedMap;
 
-import org.eclipse.jgit.lib.AlternateRepositoryDatabase;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectDatabase;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefComparator;
 import org.eclipse.jgit.lib.Repository;
@@ -126,7 +125,7 @@ public void init(final RevWalk protoWalk, final RevFlag advertisedFlag) {
 	 * <ul>
 	 * <li>{@link #send(Map)}
 	 * <li>{@link #advertiseHave(AnyObjectId)}
-	 * <li>{@link #includeAdditionalHaves()}
+	 * <li>{@link #includeAdditionalHaves(Repository)}
 	 * </ul>
 	 *
 	 * @param deref
@@ -144,7 +143,7 @@ public void setDerefTags(final boolean deref) {
 	 * <ul>
 	 * <li>{@link #send(Map)}
 	 * <li>{@link #advertiseHave(AnyObjectId)}
-	 * <li>{@link #includeAdditionalHaves()}
+	 * <li>{@link #includeAdditionalHaves(Repository)}
 	 * </ul>
 	 *
 	 * @param name
@@ -212,24 +211,15 @@ public void advertiseHave(AnyObjectId id) throws IOException {
 	/**
 	 * Include references of alternate repositories as {@code .have} lines.
 	 *
+	 * @param src
+	 *            repository to get the additional reachable objects from.
 	 * @throws IOException
 	 *             the underlying output stream failed to write out an
 	 *             advertisement record.
 	 */
-	public void includeAdditionalHaves() throws IOException {
-		additionalHaves(walk.getRepository().getObjectDatabase());
-	}
-
-	private void additionalHaves(final ObjectDatabase db) throws IOException {
-		if (db instanceof AlternateRepositoryDatabase)
-			additionalHaves(((AlternateRepositoryDatabase) db).getRepository());
-		for (ObjectDatabase alt : db.getAlternates())
-			additionalHaves(alt);
-	}
-
-	private void additionalHaves(final Repository alt) throws IOException {
-		for (final Ref r : alt.getAllRefs().values())
-			advertiseHave(r.getObjectId());
+	public void includeAdditionalHaves(Repository src) throws IOException {
+		for (ObjectId id : src.getAdditionalHaves())
+			advertiseHave(id);
 	}
 
 	/** @return true if no advertisements have been sent yet. */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
index 1b17c9f..37e03fd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
@@ -357,6 +357,6 @@ public String toString() {
 				+ "..." + (newObjectId != null ? newObjectId.abbreviate(localDb).name() : "(null)")
 				+ (fastForward ? ", fastForward" : "")
 				+ ", srcRef=" + srcRef + (forceUpdate ? ", forceUpdate" : "") + ", message=" + (message != null ? "\""
-				+ message + "\"" : "null") + ", " + localDb.getDirectory() + "]";
+				+ message + "\"" : "null") + "]";
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index e1988a6..500cf0c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -66,6 +66,7 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.TransferConfig;
+import org.eclipse.jgit.storage.pack.PackConfig;
 import org.eclipse.jgit.util.FS;
 
 /**
@@ -554,6 +555,9 @@ private static String findTrackingRefName(final String remoteName,
 	/** Timeout in seconds to wait before aborting an IO read or write. */
 	private int timeout;
 
+	/** Pack configuration used by this transport to make pack file. */
+	private PackConfig packConfig;
+
 	/**
 	 * Create a new transport instance.
 	 *
@@ -566,7 +570,7 @@ private static String findTrackingRefName(final String remoteName,
 	 *            URI passed to {@link #open(Repository, URIish)}.
 	 */
 	protected Transport(final Repository local, final URIish uri) {
-		final TransferConfig tc = local.getConfig().getTransfer();
+		final TransferConfig tc = local.getConfig().get(TransferConfig.KEY);
 		this.local = local;
 		this.uri = uri;
 		this.checkFetchedObjects = tc.isFsckObjects();
@@ -792,6 +796,32 @@ public void setTimeout(final int seconds) {
 	}
 
 	/**
+	 * Get the configuration used by the pack generator to make packs.
+	 *
+	 * If {@link #setPackConfig(PackConfig)} was previously given null a new
+	 * PackConfig is created on demand by this method using the source
+	 * repository's settings.
+	 *
+	 * @return the pack configuration. Never null.
+	 */
+	public PackConfig getPackConfig() {
+		if (packConfig == null)
+			packConfig = new PackConfig(local);
+		return packConfig;
+	}
+
+	/**
+	 * Set the configuration used by the pack generator.
+	 *
+	 * @param pc
+	 *            configuration controlling packing parameters. If null the
+	 *            source repository's settings will be used.
+	 */
+	public void setPackConfig(PackConfig pc) {
+		packConfig = pc;
+	}
+
+	/**
 	 * Fetch objects and refs from the remote repository to the local one.
 	 * <p>
 	 * This is a utility function providing standard fetch behavior. Local
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
index 56a5c97..79b88b6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
@@ -126,23 +126,7 @@ static boolean canHandle(final URIish uri) {
 			throws NotSupportedException {
 		super(local, uri);
 
-		Properties props = null;
-		File propsFile = new File(local.getDirectory(), uri.getUser());
-		if (!propsFile.isFile())
-			propsFile = new File(local.getFS().userHome(), uri.getUser());
-		if (propsFile.isFile()) {
-			try {
-				props = AmazonS3.properties(propsFile);
-			} catch (IOException e) {
-				throw new NotSupportedException(MessageFormat.format(JGitText.get().cannotReadFile, propsFile), e);
-			}
-		} else {
-			props = new Properties();
-			props.setProperty("accesskey", uri.getUser());
-			props.setProperty("secretkey", uri.getPass());
-		}
-
-		s3 = new AmazonS3(props);
+		s3 = new AmazonS3(loadProperties());
 		bucket = uri.getHost();
 
 		String p = uri.getPath();
@@ -153,6 +137,33 @@ static boolean canHandle(final URIish uri) {
 		keyPrefix = p;
 	}
 
+	private Properties loadProperties() throws NotSupportedException {
+		if (local.getDirectory() != null) {
+			File propsFile = new File(local.getDirectory(), uri.getUser());
+			if (propsFile.isFile())
+				return loadPropertiesFile(propsFile);
+		}
+
+		File propsFile = new File(local.getFS().userHome(), uri.getUser());
+		if (propsFile.isFile())
+			return loadPropertiesFile(propsFile);
+
+		Properties props = new Properties();
+		props.setProperty("accesskey", uri.getUser());
+		props.setProperty("secretkey", uri.getPass());
+		return props;
+	}
+
+	private static Properties loadPropertiesFile(File propsFile)
+			throws NotSupportedException {
+		try {
+			return AmazonS3.properties(propsFile);
+		} catch (IOException e) {
+			throw new NotSupportedException(MessageFormat.format(
+					JGitText.get().cannotReadFile, propsFile), e);
+		}
+	}
+
 	@Override
 	public FetchConnection openFetch() throws TransportException {
 		final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects");
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
index 71e7bf2..0f4c131 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -85,10 +85,10 @@
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDirectory;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.SymbolicRef;
 import org.eclipse.jgit.lib.Config.SectionParser;
+import org.eclipse.jgit.storage.file.RefDirectory;
 import org.eclipse.jgit.util.HttpSupport;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
index 08fd890..c9b18be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
@@ -61,6 +61,7 @@
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileRepository;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.io.MessageWriter;
 import org.eclipse.jgit.util.io.StreamCopyThread;
@@ -174,7 +175,7 @@ class InternalLocalFetchConnection extends BasePackFetchConnection {
 
 			final Repository dst;
 			try {
-				dst = new Repository(remoteGitDir);
+				dst = new FileRepository(remoteGitDir);
 			} catch (IOException err) {
 				throw new TransportException(uri, JGitText.get().notAGitDirectory);
 			}
@@ -314,7 +315,7 @@ class InternalLocalPushConnection extends BasePackPushConnection {
 
 			final Repository dst;
 			try {
-				dst = new Repository(remoteGitDir);
+				dst = new FileRepository(remoteGitDir);
 			} catch (IOException err) {
 				throw new TransportException(uri, JGitText.get().notAGitDirectory);
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index 77cc1a6..16d56df 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -56,10 +56,10 @@
 import java.util.Set;
 
 import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PackWriter;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
@@ -69,6 +69,8 @@
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.storage.pack.PackWriter;
 import org.eclipse.jgit.transport.BasePackFetchConnection.MultiAck;
 import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
 import org.eclipse.jgit.util.io.InterruptTimer;
@@ -101,6 +103,9 @@ public class UploadPack {
 	/** Revision traversal support over {@link #db}. */
 	private final RevWalk walk;
 
+	/** Configuration to pass into the PackWriter. */
+	private PackConfig packConfig;
+
 	/** Timeout in seconds to wait for client interaction. */
 	private int timeout;
 
@@ -258,6 +263,17 @@ public void setRefFilter(final RefFilter refFilter) {
 	}
 
 	/**
+	 * Set the configuration used by the pack generator.
+	 *
+	 * @param pc
+	 *            configuration controlling packing parameters. If null the
+	 *            source repository's settings will be used.
+	 */
+	public void setPackConfig(PackConfig pc) {
+		this.packConfig = pc;
+	}
+
+	/**
 	 * Execute the upload task on the socket.
 	 *
 	 * @param input
@@ -295,6 +311,7 @@ public void upload(final InputStream input, final OutputStream output,
 			pckOut = new PacketLineOut(rawOut);
 			service();
 		} finally {
+			walk.release();
 			if (timer != null) {
 				try {
 					timer.terminate();
@@ -393,11 +410,15 @@ private void recvWants() throws IOException {
 			}
 			if (!o.has(ADVERTISED))
 				throw new PackProtocolException(MessageFormat.format(JGitText.get().notValid, id.name()));
-			want(o);
+			try {
+				want(o);
+			} catch (IOException e) {
+				throw new PackProtocolException(MessageFormat.format(JGitText.get().notValid, id.name()), e);
+			}
 		}
 	}
 
-	private void want(RevObject o) {
+	private void want(RevObject o) throws MissingObjectException, IOException {
 		if (!o.has(WANT)) {
 			o.add(WANT);
 			wantAll.add(o);
@@ -406,9 +427,7 @@ private void want(RevObject o) {
 				wantCommits.add((RevCommit) o);
 
 			else if (o instanceof RevTag) {
-				do {
-					o = ((RevTag) o).getObject();
-				} while (o instanceof RevTag);
+				o = walk.peel(o);
 				if (o instanceof RevCommit)
 					want(o);
 			}
@@ -544,8 +563,6 @@ private boolean wantSatisfied(final RevCommit want) throws IOException {
 	}
 
 	private void sendPack() throws IOException {
-		final boolean thin = options.contains(OPTION_THIN_PACK);
-		final boolean progress = !options.contains(OPTION_NO_PROGRESS);
 		final boolean sideband = options.contains(OPTION_SIDE_BAND)
 				|| options.contains(OPTION_SIDE_BAND_64K);
 
@@ -559,32 +576,38 @@ private void sendPack() throws IOException {
 
 			packOut = new SideBandOutputStream(SideBandOutputStream.CH_DATA,
 					bufsz, rawOut);
-			if (progress)
+			if (!options.contains(OPTION_NO_PROGRESS))
 				pm = new SideBandProgressMonitor(new SideBandOutputStream(
 						SideBandOutputStream.CH_PROGRESS, bufsz, rawOut));
 		}
 
-		final PackWriter pw;
-		pw = new PackWriter(db, pm, NullProgressMonitor.INSTANCE);
-		pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
-		pw.setThin(thin);
-		pw.preparePack(wantAll, commonBase);
-		if (options.contains(OPTION_INCLUDE_TAG)) {
-			for (final Ref r : refs.values()) {
-				final RevObject o;
-				try {
-					o = walk.parseAny(r.getObjectId());
-				} catch (IOException e) {
-					continue;
+		PackConfig cfg = packConfig;
+		if (cfg == null)
+			cfg = new PackConfig(db);
+		final PackWriter pw = new PackWriter(cfg, walk.getObjectReader());
+		try {
+			pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
+			pw.setThin(options.contains(OPTION_THIN_PACK));
+			pw.preparePack(pm, wantAll, commonBase);
+			if (options.contains(OPTION_INCLUDE_TAG)) {
+				for (final Ref r : refs.values()) {
+					final RevObject o;
+					try {
+						o = walk.parseAny(r.getObjectId());
+					} catch (IOException e) {
+						continue;
+					}
+					if (o.has(WANT) || !(o instanceof RevTag))
+						continue;
+					final RevTag t = (RevTag) o;
+					if (!pw.willInclude(t) && pw.willInclude(t.getObject()))
+						pw.addObject(t);
 				}
-				if (o.has(WANT) || !(o instanceof RevTag))
-					continue;
-				final RevTag t = (RevTag) o;
-				if (!pw.willInclude(t) && pw.willInclude(t.getObject()))
-					pw.addObject(t);
 			}
+			pw.writePack(pm, NullProgressMonitor.INSTANCE, packOut);
+		} finally {
+			pw.release();
 		}
-		pw.writePack(packOut);
 		packOut.flush();
 
 		if (sideband)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
index 6254745..237b431 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
@@ -48,7 +48,6 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.security.MessageDigest;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -63,7 +62,6 @@
 import org.eclipse.jgit.errors.CompoundException;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.ObjectWritingException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
@@ -71,12 +69,12 @@
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectChecker;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PackIndex;
-import org.eclipse.jgit.lib.PackLock;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.UnpackedObjectLoader;
 import org.eclipse.jgit.revwalk.DateRevQueue;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevFlag;
@@ -84,6 +82,10 @@
 import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.ObjectDirectory;
+import org.eclipse.jgit.storage.file.PackIndex;
+import org.eclipse.jgit.storage.file.PackLock;
+import org.eclipse.jgit.storage.file.UnpackedObject;
 import org.eclipse.jgit.treewalk.TreeWalk;
 
 /**
@@ -165,8 +167,6 @@ class WalkFetchConnection extends BaseFetchConnection {
 
 	private final MutableObjectId idBuffer = new MutableObjectId();
 
-	private final MessageDigest objectDigest = Constants.newMessageDigest();
-
 	/**
 	 * Errors received while trying to obtain an object.
 	 * <p>
@@ -180,10 +180,18 @@ class WalkFetchConnection extends BaseFetchConnection {
 
 	private final List<PackLock> packLocks;
 
+	/** Inserter to write objects onto {@link #local}. */
+	private final ObjectInserter inserter;
+
+	/** Inserter to read objects from {@link #local}. */
+	private final ObjectReader reader;
+
 	WalkFetchConnection(final WalkTransport t, final WalkRemoteObjectDatabase w) {
 		Transport wt = (Transport)t;
 		local = wt.local;
 		objCheck = wt.isCheckFetchedObjects() ? new ObjectChecker() : null;
+		inserter = local.newObjectInserter();
+		reader = local.newObjectReader();
 
 		remotes = new ArrayList<WalkRemoteObjectDatabase>();
 		remotes.add(w);
@@ -200,9 +208,9 @@ class WalkFetchConnection extends BaseFetchConnection {
 		fetchErrors = new HashMap<ObjectId, List<Throwable>>();
 		packLocks = new ArrayList<PackLock>(4);
 
-		revWalk = new RevWalk(local);
+		revWalk = new RevWalk(reader);
 		revWalk.setRetainBody(false);
-		treeWalk = new TreeWalk(local);
+		treeWalk = new TreeWalk(reader);
 		COMPLETE = revWalk.newFlag("COMPLETE");
 		IN_WORK_QUEUE = revWalk.newFlag("IN_WORK_QUEUE");
 		LOCALLY_SEEN = revWalk.newFlag("LOCALLY_SEEN");
@@ -240,8 +248,12 @@ public void setPackLockMessage(final String message) {
 
 	@Override
 	public void close() {
-		for (final RemotePack p : unfetchedPacks)
-			p.tmpIdx.delete();
+		inserter.release();
+		reader.release();
+		for (final RemotePack p : unfetchedPacks) {
+			if (p.tmpIdx != null)
+				p.tmpIdx.delete();
+		}
 		for (final WalkRemoteObjectDatabase r : remotes)
 			r.close();
 	}
@@ -309,10 +321,17 @@ private void process(final ObjectId id) throws TransportException {
 	}
 
 	private void processBlob(final RevObject obj) throws TransportException {
-		if (!local.hasObject(obj))
-			throw new TransportException(MessageFormat.format(JGitText.get().cannotReadBlob, obj.name()),
-					new MissingObjectException(obj, Constants.TYPE_BLOB));
-		obj.add(COMPLETE);
+		try {
+			if (reader.has(obj, Constants.OBJ_BLOB))
+				obj.add(COMPLETE);
+			else
+				throw new TransportException(MessageFormat.format(JGitText
+						.get().cannotReadBlob, obj.name()),
+						new MissingObjectException(obj, Constants.TYPE_BLOB));
+		} catch (IOException error) {
+			throw new TransportException(MessageFormat.format(
+					JGitText.get().cannotReadBlob, obj.name()), error);
+		}
 	}
 
 	private void processTree(final RevObject obj) throws TransportException {
@@ -369,7 +388,7 @@ private void needs(final RevObject obj) {
 
 	private void downloadObject(final ProgressMonitor pm, final AnyObjectId id)
 			throws TransportException {
-		if (local.hasObject(id))
+		if (alreadyHave(id))
 			return;
 
 		for (;;) {
@@ -456,6 +475,15 @@ private void downloadObject(final ProgressMonitor pm, final AnyObjectId id)
 		}
 	}
 
+	private boolean alreadyHave(final AnyObjectId id) throws TransportException {
+		try {
+			return reader.has(id);
+		} catch (IOException error) {
+			throw new TransportException(MessageFormat.format(
+					JGitText.get().cannotReadObject, id.name()), error);
+		}
+	}
+
 	private boolean downloadPackedObject(final ProgressMonitor monitor,
 			final AnyObjectId id) throws TransportException {
 		// Search for the object in a remote pack whose index we have,
@@ -512,11 +540,12 @@ private boolean downloadPackedObject(final ProgressMonitor monitor,
 				// it failed the index and pack are unusable and we
 				// shouldn't consult them again.
 				//
-				pack.tmpIdx.delete();
+				if (pack.tmpIdx != null)
+					pack.tmpIdx.delete();
 				packItr.remove();
 			}
 
-			if (!local.hasObject(id)) {
+			if (!alreadyHave(id)) {
 				// What the hell? This pack claimed to have
 				// the object, but after indexing we didn't
 				// actually find it in the pack.
@@ -555,8 +584,7 @@ private boolean downloadLooseObject(final AnyObjectId id,
 			throws TransportException {
 		try {
 			final byte[] compressed = remote.open(looseName).toArray();
-			verifyLooseObject(id, compressed);
-			saveLooseObject(id, compressed);
+			verifyAndInsertLooseObject(id, compressed);
 			return true;
 		} catch (FileNotFoundException e) {
 			// Not available in a loose format from this alternate?
@@ -569,11 +597,11 @@ private boolean downloadLooseObject(final AnyObjectId id,
 		}
 	}
 
-	private void verifyLooseObject(final AnyObjectId id, final byte[] compressed)
-			throws IOException {
-		final UnpackedObjectLoader uol;
+	private void verifyAndInsertLooseObject(final AnyObjectId id,
+			final byte[] compressed) throws IOException {
+		final ObjectLoader uol;
 		try {
-			uol = new UnpackedObjectLoader(compressed);
+			uol = UnpackedObject.parse(compressed, id);
 		} catch (CorruptObjectException parsingError) {
 			// Some HTTP servers send back a "200 OK" status with an HTML
 			// page that explains the requested file could not be found.
@@ -592,62 +620,23 @@ private void verifyLooseObject(final AnyObjectId id, final byte[] compressed)
 			throw e;
 		}
 
-		objectDigest.reset();
-		objectDigest.update(Constants.encodedTypeString(uol.getType()));
-		objectDigest.update((byte) ' ');
-		objectDigest.update(Constants.encodeASCII(uol.getSize()));
-		objectDigest.update((byte) 0);
-		objectDigest.update(uol.getCachedBytes());
-		idBuffer.fromRaw(objectDigest.digest(), 0);
-
-		if (!AnyObjectId.equals(id, idBuffer)) {
-			throw new TransportException(MessageFormat.format(JGitText.get().incorrectHashFor
-					, id.name(), idBuffer.name(), Constants.typeString(uol.getType()), compressed.length));
-		}
+		final int type = uol.getType();
+		final byte[] raw = uol.getCachedBytes();
 		if (objCheck != null) {
 			try {
-				objCheck.check(uol.getType(), uol.getCachedBytes());
+				objCheck.check(type, raw);
 			} catch (CorruptObjectException e) {
 				throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionInvalid
-						, Constants.typeString(uol.getType()), id.name(), e.getMessage()));
+						, Constants.typeString(type), id.name(), e.getMessage()));
 			}
 		}
-	}
 
-	private void saveLooseObject(final AnyObjectId id, final byte[] compressed)
-			throws IOException, ObjectWritingException {
-		final File tmp;
-
-		tmp = File.createTempFile("noz", null, local.getObjectsDirectory());
-		try {
-			final FileOutputStream out = new FileOutputStream(tmp);
-			try {
-				out.write(compressed);
-			} finally {
-				out.close();
-			}
-			tmp.setReadOnly();
-		} catch (IOException e) {
-			tmp.delete();
-			throw e;
+		ObjectId act = inserter.insert(type, raw);
+		if (!AnyObjectId.equals(id, act)) {
+			throw new TransportException(MessageFormat.format(JGitText.get().incorrectHashFor
+					, id.name(), act.name(), Constants.typeString(type), compressed.length));
 		}
-
-		final File o = local.toFile(id);
-		if (tmp.renameTo(o))
-			return;
-
-		// Maybe the directory doesn't exist yet as the object
-		// directories are always lazily created. Note that we
-		// try the rename first as the directory likely does exist.
-		//
-		o.getParentFile().mkdir();
-		if (tmp.renameTo(o))
-			return;
-
-		tmp.delete();
-		if (local.hasObject(id))
-			return;
-		throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToStore, id.name()));
+		inserter.flush();
 	}
 
 	private Collection<WalkRemoteObjectDatabase> expandOneAlternate(
@@ -788,12 +777,11 @@ private class RemotePack {
 
 		final String idxName;
 
-		final File tmpIdx;
+		File tmpIdx;
 
 		PackIndex index;
 
 		RemotePack(final WalkRemoteObjectDatabase c, final String pn) {
-			final File objdir = local.getObjectsDirectory();
 			connection = c;
 			packName = pn;
 			idxName = packName.substring(0, packName.length() - 5) + ".idx";
@@ -803,13 +791,19 @@ private class RemotePack {
 				tn = tn.substring(5);
 			if (tn.endsWith(".idx"))
 				tn = tn.substring(0, tn.length() - 4);
-			tmpIdx = new File(objdir, "walk-" + tn + ".walkidx");
+
+			if (local.getObjectDatabase() instanceof ObjectDirectory) {
+				tmpIdx = new File(((ObjectDirectory) local.getObjectDatabase())
+						.getDirectory(), "walk-" + tn + ".walkidx");
+			}
 		}
 
 		void openIndex(final ProgressMonitor pm) throws IOException {
 			if (index != null)
 				return;
-			if (tmpIdx.isFile()) {
+			if (tmpIdx == null)
+				tmpIdx = File.createTempFile("jgit-walk-", ".idx");
+			else if (tmpIdx.isFile()) {
 				try {
 					index = PackIndex.open(tmpIdx);
 					return;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
index 0edf967..9ce0ec1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
@@ -61,12 +61,12 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.PackWriter;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefWriter;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.Ref.Storage;
+import org.eclipse.jgit.storage.pack.PackWriter;
 import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
 
 /**
@@ -103,6 +103,9 @@ class WalkPushConnection extends BaseConnection implements PushConnection {
 	/** Database connection to the remote repository. */
 	private final WalkRemoteObjectDatabase dest;
 
+	/** The configured transport we were constructed by. */
+	private final Transport transport;
+
 	/**
 	 * Packs already known to reside in the remote repository.
 	 * <p>
@@ -123,9 +126,9 @@ class WalkPushConnection extends BaseConnection implements PushConnection {
 
 	WalkPushConnection(final WalkTransport walkTransport,
 			final WalkRemoteObjectDatabase w) {
-		Transport t = (Transport)walkTransport;
-		local = t.local;
-		uri = t.getURI();
+		transport = (Transport) walkTransport;
+		local = transport.local;
+		uri = transport.getURI();
 		dest = w;
 	}
 
@@ -209,8 +212,9 @@ private void sendpack(final List<RemoteRefUpdate> updates,
 		String pathPack = null;
 		String pathIdx = null;
 
+		final PackWriter writer = new PackWriter(transport.getPackConfig(),
+				local.newObjectReader());
 		try {
-			final PackWriter pw = new PackWriter(local, monitor);
 			final List<ObjectId> need = new ArrayList<ObjectId>();
 			final List<ObjectId> have = new ArrayList<ObjectId>();
 			for (final RemoteRefUpdate r : updates)
@@ -220,20 +224,20 @@ private void sendpack(final List<RemoteRefUpdate> updates,
 				if (r.getPeeledObjectId() != null)
 					have.add(r.getPeeledObjectId());
 			}
-			pw.preparePack(need, have);
+			writer.preparePack(monitor, need, have);
 
 			// We don't have to continue further if the pack will
 			// be an empty pack, as the remote has all objects it
 			// needs to complete this change.
 			//
-			if (pw.getObjectsNumber() == 0)
+			if (writer.getObjectsNumber() == 0)
 				return;
 
 			packNames = new LinkedHashMap<String, String>();
 			for (final String n : dest.getPackNames())
 				packNames.put(n, n);
 
-			final String base = "pack-" + pw.computeName().name();
+			final String base = "pack-" + writer.computeName().name();
 			final String packName = base + ".pack";
 			pathPack = "pack/" + packName;
 			pathIdx = "pack/" + base + ".idx";
@@ -254,7 +258,7 @@ private void sendpack(final List<RemoteRefUpdate> updates,
 			OutputStream os = dest.writeFile(pathPack, monitor, wt + "..pack");
 			try {
 				os = new BufferedOutputStream(os);
-				pw.writePack(os);
+				writer.writePack(monitor, monitor, os);
 			} finally {
 				os.close();
 			}
@@ -262,7 +266,7 @@ private void sendpack(final List<RemoteRefUpdate> updates,
 			os = dest.writeFile(pathIdx, monitor, wt + "..idx");
 			try {
 				os = new BufferedOutputStream(os);
-				pw.writeIndex(os);
+				writer.writeIndex(os);
 			} finally {
 				os.close();
 			}
@@ -281,6 +285,8 @@ private void sendpack(final List<RemoteRefUpdate> updates,
 			safeDelete(pathPack);
 
 			throw new TransportException(uri, JGitText.get().cannotStoreObjects, err);
+		} finally {
+			writer.release();
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
index f1743b3..0e2adae 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
@@ -62,7 +62,7 @@
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDirectory;
+import org.eclipse.jgit.storage.file.RefDirectory;
 import org.eclipse.jgit.util.IO;
 
 /**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
index 178657a..a54b3e9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
@@ -55,8 +55,7 @@
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
 
 /**
@@ -138,19 +137,11 @@ public abstract class AbstractTreeIterator {
 	 */
 	protected int pathLen;
 
-	/**
-	 * Last modified time of the .gitignore file. Greater than 0 if a .gitignore
-	 * file exists.
-	 *
-	 */
-	protected long gitIgnoreTimeStamp;
-
 	/** Create a new iterator with no parent. */
 	protected AbstractTreeIterator() {
 		parent = null;
 		path = new byte[DEFAULT_PATH_SIZE];
 		pathOffset = 0;
-		gitIgnoreTimeStamp = 0l;
 	}
 
 	/**
@@ -170,7 +161,6 @@ protected AbstractTreeIterator() {
 	 */
 	protected AbstractTreeIterator(final String prefix) {
 		parent = null;
-		gitIgnoreTimeStamp = 0l;
 
 		if (prefix != null && prefix.length() > 0) {
 			final ByteBuffer b;
@@ -205,7 +195,6 @@ protected AbstractTreeIterator(final String prefix) {
 	 */
 	protected AbstractTreeIterator(final byte[] prefix) {
 		parent = null;
-		gitIgnoreTimeStamp = 0l;
 
 		if (prefix != null && prefix.length > 0) {
 			pathLen = prefix.length;
@@ -230,7 +219,6 @@ protected AbstractTreeIterator(final AbstractTreeIterator p) {
 		parent = p;
 		path = p.path;
 		pathOffset = p.pathLen + 1;
-		gitIgnoreTimeStamp = 0l;
 
 		try {
 			path[pathOffset - 1] = '/';
@@ -261,7 +249,6 @@ protected AbstractTreeIterator(final AbstractTreeIterator p,
 		parent = p;
 		path = childPath;
 		pathOffset = childPathOffset;
-		gitIgnoreTimeStamp = 0l;
 	}
 
 	/**
@@ -417,6 +404,24 @@ public String getEntryPathString() {
 	}
 
 	/**
+	 * Get the current entry's path hash code.
+	 * <p>
+	 * This method computes a hash code on the fly for this path, the hash is
+	 * suitable to cluster objects that may have similar paths together.
+	 *
+	 * @return path hash code; any integer may be returned.
+	 */
+	public int getEntryPathHashCode() {
+		int hash = 0;
+		for (int i = Math.max(0, pathLen - 16); i < pathLen; i++) {
+			byte c = path[i];
+			if (c != ' ')
+				hash = (hash >>> 2) + (c << 24);
+		}
+		return hash;
+	}
+
+	/**
 	 * Get the byte array buffer object IDs must be copied out of.
 	 * <p>
 	 * The id buffer contains the bytes necessary to construct an ObjectId for
@@ -445,8 +450,8 @@ public String getEntryPathString() {
 	 * otherwise the caller would not be able to exit out of the subtree
 	 * iterator correctly and return to continue walking <code>this</code>.
 	 *
-	 * @param repo
-	 *            repository to load the tree data from.
+	 * @param reader
+	 *            reader to load the tree data from.
 	 * @return a new parser that walks over the current subtree.
 	 * @throws IncorrectObjectTypeException
 	 *             the current entry is not actually a tree and cannot be parsed
@@ -454,8 +459,9 @@ public String getEntryPathString() {
 	 * @throws IOException
 	 *             a loose object or pack file could not be read.
 	 */
-	public abstract AbstractTreeIterator createSubtreeIterator(Repository repo)
-			throws IncorrectObjectTypeException, IOException;
+	public abstract AbstractTreeIterator createSubtreeIterator(
+			ObjectReader reader) throws IncorrectObjectTypeException,
+			IOException;
 
 	/**
 	 * Create a new iterator as though the current entry were a subtree.
@@ -473,12 +479,10 @@ public EmptyTreeIterator createEmptyTreeIterator() {
 	 * the caller would not be able to exit out of the subtree iterator
 	 * correctly and return to continue walking <code>this</code>.
 	 *
-	 * @param repo
-	 *            repository to load the tree data from.
+	 * @param reader
+	 *            reader to load the tree data from.
 	 * @param idBuffer
 	 *            temporary ObjectId buffer for use by this method.
-	 * @param curs
-	 *            window cursor to use during repository access.
 	 * @return a new parser that walks over the current subtree.
 	 * @throws IncorrectObjectTypeException
 	 *             the current entry is not actually a tree and cannot be parsed
@@ -486,10 +490,10 @@ public EmptyTreeIterator createEmptyTreeIterator() {
 	 * @throws IOException
 	 *             a loose object or pack file could not be read.
 	 */
-	public AbstractTreeIterator createSubtreeIterator(final Repository repo,
-			final MutableObjectId idBuffer, final WindowCursor curs)
+	public AbstractTreeIterator createSubtreeIterator(
+			final ObjectReader reader, final MutableObjectId idBuffer)
 			throws IncorrectObjectTypeException, IOException {
-		return createSubtreeIterator(repo);
+		return createSubtreeIterator(reader);
 	}
 
 	/**
@@ -605,22 +609,4 @@ public int getNameLength() {
 	public void getName(byte[] buffer, int offset) {
 		System.arraycopy(path, pathOffset, buffer, offset, pathLen - pathOffset);
 	}
-
-	/**
-	 * @return
-	 * 			  True if this iterator encountered a .gitignore file when initializing entries.
-	 * 			  Checks if the gitIgnoreTimeStamp > 0.
-	 */
-	public boolean hasGitIgnore() {
-		return gitIgnoreTimeStamp > 0;
-	}
-
-	/**
-	 * @return
-	 * 			  Last modified time of the .gitignore file, if any. Will be > 0 if a .gitignore
-	 * 			  exists.
-	 */
-	public long getGitIgnoreLastModified() {
-		return gitIgnoreTimeStamp;
-	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
index 0b9dc00..8e4094a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
@@ -54,9 +54,7 @@
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.lib.ObjectReader;
 
 /** Parses raw Git trees from the canonical semi-text/semi-binary format. */
 public class CanonicalTreeParser extends AbstractTreeIterator {
@@ -86,13 +84,11 @@ public CanonicalTreeParser() {
 	 *            may be null or the empty array to indicate the prefix is the
 	 *            root of the repository. A trailing slash ('/') is
 	 *            automatically appended if the prefix does not end in '/'.
-	 * @param repo
-	 *            repository to load the tree data from.
+	 * @param reader
+	 *            reader to load the tree data from.
 	 * @param treeId
 	 *            identity of the tree being parsed; used only in exception
 	 *            messages if data corruption is found.
-	 * @param curs
-	 *            a window cursor to use during data access from the repository.
 	 * @throws MissingObjectException
 	 *             the object supplied is not available from the repository.
 	 * @throws IncorrectObjectTypeException
@@ -101,11 +97,11 @@ public CanonicalTreeParser() {
 	 * @throws IOException
 	 *             a loose object or pack file could not be read.
 	 */
-	public CanonicalTreeParser(final byte[] prefix, final Repository repo,
-			final AnyObjectId treeId, final WindowCursor curs)
-			throws IncorrectObjectTypeException, IOException {
+	public CanonicalTreeParser(final byte[] prefix, final ObjectReader reader,
+			final AnyObjectId treeId) throws IncorrectObjectTypeException,
+			IOException {
 		super(prefix);
-		reset(repo, treeId, curs);
+		reset(reader, treeId);
 	}
 
 	private CanonicalTreeParser(final CanonicalTreeParser p) {
@@ -131,13 +127,11 @@ public void reset(final byte[] treeData) {
 	/**
 	 * Reset this parser to walk through the given tree.
 	 *
-	 * @param repo
-	 *            repository to load the tree data from.
+	 * @param reader
+	 *            reader to use during repository access.
 	 * @param id
 	 *            identity of the tree being parsed; used only in exception
 	 *            messages if data corruption is found.
-	 * @param curs
-	 *            window cursor to use during repository access.
 	 * @return the root level parser.
 	 * @throws MissingObjectException
 	 *             the object supplied is not available from the repository.
@@ -147,13 +141,13 @@ public void reset(final byte[] treeData) {
 	 * @throws IOException
 	 *             a loose object or pack file could not be read.
 	 */
-	public CanonicalTreeParser resetRoot(final Repository repo,
-			final AnyObjectId id, final WindowCursor curs)
-			throws IncorrectObjectTypeException, IOException {
+	public CanonicalTreeParser resetRoot(final ObjectReader reader,
+			final AnyObjectId id) throws IncorrectObjectTypeException,
+			IOException {
 		CanonicalTreeParser p = this;
 		while (p.parent != null)
 			p = (CanonicalTreeParser) p.parent;
-		p.reset(repo, id, curs);
+		p.reset(reader, id);
 		return p;
 	}
 
@@ -181,13 +175,11 @@ public CanonicalTreeParser next() {
 	/**
 	 * Reset this parser to walk through the given tree.
 	 *
-	 * @param repo
-	 *            repository to load the tree data from.
+	 * @param reader
+	 *            reader to use during repository access.
 	 * @param id
 	 *            identity of the tree being parsed; used only in exception
 	 *            messages if data corruption is found.
-	 * @param curs
-	 *            window cursor to use during repository access.
 	 * @throws MissingObjectException
 	 *             the object supplied is not available from the repository.
 	 * @throws IncorrectObjectTypeException
@@ -196,32 +188,21 @@ public CanonicalTreeParser next() {
 	 * @throws IOException
 	 *             a loose object or pack file could not be read.
 	 */
-	public void reset(final Repository repo, final AnyObjectId id,
-			final WindowCursor curs)
+	public void reset(final ObjectReader reader, final AnyObjectId id)
 			throws IncorrectObjectTypeException, IOException {
-		final ObjectLoader ldr = repo.openObject(curs, id);
-		if (ldr == null) {
-			final ObjectId me = id.toObjectId();
-			throw new MissingObjectException(me, Constants.TYPE_TREE);
-		}
-		final byte[] subtreeData = ldr.getCachedBytes();
-		if (ldr.getType() != Constants.OBJ_TREE) {
-			final ObjectId me = id.toObjectId();
-			throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE);
-		}
-		reset(subtreeData);
+		reset(reader.open(id, Constants.OBJ_TREE).getCachedBytes());
 	}
 
 	@Override
-	public CanonicalTreeParser createSubtreeIterator(final Repository repo,
-			final MutableObjectId idBuffer, final WindowCursor curs)
+	public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader,
+			final MutableObjectId idBuffer)
 			throws IncorrectObjectTypeException, IOException {
 		idBuffer.fromRaw(idBuffer(), idOffset());
 		if (!FileMode.TREE.equals(mode)) {
 			final ObjectId me = idBuffer.toObjectId();
 			throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE);
 		}
-		return createSubtreeIterator0(repo, idBuffer, curs);
+		return createSubtreeIterator0(reader, idBuffer);
 	}
 
 	/**
@@ -231,32 +212,25 @@ public CanonicalTreeParser createSubtreeIterator(final Repository repo,
 	 * called only once the current entry has been identified as a tree and its
 	 * identity has been converted into an ObjectId.
 	 *
-	 * @param repo
-	 *            repository to load the tree data from.
+	 * @param reader
+	 *            reader to load the tree data from.
 	 * @param id
 	 *            ObjectId of the tree to open.
-	 * @param curs
-	 *            window cursor to use during repository access.
 	 * @return a new parser that walks over the current subtree.
 	 * @throws IOException
 	 *             a loose object or pack file could not be read.
 	 */
 	public final CanonicalTreeParser createSubtreeIterator0(
-			final Repository repo, final AnyObjectId id, final WindowCursor curs)
+			final ObjectReader reader, final AnyObjectId id)
 			throws IOException {
 		final CanonicalTreeParser p = new CanonicalTreeParser(this);
-		p.reset(repo, id, curs);
+		p.reset(reader, id);
 		return p;
 	}
 
-	public CanonicalTreeParser createSubtreeIterator(final Repository repo)
+	public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader)
 			throws IncorrectObjectTypeException, IOException {
-		final WindowCursor curs = new WindowCursor();
-		try {
-			return createSubtreeIterator(repo, new MutableObjectId(), curs);
-		} finally {
-			curs.release();
-		}
+		return createSubtreeIterator(reader, new MutableObjectId());
 	}
 
 	@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
index 1776b50..7d4ee6d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
@@ -50,7 +50,7 @@
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.ObjectReader;
 
 /** Iterator over an empty tree (a directory with no files). */
 public class EmptyTreeIterator extends AbstractTreeIterator {
@@ -87,7 +87,7 @@ public EmptyTreeIterator(final AbstractTreeIterator p,
 	}
 
 	@Override
-	public AbstractTreeIterator createSubtreeIterator(final Repository repo)
+	public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader)
 			throws IncorrectObjectTypeException, IOException {
 		return new EmptyTreeIterator(this);
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
index aab25ee..2d032ab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
@@ -54,6 +54,7 @@
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.util.FS;
 
@@ -69,6 +70,7 @@ public class FileTreeIterator extends WorkingTreeIterator {
 	 *            the root of the repository.
 	 */
 	protected final File directory;
+
 	/**
 	 *  the file system abstraction which will be necessary to
 	 *            perform certain file system operations.
@@ -76,6 +78,17 @@ public class FileTreeIterator extends WorkingTreeIterator {
 	protected final FS fs;
 
 	/**
+	 * Create a new iterator to traverse the work tree and its children.
+	 *
+	 * @param repo
+	 *            the repository whose working tree will be scanned.
+	 */
+	public FileTreeIterator(Repository repo) {
+		this(repo.getWorkTree(), repo.getFS());
+		initRootIterator(repo);
+	}
+
+	/**
 	 * Create a new iterator to traverse the given directory and its children.
 	 *
 	 * @param root
@@ -111,22 +124,18 @@ protected FileTreeIterator(final FileTreeIterator p, final File root, FS fs) {
 	}
 
 	@Override
-	public AbstractTreeIterator createSubtreeIterator(final Repository repo)
+	public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader)
 			throws IncorrectObjectTypeException, IOException {
 		return new FileTreeIterator(this, ((FileEntry) current()).file, fs);
 	}
 
 	private Entry[] entries() {
-		gitIgnoreTimeStamp = 0l;
 		final File[] all = directory.listFiles();
 		if (all == null)
 			return EOF;
 		final Entry[] r = new Entry[all.length];
-		for (int i = 0; i < r.length; i++) {
+		for (int i = 0; i < r.length; i++)
 			r[i] = new FileEntry(all[i], fs);
-			if (all[i].getName().equals(Constants.DOT_GIT_IGNORE))
-				gitIgnoreTimeStamp = r[i].getLastModified();
-		}
 		return r;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
index b569174..2d6acbd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
@@ -46,6 +46,7 @@
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
 
 /**
@@ -86,6 +87,8 @@ public class NameConflictTreeWalk extends TreeWalk {
 
 	private boolean fastMinHasMatch;
 
+	private AbstractTreeIterator dfConflict;
+
 	/**
 	 * Create a new tree walker for a given repository.
 	 *
@@ -93,7 +96,17 @@ public class NameConflictTreeWalk extends TreeWalk {
 	 *            the repository the walker will obtain data from.
 	 */
 	public NameConflictTreeWalk(final Repository repo) {
-		super(repo);
+		this(repo.newObjectReader());
+	}
+
+	/**
+	 * Create a new tree walker for a given repository.
+	 *
+	 * @param or
+	 *            the reader the walker will obtain tree data from.
+	 */
+	public NameConflictTreeWalk(final ObjectReader or) {
+		super(or);
 	}
 
 	@Override
@@ -130,6 +143,7 @@ private AbstractTreeIterator fastMin() {
 		if (minRef.eof())
 			return minRef;
 
+		boolean hasConflict = false;
 		minRef.matches = minRef;
 		while (++i < trees.length) {
 			final AbstractTreeIterator t = trees[i];
@@ -145,6 +159,7 @@ && nameEqual(minRef, t)) {
 					// tree anyway.
 					//
 					t.matches = minRef;
+					hasConflict = true;
 				} else {
 					fastMinHasMatch = false;
 					t.matches = t;
@@ -171,10 +186,13 @@ && nameEqual(t, minRef)) {
 				}
 				t.matches = t;
 				minRef = t;
+				hasConflict = true;
 			} else
 				fastMinHasMatch = false;
 		}
 
+		if (hasConflict && fastMinHasMatch && dfConflict == null)
+			dfConflict = minRef;
 		return minRef;
 	}
 
@@ -270,6 +288,10 @@ private AbstractTreeIterator combineDF(final AbstractTreeIterator minRef)
 			for (final AbstractTreeIterator t : trees)
 				if (t.matches == minRef)
 					t.matches = treeMatch;
+
+			if (dfConflict == null)
+				dfConflict = treeMatch;
+
 			return treeMatch;
 		}
 
@@ -291,6 +313,9 @@ void popEntriesEqual() throws CorruptObjectException {
 				t.matches = null;
 			}
 		}
+
+		if (ch == dfConflict)
+			dfConflict = null;
 	}
 
 	@Override
@@ -308,5 +333,26 @@ void skipEntriesEqual() throws CorruptObjectException {
 				t.matches = null;
 			}
 		}
+
+		if (ch == dfConflict)
+			dfConflict = null;
+	}
+
+	/**
+	 * True if the current entry is covered by a directory/file conflict.
+	 *
+	 * This means that for some prefix of the current entry's path, this walk
+	 * has detected a directory/file conflict. Also true if the current entry
+	 * itself is a directory/file conflict.
+	 *
+	 * Example: If this TreeWalk points to foo/bar/a.txt and this method returns
+	 * true then you know that either for path foo or for path foo/bar files and
+	 * folders were detected.
+	 *
+	 * @return <code>true</code> if the current entry is covered by a
+	 *         directory/file conflict, <code>false</code> otherwise
+	 */
+	public boolean isDirectoryFileConflict() {
+		return dfConflict != null;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index 64c1aad..1685964 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -56,8 +56,8 @@
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.WindowCursor;
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
@@ -91,6 +91,42 @@ public class TreeWalk {
 	 * the caller should not need to invoke {@link #next()} unless they are
 	 * looking for a possible directory/file name conflict.
 	 *
+	 * @param reader
+	 *            the reader the walker will obtain tree data from.
+	 * @param path
+	 *            single path to advance the tree walk instance into.
+	 * @param trees
+	 *            one or more trees to walk through, all with the same root.
+	 * @return a new tree walk configured for exactly this one path; null if no
+	 *         path was found in any of the trees.
+	 * @throws IOException
+	 *             reading a pack file or loose object failed.
+	 * @throws CorruptObjectException
+	 *             an tree object could not be read as its data stream did not
+	 *             appear to be a tree, or could not be inflated.
+	 * @throws IncorrectObjectTypeException
+	 *             an object we expected to be a tree was not a tree.
+	 * @throws MissingObjectException
+	 *             a tree object was not found.
+	 */
+	public static TreeWalk forPath(final ObjectReader reader, final String path,
+			final AnyObjectId... trees) throws MissingObjectException,
+			IncorrectObjectTypeException, CorruptObjectException, IOException {
+		final TreeWalk r = new TreeWalk(reader);
+		r.setFilter(PathFilterGroup.createFromStrings(Collections
+				.singleton(path)));
+		r.setRecursive(r.getFilter().shouldBeRecursive());
+		r.reset(trees);
+		return r.next() ? r : null;
+	}
+
+	/**
+	 * Open a tree walk and filter to exactly one path.
+	 * <p>
+	 * The returned tree walk is already positioned on the requested path, so
+	 * the caller should not need to invoke {@link #next()} unless they are
+	 * looking for a possible directory/file name conflict.
+	 *
 	 * @param db
 	 *            repository to read tree object data from.
 	 * @param path
@@ -112,12 +148,12 @@ public class TreeWalk {
 	public static TreeWalk forPath(final Repository db, final String path,
 			final AnyObjectId... trees) throws MissingObjectException,
 			IncorrectObjectTypeException, CorruptObjectException, IOException {
-		final TreeWalk r = new TreeWalk(db);
-		r.setFilter(PathFilterGroup.createFromStrings(Collections
-				.singleton(path)));
-		r.setRecursive(r.getFilter().shouldBeRecursive());
-		r.reset(trees);
-		return r.next() ? r : null;
+		ObjectReader reader = db.newObjectReader();
+		try {
+			return forPath(reader, path, trees);
+		} finally {
+			reader.release();
+		}
 	}
 
 	/**
@@ -151,12 +187,10 @@ public static TreeWalk forPath(final Repository db, final String path,
 		return forPath(db, path, new ObjectId[] { tree });
 	}
 
-	private final Repository db;
+	private final ObjectReader reader;
 
 	private final MutableObjectId idBuffer = new MutableObjectId();
 
-	private final WindowCursor curs = new WindowCursor();
-
 	private TreeFilter filter;
 
 	AbstractTreeIterator[] trees;
@@ -180,18 +214,34 @@ public static TreeWalk forPath(final Repository db, final String path,
 	 *            the repository the walker will obtain data from.
 	 */
 	public TreeWalk(final Repository repo) {
-		db = repo;
+		this(repo.newObjectReader());
+	}
+
+	/**
+	 * Create a new tree walker for a given repository.
+	 *
+	 * @param or
+	 *            the reader the walker will obtain tree data from.
+	 */
+	public TreeWalk(final ObjectReader or) {
+		reader = or;
 		filter = TreeFilter.ALL;
 		trees = new AbstractTreeIterator[] { new EmptyTreeIterator() };
 	}
 
+	/** @return the reader this walker is using to load objects. */
+	public ObjectReader getObjectReader() {
+		return reader;
+	}
+
 	/**
-	 * Get the repository this tree walker is reading from.
-	 *
-	 * @return the repository configured when the walker was created.
+	 * Release any resources used by this walker's reader.
+	 * <p>
+	 * A walker that has been released can be used again, but may need to be
+	 * released after the subsequent usage.
 	 */
-	public Repository getRepository() {
-		return db;
+	public void release() {
+		reader.release();
 	}
 
 	/**
@@ -319,7 +369,7 @@ public void reset(final AnyObjectId id) throws MissingObjectException,
 			if (o instanceof CanonicalTreeParser) {
 				o.matches = null;
 				o.matchShift = 0;
-				((CanonicalTreeParser) o).reset(db, id, curs);
+				((CanonicalTreeParser) o).reset(reader, id);
 				trees[0] = o;
 			} else {
 				trees[0] = parserFor(id);
@@ -366,7 +416,7 @@ public void reset(final AnyObjectId[] ids) throws MissingObjectException,
 				if (o instanceof CanonicalTreeParser && o.pathOffset == 0) {
 					o.matches = null;
 					o.matchShift = 0;
-					((CanonicalTreeParser) o).reset(db, ids[i], curs);
+					((CanonicalTreeParser) o).reset(reader, ids[i]);
 					r[i] = o;
 					continue;
 				}
@@ -836,7 +886,7 @@ public void enterSubtree() throws MissingObjectException,
 			final AbstractTreeIterator t = trees[i];
 			final AbstractTreeIterator n;
 			if (t.matches == ch && !t.eof() && FileMode.TREE.equals(t.mode))
-				n = t.createSubtreeIterator(db, idBuffer, curs);
+				n = t.createSubtreeIterator(reader, idBuffer);
 			else
 				n = t.createEmptyTreeIterator();
 			tmp[i] = n;
@@ -911,11 +961,15 @@ private void exitSubtree() {
 	private CanonicalTreeParser parserFor(final AnyObjectId id)
 			throws IncorrectObjectTypeException, IOException {
 		final CanonicalTreeParser p = new CanonicalTreeParser();
-		p.reset(db, id, curs);
+		p.reset(reader, id);
 		return p;
 	}
 
 	static String pathOf(final AbstractTreeIterator t) {
 		return RawParseUtils.decode(Constants.CHARSET, t.path, 0, t.pathLen);
 	}
+
+	static String pathOf(final byte[] buf, int pos, int end) {
+		return RawParseUtils.decode(Constants.CHARSET, buf, pos, end);
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index cd06c24..a46f4df 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -45,6 +45,8 @@
 
 package org.eclipse.jgit.treewalk;
 
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
@@ -54,14 +56,18 @@
 import java.security.MessageDigest;
 import java.text.MessageFormat;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.ignore.IgnoreNode;
+import org.eclipse.jgit.ignore.IgnoreRule;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.util.FS;
 
 /**
@@ -106,6 +112,9 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
 	/** Current position within {@link #entries}. */
 	private int ptr;
 
+	/** If there is a .gitignore file present, the parsed rules from it. */
+	private IgnoreNode ignoreNode;
+
 	/** Create a new iterator with no parent. */
 	protected WorkingTreeIterator() {
 		super();
@@ -143,6 +152,24 @@ protected WorkingTreeIterator(final WorkingTreeIterator p) {
 		nameEncoder = p.nameEncoder;
 	}
 
+	/**
+	 * Initialize this iterator for the root level of a repository.
+	 * <p>
+	 * This method should only be invoked after calling {@link #init(Entry[])},
+	 * and only for the root iterator.
+	 *
+	 * @param repo
+	 *            the repository.
+	 */
+	protected void initRootIterator(Repository repo) {
+		Entry entry;
+		if (ignoreNode instanceof PerDirectoryIgnoreNode)
+			entry = ((PerDirectoryIgnoreNode) ignoreNode).entry;
+		else
+			entry = null;
+		ignoreNode = new RootIgnoreNode(entry, repo);
+	}
+
 	@Override
 	public byte[] idBuffer() {
 		if (contentIdFromPtr == ptr)
@@ -295,6 +322,57 @@ public long getEntryLastModified() {
 		return current().getLastModified();
 	}
 
+	/**
+	 * Determine if the current entry path is ignored by an ignore rule.
+	 *
+	 * @return true if the entry was ignored by an ignore rule file.
+	 * @throws IOException
+	 *             a relevant ignore rule file exists but cannot be read.
+	 */
+	public boolean isEntryIgnored() throws IOException {
+		return isEntryIgnored(pathLen);
+	}
+
+	/**
+	 * Determine if the entry path is ignored by an ignore rule.
+	 *
+	 * @param pLen
+	 *            the length of the path in the path buffer.
+	 * @return true if the entry is ignored by an ignore rule.
+	 * @throws IOException
+	 *             a relevant ignore rule file exists but cannot be read.
+	 */
+	protected boolean isEntryIgnored(final int pLen) throws IOException {
+		IgnoreNode rules = getIgnoreNode();
+		if (rules != null) {
+			// The ignore code wants path to start with a '/' if possible.
+			// If we have the '/' in our path buffer because we are inside
+			// a subdirectory include it in the range we convert to string.
+			//
+			int pOff = pathOffset;
+			if (0 < pOff)
+				pOff--;
+			String p = TreeWalk.pathOf(path, pOff, pLen);
+			switch (rules.isIgnored(p, FileMode.TREE.equals(mode))) {
+			case IGNORED:
+				return true;
+			case NOT_IGNORED:
+				return false;
+			case CHECK_PARENT:
+				break;
+			}
+		}
+		if (parent instanceof WorkingTreeIterator)
+			return ((WorkingTreeIterator) parent).isEntryIgnored(pLen);
+		return false;
+	}
+
+	private IgnoreNode getIgnoreNode() throws IOException {
+		if (ignoreNode instanceof PerDirectoryIgnoreNode)
+			ignoreNode = ((PerDirectoryIgnoreNode) ignoreNode).load();
+		return ignoreNode;
+	}
+
 	private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() {
 		public int compare(final Entry o1, final Entry o2) {
 			final byte[] a = o1.encodedName;
@@ -345,6 +423,8 @@ protected void init(final Entry[] list) {
 				continue;
 			if (Constants.DOT_GIT.equals(name))
 				continue;
+			if (Constants.DOT_GIT_IGNORE.equals(name))
+				ignoreNode = new PerDirectoryIgnoreNode(e);
 			if (i != o)
 				entries[o] = e;
 			e.encodeName(nameEncoder);
@@ -398,20 +478,20 @@ public boolean isModified(DirCacheEntry entry, boolean forceContentCheck,
 		if (entry.isUpdateNeeded())
 			return true;
 
-		if (getEntryLength() != entry.getLength())
+		if (!entry.isSmudged() && (getEntryLength() != entry.getLength()))
 			return true;
 
-		// determine difference in mode-bits of file and index-entry. In the
+		// Determine difference in mode-bits of file and index-entry. In the
 		// bitwise presentation of modeDiff we'll have a '1' when the two modes
 		// differ at this position.
 		int modeDiff = getEntryRawMode() ^ entry.getRawMode();
-		// ignore the executable file bits if checkFilemode tells me to do so.
+		// Ignore the executable file bits if checkFilemode tells me to do so.
 		// Ignoring is done by setting the bits representing a EXECUTABLE_FILE
 		// to '0' in modeDiff
 		if (!checkFilemode)
 			modeDiff &= ~FileMode.EXECUTABLE_FILE.getBits();
 		if (modeDiff != 0)
-			// report a modification if the modes still (after potentially
+			// Report a modification if the modes still (after potentially
 			// ignoring EXECUTABLE_FILE bits) differ
 			return true;
 
@@ -422,14 +502,58 @@ public boolean isModified(DirCacheEntry entry, boolean forceContentCheck,
 		long fileLastModified = getEntryLastModified();
 		if (cacheLastModified % 1000 == 0)
 			fileLastModified = fileLastModified - fileLastModified % 1000;
-		if (forceContentCheck) {
-			if (fileLastModified == cacheLastModified)
-				return false; // Same time, don't check content.
-			else
-				return !getEntryObjectId().equals(entry.getObjectId());
+
+		if (fileLastModified != cacheLastModified) {
+			// The file is dirty by timestamps
+			if (forceContentCheck) {
+				// But we are told to look at content even though timestamps
+				// tell us about modification
+				return contentCheck(entry);
+			} else {
+				// We are told to assume a modification if timestamps differs
+				return true;
+			}
 		} else {
-			// No content check forced, assume dirty if stat differs.
-			return fileLastModified != cacheLastModified;
+			// The file is clean when you look at timestamps.
+			if (entry.isSmudged()) {
+				// The file is clean by timestamps but the entry was smudged.
+				// Lets do a content check
+				return contentCheck(entry);
+			} else {
+				// The file is clean by timestamps and the entry is not
+				// smudged: Can't get any cleaner!
+				return false;
+			}
+		}
+	}
+
+	/**
+	 * Compares the entries content with the content in the filesystem.
+	 * Unsmudges the entry when it is detected that it is clean.
+	 *
+	 * @param entry
+	 *            the entry to be checked
+	 * @return <code>true</code> if the content matches, <code>false</code>
+	 *         otherwise
+	 */
+	private boolean contentCheck(DirCacheEntry entry) {
+		if (getEntryObjectId().equals(entry.getObjectId())) {
+			// Content has not changed
+
+			// We know the entry can't be racily clean because it's still clean.
+			// Therefore we unsmudge the entry!
+			// If by any chance we now unsmudge although we are still in the
+			// same time-slot as the last modification to the index file the
+			// next index write operation will smudge again.
+			// Caution: we are unsmudging just by setting the length of the
+			// in-memory entry object. It's the callers task to detect that we
+			// have modified the entry and to persist the modified index.
+			entry.setLength((int) getEntryLength());
+
+			return false;
+		} else {
+			// Content differs: that's a real change!
+			return true;
 		}
 	}
 
@@ -526,4 +650,59 @@ public String toString() {
 		 */
 		public abstract InputStream openInputStream() throws IOException;
 	}
+
+	/** Magic type indicating we know rules exist, but they aren't loaded. */
+	private static class PerDirectoryIgnoreNode extends IgnoreNode {
+		final Entry entry;
+
+		PerDirectoryIgnoreNode(Entry entry) {
+			super(Collections.<IgnoreRule> emptyList());
+			this.entry = entry;
+		}
+
+		IgnoreNode load() throws IOException {
+			IgnoreNode r = new IgnoreNode();
+			InputStream in = entry.openInputStream();
+			try {
+				r.parse(in);
+			} finally {
+				in.close();
+			}
+			return r.getRules().isEmpty() ? null : r;
+		}
+	}
+
+	/** Magic type indicating there may be rules for the top level. */
+	private static class RootIgnoreNode extends PerDirectoryIgnoreNode {
+		final Repository repository;
+
+		RootIgnoreNode(Entry entry, Repository repository) {
+			super(entry);
+			this.repository = repository;
+		}
+
+		@Override
+		IgnoreNode load() throws IOException {
+			IgnoreNode r;
+			if (entry != null) {
+				r = super.load();
+				if (r == null)
+					r = new IgnoreNode();
+			} else {
+				r = new IgnoreNode();
+			}
+
+			File exclude = new File(repository.getDirectory(), "info/exclude");
+			if (exclude.exists()) {
+				FileInputStream in = new FileInputStream(exclude);
+				try {
+					r.parse(in);
+				} finally {
+					in.close();
+				}
+			}
+
+			return r.getRules().isEmpty() ? null : r;
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
index 6c146f7..a8e505d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
@@ -42,13 +42,12 @@
  */
 package org.eclipse.jgit.util;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.util.regex.Pattern;
 
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectWriter;
 import org.eclipse.jgit.lib.PersonIdent;
 
 /**
@@ -62,6 +61,8 @@
  */
 public class ChangeIdUtil {
 
+	static final String CHANGE_ID = "Change-Id:";
+
 	// package-private so the unit test can test this part only
 	static String clean(String msg) {
 		return msg.//
@@ -113,12 +114,8 @@ public static ObjectId computeChangeId(final ObjectId treeId,
 		b.append(committer.toExternalString());
 		b.append("\n\n");
 		b.append(cleanMessage);
-		ObjectWriter w = new ObjectWriter(null);
-		byte[] bytes = b.toString().getBytes(Constants.CHARACTER_ENCODING);
-		ByteArrayInputStream is = new ByteArrayInputStream(bytes);
-		ObjectId sha1 = w.computeObjectSha1(Constants.OBJ_COMMIT, bytes.length,
-				is);
-		return sha1;
+		return new ObjectInserter.Formatter().idFor(Constants.OBJ_COMMIT, //
+				b.toString().getBytes(Constants.CHARACTER_ENCODING));
 	}
 
 	private static final Pattern issuePattern = Pattern
@@ -141,8 +138,39 @@ public static ObjectId computeChangeId(final ObjectId treeId,
 	 * @return a commit message with an inserted Change-Id line
 	 */
 	public static String insertId(String message, ObjectId changeId) {
-		if (message.indexOf("\nChange-Id:") > 0)
+		return insertId(message, changeId, false);
+	}
+
+	/**
+	 * Find the right place to insert a Change-Id and return it.
+	 * <p>
+	 * If no Change-Id is found the Change-Id is inserted before
+	 * the first footer line but after a Bug line.
+	 *
+	 * If Change-Id is found and replaceExisting is set to false,
+	 * the message is unchanged.
+	 *
+	 * If Change-Id is found and replaceExisting is set to true,
+	 * the Change-Id is replaced with {@code changeId}.
+	 *
+	 * @param message
+	 * @param changeId
+	 * @param replaceExisting
+	 * @return a commit message with an inserted Change-Id line
+	 */
+	public static String insertId(String message, ObjectId changeId,
+			boolean replaceExisting) {
+		if (message.indexOf(CHANGE_ID) > 0) {
+			if (replaceExisting) {
+				int i = message.indexOf(CHANGE_ID) + 10;
+				while (message.charAt(i) == ' ')
+					i++;
+				String oldId = message.length() == (i + 40) ?
+						message.substring(i) : message.substring(i, i + 41);
+				message = message.replace(oldId, "I" + changeId.getName());
+			}
 			return message;
+		}
 
 		String[] lines = message.split("\n");
 		int footerFirstLine = lines.length;
@@ -178,7 +206,8 @@ public static String insertId(String message, ObjectId changeId) {
 		}
 		if (insertAfter == lines.length && insertAfter == footerFirstLine)
 			ret.append("\n");
-		ret.append("Change-Id: I");
+		ret.append(CHANGE_ID);
+		ret.append(" I");
 		ret.append(ObjectId.toString(changeId));
 		ret.append("\n");
 		for (; i < lines.length; ++i) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index b8d4337..cb5c8bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -149,6 +149,13 @@ public File userHome() {
 	}
 
 	/**
+	 * Does this file system have problems with atomic renames?
+	 *
+	 * @return true if the caller should retry a failed rename of a lock file.
+	 */
+	public abstract boolean retryFailedLockFileCommit();
+
+	/**
 	 * Determine the user's home directory (location where preferences are).
 	 *
 	 * @return the user's home directory; null if the user does not have one.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java5.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java5.java
index 4ce0366..950d491 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java5.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java5.java
@@ -57,4 +57,9 @@ public boolean canExecute(final File f) {
 	public boolean setExecute(final File f, final boolean canExec) {
 		return false;
 	}
+
+	@Override
+	public boolean retryFailedLockFileCommit() {
+		return false;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java6.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java6.java
index 8a86d2e..192d9bb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java6.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java6.java
@@ -104,4 +104,9 @@ public boolean setExecute(final File f, final boolean canExec) {
 			throw new Error(e);
 		}
 	}
+
+	@Override
+	public boolean retryFailedLockFileCommit() {
+		return false;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
index c86f3e1..4e35e6e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
@@ -71,4 +71,9 @@ public boolean canExecute(final File f) {
 	public boolean setExecute(final File f, final boolean canExec) {
 		return false;
 	}
+
+	@Override
+	public boolean retryFailedLockFileCommit() {
+		return true;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java
index 26608bb..96b311d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java
@@ -83,6 +83,20 @@ public long get(final int i) {
 		return entries[i];
 	}
 
+	/**
+	 * Determine if an entry appears in this collection.
+	 *
+	 * @param value
+	 *            the value to search for.
+	 * @return true of {@code value} appears in this list.
+	 */
+	public boolean contains(final long value) {
+		for (int i = 0; i < count; i++)
+			if (entries[i] == value)
+				return true;
+		return false;
+	}
+
 	/** Empty this list */
 	public void clear() {
 		count = 0;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
index 9d7feb0..475c871 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -50,7 +50,7 @@
 import java.net.UnknownHostException;
 import java.util.TimeZone;
 
-import org.eclipse.jgit.lib.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
 
 /**
  * Interface to read values from the system.
@@ -74,7 +74,7 @@ public String getProperty(String key) {
 
 		public FileBasedConfig openUserConfig(FS fs) {
 			final File home = fs.userHome();
-			return new FileBasedConfig(new File(home, ".gitconfig"));
+			return new FileBasedConfig(new File(home, ".gitconfig"), fs);
 		}
 
 		public String getHostname() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
index 6c421c5..baa45c5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
@@ -126,7 +126,7 @@ public void write(final byte[] b, int off, int len) throws IOException {
 					blocks.add(s);
 				}
 
-				final int n = Math.min(Block.SZ - s.count, len);
+				final int n = Math.min(s.buffer.length - s.count, len);
 				System.arraycopy(b, off, s.buffer, s.count, n);
 				s.count += n;
 				len -= n;
@@ -139,6 +139,19 @@ public void write(final byte[] b, int off, int len) throws IOException {
 	}
 
 	/**
+	 * Dumps the entire buffer into the overflow stream, and flushes it.
+	 *
+	 * @throws IOException
+	 *             the overflow stream cannot be started, or the buffer contents
+	 *             cannot be written to it, or it failed to flush.
+	 */
+	protected void doFlush() throws IOException {
+		if (overflow == null)
+			switchToOverflow();
+		overflow.flush();
+	}
+
+	/**
 	 * Copy all bytes remaining on the input stream into this buffer.
 	 *
 	 * @param in
@@ -158,7 +171,7 @@ public void copy(final InputStream in) throws IOException {
 					blocks.add(s);
 				}
 
-				final int n = in.read(s.buffer, s.count, Block.SZ - s.count);
+				int n = in.read(s.buffer, s.count, s.buffer.length - s.count);
 				if (n < 1)
 					return;
 				s.count += n;
@@ -179,8 +192,12 @@ public void copy(final InputStream in) throws IOException {
 	 * @return total length of the buffer, in bytes.
 	 */
 	public long length() {
+		return inCoreLength();
+	}
+
+	private long inCoreLength() {
 		final Block last = last();
-		return ((long) blocks.size()) * Block.SZ - (Block.SZ - last.count);
+		return ((long) blocks.size() - 1) * Block.SZ + last.count;
 	}
 
 	/**
@@ -238,8 +255,13 @@ public void reset() {
 		if (overflow != null) {
 			destroy();
 		}
-		blocks = new ArrayList<Block>(inCoreLimit / Block.SZ);
-		blocks.add(new Block());
+		if (inCoreLimit < Block.SZ) {
+			blocks = new ArrayList<Block>(1);
+			blocks.add(new Block(inCoreLimit));
+		} else {
+			blocks = new ArrayList<Block>(inCoreLimit / Block.SZ);
+			blocks.add(new Block());
+		}
 	}
 
 	/**
@@ -257,9 +279,14 @@ private Block last() {
 	}
 
 	private boolean reachedInCoreLimit() throws IOException {
-		if (blocks.size() * Block.SZ < inCoreLimit)
+		if (inCoreLength() < inCoreLimit)
 			return false;
 
+		switchToOverflow();
+		return true;
+	}
+
+	private void switchToOverflow() throws IOException {
 		overflow = overflow();
 
 		final Block last = blocks.remove(blocks.size() - 1);
@@ -269,7 +296,6 @@ private boolean reachedInCoreLimit() throws IOException {
 
 		overflow = new BufferedOutputStream(overflow, Block.SZ);
 		overflow.write(last.buffer, 0, last.count);
-		return true;
 	}
 
 	public void close() throws IOException {
@@ -427,12 +453,20 @@ protected OutputStream overflow() throws IOException {
 	static class Block {
 		static final int SZ = 8 * 1024;
 
-		final byte[] buffer = new byte[SZ];
+		final byte[] buffer;
 
 		int count;
 
+		Block() {
+			buffer = new byte[SZ];
+		}
+
+		Block(int sz) {
+			buffer = new byte[sz];
+		}
+
 		boolean isFull() {
-			return count == SZ;
+			return count == buffer.length;
 		}
 	}
 }