Skip to content

Transforms

As developers, we change code. When we know exactly how we want to change the code, we can automate that. Compared to doing it by hand, automation is consistent and repeatable. Atomist gives us the superpower to change code on demand, in one project or hundreds, and in response to events. These changes become commits, branches, or pull requests.

Begin with a Code Transform: a function that acts on a project. You write this part, and test it with unit tests. Turn that into a command to run on demand, then an Autofix to run on every push.

This page shows how to:

  • Create a code transform that runs on-demand
  • Require parameters to your code transform
  • Customize the automated pull request
  • Make Atomist wait for a build to succeed before creating the pull request
  • Make Atomist merge the PR automatically when a build succeeds

After that, you might want to make your code transform into an Autofix.

Create a code transform

Code transforms are functions that receive the project as an input and changes the content of the project as a result.

For a quick example, assume we want to add an Apache licence file to the project. The transform would retrieve the license content and add a LICENSE file with that content.

export const AddApacheLicenseFileTransform: CodeTransform<NoParameters> = async (p: Project) => {
    const httpClient = DefaultHttpClientFactory.create();
    const license = await httpClient.exchange("https://www.apache.org/licenses/LICENSE-2.0.txt");
    return p.addFile("LICENSE", license.body as string);
};

When you want to do more in a code transform, you might want to:

  • To add or remove files, you can use the Project interface (the API for modifying files in the project).
  • To do basic operations (like a text replacement) on multiple files, check out projectUtils
  • To change code based on a language’s abstract syntax tree (AST), try astUtils
  • To change code based on a more intuitive selection criteria, try the microgrammars in parseUtils.
  • make HTTP calls in an SDM (if you’re curious why that example uses DefaultHttpClientFactory)

Creating a command for a transform

A code transform can be called through various means. One of those means is directly through issuing a command. This command needs to be defined and the transform needs to be referenced in that definition.

export const AddApacheLicenseFile: CodeTransformRegistration<NoParameters> = {
    transform: AddApacheLicenseFileTransform,
    name: "add apache license file",
    description: `Add Apache 2.0 license file`,
    intent: ["add apache license file", "add license file"],
};

Each intent acts as an alias for invoking the command. The description is going to be the title of an automatic pull request. The transform’s name will appear in the branch.

See also:

Adding the transform command to the SDM

Tell the SDM about the command. In order to achieve this you need to register the command with the SDM. In your SDM definition where you have access to the SDM instance, add the following registration:

sdm.addCodeTransformCommand(AddApacheLicenseFile);

Calling the command

Local

To test your command, run your SDM in local mode and then

atomist start --local

then, in a separate terminal, change directory to one of your repositories under your Atomist project root (usually $HOME/atomist). Then:

atomist add apache license file

Atomist will create a new branch with the changed code on it. List all your branches to find it:

git branch

Team

When you run your SDM in team mode, your command will be available in chat. Go to a channel associated with a repository, and talk to the Atomist bot: @atomist add apache license file

Atomist will create a branch and apply the code transform on that branch. It will also create a pull request for the commits generated by that branch. By default, the name of the pull request will be the description of the code transform.

If you want to run the transform against a branch other than the default branch, add targets.branch=other-branch-name to the command in chat.

See also:

Adding parameters to the code transform command

Sometimes you need additional input after issuing the command to transform a certain piece of code. Say that we wish to make the license file transformation a bit more flexible and allow of different types of licences. First we need to define the data structure that will hold the parameters.

@Parameters()
class AddApacheLicenseFileParameters {
    @Parameter({
        displayName: "License type",
        validInput: "apache20, gpl, lgpl",
        pattern: /(apache20|gpl|lgpl)/,
        required: false,
    })
    public license: "apache20"|"gpl"|"lgpl" = "apache20";
}

Next we need to make the transform aware of the new parameters and alter the internal logic in order to take those parameters into account.

export const AddApacheLicenseFileTransform: CodeTransform<AddApacheLicenseFileParameters> = async (p, params) => {
    const licenses = {
        apache20: "https://www.apache.org/licenses/LICENSE-2.0.txt",
        gpl: "https://www.gnu.org/licenses/gpl-2.0.txt",
        lgpl: "https://www.gnu.org/licenses/lgpl-3.0.txt",
    };

    const httpClient = DefaultHttpClientFactory.create();
    const license = await httpClient.exchange(licenses[params.parameters.license]);
    return p.addFile("LICENSE", license.body as string);
};

Finally, we need to alter the command registration so that it recognizes the command parameters and prompt for their values if they are required.

export const AddApacheLicenseFile: CodeTransformRegistration<AddApacheLicenseFileParameters> = {
    transform: AddApacheLicenseFileTransform,
    paramsMaker: AddApacheLicenseFileParameters,
    name: "add apache license file",
    description: `Add Apache 2.0 license file`,
    intent: ["add apache license file", "add license file"],
};

When issuing the command, it will prompt for the parameters values that are required. You can still issue the values for non-required parameters like this:

@atomist add apache license file license=gpl

Changing the branch and generated pull request

If you want, you can alter the contents of the pull request by defining a transformPresentation on the code transform.

export const AddApacheLicenseFile: CodeTransformRegistration<AddApacheLicenseFileParameters> = {
    transform: AddApacheLicenseFileTransform,
    paramsMaker: AddApacheLicenseFileParameters,
    name: "add apache license file",
    description: `Add Apache 2.0 license file`,
    intent: ["add apache license file", "add license file"],
    transformPresentation: () => new editModes.PullRequest("license-file", "Add license file"),
};

This will cause the transform to be run on the license-file branch and the resulting pull request have Add license file as a title. You can also specify the body of the pull request, and more. Check the docs on new PullRequest for more.

If you don’t want a pull request, you can specify that the transform should be applied as a commit to any branch.

In local mode, there is no such thing as a pull request, so you’ll see a branch in your repository.

Defer pull request creation based on build outcome

Atomist will automatically create a pull request when executing a code transform. However, the goal set execution can still fail. To mitigate unneeded unstable pull request creation, you can wrap your code transform registration in the makeBuildAware function.

export const AddApacheLicenseFile: CodeTransformRegistration<AddApacheLicenseFileParameters> = makeBuildAware({
    transform: AddApacheLicenseFileTransform,
    paramsMaker: AddApacheLicenseFileParameters,
    name: "add apache license file",
    description: `Add Apache 2.0 license file`,
    intent: ["add apache license file", "add license file"],
});

Enabling auto merge of pull request based on build outcome

By default, you still need to manually merge the pull request. You can however configure code transforms to auto merge on a successful goalset execution. You can achieve this by pressing the Enable Auto Merge button that is shown in Slack in the pull request message. This will add a certain label (auto-merge:on-check-success) to the pull request, which indicates to Atomist that the pull request needs to be merged on a succesful goalset execution. You can also add that label manually in Github if you want to.

Adding labels to Github

If the labels are missing in Github, issue the @atomist add auto merge labels command in the channel linked to a repository

Changing merge behavior of pull requests

By default, Atomist will merge a pull request by adding the commits to the target branch using a merge commit. It is however also capable of using different merge strategies, like rebase or squash. In order to do this, you can add different labels to the pull request. The following labels are supported: auto-merge-method:merge: use a merge commit auto-merge-method:rebase: rebase the commits onto the target branch * auto-merge-method:squash: squash all the commits into a single commit

In the event of a squash, the commit message of the new commit will be the title of the pull request.

Adding labels to Github

If the labels are missing in Github, issue the @atomist add auto merge labels command in the channel linked to a repository