#  Controlling when pull requests can be merged

## Overview

Teams using Bitbucket Server will often want to restrict when pull requests can be merged to a branch to align their
use of pull requests with their established development process. For example, teams that want to ensure thorough review
and foster knowledge sharing may want to require that a pull request has a certain number of reviewers before it can be
merged. Other teams may want to enforce a quality gate on merging by requiring a green build on the source branch, or
that a set of code metrics has been satisfied.

Bitbucket Server supports this through [Merge Request Check Plugin Modules](../reference/plugin-module-types/merge-check.html).

<div class="aui-message info shadowed" style="width:60%">
    <p class="title">Repository merge checks</p>
    <span class="aui-icon icon-info"></span>
    <p>
        Bitbucket Server supports a simplified API for hooks that require per-repository configuration.
        Pre-receive, post-receive and merge-check hooks can all be written in this style.
        See <a href="../how-tos/repository-hooks.html">Repository Hooks</a> and the corresponding
        <a href="../how-tos/repository-hooks.html#Tips">tips</a>.
    </p>
</div>

## Goals

This tutorial will take you through the steps required to write a plugin containing a
[Merge Request Check](../reference/plugin-module-types/merge-check.html)
which is designed to prevent pull requests from being merged unless the user performing the merge is an administrator 
for the target repository.

After completing this tutorial you will have learnt to do the following:

 * Generate a Bitbucket Server plugin using Atlassian SDK
 * Implement a merge request check in Java
 * Declare a merge request check in your plugin's `atlassian-plugin.xml`
 * Use various Bitbucket Server services and the merge request check API
 * Internationalize the messages your plugin emits

## Steps

### Generate a Bitbucket Server plugin using the Atlassian SDK

