Getting Started

Stash Extension Points

Miscellaneous

Post-receive hooks fire after the push has been received and accepted. They are useful for providing a message to the client.

A typical usage of post-receive hooks is for passing information to a downstream process…​ for example:

  • triggering a build in your Continuous Integration system.

  • posting a message on your internal messaging system, eg HipChat, Slack, Lync.

  • sending mail, for instance when a branch has been created or merged.

You can combine these with conditions, for instance only posting on IM when there is a new commit on a release branch, or a new tag has been created.

If you don’t need to output a message to the client, it may be easier to listen for a RepositoryPushEvent.

Adding a Post-Receive Hook

Navigate to Admin → Script Post Hooks. Click a heading to add a handler. Choose Custom Script Hook to use your own scripts to respond to pushes.

Built-in Post Hooks

Send mail

With this built-in post-receive hook, your Stash instance will send email notifications automatically after a post-receive hook has been triggered.

In order to configure it, a list of repositories for which this trigger will be in effect must be provided, as well as some other information related to the email that will be sent: subject and email templates, email format (plain text or HTML), a list of "to" email addresses and an optional list of "cc" email addresses.

This functionality will mostly require the use of a condition, to avoid sending emails every time something is pushed to a set of repositories.

The following is a basic configuration of the post hook to watch all commits in the repository "test" that involve a file with the .adoc extension:

mail post hook setup

With that configuration and after pushing some commits to the repository, you could get an output like the one shown in the following email:

mail post hook result html

There are some useful links to the stash instance which will only appear in the HTML version of the notification, more specifically you will have access to the Project, Repository, Commit and Change pages.

This functionality can be also configured to send plain text emails, in which case the output would look like the the one in the following screenshot:

mail post hook result plain
The content of the "to addresses" and "cc addresses" should be a list of emails separated by commas or spaces, being the following two examples correct:
- email1@company.com, email2@company.com
- email1@company.com email2@company.com

Pull Request Advisor

This is closely related to blocking out of date pull requests, which has more details of why you would want to smooth the workflow around pull requests.

This post-receive hook will fire if the developer is pushing any changes on a branch, for which a pull request exists with a corresponding from branch. It warns developers if their topic branch is lagging behind their target branch - showing how many commits behind they are, and the date of the first commit on the target branch which is not reachable from their topic branch.

The output will be similar to:

remote:
remote: View pull request for topic => master:
remote:   http://localhost:7990/stash/projects/TEST/repos/test/pull-requests/2
remote:
remote: The branch 'topic' is 2 commit(s) behind master.
remote:   The first commit on the target that you're missing was at Fri Jun 05 14:12:53 BST 2015 (server's timezone).
remote:   Please consider rebasing.
remote:

This gives the developer an idea of how far they are lagging their target branch.

Additionally, should there be conflicts with the target branch, they will also see:

remote:
remote: In addition the following files have conflicts:
remote:
remote:   include/ap_compat.h
remote:   include/mod_request.h
remote:

The intention is to let developers know about merge conflicts as soon as possible, as the earlier you resolve them the easier it is, plus it keeps the feature branch "deliverable".

Respond to pushes with a message

A trivial script that responds to pushes that match the condition with a message. You could use it to warn about upcoming server maintenance, as users will often not open the Stash user interface until they need to create a pull request:

broadcast message

On commit a user will see:

Counting objects: 5, done.
Writing objects: 100% (3/3), 264 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote:
remote: =====================================================================
remote: Server maintenance this weekend 6AM-9AM EST!
remote:
remote: Stash will be unavailable during this time,
remote: please commit locally and push when available.
remote:
remote: Sorry!
remote: =====================================================================
remote:

Another example is to remind users to do some administrative task after creating a release tag for example:

message on tag

On tag:

> git tag 1.1
>
> git push --tags
Total 0 (delta 0), reused 0 (delta 0)
remote:
remote: =====================================================================
remote: You've created a tag, please be sure to to
remote: update the JIRA version.
remote: =====================================================================
remote:
To http://acme.com/stash/scm/test/test.git
 * [new tag]         1.1 -> 1.1

Working with Custom Post-Receive Hooks

The same variables as in pre-receive hooks are available in the binding.

Samples

Update static site

This is an example of deploying static content such as a web site to a web server. Either the head of master could be deployed, or you can make use of a floating release label, called for instance PRODUCTION. When we detect the label has moved we deploy the content to the web server.

Typically you would copy using scp or rsync, which requires password-less SSH access to the remote web server. In this example we’ll just assume that the web server is serving content from a local directory for simplicity.

You could easily extend this to support additional labels, such as STAGING. The staging tag would be at or head of the production tag, and would deploy to a staging server for QA or user acceptance testing.

If you are deploying to a remote web server you can avoid all of this, and just replace with a command such as:

git archive --format zip PRODUCTION | \
    ssh admin@web.acme.com -c " | tar -x -C /path/to/doc/root"

This script looks more complex than it really is. All it does is:

  • If the target directory doesn’t exist, create it

  • If the target directory is empty, clone the repository

  • Update to the PRODUCTION tag

import com.atlassian.stash.hook.HookResponse
import com.atlassian.stash.io.SingleLineOutputHandler
import com.atlassian.stash.repository.RefChange
import com.atlassian.stash.repository.RefChangeType
import com.atlassian.stash.repository.Repository
import com.onresolve.scriptrunner.canned.stash.util.StashBaseScript
import com.onresolve.scriptrunner.canned.stash.util.StashCannedScriptUtils
import groovy.transform.BaseScript

@BaseScript StashBaseScript baseScript (1)

Repository repository = repository
Collection<RefChange> refChanges = refChanges
HookResponse hookResponse = hookResponse

final def productionTag = "PRODUCTION"
final def webDirLocation = System.getProperty("java.io.tmpdir") + "/web/" (2)

if (! refChanges.any { it.refId.startsWith("refs/tags/$productionTag") &&
    it.type in [RefChangeType.ADD, RefChangeType.UPDATE]
}) {
    log.debug("No change to tag, exiting")
    return
}

def target = new File(webDirLocation)
def handler = new SingleLineOutputHandler()

if (! target.exists() ) {
    target.mkdirs()
}

if (! target.list()) { (3)
    gitCommandBuilderFactory.builder(repository)
        .clone()
        .normal()
        .origin(gitScmConfig.getRepositoryDir(repository))
        .directory(webDirLocation)
        .build()
        .call()
}
else {
    gitCommandBuilderFactory.builder(repository) (4)
        .push()
        .refspec("refs/heads/*")
        .refspec("refs/tags/*")
        .force(true)
        .repository(webDirLocation)
        .build(handler)
        .call()
}

gitCommandBuilderFactory.builder() (5)
    .workingDirectory(webDirLocation)
    .command("checkout")
    .argument(productionTag)
    .build(handler)
    .call()

def msg = new StringBuilder("I have updated the website at $webDirLocation for you") (6)
hookResponse.out().print(StashCannedScriptUtils.wrapHookResponse(msg))
1 Extend a base script, which gives us access to gitCommandBuilderFactory
2 Path to web site directory, that should be updated. I am using tmp just for test purposes. This should be an empty directory, or a path that will be created.
3 Target directory doesn’t exist, so create a clone of this repository there
4 Target directory exists, so force push all refs to it
5 Checkout web dir clone to tag
6 Inform the user so they can do a quick sanity check