From a502d96a860456ec5e8c96761db70f7cabb74751 Mon Sep 17 00:00:00 2001
From: Paul Martin <paul@paulsputer.com>
Date: Sat, 30 Apr 2016 04:19:14 -0400
Subject: [PATCH] Merge pull request #1073 from gitblit/1062-DocEditorUpdates

---
 src/main/java/com/gitblit/git/GitblitReceivePack.java |  293 +++++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 207 insertions(+), 86 deletions(-)

diff --git a/src/main/java/com/gitblit/git/GitblitReceivePack.java b/src/main/java/com/gitblit/git/GitblitReceivePack.java
index 44e2bef..34bbea2 100644
--- a/src/main/java/com/gitblit/git/GitblitReceivePack.java
+++ b/src/main/java/com/gitblit/git/GitblitReceivePack.java
@@ -44,14 +44,14 @@
 
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.GitBlit;
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
 import com.gitblit.client.Translation;
-import com.gitblit.manager.IRepositoryManager;
-import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.extensions.ReceiveHook;
+import com.gitblit.manager.IGitblit;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
+import com.gitblit.tickets.BranchTicketService;
 import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.ClientLogger;
 import com.gitblit.utils.CommitCache;
@@ -87,30 +87,27 @@
 
 	protected String gitblitUrl;
 
-	protected String repositoryUrl;
-
 	protected GroovyScriptEngine gse;
 
-	private final IStoredSettings settings;
+	protected final IStoredSettings settings;
 
-	private final IRepositoryManager repositoryManager;
+	protected final IGitblit gitblit;
 
 	public GitblitReceivePack(
-			IRuntimeManager runtimeManager,
-			IRepositoryManager repositoryManager,
+			IGitblit gitblit,
 			Repository db,
 			RepositoryModel repository,
 			UserModel user) {
 
 		super(db);
-		this.settings = runtimeManager.getSettings();
-		this.repositoryManager = repositoryManager;
+		this.settings = gitblit.getSettings();
+		this.gitblit = gitblit;
 		this.repository = repository;
-		this.user = user == null ? UserModel.ANONYMOUS : user;
-		this.groovyDir = repositoryManager.getHooksFolder();
+		this.user = user;
+		this.groovyDir = gitblit.getHooksFolder();
 		try {
 			// set Grape root
-			File grapeRoot = repositoryManager.getGrapesFolder();
+			File grapeRoot = gitblit.getGrapesFolder();
 			grapeRoot.mkdirs();
 			System.setProperty("grape.root", grapeRoot.getAbsolutePath());
 			this.gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());
@@ -122,9 +119,46 @@
 		setAllowDeletes(user.canDeleteRef(repository));
 		setAllowNonFastForwards(user.canRewindRef(repository));
 
+		int maxObjectSz = settings.getInteger(Keys.git.maxObjectSizeLimit, -1);
+		if (maxObjectSz >= 0) {
+			setMaxObjectSizeLimit(maxObjectSz);
+		}
+		int maxPackSz = settings.getInteger(Keys.git.maxPackSizeLimit, -1);
+		if (maxPackSz >= 0) {
+			setMaxPackSizeLimit(maxPackSz);
+		}
+		setCheckReceivedObjects(settings.getBoolean(Keys.git.checkReceivedObjects, true));
+		setCheckReferencedObjectsAreReachable(settings.getBoolean(Keys.git.checkReferencedObjectsAreReachable, true));
+
 		// setup pre and post receive hook
 		setPreReceiveHook(this);
 		setPostReceiveHook(this);
+	}
+
+	/**
+	 * Returns true if the user is permitted to apply the receive commands to
+	 * the repository.
+	 *
+	 * @param commands
+	 * @return true if the user may push these commands
+	 */
+	protected boolean canPush(Collection<ReceiveCommand> commands) {
+		// TODO Consider supporting branch permissions here (issue-36)
+		// Not sure if that should be Gerrit-style, refs/meta/config, or
+		// gitolite-style, permissions in users.conf
+		//
+		// How could commands be empty?
+		//
+		// Because a subclass, like PatchsetReceivePack, filters receive
+		// commands before this method is called.  This makes it possible for
+		// this method to test an empty list.  In this case, we assume that the
+		// subclass receive pack properly enforces push restrictions. for the
+		// ref.
+		//
+		// The empty test is not explicitly required, it's written here to
+		// clarify special-case behavior.
+
+		return commands.isEmpty() ? true : user.canPush(repository);
 	}
 
 	/**
@@ -134,6 +168,14 @@
 	 */
 	@Override
 	public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
