From 9cc56a1f60eff2ce1db40b7078eab92e78602e1c Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Sun, 13 Jan 2013 16:49:37 -0500
Subject: [PATCH] Fix submodule links on commit, comitdiff, and tree page (issue-178)

---
 tests/com/gitblit/tests/GitServletTest.java |  705 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 669 insertions(+), 36 deletions(-)

diff --git a/tests/com/gitblit/tests/GitServletTest.java b/tests/com/gitblit/tests/GitServletTest.java
index a4c5e88..771c4b9 100644
--- a/tests/com/gitblit/tests/GitServletTest.java
+++ b/tests/com/gitblit/tests/GitServletTest.java
@@ -1,33 +1,57 @@
 package com.gitblit.tests;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.text.MessageFormat;
 import java.util.Date;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
 import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
 import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.PushLogEntry;
 import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.PushLogUtils;
 
 public class GitServletTest {
 
-	File folder = new File(GitBlitSuite.REPOSITORIES, "working/ticgit");
+	static File ticgitFolder = new File(GitBlitSuite.REPOSITORIES, "working/ticgit");
 	
-	File jgitFolder = new File(GitBlitSuite.REPOSITORIES, "working/jgit");
+	static File ticgit2Folder = new File(GitBlitSuite.REPOSITORIES, "working/ticgit2");
+
+	static File jgitFolder = new File(GitBlitSuite.REPOSITORIES, "working/jgit");
+	
+	static File jgit2Folder = new File(GitBlitSuite.REPOSITORIES, "working/jgit2");
 
 	String url = GitBlitSuite.url;
 	String account = GitBlitSuite.account;
@@ -44,21 +68,43 @@
 	public static void stopGitblit() throws Exception {
 		if (started.get()) {
 			GitBlitSuite.stopGitblit();
+			deleteWorkingFolders();
+		}
+	}
+	
+	public static void deleteWorkingFolders() throws Exception {
+		if (ticgitFolder.exists()) {
+			GitBlitSuite.close(ticgitFolder);
+			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE);
+		}
+		if (ticgit2Folder.exists()) {
+			GitBlitSuite.close(ticgit2Folder);
+			FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
+		}
+		if (jgitFolder.exists()) {
+			GitBlitSuite.close(jgitFolder);
+			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE);
+		}
+		if (jgit2Folder.exists()) {
+			GitBlitSuite.close(jgit2Folder);
+			FileUtils.delete(jgit2Folder, FileUtils.RECURSIVE);
 		}
 	}
 
 	@Test
 	public void testClone() throws Exception {
-		if (folder.exists()) {
-			FileUtils.delete(folder, FileUtils.RECURSIVE);
+		GitBlitSuite.close(ticgitFolder);
+		if (ticgitFolder.exists()) {
+			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
 		}
+		
 		CloneCommand clone = Git.cloneRepository();
 		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
-		clone.setDirectory(folder);
+		clone.setDirectory(ticgitFolder);
 		clone.setBare(false);
 		clone.setCloneAllBranches(true);
 		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
-		clone.call();
+		GitBlitSuite.close(clone.call());		
 		assertTrue(true);
 	}
 
@@ -69,20 +115,16 @@
 		model.accessRestriction = AccessRestrictionType.CLONE;
 		GitBlit.self().updateRepositoryModel(model.name, model, false);
 
-		// delete any existing working folder
-		File folder = new File(GitBlitSuite.REPOSITORIES, "working/gitblit");
-		if (folder.exists()) {
-			FileUtils.delete(folder, FileUtils.RECURSIVE);
-		}
+		// delete any existing working folder		
 		boolean cloned = false;
 		try {
 			CloneCommand clone = Git.cloneRepository();
 			clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
-			clone.setDirectory(folder);
+			clone.setDirectory(ticgit2Folder);
 			clone.setBare(false);
 			clone.setCloneAllBranches(true);
 			clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider("bogus", "bogus"));
-			clone.call();
+			GitBlitSuite.close(clone.call());
 			cloned = true;
 		} catch (Exception e) {
 			// swallow the exception which we expect
@@ -94,48 +136,639 @@
 
 		assertFalse("Bogus login cloned a repository?!", cloned);
 	}
