Search in sources :

Example 6 with TicketLink

use of com.gitblit.models.TicketModel.TicketLink in project gitblit by gitblit.

the class GitblitReceivePack method executeCommands.

/**
 * Execute commands to update references.
 */
@Override
protected void executeCommands() {
    List<ReceiveCommand> toApply = filterCommands(Result.NOT_ATTEMPTED);
    if (toApply.isEmpty()) {
        return;
    }
    ProgressMonitor updating = NullProgressMonitor.INSTANCE;
    boolean sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K);
    if (sideBand) {
        SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut);
        pm.setDelayStart(250, TimeUnit.MILLISECONDS);
        updating = pm;
    }
    BatchRefUpdate batch = getRepository().getRefDatabase().newBatchUpdate();
    batch.setAllowNonFastForwards(isAllowNonFastForwards());
    batch.setRefLogIdent(getRefLogIdent());
    batch.setRefLogMessage("push", true);
    for (ReceiveCommand cmd : toApply) {
        if (Result.NOT_ATTEMPTED != cmd.getResult()) {
            // Already rejected by the core receive process.
            continue;
        }
        batch.addCommand(cmd);
    }
    if (!batch.getCommands().isEmpty()) {
        try {
            batch.execute(getRevWalk(), updating);
        } catch (IOException err) {
            for (ReceiveCommand cmd : toApply) {
                if (cmd.getResult() == Result.NOT_ATTEMPTED) {
                    sendRejection(cmd, "lock error: {0}", err.getMessage());
                }
            }
        }
    }
    // 
    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);
    }
}
Also used : ReceiveCommand(org.eclipse.jgit.transport.ReceiveCommand) AnyObjectId(org.eclipse.jgit.lib.AnyObjectId) ObjectId(org.eclipse.jgit.lib.ObjectId) TicketModel(com.gitblit.models.TicketModel) IOException(java.io.IOException) Change(com.gitblit.models.TicketModel.Change) TreeMap(java.util.TreeMap) NullProgressMonitor(org.eclipse.jgit.lib.NullProgressMonitor) ProgressMonitor(org.eclipse.jgit.lib.ProgressMonitor) Ref(org.eclipse.jgit.lib.Ref) TicketLink(com.gitblit.models.TicketModel.TicketLink) BatchRefUpdate(org.eclipse.jgit.lib.BatchRefUpdate)

Example 7 with TicketLink

use of com.gitblit.models.TicketModel.TicketLink in project gitblit by gitblit.

the class PatchsetReceivePack method executeCommands.

