From cd7e4f9186f2ace4416780a7dd6341e01e23a45f Mon Sep 17 00:00:00 2001 From: Paul Martin <paul@paulsputer.com> Date: Wed, 06 Apr 2016 14:46:58 -0400 Subject: [PATCH] Fix for #962 - Delete patchset ability --- src/main/java/com/gitblit/tickets/ITicketService.java | 185 +++++++++++++++++++++++++++++++++++++++++----- 1 files changed, 164 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/gitblit/tickets/ITicketService.java b/src/main/java/com/gitblit/tickets/ITicketService.java index 8d922b5..e831003 100644 --- a/src/main/java/com/gitblit/tickets/ITicketService.java +++ b/src/main/java/com/gitblit/tickets/ITicketService.java @@ -36,6 +36,7 @@ import com.gitblit.IStoredSettings; import com.gitblit.Keys; import com.gitblit.extensions.TicketHook; +import com.gitblit.manager.IManager; import com.gitblit.manager.INotificationManager; import com.gitblit.manager.IPluginManager; import com.gitblit.manager.IRepositoryManager; @@ -47,8 +48,10 @@ import com.gitblit.models.TicketModel.Change; import com.gitblit.models.TicketModel.Field; import com.gitblit.models.TicketModel.Patchset; +import com.gitblit.models.TicketModel.PatchsetType; import com.gitblit.models.TicketModel.Status; import com.gitblit.tickets.TicketIndexer.Lucene; +import com.gitblit.utils.DeepCopier; import com.gitblit.utils.DiffUtils; import com.gitblit.utils.DiffUtils.DiffStat; import com.gitblit.utils.StringUtils; @@ -62,7 +65,9 @@ * @author James Moger * */ -public abstract class ITicketService { +public abstract class ITicketService implements IManager { + + public static final String SETTING_UPDATE_DIFFSTATS = "migration.updateDiffstats"; private static final String LABEL = "label"; @@ -105,6 +110,8 @@ private final Map<String, List<TicketLabel>> labelsCache; private final Map<String, List<TicketMilestone>> milestonesCache; + + private final boolean updateDiffstats; private static class TicketKey { final String repository; @@ -163,18 +170,22 @@ this.labelsCache = new ConcurrentHashMap<String, List<TicketLabel>>(); this.milestonesCache = new ConcurrentHashMap<String, List<TicketMilestone>>(); + + this.updateDiffstats = settings.getBoolean(SETTING_UPDATE_DIFFSTATS, true); } /** * Start the service. * @since 1.4.0 */ + @Override public abstract ITicketService start(); /** * Stop the service. * @since 1.4.0 */ + @Override public final ITicketService stop() { indexer.close(); ticketsCache.invalidateAll(); @@ -244,6 +255,7 @@ */ public boolean isAcceptingTicketUpdates(RepositoryModel repository) { return isReady() + && repository.hasCommits && repository.isBare && !repository.isFrozen && !repository.isMirror; @@ -379,7 +391,9 @@ } catch (IOException e) { log.error("failed to create label " + label + " in " + repository, e); } finally { - db.close(); + if (db != null) { + db.close(); + } } return lb; } @@ -405,7 +419,9 @@ } catch (IOException e) { log.error("failed to update label " + label + " in " + repository, e); } finally { - db.close(); + if (db != null) { + db.close(); + } } return false; } @@ -444,7 +460,9 @@ } catch (IOException e) { log.error("failed to rename label " + oldName + " in " + repository, e); } finally { - db.close(); + if (db != null) { + db.close(); + } } return false; } @@ -473,7 +491,9 @@ } catch (IOException e) { log.error("failed to delete label " + label + " in " + repository, e); } finally { - db.close(); + if (db != null) { + db.close(); + } } return false; } @@ -548,9 +568,10 @@ public TicketMilestone getMilestone(RepositoryModel repository, String milestone) { for (TicketMilestone ms : getMilestones(repository)) { if (ms.name.equalsIgnoreCase(milestone)) { + TicketMilestone tm = DeepCopier.copy(ms); String q = QueryBuilder.q(Lucene.rid.matches(repository.getRID())).and(Lucene.milestone.matches(milestone)).build(); - ms.tickets = indexer.queryFor(q, 1, 0, Lucene.number.name(), true); - return ms; + tm.tickets = indexer.queryFor(q, 1, 0, Lucene.number.name(), true); + return tm; } } return null; @@ -579,7 +600,9 @@ } catch (IOException e) { log.error("failed to create milestone " + milestone + " in " + repository, e); } finally { - db.close(); + if (db != null) { + db.close(); + } } return ms; } @@ -611,7 +634,9 @@ } catch (IOException e) { log.error("failed to update milestone " + milestone + " in " + repository, e); } finally { - db.close(); + if (db != null) { + db.close(); + } } return false; } @@ -627,39 +652,64 @@ * @since 1.4.0 */ public synchronized boolean renameMilestone(RepositoryModel repository, String oldName, String newName, String createdBy) { + return renameMilestone(repository, oldName, newName, createdBy, true); + } + + /** + * Renames a milestone. + * + * @param repository + * @param oldName + * @param newName + * @param createdBy + * @param notifyOpenTickets + * @return true if successful + * @since 1.6.0 + */ + public synchronized boolean renameMilestone(RepositoryModel repository, String oldName, + String newName, String createdBy, boolean notifyOpenTickets) { if (StringUtils.isEmpty(newName)) { throw new IllegalArgumentException("new milestone can not be empty!"); } Repository db = null; try { db = repositoryManager.getRepository(repository.name); - TicketMilestone milestone = getMilestone(repository, oldName); + TicketMilestone tm = getMilestone(repository, oldName); + if (tm == null) { + return false; + } StoredConfig config = db.getConfig(); config.unsetSection(MILESTONE, oldName); - config.setString(MILESTONE, newName, STATUS, milestone.status.name()); - config.setString(MILESTONE, newName, COLOR, milestone.color); - if (milestone.due != null) { - config.setString(MILESTONE, milestone.name, DUE, - new SimpleDateFormat(DUE_DATE_PATTERN).format(milestone.due)); + config.setString(MILESTONE, newName, STATUS, tm.status.name()); + config.setString(MILESTONE, newName, COLOR, tm.color); + if (tm.due != null) { + config.setString(MILESTONE, newName, DUE, + new SimpleDateFormat(DUE_DATE_PATTERN).format(tm.due)); } config.save(); milestonesCache.remove(repository.name); TicketNotifier notifier = createNotifier(); - for (QueryResult qr : milestone.tickets) { + for (QueryResult qr : tm.tickets) { Change change = new Change(createdBy); change.setField(Field.milestone, newName); TicketModel ticket = updateTicket(repository, qr.number, change); - notifier.queueMailing(ticket); + if (notifyOpenTickets && ticket.isOpen()) { + notifier.queueMailing(ticket); + } } - notifier.sendAll(); + if (notifyOpenTickets) { + notifier.sendAll(); + } return true; } catch (IOException e) { log.error("failed to rename milestone " + oldName + " in " + repository, e); } finally { - db.close(); + if (db != null) { + db.close(); + } } return false; } @@ -674,11 +724,30 @@ * @since 1.4.0 */ public synchronized boolean deleteMilestone(RepositoryModel repository, String milestone, String createdBy) { + return deleteMilestone(repository, milestone, createdBy, true); + } + + /** + * Deletes a milestone. + * + * @param repository + * @param milestone + * @param createdBy + * @param notifyOpenTickets + * @return true if successful + * @since 1.6.0 + */ + public synchronized boolean deleteMilestone(RepositoryModel repository, String milestone, + String createdBy, boolean notifyOpenTickets) { if (StringUtils.isEmpty(milestone)) { throw new IllegalArgumentException("milestone can not be empty!"); } Repository db = null; try { + TicketMilestone tm = getMilestone(repository, milestone); + if (tm == null) { + return false; + } db = repositoryManager.getRepository(repository.name); StoredConfig config = db.getConfig(); config.unsetSection(MILESTONE, milestone); @@ -686,14 +755,37 @@ milestonesCache.remove(repository.name); + TicketNotifier notifier = createNotifier(); + for (QueryResult qr : tm.tickets) { + Change change = new Change(createdBy); + change.setField(Field.milestone, ""); + TicketModel ticket = updateTicket(repository, qr.number, change); + if (notifyOpenTickets && ticket.isOpen()) { + notifier.queueMailing(ticket); + } + } + if (notifyOpenTickets) { + notifier.sendAll(); + } return true; } catch (IOException e) { log.error("failed to delete milestone " + milestone + " in " + repository, e); } finally { - db.close(); + if (db != null) { + db.close(); + } } return false; } + + /** + * Returns the set of assigned ticket ids in the repository. + * + * @param repository + * @return a set of assigned ticket ids in the repository + * @since 1.6.0 + */ + public abstract Set<Long> getIds(RepositoryModel repository); /** * Assigns a new ticket id. @@ -757,7 +849,7 @@ ticket = getTicketImpl(repository, ticketId); // if ticket exists if (ticket != null) { - if (ticket.hasPatchsets()) { + if (ticket.hasPatchsets() && updateDiffstats) { Repository r = repositoryManager.getRepository(repository.name); try { Patchset patchset = ticket.getCurrentPatchset(); @@ -789,6 +881,33 @@ * @since 1.4.0 */ protected abstract TicketModel getTicketImpl(RepositoryModel repository, long ticketId); + + + /** + * Returns the journal used to build a ticket. + * + * @param repository + * @param ticketId + * @return the journal for the ticket, if it exists, otherwise null + * @since 1.6.0 + */ + public final List<Change> getJournal(RepositoryModel repository, long ticketId) { + if (hasTicket(repository, ticketId)) { + List<Change> journal = getJournalImpl(repository, ticketId); + return journal; + } + return null; + } + + /** + * Retrieves the ticket journal. + * + * @param repository + * @param ticketId + * @return a ticket, if it exists, otherwise null + * @since 1.6.0 + */ + protected abstract List<Change> getJournalImpl(RepositoryModel repository, long ticketId); /** * Get the ticket url @@ -1095,6 +1214,30 @@ TicketModel revisedTicket = updateTicket(repository, ticket.number, deletion); return revisedTicket; } + + /** + * Deletes a patchset from a ticket. + * + * @param ticket + * @param patchset + * the patchset to delete (should be the highest revision) + * @param userName + * the user deleting the commit + * @return the revised ticket if the deletion was successful + * @since 1.8.0 + */ + public final TicketModel deletePatchset(TicketModel ticket, Patchset patchset, String userName) { + Change deletion = new Change(userName); + deletion.patchset = new Patchset(); + deletion.patchset.number = patchset.number; + deletion.patchset.rev = patchset.rev; + deletion.patchset.type = PatchsetType.Delete; + + RepositoryModel repository = repositoryManager.getRepositoryModel(ticket.repository); + TicketModel revisedTicket = updateTicket(repository, ticket.number, deletion); + + return revisedTicket; + } /** * Commit a ticket change to the repository. -- Gitblit v1.9.1