Search in sources :

Example 11 with Change

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

the class PatchsetReceivePack method merge.

/**
	 * Merge the specified patchset to the integration branch.
	 *
	 * @param ticket
	 * @param patchset
	 * @return true, if successful
	 */
public MergeStatus merge(TicketModel ticket) {
    PersonIdent committer = new PersonIdent(user.getDisplayName(), StringUtils.isEmpty(user.emailAddress) ? (user.username + "@gitblit") : user.emailAddress);
    Patchset patchset = ticket.getCurrentPatchset();
    String message = MessageFormat.format("Merged #{0,number,0} \"{1}\"", ticket.number, ticket.title);
    Ref oldRef = null;
    try {
        oldRef = getRepository().getRef(ticket.mergeTo);
    } catch (IOException e) {
        LOGGER.error("failed to get ref for " + ticket.mergeTo, e);
    }
    MergeResult mergeResult = JGitUtils.merge(getRepository(), patchset.tip, ticket.mergeTo, getRepositoryModel().mergeType, committer, message);
    if (StringUtils.isEmpty(mergeResult.sha)) {
        LOGGER.error("FAILED to merge {} to {} ({})", new Object[] { patchset, ticket.mergeTo, mergeResult.status.name() });
        return mergeResult.status;
    }
    Change change = new Change(user.username);
    change.setField(Field.status, Status.Merged);
    change.setField(Field.mergeSha, mergeResult.sha);
    change.setField(Field.mergeTo, ticket.mergeTo);
    if (StringUtils.isEmpty(ticket.responsible)) {
        // unassigned tickets are assigned to the closer
        change.setField(Field.responsible, user.username);
    }
    long ticketId = ticket.number;
    ticket = ticketService.updateTicket(repository, ticket.number, change);
    if (ticket != null) {
        ticketNotifier.queueMailing(ticket);
        if (oldRef != null) {
            ReceiveCommand cmd = new ReceiveCommand(oldRef.getObjectId(), ObjectId.fromString(mergeResult.sha), oldRef.getName());
            cmd.setResult(Result.OK);
            List<ReceiveCommand> commands = Arrays.asList(cmd);
            logRefChange(commands);
            updateIncrementalPushTags(commands);
            updateGitblitRefLog(commands);
        }
        // call patchset hooks
        for (PatchsetHook hook : gitblit.getExtensions(PatchsetHook.class)) {
            try {
                hook.onMergePatchset(ticket);
            } catch (Exception e) {
                LOGGER.error("Failed to execute extension", e);
            }
        }
        return mergeResult.status;
    } else {
        LOGGER.error("FAILED to resolve ticket {} by merge from web ui", ticketId);
    }
    return mergeResult.status;
}
Also used : ReceiveCommand(org.eclipse.jgit.transport.ReceiveCommand) Ref(org.eclipse.jgit.lib.Ref) PatchsetHook(com.gitblit.extensions.PatchsetHook) PersonIdent(org.eclipse.jgit.lib.PersonIdent) MergeResult(com.gitblit.utils.JGitUtils.MergeResult) Patchset(com.gitblit.models.TicketModel.Patchset) IOException(java.io.IOException) Change(com.gitblit.models.TicketModel.Change) IOException(java.io.IOException)

Example 12 with Change

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

the class GitblitReceivePack method processReferencedTickets.

