From 5b13adcea97c19750ebfd5be226c48bb646708cf Mon Sep 17 00:00:00 2001 From: Robin Rosenberg 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 Signed-off-by: Shawn O. Pearce --- .../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 *