Hello,
my company uses
With the help of the ScriptRunner pre-hook "Require commits to be associated with a valid Jira issue", I want to ensure that very certain branches (feature/, bugfix/ or hotfix/ with -bbb- in the following name) can only be created in our Git repository in case the corresponding Jira ticket is of the type Defect or Story. It is fine to check this only for new branches and not for any legacy ones which are already part of the repository.
The hook that I have created works like a charm in case the branches to be checked are created locally and then pushed either via CLI or any local Git client.
However, when creating branches right from a Jira ticket, the hook does not seem to work.
Other ScriptRunner pre-hooks, like "Branch and tag naming standards enforcement", that we use to generally ensure proper branch names, also work when branches are created from a Jira ticket (veto/error message is shown in Jira UI). My assumption is, that while the "Branch and tag naming standards enforcement" pre-hook simply checks against a regex, the "Require commits to be associated with a valid Jira issue" pre-hook additionally establishes the connection to Jira what might end up in a race condition-like behavior (but again, that's just a guess).
Does anybody have a clue why the "Require commits to be associated with a valid Jira issue" pre-hook does not work when the branches are created from a Jira ticket (and how to solve it)?
Please see the config of the affected hook below:
Condition:
// block git push of new branches if they are proper BBB source branches
// (short-living and "-bbb-" in the name) but the corresponding ticket is not
// of the expected type (as per the JQL clause below)
import com.atlassian.bitbucket.repository.RefChangeType
refChanges.any {
it.type == RefChangeType.ADD && it.ref.displayId.matches(/^(feature|bugfix|hotfix)\/\S+?-bbb-\S+$/)
}
JQL clause template:
type in (Story, Defect)
Thanks and best regards
Sascha
The “Require commits to be associated with a valid Jira issue” built-in pre-hook actually works as expected.
As the name of the pre-hook suggests, the pre-hook validation is based on commits. A push is allowed even the branch name doesn’t contain issue key as long as the commits contain a valid one.
I can confirm that an empty branch without associated Jira issue key can be successfully pushed from client machine.
Having said this, if you want to validate branch name on push and UI. This library script does exactly that. So, I have modified it to validate issue type instead of status name:
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, List<String> requiredIssueTypeNames, 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 issueTypeName = issueJson['fields']['issuetype']['name'] as String
if (!(issueTypeName in requiredIssueTypeNames)) {
return rejectedResult(
"Issue with key: $issueKey was found from branch name, but its issue type name: ($issueTypeName) does not match the required issue type names: ($requiredIssueTypeNames)."
)
}
RepositoryHookResult.accepted()
}
})
}
def builder = new RepositoryHookResult.Builder()
def requiredIssueTypeNames = ['Story', 'Defect']
// 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, requiredIssueTypeNames, branchRef))
} else {
def message = "Branch with name: ${branchRef.displayId} does not contain a reference to a Jira issue."
builder.add(rejectedResult(message))
}
}
builder.build()
Please also remember to set triggers to both “branch-create” and “push”.
Hey @Sascha SK ,
I unfortunately do not have an answer for you. But seeing that no one has responded in awhile, you may want to try creating a Support Ticket with us for one of our Support Engineers to investigate with you.
Regards,
Sean
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
thanks for your response. That's exactly what I did this week. Waiting for feedback from your team now.
For the others: I will share the outcome of the support ticket here whenever there is something to share.
Best regards
Sascha
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.