From 9883341c1fbb35abcb7a1d09f93f6a9e805ea551 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Thu, 06 Mar 2014 13:36:23 -0500
Subject: [PATCH] Revise push/mirror tickets branch triggering

---
 src/main/java/com/gitblit/git/ReceiveCommandEvent.java     |   38 ++++++++++++
 src/main/java/com/gitblit/git/PatchsetReceivePack.java     |   23 ++++++-
 src/main/java/com/gitblit/git/GitblitReceivePack.java      |    6 +-
 src/main/java/com/gitblit/tickets/ITicketService.java      |    4 +
 src/main/java/com/gitblit/tickets/BranchTicketService.java |   72 ++++++++++++++++++------
 src/main/java/com/gitblit/service/MirrorService.java       |   35 +++++++++--
 6 files changed, 147 insertions(+), 31 deletions(-)

diff --git a/src/main/java/com/gitblit/git/GitblitReceivePack.java b/src/main/java/com/gitblit/git/GitblitReceivePack.java
index 605632c..e3e2fae 100644
--- a/src/main/java/com/gitblit/git/GitblitReceivePack.java
+++ b/src/main/java/com/gitblit/git/GitblitReceivePack.java
@@ -51,7 +51,6 @@
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.tickets.BranchTicketService;
-import com.gitblit.tickets.BranchTicketService.TicketsBranchUpdated;
 import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.ClientLogger;
 import com.gitblit.utils.CommitCache;
@@ -344,12 +343,13 @@
 		} catch (Exception e) {
 			LOGGER.error(MessageFormat.format("Failed to update {0} pushlog", repository.name), e);
 		}
-		
+
 		// 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 TicketsBranchUpdated(repository));
+				rp.getRepository().fireEvent(new ReceiveCommandEvent(repository, cmd));
 			}
 		}
 
diff --git a/src/main/java/com/gitblit/git/PatchsetReceivePack.java b/src/main/java/com/gitblit/git/PatchsetReceivePack.java
index 2044f60..c0ab8ae 100644
--- a/src/main/java/com/gitblit/git/PatchsetReceivePack.java
+++ b/src/main/java/com/gitblit/git/PatchsetReceivePack.java
@@ -60,6 +60,7 @@
 import com.gitblit.models.TicketModel.PatchsetType;
 import com.gitblit.models.TicketModel.Status;
 import com.gitblit.models.UserModel;
+import com.gitblit.tickets.BranchTicketService;
 import com.gitblit.tickets.ITicketService;
 import com.gitblit.tickets.TicketMilestone;
 import com.gitblit.tickets.TicketNotifier;
@@ -105,7 +106,7 @@
 
 	protected final TicketNotifier ticketNotifier;
 
