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 7bad517170485b53382db90eef16ade9ee5e3548..3b615198a0d5bf737e006b8ea3073c15119b0449 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
@@ -47,7 +47,6 @@
 import static org.eclipse.jgit.http.server.ServletUtils.send;
 
 import java.io.IOException;
-import java.util.HashMap;
 import java.util.Map;
 
 import javax.servlet.http.HttpServlet;
@@ -98,7 +97,7 @@ protected void end() {
 		adv.init(walk, ADVERTISED);
 		adv.setDerefTags(true);
 
-		Map<String, Ref> refs = new HashMap<String, Ref>(db.getAllRefs());
+		Map<String, Ref> refs = db.getAllRefs();
 		refs.remove(Constants.HEAD);
 		adv.send(refs.values());
 		return out.toString().getBytes(Constants.CHARACTER_ENCODING);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
index 60dbe27acbe2e9277ac5c832ff8aa7b8e598830f..ffc28edc55e2758818ca1046652f16c0abea3cd9 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
@@ -143,7 +143,7 @@ private void list() throws Exception {
 		Ref head = refs.get(Constants.HEAD);
 		// This can happen if HEAD is stillborn
 		if (head != null) {
-			String current = head.getName();
+			String current = head.getLeaf().getName();
 			if (current.equals(Constants.HEAD))
 				addRef("(no branch)", head);
 			addRefs(refs, Constants.R_HEADS, !remote);
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 ecaf19bd1254aee93a4e25aa324c1db803bc3f79..4b5975669f5b3e888fed23e23d66c58416d48fc0 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
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2010, Google Inc.
  * Copyright (C) 2006-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  * and other copyright owners as documented in the project's IP log.
@@ -92,7 +93,7 @@ protected void show(final RevCommit c) throws Exception {
 			if (list != null) {
 				out.print(" (");
 				for (Iterator<Ref> i = list.iterator(); i.hasNext(); ) {
-					out.print(i.next().getOrigName());
+					out.print(i.next().getName());
 					if (i.hasNext())
 						out.print(" ");
 				}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java
index 7dbb21c5d595c22b3a2fb6bb630b7600713fdebe..d34f373db2c6a267cbf143859626f9aedc202f2c 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2010, Google Inc.
  * Copyright (C) 2008, Jonas Fonseca <fonseca@diku.dk>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  * and other copyright owners as documented in the project's IP log.
@@ -44,21 +45,32 @@
 
 package org.eclipse.jgit.pgm;
 
-import java.util.TreeMap;
+import java.util.Map;
+import java.util.SortedMap;
 
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefComparator;
+import org.eclipse.jgit.util.RefMap;
 
 class ShowRef extends TextBuiltin {
 	@Override
 	protected void run() throws Exception {
-		for (final Ref r : new TreeMap<String, Ref>(db.getAllRefs()).values()) {
+		for (final Ref r : getSortedRefs()) {
 			show(r.getObjectId(), r.getName());
 			if (r.getPeeledObjectId() != null)
 				show(r.getPeeledObjectId(), r.getName() + "^{}");
 		}
 	}
 
+	private Iterable<Ref> getSortedRefs() {
+		Map<String, Ref> all = db.getAllRefs();
+		if (all instanceof RefMap
+				|| (all instanceof SortedMap && ((SortedMap) all).comparator() == null))
+			return all.values();
+		return RefComparator.sort(all.values());
+	}
+
 	private void show(final AnyObjectId id, final String name) {
 		out.print(id.name());
 		out.print('\t');
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 50b889849ecfc578ce853b4af468f993efa27f22..0a5f2a0c9aaf114b1dc20d034ffb797d9e992b38 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2009-2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -63,6 +63,7 @@
 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;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.ProgressMonitor;
@@ -303,7 +304,8 @@ private Map<String, Ref> computeNewRefs() throws IOException {
 					}
 					throw new MissingObjectException(id, type);
 				}
-				refs.put(name, new Ref(Ref.Storage.PACKED, name, id));
+				refs.put(name, new ObjectIdRef.Unpeeled(Ref.Storage.PACKED,
+						name, id));
 			}
 		} finally {
 			br.close();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1774a428d48375e3b4dc3720ad6fc8a3e57f8b65
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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 junit.framework.TestCase;
+
+public class ObjectIdRefTest extends TestCase {
+	private static final ObjectId ID_A = ObjectId
+			.fromString("41eb0d88f833b558bddeb269b7ab77399cdf98ed");
+
+	private static final ObjectId ID_B = ObjectId
+			.fromString("698dd0b8d0c299f080559a1cffc7fe029479a408");
+
+	private static final String name = "refs/heads/a.test.ref";
+
+	public void testConstructor_PeeledStatusNotKnown() {
+		ObjectIdRef r;
+
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID_A);
+		assertSame(Ref.Storage.LOOSE, r.getStorage());
+		assertSame(name, r.getName());
+		assertSame(ID_A, r.getObjectId());
+		assertFalse("not peeled", r.isPeeled());
+		assertNull("no peel id", r.getPeeledObjectId());
+		assertSame("leaf is this", r, r.getLeaf());
+		assertSame("target is this", r, r.getTarget());
+		assertFalse("not symbolic", r.isSymbolic());
+
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.PACKED, name, ID_A);
+		assertSame(Ref.Storage.PACKED, r.getStorage());
+
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE_PACKED, name, ID_A);
+		assertSame(Ref.Storage.LOOSE_PACKED, r.getStorage());
+
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, name, null);
+		assertSame(Ref.Storage.NEW, r.getStorage());
+		assertSame(name, r.getName());
+		assertNull("no id on new ref", r.getObjectId());
+		assertFalse("not peeled", r.isPeeled());
+		assertNull("no peel id", r.getPeeledObjectId());
+		assertSame("leaf is this", r, r.getLeaf());
+		assertSame("target is this", r, r.getTarget());
+		assertFalse("not symbolic", r.isSymbolic());
+	}
+
+	public void testConstructor_Peeled() {
+		ObjectIdRef r;
+
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID_A);
+		assertSame(Ref.Storage.LOOSE, r.getStorage());
+		assertSame(name, r.getName());
+		assertSame(ID_A, r.getObjectId());
+		assertFalse("not peeled", r.isPeeled());
+		assertNull("no peel id", r.getPeeledObjectId());
+		assertSame("leaf is this", r, r.getLeaf());
+		assertSame("target is this", r, r.getTarget());
+		assertFalse("not symbolic", r.isSymbolic());
+
+		r = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, name, ID_A);
+		assertTrue("is peeled", r.isPeeled());
+		assertNull("no peel id", r.getPeeledObjectId());
+
+		r = new ObjectIdRef.PeeledTag(Ref.Storage.LOOSE, name, ID_A, ID_B);
+		assertTrue("is peeled", r.isPeeled());
+		assertSame(ID_B, r.getPeeledObjectId());
+	}
+
+	public void testToString() {
+		ObjectIdRef r;
+
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID_A);
+		assertEquals("Ref[" + name + "=" + ID_A.name() + "]", r.toString());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2812901bc92b68daf8ecfe01ddd426511ca3352
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java
@@ -0,0 +1,1024 @@
+/*
+ * 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 static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
+import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTag;
+
+public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
+	private Repository diskRepo;
+
+	private TestRepository repo;
+
+	private RefDirectory refdir;
+
+	private RevCommit A;
+
+	private RevCommit B;
+
+	private RevTag v1_0;
+
+	protected void setUp() throws Exception {
+		super.setUp();
+
+		diskRepo = createBareRepository();
+		refdir = (RefDirectory) diskRepo.getRefDatabase();
+
+		repo = new TestRepository(diskRepo);
+		A = repo.commit().create();
+		B = repo.commit(repo.getRevWalk().parseCommit(A));
+		v1_0 = repo.tag("v1_0", B);
+		repo.getRevWalk().parseBody(v1_0);
+	}
+
+	public void testCreate() throws IOException {
+		// setUp above created the directory. We just have to test it.
+		File d = diskRepo.getDirectory();
+		assertSame(diskRepo, refdir.getRepository());
+
+		assertTrue(new File(d, "refs").isDirectory());
+		assertTrue(new File(d, "logs").isDirectory());
+		assertTrue(new File(d, "logs/refs").isDirectory());
+		assertFalse(new File(d, "packed-refs").exists());
+
+		assertTrue(new File(d, "refs/heads").isDirectory());
+		assertTrue(new File(d, "refs/tags").isDirectory());
+		assertEquals(2, new File(d, "refs").list().length);
+		assertEquals(0, new File(d, "refs/heads").list().length);
+		assertEquals(0, new File(d, "refs/tags").list().length);
+
+		assertTrue(new File(d, "logs/refs/heads").isDirectory());
+		assertFalse(new File(d, "logs/HEAD").exists());
+		assertEquals(0, new File(d, "logs/refs/heads").list().length);
+
+		assertEquals("ref: refs/heads/master\n", read(new File(d, HEAD)));
+	}
+
+	public void testGetRefs_EmptyDatabase() throws IOException {
+		Map<String, Ref> all;
+
+		all = refdir.getRefs(RefDatabase.ALL);
+		assertTrue("no references", all.isEmpty());
+
+		all = refdir.getRefs(R_HEADS);
+		assertTrue("no references", all.isEmpty());
+
+		all = refdir.getRefs(R_TAGS);
+		assertTrue("no references", all.isEmpty());
+	}
+
+	public void testGetRefs_HeadOnOneBranch() throws IOException {
+		Map<String, Ref> all;
+		Ref head, master;
+
+		writeLooseRef("refs/heads/master", A);
+
+		all = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(2, all.size());
+		assertTrue("has HEAD", all.containsKey(HEAD));
+		assertTrue("has master", all.containsKey("refs/heads/master"));
+
+		head = all.get(HEAD);
+		master = all.get("refs/heads/master");
+
+		assertEquals(HEAD, head.getName());
+		assertTrue(head.isSymbolic());
+		assertSame(LOOSE, head.getStorage());
+		assertSame("uses same ref as target", master, head.getTarget());
+
+		assertEquals("refs/heads/master", master.getName());
+		assertFalse(master.isSymbolic());
+		assertSame(LOOSE, master.getStorage());
+		assertEquals(A, master.getObjectId());
+	}
+
+	public void testGetRefs_DeatchedHead1() throws IOException {
+		Map<String, Ref> all;
+		Ref head;
+
+		writeLooseRef(HEAD, A);
+		BUG_WorkAroundRacyGitIssues(HEAD);
+
+		all = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(1, all.size());
+		assertTrue("has HEAD", all.containsKey(HEAD));
+
+		head = all.get(HEAD);
+
+		assertEquals(HEAD, head.getName());
+		assertFalse(head.isSymbolic());
+		assertSame(LOOSE, head.getStorage());
+		assertEquals(A, head.getObjectId());
+	}
+
+	public void testGetRefs_DeatchedHead2() throws IOException {
+		Map<String, Ref> all;
+		Ref head, master;
+
+		writeLooseRef(HEAD, A);
+		writeLooseRef("refs/heads/master", B);
+		BUG_WorkAroundRacyGitIssues(HEAD);
+
+		all = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(2, all.size());
+
+		head = all.get(HEAD);
+		master = all.get("refs/heads/master");
+
+		assertEquals(HEAD, head.getName());
+		assertFalse(head.isSymbolic());
+		assertSame(LOOSE, head.getStorage());
+		assertEquals(A, head.getObjectId());
+
+		assertEquals("refs/heads/master", master.getName());
+		assertFalse(master.isSymbolic());
+		assertSame(LOOSE, master.getStorage());
+		assertEquals(B, master.getObjectId());
+	}
+
+	public void testGetRefs_DeeplyNestedBranch() throws IOException {
+		String name = "refs/heads/a/b/c/d/e/f/g/h/i/j/k";
+		Map<String, Ref> all;
+		Ref r;
+
+		writeLooseRef(name, A);
+
+		all = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(1, all.size());
+
+		r = all.get(name);
+		assertEquals(name, r.getName());
+		assertFalse(r.isSymbolic());
+		assertSame(LOOSE, r.getStorage());
+		assertEquals(A, r.getObjectId());
+	}
+
+	public void testGetRefs_HeadBranchNotBorn() throws IOException {
+		Map<String, Ref> all;
+		Ref a, b;
+
+		writeLooseRef("refs/heads/A", A);
+		writeLooseRef("refs/heads/B", B);
+
+		all = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(2, all.size());
+		assertFalse("no HEAD", all.containsKey(HEAD));
+
+		a = all.get("refs/heads/A");
+		b = all.get("refs/heads/B");
+
+		assertEquals(A, a.getObjectId());
+		assertEquals(B, b.getObjectId());
+
+		assertEquals("refs/heads/A", a.getName());
+		assertEquals("refs/heads/B", b.getName());
+	}
+
+	public void testGetRefs_LooseOverridesPacked() throws IOException {
+		Map<String, Ref> heads;
+		Ref a;
+
+		writeLooseRef("refs/heads/master", B);
+		writePackedRef("refs/heads/master", A);
+
+		heads = refdir.getRefs(R_HEADS);
+		assertEquals(1, heads.size());
+
+		a = heads.get("master");
+		assertEquals("refs/heads/master", a.getName());
+		assertEquals(B, a.getObjectId());
+	}
+
+	public void testGetRefs_IgnoresGarbageRef1() throws IOException {
+		Map<String, Ref> heads;
+		Ref a;
+
+		writeLooseRef("refs/heads/A", A);
+		write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "FAIL\n");
+
+		heads = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(1, heads.size());
+
+		a = heads.get("refs/heads/A");
+		assertEquals("refs/heads/A", a.getName());
+		assertEquals(A, a.getObjectId());
+	}
+
+	public void testGetRefs_IgnoresGarbageRef2() throws IOException {
+		Map<String, Ref> heads;
+		Ref a;
+
+		writeLooseRef("refs/heads/A", A);
+		write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "");
+
+		heads = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(1, heads.size());
+
+		a = heads.get("refs/heads/A");
+		assertEquals("refs/heads/A", a.getName());
+		assertEquals(A, a.getObjectId());
+	}
+
+	public void testGetRefs_IgnoresGarbageRef3() throws IOException {
+		Map<String, Ref> heads;
+		Ref a;
+
+		writeLooseRef("refs/heads/A", A);
+		write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "\n");
+
+		heads = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(1, heads.size());
+
+		a = heads.get("refs/heads/A");
+		assertEquals("refs/heads/A", a.getName());
+		assertEquals(A, a.getObjectId());
+	}
+
+	public void testGetRefs_IgnoresGarbageRef4() throws IOException {
+		Map<String, Ref> heads;
+		Ref a, b, c;
+
+		writeLooseRef("refs/heads/A", A);
+		writeLooseRef("refs/heads/B", B);
+		writeLooseRef("refs/heads/C", A);
+		heads = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(3, heads.size());
+		assertTrue(heads.containsKey("refs/heads/A"));
+		assertTrue(heads.containsKey("refs/heads/B"));
+		assertTrue(heads.containsKey("refs/heads/C"));
+
+		writeLooseRef("refs/heads/B", "FAIL\n");
+		BUG_WorkAroundRacyGitIssues("refs/heads/B");
+
+		heads = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(2, heads.size());
+
+		a = heads.get("refs/heads/A");
+		b = heads.get("refs/heads/B");
+		c = heads.get("refs/heads/C");
+
+		assertEquals("refs/heads/A", a.getName());
+		assertEquals(A, a.getObjectId());
+
+		assertNull("no refs/heads/B", b);
+
+		assertEquals("refs/heads/C", c.getName());
+		assertEquals(A, c.getObjectId());
+	}
+
+	public void testGetRefs_InvalidName() throws IOException {
+		writeLooseRef("refs/heads/A", A);
+
+		assertTrue("empty refs/heads", refdir.getRefs("refs/heads").isEmpty());
+		assertTrue("empty objects", refdir.getRefs("objects").isEmpty());
+		assertTrue("empty objects/", refdir.getRefs("objects/").isEmpty());
+	}
+
+	public void testGetRefs_HeadsOnly_AllLoose() throws IOException {
+		Map<String, Ref> heads;
+		Ref a, b;
+
+		writeLooseRef("refs/heads/A", A);
+		writeLooseRef("refs/heads/B", B);
+		writeLooseRef("refs/tags/v1.0", v1_0);
+
+		heads = refdir.getRefs(R_HEADS);
+		assertEquals(2, heads.size());
+
+		a = heads.get("A");
+		b = heads.get("B");
+
+		assertEquals("refs/heads/A", a.getName());
+		assertEquals("refs/heads/B", b.getName());
+
+		assertEquals(A, a.getObjectId());
+		assertEquals(B, b.getObjectId());
+	}
+
+	public void testGetRefs_HeadsOnly_AllPacked1() throws IOException {
+		Map<String, Ref> heads;
+		Ref a;
+
+		deleteLooseRef(HEAD);
+		writePackedRef("refs/heads/A", A);
+
+		heads = refdir.getRefs(R_HEADS);
+		assertEquals(1, heads.size());
+
+		a = heads.get("A");
+
+		assertEquals("refs/heads/A", a.getName());
+		assertEquals(A, a.getObjectId());
+	}
+
+	public void testGetRefs_HeadsOnly_SymrefToPacked() throws IOException {
+		Map<String, Ref> heads;
+		Ref master, other;
+
+		writeLooseRef("refs/heads/other", "ref: refs/heads/master\n");
+		writePackedRef("refs/heads/master", A);
+
+		heads = refdir.getRefs(R_HEADS);
+		assertEquals(2, heads.size());
+
+		master = heads.get("master");
+		other = heads.get("other");
+
+		assertEquals("refs/heads/master", master.getName());
+		assertEquals(A, master.getObjectId());
+
+		assertEquals("refs/heads/other", other.getName());
+		assertEquals(A, other.getObjectId());
+		assertSame(master, other.getTarget());
+	}
+
+	public void testGetRefs_HeadsOnly_Mixed() throws IOException {
+		Map<String, Ref> heads;
+		Ref a, b;
+
+		writeLooseRef("refs/heads/A", A);
+		writeLooseRef("refs/heads/B", B);
+		writePackedRef("refs/tags/v1.0", v1_0);
+
+		heads = refdir.getRefs(R_HEADS);
+		assertEquals(2, heads.size());
+
+		a = heads.get("A");
+		b = heads.get("B");
+
+		assertEquals("refs/heads/A", a.getName());
+		assertEquals("refs/heads/B", b.getName());
+
+		assertEquals(A, a.getObjectId());
+		assertEquals(B, b.getObjectId());
+	}
+
+	public void testGetRefs_TagsOnly_AllLoose() throws IOException {
+		Map<String, Ref> tags;
+		Ref a;
+
+		writeLooseRef("refs/heads/A", A);
+		writeLooseRef("refs/tags/v1.0", v1_0);
+
+		tags = refdir.getRefs(R_TAGS);
+		assertEquals(1, tags.size());
+
+		a = tags.get("v1.0");
+
+		assertEquals("refs/tags/v1.0", a.getName());
+		assertEquals(v1_0, a.getObjectId());
+	}
+
+	public void testGetRefs_TagsOnly_AllPacked() throws IOException {
+		Map<String, Ref> tags;
+		Ref a;
+
+		deleteLooseRef(HEAD);
+		writePackedRef("refs/tags/v1.0", v1_0);
+
+		tags = refdir.getRefs(R_TAGS);
+		assertEquals(1, tags.size());
+
+		a = tags.get("v1.0");
+
+		assertEquals("refs/tags/v1.0", a.getName());
+		assertEquals(v1_0, a.getObjectId());
+	}
+
+	public void testGetRefs_DiscoversNewLoose1() throws IOException {
+		Map<String, Ref> orig, next;
+		Ref orig_r, next_r;
+
+		writeLooseRef("refs/heads/master", A);
+		orig = refdir.getRefs(RefDatabase.ALL);
+
+		writeLooseRef("refs/heads/next", B);
+		next = refdir.getRefs(RefDatabase.ALL);
+
+		assertEquals(2, orig.size());
+		assertEquals(3, next.size());
+
+		assertFalse(orig.containsKey("refs/heads/next"));
+		assertTrue(next.containsKey("refs/heads/next"));
+
+		orig_r = orig.get("refs/heads/master");
+		next_r = next.get("refs/heads/master");
+		assertEquals(A, orig_r.getObjectId());
+		assertSame("uses cached instance", orig_r, next_r);
+		assertSame("same HEAD", orig_r, orig.get(HEAD).getTarget());
+		assertSame("same HEAD", orig_r, next.get(HEAD).getTarget());
+
+		next_r = next.get("refs/heads/next");
+		assertSame(LOOSE, next_r.getStorage());
+		assertEquals(B, next_r.getObjectId());
+	}
+
+	public void testGetRefs_DiscoversNewLoose2() throws IOException {
+		Map<String, Ref> orig, next, news;
+
+		writeLooseRef("refs/heads/pu", A);
+		orig = refdir.getRefs(RefDatabase.ALL);
+
+		writeLooseRef("refs/heads/new/B", B);
+		news = refdir.getRefs("refs/heads/new/");
+		next = refdir.getRefs(RefDatabase.ALL);
+
+		assertEquals(1, orig.size());
+		assertEquals(2, next.size());
+		assertEquals(1, news.size());
+
+		assertTrue(orig.containsKey("refs/heads/pu"));
+		assertTrue(next.containsKey("refs/heads/pu"));
+		assertFalse(news.containsKey("refs/heads/pu"));
+
+		assertFalse(orig.containsKey("refs/heads/new/B"));
+		assertTrue(next.containsKey("refs/heads/new/B"));
+		assertTrue(news.containsKey("B"));
+	}
+
+	public void testGetRefs_DiscoversModifiedLoose() throws IOException {
+		Map<String, Ref> all;
+
+		writeLooseRef("refs/heads/master", A);
+		all = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(A, all.get(HEAD).getObjectId());
+
+		writeLooseRef("refs/heads/master", B);
+		BUG_WorkAroundRacyGitIssues("refs/heads/master");
+		all = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(B, all.get(HEAD).getObjectId());
+	}
+
+	public void testGetRef_DiscoversModifiedLoose() throws IOException {
+		Map<String, Ref> all;
+
+		writeLooseRef("refs/heads/master", A);
+		all = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(A, all.get(HEAD).getObjectId());
+
+		writeLooseRef("refs/heads/master", B);
+		BUG_WorkAroundRacyGitIssues("refs/heads/master");
+
+		Ref master = refdir.getRef("refs/heads/master");
+		assertEquals(B, master.getObjectId());
+	}
+
+	public void testGetRefs_DiscoversDeletedLoose1() throws IOException {
+		Map<String, Ref> orig, next;
+		Ref orig_r, next_r;
+
+		writeLooseRef("refs/heads/B", B);
+		writeLooseRef("refs/heads/master", A);
+		orig = refdir.getRefs(RefDatabase.ALL);
+
+		deleteLooseRef("refs/heads/B");
+		next = refdir.getRefs(RefDatabase.ALL);
+
+		assertEquals(3, orig.size());
+		assertEquals(2, next.size());
+
+		assertTrue(orig.containsKey("refs/heads/B"));
+		assertFalse(next.containsKey("refs/heads/B"));
+
+		orig_r = orig.get("refs/heads/master");
+		next_r = next.get("refs/heads/master");
+		assertEquals(A, orig_r.getObjectId());
+		assertSame("uses cached instance", orig_r, next_r);
+		assertSame("same HEAD", orig_r, orig.get(HEAD).getTarget());
+		assertSame("same HEAD", orig_r, next.get(HEAD).getTarget());
+
+		orig_r = orig.get("refs/heads/B");
+		assertSame(LOOSE, orig_r.getStorage());
+		assertEquals(B, orig_r.getObjectId());
+	}
+
+	public void testGetRef_DiscoversDeletedLoose() throws IOException {
+		Map<String, Ref> all;
+
+		writeLooseRef("refs/heads/master", A);
+		all = refdir.getRefs(RefDatabase.ALL);
+		assertEquals(A, all.get(HEAD).getObjectId());
+
+		deleteLooseRef("refs/heads/master");
+		assertNull(refdir.getRef("refs/heads/master"));
+		assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty());
+	}
+
+	public void testGetRefs_DiscoversDeletedLoose2() throws IOException {
+		Map<String, Ref> orig, next;
+
+		writeLooseRef("refs/heads/master", A);
+		writeLooseRef("refs/heads/pu", B);
+		orig = refdir.getRefs(RefDatabase.ALL);
+
+		deleteLooseRef("refs/heads/pu");
+		next = refdir.getRefs(RefDatabase.ALL);
+
+		assertEquals(3, orig.size());
+		assertEquals(2, next.size());
+
+		assertTrue(orig.containsKey("refs/heads/pu"));
+		assertFalse(next.containsKey("refs/heads/pu"));
+	}
+
+	public void testGetRefs_DiscoversDeletedLoose3() throws IOException {
+		Map<String, Ref> orig, next;
+
+		writeLooseRef("refs/heads/master", A);
+		writeLooseRef("refs/heads/next", B);
+		writeLooseRef("refs/heads/pu", B);
+		writeLooseRef("refs/tags/v1.0", v1_0);
+		orig = refdir.getRefs(RefDatabase.ALL);
+
+		deleteLooseRef("refs/heads/pu");
+		deleteLooseRef("refs/heads/next");
+		next = refdir.getRefs(RefDatabase.ALL);
+
+		assertEquals(5, orig.size());
+		assertEquals(3, next.size());
+
+		assertTrue(orig.containsKey("refs/heads/pu"));
+		assertTrue(orig.containsKey("refs/heads/next"));
+		assertFalse(next.containsKey("refs/heads/pu"));
+		assertFalse(next.containsKey("refs/heads/next"));
+	}
+
+	public void testGetRefs_DiscoversDeletedLoose4() throws IOException {
+		Map<String, Ref> orig, next;
+		Ref orig_r, next_r;
+
+		writeLooseRef("refs/heads/B", B);
+		writeLooseRef("refs/heads/master", A);
+		orig = refdir.getRefs(RefDatabase.ALL);
+
+		deleteLooseRef("refs/heads/master");
+		next = refdir.getRefs("refs/heads/");
+
+		assertEquals(3, orig.size());
+		assertEquals(1, next.size());
+
+		assertTrue(orig.containsKey("refs/heads/B"));
+		assertTrue(orig.containsKey("refs/heads/master"));
+		assertTrue(next.containsKey("B"));
+		assertFalse(next.containsKey("master"));
+
+		orig_r = orig.get("refs/heads/B");
+		next_r = next.get("B");
+		assertEquals(B, orig_r.getObjectId());
+		assertSame("uses cached instance", orig_r, next_r);
+	}
+
+	public void testGetRefs_DiscoversDeletedLoose5() throws IOException {
+		Map<String, Ref> orig, next;
+
+		writeLooseRef("refs/heads/master", A);
+		writeLooseRef("refs/heads/pu", B);
+		orig = refdir.getRefs(RefDatabase.ALL);
+
+		deleteLooseRef("refs/heads/pu");
+		writeLooseRef("refs/tags/v1.0", v1_0);
+		next = refdir.getRefs(RefDatabase.ALL);
+
+		assertEquals(3, orig.size());
+		assertEquals(3, next.size());
+
+		assertTrue(orig.containsKey("refs/heads/pu"));
+		assertFalse(orig.containsKey("refs/tags/v1.0"));
+		assertFalse(next.containsKey("refs/heads/pu"));
+		assertTrue(next.containsKey("refs/tags/v1.0"));
+	}
+
+	public void testGetRefs_SkipsLockFiles() throws IOException {
+		Map<String, Ref> all;
+
+		writeLooseRef("refs/heads/master", A);
+		writeLooseRef("refs/heads/pu.lock", B);
+		all = refdir.getRefs(RefDatabase.ALL);
+
+		assertEquals(2, all.size());
+
+		assertTrue(all.containsKey(HEAD));
+		assertTrue(all.containsKey("refs/heads/master"));
+		assertFalse(all.containsKey("refs/heads/pu.lock"));
+	}
+
+	public void testGetRefs_CycleInSymbolicRef() throws IOException {
+		Map<String, Ref> all;
+		Ref r;
+
+		writeLooseRef("refs/1", "ref: refs/2\n");
+		writeLooseRef("refs/2", "ref: refs/3\n");
+		writeLooseRef("refs/3", "ref: refs/4\n");
+		writeLooseRef("refs/4", "ref: refs/5\n");
+		writeLooseRef("refs/5", "ref: refs/end\n");
+		writeLooseRef("refs/end", A);
+
+		all = refdir.getRefs(RefDatabase.ALL);
+		r = all.get("refs/1");
+		assertNotNull("has 1", r);
+
+		assertEquals("refs/1", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		r = r.getTarget();
+		assertEquals("refs/2", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		r = r.getTarget();
+		assertEquals("refs/3", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		r = r.getTarget();
+		assertEquals("refs/4", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		r = r.getTarget();
+		assertEquals("refs/5", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		r = r.getTarget();
+		assertEquals("refs/end", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertFalse(r.isSymbolic());
+
+		writeLooseRef("refs/5", "ref: refs/6\n");
+		writeLooseRef("refs/6", "ref: refs/end\n");
+		BUG_WorkAroundRacyGitIssues("refs/5");
+		all = refdir.getRefs(RefDatabase.ALL);
+		r = all.get("refs/1");
+		assertNull("mising 1 due to cycle", r);
+	}
+
+	public void testGetRefs_PackedNotPeeled_Sorted() throws IOException {
+		Map<String, Ref> all;
+
+		writePackedRefs("" + //
+				A.name() + " refs/heads/master\n" + //
+				B.name() + " refs/heads/other\n" + //
+				v1_0.name() + " refs/tags/v1.0\n");
+		all = refdir.getRefs(RefDatabase.ALL);
+
+		assertEquals(4, all.size());
+		final Ref head = all.get(HEAD);
+		final Ref master = all.get("refs/heads/master");
+		final Ref other = all.get("refs/heads/other");
+		final Ref tag = all.get("refs/tags/v1.0");
+
+		assertEquals(A, master.getObjectId());
+		assertFalse(master.isPeeled());
+		assertNull(master.getPeeledObjectId());
+
+		assertEquals(B, other.getObjectId());
+		assertFalse(other.isPeeled());
+		assertNull(other.getPeeledObjectId());
+
+		assertSame(master, head.getTarget());
+		assertEquals(A, head.getObjectId());
+		assertFalse(head.isPeeled());
+		assertNull(head.getPeeledObjectId());
+
+		assertEquals(v1_0, tag.getObjectId());
+		assertFalse(tag.isPeeled());
+		assertNull(tag.getPeeledObjectId());
+	}
+
+	public void testGetRef_PackedNotPeeled_WrongSort() throws IOException {
+		writePackedRefs("" + //
+				v1_0.name() + " refs/tags/v1.0\n" + //
+				B.name() + " refs/heads/other\n" + //
+				A.name() + " refs/heads/master\n");
+
+		final Ref head = refdir.getRef(HEAD);
+		final Ref master = refdir.getRef("refs/heads/master");
+		final Ref other = refdir.getRef("refs/heads/other");
+		final Ref tag = refdir.getRef("refs/tags/v1.0");
+
+		assertEquals(A, master.getObjectId());
+		assertFalse(master.isPeeled());
+		assertNull(master.getPeeledObjectId());
+
+		assertEquals(B, other.getObjectId());
+		assertFalse(other.isPeeled());
+		assertNull(other.getPeeledObjectId());
+
+		assertSame(master, head.getTarget());
+		assertEquals(A, head.getObjectId());
+		assertFalse(head.isPeeled());
+		assertNull(head.getPeeledObjectId());
+
+		assertEquals(v1_0, tag.getObjectId());
+		assertFalse(tag.isPeeled());
+		assertNull(tag.getPeeledObjectId());
+	}
+
+	public void testGetRefs_PackedWithPeeled() throws IOException {
+		Map<String, Ref> all;
+
+		writePackedRefs("# pack-refs with: peeled \n" + //
+				A.name() + " refs/heads/master\n" + //
+				B.name() + " refs/heads/other\n" + //
+				v1_0.name() + " refs/tags/v1.0\n" + //
+				"^" + v1_0.getObject().name() + "\n");
+		all = refdir.getRefs(RefDatabase.ALL);
+
+		assertEquals(4, all.size());
+		final Ref head = all.get(HEAD);
+		final Ref master = all.get("refs/heads/master");
+		final Ref other = all.get("refs/heads/other");
+		final Ref tag = all.get("refs/tags/v1.0");
+
+		assertEquals(A, master.getObjectId());
+		assertTrue(master.isPeeled());
+		assertNull(master.getPeeledObjectId());
+
+		assertEquals(B, other.getObjectId());
+		assertTrue(other.isPeeled());
+		assertNull(other.getPeeledObjectId());
+
+		assertSame(master, head.getTarget());
+		assertEquals(A, head.getObjectId());
+		assertTrue(head.isPeeled());
+		assertNull(head.getPeeledObjectId());
+
+		assertEquals(v1_0, tag.getObjectId());
+		assertTrue(tag.isPeeled());
+		assertEquals(v1_0.getObject(), tag.getPeeledObjectId());
+	}
+
+	public void testGetRef_EmptyDatabase() throws IOException {
+		Ref r;
+
+		r = refdir.getRef(HEAD);
+		assertTrue(r.isSymbolic());
+		assertSame(LOOSE, r.getStorage());
+		assertEquals("refs/heads/master", r.getTarget().getName());
+		assertSame(NEW, r.getTarget().getStorage());
+		assertNull(r.getTarget().getObjectId());
+
+		assertNull(refdir.getRef("refs/heads/master"));
+		assertNull(refdir.getRef("refs/tags/v1.0"));
+		assertNull(refdir.getRef("FETCH_HEAD"));
+		assertNull(refdir.getRef("NOT.A.REF.NAME"));
+		assertNull(refdir.getRef("master"));
+		assertNull(refdir.getRef("v1.0"));
+	}
+
+	public void testGetRef_FetchHead() throws IOException {
+		// This is an odd special case where we need to make sure we read
+		// exactly the first 40 bytes of the file and nothing further on
+		// that line, or the remainder of the file.
+		write(new File(diskRepo.getDirectory(), "FETCH_HEAD"), A.name()
+				+ "\tnot-for-merge"
+				+ "\tbranch 'master' of git://egit.eclipse.org/jgit\n");
+
+		Ref r = refdir.getRef("FETCH_HEAD");
+		assertFalse(r.isSymbolic());
+		assertEquals(A, r.getObjectId());
+		assertEquals("FETCH_HEAD", r.getName());
+		assertFalse(r.isPeeled());
+		assertNull(r.getPeeledObjectId());
+	}
+
+	public void testGetRef_AnyHeadWithGarbage() throws IOException {
+		write(new File(diskRepo.getDirectory(), "refs/heads/A"), A.name()
+				+ "012345 . this is not a standard reference\n"
+				+ "#and even more junk\n");
+
+		Ref r = refdir.getRef("refs/heads/A");
+		assertFalse(r.isSymbolic());
+		assertEquals(A, r.getObjectId());
+		assertEquals("refs/heads/A", r.getName());
+		assertFalse(r.isPeeled());
+		assertNull(r.getPeeledObjectId());
+	}
+
+	public void testGetRefs_CorruptSymbolicReference() throws IOException {
+		String name = "refs/heads/A";
+		writeLooseRef(name, "ref: \n");
+		assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty());
+	}
+
+	public void testGetRef_CorruptSymbolicReference() throws IOException {
+		String name = "refs/heads/A";
+		writeLooseRef(name, "ref: \n");
+		try {
+			refdir.getRef(name);
+			fail("read an invalid reference");
+		} catch (IOException err) {
+			String msg = err.getMessage();
+			assertEquals("Not a ref: " + name + ": ref:", msg);
+		}
+	}
+
+	public void testGetRefs_CorruptObjectIdReference() throws IOException {
+		String name = "refs/heads/A";
+		String content = "zoo" + A.name();
+		writeLooseRef(name, content + "\n");
+		assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty());
+	}
+
+	public void testGetRef_CorruptObjectIdReference() throws IOException {
+		String name = "refs/heads/A";
+		String content = "zoo" + A.name();
+		writeLooseRef(name, content + "\n");
+		try {
+			refdir.getRef(name);
+			fail("read an invalid reference");
+		} catch (IOException err) {
+			String msg = err.getMessage();
+			assertEquals("Not a ref: " + name + ": " + content, msg);
+		}
+	}
+
+	public void testIsNameConflicting() throws IOException {
+		writeLooseRef("refs/heads/a/b", A);
+		writePackedRef("refs/heads/q", B);
+
+		// new references cannot replace an existing container
+		assertTrue(refdir.isNameConflicting("refs"));
+		assertTrue(refdir.isNameConflicting("refs/heads"));
+		assertTrue(refdir.isNameConflicting("refs/heads/a"));
+
+		// existing reference is not conflicting
+		assertFalse(refdir.isNameConflicting("refs/heads/a/b"));
+
+		// new references are not conflicting
+		assertFalse(refdir.isNameConflicting("refs/heads/a/d"));
+		assertFalse(refdir.isNameConflicting("refs/heads/master"));
+
+		// existing reference must not be used as a container
+		assertTrue(refdir.isNameConflicting("refs/heads/a/b/c"));
+		assertTrue(refdir.isNameConflicting("refs/heads/q/master"));
+	}
+
+	public void testPeelLooseTag() throws IOException {
+		writeLooseRef("refs/tags/v1_0", v1_0);
+		writeLooseRef("refs/tags/current", "ref: refs/tags/v1_0\n");
+
+		final Ref tag = refdir.getRef("refs/tags/v1_0");
+		final Ref cur = refdir.getRef("refs/tags/current");
+
+		assertEquals(v1_0, tag.getObjectId());
+		assertFalse(tag.isSymbolic());
+		assertFalse(tag.isPeeled());
+		assertNull(tag.getPeeledObjectId());
+
+		assertEquals(v1_0, cur.getObjectId());
+		assertTrue(cur.isSymbolic());
+		assertFalse(cur.isPeeled());
+		assertNull(cur.getPeeledObjectId());
+
+		final Ref tag_p = refdir.peel(tag);
+		final Ref cur_p = refdir.peel(cur);
+
+		assertNotSame(tag, tag_p);
+		assertFalse(tag_p.isSymbolic());
+		assertTrue(tag_p.isPeeled());
+		assertEquals(v1_0, tag_p.getObjectId());
+		assertEquals(v1_0.getObject(), tag_p.getPeeledObjectId());
+		assertSame(tag_p, refdir.peel(tag_p));
+
+		assertNotSame(cur, cur_p);
+		assertEquals("refs/tags/current", cur_p.getName());
+		assertTrue(cur_p.isSymbolic());
+		assertEquals("refs/tags/v1_0", cur_p.getTarget().getName());
+		assertTrue(cur_p.isPeeled());
+		assertEquals(v1_0, cur_p.getObjectId());
+		assertEquals(v1_0.getObject(), cur_p.getPeeledObjectId());
+
+		// reuses cached peeling later, but not immediately due to
+		// the implementation so we have to fetch it once.
+		final Ref tag_p2 = refdir.getRef("refs/tags/v1_0");
+		assertFalse(tag_p2.isSymbolic());
+		assertTrue(tag_p2.isPeeled());
+		assertEquals(v1_0, tag_p2.getObjectId());
+		assertEquals(v1_0.getObject(), tag_p2.getPeeledObjectId());
+
+		assertSame(tag_p2, refdir.getRef("refs/tags/v1_0"));
+		assertSame(tag_p2, refdir.getRef("refs/tags/current").getTarget());
+		assertSame(tag_p2, refdir.peel(tag_p2));
+	}
+
+	public void testPeelCommit() throws IOException {
+		writeLooseRef("refs/heads/master", A);
+
+		Ref master = refdir.getRef("refs/heads/master");
+		assertEquals(A, master.getObjectId());
+		assertFalse(master.isPeeled());
+		assertNull(master.getPeeledObjectId());
+
+		Ref master_p = refdir.peel(master);
+		assertNotSame(master, master_p);
+		assertEquals(A, master_p.getObjectId());
+		assertTrue(master_p.isPeeled());
+		assertNull(master_p.getPeeledObjectId());
+
+		// reuses cached peeling later, but not immediately due to
+		// the implementation so we have to fetch it once.
+		Ref master_p2 = refdir.getRef("refs/heads/master");
+		assertNotSame(master, master_p2);
+		assertEquals(A, master_p2.getObjectId());
+		assertTrue(master_p2.isPeeled());
+		assertNull(master_p2.getPeeledObjectId());
+		assertSame(master_p2, refdir.peel(master_p2));
+	}
+
+	private void writeLooseRef(String name, AnyObjectId id) throws IOException {
+		writeLooseRef(name, id.name() + "\n");
+	}
+
+	private void writeLooseRef(String name, String content) throws IOException {
+		write(new File(diskRepo.getDirectory(), name), content);
+	}
+
+	private void writePackedRef(String name, AnyObjectId id) throws IOException {
+		writePackedRefs(id.name() + " " + name + "\n");
+	}
+
+	private void writePackedRefs(String content) throws IOException {
+		File pr = new File(diskRepo.getDirectory(), "packed-refs");
+		write(pr, content);
+	}
+
+	private void deleteLooseRef(String name) {
+		File path = new File(diskRepo.getDirectory(), name);
+		assertTrue("deleted " + name, path.delete());
+	}
+
+	/**
+	 * Kick the timestamp of a local file.
+	 * <p>
+	 * We shouldn't have to make these method calls. The cache is using file
+	 * system timestamps, and on many systems unit tests run faster than the
+	 * modification clock. Dumping the cache after we make an edit behind
+	 * RefDirectory's back allows the tests to pass.
+	 *
+	 * @param name
+	 *            the file in the repository to force a time change on.
+	 */
+	private void BUG_WorkAroundRacyGitIssues(String name) {
+		File path = new File(diskRepo.getDirectory(), name);
+		long old = path.lastModified();
+		long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
+		path.setLastModified(set);
+		assertTrue("time changed", old != path.lastModified());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
index 67ed6abb21ab4ce0779c98f48051d8d0f4fc8c97..42c8a92b933dc9ab939ac038156877d8441d68d1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
@@ -75,7 +75,7 @@ public void testReadAllIncludingSymrefs() throws Exception {
 		Ref refHEAD = allRefs.get("refs/remotes/origin/HEAD");
 		assertNotNull(refHEAD);
 		assertEquals(masterId, refHEAD.getObjectId());
-		assertTrue(refHEAD.isPeeled());
+		assertFalse(refHEAD.isPeeled());
 		assertNull(refHEAD.getPeeledObjectId());
 
 		Ref refmaster = allRefs.get("refs/remotes/origin/master");
@@ -87,7 +87,11 @@ public void testReadAllIncludingSymrefs() throws Exception {
 	public void testReadSymRefToPacked() throws IOException {
 		db.writeSymref("HEAD", "refs/heads/b");
 		Ref ref = db.getRef("HEAD");
-		assertEquals(Ref.Storage.LOOSE_PACKED, ref.getStorage());
+		assertEquals(Ref.Storage.LOOSE, ref.getStorage());
+		assertTrue("is symref", ref.isSymbolic());
+		ref = ref.getTarget();
+		assertEquals("refs/heads/b", ref.getName());
+		assertEquals(Ref.Storage.PACKED, ref.getStorage());
 	}
 
 	public void testReadSymRefToLoosePacked() throws IOException {
@@ -100,7 +104,10 @@ public void testReadSymRefToLoosePacked() throws IOException {
 
 		db.writeSymref("HEAD", "refs/heads/master");
 		Ref ref = db.getRef("HEAD");
-		assertEquals(Ref.Storage.LOOSE_PACKED, ref.getStorage());
+		assertEquals(Ref.Storage.LOOSE, ref.getStorage());
+		ref = ref.getTarget();
+		assertEquals("refs/heads/master", ref.getName());
+		assertEquals(Ref.Storage.LOOSE, ref.getStorage());
 	}
 
 	public void testReadLooseRef() throws IOException {
@@ -129,7 +136,7 @@ public void testReadLoosePackedRef() throws IOException,
 		os.close();
 
 		ref = db.getRef("refs/heads/master");
-		assertEquals(Storage.LOOSE_PACKED, ref.getStorage());
+		assertEquals(Storage.LOOSE, ref.getStorage());
 	}
 
 	/**
@@ -149,18 +156,26 @@ public void testReadSimplePackedRefSameRepo() throws IOException {
 		assertEquals(Result.FORCED, update);
 
 		ref = db.getRef("refs/heads/master");
-		assertEquals(Storage.LOOSE_PACKED, ref.getStorage());
+		assertEquals(Storage.LOOSE, ref.getStorage());
 	}
 
-	public void testOrigResolvedNamesBranch() throws IOException {
+	public void testResolvedNamesBranch() throws IOException {
 		Ref ref = db.getRef("a");
 		assertEquals("refs/heads/a", ref.getName());
-		assertEquals("refs/heads/a", ref.getOrigName());
 	}
 
-	public void testOrigResolvedNamesSymRef() throws IOException {
-		Ref ref = db.getRef("HEAD");
-		assertEquals("refs/heads/master", ref.getName());
-		assertEquals("HEAD", ref.getOrigName());
+	public void testResolvedSymRef() throws IOException {
+		Ref ref = db.getRef(Constants.HEAD);
+		assertEquals(Constants.HEAD, ref.getName());
+		assertTrue("is symbolic ref", ref.isSymbolic());
+		assertSame(Ref.Storage.LOOSE, ref.getStorage());
+
+		Ref dst = ref.getTarget();
+		assertNotNull("has target", dst);
+		assertEquals("refs/heads/master", dst.getName());
+
+		assertSame(dst.getObjectId(), ref.getObjectId());
+		assertSame(dst.getPeeledObjectId(), ref.getPeeledObjectId());
+		assertEquals(dst.isPeeled(), ref.isPeeled());
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java
index d851528cdd7c347007f561a912b1765057afaccd..cb9111758af98abe794d5015a46662b8d1aa33ee 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java
@@ -260,10 +260,10 @@ public void testDeleteForce() throws IOException {
 		delete(ref, Result.FORCED);
 	}
 
-	public void testRefKeySameAsOrigName() {
+	public void testRefKeySameAsName() {
 		Map<String, Ref> allRefs = db.getAllRefs();
 		for (Entry<String, Ref> e : allRefs.entrySet()) {
-			assertEquals(e.getKey(), e.getValue().getOrigName());
+			assertEquals(e.getKey(), e.getValue().getName());
 
 		}
 	}
@@ -308,7 +308,7 @@ public void testUpdateRefDetached() throws Exception {
 		assertEquals(ppid, db.resolve("HEAD"));
 		Ref ref = db.getRef("HEAD");
 		assertEquals("HEAD", ref.getName());
-		assertEquals("HEAD", ref.getOrigName());
+		assertTrue("is detached", !ref.isSymbolic());
 
 		// the branch HEAD referred to is left untouched
 		assertEquals(pid, db.resolve("refs/heads/master"));
@@ -337,7 +337,7 @@ public void testUpdateRefDetachedUnbornHead() throws Exception {
 		assertEquals(ppid, db.resolve("HEAD"));
 		Ref ref = db.getRef("HEAD");
 		assertEquals("HEAD", ref.getName());
-		assertEquals("HEAD", ref.getOrigName());
+		assertTrue("is detached", !ref.isSymbolic());
 
 		// the branch HEAD referred to is left untouched
 		assertNull(db.resolve("refs/heads/unborn"));
@@ -414,11 +414,14 @@ public void testRefsCacheAfterUpdate() throws Exception {
 		updateRef.setNewObjectId(oldValue);
 		update = updateRef.update();
 		assertEquals(Result.FAST_FORWARD, update);
+
 		allRefs = db.getAllRefs();
-		assertEquals("refs/heads/master", allRefs.get("refs/heads/master").getName());
-		assertEquals("refs/heads/master", allRefs.get("refs/heads/master").getOrigName());
-		assertEquals("refs/heads/master", allRefs.get("HEAD").getName());
-		assertEquals("HEAD", allRefs.get("HEAD").getOrigName());
+		Ref master = allRefs.get("refs/heads/master");
+		Ref head = allRefs.get("HEAD");
+		assertEquals("refs/heads/master", master.getName());
+		assertEquals("HEAD", head.getName());
+		assertTrue("is symbolic reference", head.isSymbolic());
+		assertSame(master, head.getTarget());
 	}
 
 	/**
@@ -430,7 +433,7 @@ public void testRefsCacheAfterUpdate() throws Exception {
 	 *
 	 * @throws Exception
 	 */
-	public void testRefsCacheAfterUpdateLoosOnly() throws Exception {
+	public void testRefsCacheAfterUpdateLooseOnly() throws Exception {
 		// Do not use the defalt repo for this case.
 		Map<String, Ref> allRefs = db.getAllRefs();
 		ObjectId oldValue = db.resolve("HEAD");
@@ -440,11 +443,14 @@ public void testRefsCacheAfterUpdateLoosOnly() throws Exception {
 		updateRef.setNewObjectId(oldValue);
 		Result update = updateRef.update();
 		assertEquals(Result.NEW, update);
+
 		allRefs = db.getAllRefs();
-		assertEquals("refs/heads/newref", allRefs.get("HEAD").getName());
-		assertEquals("HEAD", allRefs.get("HEAD").getOrigName());
-		assertEquals("refs/heads/newref", allRefs.get("refs/heads/newref").getName());
-		assertEquals("refs/heads/newref", allRefs.get("refs/heads/newref").getOrigName());
+		Ref head = allRefs.get("HEAD");
+		Ref newref = allRefs.get("refs/heads/newref");
+		assertEquals("refs/heads/newref", newref.getName());
+		assertEquals("HEAD", head.getName());
+		assertTrue("is symbolic reference", head.isSymbolic());
+		assertSame(newref, head.getTarget());
 	}
 
 	/**
@@ -575,8 +581,8 @@ public void testRenameBranchHasPreviousLog() throws IOException {
 		ObjectId oldHead = db.resolve(Constants.HEAD);
 		assertFalse("precondition for this test, branch b != HEAD", rb
 				.equals(oldHead));
-		RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
-		assertTrue("no log on old branch", new File(db.getDirectory(),
+		writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
+		assertTrue("log on old branch", new File(db.getDirectory(),
 				"logs/refs/heads/b").exists());
 		RefRename renameRef = db.renameRef("refs/heads/b",
 				"refs/heads/new/name");
@@ -598,8 +604,8 @@ public void testRenameCurrentBranch() throws IOException {
 		db.writeSymref(Constants.HEAD, "refs/heads/b");
 		ObjectId oldHead = db.resolve(Constants.HEAD);
 		assertTrue("internal test condition, b == HEAD", rb.equals(oldHead));
-		RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
-		assertTrue("no log on old branch", new File(db.getDirectory(),
+		writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
+		assertTrue("log on old branch", new File(db.getDirectory(),
 				"logs/refs/heads/b").exists());
 		RefRename renameRef = db.renameRef("refs/heads/b",
 				"refs/heads/new/name");
@@ -625,10 +631,9 @@ public void testRenameBranchAlsoInPack() throws IOException {
 		updateRef.setForceUpdate(true);
 		Result update = updateRef.update();
 		assertEquals("internal check new ref is loose", Result.FORCED, update);
-		assertEquals(Ref.Storage.LOOSE_PACKED, db.getRef("refs/heads/b")
-				.getStorage());
-		RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
-		assertTrue("no log on old branch", new File(db.getDirectory(),
+		assertEquals(Ref.Storage.LOOSE, db.getRef("refs/heads/b").getStorage());
+		writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
+		assertTrue("log on old branch", new File(db.getDirectory(),
 				"logs/refs/heads/b").exists());
 		RefRename renameRef = db.renameRef("refs/heads/b",
 				"refs/heads/new/name");
@@ -657,7 +662,7 @@ public void tryRenameWhenLocked(String toLock, String fromName,
 		db.writeSymref(Constants.HEAD, headPointsTo);
 		ObjectId oldfromId = db.resolve(fromName);
 		ObjectId oldHeadId = db.resolve(Constants.HEAD);
-		RefLogWriter.writeReflog(db, oldfromId, oldfromId, "Just a message",
+		writeReflog(db, oldfromId, oldfromId, "Just a message",
 				fromName);
 		List<org.eclipse.jgit.lib.ReflogReader.Entry> oldFromLog = db
 				.getReflogReader(fromName).getReverseEntries();
@@ -691,8 +696,8 @@ public void tryRenameWhenLocked(String toLock, String fromName,
 			assertEquals(oldFromLog.toString(), db.getReflogReader(fromName)
 					.getReverseEntries().toString());
 			if (oldHeadId != null)
-				assertEquals(oldHeadLog, db.getReflogReader(Constants.HEAD)
-						.getReverseEntries());
+				assertEquals(oldHeadLog.toString(), db.getReflogReader(
+						Constants.HEAD).getReverseEntries().toString());
 		} finally {
 			lockFile.unlock();
 		}
@@ -733,12 +738,6 @@ public void testRenameBranchCannotLockAFileHEADisToLockTo()
 				"refs/heads/new/name", "refs/heads/new/name");
 	}
 
-	public void testRenameBranchCannotLockAFileHEADisToLockTmp()
-			throws IOException {
-		tryRenameWhenLocked("RENAMED-REF.." + Thread.currentThread().getId(),
-				"refs/heads/b", "refs/heads/new/name", "refs/heads/new/name");
-	}
-
 	public void testRenameBranchCannotLockAFileHEADisOtherLockFrom()
 			throws IOException {
 		tryRenameWhenLocked("refs/heads/b", "refs/heads/b",
@@ -751,12 +750,6 @@ public void testRenameBranchCannotLockAFileHEADisOtherLockTo()
 				"refs/heads/new/name", "refs/heads/a");
 	}
 
-	public void testRenameBranchCannotLockAFileHEADisOtherLockTmp()
-			throws IOException {
-		tryRenameWhenLocked("RENAMED-REF.." + Thread.currentThread().getId(),
-				"refs/heads/b", "refs/heads/new/name", "refs/heads/a");
-	}
-
 	public void testRenameRefNameColission1avoided() throws IOException {
 		// setup
 		ObjectId rb = db.resolve("refs/heads/b");
@@ -767,7 +760,7 @@ public void testRenameRefNameColission1avoided() throws IOException {
 		assertEquals(Result.FAST_FORWARD, updateRef.update());
 		ObjectId oldHead = db.resolve(Constants.HEAD);
 		assertTrue(rb.equals(oldHead)); // assumption for this test
-		RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/a");
+		writeReflog(db, rb, rb, "Just a message", "refs/heads/a");
 		assertTrue("internal check, we have a log", new File(db.getDirectory(),
 				"logs/refs/heads/a").exists());
 
@@ -800,7 +793,7 @@ public void testRenameRefNameColission2avoided() throws IOException {
 		assertEquals(Result.FORCED, updateRef.update());
 		ObjectId oldHead = db.resolve(Constants.HEAD);
 		assertTrue(rb.equals(oldHead)); // assumption for this test
-		RefLogWriter.writeReflog(db, rb, rb, "Just a message",
+		writeReflog(db, rb, rb, "Just a message",
 				"refs/heads/prefix/a");
 		assertTrue("internal check, we have a log", new File(db.getDirectory(),
 				"logs/refs/heads/prefix/a").exists());
@@ -823,4 +816,13 @@ public void testRenameRefNameColission2avoided() throws IOException {
 		assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
 				"HEAD").getReverseEntries().get(0).getComment());
 	}
+
+	private void writeReflog(Repository db, ObjectId oldId, ObjectId newId,
+			String msg, String refName) throws IOException {
+		RefDirectory refs = (RefDirectory) db.getRefDatabase();
+		RefDirectoryUpdate update = refs.newUpdate(refName, true);
+		update.setOldObjectId(oldId);
+		update.setNewObjectId(newId);
+		refs.log(update, msg);
+	}
 }
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 8e5f6fc83b38521fef1538d011b9c9a11a4e3ee3..88bcf76710a4d37ccd2b7a58026eda779f0d54ad 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
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
  * Copyright (C) 2009, Christian Halstrick, Matthias Sohn, SAP AG
- * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2009-2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -54,7 +54,7 @@ public void testlogAllRefUpdates() throws Exception {
 
 		// check that there are no entries in the reflog and turn off writing
 		// reflogs
-		assertNull(db.getReflogReader(Constants.HEAD));
+		assertEquals(0, db.getReflogReader(Constants.HEAD).getReverseEntries().size());
 		db.getConfig().setBoolean("core", null, "logallrefupdates", false);
 
 		// do one commit and check that reflog size is 0: no reflogs should be
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..bff4fc34fa9e1c157cff868f00137d0a590e1387
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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 junit.framework.TestCase;
+
+public class SymbolicRefTest extends TestCase {
+	private static final ObjectId ID_A = ObjectId
+			.fromString("41eb0d88f833b558bddeb269b7ab77399cdf98ed");
+
+	private static final ObjectId ID_B = ObjectId
+			.fromString("698dd0b8d0c299f080559a1cffc7fe029479a408");
+
+	private static final String targetName = "refs/heads/a.test.ref";
+
+	private static final String name = "refs/remotes/origin/HEAD";
+
+	public void testConstructor() {
+		Ref t;
+		SymbolicRef r;
+
+		t = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, targetName, null);
+		r = new SymbolicRef(name, t);
+		assertSame(Ref.Storage.LOOSE, r.getStorage());
+		assertSame(name, r.getName());
+		assertNull("no id on new ref", r.getObjectId());
+		assertFalse("not peeled", r.isPeeled());
+		assertNull("no peel id", r.getPeeledObjectId());
+		assertSame("leaf is t", t, r.getLeaf());
+		assertSame("target is t", t, r.getTarget());
+		assertTrue("is symbolic", r.isSymbolic());
+
+		t = new ObjectIdRef.Unpeeled(Ref.Storage.PACKED, targetName, ID_A);
+		r = new SymbolicRef(name, t);
+		assertSame(Ref.Storage.LOOSE, r.getStorage());
+		assertSame(name, r.getName());
+		assertSame(ID_A, r.getObjectId());
+		assertFalse("not peeled", r.isPeeled());
+		assertNull("no peel id", r.getPeeledObjectId());
+		assertSame("leaf is t", t, r.getLeaf());
+		assertSame("target is t", t, r.getTarget());
+		assertTrue("is symbolic", r.isSymbolic());
+	}
+
+	public void testLeaf() {
+		Ref a;
+		SymbolicRef b, c, d;
+
+		a = new ObjectIdRef.PeeledTag(Ref.Storage.PACKED, targetName, ID_A, ID_B);
+		b = new SymbolicRef("B", a);
+		c = new SymbolicRef("C", b);
+		d = new SymbolicRef("D", c);
+
+		assertSame(c, d.getTarget());
+		assertSame(b, c.getTarget());
+		assertSame(a, b.getTarget());
+
+		assertSame(a, d.getLeaf());
+		assertSame(a, c.getLeaf());
+		assertSame(a, b.getLeaf());
+		assertSame(a, a.getLeaf());
+
+		assertSame(ID_A, d.getObjectId());
+		assertSame(ID_A, c.getObjectId());
+		assertSame(ID_A, b.getObjectId());
+
+		assertTrue(d.isPeeled());
+		assertTrue(c.isPeeled());
+		assertTrue(b.isPeeled());
+
+		assertSame(ID_B, d.getPeeledObjectId());
+		assertSame(ID_B, c.getPeeledObjectId());
+		assertSame(ID_B, b.getPeeledObjectId());
+	}
+
+	public void testToString() {
+		Ref a;
+		SymbolicRef b, c, d;
+
+		a = new ObjectIdRef.PeeledTag(Ref.Storage.PACKED, targetName, ID_A, ID_B);
+		b = new SymbolicRef("B", a);
+		c = new SymbolicRef("C", b);
+		d = new SymbolicRef("D", c);
+
+		assertEquals("SymbolicRef[D -> C -> B -> " + targetName + "="
+				+ ID_A.name() + "]", d.toString());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java
index e29e9e7214b4ed9a3152e6405c230ec39c108901..d4231bfa662c2d6c8c9a512b9d9adf9938002de1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java
@@ -543,6 +543,7 @@ public void test025_packedRefs() throws IOException {
 		w.println("0ce2ebdb36076ef0b38adbe077a07d43b43e3807 refs/tags/test022");
 		w.println("^b5d3b45a96b340441f5abb9080411705c51cc86c");
 		w.close();
+		((RefDirectory)db.getRefDatabase()).rescan();
 
 		Tag mapTag20 = db.mapTag("test020");
 		assertNotNull("have tag test020", mapTag20);
@@ -673,6 +674,8 @@ public void test027_UnpackedRefHigherPriorityThanPacked() throws IOException {
 	public void test028_LockPackedRef() throws IOException {
 		writeTrashFile(".git/packed-refs", "7f822839a2fe9760f386cbbbcb3f92c5fe81def7 refs/heads/foobar");
 		writeTrashFile(".git/HEAD", "ref: refs/heads/foobar\n");
+		BUG_WorkAroundRacyGitIssues("packed-refs");
+		BUG_WorkAroundRacyGitIssues("HEAD");
 
 		ObjectId resolve = db.resolve("HEAD");
 		assertEquals("7f822839a2fe9760f386cbbbcb3f92c5fe81def7", resolve.name());
@@ -727,4 +730,23 @@ public void test30_stripWorkDir() {
 		assertEquals("subdir/File.java", Repository.stripWorkDir(db.getWorkDir(), file));
 
 	}
+
+	/**
+	 * Kick the timestamp of a local file.
+	 * <p>
+	 * We shouldn't have to make these method calls. The cache is using file
+	 * system timestamps, and on many systems unit tests run faster than the
+	 * modification clock. Dumping the cache after we make an edit behind
+	 * RefDirectory's back allows the tests to pass.
+	 *
+	 * @param name
+	 *            the file in the repository to force a time change on.
+	 */
+	private void BUG_WorkAroundRacyGitIssues(String name) {
+		File path = new File(db.getDirectory(), name);
+		long old = path.lastModified();
+		long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
+		path.setLastModified(set);
+		assertTrue("time changed", old != path.lastModified());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
index 8e997e3f8c39a61bdcbc54438392a76cca17a9f1..99edbd98ca90d20cfc46ecb0f26b9f2cd1fe5049 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
@@ -51,6 +51,7 @@
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
@@ -88,7 +89,7 @@ public void testUpdateFastForward() throws IOException {
 		final RemoteRefUpdate rru = new RemoteRefUpdate(db,
 				"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
 				"refs/heads/master", false, null, null);
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
 		testOneUpdateStatus(rru, ref, Status.OK, true);
 	}
@@ -103,7 +104,7 @@ public void testUpdateNonFastForwardUnknownObject() throws IOException {
 		final RemoteRefUpdate rru = new RemoteRefUpdate(db,
 				"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
 				"refs/heads/master", false, null, null);
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("0000000000000000000000000000000000000001"));
 		testOneUpdateStatus(rru, ref, Status.REJECTED_NONFASTFORWARD, null);
 	}
@@ -118,7 +119,7 @@ public void testUpdateNonFastForward() throws IOException {
 		final RemoteRefUpdate rru = new RemoteRefUpdate(db,
 				"ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
 				"refs/heads/master", false, null, null);
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
 		testOneUpdateStatus(rru, ref, Status.REJECTED_NONFASTFORWARD, null);
 	}
@@ -132,7 +133,7 @@ public void testUpdateNonFastForwardForced() throws IOException {
 		final RemoteRefUpdate rru = new RemoteRefUpdate(db,
 				"ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
 				"refs/heads/master", true, null, null);
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
 		testOneUpdateStatus(rru, ref, Status.OK, false);
 	}
@@ -157,7 +158,7 @@ public void testUpdateCreateRef() throws IOException {
 	public void testUpdateDelete() throws IOException {
 		final RemoteRefUpdate rru = new RemoteRefUpdate(db, null,
 				"refs/heads/master", false, null, null);
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
 		testOneUpdateStatus(rru, ref, Status.OK, true);
 	}
@@ -183,7 +184,7 @@ public void testUpdateUpToDate() throws IOException {
 		final RemoteRefUpdate rru = new RemoteRefUpdate(db,
 				"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
 				"refs/heads/master", false, null, null);
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
 		testOneUpdateStatus(rru, ref, Status.UP_TO_DATE, null);
 	}
@@ -198,7 +199,7 @@ public void testUpdateExpectedRemote() throws IOException {
 				"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
 				"refs/heads/master", false, null, ObjectId
 						.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
 		testOneUpdateStatus(rru, ref, Status.OK, true);
 	}
@@ -214,7 +215,7 @@ public void testUpdateUnexpectedRemote() throws IOException {
 				"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
 				"refs/heads/master", false, null, ObjectId
 						.fromString("0000000000000000000000000000000000000001"));
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
 		testOneUpdateStatus(rru, ref, Status.REJECTED_REMOTE_CHANGED, null);
 	}
@@ -231,7 +232,7 @@ public void testUpdateUnexpectedRemoteVsForce() throws IOException {
 				"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
 				"refs/heads/master", true, null, ObjectId
 						.fromString("0000000000000000000000000000000000000001"));
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
 		testOneUpdateStatus(rru, ref, Status.REJECTED_REMOTE_CHANGED, null);
 	}
@@ -246,7 +247,7 @@ public void testUpdateRejectedByConnection() throws IOException {
 		final RemoteRefUpdate rru = new RemoteRefUpdate(db,
 				"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
 				"refs/heads/master", false, null, null);
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
 		testOneUpdateStatus(rru, ref, Status.REJECTED_OTHER_REASON, null);
 	}
@@ -260,7 +261,7 @@ public void testUpdateRejectedByConnection() throws IOException {
 	public void testUpdateMixedCases() throws IOException {
 		final RemoteRefUpdate rruOk = new RemoteRefUpdate(db, null,
 				"refs/heads/master", false, null, null);
-		final Ref refToChange = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref refToChange = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
 		final RemoteRefUpdate rruReject = new RemoteRefUpdate(db, null,
 				"refs/heads/nonexisting", false, null, null);
@@ -282,7 +283,7 @@ public void testTrackingRefUpdateEnabled() throws IOException {
 		final RemoteRefUpdate rru = new RemoteRefUpdate(db,
 				"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
 				"refs/heads/master", false, "refs/remotes/test/master", null);
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
 		refUpdates.add(rru);
 		advertisedRefs.add(ref);
@@ -303,7 +304,7 @@ public void testTrackingRefUpdateDisabled() throws IOException {
 		final RemoteRefUpdate rru = new RemoteRefUpdate(db,
 				"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
 				"refs/heads/master", false, null, null);
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
 		refUpdates.add(rru);
 		advertisedRefs.add(ref);
@@ -320,7 +321,7 @@ public void testTrackingRefUpdateOnReject() throws IOException {
 		final RemoteRefUpdate rru = new RemoteRefUpdate(db,
 				"ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
 				"refs/heads/master", false, null, null);
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
 		final PushResult result = testOneUpdateStatus(rru, ref,
 				Status.REJECTED_NONFASTFORWARD, null);
@@ -336,7 +337,7 @@ public void testPushResult() throws IOException {
 		final RemoteRefUpdate rru = new RemoteRefUpdate(db,
 				"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
 				"refs/heads/master", false, "refs/remotes/test/master", null);
-		final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
 		refUpdates.add(rru);
 		advertisedRefs.add(ref);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java
index 38dbe0962e3bac532e36b99858ceaad5ed7445b6..955b0c95ec1c6e4af0b8f53c1665effdec844cd9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java
@@ -46,6 +46,7 @@
 
 import junit.framework.TestCase;
 
+import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
 
 public class RefSpecTest extends TestCase {
@@ -59,12 +60,12 @@ public void testMasterMaster() {
 		assertEquals(sn + ":" + sn, rs.toString());
 		assertEquals(rs, new RefSpec(rs.toString()));
 
-		Ref r = new Ref(Ref.Storage.LOOSE, sn, null);
+		Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn, null);
 		assertTrue(rs.matchSource(r));
 		assertTrue(rs.matchDestination(r));
 		assertSame(rs, rs.expandFromSource(r));
 
-		r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null);
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn + "-and-more", null);
 		assertFalse(rs.matchSource(r));
 		assertFalse(rs.matchDestination(r));
 	}
@@ -91,12 +92,12 @@ public void testForceMasterMaster() {
 		assertEquals("+" + sn + ":" + sn, rs.toString());
 		assertEquals(rs, new RefSpec(rs.toString()));
 
-		Ref r = new Ref(Ref.Storage.LOOSE, sn, null);
+		Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn, null);
 		assertTrue(rs.matchSource(r));
 		assertTrue(rs.matchDestination(r));
 		assertSame(rs, rs.expandFromSource(r));
 
-		r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null);
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn + "-and-more", null);
 		assertFalse(rs.matchSource(r));
 		assertFalse(rs.matchDestination(r));
 	}
@@ -111,12 +112,12 @@ public void testMaster() {
 		assertEquals(sn, rs.toString());
 		assertEquals(rs, new RefSpec(rs.toString()));
 
-		Ref r = new Ref(Ref.Storage.LOOSE, sn, null);
+		Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn, null);
 		assertTrue(rs.matchSource(r));
 		assertFalse(rs.matchDestination(r));
 		assertSame(rs, rs.expandFromSource(r));
 
-		r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null);
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn + "-and-more", null);
 		assertFalse(rs.matchSource(r));
 		assertFalse(rs.matchDestination(r));
 	}
@@ -131,12 +132,12 @@ public void testForceMaster() {
 		assertEquals("+" + sn, rs.toString());
 		assertEquals(rs, new RefSpec(rs.toString()));
 
-		Ref r = new Ref(Ref.Storage.LOOSE, sn, null);
+		Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn, null);
 		assertTrue(rs.matchSource(r));
 		assertFalse(rs.matchDestination(r));
 		assertSame(rs, rs.expandFromSource(r));
 
-		r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null);
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn + "-and-more", null);
 		assertFalse(rs.matchSource(r));
 		assertFalse(rs.matchDestination(r));
 	}
@@ -151,12 +152,12 @@ public void testDeleteMaster() {
 		assertEquals(":" + sn, rs.toString());
 		assertEquals(rs, new RefSpec(rs.toString()));
 
-		Ref r = new Ref(Ref.Storage.LOOSE, sn, null);
+		Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn, null);
 		assertFalse(rs.matchSource(r));
 		assertTrue(rs.matchDestination(r));
 		assertSame(rs, rs.expandFromSource(r));
 
-		r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null);
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn + "-and-more", null);
 		assertFalse(rs.matchSource(r));
 		assertFalse(rs.matchDestination(r));
 	}
