Getting Started

Stash Extension Points

Miscellaneous

ScriptRunner allows global administrators to apply hooks, merge checks, and event handlers. You can either write your own hooks and handlers in groovy, or use the provided content. Most of these can be used in conjunction with conditions, which will give you high level of flexibility, and control.

You can apply the hooks to all repositories, or just particular repositories or projects.

choose repositories

Conditions

Most content (hooks, merge checks, event handlers) has a condition option. Typically you will combine a condition with the built-in content, for example mail out only when a branch that matches a regex is created.

Click the Expand examples link under the Condition field to display a list of examples.

expand examples

Clicking any of these will overwrite the current condition with the sample code. You will probably need to modify any string literals, such as references to project keys or groups. The result of the last line of any groovy script is returned as the result of the condition, but feel free to use the return keyword to make it clearer.

Take as an example a contrived scenario - you want to prevent creation of tags in all publicly accessible repositories. Start with the condition that matches creation of tags:

import com.atlassian.stash.repository.RefChangeType

refChanges.any { it.refId.startsWith("refs/tags/") &&
    it.type == RefChangeType.ADD
}

Now test the condition for publicly accessible projects:

import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.stash.repository.Repository
import com.atlassian.stash.user.PermissionService

Repository repository = repository

def permissionService = ComponentLocator.getComponent(PermissionService)
permissionService.isPubliclyAccessible(repository.project)

Given that they both work, we can combine the two like so:

import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.stash.repository.RefChange
import com.atlassian.stash.repository.Repository
import com.atlassian.stash.user.PermissionService
import com.atlassian.stash.repository.RefChangeType

Repository repository = repository
Collection<RefChange> refChanges = refChanges

def permissionService = ComponentLocator.getComponent(PermissionService)
def isPublic = permissionService.isPubliclyAccessible(repository.project)

def isTagCreate = refChanges.any { it.refId.startsWith("refs/tags/") &&
    it.type == RefChangeType.ADD
}

return isPublic && isTagCreate

Condition Methods

There are a couple of methods that can be used in conditions which simplify writing conditions. They can be used either as though they were global functions, in which case they operate on whatever is in the binding, or as if they were methods of e.g. PullRequest. They are:

pathsMatch

boolean pathsMatch(String syntaxAndPattern)

This will return true if the push, pull request, or merge check etc contains changes in the specific path. The syntaxAndPattern argument has two possible forms: glob and regex.

The glob method takes ant globs (an Ant-style path matching method), for instance the following matches a push that contains java files in the directory some/path.

refChanges.pathsMatch("glob:some/path/**.java")

In a merge request, this will return true if any of the files modified are under ssh/keys:

mergeRequest.pullRequest.pathsMatch("glob:ssh/keys/**")

getCommitAuthors

Similarly, you can get the commit authors for all changes in a pull request or a push using this:

Set<Person> getCommitAuthors()

For example, in an event handler where you are listening for an event related to pull requests:

event.getCommitAuthors()

This will return a Set of Person objects.

A Person may not actually correspond with a registered Stash user.

Per Repository Hooks

Most, but not all, of the hooks, event handlers and merge checks can be used by project and repository admins. In this case, system administrators will be able to define a base policy that applies globally (or to specific groups of projects or repositories), and project and repo administrators can overlay that with their own specific policy.

For per repository hooks you don’t select which repositories to apply the hook to, it will only apply to the current repository. Custom scripts are not available to per repository hooks.

Per-repository hooks can be applied from Repository Settings, then select one of the links in the Workflow section.

Hooks are not available under the Hooks section as you might expect, because Stash doesn’t allow multiple versions of the same hook to be applied. However, it is quite reasonable to for instance define multiple protect refs hooks, for example one to prevent deletion of release tags, and another to restrict creation of hotfix branches to a list of trusted users.

Conditions compile to in-memory classes. As project and repository admins generally do not have system admin permission, the condition code is restricted:

  • you can only call methods on a whitelist of certain Stash API classes. The whitelist is sufficient that variations of the example code will run, but not much else will.

  • you cannot define methods.

  • certain other constructs are not allowed.

So, a hook that is defined by an admin and applied to a single repository, is functionally the same as a per-repository hook, other than that the condition code runs in a restricted sandboxed environment.

In addition, per-repository hook conditions are compiled statically, which will prevent usage of certain groovy tricks, such as:

("java.lang.System" as Class).exit()

Per-repository conditions are validated at the time that you create the item (event handler, hook, merge check etc). For example, an attempt to call System.exit(0) results in:

per repo condition fail
Hooks defined globally, even if they are applied to just one repository, do not have their condition checked at the time of creation. This is because to validate the code requires static typing, which removes much of the power from groovy.
Entering the same condition in a global hook will result in Stash exiting, at the time that it’s invoked.