-	private boolean requireCleanMerge;
+	private boolean requireMergeablePatchset;
 
 	public PatchsetReceivePack(IGitblit gitblit, Repository db, RepositoryModel repository, UserModel user) {
 		super(gitblit, db, repository, user);
@@ -257,12 +258,26 @@
 	/** Execute commands to update references. */
 	@Override
 	protected void executeCommands() {
+		// we process patchsets unless the user is pushing something special
+		boolean processPatchsets = true;
+		for (ReceiveCommand cmd : filterCommands(Result.NOT_ATTEMPTED)) {
+			if (ticketService instanceof BranchTicketService
+					&& BranchTicketService.BRANCH.equals(cmd.getRefName())) {
+				// the user is pushing an update to the BranchTicketService data
+				processPatchsets = false;
+			}
+		}
+
 		// workaround for JGit's awful scoping choices
 		//
 		// reset the patchset refs to NOT_ATTEMPTED (see validateCommands)
 		for (ReceiveCommand cmd : filterCommands(Result.OK)) {
 			if (isPatchsetRef(cmd.getRefName())) {
 				cmd.setResult(Result.NOT_ATTEMPTED);
+			} else if (ticketService instanceof BranchTicketService
+					&& BranchTicketService.BRANCH.equals(cmd.getRefName())) {
+				// the user is pushing an update to the BranchTicketService data
+				processPatchsets = false;
 			}
 		}
 
@@ -292,7 +307,7 @@
 				continue;
 			}
 
-			if (isPatchsetRef(cmd.getRefName())) {
+			if (isPatchsetRef(cmd.getRefName()) && processPatchsets) {
 				if (ticketService == null) {
 					sendRejection(cmd, "Sorry, the ticket service is unavailable and can not accept patchsets at this time.");
 					continue;
@@ -393,6 +408,8 @@
 				for (ReceiveCommand cmd : toApply) {
 					if (cmd.getResult() == Result.NOT_ATTEMPTED) {
 						sendRejection(cmd, "lock error: {0}", err.getMessage());
+						LOGGER.error(MessageFormat.format("failed to lock {0}:{1}",
+								repository.name, cmd.getRefName()), err);
 					}
 				}
 			}
@@ -539,7 +556,7 @@
 		case MERGEABLE:
 			break;
 		default:
-			if (ticket == null || requireCleanMerge) {
+			if (ticket == null || requireMergeablePatchset) {
 				sendError("");
 				sendError("Your patchset can not be cleanly merged into {0}.", forBranch);
 				sendError("Please rebase your patchset and push again.");
diff --git a/src/main/java/com/gitblit/git/ReceiveCommandEvent.java b/src/main/java/com/gitblit/git/ReceiveCommandEvent.java
new file mode 100644
index 0000000..84dabb3
--- /dev/null
+++ b/src/main/java/com/gitblit/git/ReceiveCommandEvent.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.git;
+
+import org.eclipse.jgit.events.RefsChangedEvent;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+import com.gitblit.models.RepositoryModel;
+
+/**
+ * The event fired by other classes to allow this service to index tickets.
+ *
+ * @author James Moger
+ */
+public class ReceiveCommandEvent extends RefsChangedEvent {
+
+	public final RepositoryModel model;
+
+	public final ReceiveCommand cmd;
+
+	public ReceiveCommandEvent(RepositoryModel model, ReceiveCommand cmd) {
+		this.model = model;
+		this.cmd = cmd;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/service/MirrorService.java b/src/main/java/com/gitblit/service/MirrorService.java
index 1eb5420..cf9ccb5 100644
--- a/src/main/java/com/gitblit/service/MirrorService.java
+++ b/src/main/java/com/gitblit/service/MirrorService.java
@@ -28,6 +28,8 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.transport.FetchResult;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.ReceiveCommand.Type;
 import org.eclipse.jgit.transport.RemoteConfig;
 import org.eclipse.jgit.transport.TrackingRefUpdate;
 import org.slf4j.Logger;
@@ -35,11 +37,11 @@
 
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
+import com.gitblit.git.ReceiveCommandEvent;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.tickets.BranchTicketService;
-import com.gitblit.tickets.BranchTicketService.TicketsBranchUpdated;
 import com.gitblit.utils.JGitUtils;
 
 /**
@@ -147,7 +149,7 @@
 				FetchResult result = git.fetch().setRemote(mirror.getName()).setDryRun(testing).call();
 				Collection<TrackingRefUpdate> refUpdates = result.getTrackingRefUpdates();
 				if (refUpdates.size() > 0) {
-					boolean ticketBranchUpdated = false;
+					ReceiveCommand ticketBranchCmd = null;
 					for (TrackingRefUpdate ru : refUpdates) {
 						StringBuilder sb = new StringBuilder();
 						sb.append("updated mirror ");
@@ -164,14 +166,33 @@
 						sb.append("..");
 						sb.append(ru.getNewObjectId() == null ? "" : ru.getNewObjectId().abbreviate(7).name());
 						logger.info(sb.toString());
-						
+
 						if (BranchTicketService.BRANCH.equals(ru.getLocalName())) {
-							ticketBranchUpdated = true;
+							ReceiveCommand.Type type = null;
+							switch (ru.getResult()) {
+							case NEW:
+								type = Type.CREATE;
+								break;
+							case FAST_FORWARD:
+								type = Type.UPDATE;
+								break;
+							case FORCED:
+								type = Type.UPDATE_NONFASTFORWARD;
+								break;
+							default:
+								type = null;
+								break;
+							}
+
+							if (type != null) {
+								ticketBranchCmd = new ReceiveCommand(ru.getOldObjectId(),
+									ru.getNewObjectId(), ru.getLocalName(), type);
+							}
 						}
 					}
-					
-					if (ticketBranchUpdated) {
-						repository.fireEvent(new TicketsBranchUpdated(model));
+
+					if (ticketBranchCmd != null) {
+						repository.fireEvent(new ReceiveCommandEvent(model, ticketBranchCmd));
 					}
 				}
 			} catch (Exception e) {
diff --git a/src/main/java/com/gitblit/tickets/BranchTicketService.java b/src/main/java/com/gitblit/tickets/BranchTicketService.java
index 60dca27..fc0bd8f 100644
--- a/src/main/java/com/gitblit/tickets/BranchTicketService.java
+++ b/src/main/java/com/gitblit/tickets/BranchTicketService.java
@@ -27,6 +27,7 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 
 import javax.inject.Inject;
@@ -50,15 +51,18 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
 import org.eclipse.jgit.treewalk.TreeWalk;
 
 import com.gitblit.Constants;
+import com.gitblit.git.ReceiveCommandEvent;
 import com.gitblit.manager.INotificationManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.manager.IUserManager;
 import com.gitblit.models.PathModel;
+import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.models.RefModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.TicketModel;
@@ -78,18 +82,6 @@
  */
 public class BranchTicketService extends ITicketService implements RefsChangedListener {
 
-	/**
-	 *  The event fired by other classes to allow this service to index tickets.
-	 */
-	public static class TicketsBranchUpdated extends RefsChangedEvent {
-		
-		public final RepositoryModel model;
-		
-		public TicketsBranchUpdated(RepositoryModel model) {
-			this.model = model;
-		}
-	}
-	
 	public static final String BRANCH = "refs/gitblit/tickets";
 
 	private static final String JOURNAL = "journal.json";
@@ -111,7 +103,7 @@
 				repositoryManager);
 
 		lastAssignedId = new ConcurrentHashMap<String, AtomicLong>();
-		
+
 		// register the branch ticket service for repository ref changes
 		Repository.getGlobalListenerList().addRefsChangedListener(this);
 	}
@@ -138,16 +130,60 @@
 	}
 
 	/**
-	 * Listen for refs changed events and reindex that repository.
+	 * Listen for tickets branch changes and (re)index tickets, as appropriate
 	 */
 	@Override
-	public void onRefsChanged(RefsChangedEvent event) {
-		if (!(event instanceof TicketsBranchUpdated)) {
+	public synchronized void onRefsChanged(RefsChangedEvent event) {
+		if (!(event instanceof ReceiveCommandEvent)) {
 			return;
 		}
-		RepositoryModel repository = ((TicketsBranchUpdated) event).model;
+
+		ReceiveCommandEvent branchUpdate = (ReceiveCommandEvent) event;
+		RepositoryModel repository = branchUpdate.model;
+		ReceiveCommand cmd = branchUpdate.cmd;
 		try {
-			reindex(repository);
+			switch (cmd.getType()) {
+			case CREATE:
+			case UPDATE_NONFASTFORWARD:
+				// reindex everything
+				reindex(repository);
+				break;
+			case UPDATE:
+				// incrementally index ticket updates
+				resetCaches(repository);
+				long start = System.nanoTime();
+				log.info("incrementally indexing {} ticket branch due to received ref update", repository.name);
+				Repository db = repositoryManager.getRepository(repository.name);
+				try {
+					Set<Long> ids = new HashSet<Long>();
+					List<PathChangeModel> paths = JGitUtils.getFilesInRange(db,
+							cmd.getOldId().getName(), cmd.getNewId().getName());
+					for (PathChangeModel path : paths) {
+						String name = path.name.substring(path.name.lastIndexOf('/') + 1);
+						if (!JOURNAL.equals(name)) {
+							continue;
+						}
+						String tid = path.path.split("/")[2];
+						long ticketId = Long.parseLong(tid);
+						if (!ids.contains(ticketId)) {
+							ids.add(ticketId);
+							TicketModel ticket = getTicket(repository, ticketId);
+							log.info(MessageFormat.format("indexing ticket #{0,number,0}: {1}",
+									ticketId, ticket.title));
+							indexer.index(ticket);
+						}
+					}
+					long end = System.nanoTime();
+					log.info("incremental indexing of {0} ticket(s) completed in {1} msecs",
+							ids.size(), TimeUnit.NANOSECONDS.toMillis(end - start));
+				} finally {
+					db.close();
+				}
+				break;
+			default:
+				log.warn("Unexpected receive type {} in BranchTicketService.onRefsChanged" + cmd.getType());
+				break;
+			}
 		} catch (Exception e) {
 			log.error("failed to reindex " + repository.name, e);
 		}
diff --git a/src/main/java/com/gitblit/tickets/ITicketService.java b/src/main/java/com/gitblit/tickets/ITicketService.java
index d04cd5e..90f9c6d 100644
--- a/src/main/java/com/gitblit/tickets/ITicketService.java
+++ b/src/main/java/com/gitblit/tickets/ITicketService.java
@@ -897,6 +897,7 @@
 	public boolean deleteAll(RepositoryModel repository) {
 		boolean success = deleteAllImpl(repository);
 		if (success) {
+			log.info("Deleted all tickets for {}", repository.name);
 			resetCaches(repository);
 			indexer.deleteAll(repository);
 		}
@@ -936,6 +937,8 @@
 		TicketModel ticket = getTicket(repository, ticketId);
 		boolean success = deleteTicketImpl(repository, ticket, deletedBy);
 		if (success) {
+			log.info(MessageFormat.format("Deleted {0} ticket #{1,number,0}: {2}",
+					repository.name, ticketId, ticket.title));
 			ticketsCache.invalidate(new TicketKey(repository, ticketId));
 			indexer.delete(ticket);
 			return true;
@@ -1074,6 +1077,7 @@
 		long end = System.nanoTime();
 		long secs = TimeUnit.NANOSECONDS.toMillis(end - start);
 		log.info("reindexing completed in {} msecs.", secs);
+		resetCaches(repository);
 	}
 
 	/**

--
Gitblit v1.9.1