/**
	 * 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();
}
Also used : TicketModel(com.gitblit.models.TicketModel) Change(com.gitblit.models.TicketModel.Change) IOException(java.io.IOException) RevWalk(org.eclipse.jgit.revwalk.RevWalk) TicketLink(com.gitblit.models.TicketModel.TicketLink) LinkedHashMap(java.util.LinkedHashMap) RevCommit(org.eclipse.jgit.revwalk.RevCommit)

Example 13 with Change

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

the class TicketNotifier method getSubject.

protected String getSubject(TicketModel ticket) {
    Change lastChange = ticket.changes.get(ticket.changes.size() - 1);
    boolean newTicket = lastChange.isStatusChange() && ticket.changes.size() == 1;
    String re = newTicket ? "" : "Re: ";
    String subject = MessageFormat.format("{0}[{1}] {2} (#{3,number,0})", re, StringUtils.stripDotGit(ticket.repository), ticket.title, ticket.number);
    return subject;
}
Also used : Change(com.gitblit.models.TicketModel.Change)

Example 14 with Change

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

the class TicketNotifier method formatLastChange.

protected String formatLastChange(TicketModel ticket) {
    Change lastChange = ticket.changes.get(ticket.changes.size() - 1);
    UserModel user = getUserModel(lastChange.author);
    // define the fields we do NOT want to see in an email notification
    Set<TicketModel.Field> fieldExclusions = new HashSet<TicketModel.Field>();
    fieldExclusions.addAll(Arrays.asList(Field.watchers, Field.voters));
    StringBuilder sb = new StringBuilder();
    boolean newTicket = lastChange.isStatusChange() && Status.New == lastChange.getStatus();
    boolean isFastForward = true;
    List<RevCommit> commits = null;
    DiffStat diffstat = null;
    String pattern;
    if (lastChange.hasPatchset()) {
        // patchset uploaded
        Patchset patchset = lastChange.patchset;
        String base = "";
        // determine the changed paths
        Repository repo = null;
        try {
            repo = repositoryManager.getRepository(ticket.repository);
            if (patchset.isFF() && (patchset.rev > 1)) {
                // fast-forward update, just show the new data
                isFastForward = true;
                Patchset prev = ticket.getPatchset(patchset.number, patchset.rev - 1);
                base = prev.tip;
            } else {
                // proposal OR non-fast-forward update
                isFastForward = false;
                base = patchset.base;
            }
            diffstat = DiffUtils.getDiffStat(repo, base, patchset.tip);
            commits = JGitUtils.getRevLog(repo, base, patchset.tip);
        } catch (Exception e) {
            Logger.getLogger(getClass()).error("failed to get changed paths", e);
        } finally {
            if (repo != null) {
                repo.close();
            }
        }
        String compareUrl = ticketService.getCompareUrl(ticket, base, patchset.tip);
        if (newTicket) {
            // new proposal
            pattern = "**{0}** is proposing a change.";
            sb.append(MessageFormat.format(pattern, user.getDisplayName()));
            fieldExclusions.add(Field.status);
            fieldExclusions.add(Field.title);
            fieldExclusions.add(Field.body);
        } else {
            // describe the patchset
            if (patchset.isFF()) {
                pattern = "**{0}** added {1} {2} to patchset {3}.";
                sb.append(MessageFormat.format(pattern, user.getDisplayName(), patchset.added, patchset.added == 1 ? "commit" : "commits", patchset.number));
            } else {
                pattern = "**{0}** uploaded patchset {1}. *({2})*";
                sb.append(MessageFormat.format(pattern, user.getDisplayName(), patchset.number, patchset.type.toString().toUpperCase()));
            }
        }
        sb.append(HARD_BRK);
        sb.append(MessageFormat.format("{0} {1}, {2} {3}, <span style=\"color:darkgreen;\">+{4} insertions</span>, <span style=\"color:darkred;\">-{5} deletions</span> from {6}. [compare]({7})", commits.size(), commits.size() == 1 ? "commit" : "commits", diffstat.paths.size(), diffstat.paths.size() == 1 ? "file" : "files", diffstat.getInsertions(), diffstat.getDeletions(), isFastForward ? "previous revision" : "merge base", compareUrl));
        // note commit additions on a rebase,if any
        switch(lastChange.patchset.type) {
            case Rebase:
                if (lastChange.patchset.added > 0) {
                    sb.append(SOFT_BRK);
                    sb.append(MessageFormat.format("{0} {1} added.", lastChange.patchset.added, lastChange.patchset.added == 1 ? "commit" : "commits"));
                }
                break;
            default:
                break;
        }
        sb.append(HARD_BRK);
    } else if (lastChange.isStatusChange()) {
        if (newTicket) {
            fieldExclusions.add(Field.status);
            fieldExclusions.add(Field.title);
            fieldExclusions.add(Field.body);
            pattern = "**{0}** created this ticket.";
            sb.append(MessageFormat.format(pattern, user.getDisplayName()));
        } else if (lastChange.hasField(Field.mergeSha)) {
            // closed by merged
            pattern = "**{0}** closed this ticket by merging {1} to {2}.";
            // identify patchset that closed the ticket
            String merged = ticket.mergeSha;
            for (Patchset patchset : ticket.getPatchsets()) {
                if (patchset.tip.equals(ticket.mergeSha)) {
                    merged = patchset.toString();
                    break;
                }
            }
            sb.append(MessageFormat.format(pattern, user.getDisplayName(), merged, ticket.mergeTo));
        } else {
            // workflow status change by user
            pattern = "**{0}** changed the status of this ticket to **{1}**.";
            sb.append(MessageFormat.format(pattern, user.getDisplayName(), lastChange.getStatus().toString().toUpperCase()));
        }
        sb.append(HARD_BRK);
    } else if (lastChange.hasReview()) {
        // review
        Review review = lastChange.review;
        pattern = "**{0}** has reviewed patchset {1,number,0} revision {2,number,0}.";
        sb.append(MessageFormat.format(pattern, user.getDisplayName(), review.patchset, review.rev));
        sb.append(HARD_BRK);
        String d = settings.getString(Keys.web.datestampShortFormat, "yyyy-MM-dd");
        String t = settings.getString(Keys.web.timeFormat, "HH:mm");
        DateFormat df = new SimpleDateFormat(d + " " + t);
        List<Change> reviews = ticket.getReviews(ticket.getPatchset(review.patchset, review.rev));
        sb.append("| Date | Reviewer      | Score | Description  |\n");
        sb.append("| :--- | :------------ | :---: | :----------- |\n");
        for (Change change : reviews) {
            String name = change.author;
            UserModel u = userManager.getUserModel(change.author);
            if (u != null) {
                name = u.getDisplayName();
            }
            String score;
            switch(change.review.score) {
                case approved:
                    score = MessageFormat.format(addPattern, change.review.score.getValue());
                    break;
                case vetoed:
                    score = MessageFormat.format(delPattern, Math.abs(change.review.score.getValue()));
                    break;
                default:
                    score = "" + change.review.score.getValue();
            }
            String date = df.format(change.date);
            sb.append(String.format("| %1$s | %2$s | %3$s | %4$s |\n", date, name, score, change.review.score.toString()));
        }
        sb.append(HARD_BRK);
    } else if (lastChange.hasComment()) {
        // comment update
        sb.append(MessageFormat.format("**{0}** commented on this ticket.", user.getDisplayName()));
        sb.append(HARD_BRK);
    } else if (lastChange.hasReference()) {
        // reference update
        String type = "?";
        switch(lastChange.reference.getSourceType()) {
            case Commit:
                {
                    type = "commit";
                }
                break;
            case Ticket:
                {
                    type = "ticket";
                }
                break;
            default:
                {
                }
                break;
        }
        sb.append(MessageFormat.format("**{0}** referenced this ticket in {1} {2}", type, lastChange.toString()));
        sb.append(HARD_BRK);
    } else {
        // general update
        pattern = "**{0}** has updated this ticket.";
        sb.append(MessageFormat.format(pattern, user.getDisplayName()));
        sb.append(HARD_BRK);
    }
    // ticket link
    sb.append(MessageFormat.format("[view ticket {0,number,0}]({1})", ticket.number, ticketService.getTicketUrl(ticket)));
    sb.append(HARD_BRK);
    if (newTicket) {
        // ticket title
        sb.append(MessageFormat.format("### {0}", ticket.title));
        sb.append(HARD_BRK);
        // ticket description, on state change
        if (StringUtils.isEmpty(ticket.body)) {
            sb.append("<span style=\"color: #888;\">no description entered</span>");
        } else {
            sb.append(ticket.body);
        }
        sb.append(HARD_BRK);
        sb.append(HR);
    }
    // field changes
    if (lastChange.hasFieldChanges()) {
        Map<Field, String> filtered = new HashMap<Field, String>();
        for (Map.Entry<Field, String> fc : lastChange.fields.entrySet()) {
            if (!fieldExclusions.contains(fc.getKey())) {
                // field is included
                filtered.put(fc.getKey(), fc.getValue());
            }
        }
        // sort by field ordinal
        List<Field> fields = new ArrayList<Field>(filtered.keySet());
        Collections.sort(fields);
        if (filtered.size() > 0) {
            sb.append(HARD_BRK);
            sb.append("| Field Changes               ||\n");
            sb.append("| ------------: | :----------- |\n");
            for (Field field : fields) {
                String value;
                if (filtered.get(field) == null) {
                    value = "";
                } else {
                    value = filtered.get(field).replace("\r\n", "<br/>").replace("\n", "<br/>").replace("|", "&#124;");
                }
                sb.append(String.format("| **%1$s:** | %2$s |\n", field.name(), value));
            }
            sb.append(HARD_BRK);
        }
    }
    // new comment
    if (lastChange.hasComment()) {
        sb.append(HR);
        sb.append(lastChange.comment.text);
        sb.append(HARD_BRK);
    }
    // insert the patchset details and review instructions
    if (lastChange.hasPatchset() && ticket.isOpen()) {
        if (commits != null && commits.size() > 0) {
            // append the commit list
            String title = isFastForward ? "Commits added to previous patchset revision" : "All commits in patchset";
            sb.append(MessageFormat.format("| {0} |||\n", title));
            sb.append("| SHA | Author | Title |\n");
            sb.append("| :-- | :----- | :---- |\n");
            for (RevCommit commit : commits) {
                sb.append(MessageFormat.format("| {0} | {1} | {2} |\n", commit.getName(), commit.getAuthorIdent().getName(), StringUtils.trimString(commit.getShortMessage(), Constants.LEN_SHORTLOG).replace("|", "&#124;")));
            }
            sb.append(HARD_BRK);
        }
        if (diffstat != null) {
            // append the changed path list
            String title = isFastForward ? "Files changed since previous patchset revision" : "All files changed in patchset";
            sb.append(MessageFormat.format("| {0} |||\n", title));
            sb.append("| :-- | :----------- | :-: |\n");
            for (PathChangeModel path : diffstat.paths) {
                String add = MessageFormat.format(addPattern, path.insertions);
                String del = MessageFormat.format(delPattern, path.deletions);
                String diff = null;
                switch(path.changeType) {
                    case ADD:
                        diff = add;
                        break;
                    case DELETE:
                        diff = del;
                        break;
                    case MODIFY:
                        if (path.insertions > 0 && path.deletions > 0) {
                            // insertions & deletions
                            diff = add + "/" + del;
                        } else if (path.insertions > 0) {
                            // just insertions
                            diff = add;
                        } else {
                            // just deletions
                            diff = del;
                        }
                        break;
                    default:
                        diff = path.changeType.name();
                        break;
                }
                sb.append(MessageFormat.format("| {0} | {1} | {2} |\n", getChangeType(path.changeType), path.name, diff));
            }
            sb.append(HARD_BRK);
        }
        sb.append(formatPatchsetInstructions(ticket, lastChange.patchset));
    }
    return sb.toString();
}
Also used : HashMap(java.util.HashMap) DiffStat(com.gitblit.utils.DiffUtils.DiffStat) ArrayList(java.util.ArrayList) Patchset(com.gitblit.models.TicketModel.Patchset) Review(com.gitblit.models.TicketModel.Review) UserModel(com.gitblit.models.UserModel) Field(com.gitblit.models.TicketModel.Field) HashSet(java.util.HashSet) RevCommit(org.eclipse.jgit.revwalk.RevCommit) PathChangeModel(com.gitblit.models.PathModel.PathChangeModel) TicketModel(com.gitblit.models.TicketModel) Change(com.gitblit.models.TicketModel.Change) IOException(java.io.IOException) Repository(org.eclipse.jgit.lib.Repository) SimpleDateFormat(java.text.SimpleDateFormat) DateFormat(java.text.DateFormat) SimpleDateFormat(java.text.SimpleDateFormat) HashMap(java.util.HashMap) Map(java.util.Map) TreeMap(java.util.TreeMap)

Example 15 with Change

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

the class BranchTicketService method getTicketImpl.

/**
	 * Retrieves the ticket from the repository by first looking-up the changeId
	 * associated with the ticketId.
	 *
	 * @param repository
	 * @param ticketId
	 * @return a ticket, if it exists, otherwise null
	 */