+	
+	@Test
+	public void testUnauthorizedLoginClone() throws Exception {
+		// restrict repository access
+		RepositoryModel model = GitBlit.self().getRepositoryModel("ticgit.git");
+		model.accessRestriction = AccessRestrictionType.CLONE;
+		model.authorizationControl = AuthorizationControl.NAMED;
+		UserModel user = new UserModel("james");
+		user.password = "james";
+		GitBlit.self().updateUserModel(user.username, user, true);
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
+		
+		// delete any existing working folder		
+		boolean cloned = false;
+		try {
+			CloneCommand clone = Git.cloneRepository();
+			clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
+			clone.setDirectory(ticgit2Folder);
+			clone.setBare(false);
+			clone.setCloneAllBranches(true);
+			clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(user.username, user.password));
+			GitBlitSuite.close(clone.call());
+			cloned = true;
+		} catch (Exception e) {
+			// swallow the exception which we expect
+		}
+
+		assertFalse("Unauthorized login cloned a repository?!", cloned);
+
+		FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
+		
+		// switch to authenticated
+		model.authorizationControl = AuthorizationControl.AUTHENTICATED;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+		
+		// try clone again
+		cloned = false;
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
+		clone.setDirectory(ticgit2Folder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(user.username, user.password));
+		GitBlitSuite.close(clone.call());
+		cloned = true;
+
+		assertTrue("Authenticated login could not clone!", cloned);
+		
+		FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
+		
+		// restore anonymous repository access
+		model.accessRestriction = AccessRestrictionType.NONE;
+		model.authorizationControl = AuthorizationControl.NAMED;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+		GitBlit.self().deleteUser(user.username);
+	}
 
 	@Test
 	public void testAnonymousPush() throws Exception {
-		Git git = Git.open(folder);
-		File file = new File(folder, "TODO");
-		for (int i = 0; i < 3; i++) {
-			OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true));
-			BufferedWriter w = new BufferedWriter(os);
-			w.write("// " + new Date().toString() + "\n");
-			w.close();
-			git.add().addFilepattern(file.getName()).call();
-			git.commit().setMessage("test commit #" + (i + 1)).call();
+		GitBlitSuite.close(ticgitFolder);
+		if (ticgitFolder.exists()) {
+			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
 		}
+
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
+		clone.setDirectory(ticgitFolder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
+		GitBlitSuite.close(clone.call());		
+		assertTrue(true);
+		
+		Git git = Git.open(ticgitFolder);
+		File file = new File(ticgitFolder, "TODO");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// hellol中文 " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit").call();
 		git.push().setPushAll().call();
-		git.getRepository().close();
+		GitBlitSuite.close(git);
 	}
-	
+
 	@Test
 	public void testSubfolderPush() throws Exception {
+		GitBlitSuite.close(jgitFolder);
 		if (jgitFolder.exists()) {
-			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE);
+			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
 		}
+		
 		CloneCommand clone = Git.cloneRepository();
 		clone.setURI(MessageFormat.format("{0}/git/test/jgit.git", url));
 		clone.setDirectory(jgitFolder);
 		clone.setBare(false);
 		clone.setCloneAllBranches(true);
 		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
-		clone.call();
+		GitBlitSuite.close(clone.call());
 		assertTrue(true);
-		
+
 		Git git = Git.open(jgitFolder);
 		File file = new File(jgitFolder, "TODO");
-		for (int i = 0; i < 3; i++) {
-			OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true));
-			BufferedWriter w = new BufferedWriter(os);
-			w.write("// " + new Date().toString() + "\n");
-			w.close();
-			git.add().addFilepattern(file.getName()).call();
-			git.commit().setMessage("test commit #" + (i + 1)).call();
-		}
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit").call();
 		git.push().setPushAll().call();