+
+		if (commands.size() == 0) {
+			// no receive commands to process
+			// this can happen if receive pack subclasses intercept and filter
+			// the commands
+			LOGGER.debug("skipping pre-receive processing, no refs created, updated, or removed");
+			return;
+		}
 
 		if (repository.isMirror) {
 			// repository is a mirror
@@ -159,7 +201,7 @@
 			return;
 		}
 
-		if (!user.canPush(repository)) {
+		if (!canPush(commands)) {
 			// user does not have push permissions
 			for (ReceiveCommand cmd : commands) {
 				sendRejection(cmd, "User \"{0}\" does not have push permissions for \"{1}\"!", user.username, repository.name);
@@ -170,8 +212,11 @@
 		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));
+				// reject the push because the pushing account does not have an email address
+				for (ReceiveCommand cmd : commands) {
+					sendRejection(cmd, "Sorry, the account \"{0}\" does not have an email address set for committer verification!", user.username);
+				}
+				return;
 			}
 
 			// Optionally enforce that the committer of first parent chain
@@ -204,16 +249,9 @@
 
 						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})",
-										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}>",
-										commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username, user.emailAddress);
-							}
+							// verification failed
+							String 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);
 							cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
 							allRejected &= true;
@@ -233,23 +271,42 @@
 			}
 		}
 
-		// reset branch commit cache on REWIND and DELETE
 		for (ReceiveCommand cmd : commands) {
 			String ref = cmd.getRefName();
 			if (ref.startsWith(Constants.R_HEADS)) {
 				switch (cmd.getType()) {
 				case UPDATE_NONFASTFORWARD:
 				case DELETE:
+					// reset branch commit cache on REWIND and DELETE
 					CommitCache.instance().clear(repository.name, ref);
 					break;
 				default:
 					break;
 				}
+			} else if (ref.equals(BranchTicketService.BRANCH)) {
+				// ensure pushing user is an administrator OR an owner
+				// i.e. prevent ticket tampering
+				boolean permitted = user.canAdmin() || repository.isOwner(user.username);
+				if (!permitted) {
+					sendRejection(cmd, "{0} is not permitted to push to {1}", user.username, ref);
+				}
+			} else if (ref.startsWith(Constants.R_FOR)) {
+				// prevent accidental push to refs/for
+				sendRejection(cmd, "{0} is not configured to receive patchsets", repository.name);
+			}
+		}
+
+		// call pre-receive plugins
+		for (ReceiveHook hook : gitblit.getExtensions(ReceiveHook.class)) {
+			try {
+				hook.onPreReceive(this, commands);
+			} catch (Exception e) {
+				LOGGER.error("Failed to execute extension", e);
 			}
 		}
 
 		Set<String> scripts = new LinkedHashSet<String>();
-		scripts.addAll(repositoryManager.getPreReceiveScriptsInherited(repository));
+		scripts.addAll(gitblit.getPreReceiveScriptsInherited(repository));
 		if (!ArrayUtils.isEmpty(repository.preReceiveScripts)) {
 			scripts.addAll(repository.preReceiveScripts);
 		}
@@ -270,9 +327,48 @@
 	@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 processing, no refs created, updated, or removed");
 			return;
 		}
