From 234933ef14ca0ff2175235bc42bf1aa40040aa5d Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 27 Sep 2013 21:31:17 -0400
Subject: [PATCH] Merge receive processing into one class

---
 src/main/java/com/gitblit/git/GitblitReceivePackFactory.java |   57 +++---
 src/main/java/com/gitblit/git/GitblitReceivePack.java        |  244 +++++++++++++++++++++--------
 src/main/java/com/gitblit/git/SideBandProgressMonitor.java   |  127 +++++++++++++++
 src/main/java/com/gitblit/git/GitblitUploadPackFactory.java  |    6 
 src/test/config/test-gitblit.properties                      |    3 
 5 files changed, 334 insertions(+), 103 deletions(-)

diff --git a/src/main/java/com/gitblit/git/ReceiveHook.java b/src/main/java/com/gitblit/git/GitblitReceivePack.java
similarity index 61%
rename from src/main/java/com/gitblit/git/ReceiveHook.java
rename to src/main/java/com/gitblit/git/GitblitReceivePack.java
index d75a238..2d648bd 100644
--- a/src/main/java/com/gitblit/git/ReceiveHook.java
+++ b/src/main/java/com/gitblit/git/GitblitReceivePack.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 gitblit.com.
+ * Copyright 2013 gitblit.com.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
  */
 package com.gitblit.git;
 
+import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_SIDE_BAND_64K;
 import groovy.lang.Binding;
 import groovy.util.GroovyScriptEngine;
 
@@ -25,8 +26,13 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.transport.PostReceiveHook;
 import org.eclipse.jgit.transport.PreReceiveHook;
@@ -50,40 +56,60 @@
 import com.gitblit.utils.RefLogUtils;
 import com.gitblit.utils.StringUtils;
 
+
 /**
- * The Gitblit receive hook allows for special processing on push events.
- * That might include rejecting writes to specific branches or executing a
- * script.
- * 
+ * GitblitReceivePack processes receive commands.  It also executes Groovy pre-
+ * and post- receive hooks.
+ *
+ * The general execution flow is:
+ * <ol>
+ *    <li>onPreReceive()</li>
+ *    <li>executeCommands()</li>
+ *    <li>onPostReceive()</li>
+ * </ol>
+ *
+ * @author Android Open Source Project
  * @author James Moger
- * 
+ *
  */
