Search code examples
automationcontinuous-integrationbitbucketbitbucket-pipelinespython-black

Bitbucket Pipeline automatic running black before the commit when pull request merged


I am trying to set up Bitbucket Pipeline to auto-format my python codebase. All I'm doing is running black.

I'm working with distributed dev teams and it's hard to get everyone to install pre-commit hooks on their local machine so for now I just want to run black whenever code is merged. I don't want this to be a check that fails the merge.

My expectation is that whenever a commit is made to master branch (so whenever pull request is merged), black would be run auto-format the code AND THEN the commit is made. I am also not worried about only running black on affected files, for now I just want to run black against the whole codebase.

Anyway, with my current setup, my testing shows that black is run after the commit and not before.

here is my bitbucket-pipelines.yml:

image: python:3.10

pipelines:
  branches:
    master:
      - step:
          name: run black
          script:
            - pip install black
            - black -l 88 .

Solution

  • Ok, after much figuring out, I figured out a way. I'm not sure if this is the best way but it works. I've changed the goal for the Bitbucket Pipeline to beautify the code and commit the reformatted code whenever a pull request is created or updated instead.

    This solution is inspired by:

    Step 1: Create an OAuth2 Consumer

    1. Go to "Workspace Settings"
    2. Under category "Apps and Features", click "OAuth consumers"
    3. Click "Add consumer", fill out necessary information. Makes sure to add permission to "read" and "write" for Pull Requests permission category. Also, "Callback URL" can't be empty, I used https://google.com .
    4. Click "Save"
    5. Your new OAuth consumer will have a Key and Secret generated Adding OAuth Consumer on Bitbucket

    Step 2: Configure the Bitbucket Respository

    1. Go to the repository that will have the Bitbucket Pipeline setup
    2. Click "Repository settings"
    3. Under category "Pipelines", click "Repository variables"
    4. Add a new variable with name being BB_AUTH_STRING and value being a string of format Key:Secret (see picture), using the key and secret of the OAuth consumer we created earlier
    5. Make sure to fill the checkbox "Secured" before clicking "Add" Adding Repository variable using the key and secret of the OAuth Consumer

    Step 3: Setup your Bitbucket Pipeline YML

    The bitbucket-pipelines.yml I've settled on currently is the following:

    image: python:3.10
    
    pipelines:
      pull-requests:
        '**':
          - step:
              name: Commit if there are changed files
              script:
                - apt-get update
                - apt-get -y install curl jq
                - pip install black
                - black -l 100 .
                - modified=$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)
                # BB_AUTH_STRING is a "Repository Variable", 
                # it's value is a string of the format "Key:Secret"
                # with "Key" and "Secret" being the key and secret of a OAuth2 Workspace Consumer
                - >
                  if [ "$modified" = "false" ]; then 
                    exit 0; 
                  else
                    export BB_TOKEN=$(curl -s -S -f -X POST -u "${BB_AUTH_STRING}" \
                    https://bitbucket.org/site/oauth2/access_token \
                    -d grant_type=client_credentials -d scopes="repository" | jq --raw-output '.access_token') ;
                    git config --global user.name 'Automatic Linter';
                    git config --global user.email '[email protected]';
                    git remote set-url origin "https://x-token-auth:${BB_TOKEN}@bitbucket.org/vietthan/myrepositoryname.git";
                    git add -A;
                    git commit -m "[skip ci] automatic linter";
                    git push;
                  fi
    
              condition:
                changesets:
                  includePaths:
                    - "**"
    

    This pipeline will only run whenever a Pull-Request is created and whenever new commits are made to the Pull Request.

    The pipeline will install curl and jq.

    The pipeline will install black.

    The pipeline will then run black -l 100.

    If no files were modified, the pipeline will exit.

    Else:

    1. It will use BB_AUTH_STRING to get a temporary access token stored in BB_TOKEN.
    2. It will set the git configuration for name, email
    3. It will set the git remote path+access token, I have this pointed to my intending repository.
    4. It will add all changed files, make a commit with [skip ci] in the commit head message to skip the pipeline when this commit is push
    5. Push the commit

    Conclusion

    You now have a bitbucket pipeline that will automatically format Python code with black whenever your developers create a pull request or push new commits to it. It will avoid infinite bitbucket pipeline builds, it will detect if there are changes before committing.