Skip to main content
Example scripts
arrow icon
To homepage
Bitbucket
Data centre icon
Data Center

Prevent Branch Creation If Branch Name Lacks a Valid Jira Issue With Specific Status

Features
Pre hooks
Tags
Created 1 year ago, Updated 6 month(s) ago
App in script
ScriptRunner For Bitbucket
ScriptRunner For Bitbucket
by Adaptavist
Compatibility
compatibility bullet
Bitbucket (6.0 - 7.17)
compatibility bullet
ScriptRunner For Bitbucket (7.10.0)
Language |
groovy
import com.atlassian.applinks.api.ApplicationLinkResponseHandler
import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.application.jira.JiraApplicationType
import com.atlassian.bitbucket.hook.repository.RepositoryHookResult
import com.atlassian.bitbucket.repository.MinimalRef
import com.atlassian.bitbucket.repository.RefChangeType
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.sal.api.net.Request
import com.atlassian.sal.api.net.Response
import com.atlassian.sal.api.net.ResponseException
import groovy.json.JsonSlurper

private RepositoryHookResult rejectedResult(String message) {
    RepositoryHookResult.rejected(message, message)
}

private RepositoryHookResult getIssueKeyValidationResult(String issueKey, String requiredStatusName, MinimalRef branchRef) {
    def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService)

    def primaryJiraApplicationLink = applicationLinkService.getPrimaryApplicationLink(JiraApplicationType)

    def authenticatedRequestFactory = primaryJiraApplicationLink.createImpersonatingAuthenticatedRequestFactory() ?:
            primaryJiraApplicationLink.createAuthenticatedRequestFactory()

    authenticatedRequestFactory.createRequest(Request.MethodType.GET, "rest/api/2/issue/$issueKey")
            .addHeader('Content-Type', 'application/json')
            .execute(new ApplicationLinkResponseHandler<RepositoryHookResult>() {
                @Override
                RepositoryHookResult credentialsRequired(Response response) throws ResponseException {
                    rejectedResult(
                            "Please authenticate with Jira: ${authenticatedRequestFactory.authorisationURI}"
                    )
                }

                @Override
                RepositoryHookResult handle(Response response) throws ResponseException {
                    if (!response.successful) {
                        if (response.statusCode == 404) {
                            return rejectedResult(
                                    "Branch with name: ${branchRef.displayId} matches Jira issue key: $issueKey but no issue with this key exists, branch creation cancelled."
                            )
                        }

                        log.warn("Jira call failed, status: ${response.statusCode}.")

                        return rejectedResult(
                                'Validation of issue key in branch name failed, please contact your system administrator.'
                        )
                    }

                    def issueJson = new JsonSlurper().parseText(response.responseBodyAsString) as Map<String, Object>
                    def statusName = issueJson['fields']['status']['name'] as String

                    if (!requiredStatusName.equalsIgnoreCase(statusName)) {
                        return rejectedResult(
                                "Issue with key: $issueKey was found from branch name, but its status: ($statusName) does not match the required status name: ($requiredStatusName)."
                        )
                    }

                    RepositoryHookResult.accepted()
                }
            })
}

def builder = new RepositoryHookResult.Builder()

/// The name of the status which the issue from the branch name must be in (this is checked case insensitively).
def requiredStatusName = 'In Progress'

// A regular expression for extracting Jira issue keys from branch names, this is the default regular expression used by Bitbucket.
// If your Jira issue keys do not match the default pattern, alterations will need to be made here.
def jiraIssueKeyPattern = ~'(?<=^|[a-z]\\-|[\\s\\p{Punct}&&[^\\-]])([A-Z][A-Z0-9_]*-\\d+)(?![^\\W_])'

// Find all created refs which relate to a branch creation
def createdBranchRefs = refChanges.find { it.type == RefChangeType.ADD && it.refId.startsWith('refs/heads') }*.ref

// For each created branch ref, check the branch name for an issue key, and then verify the status of that issue via a linked Jira instance.
createdBranchRefs.each { branchRef ->
    def matcher = branchRef.id =~ jiraIssueKeyPattern

    if (matcher.find()) {
        def issueKey = matcher.group(1)

        builder.add(getIssueKeyValidationResult(issueKey, requiredStatusName, branchRef))
    } else {
        def message = "Branch with name: ${branchRef.displayId} does not contain a reference to a Jira issue."

        builder.add(rejectedResult(message))
    }
}

builder.build()
Having an issue with this script?
Report it here