Example scripts
To homepage
Bitbucket

Custom Bitbucket Commit Message Validation
App in script

ScriptRunner For Bitbucket
by Adaptavist
Compatibility

Bitbucket (6.0 - 7.17)

ScriptRunner For Bitbucket (7.10.0)
Language |
groovy
/**
* Copyright © 2020, StoneX/Gain Capital
*
* This script is released under the BSD 3-clause license: https://opensource.org/licenses/BSD-3-Clause
*
* Pre-hook checks if the message begins with ticket key in order to achieve better linkage of stories, builds,
* deployments and change request tickets through automated pipelines and improved audit.
*/
boolean isSquashedCommit(String[] commitMessage) {
// We only handle squashed commits done via Bitbucket when merging PR
// For local squashed merge, the user is expected to add [JIRA-ID]
def isSquashedCommit = false
def isPrMerge = (commitMessage[0] =~ /^Merge pull request #\d+ in.*/).find()
if (isPrMerge) {
isSquashedCommit = (commitMessage[2] =~ /^Squashed commit of the following:$/).find()
if (isSquashedCommit) {
// We check commits in the squashed commit. This is just additional check to deter
// spoofing and there should be at least one commit ID
isSquashedCommit = false
commitMessage.any { line ->
// E.g. commit 3414e31e819bcff53b8c9e4387c1eaec840a294e
// Commit hash is always length of 40
def commitIdFound = (line =~ /^commit [A-Za-z0-9]{40,40}/).find()
if (commitIdFound) {
isSquashedCommit = true
return true // break 'any' closure
}
}
}
}
isSquashedCommit
}
try {
def success = true
refChanges.each { refChange ->
refChange.getChangesets(repository).each { changeset ->
def commit = changeset.toCommit
def commitMessage = changeset.toCommit.message
def splitMessage = commitMessage.split('\n')
def committer = changeset.toCommit.committer.name.toLowerCase()
def author = changeset.toCommit.author.name.toLowerCase()
// The names of all service accounts begin with "svc."
def isCommitterSvc = (committer =~ /^svc.*/).find()
def isAuthorSvc = (author =~ /^svc.*/).find()
//Merge commits have two parents. If size is greater than 0 then it means its a merge commit
def isItMergeCommit = commit.parents.size() > 1
def isItRevertCommit = false
def isSquashedCommit = (isSquashedCommit(splitMessage as String[]) && commit.parents.size() == 1)
if (commitMessage.startsWith('Revert')) {
isItRevertCommit = (splitMessage.size() >= 3 && splitMessage[0] =~ /^Revert/).find() && (splitMessage[2] =~ /^This reverts commit/).find()
}
if (!commitMessage) {
// This should not happen as we have commit message control so we simply log and ignore
log.warn("No commit message in ${commit.displayId}")
} else if (!isItMergeCommit && !isItRevertCommit && !isCommitterSvc && !isAuthorSvc && !isSquashedCommit) {
// Message have to begin with [JIRA-ID] or "[EMERGENCY]" word
def issueMatcher = commitMessage =~ /^\s*\[+[A-Z0-9]+-[0-9]+\]+|^\s*\[EMERGENCY\]/
def isIssueInMessage = issueMatcher.find()
if (!isIssueInMessage) {
hookResponse.out().println("Commit " +
commit.displayId +
" message must contain Jira ticket ID associated with this commit." +
" https://.../display/ABC/Commit+JIRA+id+control")
success = false
}
}
}
}
return success
}
catch (Exception ex) {
log.error("Exception: ${ex}")
return false
}
Having an issue with this script?
Report it here