Ensure you have installed the Atlassian SDK on your system as described in [this tutorial](https://developer.atlassian.com/display/DOCS/Set+up+the+Atlassian+Plugin+SDK+and+Build+a+Project).
Once installed, create a Bitbucket Server plugin project by running `atlas-create-bitbucket-plugin` and supplying the
following values when prompted:

<table>
    <tr><th>groupId</th><th>artifactId</th><th>version</th><th>package</th></tr>
    <tr><td>com.mycompany.bitbucket</td><td>is-admin-merge-check</td><td>1.0.0-SNAPSHOT</td><td>com.mycompany.bitbucket.merge.checks</td></tr>
</table>

### Prepare your project and delete boilerplate code

`atlas-create-bitbucket-plugin` creates several classes you don't need, to help illustrate how to build a plugin. You
should remove all of these files:

 * `src/main/java/com/mycompany/bitbucket/merge/checks/api` and `src/main/java/com/mycompany/bitbucket/merge/checks/impl`,
   with their `MyComponent` types
 * `src/main/resources/css` and `src/main/resources/js`
 * All directories and files under `src/test/java` and `src/test/resources`
     * These were unit and integration tests for `MyComponent`, which aren't valid after `MyComponent` has been deleted

In addition to removing those directories and files, also remove the `<web-resource/>` block from
`atlassian-plugin.xml`. This example merge request doesn't include any UI components.

Now you have a basic Bitbucket Server plugin project with the following files and folders:

 * `LICENSE`
 * `README`
 * `pom.xml`
 * `src/main/java/com/mycompany/bitbucket/merge/checks`
 * `src/main/resources/atlassian-plugin.xml`

Merge request checks are part of Bitbucket Server's SPI (its service provider interface).
`atlas-create-bitbucket-plugin` generates `pom.xml` with this dependency already in place, but for reference the
dependency looks like this:

```xml
<dependencies>
    ...
    <dependency>
        <groupId>com.atlassian.bitbucket.server</groupId>
        <artifactId>bitbucket-spi</artifactId>
        <version>\${bitbucket.version}</version>
        <scope>provided</scope>
    </dependency>
    ...
</dependencies>
```

### Create a merge request check Java class

In order to implement a merge request check you will first need to create a Java class that implements the interface
`com.atlassian.bitbucket.scm.pull.MergeRequestCheck`. This check will ensure that whoever is merging the pull request is
an administrator of the target repository so let's call it `IsAdminMergeCheck`:

```java
package com.mycompany.bitbucket.merge.checks;

import com.atlassian.bitbucket.scm.pull.MergeRequest;
import com.atlassian.bitbucket.scm.pull.MergeRequestCheck;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;

@Component
public class IsAdminMergeCheck implements MergeRequestCheck {

    @Override
    public void check(@Nonnull MergeRequest request) {
        //TODO: implement me
    }
}
```

The MergeRequestCheck interface has a single method `check(MergeRequest)` that you must implement. This method is called
when Bitbucket Server is either in the process of handling a request to merge a pull request or it wants to display to the user
whether the pull request can be merged. The argument to this method is a `MergeRequest`. A `MergeRequest` encapsulates
the current request to merge and allows you to retrieve the pull request via `getPullRequest()` and to veto the merge via
`veto(String summaryMessage, String detailedMessage)`. If your check method does not call `veto(...)` before it returns
then Bitbucket Server will let the merge proceed (that is, as long as no other merge request check calls `veto(...)` and
there are no merge conflicts).

<div class="aui-message info shadowed">
    <p class="title">Atlassian Spring Scanner</p>
    <span class="aui-icon icon-info"></span>
    <p>
        Notice that <tt>IsAdminMergeCheck</tt> is annotated with the standard Spring <tt>@Component</tt> annotation.
        Plugins generated by recent versions of the SDK use
        <a href="https://bitbucket.org/atlassian/atlassian-spring-scanner">Atlassian Spring Scanner</a>
        to simplify their Spring wiring. <i>This replaces the legacy <tt>&lt;component/&gt;</tt> directive in
        <tt>atlassian-plugin.xml</tt>, and <tt>&lt;component/&gt;</tt> entries will no longer work.</i>
    </p>
</div>

Let's go ahead and implement the `check(MergeRequest)` method.

Since our check wants to ensure only users with administrative permissions for the pull request's target repository
can merge, we will need to ask Bitbucket Server if the current user has such permission. Bitbucket Server's PermissionService
can answer this question for us so let's add that to the class: add a constructor to `IsAdminMergeCheck` with a single
parameter of type `PermissionService` and assign it a field `permissionService`. This constructor argument will be injected
by Bitbucket Server at runtime when your merge request check is instantiated.

```java
package com.mycompany.bitbucket.merge.checks;

import com.atlassian.bitbucket.scm.pull.MergeRequest;
import com.atlassian.bitbucket.scm.pull.MergeRequestCheck;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;

@Component
public class IsAdminMergeCheck implements MergeRequestCheck {

    private final PermissionService permissionService;

    @Autowired
    public IsAdminMergeCheck(@ComponentImport PermissionService permissionService) {
        this.permissionService = permissionService;
    }

    @Override
    public void check(@Nonnull MergeRequest request) {
        //TODO: implement me
    }
}
```

<div class="aui-message info shadowed">
    <p class="title">Atlassian Spring Scanner</p>
    <span class="aui-icon icon-info"></span>
    <p>
        Notice the constructor is <tt>@Autowired</tt>, using the standard Spring annotation, and that the
        <tt>PermissionService</tt> parameter is annotated with <tt>@ComponentImport</tt>, an annotation from
        <a href="https://bitbucket.org/atlassian/atlassian-spring-scanner">Atlassian Spring Scanner</a>. Like
        <tt>@Component</tt>, <i><tt>@ComponentImport</tt> replaces the legacy <tt>&lt;component-import/&gt;</tt>
        directive in <tt>atlassian-plugin.xml</tt>, and <tt>&lt;component-import/&gt;</tt> entries will no longer
        work.</i>
    </p>
</div>

The PermissionService has methods to determine whether users have permission to access Bitbucket Server objects. The
method we will use is `hasRepositoryPermission(Repository, Permission)`. This takes a `Repository` and a `Permission`
and returns true if the current user has the permission or false if not. Since we want to check if the current user is
a repository admin, the permission we should use is `Permission.REPO_ADMIN`. But where do we find the target repository
for the pull request? The `MergeRequest.getPullRequest()` will give us the pull request being merged and if we call
`getToRef().getRepository()` on this we will get the repository the pull request is being merged into.

Adding this to our method we have:

```java
@Override
public void check(@Nonnull MergeRequest request) {
    if (!permissionService.hasRepositoryPermission(request.getPullRequest().getToRef().getRepository(), Permission.REPO_ADMIN)) {
        //TODO: implement me
    }
}
```

The only thing left in this class is to implement what we want to do if the user is not a repository admin. In this case
we want to call `MergeRequest.veto(String summaryMessage, String detailedMessage)` to tell Bitbucket Server we wish to
veto the merge. The `summaryMessage` should be a message explaining the problem at a high level and in as few words as
possible (while still being clear). The `detailedMessage` should be a longer message giving more information and any
context that may be helpful to the user in making them understand why the merge was vetoed and what they might be able
to change to fix the situation.

When generating messages that will be shown to the user, it is always good practice to use Bitbucket Server's internationalization
service (I18nService) so that the localised version appropriate to the user's preferred locale is chosen.
`I18nService.getText(String key, String fallbackMessage)` will return you a localised version message for the supplied
key or the fallbackMessage if no message could be found. So let's add I18nService to our class like we did with
the PermissionService:

```java
package com.mycompany.bitbucket.merge.checks;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.scm.pull.MergeRequest;
import com.atlassian.bitbucket.scm.pull.MergeRequestCheck;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;

@Component
public class IsAdminMergeCheck implements MergeRequestCheck {

    private final I18nService i18nService;
    private final PermissionService permissionService;

    @Autowired
    public IsAdminMergeCheck(@ComponentImport I18nService i18nService,
                             @ComponentImport PermissionService permissionService) {
        this.i18nService = i18nService;
        this.permissionService = permissionService;
    }

    @Override
    public void check(@Nonnull MergeRequest request) {
        if (!permissionService.hasRepositoryPermission(request.getPullRequest().getToRef().getRepository(), Permission.REPO_ADMIN)) {
            //TODO: implement me
        }
    }
}
```

Now we reference all the services we require to do our task so let's finish the implementation of the check method:

```java
package com.mycompany.bitbucket.merge.checks;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.scm.pull.MergeRequest;
import com.atlassian.bitbucket.scm.pull.MergeRequestCheck;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;

@Component
public class IsAdminMergeCheck implements MergeRequestCheck {

    private final I18nService i18nService;
    private final PermissionService permissionService;

    @Autowired
    public IsAdminMergeCheck(@ComponentImport I18nService i18nService,
                             @ComponentImport PermissionService permissionService) {
        this.i18nService = i18nService;
        this.permissionService = permissionService;
    }

    @Override
    public void check(@Nonnull MergeRequest request) {
        if (!permissionService.hasRepositoryPermission(request.getPullRequest().getToRef().getRepository(), Permission.REPO_ADMIN)) {
            String summaryMsg = i18nService.getText("mycompany.plugin.merge.check.notrepoadmin.summary",
                                                    "Only repository administrators may merge pull requests");
            String detailedMsg = i18nService.getText("mycompany.plugin.merge.check.notrepoadmin.detailed",
                                                     "The user merging the pull request must be an administrator of the target repository");
            request.veto(summaryMsg, detailedMsg);
        }
    }
}
```

### Implement atlassian-plugin.xml

After cleaning up the generated `atlassian-plugin.xml` during our preparation steps, the file should look like this:

```xml
<atlassian-plugin key="\${atlassian.plugin.key}" name="\${project.name}" plugins-version="2">
    <plugin-info>
        <description>\${project.description}</description>
        <version>\${project.version}</version>
        <vendor name="\${project.organization.name}" url="\${project.organization.url}"/>
        <param name="plugin-icon">images/pluginIcon.png</param>
        <param name="plugin-logo">images/pluginLogo.png</param>
    </plugin-info>

    <resource type="i18n" name="i18n" location="is-admin-merge-check"/>
</atlassian-plugin>
```

In previous versions of the SDK, it would be necessary to add `<component-import/>` directives for `I18nService` and
`PermissionService`, and to add a `<component/>` directive for `IsAdminMergeCheck`. Since our plugin is using Atlassian
Spring Scanner, though, these entries are not necessary _and will be ignored if present_. Instead, we used `@Component`
and `@ComponentImport` directly in `IsAdminMergeCheck` to import the necessary services and instantiate the check bean.

Simply instantiating an `IsAdminMergeCheck` bean is not enough, however. We need to register our component as a merge
request check. This is done using the `<merge-check/>` element. You need to supply a key for the `<merge-check/>` tag
that is unique within the plugin and you also need to reference the `IsAdminMergeCheck` bean.

```xml
<atlassian-plugin key="\${atlassian.plugin.key}" name="\${project.name}" plugins-version="2">
    <plugin-info>
        <description>\${project.description}</description>
        <version>\${project.version}</version>
        <vendor name="\${project.organization.name}" url="\${project.organization.url}"/>
        <param name="plugin-icon">images/pluginIcon.png</param>
        <param name="plugin-logo">images/pluginLogo.png</param>
    </plugin-info>

    <resource type="i18n" name="i18n" location="is-admin-merge-check"/>

    <merge-check key="isAdmin" class="bean:isAdminMergeCheck"/>
</atlassian-plugin>
```

<div class="aui-message info shadowed">
    <p class="title">Prototype or singleton?</p>
    <span class="aui-icon icon-info"></span>
    <p>
        Using the <tt>bean:isAdminMergeCheck</tt> syntax means <tt>IsAdminMergeCheck</tt> will be instantiated once,
        and that singleton instance will be used for all pull requests. This means <tt>IsAdminMergeCheck</tt> can't
        have any per-pull request state.
    </p>
    <p>
        An alternative is to replace the <tt>@Component</tt> on <tt>IsAdminMergeCheck</tt> with <tt>@Scanned</tt>, an
        <a href="https://bitbucket.org/atlassian/atlassian-spring-scanner">Atlassian Spring Scanner</a> annotation, and
        declare the <tt>&lt;merge-check/&gt;</tt> like this:
        <pre>&lt;merge-check key="isAdmin" class="com.mycompany.bitbucket.merge.check.IsAdminMergeCheck"/&gt;</pre>
    </p>
    <p>
        Declared this way, a new <tt>IsAdminMergeCheck</tt> will be instantiated <i>every time</i> merge checks are
        run, and will only be used once. This is somewhat less efficient, as the check is continuously instantiated
        and wired, but for certain merge checks it can be useful to be able to have per-pull request state.
    </p>
</div>

### Create a plugin resource bundle (optional)

Bitbucket Server will use fallback messages supplied to  `I18nService.getText(key, fallbackMessage)` calls, so if you
do not anticipate your plugin being used in a context other than the language your fallback messages are written in
you may choose to skip this step.

`atlas-create-bitbucket-plugin` generates a `<resource/>` element in `atlassian-plugin.xml`, and creates an empty
`is-admin-merge-check.properties` in `src/main/resources`. You can define key/value pairs in this file, in standard
[ResourceBundle](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html) format, to define the default
messages that will be used if messages localized for a more specific locale are not available.

For example, if you wanted to add German localizations for your plugin, you would add them in:

 * `src/main/resources/is-admin-merge-check_de_DE.properties`

Its contents would look something like:

```properties
mycompany.plugin.merge.check.notrepoadmin.summary=Nur Repository-Administratoren können Pull Requests akzeptieren
mycompany.plugin.merge.check.notrepoadmin.detailed=Um einen Pull Request akzeptieren zu können, muss der Benutzer ein Administrator des Ziel-Repositories sein
```

You do not _need_ to provide a resource bundle for the locale and language your fallback messages are written in.
However, for large plugins with many internationalized messages, it is considered a _best practice_ to do so. If
you have defined all of your messages in a properties file, the `I18nService` offers different methods which do not
require you to duplicate your fallback messages in Java code:

* `getText` calls can be replaced with `getMessage`
* `getKeyedText` calls can be replaced with `createKeyedMessage`

If you will be defining your fallback messages in a `ResourceBundle` you should prefer the method variants which
do not require fallback text, to prevent the two values from getting out of sync.

### Try it out!

From the command line run atlas-run. Then you should connect to `http://localhost:7990/bitbucket`, logging in as admin/admin
and create a project, repository, import some code with multiple branches and create a pull request between them. To
trigger your merge check's error message you should create a new user who has contributor permissions to the repository
(so that the merge button is displayed on the pull request page for them) but who is not an administrator of the repository. 
When this user visits the pull request page you should notice the merge button is greyed out and hovering over this button
displays a tooltip with the detailed message you supplied.

Congratulations on building your first merge request check!

## Conclusion

In this tutorial you have learnt how to create a merge request check in Java, how to use various Bitbucket Server services to
implement your merge check, how to configure your atlassian-plugin.xml to declare your merge check and also how to
provide internationalization and localization support for the messages you send to the user.