/**
 * 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;
        }
    }
    // 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;
        }
    }
    List<ReceiveCommand> toApply = filterCommands(Result.NOT_ATTEMPTED);
    if (toApply.isEmpty()) {
        return;
    }
    ProgressMonitor updating = NullProgressMonitor.INSTANCE;
    boolean sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K);
    if (sideBand) {
        SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut);
        pm.setDelayStart(250, TimeUnit.MILLISECONDS);
        updating = pm;
    }
    BatchRefUpdate batch = getRepository().getRefDatabase().newBatchUpdate();
    batch.setAllowNonFastForwards(isAllowNonFastForwards());
    batch.setRefLogIdent(getRefLogIdent());
    batch.setRefLogMessage("push", true);
    ReceiveCommand patchsetRefCmd = null;
    PatchsetCommand patchsetCmd = null;
    for (ReceiveCommand cmd : toApply) {
        if (Result.NOT_ATTEMPTED != cmd.getResult()) {
            // Already rejected by the core receive process.
            continue;
        }
        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;
            }
            if (!ticketService.isReady()) {
                sendRejection(cmd, "Sorry, the ticket service can not accept patchsets at this time.");
                continue;
            }
            if (UserModel.ANONYMOUS.equals(user)) {
                // server allows anonymous pushes, but anonymous patchset
                // contributions are prohibited by design
                sendRejection(cmd, "Sorry, anonymous patchset contributions are prohibited.");
                continue;
            }
            final Matcher m = NEW_PATCHSET.matcher(cmd.getRefName());
            if (m.matches()) {
                // prohibit pushing directly to a patchset ref
                long id = getTicketId(cmd.getRefName());
                sendError("You may not directly push directly to a patchset ref!");
                sendError("Instead, please push to one the following:");
                sendError(" - {0}{1,number,0}", Constants.R_FOR, id);
                sendError(" - {0}{1,number,0}", Constants.R_TICKET, id);
                sendRejection(cmd, "protected ref");
                continue;
            }
            if (hasRefNamespace(Constants.R_FOR)) {
                // the refs/for/ namespace exists and it must not
                LOGGER.error("{} already has refs in the {} namespace", repository.name, Constants.R_FOR);
                sendRejection(cmd, "Sorry, a repository administrator will have to remove the {} namespace", Constants.R_FOR);
                continue;
            }
            if (cmd.getNewId().equals(ObjectId.zeroId())) {
                // ref deletion request
                if (cmd.getRefName().startsWith(Constants.R_TICKET)) {
                    if (user.canDeleteRef(repository)) {
                        batch.addCommand(cmd);
                    } else {
                        sendRejection(cmd, "Sorry, you do not have permission to delete {}", cmd.getRefName());
                    }
                } else {
                    sendRejection(cmd, "Sorry, you can not delete {}", cmd.getRefName());
                }
                continue;
            }
            if (patchsetRefCmd != null) {
                sendRejection(cmd, "You may only push one patchset at a time.");
                continue;
            }
            LOGGER.info(MessageFormat.format("Verifying {0} push ref \"{1}\" received from {2}", repository.name, cmd.getRefName(), user.username));
            // responsible verification
            String responsible = PatchsetCommand.getSingleOption(cmd, PatchsetCommand.RESPONSIBLE);
            if (!StringUtils.isEmpty(responsible)) {
                UserModel assignee = gitblit.getUserModel(responsible);
                if (assignee == null) {
                    // no account by this name
                    sendRejection(cmd, "{0} can not be assigned any tickets because there is no user account by that name", responsible);
                    continue;
                } else if (!assignee.canPush(repository)) {
                    // account does not have RW permissions
                    sendRejection(cmd, "{0} ({1}) can not be assigned any tickets because the user does not have RW permissions for {2}", assignee.getDisplayName(), assignee.username, repository.name);
                    continue;
                }
            }
            // milestone verification
            String milestone = PatchsetCommand.getSingleOption(cmd, PatchsetCommand.MILESTONE);
            if (!StringUtils.isEmpty(milestone)) {
                TicketMilestone milestoneModel = ticketService.getMilestone(repository, milestone);
                if (milestoneModel == null) {
                    // milestone does not exist
                    sendRejection(cmd, "Sorry, \"{0}\" is not a valid milestone!", milestone);
                    continue;
                }
            }
            // watcher verification
            List<String> watchers = PatchsetCommand.getOptions(cmd, PatchsetCommand.WATCH);
            if (!ArrayUtils.isEmpty(watchers)) {
                boolean verified = true;
                for (String watcher : watchers) {
                    UserModel user = gitblit.getUserModel(watcher);
                    if (user == null) {
                        // watcher does not exist
                        sendRejection(cmd, "Sorry, \"{0}\" is not a valid username for the watch list!", watcher);
                        verified = false;
                        break;
                    }
                }
                if (!verified) {
                    continue;
                }
            }
            patchsetRefCmd = cmd;
            patchsetCmd = preparePatchset(cmd);
            if (patchsetCmd != null) {
                batch.addCommand(patchsetCmd);
            }
            continue;
        }
        batch.addCommand(cmd);
    }
    if (!batch.getCommands().isEmpty()) {
        try {
            batch.execute(getRevWalk(), updating);
        } catch (IOException err) {
            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);
                }
            }
        }
    }
    // 
    if (patchsetRefCmd != null && patchsetCmd != null) {
        if (!patchsetCmd.getResult().equals(Result.OK)) {
            // patchset command failed!
            LOGGER.error(patchsetCmd.getType() + " " + patchsetCmd.getRefName() + " " + patchsetCmd.getResult());
            patchsetRefCmd.setResult(patchsetCmd.getResult(), patchsetCmd.getMessage());
        } else {
            // all patchset commands were applied
            patchsetRefCmd.setResult(Result.OK);
            // update the ticket branch ref
            RefUpdate ru = updateRef(patchsetCmd.getTicketBranch(), patchsetCmd.getNewId(), patchsetCmd.getPatchsetType());
            updateReflog(ru);
            TicketModel ticket = processPatchset(patchsetCmd);
            if (ticket != null) {
                ticketNotifier.queueMailing(ticket);
            }
        }
    }
    // 
    // if there are standard ref update receive commands that were
    // successfully processed, process referenced tickets, if any
    // 
    List<ReceiveCommand> allUpdates = ReceiveCommand.filter(batch.getCommands(), Result.OK);
    List<ReceiveCommand> refUpdates = excludePatchsetCommands(allUpdates);
    List<ReceiveCommand> stdUpdates = excludeTicketCommands(refUpdates);
    if (!stdUpdates.isEmpty()) {
        int ticketsProcessed = 0;
        for (ReceiveCommand cmd : stdUpdates) {
            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;
                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);
}
Also used : ReceiveCommand(org.eclipse.jgit.transport.ReceiveCommand) BranchTicketService(com.gitblit.tickets.BranchTicketService) Matcher(java.util.regex.Matcher) TicketModel(com.gitblit.models.TicketModel) IOException(java.io.IOException) Change(com.gitblit.models.TicketModel.Change) NullProgressMonitor(org.eclipse.jgit.lib.NullProgressMonitor) ProgressMonitor(org.eclipse.jgit.lib.ProgressMonitor) UserModel(com.gitblit.models.UserModel) TicketLink(com.gitblit.models.TicketModel.TicketLink) BatchRefUpdate(org.eclipse.jgit.lib.BatchRefUpdate) TicketMilestone(com.gitblit.tickets.TicketMilestone) RefUpdate(org.eclipse.jgit.lib.RefUpdate) BatchRefUpdate(org.eclipse.jgit.lib.BatchRefUpdate)

Example 8 with TicketLink

use of com.gitblit.models.TicketModel.TicketLink in project gitblit by gitblit.

the class JGitUtils method identifyTicketsFromCommitMessage.

/**
 * Try to identify all referenced tickets from the commit.
 *
 * @param commit
 * @return a collection of TicketLinks
 */
