From 2e73efcaedea190795ba45ca72f924f697cc296e Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 26 Sep 2014 08:43:09 -0400
Subject: [PATCH] Improve the error message when too many commits are pushed for a proposal
---
src/main/java/com/gitblit/tickets/BranchTicketService.java | 192 ++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 170 insertions(+), 22 deletions(-)
diff --git a/src/main/java/com/gitblit/tickets/BranchTicketService.java b/src/main/java/com/gitblit/tickets/BranchTicketService.java
index 14ed809..5a42c6a 100644
--- a/src/main/java/com/gitblit/tickets/BranchTicketService.java
+++ b/src/main/java/com/gitblit/tickets/BranchTicketService.java
@@ -27,36 +27,43 @@
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;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.events.RefsChangedEvent;
+import org.eclipse.jgit.events.RefsChangedListener;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefRename;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
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.IPluginManager;
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;
@@ -74,9 +81,9 @@
* @author James Moger
*
*/
-public class BranchTicketService extends ITicketService {
+public class BranchTicketService extends ITicketService implements RefsChangedListener {
- public static final String BRANCH = "refs/gitblit/tickets";
+ public static final String BRANCH = "refs/meta/gitblit/tickets";
private static final String JOURNAL = "journal.json";
@@ -84,19 +91,23 @@
private final Map<String, AtomicLong> lastAssignedId;
- @Inject
public BranchTicketService(
IRuntimeManager runtimeManager,
+ IPluginManager pluginManager,
INotificationManager notificationManager,
IUserManager userManager,
IRepositoryManager repositoryManager) {
super(runtimeManager,
+ pluginManager,
notificationManager,
userManager,
repositoryManager);
lastAssignedId = new ConcurrentHashMap<String, AtomicLong>();
+
+ // register the branch ticket service for repository ref changes
+ Repository.getGlobalListenerList().addRefsChangedListener(this);
}
@Override
@@ -121,23 +132,105 @@
}
/**
- * Returns a RefModel for the refs/gitblit/tickets branch in the repository.
+ * Listen for tickets branch changes and (re)index tickets, as appropriate
+ */
+ @Override
+ public synchronized void onRefsChanged(RefsChangedEvent event) {
+ if (!(event instanceof ReceiveCommandEvent)) {
+ return;
+ }
+
+ ReceiveCommandEvent branchUpdate = (ReceiveCommandEvent) event;
+ RepositoryModel repository = branchUpdate.model;
+ ReceiveCommand cmd = branchUpdate.cmd;
+ try {
+ 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);
+ }
+ }
+
+ /**
+ * Returns a RefModel for the refs/meta/gitblit/tickets branch in the repository.
* If the branch can not be found, null is returned.
*
* @return a refmodel for the gitblit tickets branch or null
*/
private RefModel getTicketsBranch(Repository db) {
- List<RefModel> refs = JGitUtils.getRefs(db, Constants.R_GITBLIT);
+ List<RefModel> refs = JGitUtils.getRefs(db, "refs/");
+ Ref oldRef = null;
for (RefModel ref : refs) {
if (ref.reference.getName().equals(BRANCH)) {
return ref;
+ } else if (ref.reference.getName().equals("refs/gitblit/tickets")) {
+ oldRef = ref.reference;
+ }
+ }
+ if (oldRef != null) {
+ // rename old ref to refs/meta/gitblit/tickets
+ RefRename cmd;
+ try {
+ cmd = db.renameRef(oldRef.getName(), BRANCH);
+ cmd.setRefLogIdent(new PersonIdent("Gitblit", "gitblit@localhost"));
+ cmd.setRefLogMessage("renamed " + oldRef.getName() + " => " + BRANCH);
+ Result res = cmd.rename();
+ switch (res) {
+ case RENAMED:
+ log.info(db.getDirectory() + " " + cmd.getRefLogMessage());
+ return getTicketsBranch(db);
+ default:
+ log.error("failed to rename " + oldRef.getName() + " => " + BRANCH + " (" + res.name() + ")");
+ }
+ } catch (IOException e) {
+ log.error("failed to rename tickets branch", e);
}
}
return null;
}
/**
- * Creates the refs/gitblit/tickets branch.
+ * Creates the refs/meta/gitblit/tickets branch.
* @param db
*/
private void createTicketsBranch(Repository db) {
@@ -150,7 +243,7 @@
* folder with the remaining characters as a subfolder within that folder.
*
* @param ticketId
- * @return the root path of the ticket content on the refs/gitblit/tickets branch
+ * @return the root path of the ticket content on the refs/meta/gitblit/tickets branch
*/
private String toTicketPath(long ticketId) {
StringBuilder sb = new StringBuilder();
@@ -285,6 +378,37 @@
}
/**
+ * Returns the assigned ticket ids.
+ *
+ * @return the assigned ticket ids
+ */
+ @Override
+ public synchronized Set<Long> getIds(RepositoryModel repository) {
+ Repository db = repositoryManager.getRepository(repository.name);
+ try {
+ if (getTicketsBranch(db) == null) {
+ return Collections.emptySet();
+ }
+ Set<Long> ids = new TreeSet<Long>();
+ List<PathModel> paths = JGitUtils.getDocuments(db, Arrays.asList("json"), BRANCH);
+ for (PathModel 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);
+ ids.add(ticketId);
+ }
+ return ids;
+ } finally {
+ if (db != null) {
+ db.close();
+ }
+ }
+ }
+
+ /**
* Assigns a new ticket id.
*
* @param repository
@@ -305,16 +429,10 @@
}
AtomicLong lastId = lastAssignedId.get(repository.name);
if (lastId.get() <= 0) {
- List<PathModel> paths = JGitUtils.getDocuments(db, Arrays.asList("json"), BRANCH);
- for (PathModel 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 (ticketId > lastId.get()) {
- lastId.set(ticketId);
+ Set<Long> ids = getIds(repository);
+ for (long id : ids) {
+ if (id > lastId.get()) {
+ lastId.set(id);
}
}
}
@@ -427,6 +545,28 @@
ticket.number = ticketId;
}
return ticket;
+ } finally {
+ db.close();
+ }
+ }
+
+ /**
+ * Retrieves the journal for the ticket.
+ *
+ * @param repository
+ * @param ticketId
+ * @return a journal, if it exists, otherwise null
+ */
+ @Override
+ protected List<Change> getJournalImpl(RepositoryModel repository, long ticketId) {
+ Repository db = repositoryManager.getRepository(repository.name);
+ try {
+ List<Change> changes = getJournal(db, ticketId);
+ if (ArrayUtils.isEmpty(changes)) {
+ log.warn("Empty journal for {}:{}", repository, ticketId);
+ return null;
+ }
+ return changes;
} finally {
db.close();
}
@@ -566,7 +706,9 @@
ticket.number, db.getDirectory()), t);
} finally {
// release the treewalk
- treeWalk.release();
+ if (treeWalk != null) {
+ treeWalk.release();
+ }
}
} finally {
db.close();
@@ -681,8 +823,12 @@
List<DirCacheEntry> list = new ArrayList<DirCacheEntry>();
TreeWalk tw = null;
try {
- tw = new TreeWalk(db);
ObjectId treeId = db.resolve(BRANCH + "^{tree}");
+ if (treeId == null) {
+ // branch does not exist yet, could be migrating tickets
+ return list;
+ }
+ tw = new TreeWalk(db);
int hIdx = tw.addTree(treeId);
tw.setRecursive(true);
@@ -782,7 +928,9 @@
} catch (Exception e) {
log.error(null, e);
} finally {
- db.close();
+ if (db != null) {
+ db.close();
+ }
}
return false;
}
--
Gitblit v1.9.1