Repository Hooks and Merge Checks

Introduction

For users familiar with the Git hook system, Bitbucket Server now provides a way for plugin developers to create their own hooks that can be installed and managed without having to touch the filesystem.

Hooks have been implemented using familiar modules within the Atlassian plugin system.

There are countless examples of hooks that could be deployed to a Bitbucket Server instance:

  • Restrict deletion of branches
  • Restrict 'force-pushes' to select branches
  • Integrate with a CI server (e.g. Bamboo) or other 3rd party application, to notify when code is pushed or branches are created, merged or deleted
  • Require that pull requests have a minimum number of reviewers who have accepted
  • Enforce a specific workflow (e.g. master cannot be merged into a release branch)
  • Mirror a repository with another instance of Bitbucket Server
  • Ensure all commits contain a JIRA key

Bitbucket Server supports this through Repository Hook Modules.

Categories

Currently there are three types of hooks that are available in Bitbucket Server.

Pre-receive

Pre-receive hooks are used to potentially block pushes to Bitbucket Server. An example might be to stop deletion of branches or limit force-pushes to personal branches. Pre-receive hooks do not fire when a pull-request is merged - for that, you may also want to implement a repository merge-check.

Asynchronous Post-receive

Post-receive hooks are invoked after a push or merge has taken place. An example might be to notify a build server, such as Bamboo, that there are new commits on a branch to be built.

Asynchronous post-receive hooks are preferred when there is no need to return any information to the client, as they are triggered after the push is complete. This is a better experience for the user as they will not have to wait for potentially slow operations.

Merge-checks

Merge checks are used to block pull requests from being merged until a required set of conditions have been met. An example might be that all reviewers must accept the request, or that any associated builds are green.

A RepositoryMergeRequestCheck has similar functionality to the Merge Check Plugin Module, but additionally allows users to enable, disable and provide configuration on a per-repository basis via the Bitbucket Server UI.

How-to

Repository hook video tutorial

Learn to build a simple repository hook from scratch in this 30 minute video tutorial.

Note: This tutorial pre-dates the renaming of the product to "Bitbucket Server" from "Stash", but is still a useful resource.

Generate a Bitbucket Server plugin using the Atlassian SDK

Ensure you have a Bitbucket Server plugin which will contain your hook, which you can create by following this guide. Don't forget to add bitbucket-spi to your pom.xml as this will be required to compile against the hook API.

Enabling your hook

Repository hooks need to be enabled and configured on a per repository basis, details are here.

Examples

Javadoc

For Java API specific documentation, please see the accompanying Javadoc for the revelant classes/packages.

Configuration

It is possible to add configuration to your hook by specifying a configuration screen. The values of input elements defined in the screen will be saved by the framework and passed to the hook each time it is called. Generally we use AUI templates to generate our controls to keep styling the same, however a simple element <input name="x" type="text"/> would work.

Hook configuration screens can be defined using Closure Templates. Atlassian is currently running an older version of Closure Templates, so you might find the best documentation in the Internet Archive.

Bitbucket Server provides bitbucket.component.branchSelector field and input templates that you can include to let users select a branch or tag.

Atlassian User Interface (AUI)

A number of AUI Soy templates are also available to assist in creating a configuration form for your hook. For more information about AUI please read the AUI Docs or try out the live demos in the sandbox. For an a list of available AUI Soy templates that you can use in your own configuration form, consult the AUI Soy source.

Example

This template adds an AUI style text field with the id "url", user inputted text data will be saved with the key "url" and passed to the hook inside a Settings object.

/**
 * @param config
 * @param? errors
 */
{template .formContents}
    {call aui.form.textField}
        /** set the name of the input field -- this will be used as the setting key */
        {param id: 'url' /}
        /** supply the previous value to the text field so the user can see the saved data */
        {param value: $config['url'] /}
        {param labelContent: getText('bitbucket.web.test.hook.config.label') /}
        {param descriptionText: getText('bitbucket.web.test.hook.config.description') /}
        {param extraClasses: 'long' /}
        /** if validation returned errors then display them in the error text. */
        {param errorTexts: $errors ? $errors['url'] : null /}
    {/call}
{/template}

The matching hook accesses the "url" value and also validates it.

public class MyRepositoryHook implements AsyncPostReceiveRepositoryHook, RepositorySettingsValidator {

    @Override
    public void postReceive(RepositoryHookContext context, Collection<RefChange> refChanges) {
        String url = context.getSettings().getString("url");
        if (url != null) {
            //do stuff
        }
    }

    @Override
    public void validate(Settings settings, SettingsValidationErrors errors, Repository repository) {
        if (settings.getString("url", "").isEmpty()) {
            errors.addFieldError("url", "Url field is blank, please supply one");
        }
    }
}

Adding Icons

Each hook can have an icon to display in the repository hook administration screen. See the repository hook module type for details.

Marketplace

If and when you decide to add your plugin to Atlassian's Marketplace, you can make your plugin more discoverable by selecting the "Repository Hooks" category when creating your plugin.

Tips

Hooks and Forks

With the introduction of Forks in Stash 2.4 there are some extra considerations with regard to hooks. Due to the nature of forks, hooks on the parent repository will not fire for pushes made to the forked copy. When those changes are merged into the parent repository via a pull request, only merge checks are run, not pre-receive hooks. Instead, in addition to any checks that you wish to enforce with a pre-receive hook, consider also adding a companion merge-check that ensures the same set of conditions still hold, which may be crucial for quality control.

For more information please see Workflow strategies in Bitbucket Server.

Performance

When writing a merge-check, it is important to realise that they are run on every page view of a pull request. This impacts the kinds of operations that can or should be run, such as contacting an external service or performing CPU-intensive tasks. In the case of slow operations the caching of results, based on something like the pull request commit, may need to be considered.

Thread safety

In both examples the repository hooks follow the convention that the module class should be written to be thread-safe. If in-memory state or caching needs to be done then it is suggested that a separate 'component' be injected into the hook.

When writing hooks keep in mind that the ordering of the hooks is non-deterministic, and shouldn't be relied on. Passing state between hooks of the same type is fraught with danger and should not be attempted; each hook should be completely standalone.