package com.monitorjbl.plugins; import com.atlassian.bitbucket.build.BuildStatusSetEvent; import com.atlassian.bitbucket.event.pull.PullRequestParticipantStatusUpdatedEvent; import com.atlassian.bitbucket.permission.Permission; import com.atlassian.bitbucket.pull.PullRequest; import com.atlassian.bitbucket.pull.PullRequestMergeRequest; import com.atlassian.bitbucket.pull.PullRequestSearchRequest; import com.atlassian.bitbucket.pull.PullRequestService; import com.atlassian.bitbucket.pull.PullRequestState; import com.atlassian.bitbucket.repository.Repository; import com.atlassian.bitbucket.user.SecurityService; import com.atlassian.bitbucket.util.Page; import com.atlassian.bitbucket.util.PageRequest; import com.atlassian.bitbucket.util.PageRequestImpl; import com.atlassian.event.api.EventListener; import com.monitorjbl.plugins.config.Config; import com.monitorjbl.plugins.config.ConfigDao; import java.util.concurrent.atomic.AtomicBoolean; public class PullRequestListener { private static final String PR_APPROVE_BUCKET = "AUTOMERGE_PR_APPROVAL"; private static final String BUILD_APPROVE_BUCKET = "AUTOMERGE_BUILD_APPROVAL"; public static final int MAX_COMMITS = 1048576; public static final int SEARCH_PAGE_SIZE = 50; private final AsyncProcessor asyncProcessor; private final ConfigDao configDao; private final PullRequestService prService; private final SecurityService securityService; private final RegexUtils regexUtils; public PullRequestListener(AsyncProcessor asyncProcessor, ConfigDao configDao, PullRequestService prService, SecurityService securityService, RegexUtils regexUtils) { this.asyncProcessor = asyncProcessor; this.configDao = configDao; this.prService = prService; this.securityService = securityService; this.regexUtils = regexUtils; } @EventListener public void prApprovalListener(PullRequestParticipantStatusUpdatedEvent event) { asyncProcessor.dispatch(new ApprovalTaskProcessor(event.getPullRequest())); } @EventListener public void buildStatusListener(BuildStatusSetEvent event) { asyncProcessor.dispatch(new BuildTaskProcessor(event.getCommitId())); } void automergePullRequest(PullRequest pr) { Repository repo = pr.getToRef().getRepository(); Config config = configDao.getConfigForRepo(repo.getProject().getKey(), repo.getSlug()); String toBranch = regexUtils.formatBranchName(pr.getToRef().getId()); String fromBranch = regexUtils.formatBranchName(pr.getFromRef().getId()); if((regexUtils.match(config.getAutomergePRs(), toBranch) || regexUtils.match(config.getAutomergePRsFrom(), fromBranch)) && !regexUtils.match(config.getBlockedPRs(), toBranch) && prService.canMerge(repo.getId(), pr.getId()).canMerge()) { securityService.impersonating(pr.getAuthor().getUser(), "Performing automerge on behalf of " + pr.getAuthor().getUser().getSlug()).call(() -> { prService.merge(new PullRequestMergeRequest.Builder(pr).build()); return null; }); } } PullRequest findPRByCommitId(String commitId) { PullRequestSearchRequest request = new PullRequestSearchRequest.Builder() .state(PullRequestState.OPEN) .withProperties(false) .build(); PageRequest nextPage = new PageRequestImpl(0, SEARCH_PAGE_SIZE); do { Page<PullRequest> page = prService.search(request, nextPage); PullRequest pr = searchForPR(page, commitId); if(pr != null) { return pr; } else { nextPage = page.getNextPageRequest(); } } while(nextPage != null); return null; } private PullRequest searchForPR(Page<PullRequest> requests, String commitId) { for(PullRequest pr : requests.getValues()) { AtomicBoolean found = new AtomicBoolean(false); prService.streamCommits(pr.getToRef().getRepository().getId(), pr.getId(), commit -> { found.set(commit.getId().equals(commitId)); return !found.get(); }); if(found.get()) { return pr; } } return null; } private class ApprovalTaskProcessor implements Runnable { private final PullRequest pr; public ApprovalTaskProcessor(PullRequest pr) { this.pr = pr; } @Override public void run() { securityService.withPermission(Permission.ADMIN, "Automerge check (PR approval)").call(() -> { automergePullRequest(prService.getById(pr.getToRef().getRepository().getId(), pr.getId())); return null; }); } } private class BuildTaskProcessor implements Runnable { private final String commitId; public BuildTaskProcessor(String commitId) { this.commitId = commitId; } @Override public void run() { securityService.withPermission(Permission.ADMIN, "Automerge check (PR approval)").call(() -> { PullRequest pr = findPRByCommitId(commitId); if(pr != null) { automergePullRequest(pr); } return null; }); } } }