/*
 * Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
 * Copyright (C) 2009-2010, Google Inc.
 * Copyright (C) 2008-2013, Robin Rosenberg <robin.rosenberg@dewire.com> and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0 which is available at
 * https://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.eclipse.jgit.internal.storage.file;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.junit.Assert.assertEquals;
import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
import static org.eclipse.jgit.lib.RefUpdate.Result.FORCED;
import static org.eclipse.jgit.lib.RefUpdate.Result.IO_FAILURE;
import static org.eclipse.jgit.lib.RefUpdate.Result.LOCK_FAILURE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;

import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefRename;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test;

public class RefUpdateTest extends SampleDataRepositoryTestCase {
	private void writeSymref(String src, String dst) throws IOException {
		RefUpdate u = db.updateRef(src);
		switch (u.link(dst)) {
		case NEW:
		case FORCED:
		case NO_CHANGE:
			break;
		default:
			fail("link " + src + " to " + dst);
		}
	}

	private RefUpdate updateRef(String name) throws IOException {
		final RefUpdate ref = db.updateRef(name);
		ref.setNewObjectId(db.resolve(Constants.HEAD));
		return ref;
	}

	private void delete(RefUpdate ref, Result expected)
			throws IOException {
		delete(ref, expected, true, true);
	}

	private void delete(final RefUpdate ref, final Result expected,
			final boolean exists, final boolean removed) throws IOException {
		delete(db, ref, expected, exists, removed);
	}

	private void delete(Repository repo, final RefUpdate ref,
			final Result expected, final boolean exists, final boolean removed)
			throws IOException {
		assertEquals(exists, getRef(repo, ref.getName()).isPresent());
		assertEquals(expected, ref.delete());
		assertEquals(!removed, getRef(repo, ref.getName()).isPresent());
	}

	private Optional<Ref> getRef(Repository repo, String name)
			throws IOException {
		return getRef(repo.getRefDatabase().getRefs(), name);
	}

	private Optional<Ref> getRef(List<Ref> refs, String name) {
		return refs.stream().filter(r -> r.getName().equals(name)).findAny();
	}

	@Test
	public void testNoCacheObjectIdSubclass() throws IOException {
		final String newRef = "refs/heads/abc";
		final RefUpdate ru = updateRef(newRef);
		final SubclassedId newid = new SubclassedId(ru.getNewObjectId());
		ru.setNewObjectId(newid);
		Result update = ru.update();
		assertEquals(Result.NEW, update);
		final Ref r = getRef(db, newRef).get();
		assertEquals(newRef, r.getName());
		assertNotNull(r.getObjectId());
		assertNotSame(newid, r.getObjectId());
		assertSame(ObjectId.class, r.getObjectId().getClass());
		assertEquals(newid, r.getObjectId());
		List<ReflogEntry> reverseEntries1 = db
				.getReflogReader("refs/heads/abc").getReverseEntries();
		ReflogEntry entry1 = reverseEntries1.get(0);
		assertEquals(1, reverseEntries1.size());
		assertEquals(ObjectId.zeroId(), entry1.getOldId());
		assertEquals(r.getObjectId(), entry1.getNewId());
		assertEquals(new PersonIdent(db).toString(),  entry1.getWho().toString());
		assertEquals("", entry1.getComment());
		List<ReflogEntry> reverseEntries2 = db.getReflogReader("HEAD")
				.getReverseEntries();
		assertEquals(0, reverseEntries2.size());
	}

	@Test
	public void testNewNamespaceConflictWithLoosePrefixNameExists()
			throws IOException {
		final String newRef = "refs/heads/z";
		final RefUpdate ru = updateRef(newRef);
		Result update = ru.update();
		assertEquals(Result.NEW, update);
		// end setup
		final String newRef2 = "refs/heads/z/a";
		final RefUpdate ru2 = updateRef(newRef2);
		Result update2 = ru2.update();
		assertEquals(Result.LOCK_FAILURE, update2);
		assertEquals(1, db.getReflogReader("refs/heads/z").getReverseEntries().size());
		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
	}

	@Test
	public void testNewNamespaceConflictWithPackedPrefixNameExists()
			throws IOException {
		final String newRef = "refs/heads/master/x";
		final RefUpdate ru = updateRef(newRef);
		Result update = ru.update();
		assertEquals(Result.LOCK_FAILURE, update);
		assertNull(db.getReflogReader("refs/heads/master/x"));
		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
	}

	@Test
	public void testNewNamespaceConflictWithLoosePrefixOfExisting()
			throws IOException {
		final String newRef = "refs/heads/z/a";
		final RefUpdate ru = updateRef(newRef);
		Result update = ru.update();
		assertEquals(Result.NEW, update);
		// end setup
		final String newRef2 = "refs/heads/z";
		final RefUpdate ru2 = updateRef(newRef2);
		Result update2 = ru2.update();
		assertEquals(Result.LOCK_FAILURE, update2);
		assertEquals(1, db.getReflogReader("refs/heads/z/a").getReverseEntries().size());
		assertNull(db.getReflogReader("refs/heads/z"));
		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
	}

	@Test
	public void testNewNamespaceConflictWithPackedPrefixOfExisting()
			throws IOException {
		final String newRef = "refs/heads/prefix";
		final RefUpdate ru = updateRef(newRef);
		Result update = ru.update();
		assertEquals(Result.LOCK_FAILURE, update);
		assertNull(db.getReflogReader("refs/heads/prefix"));
		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
	}

	/**
	 * Delete a ref that is pointed to by HEAD
	 *
	 * @throws IOException
	 */
	@Test
	public void testDeleteHEADreferencedRef() throws IOException {
		ObjectId pid = db.resolve("refs/heads/master^");
		RefUpdate updateRef = db.updateRef("refs/heads/master");
		updateRef.setNewObjectId(pid);
		updateRef.setForceUpdate(true);
		Result update = updateRef.update();
		assertEquals(Result.FORCED, update); // internal

		RefUpdate updateRef2 = db.updateRef("refs/heads/master");
		Result delete = updateRef2.delete();
		assertEquals(Result.REJECTED_CURRENT_BRANCH, delete);
		assertEquals(pid, db.resolve("refs/heads/master"));
		assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size());
		assertEquals(0,db.getReflogReader("HEAD").getReverseEntries().size());
	}

	@Test
	public void testWriteReflog() throws IOException {
		ObjectId pid = db.resolve("refs/heads/master^");
		RefUpdate updateRef = db.updateRef("refs/heads/master");
		updateRef.setNewObjectId(pid);
		updateRef.setForceUpdate(true);
		Result update = updateRef.update();
		assertEquals(Result.FORCED, update);
		assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size());
	}

	@Test
	public void testLooseDelete() throws IOException {
		final String newRef = "refs/heads/abc";
		RefUpdate ref = updateRef(newRef);
		ref.update(); // create loose ref
		ref = updateRef(newRef); // refresh
		delete(ref, Result.NO_CHANGE);
		assertNull(db.getReflogReader("refs/heads/abc"));
	}

	@Test
	public void testDeleteHead() throws IOException {
		final RefUpdate ref = updateRef(Constants.HEAD);
		delete(ref, Result.REJECTED_CURRENT_BRANCH, true, false);
		assertEquals(0, db.getReflogReader("refs/heads/master").getReverseEntries().size());
		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
	}

	@Test
	public void testDeleteHeadInBareRepo() throws IOException {
		Repository bareRepo = createBareRepository();
		String master = "refs/heads/master";
		Ref head = bareRepo.exactRef(Constants.HEAD);
		assertNotNull(head);
		assertTrue(head.isSymbolic());
		assertEquals(master, head.getLeaf().getName());
		assertNull(head.getObjectId());
		assertNull(bareRepo.exactRef(master));

		ObjectId blobId;
		try (ObjectInserter ins = bareRepo.newObjectInserter()) {
			blobId = ins.insert(Constants.OBJ_BLOB, "contents".getBytes(UTF_8));
			ins.flush();
		}

		// Create master via HEAD, so we delete it.
		RefUpdate ref = bareRepo.updateRef(Constants.HEAD);
		ref.setNewObjectId(blobId);
		assertEquals(Result.NEW, ref.update());

		head = bareRepo.exactRef(Constants.HEAD);
		assertTrue(head.isSymbolic());
		assertEquals(master, head.getLeaf().getName());
		assertEquals(blobId, head.getLeaf().getObjectId());
		assertEquals(blobId, bareRepo.exactRef(master).getObjectId());

		// Unlike in a non-bare repo, deleting the HEAD is allowed, and leaves HEAD
		// back in a dangling state.
		ref = bareRepo.updateRef(Constants.HEAD);
		ref.setExpectedOldObjectId(blobId);
		ref.setForceUpdate(true);
		delete(bareRepo, ref, Result.FORCED, true, true);

		head = bareRepo.exactRef(Constants.HEAD);
		assertNotNull(head);
		assertTrue(head.isSymbolic());
		assertEquals(master, head.getLeaf().getName());
		assertNull(head.getObjectId());
		assertNull(bareRepo.exactRef(master));
	}

	@Test
	public void testDeleteSymref() throws IOException {
		RefUpdate dst = updateRef("refs/heads/abc");
		assertEquals(Result.NEW, dst.update());
		ObjectId id = dst.getNewObjectId();

		RefUpdate u = db.updateRef("refs/symref");
		assertEquals(Result.NEW, u.link(dst.getName()));

		Ref ref = db.exactRef(u.getName());
		assertNotNull(ref);
		assertTrue(ref.isSymbolic());
		assertEquals(dst.getName(), ref.getLeaf().getName());
		assertEquals(id, ref.getLeaf().getObjectId());

		u = db.updateRef(u.getName());
		u.setDetachingSymbolicRef();
		u.setForceUpdate(true);
		assertEquals(Result.FORCED, u.delete());

		assertNull(db.exactRef(u.getName()));
		ref = db.exactRef(dst.getName());
		assertNotNull(ref);
		assertFalse(ref.isSymbolic());
		assertEquals(id, ref.getObjectId());
	}

	/**
	 * Delete a loose ref and make sure the directory in refs is deleted too,
	 * and the reflog dir too
	 *
	 * @throws IOException
	 */
	@Test
	public void testDeleteLooseAndItsDirectory() throws IOException {
		ObjectId pid = db.resolve("refs/heads/c^");
		RefUpdate updateRef = db.updateRef("refs/heads/z/c");
		updateRef.setNewObjectId(pid);
		updateRef.setForceUpdate(true);
		updateRef.setRefLogMessage("new test ref", false);
		Result update = updateRef.update();
		assertEquals(Result.NEW, update); // internal
		assertTrue(new File(db.getDirectory(), Constants.R_HEADS + "z")
				.exists());
		assertTrue(new File(db.getDirectory(), "logs/refs/heads/z").exists());

		// The real test here
		RefUpdate updateRef2 = db.updateRef("refs/heads/z/c");
		updateRef2.setForceUpdate(true);
		Result delete = updateRef2.delete();
		assertEquals(Result.FORCED, delete);
		assertNull(db.resolve("refs/heads/z/c"));
		assertFalse(new File(db.getDirectory(), Constants.R_HEADS + "z")
				.exists());
		assertFalse(new File(db.getDirectory(), "logs/refs/heads/z").exists());
	}

	@Test
	public void testDeleteNotFound() throws IOException {
		final RefUpdate ref = updateRef("refs/heads/xyz");
		delete(ref, Result.NEW, false, true);
	}

	@Test
	public void testDeleteFastForward() throws IOException {
		final RefUpdate ref = updateRef("refs/heads/a");
		delete(ref, Result.FAST_FORWARD);
	}

	@Test
	public void testDeleteForce() throws IOException {
		final RefUpdate ref = db.updateRef("refs/heads/b");
		ref.setNewObjectId(db.resolve("refs/heads/a"));
		delete(ref, Result.REJECTED, true, false);
		ref.setForceUpdate(true);
		delete(ref, Result.FORCED);
	}

	@Test
	public void testDeleteWithoutHead() throws IOException {
		// Prepare repository without HEAD
		RefUpdate refUpdate = db.updateRef(Constants.HEAD, true);
		refUpdate.setForceUpdate(true);
		refUpdate.setNewObjectId(ObjectId.zeroId());
		Result updateResult = refUpdate.update();
		assertEquals(Result.FORCED, updateResult);

		assertEquals(ObjectId.zeroId(), db.exactRef("HEAD").getObjectId());
		Result deleteHeadResult = db.updateRef(Constants.HEAD).delete();
		assertEquals(Result.NO_CHANGE, deleteHeadResult);

		// Any result is ok as long as it's not an NPE
		db.updateRef(Constants.R_HEADS + "master").delete();
	}

	@Test
	public void testRefKeySameAsName() {
		@SuppressWarnings("deprecation")
		Map<String, Ref> allRefs = db.getAllRefs();
		for (Entry<String, Ref> e : allRefs.entrySet()) {
			assertEquals(e.getKey(), e.getValue().getName());
		}
	}

	/**
	 * Try modify a ref forward, fast forward
	 *
	 * @throws IOException
	 */
	@Test
	public void testUpdateRefForward() throws IOException {
		ObjectId ppid = db.resolve("refs/heads/master^");
		ObjectId pid = db.resolve("refs/heads/master");

		RefUpdate updateRef = db.updateRef("refs/heads/master");
		updateRef.setNewObjectId(ppid);
		updateRef.setForceUpdate(true);
		Result update = updateRef.update();
		assertEquals(Result.FORCED, update);
		assertEquals(ppid, db.resolve("refs/heads/master"));

		// real test
		RefUpdate updateRef2 = db.updateRef("refs/heads/master");
		updateRef2.setNewObjectId(pid);
		Result update2 = updateRef2.update();
		assertEquals(Result.FAST_FORWARD, update2);
		assertEquals(pid, db.resolve("refs/heads/master"));
	}

	/**
	 * Update the HEAD ref. Only it should be changed, not what it points to.
	 *
	 * @throws Exception
	 */
	@Test
	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.exactRef("HEAD");
		assertEquals("HEAD", ref.getName());
		assertTrue("is detached", !ref.isSymbolic());

		// the branch HEAD referred to is left untouched
		assertEquals(pid, db.resolve("refs/heads/master"));
		ReflogReader reflogReader = db.getReflogReader("HEAD");
		ReflogEntry 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
	 */
	@Test
	public void testUpdateRefDetachedUnbornHead() throws Exception {
		ObjectId ppid = db.resolve("refs/heads/master^");
		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.exactRef("HEAD");
		assertEquals("HEAD", ref.getName());
		assertTrue("is detached", !ref.isSymbolic());

		// the branch HEAD referred to is left untouched
		assertNull(db.resolve("refs/heads/unborn"));
		ReflogReader reflogReader = db.getReflogReader("HEAD");
		ReflogEntry 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.
	 *
	 * @throws IOException
	 */
	@Test
	public void testDeleteLoosePacked() throws IOException {
		ObjectId pid = db.resolve("refs/heads/c^");
		RefUpdate updateRef = db.updateRef("refs/heads/c");
		updateRef.setNewObjectId(pid);
		updateRef.setForceUpdate(true);
		Result update = updateRef.update();
		assertEquals(Result.FORCED, update); // internal

		// The real test here
		RefUpdate updateRef2 = db.updateRef("refs/heads/c");
		updateRef2.setForceUpdate(true);
		Result delete = updateRef2.delete();
		assertEquals(Result.FORCED, delete);
		assertNull(db.resolve("refs/heads/c"));
	}

	/**
	 * Try modify a ref to same
	 *
	 * @throws IOException
	 */
	@Test
	public void testUpdateRefNoChange() throws IOException {
		ObjectId pid = db.resolve("refs/heads/master");
		RefUpdate updateRef = db.updateRef("refs/heads/master");
		updateRef.setNewObjectId(pid);
		Result update = updateRef.update();
		assertEquals(Result.NO_CHANGE, update);
		assertEquals(pid, db.resolve("refs/heads/master"));
	}

	/**
	 * Test case originating from
	 * <a href="http://bugs.eclipse.org/285991">bug 285991</a>
	 *
	 * Make sure the in memory cache is updated properly after
	 * update of symref. This one did not fail because the
	 * ref was packed due to implementation issues.
	 *
	 * @throws Exception
	 */
	@Test
	public void testRefsCacheAfterUpdate() throws Exception {
		// Do not use the default repo for this case.
		List<Ref> allRefs = db.getRefDatabase().getRefs();
		ObjectId oldValue = db.resolve("HEAD");
		ObjectId newValue = db.resolve("HEAD^");
		// first make HEAD refer to loose ref
		RefUpdate updateRef = db.updateRef(Constants.HEAD);
		updateRef.setForceUpdate(true);
		updateRef.setNewObjectId(newValue);
		Result update = updateRef.update();
		assertEquals(Result.FORCED, update);

		// now update that ref
		updateRef = db.updateRef(Constants.HEAD);
		updateRef.setNewObjectId(oldValue);
		update = updateRef.update();
		assertEquals(Result.FAST_FORWARD, update);

		allRefs = db.getRefDatabase().getRefs();
		Ref master = getRef(allRefs, "refs/heads/master").get();
		Ref head = getRef(allRefs, "HEAD").get();
		assertEquals("refs/heads/master", master.getName());
		assertEquals("HEAD", head.getName());
		assertTrue("is symbolic reference", head.isSymbolic());
		assertSame(master, head.getTarget());
	}

	/**
	 * Test case originating from
	 * <a href="http://bugs.eclipse.org/285991">bug 285991</a>
	 *
	 * Make sure the in memory cache is updated properly after
	 * update of symref.
	 *
	 * @throws Exception
	 */
	@Test
	public void testRefsCacheAfterUpdateLooseOnly() throws Exception {
		// Do not use the default repo for this case.
		List<Ref> allRefs = db.getRefDatabase().getRefs();
		ObjectId oldValue = db.resolve("HEAD");
		writeSymref(Constants.HEAD, "refs/heads/newref");
		RefUpdate updateRef = db.updateRef(Constants.HEAD);
		updateRef.setForceUpdate(true);
		updateRef.setNewObjectId(oldValue);
		Result update = updateRef.update();
		assertEquals(Result.NEW, update);

		allRefs = db.getRefDatabase().getRefs();
		Ref head = getRef(allRefs, "HEAD").get();
		Ref newref = getRef(allRefs, "refs/heads/newref").get();
		assertEquals("refs/heads/newref", newref.getName());
		assertEquals("HEAD", head.getName());
		assertTrue("is symbolic reference", head.isSymbolic());
		assertSame(newref, head.getTarget());
	}

	/**
	 * Try modify a ref, but get wrong expected old value
	 *
	 * @throws IOException
	 */
	@Test
	public void testUpdateRefLockFailureWrongOldValue() throws IOException {
		ObjectId pid = db.resolve("refs/heads/master");
		RefUpdate updateRef = db.updateRef("refs/heads/master");
		updateRef.setNewObjectId(pid);
		updateRef.setExpectedOldObjectId(db.resolve("refs/heads/master^"));
		Result update = updateRef.update();
		assertEquals(Result.LOCK_FAILURE, update);
		assertEquals(pid, db.resolve("refs/heads/master"));
	}

	/**
	 * Try modify a ref forward, fast forward, checking old value first
	 *
	 * @throws IOException
	 */
	@Test
	public void testUpdateRefForwardWithCheck1() throws IOException {
		ObjectId ppid = db.resolve("refs/heads/master^");
		ObjectId pid = db.resolve("refs/heads/master");

		RefUpdate updateRef = db.updateRef("refs/heads/master");
		updateRef.setNewObjectId(ppid);
		updateRef.setForceUpdate(true);
		Result update = updateRef.update();
		assertEquals(Result.FORCED, update);
		assertEquals(ppid, db.resolve("refs/heads/master"));

		// real test
		RefUpdate updateRef2 = db.updateRef("refs/heads/master");
		updateRef2.setExpectedOldObjectId(ppid);
		updateRef2.setNewObjectId(pid);
		Result update2 = updateRef2.update();
		assertEquals(Result.FAST_FORWARD, update2);
		assertEquals(pid, db.resolve("refs/heads/master"));
	}

	/**
	 * Try modify a ref forward, fast forward, checking old commit first
	 *
	 * @throws IOException
	 */
	@Test
	public void testUpdateRefForwardWithCheck2() throws IOException {
		ObjectId ppid = db.resolve("refs/heads/master^");
		ObjectId pid = db.resolve("refs/heads/master");

		RefUpdate updateRef = db.updateRef("refs/heads/master");
		updateRef.setNewObjectId(ppid);
		updateRef.setForceUpdate(true);
		Result update = updateRef.update();
		assertEquals(Result.FORCED, update);
		assertEquals(ppid, db.resolve("refs/heads/master"));

		// real test
		try (RevWalk rw = new RevWalk(db)) {
			RevCommit old = rw.parseCommit(ppid);
			RefUpdate updateRef2 = db.updateRef("refs/heads/master");
			updateRef2.setExpectedOldObjectId(old);
			updateRef2.setNewObjectId(pid);
			Result update2 = updateRef2.update();
			assertEquals(Result.FAST_FORWARD, update2);
			assertEquals(pid, db.resolve("refs/heads/master"));
		}
	}

	/**
	 * Try modify a ref that is locked
	 *
	 * @throws IOException
	 */
	@Test
	public void testUpdateRefLockFailureLocked() throws IOException {
		ObjectId opid = db.resolve("refs/heads/master");
		ObjectId pid = db.resolve("refs/heads/master^");
		RefUpdate updateRef = db.updateRef("refs/heads/master");
		updateRef.setNewObjectId(pid);
		LockFile lockFile1 = new LockFile(new File(db.getDirectory(),
				"refs/heads/master"));
		try {
			assertTrue(lockFile1.lock()); // precondition to test
			Result update = updateRef.update();
			assertEquals(Result.LOCK_FAILURE, update);
			assertEquals(opid, db.resolve("refs/heads/master"));
			LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master"));
			assertFalse(lockFile2.lock()); // was locked, still is
		} finally {
			lockFile1.unlock();
		}
	}

	/**
	 * Try to delete a ref. Delete requires force.
	 *
	 * @throws IOException
	 */
	@Test
	public void testDeleteLoosePackedRejected() throws IOException {
		ObjectId pid = db.resolve("refs/heads/c^");
		ObjectId oldpid = db.resolve("refs/heads/c");
		RefUpdate updateRef = db.updateRef("refs/heads/c");
		updateRef.setNewObjectId(pid);
		Result update = updateRef.update();
		assertEquals(Result.REJECTED, update);
		assertEquals(oldpid, db.resolve("refs/heads/c"));
	}

	@Test
	public void testRenameBranchNoPreviousLog() throws IOException {
		assertFalse("precondition, no log on old branchg", new File(db
				.getDirectory(), "logs/refs/heads/b").exists());
		ObjectId rb = db.resolve("refs/heads/b");
		ObjectId oldHead = db.resolve(Constants.HEAD);
		assertFalse(rb.equals(oldHead)); // assumption for this test
		RefRename renameRef = db.renameRef("refs/heads/b",
				"refs/heads/new/name");
		Result result = renameRef.rename();
		assertEquals(Result.RENAMED, result);
		assertEquals(rb, db.resolve("refs/heads/new/name"));
		assertNull(db.resolve("refs/heads/b"));
		assertEquals(1, db.getReflogReader("new/name").getReverseEntries().size());
		assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name")
				.getLastEntry().getComment());
		assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
		assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged
	}

	@Test
	public void testRenameBranchHasPreviousLog() throws IOException {
		ObjectId rb = db.resolve("refs/heads/b");
		ObjectId oldHead = db.resolve(Constants.HEAD);
		assertFalse("precondition for this test, branch b != HEAD", rb
				.equals(oldHead));
		writeReflog(db, 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");
		Result result = renameRef.rename();
		assertEquals(Result.RENAMED, result);
		assertEquals(rb, db.resolve("refs/heads/new/name"));
		assertNull(db.resolve("refs/heads/b"));
		assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size());
		assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name")
				.getLastEntry().getComment());
		assertEquals("Just a message", db.getReflogReader("new/name")
				.getReverseEntries().get(1).getComment());
		assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
		assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged
	}

	@Test
	public void testRenameCurrentBranch() throws IOException {
		ObjectId rb = db.resolve("refs/heads/b");
		writeSymref(Constants.HEAD, "refs/heads/b");
		ObjectId oldHead = db.resolve(Constants.HEAD);
		assertEquals("internal test condition, b == HEAD", oldHead, rb);
		writeReflog(db, 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");
		Result result = renameRef.rename();
		assertEquals(Result.RENAMED, result);
		assertEquals(rb, db.resolve("refs/heads/new/name"));
		assertNull(db.resolve("refs/heads/b"));
		assertEquals("Branch: renamed b to new/name", db.getReflogReader(
				"new/name").getLastEntry().getComment());
		assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
		assertEquals(rb, db.resolve(Constants.HEAD));
		assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size());
		assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name").getReverseEntries().get(0).getComment());
		assertEquals("Just a message", db.getReflogReader("new/name").getReverseEntries().get(1).getComment());
	}

	@Test
	public void testRenameBranchAlsoInPack() throws IOException {
		ObjectId rb = db.resolve("refs/heads/b");
		ObjectId rb2 = db.resolve("refs/heads/b~1");
		assertEquals(Ref.Storage.PACKED, db.exactRef("refs/heads/b").getStorage());
		RefUpdate updateRef = db.updateRef("refs/heads/b");
		updateRef.setNewObjectId(rb2);
		updateRef.setForceUpdate(true);
		Result update = updateRef.update();
		assertEquals("internal check new ref is loose", Result.FORCED, update);
		assertEquals(Ref.Storage.LOOSE, db.exactRef("refs/heads/b").getStorage());
		writeReflog(db, 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");
		Result result = renameRef.rename();
		assertEquals(Result.RENAMED, result);
		assertEquals(rb2, db.resolve("refs/heads/new/name"));
		assertNull(db.resolve("refs/heads/b"));
		assertEquals("Branch: renamed b to new/name", db.getReflogReader(
				"new/name").getLastEntry().getComment());
		assertEquals(3, db.getReflogReader("refs/heads/new/name").getReverseEntries().size());
		assertEquals("Branch: renamed b to new/name", db.getReflogReader("refs/heads/new/name").getReverseEntries().get(0).getComment());
		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
		// make sure b's log file is gone too.
		assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());

		// Create new Repository instance, to reread caches and make sure our
		// assumptions are persistent.
		try (Repository ndb = new FileRepository(db.getDirectory())) {
			assertEquals(rb2, ndb.resolve("refs/heads/new/name"));
			assertNull(ndb.resolve("refs/heads/b"));
		}
	}

	public void tryRenameWhenLocked(String toLock, String fromName,
			String toName, String headPointsTo) throws IOException {
		// setup
		writeSymref(Constants.HEAD, headPointsTo);
		ObjectId oldfromId = db.resolve(fromName);
		ObjectId oldHeadId = db.resolve(Constants.HEAD);
		writeReflog(db, oldfromId, "Just a message", fromName);
		List<ReflogEntry> oldFromLog = db
				.getReflogReader(fromName).getReverseEntries();
		List<ReflogEntry> oldHeadLog = oldHeadId != null ? db
				.getReflogReader(Constants.HEAD).getReverseEntries() : null;

		assertTrue("internal check, we have a log", new File(db.getDirectory(),
				"logs/" + fromName).exists());

		// "someone" has branch X locked
		LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock));
		try {
			assertTrue(lockFile.lock());

			// Now this is our test
			RefRename renameRef = db.renameRef(fromName, toName);
			Result result = renameRef.rename();
			assertEquals(Result.LOCK_FAILURE, result);

			// Check that the involved refs are the same despite the failure
			assertExists(false, toName);
			if (!toLock.equals(toName))
				assertExists(false, toName + LOCK_SUFFIX);
			assertExists(true, toLock + LOCK_SUFFIX);
			if (!toLock.equals(fromName))
				assertExists(false, "logs/" + fromName + LOCK_SUFFIX);
			assertExists(false, "logs/" + toName + LOCK_SUFFIX);
			assertEquals(oldHeadId, db.resolve(Constants.HEAD));
			assertEquals(oldfromId, db.resolve(fromName));
			assertNull(db.resolve(toName));
			assertEquals(oldFromLog.toString(), db.getReflogReader(fromName)
					.getReverseEntries().toString());
			if (oldHeadId != null && oldHeadLog != null)
				assertEquals(oldHeadLog.toString(), db.getReflogReader(
						Constants.HEAD).getReverseEntries().toString());
		} finally {
			lockFile.unlock();
		}
	}

	private void assertExists(boolean positive, String toName) {
		assertEquals(toName + (positive ? " " : " does not ") + "exist",
				positive, new File(db.getDirectory(), toName).exists());
	}

	@Test
	public void testRenameBranchCannotLockAFileHEADisFromLockHEAD()
			throws IOException {
		tryRenameWhenLocked("HEAD", "refs/heads/b", "refs/heads/new/name",
				"refs/heads/b");
	}

	@Test
	public void testRenameBranchCannotLockAFileHEADisFromLockFrom()
			throws IOException {
		tryRenameWhenLocked("refs/heads/b", "refs/heads/b",
				"refs/heads/new/name", "refs/heads/b");
	}

	@Test
	public void testRenameBranchCannotLockAFileHEADisFromLockTo()
			throws IOException {
		tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b",
				"refs/heads/new/name", "refs/heads/b");
	}

	@Test
	public void testRenameBranchCannotLockAFileHEADisToLockFrom()
			throws IOException {
		tryRenameWhenLocked("refs/heads/b", "refs/heads/b",
				"refs/heads/new/name", "refs/heads/new/name");
	}

	@Test
	public void testRenameBranchCannotLockAFileHEADisToLockTo()
			throws IOException {
		tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b",
				"refs/heads/new/name", "refs/heads/new/name");
	}

	@Test
	public void testRenameBranchCannotLockAFileHEADisOtherLockFrom()
			throws IOException {
		tryRenameWhenLocked("refs/heads/b", "refs/heads/b",
				"refs/heads/new/name", "refs/heads/a");
	}

	@Test
	public void testRenameBranchCannotLockAFileHEADisOtherLockTo()
			throws IOException {
		tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b",
				"refs/heads/new/name", "refs/heads/a");
	}

	@Test
	public void testUpdateChecksOldValue() throws Exception {
		ObjectId cur = db.resolve("master");
		ObjectId prev = db.resolve("master^");
		RefUpdate u1 = db.updateRef("refs/heads/master");
		RefUpdate u2 = db.updateRef("refs/heads/master");

		u1.setExpectedOldObjectId(cur);
		u1.setNewObjectId(prev);
		u1.setForceUpdate(true);

		u2.setExpectedOldObjectId(cur);
		u2.setNewObjectId(prev);
		u2.setForceUpdate(true);

		assertEquals(FORCED, u1.update());
		assertEquals(LOCK_FAILURE, u2.update());
	}

	@Test
	public void testRenameAtomic() throws IOException {
		ObjectId prevId = db.resolve("refs/heads/master^");

		RefRename rename = db.renameRef("refs/heads/master", "refs/heads/newmaster");

		RefUpdate updateRef = db.updateRef("refs/heads/master");
		updateRef.setNewObjectId(prevId);
		updateRef.setForceUpdate(true);
		assertEquals(FORCED, updateRef.update());
		assertEquals(RefUpdate.Result.LOCK_FAILURE, rename.rename());
	}

	@Test
	public void testRenameSymref() throws IOException {
		db.resolve("HEAD");
		RefRename r = db.renameRef("HEAD", "KOPF");
		assertEquals(IO_FAILURE, r.rename());
	}

	@Test
	public void testRenameRefNameColission1avoided() throws IOException {
		// setup
		ObjectId rb = db.resolve("refs/heads/b");
		writeSymref(Constants.HEAD, "refs/heads/a");
		RefUpdate updateRef = db.updateRef("refs/heads/a");
		updateRef.setNewObjectId(rb);
		updateRef.setRefLogMessage("Setup", false);
		assertEquals(Result.FAST_FORWARD, updateRef.update());
		ObjectId oldHead = db.resolve(Constants.HEAD);
		assertEquals(oldHead, rb); // assumption for this test
		writeReflog(db, rb, "Just a message", "refs/heads/a");
		assertTrue("internal check, we have a log", new File(db.getDirectory(),
				"logs/refs/heads/a").exists());

		// Now this is our test
		RefRename renameRef = db.renameRef("refs/heads/a", "refs/heads/a/b");
		Result result = renameRef.rename();
		assertEquals(Result.RENAMED, result);
		assertNull(db.resolve("refs/heads/a"));
		assertEquals(rb, db.resolve("refs/heads/a/b"));
		assertEquals(3, db.getReflogReader("a/b").getReverseEntries().size());
		assertEquals("Branch: renamed a to a/b", db.getReflogReader("a/b")
				.getReverseEntries().get(0).getComment());
		assertEquals("Just a message", db.getReflogReader("a/b")
				.getReverseEntries().get(1).getComment());
		assertEquals("Setup", db.getReflogReader("a/b").getReverseEntries()
				.get(2).getComment());
		// same thing was logged to HEAD
		assertEquals("Branch: renamed a to a/b", db.getReflogReader("HEAD")
				.getReverseEntries().get(0).getComment());
	}

	@Test
	public void testRenameRefNameColission2avoided() throws IOException {
		// setup
		ObjectId rb = db.resolve("refs/heads/b");
		writeSymref(Constants.HEAD, "refs/heads/prefix/a");
		RefUpdate updateRef = db.updateRef("refs/heads/prefix/a");
		updateRef.setNewObjectId(rb);
		updateRef.setRefLogMessage("Setup", false);
		updateRef.setForceUpdate(true);
		assertEquals(Result.FORCED, updateRef.update());
		ObjectId oldHead = db.resolve(Constants.HEAD);
		assertEquals(oldHead, rb); // assumption for this test
		writeReflog(db, 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());

		// Now this is our test
		RefRename renameRef = db.renameRef("refs/heads/prefix/a",
				"refs/heads/prefix");
		Result result = renameRef.rename();
		assertEquals(Result.RENAMED, result);

		assertNull(db.resolve("refs/heads/prefix/a"));
		assertEquals(rb, db.resolve("refs/heads/prefix"));
		assertEquals(3, db.getReflogReader("prefix").getReverseEntries().size());
		assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
				"prefix").getReverseEntries().get(0).getComment());
		assertEquals("Just a message", db.getReflogReader("prefix")
				.getReverseEntries().get(1).getComment());
		assertEquals("Setup", db.getReflogReader("prefix").getReverseEntries()
				.get(2).getComment());
		assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
				"HEAD").getReverseEntries().get(0).getComment());
	}

	@Test
	public void testCreateMissingObject() throws IOException {
		String name = "refs/heads/abc";
		ObjectId bad =
				ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
		RefUpdate ru = db.updateRef(name);
		ru.setNewObjectId(bad);
		Result update = ru.update();
		assertEquals(Result.REJECTED_MISSING_OBJECT, update);

		Ref ref = db.exactRef(name);
		assertNull(ref);
	}

	@Test
	public void testUpdateMissingObject() throws IOException {
		String name = "refs/heads/abc";
		RefUpdate ru = updateRef(name);
		Result update = ru.update();
		assertEquals(Result.NEW, update);
		ObjectId oldId = ru.getNewObjectId();

		ObjectId bad =
				ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
		ru = db.updateRef(name);
		ru.setNewObjectId(bad);
		update = ru.update();
		assertEquals(Result.REJECTED_MISSING_OBJECT, update);

		Ref ref = db.exactRef(name);
		assertNotNull(ref);
		assertEquals(oldId, ref.getObjectId());
	}

	@Test
	public void testForceUpdateMissingObject() throws IOException {
		String name = "refs/heads/abc";
		RefUpdate ru = updateRef(name);
		Result update = ru.update();
		assertEquals(Result.NEW, update);
		ObjectId oldId = ru.getNewObjectId();

		ObjectId bad =
				ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
		ru = db.updateRef(name);
		ru.setNewObjectId(bad);
		update = ru.forceUpdate();
		assertEquals(Result.REJECTED_MISSING_OBJECT, update);

		Ref ref = db.exactRef(name);
		assertNotNull(ref);
		assertEquals(oldId, ref.getObjectId());
	}

	private static void writeReflog(Repository db, ObjectId newId, String msg,
			String refName) throws IOException {
		RefDirectory refs = (RefDirectory) db.getRefDatabase();
		RefDirectoryUpdate update = refs.newUpdate(refName, true);
		update.setNewObjectId(newId);
		refs.log(false, update, msg, true);
	}

	private static class SubclassedId extends ObjectId {
		SubclassedId(AnyObjectId src) {
			super(src);
		}
	}
}