-		git.getRepository().close();
+		GitBlitSuite.close(git);
+	}
+	
+	@Test
+	public void testPushToFrozenRepo() throws Exception {
+		GitBlitSuite.close(jgitFolder);
+		if (jgitFolder.exists()) {
+			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+		}
+		
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/git/test/jgit.git", url));
+		clone.setDirectory(jgitFolder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
+		GitBlitSuite.close(clone.call());
+		assertTrue(true);
+		
+		// freeze repo
+		RepositoryModel model = GitBlit.self().getRepositoryModel("test/jgit.git");
+		model.isFrozen = true;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		Git git = Git.open(jgitFolder);
+		File file = new File(jgitFolder, "TODO");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit").call();
+		
+		try {
+			git.push().setPushAll().call();
+			assertTrue(false);
+		} catch (Exception e) {
+			assertTrue(e.getCause().getMessage().contains("access forbidden"));
+		}
+		
+		// unfreeze repo
+		model.isFrozen = false;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		git.push().setPushAll().call();
+		GitBlitSuite.close(git);
+	}
+	
+	@Test
+	public void testPushToNonBareRepository() throws Exception {
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/git/working/jgit", url));
+		clone.setDirectory(jgit2Folder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
+		GitBlitSuite.close(clone.call());
+		assertTrue(true);
+
+		Git git = Git.open(jgit2Folder);
+		File file = new File(jgit2Folder, "NONBARE");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit followed by push to non-bare repository").call();
+		try {
+			git.push().setPushAll().call();
+			assertTrue(false);
+		} catch (Exception e) {
+			assertTrue(e.getCause().getMessage().contains("git-receive-pack not permitted"));
+		}
+		GitBlitSuite.close(git);
+	}
+
+	@Test
+	public void testCommitterVerification() throws Exception {
+		UserModel user = new UserModel("james");
+		user.password = "james";
+
+		// account only uses account name to verify
+		testCommitterVerification(user, user.username, null, true);
+		// committer email address is ignored because account does not specify email
+		testCommitterVerification(user, user.username, "something", true);
+		// completely different committer
+		testCommitterVerification(user, "joe", null, false);
+
+		// test display name verification
+		user.displayName = "James Moger";
+		testCommitterVerification(user, user.displayName, null, true);
+		testCommitterVerification(user, user.displayName, "something", true);
+		testCommitterVerification(user, "joe", null, false);
+		
+		// test email address verification
+		user.emailAddress = "something";
+		testCommitterVerification(user, user.displayName, null, false);
+		testCommitterVerification(user, user.displayName, "somethingelse", false);
+		testCommitterVerification(user, user.displayName, user.emailAddress, true);
+		
+		// use same email address but with different committer
+		testCommitterVerification(user, "joe", "somethingelse", false);
+	}
+	
+	private void testCommitterVerification(UserModel user, String displayName, String emailAddress, boolean expectedSuccess) throws Exception {
+		
+		if (GitBlit.self().getUserModel(user.username) != null) {
+			GitBlit.self().deleteUser(user.username);
+		}
+		
+		CredentialsProvider cp = new UsernamePasswordCredentialsProvider(user.username, user.password);
+		
+		// fork from original to a temporary bare repo
+		File verification = new File(GitBlitSuite.REPOSITORIES, "refchecks/verify-committer.git");
+		if (verification.exists()) {
+			FileUtils.delete(verification, FileUtils.RECURSIVE);
+		}
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
+		clone.setDirectory(verification);
+		clone.setBare(true);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		GitBlitSuite.close(clone.call());
+		
+		// require push permissions and committer verification
+		RepositoryModel model = GitBlit.self().getRepositoryModel("refchecks/verify-committer.git");
+		model.authorizationControl = AuthorizationControl.NAMED;
+		model.accessRestriction = AccessRestrictionType.PUSH;
+		model.verifyCommitter = true;
+		
+		// grant user push permission
+		user.setRepositoryPermission(model.name, AccessPermission.PUSH);
+		
+		GitBlit.self().updateUserModel(user.username, user, true);
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		// clone temp bare repo to working copy
+		File local = new File(GitBlitSuite.REPOSITORIES, "refchecks/verify-wc");
+		if (local.exists()) {
+			FileUtils.delete(local, FileUtils.RECURSIVE);
+		}
+		clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/git/{1}", url, model.name));
+		clone.setDirectory(local);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		GitBlitSuite.close(clone.call());
+		
+		Git git = Git.open(local);
+		
+		// force an identity which may or may not match the account's identity
+		git.getRepository().getConfig().setString("user", null, "name", displayName);
+		git.getRepository().getConfig().setString("user", null, "email", emailAddress);
+		git.getRepository().getConfig().save();
+		
+		// commit a file and push it
+		File file = new File(local, "PUSHCHK");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("push test").call();
+		Iterable<PushResult> results = git.push().setCredentialsProvider(cp).setRemote("origin").call();
+		
+		for (PushResult result : results) {
+			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+			Status status = ref.getStatus();
+			if (expectedSuccess) {
+				assertTrue("Verification failed! User was NOT able to push commit! " + status.name(), Status.OK.equals(status));
+			} else {
+				assertTrue("Verification failed! User was able to push commit! " + status.name(), Status.REJECTED_OTHER_REASON.equals(status));
+			}
+		}
+		
+		GitBlitSuite.close(git);
+		// close serving repository
+		GitBlitSuite.close(verification);
+	}
+
+	@Test
+	public void testBlockClone() throws Exception {
+		testRefChange(AccessPermission.VIEW, null, null, null);
+	}
+
+	@Test
+	public void testBlockPush() throws Exception {
+		testRefChange(AccessPermission.CLONE, null, null, null);
+	}
+
+	@Test
+	public void testBlockBranchCreation() throws Exception {
+		testRefChange(AccessPermission.PUSH, Status.REJECTED_OTHER_REASON, null, null);
+	}
+
+	@Test
+	public void testBlockBranchDeletion() throws Exception {
+		testRefChange(AccessPermission.CREATE, Status.OK, Status.REJECTED_OTHER_REASON, null);
+	}
+	
+	@Test
+	public void testBlockBranchRewind() throws Exception {
+		testRefChange(AccessPermission.DELETE, Status.OK, Status.OK, Status.REJECTED_OTHER_REASON);
+	}
+
+	@Test
+	public void testBranchRewind() throws Exception {		
+		testRefChange(AccessPermission.REWIND, Status.OK, Status.OK, Status.OK);
+	}
+
+	private void testRefChange(AccessPermission permission, Status expectedCreate, Status expectedDelete, Status expectedRewind) throws Exception {
+
+		UserModel user = new UserModel("james");
+		user.password = "james";
+		
+		if (GitBlit.self().getUserModel(user.username) != null) {
+			GitBlit.self().deleteUser(user.username);
+		}
+		
+		CredentialsProvider cp = new UsernamePasswordCredentialsProvider(user.username, user.password);
+		
+		// fork from original to a temporary bare repo
+		File refChecks = new File(GitBlitSuite.REPOSITORIES, "refchecks/ticgit.git");
+		if (refChecks.exists()) {
+			FileUtils.delete(refChecks, FileUtils.RECURSIVE);
+		}
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
+		clone.setDirectory(refChecks);
+		clone.setBare(true);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		GitBlitSuite.close(clone.call());
+
+		// elevate repository to clone permission
+		RepositoryModel model = GitBlit.self().getRepositoryModel("refchecks/ticgit.git");
+		switch (permission) {
+			case VIEW:
+				model.accessRestriction = AccessRestrictionType.CLONE;
+				break;
+			case CLONE:
+				model.accessRestriction = AccessRestrictionType.CLONE;
+				break;
+			default:
+				model.accessRestriction = AccessRestrictionType.PUSH;
+		}
+		model.authorizationControl = AuthorizationControl.NAMED;
+		
+		// grant user specified
+		user.setRepositoryPermission(model.name, permission);
+
+		GitBlit.self().updateUserModel(user.username, user, true);
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		// clone temp bare repo to working copy
+		File local = new File(GitBlitSuite.REPOSITORIES, "refchecks/ticgit-wc");
+		if (local.exists()) {
+			FileUtils.delete(local, FileUtils.RECURSIVE);
+		}
+		clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/git/{1}", url, model.name));
+		clone.setDirectory(local);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		
+		try {
+			GitBlitSuite.close(clone.call());
+		} catch (GitAPIException e) {
+			if (permission.atLeast(AccessPermission.CLONE)) {
+				throw e;
+			} else {
+				// close serving repository
+				GitBlitSuite.close(refChecks);
+				
+				// user does not have clone permission
+				assertTrue(e.getMessage(), e.getMessage().contains("not permitted"));	
+				return;
+			}
+		}
+		
+		Git git = Git.open(local);
+		
+		// commit a file and push it
+		File file = new File(local, "PUSHCHK");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("push test").call();
+		Iterable<PushResult> results = null;
+		try {
+			results = git.push().setCredentialsProvider(cp).setRemote("origin").call();
+		} catch (GitAPIException e) {
+			if (permission.atLeast(AccessPermission.PUSH)) {
+				throw e;
+			} else {
+				// close serving repository
+				GitBlitSuite.close(refChecks);
+				
+				// user does not have push permission
+				assertTrue(e.getMessage(), e.getMessage().contains("not permitted"));
+				GitBlitSuite.close(git);
+				return;
+			}
+		}
+		
+		for (PushResult result : results) {
+			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+			Status status = ref.getStatus();
+			if (permission.atLeast(AccessPermission.PUSH)) {
+				assertTrue("User failed to push commit?! " + status.name(), Status.OK.equals(status));
+			} else {
+				// close serving repository
+				GitBlitSuite.close(refChecks);
+
+				assertTrue("User was able to push commit! " + status.name(), Status.REJECTED_OTHER_REASON.equals(status));
+				GitBlitSuite.close(git);
+				// skip delete test
+				return;
+			}
+		}
+		
+		// create a local branch and push the new branch back to the origin				
+		git.branchCreate().setName("protectme").call();
+		RefSpec refSpec = new RefSpec("refs/heads/protectme:refs/heads/protectme");
+		results = git.push().setCredentialsProvider(cp).setRefSpecs(refSpec).setRemote("origin").call();
+		for (PushResult result : results) {
+			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/protectme");
+			Status status = ref.getStatus();
+			if (Status.OK.equals(expectedCreate)) {
+				assertTrue("User failed to push creation?! " + status.name(), status.equals(expectedCreate));
+			} else {
+				// close serving repository
+				GitBlitSuite.close(refChecks);
+
+				assertTrue("User was able to push ref creation! " + status.name(), status.equals(expectedCreate));
+				GitBlitSuite.close(git);
+				// skip delete test
+				return;
+			}
+		}
+		
+		// delete the branch locally
+		git.branchDelete().setBranchNames("protectme").call();
+		
+		// push a delete ref command
+		refSpec = new RefSpec(":refs/heads/protectme");
+		results = git.push().setCredentialsProvider(cp).setRefSpecs(refSpec).setRemote("origin").call();
+		for (PushResult result : results) {
+			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/protectme");
+			Status status = ref.getStatus();
+			if (Status.OK.equals(expectedDelete)) {
+				assertTrue("User failed to push ref deletion?! " + status.name(), status.equals(Status.OK));
+			} else {
+				// close serving repository
+				GitBlitSuite.close(refChecks);
+
+				assertTrue("User was able to push ref deletion?! " + status.name(), status.equals(expectedDelete));
+				GitBlitSuite.close(git);
+				// skip rewind test
+				return;
+			}
+		}
+		
+		// rewind master by two commits
+		git.reset().setRef("HEAD~2").setMode(ResetType.HARD).call();
+		
+		// commit a change on this detached HEAD
+		file = new File(local, "REWINDCHK");
+		os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		RevCommit commit = git.commit().setMessage("rewind master and new commit").call();
+		
+		// Reset master to our new commit now we our local branch tip is no longer
+		// upstream of the remote branch tip.  It is an alternate tip of the branch.
+		JGitUtils.setBranchRef(git.getRepository(), "refs/heads/master", commit.getName());
+		
+		// Try pushing our new tip to the origin.
+		// This requires the server to "rewind" it's master branch and update it
+		// to point to our alternate tip.  This leaves the original master tip
+		// unreferenced.
+		results = git.push().setCredentialsProvider(cp).setRemote("origin").setForce(true).call();
+		for (PushResult result : results) {
+			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+			Status status = ref.getStatus();
+			if (Status.OK.equals(expectedRewind)) {
+				assertTrue("User failed to rewind master?! " + status.name(), status.equals(expectedRewind));
+			} else {
+				assertTrue("User was able to rewind master?! " + status.name(), status.equals(expectedRewind));
+			}
+		}
+		GitBlitSuite.close(git);
+		
+		// close serving repository
+		GitBlitSuite.close(refChecks);
+
+		GitBlit.self().deleteUser(user.username);
+	}
+	
+	@Test
+	public void testCreateOnPush() throws Exception {
+		testCreateOnPush(false, false);
+		testCreateOnPush(true, false);
+		testCreateOnPush(false, true);
+	}
+	
+	private void testCreateOnPush(boolean canCreate, boolean canAdmin) throws Exception {
+
+		UserModel user = new UserModel("sampleuser");
+		user.password = user.username;
+		
+		if (GitBlit.self().getUserModel(user.username) != null) {
+			GitBlit.self().deleteUser(user.username);
+		}
+		
+		user.canCreate = canCreate;
+		user.canAdmin = canAdmin;
+		
+		GitBlit.self().updateUserModel(user.username, user, true);
+
+		CredentialsProvider cp = new UsernamePasswordCredentialsProvider(user.username, user.password);
+		
+		// fork from original to a temporary bare repo
+		File tmpFolder = File.createTempFile("gitblit", "").getParentFile();
+		File createCheck = new File(tmpFolder, "ticgit.git");
+		if (createCheck.exists()) {
+			FileUtils.delete(createCheck, FileUtils.RECURSIVE);
+		}
+		
+		File personalRepo = new File(GitBlitSuite.REPOSITORIES, MessageFormat.format("~{0}/ticgit.git", user.username));
+		GitBlitSuite.close(personalRepo);
+		if (personalRepo.exists()) {
+			FileUtils.delete(personalRepo, FileUtils.RECURSIVE);
+		}
+
+		File projectRepo = new File(GitBlitSuite.REPOSITORIES, "project/ticgit.git");
+		GitBlitSuite.close(projectRepo);
+		if (projectRepo.exists()) {
+			FileUtils.delete(projectRepo, FileUtils.RECURSIVE);
+		}
+
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
+		clone.setDirectory(createCheck);
+		clone.setBare(true);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		Git git = clone.call();
+		
+		GitBlitSuite.close(personalRepo);
+		
+		// add a personal repository remote and a project remote
+		git.getRepository().getConfig().setString("remote", "user", "url", MessageFormat.format("{0}/git/~{1}/ticgit.git", url, user.username));
+		git.getRepository().getConfig().setString("remote", "project", "url", MessageFormat.format("{0}/git/project/ticgit.git", url));
+		git.getRepository().getConfig().save();
+
+		// push to non-existent user repository
+		try {
+			Iterable<PushResult> results = git.push().setRemote("user").setPushAll().setCredentialsProvider(cp).call();
+
+			for (PushResult result : results) {
+				RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+				Status status = ref.getStatus();
+				assertTrue("User failed to create repository?! " + status.name(), Status.OK.equals(status));
+			}
+
+			assertTrue("User canAdmin:" + user.canAdmin + " canCreate:" + user.canCreate, user.canAdmin || user.canCreate);
+			
+			// confirm default personal repository permissions
+			RepositoryModel model = GitBlit.self().getRepositoryModel(MessageFormat.format("~{0}/ticgit.git", user.username));
+			assertEquals("Unexpected owner", user.username, model.owner);
+			assertEquals("Unexpected authorization control", AuthorizationControl.NAMED, model.authorizationControl);
+			assertEquals("Unexpected access restriction", AccessRestrictionType.VIEW, model.accessRestriction);
+			
+		} catch (GitAPIException e) {
+			assertTrue(e.getMessage(), e.getMessage().contains("git-receive-pack not found"));
+			assertFalse("User canAdmin:" + user.canAdmin + " canCreate:" + user.canCreate, user.canAdmin || user.canCreate);
+		}
+		
+		// push to non-existent project repository
+		try {
+			Iterable<PushResult> results = git.push().setRemote("project").setPushAll().setCredentialsProvider(cp).call();
+			GitBlitSuite.close(git);
+
+			for (PushResult result : results) {
+				RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+				Status status = ref.getStatus();
+				assertTrue("User failed to create repository?! " + status.name(), Status.OK.equals(status));
+			}
+			
+			assertTrue("User canAdmin:" + user.canAdmin, user.canAdmin);
+			
+			// confirm default project repository permissions
+			RepositoryModel model = GitBlit.self().getRepositoryModel("project/ticgit.git");
+			assertEquals("Unexpected owner", user.username, model.owner);
+			assertEquals("Unexpected authorization control", AuthorizationControl.fromName(GitBlit.getString(Keys.git.defaultAuthorizationControl, "NAMED")), model.authorizationControl);
+			assertEquals("Unexpected access restriction", AccessRestrictionType.fromName(GitBlit.getString(Keys.git.defaultAccessRestriction, "NONE")), model.accessRestriction);
+
+		} catch (GitAPIException e) {
+			assertTrue(e.getMessage(), e.getMessage().contains("git-receive-pack not found"));
+			assertFalse("User canAdmin:" + user.canAdmin, user.canAdmin);
+		}
+
+		GitBlitSuite.close(git);
+		GitBlit.self().deleteUser(user.username);
+	}
+	
+	@Test
+	public void testPushLog() throws IOException {
+		String name = "refchecks/ticgit.git";
+		File refChecks = new File(GitBlitSuite.REPOSITORIES, name);
+		FileRepository repository = new FileRepository(refChecks);
+		List<PushLogEntry> pushes = PushLogUtils.getPushLog(name, repository);
+		GitBlitSuite.close(repository);
+		assertTrue("Repository has an empty push log!", pushes.size() > 0);
 	}
 }

--
Gitblit v1.9.1