@@ -175,7 +176,7 @@ public void testForceRemotesOrigin() {
 		Ref r;
 		RefSpec expanded;
 
-		r = new Ref(Ref.Storage.LOOSE, "refs/heads/master", null);
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master", null);
 		assertTrue(rs.matchSource(r));
 		assertFalse(rs.matchDestination(r));
 		expanded = rs.expandFromSource(r);
@@ -185,11 +186,11 @@ public void testForceRemotesOrigin() {
 		assertEquals(r.getName(), expanded.getSource());
 		assertEquals("refs/remotes/origin/master", expanded.getDestination());
 
-		r = new Ref(Ref.Storage.LOOSE, "refs/remotes/origin/next", null);
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/remotes/origin/next", null);
 		assertFalse(rs.matchSource(r));
 		assertTrue(rs.matchDestination(r));
 
-		r = new Ref(Ref.Storage.LOOSE, "refs/tags/v1.0", null);
+		r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/tags/v1.0", null);
 		assertFalse(rs.matchSource(r));
 		assertFalse(rs.matchDestination(r));
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
index c9522341b0bf1cc9514483ee8be639a5b5152b3c..c6471ded9bc5e4e81f95cd1fd1923d6cbc6f7b1c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
@@ -49,6 +49,7 @@
 import junit.framework.TestCase;
 
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
 
 public class RefListTest extends TestCase {
@@ -426,6 +427,6 @@ private RefList<Ref> toList(Ref... refs) {
 	}
 
 	private static Ref newRef(final String name) {
-		return new Ref(Ref.Storage.LOOSE, name, ID);
+		return new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
index 6ce206e01328cb18e4b3f242429757cf8d6ca36f..c4c2383f5cf19dd091d4c50ae08d8e6697bc1175 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
@@ -50,7 +50,9 @@
 import junit.framework.TestCase;
 
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.SymbolicRef;
 
 public class RefMapTest extends TestCase {
 	private static final ObjectId ID_ONE = ObjectId
@@ -176,6 +178,49 @@ public void testIterator_FailsAtEnd() {
 		}
 	}
 
+	public void testIterator_MissingUnresolvedSymbolicRefIsBug() {
+		final Ref master = newRef("refs/heads/master", ID_ONE);
+		final Ref headR = newRef("HEAD", master);
+
+		loose = toList(master);
+		// loose should have added newRef("HEAD", "refs/heads/master")
+		resolved = toList(headR);
+
+		RefMap map = new RefMap("", packed, loose, resolved);
+		Iterator<Ref> itr = map.values().iterator();
+		try {
+			itr.hasNext();
+			fail("iterator did not catch bad input");
+		} catch (IllegalStateException err) {
+			// expected
+		}
+	}
+
+	public void testMerge_HeadMaster() {
+		final Ref master = newRef("refs/heads/master", ID_ONE);
+		final Ref headU = newRef("HEAD", "refs/heads/master");
+		final Ref headR = newRef("HEAD", master);
+
+		loose = toList(headU, master);
+		resolved = toList(headR);
+
+		RefMap map = new RefMap("", packed, loose, resolved);
+		assertEquals(2, map.size());
+		assertFalse(map.isEmpty());
+		assertTrue(map.containsKey("refs/heads/master"));
+		assertSame(master, map.get("refs/heads/master"));
+
+		// resolved overrides loose given same name
+		assertSame(headR, map.get("HEAD"));
+
+		Iterator<Ref> itr = map.values().iterator();
+		assertTrue(itr.hasNext());
+		assertSame(headR, itr.next());
+		assertTrue(itr.hasNext());
+		assertSame(master, itr.next());
+		assertFalse(itr.hasNext());
+	}
+
 	public void testMerge_PackedLooseLoose() {
 		final Ref refA = newRef("A", ID_ONE);
 		final Ref refB_ONE = newRef("B", ID_ONE);
@@ -297,6 +342,42 @@ public void testPut_WithPrefix() {
 		assertSame(refA_one, map.get("A"));
 	}
 
+	public void testPut_CollapseResolved() {
+		final Ref master = newRef("refs/heads/master", ID_ONE);
+		final Ref headU = newRef("HEAD", "refs/heads/master");
+		final Ref headR = newRef("HEAD", master);
+		final Ref a = newRef("refs/heads/A", ID_ONE);
+
+		loose = toList(headU, master);
+		resolved = toList(headR);
+
+		RefMap map = new RefMap("", packed, loose, resolved);
+		assertNull(map.put(a.getName(), a));
+		assertSame(a, map.get(a.getName()));
+		assertSame(headR, map.get("HEAD"));
+	}
+
+	public void testRemove() {
+		final Ref master = newRef("refs/heads/master", ID_ONE);
+		final Ref headU = newRef("HEAD", "refs/heads/master");
+		final Ref headR = newRef("HEAD", master);
+
+		packed = toList(master);
+		loose = toList(headU, master);
+		resolved = toList(headR);
+
+		RefMap map = new RefMap("", packed, loose, resolved);
+		assertNull(map.remove("not.a.reference"));
+
+		assertSame(master, map.remove("refs/heads/master"));
+		assertNull(map.get("refs/heads/master"));
+
+		assertSame(headR, map.remove("HEAD"));
+		assertNull(map.get("HEAD"));
+
+		assertTrue(map.isEmpty());
+	}
+
 	public void testToString_NoPrefix() {
 		final Ref a = newRef("refs/heads/A", ID_ONE);
 		final Ref b = newRef("refs/heads/B", ID_TWO);
@@ -376,7 +457,16 @@ private RefList<Ref> toList(Ref... refs) {
 		return b.toRefList();
 	}
 
+	private static Ref newRef(String name, String dst) {
+		return newRef(name,
+				new ObjectIdRef.Unpeeled(Ref.Storage.NEW, dst, null));
+	}
+
+	private static Ref newRef(String name, Ref dst) {
+		return new SymbolicRef(name, dst);
+	}
+
 	private static Ref newRef(String name, ObjectId id) {
-		return new Ref(Ref.Storage.LOOSE, name, id);
+		return new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, id);
 	}
 }
diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java
index 007a0e8d46102c1baac2fdac4d70e9a077975cd6..4a5d4603ca5984da465921abe38c3eb40491c661 100644
--- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java
+++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2010, Google Inc.
  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  * and other copyright owners as documented in the project's IP log.
@@ -146,7 +147,7 @@ void paintTriangleDown(final int cx, final int y, final int h) {
 	@Override
 	protected int drawLabel(int x, int y, Ref ref) {
 		String txt;
-		String name = ref.getOrigName();
+		String name = ref.getName();
 		if (name.startsWith(Constants.R_HEADS)) {
 			g.setBackground(Color.GREEN);
 			txt = name.substring(Constants.R_HEADS.length());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java
index b6da244e83854be55b9eb93d05fe9cfe8d20f83b..bd773c4705a81969aa8fa05e322850fad723afee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java
@@ -49,6 +49,7 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.channels.FileLock;
@@ -65,6 +66,15 @@
  * name.
  */
 public class LockFile {
+	static final String SUFFIX = ".lock"; //$NON-NLS-1$
+
+	/** Filter to skip over active lock files when listing a directory. */
+	static final FilenameFilter FILTER = new FilenameFilter() {
+		public boolean accept(File dir, String name) {
+			return !name.endsWith(SUFFIX);
+		}
+	};
+
 	private final File ref;
 
 	private final File lck;
@@ -87,7 +97,7 @@ public class LockFile {
 	 */
 	public LockFile(final File f) {
 		ref = f;
-		lck = new File(ref.getParentFile(), ref.getName() + ".lock");
+		lck = new File(ref.getParentFile(), ref.getName() + SUFFIX);
 	}
 
 	/**
@@ -334,6 +344,30 @@ public void setNeedStatInformation(final boolean on) {
 		needStatInformation = on;
 	}
 
+	/**
+	 * Wait until the lock file information differs from the old file.
+	 * <p>
+	 * This method tests both the length and the last modification date. If both
+	 * are the same, this method sleeps until it can force the new lock file's
+	 * modification date to be later than the target file.
+	 *
+	 * @throws InterruptedException
+	 *             the thread was interrupted before the last modified date of
+	 *             the lock file was different from the last modified date of
+	 *             the target file.
+	 */
+	public void waitForStatChange() throws InterruptedException {
+		if (ref.length() == lck.length()) {
+			long otime = ref.lastModified();
+			long ntime = lck.lastModified();
+			while (otime == ntime) {
+				Thread.sleep(25 /* milliseconds */);
+				lck.setLastModified(System.currentTimeMillis());
+				ntime = lck.lastModified();
+			}
+		}
+	}
+
 	/**
 	 * Commit this change and release the lock.
 	 * <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
new file mode 100644
index 0000000000000000000000000000000000000000..babfc6f0752f1c1dd9cb5feed500fb94cc8be1e3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * Copyright (C) 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;
+
+/** A {@link Ref} that points directly at an {@link ObjectId}. */
+public abstract class ObjectIdRef implements Ref {
+	/** Any reference whose peeled value is not yet known. */
+	public static class Unpeeled extends ObjectIdRef {
+		/**
+		 * Create a new ref pairing.
+		 *
+		 * @param st
+		 *            method used to store this ref.
+		 * @param name
+		 *            name of this ref.
+		 * @param id
+		 *            current value of the ref. May be null to indicate a ref
+		 *            that does not exist yet.
+		 */
+		public Unpeeled(Storage st, String name, ObjectId id) {
+			super(st, name, id);
+		}
+
+		public ObjectId getPeeledObjectId() {
+			return null;
+		}
+
+		public boolean isPeeled() {
+			return false;
+		}
+	}
+
+	/** An annotated tag whose peeled object has been cached. */
+	public static class PeeledTag extends ObjectIdRef {
+		private final ObjectId peeledObjectId;
+
+		/**
+		 * Create a new ref pairing.
+		 *
+		 * @param st
+		 *            method used to store this ref.
+		 * @param name
+		 *            name of this ref.
+		 * @param id
+		 *            current value of the ref.
+		 * @param p
+		 *            the first non-tag object that tag {@code id} points to.
+		 */
+		public PeeledTag(Storage st, String name, ObjectId id, ObjectId p) {
+			super(st, name, id);
+			peeledObjectId = p;
+		}
+
+		public ObjectId getPeeledObjectId() {
+			return peeledObjectId;
+		}
+
+		public boolean isPeeled() {
+			return true;
+		}
+	}
+
+	/** A reference to a non-tag object coming from a cached source. */
+	public static class PeeledNonTag extends ObjectIdRef {
+		/**
+		 * Create a new ref pairing.
+		 *
+		 * @param st
+		 *            method used to store this ref.
+		 * @param name
+		 *            name of this ref.
+		 * @param id
+		 *            current value of the ref. May be null to indicate a ref
+		 *            that does not exist yet.
+		 */
+		public PeeledNonTag(Storage st, String name, ObjectId id) {
+			super(st, name, id);
+		}
+
+		public ObjectId getPeeledObjectId() {
+			return null;
+		}
+
+		public boolean isPeeled() {
+			return true;
+		}
+	}
+
+	private final String name;
+
+	private final Storage storage;
+
+	private final ObjectId objectId;
+
+	/**
+	 * Create a new ref pairing.
+	 *
+	 * @param st
+	 *            method used to store this ref.
+	 * @param name
+	 *            name of this ref.
+	 * @param id
+	 *            current value of the ref. May be null to indicate a ref that
+	 *            does not exist yet.
+	 */
+	protected ObjectIdRef(Storage st, String name, ObjectId id) {
+		this.name = name;
+		this.storage = st;
+		this.objectId = id;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public boolean isSymbolic() {
+		return false;
+	}
+
+	public Ref getLeaf() {
+		return this;
+	}
+
+	public Ref getTarget() {
+		return this;
+	}
+
+	public ObjectId getObjectId() {
+		return objectId;
+	}
+
+	public Storage getStorage() {
+		return storage;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder r = new StringBuilder();
+		r.append("Ref[");
+		r.append(getName());
+		r.append('=');
+		r.append(ObjectId.toString(getObjectId()));
+		r.append(']');
+		return r.toString();
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
index 162e399d99ab45163bacc1d3d588e96ce9197f49..f119c44fe2d9de2b53e2cde6da67529813687ed0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
@@ -50,12 +50,12 @@
  * identifier. The object identifier can be any valid Git object (blob, tree,
  * commit, annotated tag, ...).
  * <p>
- * The ref name has the attributes of the ref that was asked for as well as
- * the ref it was resolved to for symbolic refs plus the object id it points
- * to and (for tags) the peeled target object id, i.e. the tag resolved
- * recursively until a non-tag object is referenced.
+ * The ref name has the attributes of the ref that was asked for as well as the
+ * ref it was resolved to for symbolic refs plus the object id it points to and
+ * (for tags) the peeled target object id, i.e. the tag resolved recursively
+ * until a non-tag object is referenced.
  */
-public class Ref {
+public interface Ref {
 	/** Location where a {@link Ref} is stored. */
 	public static enum Storage {
 		/**
@@ -73,8 +73,7 @@ public static enum Storage {
 		LOOSE(true, false),
 
 		/**
-		 * The ref is stored in the <code>packed-refs</code> file, with
-		 * others.
+		 * The ref is stored in the <code>packed-refs</code> file, with others.
 		 * <p>
 		 * Updating this ref requires rewriting the file, with perhaps many
 		 * other refs being included at the same time.
@@ -122,123 +121,63 @@ public boolean isPacked() {
 		}
 	}
 
-	private final Storage storage;
-
-	private final String name;
-
-	private ObjectId objectId;
-
-	private ObjectId peeledObjectId;
-
-	private final String origName;
-
-	private final boolean peeled;
-
 	/**
-	 * Create a new ref pairing.
+	 * What this ref is called within the repository.
 	 *
-	 * @param st
-	 *            method used to store this ref.
-	 * @param origName
-	 *            The name used to resolve this ref
-	 * @param refName
-	 *            name of this ref.
-	 * @param id
-	 *            current value of the ref. May be null to indicate a ref that
-	 *            does not exist yet.
+	 * @return name of this ref.
 	 */
-	public Ref(final Storage st, final String origName, final String refName, final ObjectId id) {
-		this(st, origName, refName, id, null, false);
-	}
+	public String getName();
 
 	/**
-	 * Create a new ref pairing.
+	 * Test if this reference is a symbolic reference.
+	 * <p>
+	 * A symbolic reference does not have its own {@link ObjectId} value, but
+	 * instead points to another {@code Ref} in the same database and always
+	 * uses that other reference's value as its own.
 	 *
-	 * @param st
-	 *            method used to store this ref.
-	 * @param refName
-	 *            name of this ref.
-	 * @param id
-	 *            current value of the ref. May be null to indicate a ref that
-	 *            does not exist yet.
+	 * @return true if this is a symbolic reference; false if this reference
+	 *         contains its own ObjectId.
 	 */
-	public Ref(final Storage st, final String refName, final ObjectId id) {
-		this(st, refName, refName, id, null, false);
-	}
+	public abstract boolean isSymbolic();
 
 	/**
-	 * Create a new ref pairing.
+	 * Traverse target references until {@link #isSymbolic()} is false.
+	 * <p>
+	 * If {@link #isSymbolic()} is false, returns {@code this}.
+	 * <p>
+	 * If {@link #isSymbolic()} is true, this method recursively traverses
+	 * {@link #getTarget()} until {@link #isSymbolic()} returns false.
+	 * <p>
+	 * This method is effectively
 	 *
-	 * @param st
-	 *            method used to store this ref.
-	 * @param origName
-	 *            The name used to resolve this ref
-	 * @param refName
-	 *            name of this ref.
-	 * @param id
-	 *            current value of the ref. May be null to indicate a ref that
-	 *            does not exist yet.
-	 * @param peel
-	 *            peeled value of the ref's tag. May be null if this is not a
-	 *            tag or not yet peeled (in which case the next parameter should be null)
-	 * @param peeled
-	 * 			  true if peel represents a the peeled value of the object
-	 */
-	public Ref(final Storage st, final String origName, final String refName, final ObjectId id,
-			final ObjectId peel, final boolean peeled) {
-		storage = st;
-		this.origName = origName;
-		name = refName;
-		objectId = id;
-		peeledObjectId = peel;
-		this.peeled = peeled;
-	}
-
-	/**
-	 * Create a new ref pairing.
+	 * <pre>
+	 * return isSymbolic() ? getTarget().getLeaf() : this;
+	 * </pre>
 	 *
-	 * @param st
-	 *            method used to store this ref.
-	 * @param refName
-	 *            name of this ref.
-	 * @param id
-	 *            current value of the ref. May be null to indicate a ref that
-	 *            does not exist yet.
-	 * @param peel
-	 *            peeled value of the ref's tag. May be null if this is not a
-	 *            tag or the peeled value is not known.
-	 * @param peeled
-	 * 			  true if peel represents a the peeled value of the object
+	 * @return the reference that actually stores the ObjectId value.
 	 */
-	public Ref(final Storage st, final String refName, final ObjectId id,
-			final ObjectId peel, boolean peeled) {
-		this(st, refName, refName, id, peel, peeled);
-	}
+	public abstract Ref getLeaf();
 
 	/**
-	 * What this ref is called within the repository.
+	 * Get the reference this reference points to, or {@code this}.
+	 * <p>
+	 * If {@link #isSymbolic()} is true this method returns the reference it
+	 * directly names, which might not be the leaf reference, but could be
+	 * another symbolic reference.
+	 * <p>
+	 * If this is a leaf level reference that contains its own ObjectId,this
+	 * method returns {@code this}.
 	 *
-	 * @return name of this ref.
-	 */
-	public String getName() {
-		return name;
-	}
-
-	/**
-	 * @return the originally resolved name
+	 * @return the target reference, or {@code this}.
 	 */
-	public String getOrigName() {
-		return origName;
-	}
+	public abstract Ref getTarget();
 
 	/**
 	 * Cached value of this ref.
 	 *
 	 * @return the value of this ref at the last time we read it.
 	 */
-	public ObjectId getObjectId() {
-		return objectId;
-	}
+	public abstract ObjectId getObjectId();
 
 	/**
 	 * Cached value of <code>ref^{}</code> (the ref peeled to commit).
@@ -247,18 +186,12 @@ public ObjectId getObjectId() {
 	 *         blob) that the annotated tag refers to; null if this ref does not
 	 *         refer to an annotated tag.
 	 */
-	public ObjectId getPeeledObjectId() {
-		if (!peeled)
-			return null;
-		return peeledObjectId;
-	}
+	public abstract ObjectId getPeeledObjectId();
 
 	/**
 	 * @return whether the Ref represents a peeled tag
 	 */
-	public boolean isPeeled() {
-		return peeled;
-	}
+	public abstract boolean isPeeled();
 
 	/**
 	 * How was this ref obtained?
@@ -268,18 +201,5 @@ public boolean isPeeled() {
 	 *
 	 * @return type of ref.
 	 */
-	public Storage getStorage() {
-		return storage;
-	}
-
-	public String toString() {
-		String o = "";
-		if (!origName.equals(name))
-			o = "(" + origName + ")";
-		return "Ref[" + o + name + "=" + ObjectId.toString(getObjectId()) + "]";
-	}
-
-	void setPeeledObjectId(final ObjectId id) {
-		peeledObjectId = id;
-	}
+	public abstract Storage getStorage();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
index fb6a27db30d8c831b83f9e2fc5f97b23188de824..22d0c1ad3bec92d6872de87003a30fea190bef71 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2007-2009, 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
@@ -44,499 +43,169 @@
 
 package org.eclipse.jgit.lib;
 
-import static org.eclipse.jgit.lib.Constants.R_TAGS;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.HashMap;
 import java.util.Map;
 
-import org.eclipse.jgit.errors.ObjectWritingException;
-import org.eclipse.jgit.lib.Ref.Storage;
-import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.IO;
-import org.eclipse.jgit.util.RawParseUtils;
-
-class RefDatabase {
-	private static final String REFS_SLASH = "refs/";
-
-	private static final String[] refSearchPaths = { "", REFS_SLASH,
-			R_TAGS, Constants.R_HEADS, Constants.R_REMOTES };
-
-	private final Repository db;
-
-	private final File gitDir;
-
-	private final File refsDir;
-
-	private Map<String, Ref> looseRefs;
-	private Map<String, Long> looseRefsMTime;
-	private Map<String, String> looseSymRefs;
-
-	private final File packedRefsFile;
-
-	private Map<String, Ref> packedRefs;
-
-	private long packedRefsLastModified;
-
-	private long packedRefsLength;
-
-	int lastRefModification;
-
-	int lastNotifiedRefModification;
-
-	private int refModificationCounter;
+/**
+ * Abstraction of name to {@link ObjectId} mapping.
+ * <p>
+ * A reference database stores a mapping of reference names to {@link ObjectId}.
+ * Every {@link Repository} has a single reference database, mapping names to
+ * the tips of the object graph contained by the {@link ObjectDatabase}.
+ */
+public abstract class RefDatabase {
+	/**
+	 * Order of prefixes to search when using non-absolute references.
+	 * <p>
+	 * The implementation's {@link #getRef(String)} method must take this search
+	 * space into consideration when locating a reference by name. The first
+	 * entry in the path is always {@code ""}, ensuring that absolute references
+	 * are resolved without further mangling.
+	 */
+	protected static final String[] SEARCH_PATH = { "", //$NON-NLS-1$
+			Constants.R_REFS, //
+			Constants.R_TAGS, //
+			Constants.R_HEADS, //
+			Constants.R_REMOTES //
+	};
 
-	RefDatabase(final Repository r) {
-		db = r;
-		gitDir = db.getDirectory();
-		refsDir = FS.resolve(gitDir, "refs");
-		packedRefsFile = FS.resolve(gitDir, Constants.PACKED_REFS);
-		clearCache();
-	}
+	/**
+	 * Maximum number of times a {@link SymbolicRef} can be traversed.
+	 * <p>
+	 * If the reference is nested deeper than this depth, the implementation
+	 * should either fail, or at least claim the reference does not exist.
+	 */
+	protected static final int MAX_SYMBOLIC_REF_DEPTH = 5;
 
-	synchronized void clearCache() {
-		looseRefs = new HashMap<String, Ref>();
-		looseRefsMTime = new HashMap<String, Long>();
-		packedRefs = new HashMap<String, Ref>();
-		looseSymRefs = new HashMap<String, String>();
-		packedRefsLastModified = 0;
-		packedRefsLength = 0;
-	}
+	/** Magic value for {@link #getRefs(String)} to return all references. */
+	public static final String ALL = "";//$NON-NLS-1$
 
-	Repository getRepository() {
-		return db;
-	}
+	/**
+	 * Initialize a new reference database at this location.
+	 *
+	 * @throws IOException
+	 *             the database could not be created.
+	 */
+	public abstract void create() throws IOException;
 
-	void create() {
-		refsDir.mkdir();
-		new File(refsDir, "heads").mkdir();
-		new File(refsDir, "tags").mkdir();
-	}
+	/** Close any resources held by this database. */
+	public abstract void close();
 
-	ObjectId idOf(final String name) throws IOException {
-		refreshPackedRefs();
-		final Ref r = readRefBasic(name, 0);
-		return r != null ? r.getObjectId() : null;
-	}
+	/**
+	 * Determine if a proposed reference name overlaps with an existing one.
+	 * <p>
+	 * Reference names use '/' as a component separator, and may be stored in a
+	 * hierarchical storage such as a directory on the local filesystem.
+	 * <p>
+	 * If the reference "refs/heads/foo" exists then "refs/heads/foo/bar" must
+	 * not exist, as a reference cannot have a value and also be a container for
+	 * other references at the same time.
+	 * <p>
+	 * If the reference "refs/heads/foo/bar" exists than the reference
+	 * "refs/heads/foo" cannot exist, for the same reason.
+	 *
+	 * @param name
+	 *            proposed name.
+	 * @return true if the name overlaps with an existing reference; false if
+	 *         using this name right now would be safe.
+	 * @throws IOException
+	 *             the database could not be read to check for conflicts.
+	 */
+	public abstract boolean isNameConflicting(String name) throws IOException;
 
 	/**
-	 * Create a command to update, create or delete a ref in this repository.
+	 * Create a symbolic reference from one name to another.
 	 *
 	 * @param name
-	 *            name of the ref the caller wants to modify.
-	 * @return an update command. The caller must finish populating this command
-	 *         and then invoke one of the update methods to actually make a
-	 *         change.
+	 *            the name of the reference. Should be {@link Constants#HEAD} or
+	 *            starting with {@link Constants#R_REFS}.
+	 * @param target
+	 *            the target of the reference.
 	 * @throws IOException
-	 *             a symbolic ref was passed in and could not be resolved back
-	 *             to the base ref, as the symbolic ref could not be read.
+	 *             the reference could not be created or overwritten.
 	 */
-	RefUpdate newUpdate(final String name) throws IOException {
-		return newUpdate(name, false);
-	}
+	public abstract void link(String name, String target) throws IOException;
 
 	/**
-	 * Create a command to update, create or delete a ref in this repository.
+	 * Create a new update command to create, modify or delete a reference.
 	 *
 	 * @param name
-	 *            name of the ref the caller wants to modify.
+	 *            the name of the reference.
 	 * @param detach
-	 *            true to detach the ref, i.e. replace symref with object ref
-	 * @return an update command. The caller must finish populating this command
-	 *         and then invoke one of the update methods to actually make a
-	 *         change.
+	 *            if {@code true} and {@code name} is currently a
+	 *            {@link SymbolicRef}, the update will replace it with an
+	 *            {@link ObjectIdRef}. Otherwise, the update will recursively
+	 *            traverse {@link SymbolicRef}s and operate on the leaf
+	 *            {@link ObjectIdRef}.
+	 * @return a new update for the requested name; never null.
 	 * @throws IOException
-	 *             a symbolic ref was passed in and could not be resolved back
-	 *             to the base ref, as the symbolic ref could not be read.
+	 *             the reference space cannot be accessed.
 	 */
-	RefUpdate newUpdate(final String name, boolean detach) throws IOException {
-		refreshPackedRefs();
-		Ref r = readRefBasic(name, 0);
-		if (r == null)
-			r = new Ref(Ref.Storage.NEW, name, null);
-		else if (detach)
-			r = new Ref(Ref.Storage.NEW, name, r.getObjectId());
-		return new RefUpdate(this, r, fileForRef(r.getName()));
-	}
-
-	void stored(final String origName, final String name, final ObjectId id, final long time) {
-		synchronized (this) {
-			looseRefs.put(name, new Ref(Ref.Storage.LOOSE, name, name, id));
-			looseRefsMTime.put(name, time);
-			setModified();
-		}
-		db.fireRefsMaybeChanged();
-	}
+	public abstract RefUpdate newUpdate(String name, boolean detach)
+			throws IOException;
 
 	/**
-	 * An set of update operations for renaming a ref
+	 * Create a new update command to rename a reference.
 	 *
-	 * @param fromRef Old ref name
-	 * @param toRef New ref name
-	 * @return a RefUpdate operation to rename a ref
+	 * @param fromName
+	 *            name of reference to rename from
+	 * @param toName
+	 *            name of reference to rename to
+	 * @return an update command that knows how to rename a branch to another.
 	 * @throws IOException
+	 *             the reference space cannot be accessed.
 	 */
-	RefRename newRename(String fromRef, String toRef) throws IOException {
-		refreshPackedRefs();
-		Ref f = readRefBasic(fromRef, 0);
-		Ref t = new Ref(Ref.Storage.NEW, toRef, null);
-		RefUpdate refUpdateFrom = new RefUpdate(this, f, fileForRef(f.getName()));
-		RefUpdate refUpdateTo = new RefUpdate(this, t, fileForRef(t.getName()));
-		return new RefRename(refUpdateTo, refUpdateFrom);
-	}
+	public abstract RefRename newRename(String fromName, String toName)
+			throws IOException;
 
 	/**
-	 * Writes a symref (e.g. HEAD) to disk
+	 * Read a single reference.
+	 * <p>
+	 * Aside from taking advantage of {@link #SEARCH_PATH}, this method may be
+	 * able to more quickly resolve a single reference name than obtaining the
+	 * complete namespace by {@code getRefs(ALL).get(name)}.
 	 *
 	 * @param name
-	 *            symref name
-	 * @param target
-	 *            pointed to ref
+	 *            the name of the reference. May be a short name which must be
+	 *            searched for using the standard {@link #SEARCH_PATH}.
+	 * @return the reference (if it exists); else {@code null}.
 	 * @throws IOException
+	 *             the reference space cannot be accessed.
 	 */
-	void link(final String name, final String target) throws IOException {
-		final byte[] content = Constants.encode("ref: " + target + "\n");
-		lockAndWriteFile(fileForRef(name), content);
-		synchronized (this) {
-			looseSymRefs.remove(name);
-			setModified();
-		}
-		db.fireRefsMaybeChanged();
-	}
-
-	void uncacheSymRef(String name) {
-		synchronized(this) {
-			looseSymRefs.remove(name);
-			setModified();
-		}
-	}
-
-	void uncacheRef(String name) {
-		looseRefs.remove(name);
-		looseRefsMTime.remove(name);
-		packedRefs.remove(name);
-	}
-
-	private void setModified() {
-		lastRefModification = refModificationCounter++;
-	}
-
-	Ref readRef(final String partialName) throws IOException {
-		refreshPackedRefs();
-		for (int k = 0; k < refSearchPaths.length; k++) {
-			final Ref r = readRefBasic(refSearchPaths[k] + partialName, 0);
-			if (r != null && r.getObjectId() != null)
-				return r;
-		}
-		return null;
-	}
+	public abstract Ref getRef(String name) throws IOException;
 
 	/**
-	 * @return all known refs (heads, tags, remotes).
+	 * Get a section of the reference namespace.
+	 *
+	 * @param prefix
+	 *            prefix to search the namespace with; must end with {@code /}.
+	 *            If the empty string ({@link #ALL}), obtain a complete snapshot
+	 *            of all references.
+	 * @return modifiable map that is a complete snapshot of the current
+	 *         reference namespace, with {@code prefix} removed from the start
+	 *         of each key. The map can be an unsorted map.
+	 * @throws IOException
+	 *             the reference space cannot be accessed.
 	 */
-	Map<String, Ref> getAllRefs() {
-		return readRefs();
-	}
+	public abstract Map<String, Ref> getRefs(String prefix) throws IOException;
 
 	/**
-	 * @return all tags; key is short tag name ("v1.0") and value of the entry
-	 *         contains the ref with the full tag name ("refs/tags/v1.0").
+	 * Peel a possibly unpeeled reference by traversing the annotated tags.
+	 * <p>
+	 * If the reference cannot be peeled (as it does not refer to an annotated
+	 * tag) the peeled id stays null, but {@link Ref#isPeeled()} will be true.
+	 * <p>
+	 * Implementors should check {@link Ref#isPeeled()} before performing any
+	 * additional work effort.
+	 *
+	 * @param ref
+	 *            The reference to peel
+	 * @return {@code ref} if {@code ref.isPeeled()} is true; otherwise a new
+	 *         Ref object representing the same data as Ref, but isPeeled() will
+	 *         be true and getPeeledObjectId() will contain the peeled object
+	 *         (or null).
+	 * @throws IOException
+	 *             the reference space or object space cannot be accessed.
 	 */
-	Map<String, Ref> getTags() {
-		final Map<String, Ref> tags = new HashMap<String, Ref>();
-		for (final Ref r : readRefs().values()) {
-			if (r.getName().startsWith(R_TAGS))
-				tags.put(r.getName().substring(R_TAGS.length()), r);
-		}
-		return tags;
-	}
-
-	private Map<String, Ref> readRefs() {
-		final HashMap<String, Ref> avail = new HashMap<String, Ref>();
-		readPackedRefs(avail);
-		readLooseRefs(avail, REFS_SLASH, refsDir);
-		try {
-			final Ref r = readRefBasic(Constants.HEAD, 0);
-			if (r != null && r.getObjectId() != null)
-				avail.put(Constants.HEAD, r);
-		} catch (IOException e) {
-			// ignore here
-		}
-		db.fireRefsMaybeChanged();
-		return avail;
-	}
-
-	private synchronized void readPackedRefs(final Map<String, Ref> avail) {
-		refreshPackedRefs();
-		avail.putAll(packedRefs);
-	}
-
-	private void readLooseRefs(final Map<String, Ref> avail,
-			final String prefix, final File dir) {
-		final File[] entries = dir.listFiles();
-		if (entries == null)
-			return;
-
-		for (final File ent : entries) {
-			final String entName = ent.getName();
-			if (".".equals(entName) || "..".equals(entName))
-				continue;
-			if (ent.isDirectory()) {
-				readLooseRefs(avail, prefix + entName + "/", ent);
-			} else {
-				try {
-					final Ref ref = readRefBasic(prefix + entName, 0);
-					if (ref != null)
-						avail.put(ref.getOrigName(), ref);
-				} catch (IOException e) {
-					continue;
-				}
-			}
-		}
-	}
-
-	Ref peel(final Ref ref) {
-		if (ref.isPeeled())
-			return ref;
-		ObjectId peeled = null;
-		try {
-			Object target = db.mapObject(ref.getObjectId(), ref.getName());
-			while (target instanceof Tag) {
-				final Tag tag = (Tag)target;
-				peeled = tag.getObjId();
-				if (Constants.TYPE_TAG.equals(tag.getType()))
-					target = db.mapObject(tag.getObjId(), ref.getName());
-				else
-					break;
-			}
-		} catch (IOException e) {
-			// Ignore a read error.  Callers will also get the same error
-			// if they try to use the result of getPeeledObjectId.
-		}
-		return new Ref(ref.getStorage(), ref.getName(), ref.getObjectId(), peeled, true);
-
-	}
-
-	private File fileForRef(final String name) {
-		if (name.startsWith(REFS_SLASH))
-			return new File(refsDir, name.substring(REFS_SLASH.length()));
-		return new File(gitDir, name);
-	}
-
-	private Ref readRefBasic(final String name, final int depth) throws IOException {
-		return readRefBasic(name, name, depth);
-	}
-
-	private synchronized Ref readRefBasic(final String origName,
-			final String name, final int depth) throws IOException {
-		// Prefer loose ref to packed ref as the loose
-		// file can be more up-to-date than a packed one.
-		//
-		Ref ref = looseRefs.get(origName);
-		final File loose = fileForRef(name);
-		final long mtime = loose.lastModified();
-		if (ref != null) {
-			Long cachedlastModified = looseRefsMTime.get(name);
-			if (cachedlastModified != null && cachedlastModified == mtime) {
-				if (packedRefs.containsKey(origName))
-					return new Ref(Storage.LOOSE_PACKED, origName, ref
-							.getObjectId(), ref.getPeeledObjectId(), ref
-							.isPeeled());
-				else
-					return ref;
-			}
-			looseRefs.remove(origName);
-			looseRefsMTime.remove(origName);
-		}
-
-		if (mtime == 0) {
-			// If last modified is 0 the file does not exist.
-			// Try packed cache.
-			//
-			ref = packedRefs.get(name);
-			if (ref != null)
-				if (!ref.getOrigName().equals(origName))
-					ref = new Ref(Storage.LOOSE_PACKED, origName, name, ref.getObjectId());
-			return ref;
-		}
-
-		String line = null;
-		try {
-			Long cachedlastModified = looseRefsMTime.get(name);
-			if (cachedlastModified != null && cachedlastModified == mtime) {
-				line = looseSymRefs.get(name);
-			}
-			if (line == null) {
-				line = readLine(loose);
-				looseRefsMTime.put(name, mtime);
-				looseSymRefs.put(name, line);
-			}
-		} catch (FileNotFoundException notLoose) {
-			return packedRefs.get(name);
-		}
-
-		if (line == null || line.length() == 0) {
-			looseRefs.remove(origName);
-			looseRefsMTime.remove(origName);
-			return new Ref(Ref.Storage.LOOSE, origName, name, null);
-		}
-
-		if (line.startsWith("ref: ")) {
-			if (depth >= 5) {
-				throw new IOException("Exceeded maximum ref depth of " + depth
-						+ " at " + name + ".  Circular reference?");
-			}
-
-			final String target = line.substring("ref: ".length());
-			Ref r = readRefBasic(target, target, depth + 1);
-			Long cachedMtime = looseRefsMTime.get(name);
-			if (cachedMtime != null && cachedMtime != mtime)
-				setModified();
-			looseRefsMTime.put(name, mtime);
-			if (r == null)
-				return new Ref(Ref.Storage.LOOSE, origName, target, null);
-			if (!origName.equals(r.getName()))
-				r = new Ref(Ref.Storage.LOOSE_PACKED, origName, r.getName(), r.getObjectId(), r.getPeeledObjectId(), true);
-			return r;
-		}
-
-		setModified();
-
-		final ObjectId id;
-		try {
-			id = ObjectId.fromString(line);
-		} catch (IllegalArgumentException notRef) {
-			throw new IOException("Not a ref: " + name + ": " + line);
-		}
-
-		Storage storage;
-		if (packedRefs.containsKey(name))
-			storage = Ref.Storage.LOOSE_PACKED;
-		else
-			storage = Ref.Storage.LOOSE;
-		ref = new Ref(storage, name, id);
-		looseRefs.put(name, ref);
-		looseRefsMTime.put(name, mtime);
-
-		if (!origName.equals(name)) {
-			ref = new Ref(Ref.Storage.LOOSE, origName, name, id);
-			looseRefs.put(origName, ref);
-		}
-
-		return ref;
-	}
-
-	private synchronized void refreshPackedRefs() {
-		final long currTime = packedRefsFile.lastModified();
-		final long currLen = currTime == 0 ? 0 : packedRefsFile.length();
-		if (currTime == packedRefsLastModified && currLen == packedRefsLength)
-			return;
-		if (currTime == 0) {
-			packedRefsLastModified = 0;
-			packedRefsLength = 0;
-			packedRefs = new HashMap<String, Ref>();
-			return;
-		}
-
-		final Map<String, Ref> newPackedRefs = new HashMap<String, Ref>();
-		try {
-			final BufferedReader b = openReader(packedRefsFile);
-			try {
-				String p;
-				Ref last = null;
-				while ((p = b.readLine()) != null) {
-					if (p.charAt(0) == '#')
-						continue;
-
-					if (p.charAt(0) == '^') {
-						if (last == null)
-							throw new IOException("Peeled line before ref.");
-
-						final ObjectId id = ObjectId.fromString(p.substring(1));
-						last = new Ref(Ref.Storage.PACKED, last.getName(), last
-								.getName(), last.getObjectId(), id, true);
-						newPackedRefs.put(last.getName(), last);
-						continue;
-					}
-
-					final int sp = p.indexOf(' ');
-					final ObjectId id = ObjectId.fromString(p.substring(0, sp));
-					final String name = copy(p, sp + 1, p.length());
-					last = new Ref(Ref.Storage.PACKED, name, name, id);
-					newPackedRefs.put(last.getName(), last);
-				}
-			} finally {
-				b.close();
-			}
-			packedRefsLastModified = currTime;
-			packedRefsLength = currLen;
-			packedRefs = newPackedRefs;
-			setModified();
-		} catch (FileNotFoundException noPackedRefs) {
-			// Ignore it and leave the new map empty.
-			//
-			packedRefsLastModified = 0;
-			packedRefsLength = 0;
-			packedRefs = newPackedRefs;
-		} catch (IOException e) {
-			throw new RuntimeException("Cannot read packed refs", e);
-		}
-	}
-
-	private static String copy(final String src, final int off, final int end) {
-		return new StringBuilder(end - off).append(src, off, end).toString();
-	}
-
-	private void lockAndWriteFile(File file, byte[] content) throws IOException {
-		String name = file.getName();
-		final LockFile lck = new LockFile(file);
-		if (!lck.lock())
-			throw new ObjectWritingException("Unable to lock " + name);
-		try {
-			lck.write(content);
-		} catch (IOException ioe) {
-			throw new ObjectWritingException("Unable to write " + name, ioe);
-		}
-		if (!lck.commit())
-			throw new ObjectWritingException("Unable to write " + name);
-	}
-
-	synchronized void removePackedRef(String name) throws IOException {
-		packedRefs.remove(name);
-		writePackedRefs();
-	}
-
-	private void writePackedRefs() throws IOException {
-		new RefWriter(packedRefs.values()) {
-			@Override
-			protected void writeFile(String name, byte[] content) throws IOException {
-				lockAndWriteFile(new File(db.getDirectory(), name), content);
-			}
-		}.writePackedRefs();
-	}
-
-	private static String readLine(final File file)
-			throws FileNotFoundException, IOException {
-		final byte[] buf = IO.readFully(file, 4096);
-		int n = buf.length;
-
-		// remove trailing whitespaces
-		while (n > 0 && Character.isWhitespace(buf[n - 1]))
-			n--;
-
-		if (n == 0)
-			return null;
-		return RawParseUtils.decode(buf, 0, n);
-	}
-
-	private static BufferedReader openReader(final File fileLocation)
-			throws FileNotFoundException {
-		return new BufferedReader(new InputStreamReader(new FileInputStream(
-				fileLocation), Constants.CHARSET));
-	}
+	public abstract Ref peel(Ref ref) throws IOException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java
new file mode 100644
index 0000000000000000000000000000000000000000..9fdb48fe83e28d2bb1e0fdf5a84b411e02ad9449
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java
@@ -0,0 +1,1015 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2009-2010, Google Inc.
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, 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 static org.eclipse.jgit.lib.Constants.CHARSET;
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.LOGS;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
+import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Constants.R_REFS;
+import static org.eclipse.jgit.lib.Constants.R_REMOTES;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
+import static org.eclipse.jgit.lib.Constants.encode;
+import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jgit.errors.ObjectWritingException;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.RefList;
+import org.eclipse.jgit.util.RefMap;
+
+/**
+ * Traditional file system based {@link RefDatabase}.
+ * <p>
+ * This is the classical reference database representation for a Git repository.
+ * References are stored in two formats: loose, and packed.
+ * <p>
+ * Loose references are stored as individual files within the {@code refs/}
+ * directory. The file name matches the reference name and the file contents is
+ * the current {@link ObjectId} in string form.
+ * <p>
+ * Packed references are stored in a single text file named {@code packed-refs}.
+ * In the packed format, each reference is stored on its own line. This file
+ * reduces the number of files needed for large reference spaces, reducing the
+ * overall size of a Git repository on disk.
+ */
+public class RefDirectory extends RefDatabase {
+	/** Magic string denoting the start of a symbolic reference file. */
+	public static final String SYMREF = "ref: "; //$NON-NLS-1$
+
+	/** Magic string denoting the header of a packed-refs file. */
+	public static final String PACKED_REFS_HEADER = "# pack-refs with:"; //$NON-NLS-1$
+
+	/** 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 File gitDir;
+
+	private final File refsDir;
+
+	private final File logsDir;
+
+	private final File logsRefsDir;
+
+	private final File packedRefsFile;
+
+	/**
+	 * Immutable sorted list of loose references.
+	 * <p>
+	 * Symbolic references in this collection are stored unresolved, that is
+	 * their target appears to be a new reference with no ObjectId. These are
+	 * converted into resolved references during a get operation, ensuring the
+	 * live value is always returned.
+	 */
+	private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<RefList<LooseRef>>();
+
+	/** Immutable sorted list of packed references. */
+	private final AtomicReference<PackedRefList> packedRefs = new AtomicReference<PackedRefList>();
+
+	/**
+	 * Number of modifications made to this database.
+	 * <p>
+	 * This counter is incremented when a change is made, or detected from the
+	 * filesystem during a read operation.
+	 */
+	private final AtomicInteger modCnt = new AtomicInteger();
+
+	/**
+	 * Last {@link #modCnt} that we sent to listeners.
+	 * <p>
+	 * This value is compared to {@link #modCnt}, and a notification is sent to
+	 * the listeners only when it differs.
+	 */
+	private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
+
+	RefDirectory(final Repository db) {
+		parent = db;
+		gitDir = db.getDirectory();
+		refsDir = FS.resolve(gitDir, R_REFS);
+		logsDir = FS.resolve(gitDir, LOGS);
+		logsRefsDir = FS.resolve(gitDir, LOGS + '/' + R_REFS);
+		packedRefsFile = FS.resolve(gitDir, PACKED_REFS);
+
+		looseRefs.set(RefList.<LooseRef> emptyList());
+		packedRefs.set(PackedRefList.NO_PACKED_REFS);
+	}
+
+	Repository getRepository() {
+		return parent;
+	}
+
+	public void create() throws IOException {
+		refsDir.mkdir();
+		logsDir.mkdir();
+		logsRefsDir.mkdir();
+
+		new File(refsDir, R_HEADS.substring(R_REFS.length())).mkdir();
+		new File(refsDir, R_TAGS.substring(R_REFS.length())).mkdir();
+		new File(logsRefsDir, R_HEADS.substring(R_REFS.length())).mkdir();
+	}
+
+	@Override
+	public void close() {
+		// We have no resources to close.
+	}
+
+	void rescan() {
+		looseRefs.set(RefList.<LooseRef> emptyList());
+		packedRefs.set(PackedRefList.NO_PACKED_REFS);
+	}
+
+	@Override
+	public boolean isNameConflicting(String name) throws IOException {
+		RefList<Ref> packed = getPackedRefs();
+		RefList<LooseRef> loose = getLooseRefs();
+
+		// Cannot be nested within an existing reference.
+		int lastSlash = name.lastIndexOf('/');
+		while (0 < lastSlash) {
+			String needle = name.substring(0, lastSlash);
+			if (loose.contains(needle) || packed.contains(needle))
+				return true;
+			lastSlash = name.lastIndexOf('/', lastSlash - 1);
+		}
+
+		// Cannot be the container of an existing reference.
+		String prefix = name + '/';
+		int idx;
+
+		idx = -(packed.find(prefix) + 1);
+		if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix))
+			return true;
+
+		idx = -(loose.find(prefix) + 1);
+		if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix))
+			return true;
+
+		return false;
+	}
+
+	private RefList<LooseRef> getLooseRefs() {
+		final RefList<LooseRef> oldLoose = looseRefs.get();
+
+		LooseScanner scan = new LooseScanner(oldLoose);
+		scan.scan(ALL);
+
+		RefList<LooseRef> loose;
+		if (scan.newLoose != null) {
+			loose = scan.newLoose.toRefList();
+			if (looseRefs.compareAndSet(oldLoose, loose))
+				modCnt.incrementAndGet();
+		} else
+			loose = oldLoose;
+		return loose;
+	}
+
+	@Override
+	public Ref getRef(final String needle) throws IOException {
+		final RefList<Ref> packed = getPackedRefs();
+		Ref ref = null;
+		for (String prefix : SEARCH_PATH) {
+			ref = readRef(prefix + needle, packed);
+			if (ref != null) {
+				ref = resolve(ref, 0, null, null, packed);
+				break;
+			}
+		}
+		fireRefsChanged();
+		return ref;
+	}
+
+	@Override
+	public Map<String, Ref> getRefs(String prefix) throws IOException {
+		final RefList<Ref> packed = getPackedRefs();
+		final RefList<LooseRef> oldLoose = looseRefs.get();
+
+		LooseScanner scan = new LooseScanner(oldLoose);
+		scan.scan(prefix);
+
+		RefList<LooseRef> loose;
+		if (scan.newLoose != null) {
+			loose = scan.newLoose.toRefList();
+			if (looseRefs.compareAndSet(oldLoose, loose))
+				modCnt.incrementAndGet();
+		} else
+			loose = oldLoose;
+		fireRefsChanged();
+
+		RefList.Builder<Ref> symbolic = scan.symbolic;
+		for (int idx = 0; idx < symbolic.size();) {
+			Ref ref = symbolic.get(idx);
+			ref = resolve(ref, 0, prefix, loose, packed);
+			if (ref != null && ref.getObjectId() != null) {
+				symbolic.set(idx, ref);
+				idx++;
+			} else {
+				// A broken symbolic reference, we have to drop it from the
+				// collections the client is about to receive. Should be a
+				// rare occurrence so pay a copy penalty.
+				loose = loose.remove(idx);
+				symbolic.remove(idx);
+			}
+		}
+
+		return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList());
+	}
+
+	@SuppressWarnings("unchecked")
+	private RefList<Ref> upcast(RefList<? extends Ref> loose) {
+		return (RefList<Ref>) loose;
+	}
+
+	private class LooseScanner {
+		private final RefList<LooseRef> curLoose;
+
+		private int curIdx;
+
+		final RefList.Builder<Ref> symbolic = new RefList.Builder<Ref>(4);
+
+		RefList.Builder<LooseRef> newLoose;
+
+		LooseScanner(final RefList<LooseRef> curLoose) {
+			this.curLoose = curLoose;
+		}
+
+		void scan(String prefix) {
+			if (ALL.equals(prefix)) {
+				scanOne(HEAD);
+				scanTree(R_REFS, refsDir);
+
+				// If any entries remain, they are deleted, drop them.
+				if (newLoose == null && curIdx < curLoose.size())
+					newLoose = curLoose.copy(curIdx);
+
+			} else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) {
+				curIdx = -(curLoose.find(prefix) + 1);
+				File dir = new File(refsDir, prefix.substring(R_REFS.length()));
+				scanTree(prefix, dir);
+
+				// Skip over entries still within the prefix; these have
+				// been removed from the directory.
+				while (curIdx < curLoose.size()) {
+					if (!curLoose.get(curIdx).getName().startsWith(prefix))
+						break;
+					if (newLoose == null)
+						newLoose = curLoose.copy(curIdx);
+					curIdx++;
+				}
+
+				// Keep any entries outside of the prefix space, we
+				// do not know anything about their status.
+				if (newLoose != null) {
+					while (curIdx < curLoose.size())
+						newLoose.add(curLoose.get(curIdx++));
+				}
+			}
+		}
+
+		private void scanTree(String prefix, File dir) {
+			final String[] entries = dir.list(LockFile.FILTER);
+			if (entries != null && 0 < entries.length) {
+				Arrays.sort(entries);
+				for (String name : entries) {
+					File e = new File(dir, name);
+					if (e.isDirectory())
+						scanTree(prefix + name + '/', e);
+					else
+						scanOne(prefix + name);
+				}
+			}
+		}
+
+		private void scanOne(String name) {
+			LooseRef cur;
+
+			if (curIdx < curLoose.size()) {
+				do {
+					cur = curLoose.get(curIdx);
+					int cmp = RefComparator.compareTo(cur, name);
+					if (cmp < 0) {
+						// Reference is not loose anymore, its been deleted.
+						// Skip the name in the new result list.
+						if (newLoose == null)
+							newLoose = curLoose.copy(curIdx);
+						curIdx++;
+						cur = null;
+						continue;
+					}
+
+					if (cmp > 0) // Newly discovered loose reference.
+						cur = null;
+					break;
+				} while (curIdx < curLoose.size());
+			} else
+				cur = null; // Newly discovered loose reference.
+
+			LooseRef n;
+			try {
+				n = scanRef(cur, name);
+			} catch (IOException notValid) {
+				n = null;
+			}
+
+			if (n != null) {
+				if (cur != n && newLoose == null)
+					newLoose = curLoose.copy(curIdx);
+				if (newLoose != null)
+					newLoose.add(n);
+				if (n.isSymbolic())
+					symbolic.add(n);
+			} else if (cur != null) {
+				// Tragically, this file is no longer a loose reference.
+				// Kill our cached entry of it.
+				if (newLoose == null)
+					newLoose = curLoose.copy(curIdx);
+			}
+
+			if (cur != null)
+				curIdx++;
+		}
+	}
+
+	@Override
+	public Ref peel(final Ref ref) throws IOException {
+		final Ref leaf = ref.getLeaf();
+		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());
+		}
+
+		// 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.
+		if (leaf.getStorage().isLoose()) {
+			RefList<LooseRef> curList = looseRefs.get();
+			int idx = curList.find(leaf.getName());
+			if (0 <= idx && curList.get(idx) == leaf) {
+				LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf);
+				RefList<LooseRef> newList = curList.set(idx, asPeeled);
+				looseRefs.compareAndSet(curList, newList);
+			}
+		}
+
+		return recreate(ref, newLeaf);
+	}
+
+	private static Ref recreate(final Ref old, final ObjectIdRef leaf) {
+		if (old.isSymbolic()) {
+			Ref dst = recreate(old.getTarget(), leaf);
+			return new SymbolicRef(old.getName(), dst);
+		}
+		return leaf;
+	}
+
+	@Override
+	public void link(String name, String target) throws IOException {
+		LockFile lck = new LockFile(fileFor(name));
+		if (!lck.lock())
+			throw new IOException("Cannot lock " + name);
+		lck.setNeedStatInformation(true);
+		try {
+			lck.write(encode(SYMREF + target + '\n'));
+			if (!lck.commit())
+				throw new IOException("Cannot write " + name);
+		} finally {
+			lck.unlock();
+		}
+		putLooseRef(newSymbolicRef(lck.getCommitLastModified(), name, target));
+		fireRefsChanged();
+	}
+
+	@Override
+	public RefDirectoryUpdate newUpdate(String name, boolean detach)
+			throws IOException {
+		final RefList<Ref> packed = getPackedRefs();
+		Ref ref = readRef(name, packed);
+		if (ref != null)
+			ref = resolve(ref, 0, null, null, packed);
+		if (ref == null)
+			ref = new ObjectIdRef.Unpeeled(NEW, name, null);
+		else if (detach && ref.isSymbolic())
+			ref = new ObjectIdRef.Unpeeled(LOOSE, name, ref.getObjectId());
+		return new RefDirectoryUpdate(this, ref);
+	}
+
+	@Override
+	public RefDirectoryRename newRename(String fromName, String toName)
+			throws IOException {
+		RefDirectoryUpdate from = newUpdate(fromName, false);
+		RefDirectoryUpdate to = newUpdate(toName, false);
+		return new RefDirectoryRename(from, to);
+	}
+
+	void stored(RefDirectoryUpdate update, long modified) {
+		final ObjectId target = update.getNewObjectId().copy();
+		final Ref leaf = update.getRef().getLeaf();
+		putLooseRef(new LooseUnpeeled(modified, leaf.getName(), target));
+	}
+
+	private void putLooseRef(LooseRef ref) {
+		RefList<LooseRef> cList, nList;
+		do {
+			cList = looseRefs.get();
+			nList = cList.put(ref);
+		} while (!looseRefs.compareAndSet(cList, nList));
+		modCnt.incrementAndGet();
+		fireRefsChanged();
+	}
+
+	void delete(RefDirectoryUpdate update) throws IOException {
+		Ref dst = update.getRef().getLeaf();
+		String name = dst.getName();
+
+		// Write the packed-refs file using an atomic update. We might
+		// wind up reading it twice, before and after the lock, to ensure
+		// we don't miss an edit made externally.
+		final PackedRefList packed = getPackedRefs();
+		if (packed.contains(name)) {
+			LockFile lck = new LockFile(packedRefsFile);
+			if (!lck.lock())
+				throw new IOException("Cannot lock " + packedRefsFile);
+			try {
+				PackedRefList cur = readPackedRefs(0, 0);
+				int idx = cur.find(name);
+				if (0 <= idx)
+					commitPackedRefs(lck, cur.remove(idx), packed);
+			} finally {
+				lck.unlock();
+			}
+		}
+
+		RefList<LooseRef> curLoose, newLoose;
+		do {
+			curLoose = looseRefs.get();
+			int idx = curLoose.find(name);
+			if (idx < 0)
+				break;
+			newLoose = curLoose.remove(idx);
+		} while (!looseRefs.compareAndSet(curLoose, newLoose));
+
+		int levels = levelsIn(name) - 2;
+		delete(logFor(name), levels);
+		if (dst.getStorage().isLoose()) {
+			update.unlock();
+			delete(fileFor(name), levels);
+		}
+
+		modCnt.incrementAndGet();
+		fireRefsChanged();
+	}
+
+	void log(final RefUpdate update, final String msg) throws IOException {
+		final ObjectId oldId = update.getOldObjectId();
+		final ObjectId newId = update.getNewObjectId();
+		final Ref ref = update.getRef();
+
+		PersonIdent ident = update.getRefLogIdent();
+		if (ident == null)
+			ident = new PersonIdent(parent);
+		else
+			ident = new PersonIdent(ident);
+
+		final StringBuilder r = new StringBuilder();
+		r.append(ObjectId.toString(oldId));
+		r.append(' ');
+		r.append(ObjectId.toString(newId));
+		r.append(' ');
+		r.append(ident.toExternalString());
+		r.append('\t');
+		r.append(msg);
+		r.append('\n');
+		final byte[] rec = encode(r.toString());
+
+		if (ref.isSymbolic())
+			log(ref.getName(), rec);
+		log(ref.getLeaf().getName(), rec);
+	}
+
+	private void log(final String refName, final byte[] rec) throws IOException {
+		final File log = logFor(refName);
+		final boolean write;
+		if (isLogAllRefUpdates() && shouldAutoCreateLog(refName))
+			write = true;
+		else if (log.isFile())
+			write = true;
+		else
+			write = false;
+
+		if (write) {
+			FileOutputStream out;
+			try {
+				out = new FileOutputStream(log, true);
+			} catch (FileNotFoundException err) {
+				final File dir = log.getParentFile();
+				if (dir.exists())
+					throw err;
+				if (!dir.mkdirs() && !dir.isDirectory())
+					throw new IOException("Cannot create directory " + dir);
+				out = new FileOutputStream(log, true);
+			}
+			try {
+				out.write(rec);
+			} finally {
+				out.close();
+			}
+		}
+	}
+
+	private boolean isLogAllRefUpdates() {
+		return parent.getConfig().getCore().isLogAllRefUpdates();
+	}
+
+	private boolean shouldAutoCreateLog(final String refName) {
+		return refName.equals(HEAD) //
+				|| refName.startsWith(R_HEADS) //
+				|| refName.startsWith(R_REMOTES);
+	}
+
+	private Ref resolve(final Ref ref, int depth, String prefix,
+			RefList<LooseRef> loose, RefList<Ref> packed) throws IOException {
+		if (ref.isSymbolic()) {
+			Ref dst = ref.getTarget();
+
+			if (MAX_SYMBOLIC_REF_DEPTH <= depth)
+				return null; // claim it doesn't exist
+
+			// If the cached value can be assumed to be current due to a
+			// recent scan of the loose directory, use it.
+			if (loose != null && dst.getName().startsWith(prefix)) {
+				int idx;
+				if (0 <= (idx = loose.find(dst.getName())))
+					dst = loose.get(idx);
+				else if (0 <= (idx = packed.find(dst.getName())))
+					dst = packed.get(idx);
+				else
+					return ref;
+			} else {
+				dst = readRef(dst.getName(), packed);
+				if (dst == null)
+					return ref;
+			}
+
+			dst = resolve(dst, depth + 1, prefix, loose, packed);
+			if (dst == null)
+				return null;
+			return new SymbolicRef(ref.getName(), dst);
+		}
+		return ref;
+	}
+
+	private PackedRefList getPackedRefs() throws IOException {
+		long size = packedRefsFile.length();
+		long mtime = size != 0 ? packedRefsFile.lastModified() : 0;
+
+		final PackedRefList curList = packedRefs.get();
+		if (size == curList.lastSize && mtime == curList.lastModified)
+			return curList;
+
+		final PackedRefList newList = readPackedRefs(size, mtime);
+		if (packedRefs.compareAndSet(curList, newList))
+			modCnt.incrementAndGet();
+		return newList;
+	}
+
+	private PackedRefList readPackedRefs(long size, long mtime)
+			throws IOException {
+		final BufferedReader br;
+		try {
+			br = new BufferedReader(new InputStreamReader(new FileInputStream(
+					packedRefsFile), CHARSET));
+		} catch (FileNotFoundException noPackedRefs) {
+			// Ignore it and leave the new list empty.
+			return PackedRefList.NO_PACKED_REFS;
+		}
+		try {
+			return new PackedRefList(parsePackedRefs(br), size, mtime);
+		} finally {
+			br.close();
+		}
+	}
+
+	private RefList<Ref> parsePackedRefs(final BufferedReader br)
+			throws IOException {
+		RefList.Builder<Ref> all = new RefList.Builder<Ref>();
+		Ref last = null;
+		boolean peeled = false;
+		boolean needSort = false;
+
+		String p;
+		while ((p = br.readLine()) != null) {
+			if (p.charAt(0) == '#') {
+				if (p.startsWith(PACKED_REFS_HEADER)) {
+					p = p.substring(PACKED_REFS_HEADER.length());
+					peeled = p.contains(PACKED_REFS_PEELED);
+				}
+				continue;
+			}
+
+			if (p.charAt(0) == '^') {
+				if (last == null)
+					throw new IOException("Peeled line before ref.");
+
+				ObjectId id = ObjectId.fromString(p.substring(1));
+				last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last
+						.getObjectId(), id);
+				all.set(all.size() - 1, last);
+				continue;
+			}
+
+			int sp = p.indexOf(' ');
+			ObjectId id = ObjectId.fromString(p.substring(0, sp));
+			String name = copy(p, sp + 1, p.length());
+			ObjectIdRef cur;
+			if (peeled)
+				cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id);
+			else
+				cur = new ObjectIdRef.Unpeeled(PACKED, name, id);
+			if (last != null && RefComparator.compareTo(last, cur) > 0)
+				needSort = true;
+			all.add(cur);
+			last = cur;
+		}
+
+		if (needSort)
+			all.sort();
+		return all.toRefList();
+	}
+
+	private static String copy(final String src, final int off, final int end) {
+		// Don't use substring since it could leave a reference to the much
+		// larger existing string. Force construction of a full new object.
+		return new StringBuilder(end - off).append(src, off, end).toString();
+	}
+
+	private void commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
+			final PackedRefList oldPackedList) throws IOException {
+		new RefWriter(refs) {
+			@Override
+			protected void writeFile(String name, byte[] content)
+					throws IOException {
+				lck.setNeedStatInformation(true);
+				try {
+					lck.write(content);
+				} catch (IOException ioe) {
+					throw new ObjectWritingException("Unable to write " + name,
+							ioe);
+				}
+				try {
+					lck.waitForStatChange();
+				} catch (InterruptedException e) {
+					lck.unlock();
+					throw new ObjectWritingException("Interrupted writing "
+							+ name);
+				}
+				if (!lck.commit())
+					throw new ObjectWritingException("Unable to write " + name);
+
+				packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs,
+						content.length, lck.getCommitLastModified()));
+			}
+		}.writePackedRefs();
+	}
+
+	private Ref readRef(String name, RefList<Ref> packed) throws IOException {
+		final RefList<LooseRef> curList = looseRefs.get();
+		final int idx = curList.find(name);
+		if (0 <= idx) {
+			final LooseRef o = curList.get(idx);
+			final LooseRef n = scanRef(o, name);
+			if (n == null) {
+				if (looseRefs.compareAndSet(curList, curList.remove(idx)))
+					modCnt.incrementAndGet();
+				return packed.get(name);
+			}
+
+			if (o == n)
+				return n;
+			if (looseRefs.compareAndSet(curList, curList.set(idx, n)))
+				modCnt.incrementAndGet();
+			return n;
+		}
+
+		final LooseRef n = scanRef(null, name);
+		if (n == null)
+			return packed.get(name);
+		if (looseRefs.compareAndSet(curList, curList.add(idx, n)))
+			modCnt.incrementAndGet();
+		return n;
+	}
+
+	private LooseRef scanRef(LooseRef ref, String name) throws IOException {
+		final File path = fileFor(name);
+		final long modified = path.lastModified();
+
+		if (ref != null) {
+			if (ref.getLastModified() == modified)
+				return ref;
+			name = ref.getName();
+		} else if (modified == 0)
+			return null;
+
+		final byte[] buf;
+		try {
+			buf = IO.readFully(path, 4096);
+		} catch (FileNotFoundException noFile) {
+			return null; // doesn't exist; not a reference.
+		}
+
+		int n = buf.length;
+		if (n == 0)
+			return null; // empty file; not a reference.
+
+		if (isSymRef(buf, n)) {
+			// trim trailing whitespace
+			while (0 < n && Character.isWhitespace(buf[n - 1]))
+				n--;
+			if (n < 6) {
+				String content = RawParseUtils.decode(buf, 0, n);
+				throw new IOException("Not a ref: " + name + ": " + content);
+			}
+			final String target = RawParseUtils.decode(buf, 5, n);
+			return newSymbolicRef(modified, name, target);
+		}
+
+		if (n < OBJECT_ID_STRING_LENGTH)
+			return null; // impossibly short object identifier; not a reference.
+
+		final ObjectId id;
+		try {
+			id = ObjectId.fromString(buf, 0);
+		} catch (IllegalArgumentException notRef) {
+			while (0 < n && Character.isWhitespace(buf[n - 1]))
+				n--;
+			String content = RawParseUtils.decode(buf, 0, n);
+			throw new IOException("Not a ref: " + name + ": " + content);
+		}
+		return new LooseUnpeeled(modified, name, id);
+	}
+
+	private static boolean isSymRef(final byte[] buf, int n) {
+		if (n < 6)
+			return false;
+		return /**/buf[0] == 'r' //
+				&& buf[1] == 'e' //
+				&& buf[2] == 'f' //
+				&& buf[3] == ':' //
+				&& buf[4] == ' ';
+	}
+
+	/** If the parent should fire listeners, fires them. */
+	private void fireRefsChanged() {
+		final int last = lastNotifiedModCnt.get();
+		final int curr = modCnt.get();
+		if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr))
+			parent.fireRefsChanged();
+	}
+
+	/**
+	 * Create a reference update to write a temporary reference.
+	 *
+	 * @return an update for a new temporary reference.
+	 * @throws IOException
+	 *             a temporary name cannot be allocated.
+	 */
+	RefDirectoryUpdate newTemporaryUpdate() throws IOException {
+		File tmp = File.createTempFile("renamed_", "_ref", refsDir);
+		String name = Constants.R_REFS + tmp.getName();
+		Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null);
+		return new RefDirectoryUpdate(this, ref);
+	}
+
+	/**
+	 * Locate the file on disk for a single reference name.
+	 *
+	 * @param name
+	 *            name of the ref, relative to the Git repository top level
+	 *            directory (so typically starts with refs/).
+	 * @return the loose file location.
+	 */
+	File fileFor(String name) {
+		if (name.startsWith(R_REFS)) {
+			name = name.substring(R_REFS.length());
+			return new File(refsDir, name);
+		}
+		return new File(gitDir, name);
+	}
+
+	/**
+	 * Locate the log file on disk for a single reference name.
+	 *
+	 * @param name
+	 *            name of the ref, relative to the Git repository top level
+	 *            directory (so typically starts with refs/).
+	 * @return the log file location.
+	 */
+	File logFor(String name) {
+		if (name.startsWith(R_REFS)) {
+			name = name.substring(R_REFS.length());
+			return new File(logsRefsDir, name);
+		}
+		return new File(logsDir, name);
+	}
+
+	static int levelsIn(final String name) {
+		int count = 0;
+		for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1))
+			count++;
+		return count;
+	}
+
+	static void delete(final File file, final int depth) throws IOException {
+		if (!file.delete() && file.isFile())
+			throw new IOException("File cannot be deleted: " + file);
+
+		File dir = file.getParentFile();
+		for (int i = 0; i < depth; ++i) {
+			if (!dir.delete())
+				break; // ignore problem here
+			dir = dir.getParentFile();
+		}
+	}
+
+	private static class PackedRefList extends RefList<Ref> {
+		static final PackedRefList NO_PACKED_REFS = new PackedRefList(RefList
+				.emptyList(), 0, 0);
+
+		/** Last length of the packed-refs file when we read it. */
+		final long lastSize;
+
+		/** Last modified time of the packed-refs file when we read it. */
+		final long lastModified;
+
+		PackedRefList(RefList<Ref> src, long size, long mtime) {
+			super(src);
+			lastSize = size;
+			lastModified = mtime;
+		}
+	}
+
+	private static LooseSymbolicRef newSymbolicRef(long lastModified,
+			String name, String target) {
+		Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
+		return new LooseSymbolicRef(lastModified, name, dst);
+	}
+
+	private static interface LooseRef extends Ref {
+		long getLastModified();
+
+		LooseRef peel(ObjectIdRef newLeaf);
+	}
+
+	private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag
+			implements LooseRef {
+		private final long lastModified;
+
+		LoosePeeledTag(long mtime, String refName, ObjectId id, ObjectId p) {
+			super(LOOSE, refName, id, p);
+			this.lastModified = mtime;
+		}
+
+		public long getLastModified() {
+			return lastModified;
+		}
+
+		public LooseRef peel(ObjectIdRef newLeaf) {
+			return this;
+		}
+	}
+
+	private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag
+			implements LooseRef {
+		private final long lastModified;
+
+		LooseNonTag(long mtime, String refName, ObjectId id) {
+			super(LOOSE, refName, id);
+			this.lastModified = mtime;
+		}
+
+		public long getLastModified() {
+			return lastModified;
+		}
+
+		public LooseRef peel(ObjectIdRef newLeaf) {
+			return this;
+		}
+	}
+
+	private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled
+			implements LooseRef {
+		private final long lastModified;
+
+		LooseUnpeeled(long mtime, String refName, ObjectId id) {
+			super(LOOSE, refName, id);
+			this.lastModified = mtime;
+		}
+
+		public long getLastModified() {
+			return lastModified;
+		}
+
+		public LooseRef peel(ObjectIdRef newLeaf) {
+			if (newLeaf.getPeeledObjectId() != null)
+				return new LoosePeeledTag(lastModified, getName(),
+						getObjectId(), newLeaf.getPeeledObjectId());
+			else
+				return new LooseNonTag(lastModified, getName(), getObjectId());
+		}
+	}
+
+	private final static class LooseSymbolicRef extends SymbolicRef implements
+			LooseRef {
+		private final long lastModified;
+
+		LooseSymbolicRef(long mtime, String refName, Ref target) {
+			super(refName, target);
+			this.lastModified = mtime;
+		}
+
+		public long getLastModified() {
+			return lastModified;
+		}
+
+		public LooseRef peel(ObjectIdRef newLeaf) {
+			// We should never try to peel the symbolic references.
+			throw new UnsupportedOperationException();
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java
new file mode 100644
index 0000000000000000000000000000000000000000..e43d2a561727395b2e711da9413f823ae78674d6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * Copyright (C) 2009, Robin Rosenberg
+ * 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;
+
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/**
+ * Rename any reference stored by {@link RefDirectory}.
+ * <p>
+ * This class works by first renaming the source reference to a temporary name,
+ * then renaming the temporary name to the final destination reference.
+ * <p>
+ * This strategy permits switching a reference like {@code refs/heads/foo},
+ * which is a file, to {@code refs/heads/foo/bar}, which is stored inside a
+ * directory that happens to match the source name.
+ */
+class RefDirectoryRename extends RefRename {
+	private final RefDirectory refdb;
+
+	/**
+	 * The value of the source reference at the start of the rename.
+	 * <p>
+	 * At the end of the rename the destination reference must have this same
+	 * value, otherwise we have a concurrent update and the rename must fail
+	 * without making any changes.
+	 */
+	private ObjectId objId;
+
+	/** True if HEAD must be moved to the destination reference. */
+	private boolean updateHEAD;
+
+	/** A reference we backup {@link #objId} into during the rename. */
+	private RefDirectoryUpdate tmp;
+
+	RefDirectoryRename(RefDirectoryUpdate src, RefDirectoryUpdate dst) {
+		super(src, dst);
+		refdb = src.getRefDatabase();
+	}
+
+	@Override
+	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();
+		try {
+			// First backup the source so its never unreachable.
+			tmp.setNewObjectId(objId);
+			tmp.setForceUpdate(true);
+			tmp.disableRefLog();
+			switch (tmp.update(rw)) {
+			case NEW:
+			case FORCED:
+			case NO_CHANGE:
+				break;
+			default:
+				return tmp.getResult();
+			}
+
+			// Save the source's log under the temporary name, we must do
+			// this before we delete the source, otherwise we lose the log.
+			if (!renameLog(source, tmp))
+				return Result.IO_FAILURE;
+
+			// If HEAD has to be updated, link it now to destination.
+			// We have to link before we delete, otherwise the delete
+			// fails because its the current branch.
+			RefUpdate dst = destination;
+			if (updateHEAD) {
+				if (!linkHEAD(destination)) {
+					renameLog(tmp, source);
+					return Result.LOCK_FAILURE;
+				}
+
+				// Replace the update operation so HEAD will log the rename.
+				dst = refdb.newUpdate(Constants.HEAD, false);
+				dst.setRefLogIdent(destination.getRefLogIdent());
+				dst.setRefLogMessage(destination.getRefLogMessage(), false);
+			}
+
+			// Delete the source name so its path is free for replacement.
+			source.setExpectedOldObjectId(objId);
+			source.setForceUpdate(true);
+			source.disableRefLog();
+			if (source.delete(rw) != Result.FORCED) {
+				renameLog(tmp, source);
+				if (updateHEAD)
+					linkHEAD(source);
+				return source.getResult();
+			}
+
+			// Move the log to the destination.
+			if (!renameLog(tmp, destination)) {
+				renameLog(tmp, source);
+				source.setExpectedOldObjectId(ObjectId.zeroId());
+				source.setNewObjectId(objId);
+				source.update(rw);
+				if (updateHEAD)
+					linkHEAD(source);
+				return Result.IO_FAILURE;
+			}
+
+			// Create the destination, logging the rename during the creation.
+			dst.setExpectedOldObjectId(ObjectId.zeroId());
+			dst.setNewObjectId(objId);
+			if (dst.update(rw) != Result.NEW) {
+				// If we didn't create the destination we have to undo
+				// our work. Put the log back and restore source.
+				if (renameLog(destination, tmp))
+					renameLog(tmp, source);
+				source.setExpectedOldObjectId(ObjectId.zeroId());
+				source.setNewObjectId(objId);
+				source.update(rw);
+				if (updateHEAD)
+					linkHEAD(source);
+				return dst.getResult();
+			}
+
+			return Result.RENAMED;
+		} finally {
+			// Always try to free the temporary name.
+			try {
+				refdb.delete(tmp);
+			} catch (IOException err) {
+				refdb.fileFor(tmp.getName()).delete();
+			}
+		}
+	}
+
+	private boolean renameLog(RefUpdate src, RefUpdate dst) {
+		File srcLog = refdb.logFor(src.getName());
+		File dstLog = refdb.logFor(dst.getName());
+
+		if (!srcLog.exists())
+			return true;
+
+		if (!rename(srcLog, dstLog))
+			return false;
+
+		try {
+			final int levels = RefDirectory.levelsIn(src.getName()) - 2;
+			RefDirectory.delete(srcLog, levels);
+			return true;
+		} catch (IOException e) {
+			rename(dstLog, srcLog);
+			return false;
+		}
+	}
+
+	private static boolean rename(File src, File dst) {
+		if (src.renameTo(dst))
+			return true;
+
+		File dir = dst.getParentFile();
+		if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory())
+			return false;
+		return src.renameTo(dst);
+	}
+
+	private boolean linkHEAD(RefUpdate target) {
+		try {
+			refdb.link(Constants.HEAD, target.getName());
+			return true;
+		} catch (IOException e) {
+			return false;
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java
new file mode 100644
index 0000000000000000000000000000000000000000..113b74b44efe58bab980515abde06f267bd4e500
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * Copyright (C) 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;
+
+/** Updates any reference stored by {@link RefDirectory}. */
+class RefDirectoryUpdate extends RefUpdate {
+	private final RefDirectory database;
+
+	private LockFile lock;
+
+	RefDirectoryUpdate(final RefDirectory r, final Ref ref) {
+		super(ref);
+		database = r;
+	}
+
+	@Override
+	protected RefDirectory getRefDatabase() {
+		return database;
+	}
+
+	@Override
+	protected Repository getRepository() {
+		return database.getRepository();
+	}
+
+	@Override
+	protected boolean tryLock() throws IOException {
+		Ref dst = getRef().getLeaf();
+		String name = dst.getName();
+		lock = new LockFile(database.fileFor(name));
+		if (lock.lock()) {
+			dst = database.getRef(name);
+			setOldObjectId(dst != null ? dst.getObjectId() : null);
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	@Override
+	protected void unlock() {
+		if (lock != null) {
+			lock.unlock();
+			lock = null;
+		}
+	}
+
+	@Override
+	protected Result doUpdate(final Result status) throws IOException {
+		lock.setNeedStatInformation(true);
+		lock.write(getNewObjectId());
+
+		String msg = getRefLogMessage();
+		if (msg != null) {
+			if (isRefLogIncludingResult()) {
+				String strResult = toResultString(status);
+				if (strResult != null) {
+					if (msg.length() > 0)
+						msg = msg + ": " + strResult;
+					else
+						msg = strResult;
+				}
+			}
+			database.log(this, msg);
+		}
+		if (!lock.commit())
+			return Result.LOCK_FAILURE;
+		database.stored(this, lock.getCommitLastModified());
+		return status;
+	}
+
+	private String toResultString(final Result status) {
+		switch (status) {
+		case FORCED:
+			return "forced-update";
+		case FAST_FORWARD:
+			return "fast forward";
+		case NEW:
+			return "created";
+		default:
+			return null;
+		}
+	}
+
+	@Override
+	protected Result doDelete(final Result status) throws IOException {
+		if (getRef().getLeaf().getStorage() != Ref.Storage.NEW)
+			database.delete(this);
+		return status;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java
deleted file mode 100644
index 304968557341ec64eb13a2aa7ff32831ad15b6f1..0000000000000000000000000000000000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
- * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
- * Copyright (C) 2009, Google Inc.
- * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006, 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.FileOutputStream;
-import java.io.IOException;
-
-/**
- * Utility class to work with reflog files
- *
- * @author Dave Watson
- */
-public class RefLogWriter {
-	static void append(final RefUpdate u, final String msg) throws IOException {
-		final ObjectId oldId = u.getOldObjectId();
-		final ObjectId newId = u.getNewObjectId();
-		final Repository db = u.getRepository();
-		final PersonIdent ident = u.getRefLogIdent();
-
-		appendOneRecord(oldId, newId, ident, msg, db, u.getName());
-		if (!u.getName().equals(u.getOrigName()))
-			appendOneRecord(oldId, newId, ident, msg, db, u.getOrigName());
-	}
-
-	static void append(RefRename refRename, String logName, String msg) throws IOException {
-		final ObjectId id = refRename.getObjectId();
-		final Repository db = refRename.getRepository();
-		final PersonIdent ident = refRename.getRefLogIdent();
-		appendOneRecord(id, id, ident, msg, db, logName);
-	}
-
-	static void renameTo(final Repository db, final RefUpdate from,
-			final RefUpdate to) throws IOException {
-		final File logdir = new File(db.getDirectory(), Constants.LOGS);
-		final File reflogFrom = new File(logdir, from.getName());
-		if (!reflogFrom.exists())
-			return;
-		final File reflogTo = new File(logdir, to.getName());
-		final File reflogToDir = reflogTo.getParentFile();
-		File tmp = new File(logdir, "tmp-renamed-log.." + Thread.currentThread().getId());
-		if (!reflogFrom.renameTo(tmp)) {
-			throw new IOException("Cannot rename " + reflogFrom + " to (" + tmp
-					+ ")" + reflogTo);
-		}
-		RefUpdate.deleteEmptyDir(reflogFrom, RefUpdate.count(from.getName(),
-				'/'));
-		if (!reflogToDir.exists() && !reflogToDir.mkdirs()) {
-			throw new IOException("Cannot create directory " + reflogToDir);
-		}
-		if (!tmp.renameTo(reflogTo)) {
-			throw new IOException("Cannot rename (" + tmp + ")" + reflogFrom
-					+ " to " + reflogTo);
-		}
-	}
-
-	private static void appendOneRecord(final ObjectId oldId,
-			final ObjectId newId, PersonIdent ident, final String msg,
-			final Repository db, final String refName) throws IOException {
-		if (ident == null)
-			ident = new PersonIdent(db);
-		else
-			ident = new PersonIdent(ident);
-
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(oldId));
-		r.append(' ');
-		r.append(ObjectId.toString(newId));
-		r.append(' ');
-		r.append(ident.toExternalString());
-		r.append('\t');
-		r.append(msg);
-		r.append('\n');
-
-		final byte[] rec = Constants.encode(r.toString());
-		final File logdir = new File(db.getDirectory(), Constants.LOGS);
-		final File reflog = new File(logdir, refName);
-		if (reflog.exists() || db.getConfig().getCore().isLogAllRefUpdates()) {
-			final File refdir = reflog.getParentFile();
-
-			if (!refdir.exists() && !refdir.mkdirs())
-				throw new IOException("Cannot create directory " + refdir);
-
-			final FileOutputStream out = new FileOutputStream(reflog, true);
-			try {
-				out.write(rec);
-			} finally {
-				out.close();
-			}
-		}
-	}
-
-	/**
-	 * Writes reflog entry for ref specified by refName
-	 *
-	 * @param repo
-	 *            repository to use
-	 * @param oldCommit
-	 *            previous commit
-	 * @param commit
-	 *            new commit
-	 * @param message
-	 *            reflog message
-	 * @param refName
-	 *            full ref name
-	 * @throws IOException
-	 * @deprecated rely upon {@link RefUpdate}'s automatic logging instead.
-	 */
-	public static void writeReflog(Repository repo, ObjectId oldCommit,
-			ObjectId commit, String message, String refName) throws IOException {
-		appendOneRecord(oldCommit, commit, null, message, repo, refName);
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
index ba7f208f34e810c4f9975a39e78912288af41a27..2ff5c614b5995bf76e08d9d9d0eec0a867f53c03 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
@@ -1,6 +1,8 @@
 /*
+ * Copyright (C) 2009-2010, Google Inc.
  * Copyright (C) 2009, Robin Rosenberg
  * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 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
@@ -49,25 +51,96 @@
 import org.eclipse.jgit.lib.RefUpdate.Result;
 
 /**
- * A RefUpdate combination for renaming a ref
+ * A RefUpdate combination for renaming a reference.
+ * <p>
+ * If the source reference is currently pointed to by {@code HEAD}, then the
+ * HEAD symbolic reference is updated to point to the new destination.
  */
-public class RefRename {
-	private RefUpdate newToUpdate;
+public abstract class RefRename {
+	/** Update operation to read and delete the source reference. */
+	protected final RefUpdate source;
 
-	private RefUpdate oldFromDelete;
+	/** Update operation to create/overwrite the destination reference. */
+	protected final RefUpdate destination;
 
-	private Result renameResult = Result.NOT_ATTEMPTED;
+	private Result result = Result.NOT_ATTEMPTED;
 
-	RefRename(final RefUpdate toUpdate, final RefUpdate fromUpdate) {
-		newToUpdate = toUpdate;
-		oldFromDelete = fromUpdate;
+	/**
+	 * Initialize a new rename operation.
+	 *
+	 * @param src
+	 *            operation to read and delete the source.
+	 * @param dst
+	 *            operation to create (or overwrite) the destination.
+	 */
+	protected RefRename(final RefUpdate src, final RefUpdate dst) {
+		source = src;
+		destination = dst;
+
+		Repository repo = destination.getRepository();
+		String cmd = "";
+		if (source.getName().startsWith(Constants.R_HEADS)
+				&& destination.getName().startsWith(Constants.R_HEADS))
+			cmd = "Branch: ";
+		setRefLogMessage(cmd + "renamed "
+				+ repo.shortenRefName(source.getName()) + " to "
+				+ repo.shortenRefName(destination.getName()));
+	}
+
+	/** @return identity of the user making the change in the reflog. */
+	public PersonIdent getRefLogIdent() {
+		return destination.getRefLogIdent();
+	}
+
+	/**
+	 * Set the identity of the user appearing in the reflog.
+	 * <p>
+	 * The timestamp portion of the identity is ignored. A new identity with the
+	 * current timestamp will be created automatically when the rename occurs
+	 * and the log record is written.
+	 *
+	 * @param pi
+	 *            identity of the user. If null the identity will be
+	 *            automatically determined based on the repository
+	 *            configuration.
+	 */
+	public void setRefLogIdent(final PersonIdent pi) {
+		destination.setRefLogIdent(pi);
+	}
+
+	/**
+	 * Get the message to include in the reflog.
+	 *
+	 * @return message the caller wants to include in the reflog; null if the
+	 *         rename should not be logged.
+	 */
+	public String getRefLogMessage() {
+		return destination.getRefLogMessage();
+	}
+
+	/**
+	 * Set the message to include in the reflog.
+	 *
+	 * @param msg
+	 *            the message to describe this change.
+	 */
+	public void setRefLogMessage(final String msg) {
+		if (msg == null)
+			disableRefLog();
+		else
+			destination.setRefLogMessage(msg, false);
+	}
+
+	/** Don't record this rename in the ref's associated reflog. */
+	public void disableRefLog() {
+		destination.setRefLogMessage("", false);
 	}
 
 	/**
 	 * @return result of rename operation
 	 */
 	public Result getResult() {
-		return renameResult;
+		return result;
 	}
 
 	/**
@@ -75,101 +148,33 @@ public Result getResult() {
 	 * @throws IOException
 	 */
 	public Result rename() throws IOException {
-		Ref oldRef = oldFromDelete.db.readRef(Constants.HEAD);
-		boolean renameHEADtoo = oldRef != null
-				&& oldRef.getName().equals(oldFromDelete.getName());
-		Repository db = oldFromDelete.getRepository();
 		try {
-			RefLogWriter.renameTo(db, oldFromDelete,
-					newToUpdate);
-			newToUpdate.setRefLogMessage(null, false);
-			String tmpRefName = "RENAMED-REF.." + Thread.currentThread().getId();
-			RefUpdate tmpUpdateRef = db.updateRef(tmpRefName);
-			if (renameHEADtoo) {
-				try {
-					oldFromDelete.db.link(Constants.HEAD, tmpRefName);
-				} catch (IOException e) {
-					RefLogWriter.renameTo(db,
-							newToUpdate, oldFromDelete);
-					return renameResult = Result.LOCK_FAILURE;
-				}
-			}
-			tmpUpdateRef.setNewObjectId(oldFromDelete.getOldObjectId());
-			tmpUpdateRef.setForceUpdate(true);
-			Result update = tmpUpdateRef.update();
-			if (update != Result.FORCED && update != Result.NEW && update != Result.NO_CHANGE) {
-				RefLogWriter.renameTo(db,
-						newToUpdate, oldFromDelete);
-				if (renameHEADtoo) {
-					oldFromDelete.db.link(Constants.HEAD, oldFromDelete.getName());
-				}
-				return renameResult = update;
-			}
-
-			oldFromDelete.setExpectedOldObjectId(oldFromDelete.getOldObjectId());
-			oldFromDelete.setForceUpdate(true);
-			Result delete = oldFromDelete.delete();
-			if (delete != Result.FORCED) {
-				if (db.getRef(
-						oldFromDelete.getName()) != null) {
-					RefLogWriter.renameTo(db,
-							newToUpdate, oldFromDelete);
-					if (renameHEADtoo) {
-						oldFromDelete.db.link(Constants.HEAD, oldFromDelete
-								.getName());
-					}
-				}
-				return renameResult = delete;
-			}
-
-			newToUpdate.setNewObjectId(tmpUpdateRef.getNewObjectId());
-			Result updateResult = newToUpdate.update();
-			if (updateResult != Result.NEW) {
-				RefLogWriter.renameTo(db, newToUpdate, oldFromDelete);
-				if (renameHEADtoo) {
-					oldFromDelete.db.link(Constants.HEAD, oldFromDelete.getName());
-				}
-				oldFromDelete.setExpectedOldObjectId(null);
-				oldFromDelete.setNewObjectId(oldFromDelete.getOldObjectId());
-				oldFromDelete.setForceUpdate(true);
-				oldFromDelete.setRefLogMessage(null, false);
-				Result undelete = oldFromDelete.update();
-				if (undelete != Result.NEW && undelete != Result.LOCK_FAILURE)
-					return renameResult = Result.IO_FAILURE;
-				return renameResult = Result.LOCK_FAILURE;
-			}
-
-			if (renameHEADtoo) {
-				oldFromDelete.db.link(Constants.HEAD, newToUpdate.getName());
-			} else {
-				db.fireRefsMaybeChanged();
-			}
-			RefLogWriter.append(this, newToUpdate.getName(), "Branch: renamed "
-					+ db.shortenRefName(oldFromDelete.getName()) + " to "
-					+ db.shortenRefName(newToUpdate.getName()));
-			if (renameHEADtoo)
-				RefLogWriter.append(this, Constants.HEAD, "Branch: renamed "
-						+ db.shortenRefName(oldFromDelete.getName()) + " to "
-						+ db.shortenRefName(newToUpdate.getName()));
-			return renameResult = Result.RENAMED;
-		} catch (RuntimeException e) {
-			throw e;
+			result = doRename();
+			return result;
+		} catch (IOException err) {
+			result = Result.IO_FAILURE;
+			throw err;
 		}
 	}
 
-	ObjectId getObjectId() {
-		return oldFromDelete.getOldObjectId();
-	}
-
-	Repository getRepository() {
-		return oldFromDelete.getRepository();
-	}
-
-	PersonIdent getRefLogIdent() {
-		return newToUpdate.getRefLogIdent();
-	}
+	/**
+	 * @return the result of the rename operation.
+	 * @throws IOException
+	 */
+	protected abstract Result doRename() throws IOException;
 
-	String getToName() {
-		return newToUpdate.getName();
+	/**
+	 * @return true if the {@code Constants#HEAD} reference needs to be linked
+	 *         to the new destination name.
+	 * @throws IOException
+	 *             the current value of {@code HEAD} cannot be read.
+	 */
+	protected boolean needToUpdateHEAD() throws IOException {
+		Ref head = source.getRefDatabase().getRef(Constants.HEAD);
+		if (head.isSymbolic()) {
+			head = head.getTarget();
+			return head.getName().equals(source.getName());
+		}
+		return false;
 	}
 }
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 856eb15198ead1f3b999fdbc8fab90ec2078f7b1..33e12a110df740fe08afd6844420ab25fb2b84aa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008-2010, Google Inc.
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  * and other copyright owners as documented in the project's IP log.
  *
@@ -45,19 +44,17 @@
 
 package org.eclipse.jgit.lib;
 
-import java.io.File;
 import java.io.IOException;
 
 import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.Ref.Storage;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
 
 /**
- * Updates any locally stored ref.
+ * Creates, updates or deletes any reference.
  */
-public class RefUpdate {
+public abstract class RefUpdate {
 	/** Status of an update request. */
 	public static enum Result {
 		/** The ref update/delete has not been attempted by the caller. */
@@ -142,12 +139,6 @@ public static enum Result {
 		RENAMED
 	}
 
-	/** Repository the ref is stored in. */
-	final RefDatabase db;
-
-	/** Location of the loose file holding the value of this ref. */
-	final File looseFile;
-
 	/** New value the caller wants this ref to have. */
 	private ObjectId newValue;
 
@@ -170,22 +161,52 @@ public static enum Result {
 	private ObjectId expValue;
 
 	/** Result of the update operation. */
-	Result result = Result.NOT_ATTEMPTED;
+	private Result result = Result.NOT_ATTEMPTED;
 
 	private final Ref ref;
 
-	RefUpdate(final RefDatabase r, final Ref ref, final File f) {
-		db = r;
+	RefUpdate(final Ref ref) {
 		this.ref = ref;
 		oldValue = ref.getObjectId();
-		looseFile = f;
 		refLogMessage = "";
 	}
 
-	/** @return the repository the updated ref resides in */
-	public Repository getRepository() {
-		return db.getRepository();
-	}
+	/** @return the reference database this update modifies. */
+	protected abstract RefDatabase getRefDatabase();
+
+	/** @return the repository storing the database's objects. */
+	protected abstract Repository getRepository();
+
+	/**
+	 * Try to acquire the lock on the reference.
+	 * <p>
+	 * If the locking was successful the implementor must set the current
+	 * identity value by calling {@link #setOldObjectId(ObjectId)}.
+	 *
+	 * @return true if the lock was acquired and the reference is likely
+	 *         protected from concurrent modification; false if it failed.
+	 * @throws IOException
+	 *             the lock couldn't be taken due to an unexpected storage
+	 *             failure, and not because of a concurrent update.
+	 */
+	protected abstract boolean tryLock() throws IOException;
+
+	/** Releases the lock taken by {@link #tryLock} if it succeeded. */
+	protected abstract void unlock();
+
+	/**
+	 * @param desiredResult
+	 * @return {@code result}
+	 * @throws IOException
+	 */
+	protected abstract Result doUpdate(Result desiredResult) throws IOException;
+
+	/**
+	 * @param desiredResult
+	 * @return {@code result}
+	 * @throws IOException
+	 */
+	protected abstract Result doDelete(Result desiredResult) throws IOException;
 
 	/**
 	 * Get the name of the ref this update will operate on.
@@ -193,16 +214,12 @@ public Repository getRepository() {
 	 * @return name of underlying ref.
 	 */
 	public String getName() {
-		return ref.getName();
+		return getRef().getName();
 	}
 
-	/**
-	 * Get the requested name of the ref thit update will operate on
-	 *
-	 * @return original (requested) name of the underlying ref.
-	 */
-	public String getOrigName() {
-		return ref.getOrigName();
+	/** @return the reference this update will create or modify. */
+	public Ref getRef() {
+		return ref;
 	}
 
 	/**
@@ -295,12 +312,17 @@ public String getRefLogMessage() {
 		return refLogMessage;
 	}
 
+	/** @return {@code true} if the ref log message should show the result. */
+	protected boolean isRefLogIncludingResult() {
+		return refLogIncludeResult;
+	}
+
 	/**
 	 * Set the message to include in the reflog.
 	 *
 	 * @param msg
-	 *            the message to describe this change. It may be null
-	 *            if appendStatus is null in order not to append to the reflog
+	 *            the message to describe this change. It may be null if
+	 *            appendStatus is null in order not to append to the reflog
 	 * @param appendStatus
 	 *            true if the status of the ref change (fast-forward or
 	 *            forced-update) should be appended to the user supplied
@@ -339,6 +361,16 @@ public ObjectId getOldObjectId() {
 		return oldValue;
 	}
 
+	/**
+	 * Set the old value of the ref.
+	 *
+	 * @param old
+	 *            the old value.
+	 */
+	protected void setOldObjectId(ObjectId old) {
+		oldValue = old;
+	}
+
 	/**
 	 * Get the status of this update.
 	 * <p>
@@ -378,7 +410,7 @@ public Result forceUpdate() throws IOException {
 	 * This is the same as:
 	 *
 	 * <pre>
-	 * return update(new RevWalk(repository));
+	 * return update(new RevWalk(getRepository()));
 	 * </pre>
 	 *
 	 * @return the result status of the update.
@@ -386,7 +418,7 @@ public Result forceUpdate() throws IOException {
 	 *             an unexpected IO error occurred while writing changes.
 	 */
 	public Result update() throws IOException {
-		return update(new RevWalk(db.getRepository()));
+		return update(new RevWalk(getRepository()));
 	}
 
 	/**
@@ -404,7 +436,14 @@ public Result update() throws IOException {
 	public Result update(final RevWalk walk) throws IOException {
 		requireCanDoUpdate();
 		try {
-			return result = updateImpl(walk, new UpdateStore());
+			return result = updateImpl(walk, new Store() {
+				@Override
+				Result execute(Result status) throws IOException {
+					if (status == Result.NO_CHANGE)
+						return status;
+					return doUpdate(status);
+				}
+			});
 		} catch (IOException x) {
 			result = Result.IO_FAILURE;
 			throw x;
@@ -417,14 +456,14 @@ public Result update(final RevWalk walk) throws IOException {
 	 * This is the same as:
 	 *
 	 * <pre>
-	 * return delete(new RevWalk(repository));
+	 * return delete(new RevWalk(getRepository()));
 	 * </pre>
 	 *
 	 * @return the result status of the delete.
 	 * @throws IOException
 	 */
 	public Result delete() throws IOException {
-		return delete(new RevWalk(db.getRepository()));
+		return delete(new RevWalk(getRepository()));
 	}
 
 	/**
@@ -437,14 +476,23 @@ public Result delete() throws IOException {
 	 * @throws IOException
 	 */
 	public Result delete(final RevWalk walk) throws IOException {
-		if (getName().startsWith(Constants.R_HEADS)) {
-			final Ref head = db.readRef(Constants.HEAD);
-			if (head != null && getName().equals(head.getName()))
-				return result = Result.REJECTED_CURRENT_BRANCH;
+		final String myName = getRef().getLeaf().getName();
+		if (myName.startsWith(Constants.R_HEADS)) {
+			Ref head = getRefDatabase().getRef(Constants.HEAD);
+			while (head.isSymbolic()) {
+				head = head.getTarget();
+				if (myName.equals(head.getName()))
+					return result = Result.REJECTED_CURRENT_BRANCH;
+			}
 		}
 
 		try {
-			return result = updateImpl(walk, new DeleteStore());
+			return result = updateImpl(walk, new Store() {
+				@Override
+				Result execute(Result status) throws IOException {
+					return doDelete(status);
+				}
+			});
 		} catch (IOException x) {
 			result = Result.IO_FAILURE;
 			throw x;
@@ -453,17 +501,14 @@ public Result delete(final RevWalk walk) throws IOException {
 
 	private Result updateImpl(final RevWalk walk, final Store store)
 			throws IOException {
-		final LockFile lock;
 		RevObject newObj;
 		RevObject oldObj;
 
-		if (isNameConflicting())
-			return Result.LOCK_FAILURE;
-		lock = new LockFile(looseFile);
-		if (!lock.lock())
+		if (getRefDatabase().isNameConflicting(getName()))
 			return Result.LOCK_FAILURE;
 		try {
-			oldValue = db.idOf(getName());
+			if (!tryLock())
+				return Result.LOCK_FAILURE;
 			if (expValue != null) {
 				final ObjectId o;
 				o = oldValue != null ? oldValue : ObjectId.zeroId();
@@ -471,39 +516,24 @@ private Result updateImpl(final RevWalk walk, final Store store)
 					return Result.LOCK_FAILURE;
 			}
 			if (oldValue == null)
-				return store.store(lock, Result.NEW);
+				return store.execute(Result.NEW);
 
 			newObj = safeParse(walk, newValue);
 			oldObj = safeParse(walk, oldValue);
 			if (newObj == oldObj)
-				return store.store(lock, Result.NO_CHANGE);
+				return store.execute(Result.NO_CHANGE);
 
 			if (newObj instanceof RevCommit && oldObj instanceof RevCommit) {
 				if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj))
-					return store.store(lock, Result.FAST_FORWARD);
+					return store.execute(Result.FAST_FORWARD);
 			}
 
 			if (isForceUpdate())
-				return store.store(lock, Result.FORCED);
+				return store.execute(Result.FORCED);
 			return Result.REJECTED;
 		} finally {
-			lock.unlock();
-		}
-	}
-
-	private boolean isNameConflicting() throws IOException {
-		final String myName = getName();
-		final int lastSlash = myName.lastIndexOf('/');
-		if (lastSlash > 0)
-			if (db.getRepository().getRef(myName.substring(0, lastSlash)) != null)
-				return true;
-
-		final String rName = myName + "/";
-		for (Ref r : db.getAllRefs().values()) {
-			if (r.getName().startsWith(rName))
-				return true;
+			unlock();
 		}
-		return false;
 	}
 
 	private static RevObject safeParse(final RevWalk rw, final AnyObjectId id)
@@ -520,123 +550,11 @@ private static RevObject safeParse(final RevWalk rw, final AnyObjectId id)
 		}
 	}
 
-	private Result updateStore(final LockFile lock, final Result status)
-			throws IOException {
-		if (status == Result.NO_CHANGE)
-			return status;
-		lock.setNeedStatInformation(true);
-		lock.write(newValue);
-		String msg = getRefLogMessage();
-		if (msg != null) {
-			if (refLogIncludeResult) {
-				String strResult = toResultString(status);
-				if (strResult != null) {
-					if (msg.length() > 0)
-						msg = msg + ": " + strResult;
-					else
-						msg = strResult;
-				}
-			}
-			RefLogWriter.append(this, msg);
-		}
-		if (!lock.commit())
-			return Result.LOCK_FAILURE;
-		db.stored(this.ref.getOrigName(),  ref.getName(), newValue, lock.getCommitLastModified());
-		return status;
-	}
-
-	private static String toResultString(final Result status) {
-		switch (status) {
-		case FORCED:
-			return "forced-update";
-		case FAST_FORWARD:
-			return "fast forward";
-		case NEW:
-			return "created";
-		default:
-			return null;
-		}
-	}
-
 	/**
 	 * Handle the abstraction of storing a ref update. This is because both
 	 * updating and deleting of a ref have merge testing in common.
 	 */
 	private abstract class Store {
-		abstract Result store(final LockFile lock, final Result status)
-				throws IOException;
-	}
-
-	class UpdateStore extends Store {
-
-		@Override
-		Result store(final LockFile lock, final Result status)
-				throws IOException {
-			return updateStore(lock, status);
-		}
-	}
-
-	class DeleteStore extends Store {
-
-		@Override
-		Result store(LockFile lock, Result status) throws IOException {
-			Storage storage = ref.getStorage();
-			if (storage == Storage.NEW)
-				return status;
-			if (storage.isPacked())
-				db.removePackedRef(ref.getName());
-
-			final int levels = count(ref.getName(), '/') - 2;
-
-			// Delete logs _before_ unlocking
-			final File gitDir = db.getRepository().getDirectory();
-			final File logDir = new File(gitDir, Constants.LOGS);
-			deleteFileAndEmptyDir(new File(logDir, ref.getName()), levels);
-
-			// We have to unlock before (maybe) deleting the parent directories
-			lock.unlock();
-			if (storage.isLoose())
-				deleteFileAndEmptyDir(looseFile, levels);
-			db.uncacheRef(ref.getName());
-			return status;
-		}
-
-		private void deleteFileAndEmptyDir(final File file, final int depth)
-				throws IOException {
-			if (file.isFile()) {
-				if (!file.delete())
-					throw new IOException("File cannot be deleted: " + file);
-				File dir = file.getParentFile();
-				for  (int i = 0; i < depth; ++i) {
-					if (!dir.delete())
-						break; // ignore problem here
-					dir = dir.getParentFile();
-				}
-			}
-		}
-	}
-
-	UpdateStore newUpdateStore() {
-		return new UpdateStore();
-	}
-
-	DeleteStore newDeleteStore() {
-		return new DeleteStore();
-	}
-
-	static void deleteEmptyDir(File dir, int depth) {
-		for (; depth > 0 && dir != null; depth--) {
-			if (dir.exists() && !dir.delete())
-				break;
-			dir = dir.getParentFile();
-		}
-	}
-
-	static int count(final String s, final char c) {
-		int count = 0;
-		for (int p = s.indexOf(c); p >= 0; p = s.indexOf(c, p + 1)) {
-			count++;
-		}
-		return count;
+		abstract Result execute(Result status) throws IOException;
 	}
 }
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 c48449933b93ffb4dc95c632981a7dc42cda35f9..6b0c7b3a9822c0e5e60f3f89d1f744f583031d37 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
@@ -49,6 +49,10 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.util.Collection;
+import java.util.Map;
+
+import org.eclipse.jgit.util.RefList;
+import org.eclipse.jgit.util.RefMap;
 
 /**
  * Writes out refs to the {@link Constants#INFO_REFS} and
@@ -70,6 +74,22 @@ public RefWriter(Collection<Ref> refs) {
 		this.refs = RefComparator.sort(refs);
 	}
 
+	/**
+	 * @param refs
+	 *            the complete set of references. This should have been computed
+	 *            by applying updates to the advertised refs already discovered.
+	 */
+	public RefWriter(Map<String, Ref> refs) {
+		if (refs instanceof RefMap)
+			this.refs = refs.values();
+		else
+			this.refs = RefComparator.sort(refs.values());
+	}
+
+	RefWriter(RefList<Ref> list) {
+		this.refs = list.asList();
+	}
+
 	/**
 	 * Rebuild the {@link Constants#INFO_REFS}.
 	 * <p>
@@ -85,7 +105,7 @@ public void writeInfoRefs() throws IOException {
 		final StringWriter w = new StringWriter();
 		final char[] tmp = new char[Constants.OBJECT_ID_STRING_LENGTH];
 		for (final Ref r : refs) {
-			if (Constants.HEAD.equals(r.getOrigName())) {
+			if (Constants.HEAD.equals(r.getName())) {
 				// Historically HEAD has never been published through
 				// the INFO_REFS file. This is a mistake, but its the
 				// way things are.
@@ -121,19 +141,18 @@ public void writeInfoRefs() throws IOException {
 	 */
 	public void writePackedRefs() throws IOException {
 		boolean peeled = false;
-
 		for (final Ref r : refs) {
-			if (r.getStorage() != Ref.Storage.PACKED)
-				continue;
-			if (r.getPeeledObjectId() != null)
+			if (r.getStorage().isPacked() && r.isPeeled()) {
 				peeled = true;
+				break;
+			}
 		}
 
 		final StringWriter w = new StringWriter();
 		if (peeled) {
-			w.write("# pack-refs with:");
+			w.write(RefDirectory.PACKED_REFS_HEADER);
 			if (peeled)
-				w.write(" peeled");
+				w.write(RefDirectory.PACKED_REFS_PEELED);
 			w.write('\n');
 		}
 
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 bbc5cc2a6afd07a9ec26c16152e833b5f5bd51cc..ecae243a41dc1e4f26522383c3a2e5463a8a6577 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2008-2010, Google Inc.
  * Copyright (C) 2006-2009, 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.
@@ -45,10 +46,7 @@
 
 package org.eclipse.jgit.lib;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -223,7 +221,7 @@ public Repository(final File d, final File workTree, final File objectDir,
 			}
 		}
 
-		refs = new RefDatabase(this);
+		refs = new RefDirectory(this);
 		if (objectDir != null)
 			objectDatabase = new ObjectDirectory(FS.resolve(objectDir, ""),
 					alternateObjectDir);
@@ -310,6 +308,11 @@ public ObjectDatabase getObjectDatabase() {
 		return objectDatabase;
 	}
 
+	/** @return the reference database which stores the reference namespace. */
+	public RefDatabase getRefDatabase() {
+		return refs;
+	}
+
 	/**
 	 * @return the configuration of this repository
 	 */
@@ -591,7 +594,7 @@ public Tag mapTag(final String refName, final ObjectId id) throws IOException {
 	 *             to the base ref, as the symbolic ref could not be read.
 	 */
 	public RefUpdate updateRef(final String ref) throws IOException {
-		return refs.newUpdate(ref);
+		return updateRef(ref, false);
 	}
 
 	/**
@@ -862,7 +865,7 @@ else if (item.equals("")) {
 	private ObjectId resolveSimple(final String revstr) throws IOException {
 		if (ObjectId.isId(revstr))
 			return ObjectId.fromString(revstr);
-		final Ref r = refs.readRef(revstr);
+		final Ref r = refs.getRef(revstr);
 		return r != null ? r.getObjectId() : null;
 	}
 
@@ -875,8 +878,10 @@ public void incrementOpen() {
 	 * Close all resources used by this repository
 	 */
 	public void close() {
-		if (useCnt.decrementAndGet() == 0)
+		if (useCnt.decrementAndGet() == 0) {
 			objectDatabase.close();
+			refs.close();
+		}
 	}
 
 	/**
@@ -911,53 +916,48 @@ public String toString() {
 	}
 
 	/**
-	 * @return name of current branch
+	 * Get the name of the reference that {@code HEAD} points to.
+	 * <p>
+	 * This is essentially the same as doing:
+	 *
+	 * <pre>
+	 * return getRef(Constants.HEAD).getTarget().getName()
+	 * </pre>
+	 *
+	 * Except when HEAD is detached, in which case this method returns the
+	 * current ObjectId in hexadecimal string format.
+	 *
+	 * @return name of current branch (for example {@code refs/heads/master}) or
+	 *         an ObjectId in hex format if the current branch is detached.
 	 * @throws IOException
 	 */
 	public String getFullBranch() throws IOException {
-		final File ptr = new File(getDirectory(),Constants.HEAD);
-		final BufferedReader br = new BufferedReader(new FileReader(ptr));
-		String ref;
-		try {
-			ref = br.readLine();
-		} finally {
-			br.close();
-		}
-		if (ref.startsWith("ref: "))
-			ref = ref.substring(5);
-		return ref;
+		Ref head = getRef(Constants.HEAD);
+		if (head == null)
+			return null;
+		if (head.isSymbolic())
+			return head.getTarget().getName();
+		if (head.getObjectId() != null)
+			return head.getObjectId().name();
+		return null;
 	}
 
 	/**
-	 * @return name of current branch.
+	 * Get the short name of the current branch that {@code HEAD} points to.
+	 * <p>
+	 * This is essentially the same as {@link #getFullBranch()}, except the
+	 * leading prefix {@code refs/heads/} is removed from the reference before
+	 * it is returned to the caller.
+	 *
+	 * @return name of current branch (for example {@code master}), or an
+	 *         ObjectId in hex format if the current branch is detached.
 	 * @throws IOException
 	 */
 	public String getBranch() throws IOException {
-		try {
-			final File ptr = new File(getDirectory(), Constants.HEAD);
-			final BufferedReader br = new BufferedReader(new FileReader(ptr));
-			String ref;
-			try {
-				ref = br.readLine();
-			} finally {
-				br.close();
-			}
-			if (ref.startsWith("ref: "))
-				ref = ref.substring(5);
-			if (ref.startsWith("refs/heads/"))
-				ref = ref.substring(11);
-			return ref;
-		} catch (FileNotFoundException e) {
-			final File ptr = new File(getDirectory(),"head-name");
-			final BufferedReader br = new BufferedReader(new FileReader(ptr));
-			String ref;
-			try {
-				ref = br.readLine();
-			} finally {
-				br.close();
-			}
-			return ref;
-		}
+		String name = getFullBranch();
+		if (name != null)
+			return shortenRefName(name);
+		return name;
 	}
 
 	/**
@@ -971,26 +971,35 @@ public String getBranch() throws IOException {
 	 * @throws IOException
 	 */
 	public Ref getRef(final String name) throws IOException {
-		return refs.readRef(name);
+		return refs.getRef(name);
 	}
 
 	/**
-	 * @return all known refs (heads, tags, remotes).
+	 * @return mutable map of all known refs (heads, tags, remotes).
 	 */
 	public Map<String, Ref> getAllRefs() {
-		return refs.getAllRefs();
+		try {
+			return refs.getRefs(RefDatabase.ALL);
+		} catch (IOException e) {
+			return new HashMap<String, Ref>();
+		}
 	}
 
 	/**
-	 * @return all tags; key is short tag name ("v1.0") and value of the entry
-	 *         contains the ref with the full tag name ("refs/tags/v1.0").
+	 * @return mutable map of all tags; key is short tag name ("v1.0") and value
+	 *         of the entry contains the ref with the full tag name
+	 *         ("refs/tags/v1.0").
 	 */
 	public Map<String, Ref> getTags() {
-		return refs.getTags();
+		try {
+			return refs.getRefs(Constants.R_TAGS);
+		} catch (IOException e) {
+			return new HashMap<String, Ref>();
+		}
 	}
 
 	/**
-	 * Peel a possibly unpeeled ref and updates it.
+	 * Peel a possibly unpeeled reference to an annotated tag.
 	 * <p>
 	 * If the ref cannot be peeled (as it does not refer to an annotated tag)
 	 * the peeled id stays null, but {@link Ref#isPeeled()} will be true.
@@ -1003,7 +1012,14 @@ public Map<String, Ref> getTags() {
 	 *         (or null).
 	 */
 	public Ref peel(final Ref ref) {
-		return refs.peel(ref);
+		try {
+			return refs.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
+			// same as if the reference was not an annotated tag.
+			return ref;
+		}
 	}
 
 	/**
@@ -1013,8 +1029,7 @@ public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
 		Map<String, Ref> allRefs = getAllRefs();
 		Map<AnyObjectId, Set<Ref>> ret = new HashMap<AnyObjectId, Set<Ref>>(allRefs.size());
 		for (Ref ref : allRefs.values()) {
-			if (!ref.isPeeled())
-				ref = peel(ref);
+			ref = peel(ref);
 			AnyObjectId target = ref.getPeeledObjectId();
 			if (target == null)
 				target = ref.getObjectId();
@@ -1033,11 +1048,6 @@ public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
 		return ret;
 	}
 
-	/** Clean up stale caches */
-	public void refreshFromDisk() {
-		refs.clearCache();
-	}
-
 	/**
 	 * @return a representation of the index associated with this repo
 	 * @throws IOException
@@ -1115,7 +1125,7 @@ public static boolean isValidRefName(final String refName) {
 		final int len = refName.length();
 		if (len == 0)
 			return false;
-		if (refName.endsWith(".lock"))
+		if (refName.endsWith(LockFile.SUFFIX))
 			return false;
 
 		int components = 1;
@@ -1233,20 +1243,17 @@ public static void removeAnyRepositoryChangedListener(final RepositoryListener l
 		allListeners.remove(l);
 	}
 
-	void fireRefsMaybeChanged() {
-		if (refs.lastRefModification != refs.lastNotifiedRefModification) {
-			refs.lastNotifiedRefModification = refs.lastRefModification;
-			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 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);
 		}
 	}
 
@@ -1298,7 +1305,7 @@ public String shortenRefName(String refName) {
 	public ReflogReader getReflogReader(String refName) throws IOException {
 		Ref ref = getRef(refName);
 		if (ref != null)
-			return new ReflogReader(this, ref.getOrigName());
+			return new ReflogReader(this, ref.getName());
 		return null;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
new file mode 100644
index 0000000000000000000000000000000000000000..d169d61f1c82d098891e5f46c8a54979b9bdee87
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
@@ -0,0 +1,121 @@
+/*
+ * 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;
+
+/**
+ * A reference that indirectly points at another {@link Ref}.
+ * <p>
+ * A symbolic reference always derives its current value from the target
+ * reference.
+ */
+public class SymbolicRef implements Ref {
+	private final String name;
+
+	private final Ref target;
+
+	/**
+	 * Create a new ref pairing.
+	 *
+	 * @param refName
+	 *            name of this ref.
+	 * @param target
+	 *            the ref we reference and derive our value from.
+	 */
+	public SymbolicRef(String refName, Ref target) {
+		this.name = refName;
+		this.target = target;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public boolean isSymbolic() {
+		return true;
+	}
+
+	public Ref getLeaf() {
+		Ref dst = getTarget();
+		while (dst.isSymbolic())
+			dst = dst.getTarget();
+		return dst;
+	}
+
+	public Ref getTarget() {
+		return target;
+	}
+
+	public ObjectId getObjectId() {
+		return getLeaf().getObjectId();
+	}
+
+	public Storage getStorage() {
+		return Storage.LOOSE;
+	}
+
+	public ObjectId getPeeledObjectId() {
+		return getLeaf().getPeeledObjectId();
+	}
+
+	public boolean isPeeled() {
+		return getLeaf().isPeeled();
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder r = new StringBuilder();
+		r.append("SymbolicRef[");
+		Ref cur = this;
+		while (cur.isSymbolic()) {
+			r.append(cur.getName());
+			r.append(" -> ");
+			cur = cur.getTarget();
+		}
+		r.append(cur.getName());
+		r.append('=');
+		r.append(ObjectId.toString(cur.getObjectId()));
+		r.append("]");
+		return r.toString();
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
index 90634d207a7008c34b1e261df5a514ec5ab53772..74c27a7cdae02149cecb9047267892f67aaaa1f3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -61,6 +61,7 @@
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.util.io.InterruptTimer;
@@ -209,11 +210,11 @@ private void readAdvertisedRefsImpl() throws IOException {
 				if (prior.getPeeledObjectId() != null)
 					throw duplicateAdvertisement(name + "^{}");
 
-				avail.put(name, new Ref(Ref.Storage.NETWORK, name, prior
-						.getObjectId(), id, true));
+				avail.put(name, new ObjectIdRef.PeeledTag(
+						Ref.Storage.NETWORK, name, prior.getObjectId(), id));
 			} else {
-				final Ref prior;
-				prior = avail.put(name, new Ref(Ref.Storage.NETWORK, name, id));
+				final Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
+						Ref.Storage.NETWORK, name, id));
 				if (prior != null)
 					throw duplicateAdvertisement(name);
 			}
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 68196354654c54d7ec3ba65bd50e8d28979b92b4..c788244f74760fda00d065ccf8d88b9c7dff8315 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008-2010, Google Inc.
  * Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2009, Sasa Zivkov <sasa.zivkov@sap.com>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
@@ -65,6 +65,7 @@
 import org.eclipse.jgit.errors.TransportException;
 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;
@@ -140,8 +141,8 @@ private void readBundleV2() throws IOException {
 
 			final String name = line.substring(41, line.length());
 			final ObjectId id = ObjectId.fromString(line.substring(0, 40));
-			final Ref prior = avail.put(name, new Ref(Ref.Storage.NETWORK,
-					name, id));
+			final Ref prior = avail.put(name, new ObjectIdRef.Unpeeled(
+					Ref.Storage.NETWORK, name, id));
 			if (prior != null)
 				throw duplicateAdvertisement(name);
 		}
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 f85c22ddb5e6cd589f0ee13cf9a9ec1b74b8ee7e..f8be827dfdb019846ed8279f08931891fec121dd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008-2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -52,7 +52,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -590,10 +589,10 @@ public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException {
 		adv.advertiseCapability(CAPABILITY_REPORT_STATUS);
 		if (allowOfsDelta)
 			adv.advertiseCapability(CAPABILITY_OFS_DELTA);
-		refs = new HashMap<String, Ref>(db.getAllRefs());
+		refs = db.getAllRefs();
 		final Ref head = refs.remove(Constants.HEAD);
 		adv.send(refs.values());
-		if (head != null && head.getName().equals(head.getOrigName()))
+		if (!head.isSymbolic())
 			adv.advertiseHave(head.getObjectId());
 		adv.includeAdditionalHaves();
 		if (adv.isEmpty())
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 953fae91842663745a0a980c2c6a44848aa74efc..77f30140c6b14e143276cca3416f2d91d04d94ce 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008-2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -170,9 +170,9 @@ public void send(final Collection<Ref> refs) throws IOException {
 		for (final Ref r : RefComparator.sort(refs)) {
 			final RevObject obj = parseAnyOrNull(r.getObjectId());
 			if (obj != null) {
-				advertiseAny(obj, r.getOrigName());
+				advertiseAny(obj, r.getName());
 				if (derefTags && obj instanceof RevTag)
-					advertiseTag((RevTag) obj, r.getOrigName() + "^{}");
+					advertiseTag((RevTag) obj, r.getName() + "^{}");
 			}
 		}
 	}
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 6a1a17f6055fc354d965f763a8572492ff355671..a3fd1ceae518791034046234be94275a033d749d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
@@ -61,9 +61,11 @@
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
 import org.eclipse.jgit.lib.Ref.Storage;
 import org.eclipse.jgit.util.FS;
 
@@ -305,16 +307,15 @@ private Ref readRef(final TreeMap<String, Ref> avail, final String rn)
 				if (r == null)
 					r = readRef(avail, target);
 				if (r == null)
-					return null;
-				r = new Ref(r.getStorage(), rn, r.getObjectId(), r
-						.getPeeledObjectId(), r.isPeeled());
+					r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
+				r = new SymbolicRef(rn, r);
 				avail.put(r.getName(), r);
 				return r;
 			}
 
 			if (ObjectId.isId(s)) {
-				final Ref r = new Ref(loose(avail.get(rn)), rn, ObjectId
-						.fromString(s));
+				final Ref r = new ObjectIdRef.Unpeeled(loose(avail.get(rn)),
+						rn, ObjectId.fromString(s));
 				avail.put(r.getName(), r);
 				return r;
 			}
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 c521e805146dd44869525c5c496c14d18721d0b9..f041765b8af53153387afef7b39bfa9c927e21bb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -80,9 +80,12 @@
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
+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.util.HttpSupport;
 import org.eclipse.jgit.util.IO;
@@ -240,16 +243,17 @@ private FetchConnection newDumbConnection(InputStream in)
 				br = toBufferedReader(openInputStream(conn));
 				try {
 					String line = br.readLine();
-					if (line != null && line.startsWith("ref: ")) {
-						Ref src = refs.get(line.substring(5));
-						if (src != null) {
-							refs.put(Constants.HEAD, new Ref(
-									Ref.Storage.NETWORK, Constants.HEAD, src
-											.getName(), src.getObjectId()));
-						}
+					if (line != null && line.startsWith(RefDirectory.SYMREF)) {
+						String target = line.substring(RefDirectory.SYMREF.length());
+						Ref r = refs.get(target);
+						if (r == null)
+							r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
+						r = new SymbolicRef(Constants.HEAD, r);
+						refs.put(r.getName(), r);
 					} else if (line != null && ObjectId.isId(line)) {
-						refs.put(Constants.HEAD, new Ref(Ref.Storage.NETWORK,
-								Constants.HEAD, ObjectId.fromString(line)));
+						Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK,
+								Constants.HEAD, ObjectId.fromString(line));
+						refs.put(r.getName(), r);
 					}
 				} finally {
 					br.close();
@@ -527,10 +531,11 @@ Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
 					if (prior.getPeeledObjectId() != null)
 						throw duplicateAdvertisement(name + "^{}");
 
-					avail.put(name, new Ref(Ref.Storage.NETWORK, name, prior
-							.getObjectId(), id, true));
+					avail.put(name, new ObjectIdRef.PeeledTag(
+							Ref.Storage.NETWORK, name,
+							prior.getObjectId(), id));
 				} else {
-					final Ref prior = avail.put(name, new Ref(
+					Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
 							Ref.Storage.NETWORK, name, id));
 					if (prior != null)
 						throw duplicateAdvertisement(name);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
index 617cca8907d77535bc0c6faabb9542e89ca5bf0a..2a196b51d2ac9d458103a64130e3a274422a79ad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
@@ -59,9 +59,11 @@
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
 import org.eclipse.jgit.lib.Ref.Storage;
 
 import com.jcraft.jsch.Channel;
@@ -389,21 +391,20 @@ private Ref readRef(final TreeMap<String, Ref> avail,
 				throw new TransportException("Empty ref: " + name);
 
 			if (line.startsWith("ref: ")) {
-				final String p = line.substring("ref: ".length());
-				Ref r = readRef(avail, ROOT_DIR + p, p);
+				final String target = line.substring("ref: ".length());
+				Ref r = avail.get(target);
 				if (r == null)
-					r = avail.get(p);
-				if (r != null) {
-					r = new Ref(loose(r), name, r.getObjectId(), r
-							.getPeeledObjectId(), true);
-					avail.put(name, r);
-				}
+					r = readRef(avail, ROOT_DIR + target, target);
+				if (r == null)
+					r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
+				r = new SymbolicRef(name, r);
+				avail.put(r.getName(), r);
 				return r;
 			}
 
 			if (ObjectId.isId(line)) {
-				final Ref r = new Ref(loose(avail.get(name)), name, ObjectId
-						.fromString(line));
+				final Ref r = new ObjectIdRef.Unpeeled(loose(avail.get(name)),
+						name, ObjectId.fromString(line));
 				avail.put(r.getName(), r);
 				return r;
 			}
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 56f73c50b2ef1c604da8a4356bff89b45a00b17d..88b7ca438b49ca6a64e3740676a4064b3dcec821 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
@@ -58,6 +58,7 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 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;
@@ -324,8 +325,8 @@ private void deleteCommand(final RemoteRefUpdate u) {
 	private void updateCommand(final RemoteRefUpdate u) {
 		try {
 			dest.writeRef(u.getRemoteName(), u.getNewObjectId());
-			newRefs.put(u.getRemoteName(), new Ref(Storage.LOOSE, u
-					.getRemoteName(), u.getNewObjectId()));
+			newRefs.put(u.getRemoteName(), new ObjectIdRef.Unpeeled(
+					Storage.LOOSE, u.getRemoteName(), u.getNewObjectId()));
 			u.setStatus(Status.OK);
 		} catch (IOException e) {
 			u.setStatus(Status.REJECTED_OTHER_REASON);
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 6a010fb4e564dbf93c2a9f4a7c34b13f4f92143f..2aa644ce8b289f7fabc39665c6a1ddcf27b38751 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
@@ -57,8 +57,10 @@
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
+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.util.IO;
 
 /**
@@ -433,18 +435,24 @@ protected void readPackedRefs(final Map<String, Ref> avail)
 	private void readPackedRefsImpl(final Map<String, Ref> avail,
 			final BufferedReader br) throws IOException {
 		Ref last = null;
+		boolean peeled = false;
 		for (;;) {
 			String line = br.readLine();
 			if (line == null)
 				break;
-			if (line.charAt(0) == '#')
+			if (line.charAt(0) == '#') {
+				if (line.startsWith(RefDirectory.PACKED_REFS_HEADER)) {
+					line = line.substring(RefDirectory.PACKED_REFS_HEADER.length());
+					peeled = line.contains(RefDirectory.PACKED_REFS_PEELED);
+				}
 				continue;
+			}
 			if (line.charAt(0) == '^') {
 				if (last == null)
 					throw new TransportException("Peeled line before ref.");
 				final ObjectId id = ObjectId.fromString(line.substring(1));
-				last = new Ref(Ref.Storage.PACKED, last.getName(), last
-						.getObjectId(), id, true);
+				last = new ObjectIdRef.PeeledTag(Ref.Storage.PACKED, last
+						.getName(), last.getObjectId(), id);
 				avail.put(last.getName(), last);
 				continue;
 			}
@@ -454,7 +462,10 @@ private void readPackedRefsImpl(final Map<String, Ref> avail,
 				throw new TransportException("Unrecognized ref: " + line);
 			final ObjectId id = ObjectId.fromString(line.substring(0, sp));
 			final String name = line.substring(sp + 1);
-			last = new Ref(Ref.Storage.PACKED, name, id);
+			if (peeled)
+				last = new ObjectIdRef.PeeledNonTag(Ref.Storage.PACKED, name, id);
+			else
+				last = new ObjectIdRef.Unpeeled(Ref.Storage.PACKED, name, id);
 			avail.put(last.getName(), last);
 		}
 	}