+
+		logRefChange(commands);
+		updateIncrementalPushTags(commands);
+		updateGitblitRefLog(commands);
+
+		// check for updates pushed to the BranchTicketService branch
+		// if the BranchTicketService is active it will reindex, as appropriate
+		for (ReceiveCommand cmd : commands) {
+			if (Result.OK.equals(cmd.getResult())
+					&& BranchTicketService.BRANCH.equals(cmd.getRefName())) {
+				rp.getRepository().fireEvent(new ReceiveCommandEvent(repository, cmd));
+			}
+		}
+
+		// call post-receive plugins
+		for (ReceiveHook hook : gitblit.getExtensions(ReceiveHook.class)) {
+			try {
+				hook.onPostReceive(this, commands);
+			} catch (Exception e) {
+				LOGGER.error("Failed to execute extension", e);
+			}
+		}
+
+		// run Groovy hook scripts
+		Set<String> scripts = new LinkedHashSet<String>();
+		scripts.addAll(gitblit.getPostReceiveScriptsInherited(repository));
+		if (!ArrayUtils.isEmpty(repository.postReceiveScripts)) {
+			scripts.addAll(repository.postReceiveScripts);
+		}
+		runGroovy(commands, scripts);
+	}
+
+	/**
+	 * Log the ref changes in the container log.
+	 *
+	 * @param commands
+	 */
+	protected void logRefChange(Collection<ReceiveCommand> commands) {
+		boolean isRefCreationOrDeletion = false;
 
 		// log ref changes
 		for (ReceiveCommand cmd : commands) {
@@ -282,9 +378,11 @@
 				switch (cmd.getType()) {
 				case DELETE:
 					LOGGER.info(MessageFormat.format("{0} DELETED {1} in {2} ({3})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name()));
+					isRefCreationOrDeletion = true;
 					break;
 				case CREATE:
 					LOGGER.info(MessageFormat.format("{0} CREATED {1} in {2}", user.username, cmd.getRefName(), repository.name));
+					isRefCreationOrDeletion = true;
 					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()));
@@ -298,57 +396,68 @@
 			}
 		}
 
-		if (repository.useIncrementalPushTags) {
-			// tag each pushed branch tip
-			String emailAddress = user.emailAddress == null ? rp.getRefLogIdent().getEmailAddress() : user.emailAddress;
-			PersonIdent userIdent = new PersonIdent(user.getDisplayName(), emailAddress);
+		if (isRefCreationOrDeletion) {
+			gitblit.resetRepositoryCache(repository.name);
+		}
+	}
 
-			for (ReceiveCommand cmd : commands) {
-				if (!cmd.getRefName().startsWith(Constants.R_HEADS)) {
-					// only tag branch ref changes
-					continue;
+	/**
+	 * Optionally update the incremental push tags.
+	 *
+	 * @param commands
+	 */
+	protected void updateIncrementalPushTags(Collection<ReceiveCommand> commands) {
+		if (!repository.useIncrementalPushTags) {
+			return;
+		}
+
+		// tag each pushed branch tip
+		String emailAddress = user.emailAddress == null ? getRefLogIdent().getEmailAddress() : user.emailAddress;
+		PersonIdent userIdent = new PersonIdent(user.getDisplayName(), emailAddress);
+
+		for (ReceiveCommand cmd : commands) {
+			if (!cmd.getRefName().startsWith(Constants.R_HEADS)) {
+				// only tag branch ref changes
+				continue;
+			}
+
+			if (!ReceiveCommand.Type.DELETE.equals(cmd.getType())
+					&& ReceiveCommand.Result.OK.equals(cmd.getResult())) {
+				String objectId = cmd.getNewId().getName();
+				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);
+				String prefix;
+				if (StringUtils.isEmpty(repository.incrementalPushTagPrefix)) {
+					prefix = settings.getString(Keys.git.defaultIncrementalPushTagPrefix, "r");
+				} else {
+					prefix = repository.incrementalPushTagPrefix;
 				}
 
-				if (!ReceiveCommand.Type.DELETE.equals(cmd.getType())
-						&& ReceiveCommand.Result.OK.equals(cmd.getResult())) {
-					String objectId = cmd.getNewId().getName();
-					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);
-					String prefix;
-					if (StringUtils.isEmpty(repository.incrementalPushTagPrefix)) {
-						prefix = settings.getString(Keys.git.defaultIncrementalPushTagPrefix, "r");
-					} else {
-						prefix = repository.incrementalPushTagPrefix;
-					}
-
-					JGitUtils.createIncrementalRevisionTag(
-							rp.getRepository(),
-							objectId,
-							userIdent,
-							prefix,
-							"0",
-							msg);
-				}
+				JGitUtils.createIncrementalRevisionTag(
+						getRepository(),
+						objectId,
+						userIdent,
+						prefix,
+						"0",
+						msg);
 			}
 		}
+	}
 
-		// update push log
+	/**
+	 * Update Gitblit's internal reflog.
+	 *
+	 * @param commands
+	 */
+	protected void updateGitblitRefLog(Collection<ReceiveCommand> commands) {
 		try {
-			RefLogUtils.updateRefLog(user, rp.getRepository(), commands);
-			LOGGER.debug(MessageFormat.format("{0} push log updated", repository.name));
+			RefLogUtils.updateRefLog(user, getRepository(), commands);
+			LOGGER.debug(MessageFormat.format("{0} reflog 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} reflog", repository.name), e);
 		}
-
-		// run Groovy hook scripts
-		Set<String> scripts = new LinkedHashSet<String>();
-		scripts.addAll(repositoryManager.getPostReceiveScriptsInherited(repository));
-		if (!ArrayUtils.isEmpty(repository.postReceiveScripts)) {
-			scripts.addAll(repository.postReceiveScripts);
-		}
-		runGroovy(commands, scripts);
 	}
 
 	/** Execute commands to update references. */
@@ -397,11 +506,7 @@
 		this.gitblitUrl = url;
 	}
 