-public class ReceiveHook implements PreReceiveHook, PostReceiveHook {
+public class GitblitReceivePack extends ReceivePack implements PreReceiveHook, PostReceiveHook {
 
-	protected final Logger logger = LoggerFactory.getLogger(ReceiveHook.class);
+	private static final Logger LOGGER = LoggerFactory.getLogger(GitblitReceivePack.class);
 
-	protected UserModel user;
-	
-	protected RepositoryModel repository;
+	protected final RepositoryModel repository;
+
+	protected final UserModel user;
+
+	protected final File groovyDir;
 
 	protected String gitblitUrl;
 
-	private GroovyScriptEngine gse;
+	protected String repositoryUrl;
 
-	private File groovyDir;
+	protected GroovyScriptEngine gse;
 
-	public ReceiveHook() {
-		groovyDir = GitBlit.getGroovyScriptsFolder();
+	public GitblitReceivePack(Repository db, RepositoryModel repository, UserModel user) {
+		super(db);
+		this.repository = repository;
+		this.user = user == null ? UserModel.ANONYMOUS : user;
+		this.groovyDir = GitBlit.getGroovyScriptsFolder();
 		try {
 			// set Grape root
 			File grapeRoot = GitBlit.getFileOrFolder(Keys.groovy.grapeFolder, "${baseFolder}/groovy/grape").getAbsoluteFile();
 			grapeRoot.mkdirs();
 			System.setProperty("grape.root", grapeRoot.getAbsolutePath());
-
-			gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());			
+			this.gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());
 		} catch (IOException e) {
-			//throw new ServletException("Failed to instantiate Groovy Script Engine!", e);
 		}
+
+		// set advanced ref permissions
+		setAllowCreates(user.canCreateRef(repository));
+		setAllowDeletes(user.canDeleteRef(repository));
+		setAllowNonFastForwards(user.canRewindRef(repository));
+		
+		// setup pre and post receive hook
+		setPreReceiveHook(this);
+		setPostReceiveHook(this);
 	}
 
 	/**
@@ -93,32 +119,27 @@
 	 */
 	@Override
 	public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
+
 		if (repository.isFrozen) {
 			// repository is frozen/readonly
-			String reason = MessageFormat.format("Gitblit does not allow pushes to \"{0}\" because it is frozen!", repository.name);
-			logger.warn(reason);
 			for (ReceiveCommand cmd : commands) {
-				cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
+				sendRejection(cmd, "Gitblit does not allow pushes to \"{0}\" because it is frozen!", repository.name);
 			}
 			return;
 		}
-		
+
 		if (!repository.isBare) {
 			// repository has a working copy
-			String reason = MessageFormat.format("Gitblit does not allow pushes to \"{0}\" because it has a working copy!", repository.name);
-			logger.warn(reason);
 			for (ReceiveCommand cmd : commands) {
-				cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
+				sendRejection(cmd, "Gitblit does not allow pushes to \"{0}\" because it has a working copy!", repository.name);
 			}
 			return;
 		}
 
 		if (!user.canPush(repository)) {
 			// user does not have push permissions
-			String reason = MessageFormat.format("User \"{0}\" does not have push permissions for \"{1}\"!", user.username, repository.name);
-			logger.warn(reason);
 			for (ReceiveCommand cmd : commands) {
-				cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
+				sendRejection(cmd, "User \"{0}\" does not have push permissions for \"{1}\"!", user.username, repository.name);
 			}
 			return;
 		}
@@ -126,51 +147,51 @@
 		if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH) && repository.verifyCommitter) {
 			// enforce committer verification
 			if (StringUtils.isEmpty(user.emailAddress)) {
-				// emit warning if user does not have an email address 
-				logger.warn(MessageFormat.format("Consider setting an email address for {0} ({1}) to improve committer verification.", user.getDisplayName(), user.username));
+				// emit warning if user does not have an email address
+				LOGGER.warn(MessageFormat.format("Consider setting an email address for {0} ({1}) to improve committer verification.", user.getDisplayName(), user.username));
 			}
 
-			// Optionally enforce that the committer of the left parent chain
+			// Optionally enforce that the committer of first parent chain
 			// match the account being used to push the commits.
-			// 
+			//
 			// This requires all merge commits are executed with the "--no-ff"
 			// option to force a merge commit even if fast-forward is possible.
-			// This ensures that the chain of left parents has the commit
+			// This ensures that the chain first parents has the commit
 			// identity of the merging user.
 			boolean allRejected = false;
 			for (ReceiveCommand cmd : commands) {
-				String linearParent = null;
+				String firstParent = null;
 				try {
 					List<RevCommit> commits = JGitUtils.getRevLog(rp.getRepository(), cmd.getOldId().name(), cmd.getNewId().name());
 					for (RevCommit commit : commits) {
-						
-						if (linearParent != null) {
-		            		if (!commit.getName().equals(linearParent)) {
+
+						if (firstParent != null) {
+		            		if (!commit.getName().equals(firstParent)) {
 		            			// ignore: commit is right-descendant of a merge
 		            			continue;
 		            		}
 		            	}
-						
+
 						// update expected next commit id
 						if (commit.getParentCount() == 0) {
-		                	linearParent = null;
+		                	firstParent = null;
 						} else {
-							linearParent = commit.getParents()[0].getId().getName();
+							firstParent = commit.getParents()[0].getId().getName();
 						}
-						
+
 						PersonIdent committer = commit.getCommitterIdent();
 						if (!user.is(committer.getName(), committer.getEmailAddress())) {
 							String reason;
 							if (StringUtils.isEmpty(user.emailAddress)) {
 								// account does not have an email address
-								reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4})", 
+								reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4})",
 										commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username);
 							} else {
 								// account has an email address
-								reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4}) <{5}>", 
+								reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4}) <{5}>",
 										commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username, user.emailAddress);
 							}
-							logger.warn(reason);
+							LOGGER.warn(reason);
 							cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
 							allRejected &= true;
 							break;
@@ -179,7 +200,7 @@
 						}
 					}
 				} catch (Exception e) {
-					logger.error("Failed to verify commits were made by pushing user", e);
+					LOGGER.error("Failed to verify commits were made by pushing user", e);
 				}
 			}
 
