Asynchronous Post-Receive with simple configuration

Here is an example of an asynchronous post-receive hook that notifies a specific url endpoint of changes. This might be useful for notifying your CI build server that Bitbucket Server has been updated, rather than polling. The source is available for download from Bitbucket here.

src/main/resources/atlassian-plugin.xml

<atlassian-plugin key="com.atlassian.bitbucket.server.bitbucket-docs" name="Bitbucket Server - Documentation" plugins-version="2">
    <plugin-info>
        <description>A basic post-receive hook that notifies a remote server when changes are pushed to Bitbucket Server</description>
        <vendor name="Atlassian" url="http://www.atlassian.com"/>
        <version>4.7.1</version>
    </plugin-info>

    <resource type="i18n" name="i18n" location="i18n/post-receive-hook" />

    <repository-hook key="examplehook" name="Webhook" class="com.mycompany.example.plugin.myhook.MyRepositoryHook">
        <description>Webhook for notifying a configured endpoint of changes to this repository.</description>
        <icon>icons/example.png</icon>
        <config-form name="Simple Hook Config" key="simpleHook-config">
            <view>bitbucket.config.example.hook.simple.formContents</view>
            <directory location="/static/"/>
        </config-form>
    </repository-hook>

</atlassian-plugin>

src/main/java/com/mycompany/example/plugin/myhook/MyRepositoryHook.java

package com.mycompany.example.plugin.myhook;

import com.atlassian.bitbucket.hook.repository.*;
import com.atlassian.bitbucket.repository.*;
import com.atlassian.bitbucket.setting.*;

import javax.annotation.Nonnull;
import java.net.URL;
import java.util.Collection;

/**
 * Note that hooks can implement RepositorySettingsValidator directly.
 */
public class MyRepositoryHook implements AsyncPostReceiveRepositoryHook, RepositorySettingsValidator {

    /**
     * Connects to a configured URL to notify of all changes.
     */
    @Override
    public void postReceive(@Nonnull RepositoryHookContext context, @Nonnull Collection<RefChange> refChanges) {
        String url = context.getSettings().getString("url");
        if (url != null) {
            try {
                new URL(url).openConnection().getInputStream().close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

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

Note the absence of HookResponse in the method signature, which comes from being asynchronous. This is important to ensure that pushes are not affected by slow hooks.

src/main/resources/static/simple.soy

{namespace bitbucket.config.example.hook.simple}

/**
 * @param config
 * @param? errors
 */
{template .formContents}
    {call aui.form.textField}
        {param id: 'url' /}
        {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' /}
        {param errorTexts: $errors ? $errors['url'] : null /}
    {/call}
{/template}

src/main/resources/i18n/post-receive-hook.properties

bitbucket.web.test.hook.config.label=URL
bitbucket.web.test.hook.config.description=A URL to be notified