From c2188a840bc4153ae92112b04b2e06a90d3944aa Mon Sep 17 00:00:00 2001 From: Paul Martin <paul@paulsputer.com> Date: Wed, 27 Apr 2016 18:58:06 -0400 Subject: [PATCH] Ticket Reference handling #1048 --- src/main/java/com/gitblit/git/GitblitReceivePack.java | 544 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 files changed, 456 insertions(+), 88 deletions(-) diff --git a/src/main/java/com/gitblit/git/GitblitReceivePack.java b/src/main/java/com/gitblit/git/GitblitReceivePack.java index 8da603a..f271f6f 100644 --- a/src/main/java/com/gitblit/git/GitblitReceivePack.java +++ b/src/main/java/com/gitblit/git/GitblitReceivePack.java @@ -22,18 +22,28 @@ import java.io.File; import java.io.IOException; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.concurrent.TimeUnit; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.PostReceiveHook; import org.eclipse.jgit.transport.PreReceiveHook; import org.eclipse.jgit.transport.ReceiveCommand; @@ -44,20 +54,30 @@ 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.TicketModel; import com.gitblit.models.UserModel; +import com.gitblit.models.TicketModel.Change; +import com.gitblit.models.TicketModel.Field; +import com.gitblit.models.TicketModel.Patchset; +import com.gitblit.models.TicketModel.Status; +import com.gitblit.models.TicketModel.TicketAction; +import com.gitblit.models.TicketModel.TicketLink; +import com.gitblit.tickets.BranchTicketService; +import com.gitblit.tickets.ITicketService; +import com.gitblit.tickets.TicketNotifier; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.ClientLogger; import com.gitblit.utils.CommitCache; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.RefLogUtils; import com.gitblit.utils.StringUtils; +import com.google.common.collect.Lists; /** @@ -87,35 +107,91 @@ protected String gitblitUrl; - protected String repositoryUrl; - protected GroovyScriptEngine gse; - public GitblitReceivePack(Repository db, RepositoryModel repository, UserModel user) { + protected final IStoredSettings settings; + + protected final IGitblit gitblit; + + protected final ITicketService ticketService; + + protected final TicketNotifier ticketNotifier; + + + public GitblitReceivePack( + IGitblit gitblit, + Repository db, + RepositoryModel repository, + UserModel user) { + super(db); - - IRepositoryManager repositoryManager = GitBlit.getManager(IRepositoryManager.class); - + 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()); } catch (IOException e) { } + if (gitblit.getTicketService().isAcceptingTicketUpdates(repository)) { + this.ticketService = gitblit.getTicketService(); + this.ticketNotifier = this.ticketService.createNotifier(); + } else { + this.ticketService = null; + this.ticketNotifier = null; + } + // set advanced ref permissions setAllowCreates(user.canCreateRef(repository)); 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); } /** @@ -125,6 +201,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 @@ -150,7 +234,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); @@ -161,8 +245,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 @@ -195,16 +282,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; @@ -224,24 +304,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); } } - IRepositoryManager repositoryManager = GitBlit.getManager(IRepositoryManager.class); + // 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); } @@ -262,11 +360,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; } - IStoredSettings settings = GitBlit.getManager(IRuntimeManager.class).getSettings(); + 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) { @@ -276,9 +411,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())); @@ -292,59 +429,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); } - - IRepositoryManager repositoryManager = GitBlit.getManager(IRepositoryManager.class); - - // 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. */ @@ -387,17 +533,111 @@ } } } + + // + // if there are ref update receive commands that were + // successfully processed and there is an active ticket service for the repository + // then process any referenced tickets + // + if (ticketService != null) { + List<ReceiveCommand> allUpdates = ReceiveCommand.filter(batch.getCommands(), Result.OK); + if (!allUpdates.isEmpty()) { + int ticketsProcessed = 0; + for (ReceiveCommand cmd : allUpdates) { + switch (cmd.getType()) { + case CREATE: + case UPDATE: + if (cmd.getRefName().startsWith(Constants.R_HEADS)) { + Collection<TicketModel> tickets = processReferencedTickets(cmd); + ticketsProcessed += tickets.size(); + for (TicketModel ticket : tickets) { + ticketNotifier.queueMailing(ticket); + } + } + break; + + case UPDATE_NONFASTFORWARD: + if (cmd.getRefName().startsWith(Constants.R_HEADS)) { + String base = JGitUtils.getMergeBase(getRepository(), cmd.getOldId(), cmd.getNewId()); + List<TicketLink> deletedRefs = JGitUtils.identifyTicketsBetweenCommits(getRepository(), settings, base, cmd.getOldId().name()); + for (TicketLink link : deletedRefs) { + link.isDelete = true; + } + Change deletion = new Change(user.username); + deletion.pendingLinks = deletedRefs; + ticketService.updateTicket(repository, 0, deletion); + + Collection<TicketModel> tickets = processReferencedTickets(cmd); + ticketsProcessed += tickets.size(); + for (TicketModel ticket : tickets) { + ticketNotifier.queueMailing(ticket); + } + } + break; + case DELETE: + //Identify if the branch has been merged + SortedMap<Integer, String> bases = new TreeMap<Integer, String>(); + try { + ObjectId dObj = cmd.getOldId(); + Collection<Ref> tips = getRepository().getRefDatabase().getRefs(Constants.R_HEADS).values(); + for (Ref ref : tips) { + ObjectId iObj = ref.getObjectId(); + String mergeBase = JGitUtils.getMergeBase(getRepository(), dObj, iObj); + if (mergeBase != null) { + int d = JGitUtils.countCommits(getRepository(), getRevWalk(), mergeBase, dObj.name()); + bases.put(d, mergeBase); + //All commits have been merged into some other branch + if (d == 0) { + break; + } + } + } + + if (bases.isEmpty()) { + //TODO: Handle orphan branch case + } else { + if (bases.firstKey() > 0) { + //Delete references from the remaining commits that haven't been merged + String mergeBase = bases.get(bases.firstKey()); + List<TicketLink> deletedRefs = JGitUtils.identifyTicketsBetweenCommits(getRepository(), + settings, mergeBase, dObj.name()); + + for (TicketLink link : deletedRefs) { + link.isDelete = true; + } + Change deletion = new Change(user.username); + deletion.pendingLinks = deletedRefs; + ticketService.updateTicket(repository, 0, deletion); + } + } + + } catch (IOException e) { + LOGGER.error(null, e); + } + break; + + default: + break; + } + } + + if (ticketsProcessed == 1) { + sendInfo("1 ticket updated"); + } else if (ticketsProcessed > 1) { + sendInfo("{0} tickets updated", ticketsProcessed); + } + } + + // reset the ticket caches for the repository + ticketService.resetCaches(repository); + } } 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) { + public void sendRejection(final ReceiveCommand cmd, final String why, Object... objects) { String text; if (ArrayUtils.isEmpty(objects)) { text = why; @@ -408,15 +648,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; @@ -425,10 +665,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; @@ -437,7 +679,9 @@ text = MessageFormat.format(msg, objects); super.sendError(text); } - LOGGER.error(text + " (" + user.username + ")"); + if (!StringUtils.isEmpty(msg)) { + LOGGER.error(text + " (" + user.username + ")"); + } } /** @@ -455,7 +699,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); @@ -491,4 +735,128 @@ } } } + + public IGitblit getGitblit() { + return gitblit; + } + + public RepositoryModel getRepositoryModel() { + return repository; + } + + public UserModel getUserModel() { + return user; + } + + /** + * Automatically closes open tickets and adds references to tickets if made in the commit message. + * + * @param cmd + */ + private Collection<TicketModel> processReferencedTickets(ReceiveCommand cmd) { + Map<Long, TicketModel> changedTickets = new LinkedHashMap<Long, TicketModel>(); + + final RevWalk rw = getRevWalk(); + try { + rw.reset(); + rw.markStart(rw.parseCommit(cmd.getNewId())); + if (!ObjectId.zeroId().equals(cmd.getOldId())) { + rw.markUninteresting(rw.parseCommit(cmd.getOldId())); + } + + RevCommit c; + while ((c = rw.next()) != null) { + rw.parseBody(c); + List<TicketLink> ticketLinks = JGitUtils.identifyTicketsFromCommitMessage(getRepository(), settings, c); + if (ticketLinks == null) { + continue; + } + + for (TicketLink link : ticketLinks) { + + TicketModel ticket = ticketService.getTicket(repository, link.targetTicketId); + if (ticket == null) { + continue; + } + + Change change = null; + String commitSha = c.getName(); + String branchName = Repository.shortenRefName(cmd.getRefName()); + + switch (link.action) { + case Commit: { + //A commit can reference a ticket in any branch even if the ticket is closed. + //This allows developers to identify and communicate related issues + change = new Change(user.username); + change.referenceCommit(commitSha); + } break; + + case Close: { + // As this isn't a patchset theres no merging taking place when closing a ticket + if (ticket.isClosed()) { + continue; + } + + change = new Change(user.username); + change.setField(Field.status, Status.Fixed); + + if (StringUtils.isEmpty(ticket.responsible)) { + // unassigned tickets are assigned to the closer + change.setField(Field.responsible, user.username); + } + } + + default: { + //No action + } break; + } + + if (change != null) { + ticket = ticketService.updateTicket(repository, ticket.number, change); + } + + if (ticket != null) { + sendInfo(""); + sendHeader("#{0,number,0}: {1}", ticket.number, StringUtils.trimString(ticket.title, Constants.LEN_SHORTLOG)); + + switch (link.action) { + case Commit: { + sendInfo("referenced by push of {0} to {1}", commitSha, branchName); + changedTickets.put(ticket.number, ticket); + } break; + + case Close: { + sendInfo("closed by push of {0} to {1}", commitSha, branchName); + changedTickets.put(ticket.number, ticket); + } break; + + default: { } + } + + sendInfo(ticketService.getTicketUrl(ticket)); + sendInfo(""); + } else { + switch (link.action) { + case Commit: { + sendError("FAILED to reference ticket {0} by push of {1}", link.targetTicketId, commitSha); + } break; + + case Close: { + sendError("FAILED to close ticket {0} by push of {1}", link.targetTicketId, commitSha); + } break; + + default: { } + } + } + } + } + + } catch (IOException e) { + LOGGER.error("Can't scan for changes to reference or close", e); + } finally { + rw.reset(); + } + + return changedTickets.values(); + } } -- Gitblit v1.9.1