How to create commit automatically in a merge request in GitLab
Tags: gitlab, ci
Published: 2021-07-28 19:04:41

There are situations when you would like to create a commit **during** continuous integration (CI) of your merge request (MR). For example:
* apply automatic fixes from a linter
* extracting translations from the code

In this article, I'll show how to set it up with GitLab.

# Assumptions
I will job to be triggered manually. In the real-world examples listed above, you would like to automatically trigger them and just let the git ignore empty commits. As I'm always doing some change in my simplified example, doing it manually saves us from an infinite loop of an ever-changing branch.

I want the changes to be done only on the non-main (non-master) branches. There could be cases where changing master directly makes sense, but I'm used to MR-heavy workflow & I find direct commits to main a bit icky.

# Credentials
This approach is only achievable with Personal Access Tokens. You will need somebody on your team to set it up via their account and pretty much make them share the key with the whole team. And it will stop working as soon as you remove the credentials from the person - which is likely to complicate stuff even further when your CI maintainer leaves the company.

## Creating s token
Go to [Personal Access Tokens](https://gitlab.com/-/profile/personal_access_tokens) page, and create a token with at least **write_repository** permission:

![write_repository.png](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/muv3vcmae10yne0b25v0.png)

## Adding the token 
Once created, you need to add it as a variable in your project's CI/CD setting page. The variable name I use further in the code is `GITLAB_PUSH_TOKEN`. With the above assumptions, you need to set the variable **not** to be protected - it has to be available on non-main branches. I recommend you make it *masked* - it will still be available for people who have access to this setting page, but at least your key will not appear in the job logs.

![add-variable.png](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i9lkogaebtg6uyhxsu02.png)

# Variables
In the following configuration, I use a lot of predefined GitLab variables. You can find the complete list of them [in the doc](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html).

# The configuration

```yml
stages:
  - test
```
Stages definition, I called it `test` following what would make sense in the lint changes use case.

```yml
add-commit:
  stage: test
```
Creates a job & sets the stage.

```yml
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: never
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
      when: manual
```
I make it never run on main/master & manually run on all the other branches.

```yml
  script:
    - echo 'test' >> $CI_COMMIT_BRANCH
    - git add .
```
My dummy changes. I put `test` into the file named after the branch.

```yml
    - git status
```
Sanity check. Helped me when I was developing the example and will be useful in troubleshooting if needed later on. 

```yml
    - git -c user.email="$GITLAB_USER_EMAIL" -c user.name="$GITLAB_USER_NAME" commit -m "add change in $CI_PIPELINE_ID"
```
The meaning is as follows:
* `-c user. ...`- sets git configuration for the command alone. Feels cleaner to me than calling `git config --global ...`, even though the context in which it would be run is discarded immediately after the job is done.
* `commit ...` is already a standard commit call, but it has to be **after** the configuration was set

```yml
    - git push "https://gitlab-ci-token:$GITLAB_PUSH_TOKEN@gitlab.com/marcin-wosinek/automated-commit.git" HEAD:$CI_COMMIT_BRANCH
```
* `git push https://gitlab-ci-token:$GITLAB_PUSH_TOKEN` pushes with the token read from `$GITLAB_PUSH_TOKEN` we defined earlier. Any configuration mismatch will fail here.
* `gitlab.com/marcin-wosinek/automated-commit.git` - of course, you will need to replace `marcin-wosinek` with your username/organization & `automated-commit with your project name. If you use a different GitLab server, it should be set here as well.
* `HEAD:$CI_COMMIT_BRANCH` - the CI is in the detached HEAD state - there is no current branch. To work around it, I'm explicitly setting what branch I'm pushing to.

## Complete `.gitlab-ci.yml`
```yml
stages:
  - test

add-commit:
  stage: test
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: never
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
      when: manual
  script:
    - echo 'test' >> $CI_COMMIT_BRANCH
    - git add .
    - git status
    - git -c user.email="$GITLAB_USER_EMAIL" -c user.name="$GITLAB_USER_NAME" commit -m "add change in $CI_PIPELINE_ID"
    - git push "https://gitlab-ci-token:$GITLAB_PUSH_TOKEN@gitlab.com/marcin-wosinek/automated-commit.git" HEAD:$CI_COMMIT_BRANCH

```

# Links
You can find my test repository [here](https://gitlab.com/marcin-wosinek/automated-commit/). The CI created [commit](https://gitlab.com/marcin-wosinek/automated-commit/-/commit/eacfe15506ca1b2df8a3d932e8468c2545276be4).

# Summary

In this article, we have seen how to make automated commits in GitLab CI. Would you mind sharing in a comment what use case you want to use this for?