Stash is now called Bitbucket Server. Read our announcement blog.

One of the most highly requested features in Stash has been commit hooks.

Git has an in-built mechanism called “hooks” which allows you to hook into just before and after a push event. In Git, hooks are scripts that must be placed on the filesystem in each repository, which requires a system administrator with the appropriate access to copy scripts around manually. That’s not exactly a great user experience – we can do better! We started dreaming up ways to make Git hooks more accessible to the masses…

  • Users can create hooks without having to write bash scripts and upload them to the filesystem.
  • Hooks will be able to harness the full power of the Altassian ecosystem.
  • Users will be able to share and sell their hooks on the Atlassian Marketplace.

Pre and Post Receive Hooks

Currently there are three integration points in the recently released Stash 2.2 for hooks:

  • Pre-receive hooks
  • Merge checks
  • Post-receive hooks

Pre-receive mirrors the behaviour of the Git hook and will run when a client pushes a branch or branches to Stash. It’s important to note that this hook isn’t called when merging a pull-request. For that you can define a merge-check, which allows you to veto a potential merge of a pull-request until one or more conditions are met. This might include requiring a minimum number of acceptances, or that all the associated builds are green (so you don’t break master). A post-receive hook will fire when a push or merge has been successful, allowing you to notify external systems, like Jira or Bamboo, of changes to a given repository. Doing all this in a shell script is certainly possible, but it isn’t going to make use of Stash’s powerful API and ecosystem which is perfect for building hooks.

To assist in making a kick-ass developer experience, not only should you be able to create a custom hook, it should also be dead-simple to add custom settings. Currently, storing data against a repository can be done manually with plugin settings or Active Objects; for simple plugins this is too much overhead. Finally we wanted a sexy UI that would allow these hooks to be enabled/disabled and configured on a per-repository basis. Something like this.

Less talk, more code

As a simple example, let’s create a hook that notifies members of a team that new changes are available. A good way to broadcast that might be to use Hipchat, a team-based group chat and IM client.

[cc lang='java']
public class HipchatNotifyHook implements AsyncPostReceiveRepositoryHook, RepositorySettingsValidator {

    private static final String AUTH_TOKEN_KEY = "authToken";
    private static final String ROOM_NAME_KEY = "roomName";

    @Override
    public void postReceive(HookContext context, Collection refChanges) {
        String room = context.getSettings().getString(ROOM_NAME_KEY);
        createClient(context.getSettings()).notifyRoom(room, "Stash", HipchatTemplates.buildHtml(refChanges));
    }

    @Override
    public void validate(Settings settings, SettingsValidationErrors errors, Repository repository) {
        if (createClient(settings).authTest()) {
            errors.addFieldError(AUTH_TOKEN_KEY, "Invalid authorization token");
        }
    }

    private HipchatProxyClient createClient(Settings settings) {
        return new HipchatProxyClient(settings.getString(AUTH_TOKEN_KEY));
    }
}
[/cc]

This hook needs to be declared in the atlassian-plugin.xml with the new repository-hook module descriptor.

[cc lang='xml' escaped='true']
<repository-hook key="hipchat-hook" name="Hipchat Push Notification" class="com.atlassian.stash.plugin.hooks.hipchat.HipchatNotifyHook">
    <description>Sends a notice to the specified Hipchat room whenever someone pushes to the repository.</description>
    <config-form>
        <view>com.atlassian.stash.plugin.hooks.hipchat.hipChatConfigForm</view>
        <directory location="/static/" />
    </config-form>
</repository-hook>
[/cc]

And finally create the matching configuration form using AUI Soy templates:

[cc lang='js']
{namespace com.atlassian.stash.plugin.hooks.hipchat}

/**
 * @param? config
 * @param? errors
 */
{template .hipChatConfigForm}
    {call aui.form.textField}
        {param id: 'roomName' /}
        {param value: $config ? $config.roomName : null /}
        {param labelContent: 'Room Name' /}
        {param description: 'Name of the Hipchat room (or ID)' /}
        {param errorTexts: $errors ? $errors.roomName : null /}
        {param isRequired: true /}
    {/call}
    {call aui.form.textField}
        {param id: 'authToken' /}
        {param value: $config ? $config.authToken : null /}
        {param labelContent: 'API Token' /}
        {param description: 'Your Hipchat API token. You need a \'user\' level token. Generate one at https://yourdomain.hipchat.com/group_admin/api.' /}
        {param errorTexts: $errors ? $errors.authToken : null /}
        {param isRequired: true /}
    {/call}
{/template}
[/cc]

That’s it! And the result?

The Hipchat post-receive hook is available out-of-the-box in Stash 2.2. For more information on everything related to developing your own hook, see the how-to guide.

More Hooks

Some other awesome hooks that are now possible include:

  • Reject force pushes, possibly on specific branches
  • Require PRs to have a specified number of green Bamboo builds
  • Require that specific branches, like master, only be updated via PR merges
  • And lots more!

I’m excited by the release of this feature, but I’m more excited to see what you, our users, will be able to build that we have not even imagined yet.

Go forth and get plugged hooked in!

Hint: A Stash Git hook might be a perfect add-on opportunity for an entry into Atlassian Codegeist (which is offering a $10,000 prize for an awesome Stash add-on).

Hooked on Stash