It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

Bitbucket auto merge pull requests when conditions are met



I am working on making our pull requests merge automatically if a certain set of conditions are true.

However, I have not been successful yet and therefore kindly ask for your help!

I am using the adaptavist ScriptRunner for Bitbucket version 4.5.2 (Self-hosted Bitbucket).

By testing I have figured out that a merge, triggered from adaptavist ScriptRunner, can only happen if the pull request "conditions" are met.
In my case:
✓ Requires 1 approvers
✓ Requires all tasks to be resolved 
✓ Requires a minimum of 1 successful builds 

It seems like build status and issue state is not part of the pull request? I do not know if this is true? But I can not find any information about e.g. build statuses in the documentation here:

I can make the adaptavist ScriptRunner fire events e.g. in case of approval with the following code:

event.pullRequest.reviewers.count {it.approved} >= 1

However if the tasks are not resolved or there are no successful builds the merge will not happen (This is as it should be!)
But when tasks do get resolved or a successful build comes in I cannot get the adaptavist ScriptRunner to trigger, I have tried with the following code:


If my suspicions is true and Bitbucket only handles pull requests events which are defined in git this means that I have no way to check for these events with the adaptavist ScriptRunner right?

Some elegant solution for me would be to make Bitbucket and adaptavist ScriptRunner able to trigger on all events on a pull request, i.e. issue state change in jira, build status from CI, approvals.
Or make a scheduler which checks every 10 seconds if something is ready to be merged. 

I have heard about a workaround where the CI server tries to merge the pull requests if the conditions are met, but I think this is a far from optimal solution.

If anymore information is needed feel free to ask! 

Anyone have any information or helpful thoughts about how I can get this to work?

1 answer

This question took a long time to find. In the future could you add the tag "com.onresolve.jira.stash.groovyrunner" so we can answer more quickly. I have added it for you now.

Your correct the pull request does not hold all this information, even if it did it wouldn't trigger with the auto merge built-in event handler, thats only triggered on a pull request approval. You will need to do a custom event listener.

This is not trivial to do for a number of reasons. The first being that not all events are triggered in Bitbucket, some are in JIRA also.

Although I have got a partial solution for you which comprises of an event listener which is triggered on a pull request approval which then uses an application link between JIRA and Bitbucket to get issue statuses and the BuildStatusService to get the build status

First you need a custom event handler script, adding this an the inline script for events, PullRequestApprovedEvent:

import com.atlassian.bitbucket.event.pull.PullRequestApprovedEvent
import com.atlassian.bitbucket.integration.jira.JiraIssueService
import com.atlassian.bitbucket.pull.PullRequest
import com.atlassian.bitbucket.pull.PullRequestMergeRequest
import com.atlassian.bitbucket.pull.PullRequestService
import com.atlassian.bitbucket.user.SecurityService
import com.atlassian.bitbucket.util.Operation
import com.atlassian.sal.api.component.ComponentLocator
import com.onresolve.scriptrunner.canned.bitbucket.util.BitbucketBaseScript
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import groovy.transform.BaseScript

//✓ Requires 1 approvers
//✓ Requires all tasks to be resolved
//✓ Requires a minimum of 1 successful build

@BaseScript BitbucketBaseScript baseScript

def buildStatusService = ComponentLocator.getComponent(BuildStatusService)
def jiraIssueService = ComponentLocator.getComponent(JiraIssueService)
def securityService = ComponentLocator.getComponent(SecurityService)
def pullRequestService = ComponentLocator.getComponent(PullRequestService)

def pullRequests = []
def repository = null

if (event instanceof PullRequestApprovedEvent) {
    def event = event as PullRequestApprovedEvent

// we need to gather the pull requests for each event using above approach

// go through each pull request and check conditions
pullRequests.each { pullRequest ->
    // require 1 approver
    if (pullRequest.participants.find { it.approved }) {
        def summaries = buildStatusService.getSummaries(pullRequest.getCommits()*.id)

        def successfulBuild = summaries.values().find { it.successfulCount > 0 }

        // require 1 successful build
        if (successfulBuild) {

            def issueKeys = jiraIssueService.getIssuesForPullRequest(repository,*.key

            def jiraLink = getJiraAppLink()

            def authenticatedRequestFactory = jiraLink.createImpersonatingAuthenticatedRequestFactory()

            def allIssuesResolved = issueKeys.every { String key ->
                def response = authenticatedRequestFactory
                    .createRequest(Request.MethodType.GET, "/rest/api/2/issue/$key?fields=status")
                    .addHeader("Content-Type", "application/json")
                    .executeAndReturn(new ReturningResponseHandler<Response, Map>() {
                    Map handle(Response response) throws ResponseException {
                        if (response.statusCode != HttpURLConnection.HTTP_OK) {
                            throw new Exception(response.getResponseBodyAsString())

                        return response.getEntity(Map)

       == "Done"

            // require all issues to be resolved - this doesn't handle subtasks or linked issues
            if (allIssuesResolved) {
                mergePullRequest(securityService, pullRequest, pullRequestService)

private Object mergePullRequest(SecurityService securityService, PullRequest pullRequest, pullRequestService) {
    securityService.impersonating(, "Merging pull-request on behalf of author").call(new Operation<Object, RuntimeException>() {
        public Object perform() {
            pullRequestService.merge(new PullRequestMergeRequest.Builder(pullRequest).message("Automatically merged").build())

This would be more optimal than having a service poll every few minutes.Unfortunately this event handler won't fire when all the subtasks are resolved in JIRA or when the build status changes for the pull request. I haven't quite got a solution for that currently. I will update this when I have, but this should get you going.

At a high level, maybe consider adding a REST endpoint in Bitbucket and calling it from your build server once the build is complete and successful, then publishing an event from that endpoint inside Bitbucket which will fire the above listener. Also could be the same if you add a scripted post-function on a JIRA workflow transition to signal when an issue is done and call an endpoint in Bitbucket.

You can publish events using the code below:

def eventPublisher = ComponentLocator.getComponent(EventPublisher)
// change for your event
eventPublisher.publish(new RepositoryPushEvent("", repository, []))

Its just mapping the build back to the actual pull request which is tricky.

Hope this helps,

Suggest an answer

Log in or Sign up to answer
Community showcase
Published in Bitbucket

Atlassian supported Jenkins integration for Bitbucket Server

We’ve been building a plugin to integrate Bitbucket Server and Jenkins CI, and I’m excited to announce that our alpha is ready to download and install. It lets you seamlessly configure a Jenkins job ...

395 views 0 9
Read article

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you