@@ -188,7 +209,7 @@
 				return;
 			}
 		}
-		
+
 		// reset branch commit cache on REWIND and DELETE
 		for (ReceiveCommand cmd : commands) {
 			String ref = cmd.getRefName();
@@ -209,10 +230,10 @@
 		if (!ArrayUtils.isEmpty(repository.preReceiveScripts)) {
 			scripts.addAll(repository.preReceiveScripts);
 		}
-		runGroovy(repository, user, commands, rp, scripts);
+		runGroovy(commands, scripts);
 		for (ReceiveCommand cmd : commands) {
 			if (!Result.NOT_ATTEMPTED.equals(cmd.getResult())) {
-				logger.warn(MessageFormat.format("{0} {1} because \"{2}\"", cmd.getNewId()
+				LOGGER.warn(MessageFormat.format("{0} {1} because \"{2}\"", cmd.getNewId()
 						.getName(), cmd.getResult(), cmd.getMessage()));
 			}
 		}
@@ -226,26 +247,27 @@
 	@Override
 	public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
 		if (commands.size() == 0) {
-			logger.debug("skipping post-receive hooks, no refs created, updated, or removed");
+			LOGGER.debug("skipping post-receive hooks, no refs created, updated, or removed");
 			return;
 		}
 
 		// log ref changes
 		for (ReceiveCommand cmd : commands) {
+
 			if (Result.OK.equals(cmd.getResult())) {
 				// add some logging for important ref changes
 				switch (cmd.getType()) {
 				case DELETE:
-					logger.info(MessageFormat.format("{0} DELETED {1} in {2} ({3})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name()));
+					LOGGER.info(MessageFormat.format("{0} DELETED {1} in {2} ({3})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name()));
 					break;
 				case CREATE:
-					logger.info(MessageFormat.format("{0} CREATED {1} in {2}", user.username, cmd.getRefName(), repository.name));
+					LOGGER.info(MessageFormat.format("{0} CREATED {1} in {2}", user.username, cmd.getRefName(), repository.name));
 					break;
 				case UPDATE:
-					logger.info(MessageFormat.format("{0} UPDATED {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
+					LOGGER.info(MessageFormat.format("{0} UPDATED {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
 					break;
 				case UPDATE_NONFASTFORWARD:
-					logger.info(MessageFormat.format("{0} UPDATED NON-FAST-FORWARD {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
+					LOGGER.info(MessageFormat.format("{0} UPDATED NON-FAST-FORWARD {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
 					break;
 				default:
 					break;
@@ -259,7 +281,7 @@
 			PersonIdent userIdent = new PersonIdent(user.getDisplayName(), emailAddress);
 
 			for (ReceiveCommand cmd : commands) {
-				if (!cmd.getRefName().startsWith("refs/heads/")) {
+				if (!cmd.getRefName().startsWith(Constants.R_HEADS)) {
 					// only tag branch ref changes
 					continue;
 				}
@@ -267,7 +289,7 @@
 				if (!ReceiveCommand.Type.DELETE.equals(cmd.getType())
 						&& ReceiveCommand.Result.OK.equals(cmd.getResult())) {
 					String objectId = cmd.getNewId().getName();
-					String branch = cmd.getRefName().substring("refs/heads/".length());
+					String branch = cmd.getRefName().substring(Constants.R_HEADS.length());
 					// get translation based on the server's locale setting
 					String template = Translation.get("gb.incrementalPushTagMessage");
 					String msg = MessageFormat.format(template, branch);
@@ -286,36 +308,120 @@
 							"0",
 							msg);
 				}
-			}				
+			}
 		}
 
 		// update push log
 		try {
 			RefLogUtils.updateRefLog(user, rp.getRepository(), commands);
-			logger.debug(MessageFormat.format("{0} push log updated", repository.name));
+			LOGGER.debug(MessageFormat.format("{0} push log updated", repository.name));
 		} catch (Exception e) {
-			logger.error(MessageFormat.format("Failed to update {0} pushlog", repository.name), e);
+			LOGGER.error(MessageFormat.format("Failed to update {0} pushlog", repository.name), e);
 		}
 
-		// run Groovy hook scripts 
+		// run Groovy hook scripts
 		Set<String> scripts = new LinkedHashSet<String>();
 		scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository));
 		if (!ArrayUtils.isEmpty(repository.postReceiveScripts)) {
 			scripts.addAll(repository.postReceiveScripts);
 		}
-		runGroovy(repository, user, commands, rp, scripts);
+		runGroovy(commands, scripts);
+	}
+
+	/** Execute commands to update references. */
+	@Override
+	protected void executeCommands() {
+		List<ReceiveCommand> toApply = filterCommands(Result.NOT_ATTEMPTED);
+		if (toApply.isEmpty()) {
+			return;
+		}
+
+		ProgressMonitor updating = NullProgressMonitor.INSTANCE;
+		boolean sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K);
+		if (sideBand) {
+			SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut);
+			pm.setDelayStart(250, TimeUnit.MILLISECONDS);
+			updating = pm;
+		}
+
+		BatchRefUpdate batch = getRepository().getRefDatabase().newBatchUpdate();
+		batch.setAllowNonFastForwards(isAllowNonFastForwards());
+		batch.setRefLogIdent(getRefLogIdent());
+		batch.setRefLogMessage("push", true);
+
+		for (ReceiveCommand cmd : toApply) {
+			if (Result.NOT_ATTEMPTED != cmd.getResult()) {
+				// Already rejected by the core receive process.
+				continue;
+			}
+			batch.addCommand(cmd);
+		}
+
+		if (!batch.getCommands().isEmpty()) {
+			try {
+				batch.execute(getRevWalk(), updating);
+			} catch (IOException err) {
+				for (ReceiveCommand cmd : toApply) {
+					if (cmd.getResult() == Result.NOT_ATTEMPTED) {
+						sendRejection(cmd, "lock error: {0}", err.getMessage());
+					}
+				}
+			}
+		}
+	}
+
+	protected void setGitblitUrl(String url) {
+		this.gitblitUrl = url;
+	}
+
+	protected void setRepositoryUrl(String url) {
+		this.repositoryUrl = url;
+	}
+
+	protected void sendRejection(final ReceiveCommand cmd, final String why, Object... objects) {
+		String text;
+		if (ArrayUtils.isEmpty(objects)) {
+			text = why;
+		} else {
+			text = MessageFormat.format(why, objects);
+		}
+		cmd.setResult(Result.REJECTED_OTHER_REASON, text);
+		LOGGER.error(text + " (" + user.username + ")");
+	}
+
+	protected void sendMessage(String msg, Object... objects) {
+		String text;
+		if (ArrayUtils.isEmpty(objects)) {
+			text = msg;
+			super.sendMessage(msg);
+		} else {
+			text = MessageFormat.format(msg, objects);
+			super.sendMessage(text);
+		}
+		LOGGER.info(text + " (" + user.username + ")");
+	}
+
+	protected void sendError(String msg, Object... objects) {
+		String text;
+		if (ArrayUtils.isEmpty(objects)) {
+			text = msg;
+			super.sendError(msg);
+		} else {
+			text = MessageFormat.format(msg, objects);
+			super.sendError(text);
+		}
+		LOGGER.error(text + " (" + user.username + ")");
 	}
 
 	/**
 	 * Runs the specified Groovy hook scripts.
-	 * 
+	 *
 	 * @param repository
 	 * @param user
 	 * @param commands
 	 * @param scripts
 	 */
-	protected void runGroovy(RepositoryModel repository, UserModel user,
-			Collection<ReceiveCommand> commands, ReceivePack rp, Set<String> scripts) {
+	protected void runGroovy(Collection<ReceiveCommand> commands, Set<String> scripts) {
 		if (scripts == null || scripts.size() == 0) {
 			// no Groovy scripts to execute
 			return;
@@ -324,12 +430,12 @@
 		Binding binding = new Binding();
 		binding.setVariable("gitblit", GitBlit.self());
 		binding.setVariable("repository", repository);
-		binding.setVariable("receivePack", rp);
+		binding.setVariable("receivePack", this);
 		binding.setVariable("user", user);
 		binding.setVariable("commands", commands);
 		binding.setVariable("url", gitblitUrl);
-		binding.setVariable("logger", logger);
-		binding.setVariable("clientLogger", new ClientLogger(rp));
+		binding.setVariable("logger", LOGGER);
+		binding.setVariable("clientLogger", new ClientLogger(this));
 		for (String script : scripts) {
 			if (StringUtils.isEmpty(script)) {
 				continue;
@@ -347,15 +453,15 @@
 				Object result = gse.run(script, binding);
 				if (result instanceof Boolean) {
 					if (!((Boolean) result)) {
-						logger.error(MessageFormat.format(
+						LOGGER.error(MessageFormat.format(
 								"Groovy script {0} has failed!  Hook scripts aborted.", script));
 						break;
 					}
 				}
 			} catch (Exception e) {
-				logger.error(
+				LOGGER.error(
 						MessageFormat.format("Failed to execute Groovy script {0}", script), e);
 			}
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java b/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java
index 77a3df6..b9eb8a6 100644
--- a/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java
+++ b/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java
@@ -30,11 +30,11 @@
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.StringUtils;
 
 /**
- * The receive pack factory creates a receive pack which accepts pushes from
- * clients.
- * 
+ * The receive pack factory creates the receive pack which processes pushes.
+ *
  * @author James Moger
  *
  * @param <X> the connection type
@@ -42,62 +42,59 @@
 public class GitblitReceivePackFactory<X> implements ReceivePackFactory<X> {
 
 	protected final Logger logger = LoggerFactory.getLogger(GitblitReceivePackFactory.class);
-	
+
 	@Override
 	public ReceivePack create(X req, Repository db)
 			throws ServiceNotEnabledException, ServiceNotAuthorizedException {
 
-		final ReceivePack rp = new ReceivePack(db);
 		UserModel user = UserModel.ANONYMOUS;
 		String repositoryName = "";
 		String origin = "";
 		String gitblitUrl = "";
+		String repositoryUrl = "";
 		int timeout = 0;
-		
+
 		if (req instanceof HttpServletRequest) {
-			// http/https request may or may not be authenticated 
+			// http/https request may or may not be authenticated
 			HttpServletRequest request = (HttpServletRequest) req;
 			repositoryName = request.getAttribute("gitblitRepositoryName").toString();
 			origin = request.getRemoteHost();
 			gitblitUrl = HttpUtils.getGitblitURL(request);
+			repositoryUrl = request.getRequestURI();
 
 			// determine pushing user
 			String username = request.getRemoteUser();
-			if (username != null && !"".equals(username)) {
-				user = GitBlit.self().getUserModel(username);
-				if (user == null) {
-					// anonymous push, create a temporary usermodel
-					user = new UserModel(username);
+			if (!StringUtils.isEmpty(username)) {
+				UserModel u = GitBlit.self().getUserModel(username);
+				if (u != null) {
+					user = u;
 				}
 			}
 		} else if (req instanceof GitDaemonClient) {
-			// git daemon request is alway anonymous
+			// git daemon request is always anonymous
 			GitDaemonClient client = (GitDaemonClient) req;
 			repositoryName = client.getRepositoryName();
 			origin = client.getRemoteAddress().getHostAddress();
+
 			// set timeout from Git daemon
 			timeout = client.getDaemon().getTimeout();
 		}
 
-		// set pushing user identity for reflog
+		// TODO make this a setting
+		boolean allowAnonymousPushes = true;
+		if (!allowAnonymousPushes && UserModel.ANONYMOUS.equals(user)) {
+			// prohibit anonymous pushes
+			throw new ServiceNotEnabledException();
+		}
+
+		final RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
+
+		final GitblitReceivePack rp = new GitblitReceivePack(db, repository, user);
+		rp.setGitblitUrl(gitblitUrl);
+		rp.setRepositoryUrl(repositoryUrl);
 		rp.setRefLogIdent(new PersonIdent(user.username, user.username + "@" + origin));
 		rp.setTimeout(timeout);
-		
-		// set advanced ref permissions
-		RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
-		rp.setAllowCreates(user.canCreateRef(repository));
-		rp.setAllowDeletes(user.canDeleteRef(repository));
-		rp.setAllowNonFastForwards(user.canRewindRef(repository));
-
-		// setup the receive hook
-		ReceiveHook hook = new ReceiveHook();
-		hook.user = user;
-		hook.repository = repository;
-		hook.gitblitUrl = gitblitUrl;
-
-		rp.setPreReceiveHook(hook);
-		rp.setPostReceiveHook(hook);
 
 		return rp;
 	}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java b/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java
index 1756ac5..6c06fa3 100644
--- a/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java
+++ b/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java
@@ -16,7 +16,6 @@
 package com.gitblit.git;
 
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -30,6 +29,7 @@
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.eclipse.jgit.transport.resolver.UploadPackFactory;
 
+import com.gitblit.Constants;
 import com.gitblit.GitBlit;
 import com.gitblit.models.UserModel;
 
@@ -94,7 +94,7 @@
 			// JGit's RefMap is custom and does not support iterator removal :(
 			List<String> toRemove = new ArrayList<String>();
 			for (String ref : refs.keySet()) {
-				if (ref.startsWith("refs/gitblit/")) {
+				if (ref.startsWith(Constants.R_GITBLIT)) {
 					toRemove.add(ref);
 				}
 			}
@@ -104,4 +104,4 @@
 			return refs;
 		}
 	}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/git/SideBandProgressMonitor.java b/src/main/java/com/gitblit/git/SideBandProgressMonitor.java
new file mode 100644
index 0000000..0322f18
--- /dev/null
+++ b/src/main/java/com/gitblit/git/SideBandProgressMonitor.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2008-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.gitblit.git;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.lib.BatchingProgressMonitor;
+import org.eclipse.jgit.lib.Constants;
+
+/** Write progress messages out to the sideband channel. */
+class SideBandProgressMonitor extends BatchingProgressMonitor {
+	private final OutputStream out;
+
+	private boolean write;
+
+	SideBandProgressMonitor(final OutputStream os) {
+		out = os;
+		write = true;
+	}
+
+	@Override
+	protected void onUpdate(String taskName, int workCurr) {
+		StringBuilder s = new StringBuilder();
+		format(s, taskName, workCurr);
+		s.append("   \r"); //$NON-NLS-1$
+		send(s);
+	}
+
+	@Override
+	protected void onEndTask(String taskName, int workCurr) {
+		StringBuilder s = new StringBuilder();
+		format(s, taskName, workCurr);
+		s.append(", done\n"); //$NON-NLS-1$
+		send(s);
+	}
+
+	private void format(StringBuilder s, String taskName, int workCurr) {
+		s.append(taskName);
+		s.append(": "); //$NON-NLS-1$
+		s.append(workCurr);
+	}
+
+	@Override
+	protected void onUpdate(String taskName, int cmp, int totalWork, int pcnt) {
+		StringBuilder s = new StringBuilder();
+		format(s, taskName, cmp, totalWork, pcnt);
+		s.append("   \r"); //$NON-NLS-1$
+		send(s);
+	}
+
+	@Override
+	protected void onEndTask(String taskName, int cmp, int totalWork, int pcnt) {
+		StringBuilder s = new StringBuilder();
+		format(s, taskName, cmp, totalWork, pcnt);
+		s.append("\n"); //$NON-NLS-1$
+		send(s);
+	}
+
+	private void format(StringBuilder s, String taskName, int cmp,
+			int totalWork, int pcnt) {
+		s.append(taskName);
+		s.append(": "); //$NON-NLS-1$
+		if (pcnt < 100)
+			s.append(' ');
+		if (pcnt < 10)
+			s.append(' ');
+		s.append(pcnt);
+		s.append("% ("); //$NON-NLS-1$
+		s.append(cmp);
+		s.append("/"); //$NON-NLS-1$
+		s.append(totalWork);
+		s.append(")"); //$NON-NLS-1$
+	}
+
+	private void send(StringBuilder s) {
+		if (write) {
+			try {
+				out.write(Constants.encode(s.toString()));
+				out.flush();
+			} catch (IOException err) {
+				write = false;
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/test/config/test-gitblit.properties b/src/test/config/test-gitblit.properties
index 7876407..0d797b4 100644
--- a/src/test/config/test-gitblit.properties
+++ b/src/test/config/test-gitblit.properties
@@ -1,7 +1,8 @@
 #
 # Gitblit Unit Testing properties
 #
-
+git.allowAnonymousPushes = true
+git.defaultAccessRestriction = NONE
 git.repositoriesFolder = ${baseFolder}/git
 git.searchRepositoriesSubfolders = true
 git.enableGitServlet = true

--
Gitblit v1.9.1