@NotNull
public static List<TicketLink> identifyTicketsFromCommitMessage(Repository repository, IStoredSettings settings, RevCommit commit) {
    List<TicketLink> ticketLinks = new ArrayList<TicketLink>();
    List<Long> linkedTickets = new ArrayList<Long>();
    // parse commit message looking for fixes/closes #n
    final String xFixDefault = "(?:fixes|closes)[\\s-]+#?(\\d+)";
    String xFix = settings.getString(Keys.tickets.closeOnPushCommitMessageRegex, xFixDefault);
    if (StringUtils.isEmpty(xFix)) {
        xFix = xFixDefault;
    }
    try {
        Pattern p = Pattern.compile(xFix, Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(commit.getFullMessage());
        while (m.find()) {
            String val = m.group(1);
            long number = Long.parseLong(val);
            if (number > 0) {
                ticketLinks.add(new TicketLink(number, TicketAction.Close));
                linkedTickets.add(number);
            }
        }
    } catch (Exception e) {
        LOGGER.error(String.format("Failed to parse \"%s\" in commit %s", xFix, commit.getName()), e);
    }
    // parse commit message looking for ref #n
    final String xRefDefault = "(?:ref|task|issue|bug)?[\\s-]*#(\\d+)";
    String xRef = settings.getString(Keys.tickets.linkOnPushCommitMessageRegex, xRefDefault);
    if (StringUtils.isEmpty(xRef)) {
        xRef = xRefDefault;
    }
    try {
        Pattern p = Pattern.compile(xRef, Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(commit.getFullMessage());
        while (m.find()) {
            String val = m.group(1);
            long number = Long.parseLong(val);
            // Most generic case so don't included tickets more precisely linked
            if ((number > 0) && (!linkedTickets.contains(number))) {
                ticketLinks.add(new TicketLink(number, TicketAction.Commit, commit.getName()));
                linkedTickets.add(number);
            }
        }
    } catch (Exception e) {
        LOGGER.error(String.format("Failed to parse \"%s\" in commit %s", xRef, commit.getName()), e);
    }
    return ticketLinks;
}
Also used : Pattern(java.util.regex.Pattern) Matcher(java.util.regex.Matcher) ArrayList(java.util.ArrayList) TicketLink(com.gitblit.models.TicketModel.TicketLink) RevisionSyntaxException(org.eclipse.jgit.errors.RevisionSyntaxException) MissingObjectException(org.eclipse.jgit.errors.MissingObjectException) GitAPIException(org.eclipse.jgit.api.errors.GitAPIException) LargeObjectException(org.eclipse.jgit.errors.LargeObjectException) GitBlitException(com.gitblit.GitBlitException) AmbiguousObjectException(org.eclipse.jgit.errors.AmbiguousObjectException) ConfigInvalidException(org.eclipse.jgit.errors.ConfigInvalidException) IncorrectObjectTypeException(org.eclipse.jgit.errors.IncorrectObjectTypeException) ConcurrentRefUpdateException(org.eclipse.jgit.api.errors.ConcurrentRefUpdateException) IOException(java.io.IOException) JGitInternalException(org.eclipse.jgit.api.errors.JGitInternalException) StopWalkException(org.eclipse.jgit.errors.StopWalkException) NotNull(org.jetbrains.annotations.NotNull)

Example 9 with TicketLink

use of com.gitblit.models.TicketModel.TicketLink in project gitblit by gitblit.

the class ITicketService method deletePatchset.

/**
 * 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;
    // Find and delete references to tickets by the removed commits
    List<TicketLink> patchsetTicketLinks = JGitUtils.identifyTicketsBetweenCommits(repositoryManager.getRepository(ticket.repository), settings, patchset.base, patchset.tip);
    for (TicketLink link : patchsetTicketLinks) {
        link.isDelete = true;
    }
    deletion.pendingLinks = patchsetTicketLinks;
    RepositoryModel repositoryModel = repositoryManager.getRepositoryModel(ticket.repository);
    TicketModel revisedTicket = updateTicket(repositoryModel, ticket.number, deletion);
    return revisedTicket;
}
Also used : Patchset(com.gitblit.models.TicketModel.Patchset) TicketModel(com.gitblit.models.TicketModel) Change(com.gitblit.models.TicketModel.Change) RepositoryModel(com.gitblit.models.RepositoryModel) TicketLink(com.gitblit.models.TicketModel.TicketLink)

Aggregations

TicketLink (com.gitblit.models.TicketModel.TicketLink)9 IOException (java.io.IOException)8 TicketModel (com.gitblit.models.TicketModel)7 Change (com.gitblit.models.TicketModel.Change)7 RevCommit (org.eclipse.jgit.revwalk.RevCommit)4 RevWalk (org.eclipse.jgit.revwalk.RevWalk)4 Patchset (com.gitblit.models.TicketModel.Patchset)3 BatchRefUpdate (org.eclipse.jgit.lib.BatchRefUpdate)3 Ref (org.eclipse.jgit.lib.Ref)3 ArrayList (java.util.ArrayList)2 LinkedHashMap (java.util.LinkedHashMap)2 Matcher (java.util.regex.Matcher)2 NullProgressMonitor (org.eclipse.jgit.lib.NullProgressMonitor)2 ProgressMonitor (org.eclipse.jgit.lib.ProgressMonitor)2 RefUpdate (org.eclipse.jgit.lib.RefUpdate)2 ReceiveCommand (org.eclipse.jgit.transport.ReceiveCommand)2 GitBlitException (com.gitblit.GitBlitException)1 TicketHook (com.gitblit.extensions.TicketHook)1 RepositoryModel (com.gitblit.models.RepositoryModel)1 UserModel (com.gitblit.models.UserModel)1