-	protected void setRepositoryUrl(String url) {
-		this.repositoryUrl = url;
-	}
-
-	protected void sendRejection(final ReceiveCommand cmd, final String why, Object... objects) {
+	public void sendRejection(final ReceiveCommand cmd, final String why, Object... objects) {
 		String text;
 		if (ArrayUtils.isEmpty(objects)) {
 			text = why;
@@ -412,15 +517,15 @@
 		LOGGER.error(text + " (" + user.username + ")");
 	}
 
-	protected void sendHeader(String msg, Object... objects) {
-		sendMessage("--->", msg, objects);
+	public void sendHeader(String msg, Object... objects) {
+		sendInfo("--> ", msg, objects);
 	}
 
-	protected void sendMessage(String msg, Object... objects) {
-		sendMessage("    ", msg, objects);
+	public void sendInfo(String msg, Object... objects) {
+		sendInfo("    ", msg, objects);
 	}
 
-	protected void sendMessage(String prefix, String msg, Object... objects) {
+	private void sendInfo(String prefix, String msg, Object... objects) {
 		String text;
 		if (ArrayUtils.isEmpty(objects)) {
 			text = msg;
@@ -429,10 +534,12 @@
 			text = MessageFormat.format(msg, objects);
 			super.sendMessage(prefix + text);
 		}
-		LOGGER.info(text + " (" + user.username + ")");
+		if (!StringUtils.isEmpty(msg)) {
+			LOGGER.info(text + " (" + user.username + ")");
+		}
 	}
 
-	protected void sendError(String msg, Object... objects) {
+	public void sendError(String msg, Object... objects) {
 		String text;
 		if (ArrayUtils.isEmpty(objects)) {
 			text = msg;
@@ -441,7 +548,9 @@
 			text = MessageFormat.format(msg, objects);
 			super.sendError(text);
 		}
-		LOGGER.error(text + " (" + user.username + ")");
+		if (!StringUtils.isEmpty(msg)) {
+			LOGGER.error(text + " (" + user.username + ")");
+		}
 	}
 
 	/**
@@ -459,7 +568,7 @@
 		}
 
 		Binding binding = new Binding();
-		binding.setVariable("gitblit", GitBlit.self());
+		binding.setVariable("gitblit", gitblit);
 		binding.setVariable("repository", repository);
 		binding.setVariable("receivePack", this);
 		binding.setVariable("user", user);
@@ -495,4 +604,16 @@
 			}
 		}
 	}
+
+	public IGitblit getGitblit() {
+		return gitblit;
+	}
+
+	public RepositoryModel getRepositoryModel() {
+		return repository;
+	}
+
+	public UserModel getUserModel() {
+		return user;
+	}
 }

--
Gitblit v1.9.1