@Override
protected TicketModel getTicketImpl(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;
        }
        TicketModel ticket = TicketModel.buildTicket(changes);
        if (ticket != null) {
            ticket.project = repository.projectPath;
            ticket.repository = repository.name;
            ticket.number = ticketId;
        }
        return ticket;
    } finally {
        db.close();
    }
}
Also used : Repository(org.eclipse.jgit.lib.Repository) TicketModel(com.gitblit.models.TicketModel) Change(com.gitblit.models.TicketModel.Change)

Aggregations

Change (com.gitblit.models.TicketModel.Change)45 TicketModel (com.gitblit.models.TicketModel)32 IOException (java.io.IOException)15 Repository (org.eclipse.jgit.lib.Repository)9 Test (org.junit.Test)9 Patchset (com.gitblit.models.TicketModel.Patchset)8 TicketLink (com.gitblit.models.TicketModel.TicketLink)7 ArrayList (java.util.ArrayList)7 RepositoryModel (com.gitblit.models.RepositoryModel)6 UserModel (com.gitblit.models.UserModel)5 RevCommit (org.eclipse.jgit.revwalk.RevCommit)4 Reference (com.gitblit.models.TicketModel.Reference)3 Ref (org.eclipse.jgit.lib.Ref)3 RevWalk (org.eclipse.jgit.revwalk.RevWalk)3 ReceiveCommand (org.eclipse.jgit.transport.ReceiveCommand)3 PatchsetHook (com.gitblit.extensions.PatchsetHook)2 PathChangeModel (com.gitblit.models.PathModel.PathChangeModel)2 RefModel (com.gitblit.models.RefModel)2 Attachment (com.gitblit.models.TicketModel.Attachment)2 Review (com.gitblit.models.TicketModel.Review)2