Add regression test on non-forced fetch and local updates

When a non-forced fetch is executed concurrently with a local
ref update, the operation should fail with a REJECTED reason
if the fetched ref is not a fast-forward update of the locally
updated ref.

This is a preparation work for the fix of fetch concurrency
with local ref-updates, for ensuring the non-regression of existing
not-forced fetch operations.

Bug: jgit-252
Change-Id: I2a9b444a04a06d5a034a940abb0603f50e23c45a
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
index 51e09e0..657a93b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
@@ -26,9 +26,13 @@
 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.storage.pack.PackStatistics;
 import org.eclipse.jgit.transport.BasePackFetchConnection.FetchConfig;
 import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
@@ -258,6 +262,50 @@ public void testReceivePackFactory() throws Exception {
 		}
 	}
 
+	@Test
+	public void nonForcedFetchRejectedWithConcurrentLocalRefUpdate()
+		throws Exception {
+		String localRef = Constants.R_HEADS + "localfoo";
+		String remoteBranchName = "remotefoo";
+		String nonForcedRemoteRef = Constants.R_HEADS + remoteBranchName;
+
+		try (Repository repository = local.getRepository();
+			Git git = new Git(repository);
+			RevWalk revWalk = local.getRevWalk()) {
+			RefSpec nonForcedFetchRefSpec = new RefSpec(
+				nonForcedRemoteRef + ":" + localRef);
+			RevCommit oldLocalCommit = remote.branch(remoteBranchName)
+				.commit().add("base.txt", "base").create();
+
+			git.fetch().setRemote(registerDefaultTestProtocol().toString()).setRefSpecs(nonForcedFetchRefSpec)
+				.call();
+			assertEquals(oldLocalCommit, repository.exactRef(localRef).getObjectId());
+
+			RevCommit updatedLocalCommit = local.commit()
+				.parent(revWalk.parseCommit(oldLocalCommit))
+				.add("local.txt", "new").create();
+			RevCommit remoteCommit = remote.commit().parent(oldLocalCommit)
+				.add("remote.txt", "remote").create();
+			remote.update(remoteBranchName, remoteCommit);
+
+			URIish uri = registerTestProtocolUpdatingRef(localRef, updatedLocalCommit);
+
+			FetchResult result = git.fetch().setRemote(uri.toString())
+				.setRefSpecs(nonForcedFetchRefSpec).call();
+
+			// Fetch has been rejected (non-ff)
+			TrackingRefUpdate update = result.getTrackingRefUpdate(localRef);
+			assertEquals(RefUpdate.Result.REJECTED, update.getResult());
+
+			// Fetched object has been downloaded
+			assertTrue(repository.getObjectDatabase()
+				.has(remoteCommit));
+			// ... but the local ref has not been altered
+			assertEquals(updatedLocalCommit,
+				repository.exactRef(localRef).getObjectId());
+		}
+	}
+
 	private TestProtocol<User> registerDefault() {
 		return registerProto(new DefaultUpload(), new DefaultReceive());
 	}
@@ -269,4 +317,22 @@ private TestProtocol<User> registerProto(UploadPackFactory<User> upf,
 		Transport.register(proto);
 		return proto;
 	}
+
+	private <T extends AnyObjectId> URIish registerTestProtocolUpdatingRef(String refName, T objectId) {
+		TestProtocol<User> proto = registerProto((User req, Repository db) -> {
+			try {
+				local.update(refName, objectId);
+			} catch (Exception e) {
+				throw new AssertionError("Cannot update local ref " + refName,
+						e);
+			}
+			return new UploadPack(db);
+		}, new DefaultReceive());
+		return proto.register(new User("user"), remote.getRepository());
+	}
+
+	private URIish registerDefaultTestProtocol() {
+		TestProtocol<User> setupProto = registerDefault();
+		return setupProto.register(new User("user"), remote.getRepository());
+	}
 }