From 5b13adcea97c19750ebfd5be226c48bb646708cf Mon Sep 17 00:00:00 2001
From: Robin Rosenberg <robin.rosenberg@dewire.com>
Date: Mon, 28 Dec 2009 16:49:50 +0100
Subject: [PATCH] Add support for creating detached heads

An extra flag when creating a RefUpdate object allows the
caller to destroy the symref and replace it with an object
ref, a.k.a. detached HEAD.

Change-Id: Ia88d48eab1eb4861ebfa39e3be9258c3824a19db
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../org/eclipse/jgit/lib/RefUpdateTest.java   | 58 +++++++++++++++++++
 .../src/org/eclipse/jgit/lib/RefDatabase.java | 20 +++++++
 .../src/org/eclipse/jgit/lib/Repository.java  | 18 ++++++
 3 files changed, 96 insertions(+)

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 fa77c6a8d..d851528cd 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
@@ -292,6 +292,64 @@ public void testUpdateRefForward() throws IOException {
 		assertEquals(pid, db.resolve("refs/heads/master"));
 	}
 
+	/**
+	 * Update the HEAD ref. Only it should be changed, not what it points to.
+	 *
+	 * @throws Exception
+	 */
+	public void testUpdateRefDetached() throws Exception {
+		ObjectId pid = db.resolve("refs/heads/master");
+		ObjectId ppid = db.resolve("refs/heads/master^");
+		RefUpdate updateRef = db.updateRef("HEAD", true);
+		updateRef.setForceUpdate(true);
+		updateRef.setNewObjectId(ppid);
+		Result update = updateRef.update();
+		assertEquals(Result.FORCED, update);
+		assertEquals(ppid, db.resolve("HEAD"));
+		Ref ref = db.getRef("HEAD");
+		assertEquals("HEAD", ref.getName());
+		assertEquals("HEAD", ref.getOrigName());
+
+		// the branch HEAD referred to is left untouched
+		assertEquals(pid, db.resolve("refs/heads/master"));
+		ReflogReader reflogReader = new  ReflogReader(db, "HEAD");
+		org.eclipse.jgit.lib.ReflogReader.Entry e = reflogReader.getReverseEntries().get(0);
+		assertEquals(pid, e.getOldId());
+		assertEquals(ppid, e.getNewId());
+		assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
+		assertEquals("GIT_COMMITTER_NAME", e.getWho().getName());
+		assertEquals(1250379778000L, e.getWho().getWhen().getTime());
+	}
+
+	/**
+	 * Update the HEAD ref when the referenced branch is unborn
+	 *
+	 * @throws Exception
+	 */
+	public void testUpdateRefDetachedUnbornHead() throws Exception {
+		ObjectId ppid = db.resolve("refs/heads/master^");
+		db.writeSymref("HEAD", "refs/heads/unborn");
+		RefUpdate updateRef = db.updateRef("HEAD", true);
+		updateRef.setForceUpdate(true);
+		updateRef.setNewObjectId(ppid);
+		Result update = updateRef.update();
+		assertEquals(Result.NEW, update);
+		assertEquals(ppid, db.resolve("HEAD"));
+		Ref ref = db.getRef("HEAD");
+		assertEquals("HEAD", ref.getName());
+		assertEquals("HEAD", ref.getOrigName());
+
+		// the branch HEAD referred to is left untouched
+		assertNull(db.resolve("refs/heads/unborn"));
+		ReflogReader reflogReader = new  ReflogReader(db, "HEAD");
+		org.eclipse.jgit.lib.ReflogReader.Entry e = reflogReader.getReverseEntries().get(0);
+		assertEquals(ObjectId.zeroId(), e.getOldId());
+		assertEquals(ppid, e.getNewId());
+		assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
+		assertEquals("GIT_COMMITTER_NAME", e.getWho().getName());
+		assertEquals(1250379778000L, e.getWho().getWhen().getTime());
+	}
+
 	/**
 	 * Delete a ref that exists both as packed and loose. Make sure the ref
 	 * cannot be resolved after delete.
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 67358c808..fb6a27db3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -137,10 +137,30 @@ ObjectId idOf(final String name) throws IOException {
 	 *             to the base ref, as the symbolic ref could not be read.
 	 */
 	RefUpdate newUpdate(final String name) throws IOException {
+		return newUpdate(name, false);
+	}
+
+	/**
+	 * Create a command to update, create or delete a ref in this repository.
+	 *
+	 * @param name
+	 *            name of the ref the caller wants to modify.
+	 * @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.
+	 * @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.
+	 */
+	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()));
 	}
 
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 5b8b34fa6..f576b057c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -501,6 +501,24 @@ public RefUpdate updateRef(final String ref) throws IOException {
 		return refs.newUpdate(ref);
 	}
 
+	/**
+	 * Create a command to update, create or delete a ref in this repository.
+	 *
+	 * @param ref
+	 *            name of the ref the caller wants to modify.
+	 * @param detach
+	 *            true to create a detached head
+	 * @return an update command. The caller must finish populating this command
+	 *         and then invoke one of the update methods to actually make a
+	 *         change.
+	 * @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.
+	 */
+	public RefUpdate updateRef(final String ref, final boolean detach) throws IOException {
+		return refs.newUpdate(ref, detach);
+	}
+
 	/**
 	 * Create a command to rename a ref in this repository
 	 *
-- 
GitLab