You can now vote for scripts to help people know if they're useful or not. Login or create an account to vote!

1

Enforce Gitflow workflow

Overview

This script enforces the Gitflow workflow in pull requests ensuring that only a release branch, or a hotfix, can be merged into the main branch. It can also provide an optional condition for source branches to be merged into the develop branch before being merged into the main branch.

Good to Know

  • This script can be used in a Custom merge check. Create a new merge check, paste this into the 'script' field, and update the Dynamic Form fields.
  • You can define multiple prefixes that qualify as hotfix or release branches.
  • Release branches and hotfixes need to be merged into both main and develop branches. This script can be configured to enforce that a hotfix or release branch is merged into the develop branch first.
  • For more information on the Gitflow workflow, please read Atlassian's documentation here: https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow.

Requirements

  • Bitbucket Bitbucket (6.0 - 7.17)
  • ScriptRunner for Bitbucket ScriptRunner for Bitbucket (6.18.0)
    
import com.atlassian.bitbucket.hook.repository.RepositoryHookResult import com.atlassian.bitbucket.io.SingleLineOutputHandler import com.atlassian.bitbucket.pull.PullRequestService import com.atlassian.bitbucket.repository.Branch import com.atlassian.bitbucket.repository.RefService import com.atlassian.bitbucket.repository.Repository import com.atlassian.bitbucket.repository.RepositoryBranchesRequest import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory import com.atlassian.bitbucket.util.Page import com.atlassian.bitbucket.util.PageProvider import com.atlassian.bitbucket.util.PageRequest import com.atlassian.bitbucket.util.PagedIterable import com.atlassian.sal.api.component.ComponentLocator import com.onresolve.scriptrunner.parameters.annotation.Checkbox import com.onresolve.scriptrunner.parameters.annotation.ShortTextInput import com.onresolve.scriptrunner.runner.ScriptRunnerImpl /** * The name of the main branch */ @ShortTextInput(label = 'Main Branch', description = "The name of the main branch.") String MAIN_BRANCH /** * The name of the develop branch */ @ShortTextInput(label = 'Develop Branch', description = "The name of the develop branch.") String DEVELOP_BRANCH /** * A list of prefixes that can be used for release branches. * For example, if you add 'release' as a supported prefix, you should be able to merge a branch named 'release/acme1.2.3' into main. */ @ShortTextInput(label = 'Release branch prefixes', description = "The prefixes that are used for release branches, separated by commas.") String RELEASE_PREFIXES_STR String[] releaseBranchPrefixes = RELEASE_PREFIXES_STR.trim().split("\\s*,\\s*") /** * A list of prefixes that can be used for hotfix branches. * For example, if you add 'bug' as a supported prefix, you should be able to merge a branch named 'bug/ABC-123' into main. */ @ShortTextInput(label = 'Hotfix branch prefixes', description = "The prefixes that are used for hotfix branches, separated by commas.") String HOTFIX_PREFIXES_STR String[] hotfixBranchPrefixes = HOTFIX_PREFIXES_STR.trim().split("\\s*,\\s*") /** * If set to true, the source branch will need to be merged into the develop branch first. */ @Checkbox(label = 'Require merge into develop', description = "Block the pull request if the changes are not merged into the develop branch first.") boolean REQUIRE_SOURCE_IS_MERGED_INTO_DEVELOP_FIRST Branch getBranch(RefService refService, Repository repository, String branchName) { def repositoryBranchesRequest = new RepositoryBranchesRequest.Builder(repository).build() new PagedIterable(new PageProvider() { @Override Page get(PageRequest pageRequest) { refService.getBranches(repositoryBranchesRequest, pageRequest) as Page } }, 1000).iterator().find { Branch it -> it.displayId == branchName } as Branch } def pullRequestService = ComponentLocator.getComponent(PullRequestService) def refService = ComponentLocator.getComponent(RefService) def pullRequest = pullRequestService.getById(mergeRequest.pullRequest.fromRef.repository.id, mergeRequest.pullRequest.id) def repo = pullRequest.fromRef.repository def sourceBranch = getBranch(refService, repo, pullRequest.fromRef.displayId) def developBranch = getBranch(refService, repo, DEVELOP_BRANCH) def branchMatchesPrefixes(Branch branch, String[] allowedPrefixes) { allowedPrefixes.find { type -> branch.id.startsWith("refs/heads/${type}/") } } if (pullRequest.toRef.displayId != MAIN_BRANCH) { return RepositoryHookResult.accepted() } else { if (branchMatchesPrefixes(sourceBranch, hotfixBranchPrefixes) || branchMatchesPrefixes(sourceBranch, releaseBranchPrefixes)) { // Optionally, make sure that develop branch is up to date if (REQUIRE_SOURCE_IS_MERGED_INTO_DEVELOP_FIRST) { if (!developBranch) { return RepositoryHookResult.rejected("Pull request does not comply with the Gitflow workflow", "Could not locate develop branch with name '${DEVELOP_BRANCH}'") } GitCommandBuilderFactory gitCommandBuilderFactory = ScriptRunnerImpl.getOsgiService(GitCommandBuilderFactory) def command = gitCommandBuilderFactory.builder(repo) .command("branch") .argument("--no-color") .argument("--contains") .argument(sourceBranch.latestCommit) .argument(developBranch.displayId) .build(new SingleLineOutputHandler()) if (command.call().trim() != DEVELOP_BRANCH) { return RepositoryHookResult.rejected("Pull request does not comply with the Gitflow workflow", "Could not find commits in `${developBranch.displayId}`. You need to merge `${sourceBranch.displayId}`" + " into `${developBranch.displayId}` before merging this into '${MAIN_BRANCH}'") } } else { return RepositoryHookResult.accepted() } } else { return RepositoryHookResult.rejected("Pull request does not comply with the Gitflow workflow", "You cannot merge `${sourceBranch.displayId}` into '${MAIN_BRANCH}'. " + "Allowed prefixes for a hotfix branch are: ${hotfixBranchPrefixes.join(", ")}. " + "Allowed prefixes for a release branch are: ${releaseBranchPrefixes.join(", ")}.") } }
Discovered an issue? Report it here

Suggested for you