Accessing commit message in pull_request event

tl;dr:

Feaure request: Make head_commit available with pull_request’s synchronize action.

I’ve a workflow which triggers on pull_request’s default types/actions. I want to run a job when there are new commits pushed to a PR, but I wanted to run the job only if commit message included specific keywords. So, I tried to access <font face="courier new,courier">github.event.head_commit.message</font>, as suggested on various forums.

But unfortunately, head_commit is not available during pull_request’s synchronize action. Following is a relevant bit from github context dump:

{
  "event_name": "pull_request",
  "event": {
     "action": "synchronize",
  }
}:  
4 Likes

Yes, currently you can’t see commit message in pull_request‘s synchronize event. But you can get the commit id in github.event.after.

after commit.png

Then you can run a git command to get the commit message : git log --format=%B -n 1 <commit>

Please set this commit message as the value of an environment variable using set-env command.

Then you can add if condition in all your next steps, to judge if commit message included specific keywords

Please see my example:

name: filter commit nessage
on: pull_request

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1

      - name : GITHUB CONTEXT
        env:
          GITHUB_CONTEXT: ${{ toJson(github) }}
        run: echo "$GITHUB_CONTEXT"

      - name: get commit message
        run: |
           echo ::set-env name=commitmsg::$(git log --format=%B -n 1 ${{ github.event.after }})
      - name: show commit message
        run : echo $commitmsg

      - name: step1
        if: contains( env.commitmsg , 'try' )
        run: echo hello

filter commit message.png

6 Likes

This workaround is available only under “steps”. Would be great if it was available in github context and we could skip a “job”.

I did a similar workaround, which is pretty repetitive:

steps:
      - uses: actions/checkout@v1
      - id: log
        run: echo "::set-output name=message::$(git log --no-merges -1 --oneline)"
      - if: "!contains(steps.log.outputs.message, 'ci skip')"
        uses: actions/setup-node@v1
        with:
          node-version: 10
      - if: "!contains(steps.log.outputs.message, 'ci skip')"
        run: npm install
8 Likes

This just bit me and I was about to post about it.

In my opinion, something like this should work for any commit-driven trigger, whether via pull_request or push:

jobs:
  build:
    runs-on: ubuntu-latest
    if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')"
5 Likes

Did GitHub remove

github.event.head_commit.message

I don’t see it anywhere in my context dump.

1 Like

Any new on that feature ? Would be really helpfull

The workaround to avoid duplicate in every steps is:

  • create preBuild job with isCI step (to check message)
  • map output of isCI to preBuild
  • Use needs.preBuild.outputs.isCI in if condition of build job

Check docs here https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjobs_idoutputs

2 Likes

The above solutions didn’t work well for me. In particular, I realized that ${{github.event.after}} doesn’t seem to be defined for the first commit in a pull request. It is only defined when pushing a second/third/etc. commit to an existing pull request.

Therefore, I’m using HEAD^2 instead, which means the second parent of HEAD, and is the commit we are interested in. If the event if a push event rather than a pull_request event, I’m simply using HEAD. It seems to work perfectly.

Here is the code I use in practice. Note that we need some ugly syntax to support multiline commit messages (see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#multiline-strings). Also note that we need fetch-depth greater than one for this to work (the default is 1). Finally, note that I have included some code to get the branch name as well, in case anyone is interested.

name: Build
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:

    # We need to fetch more than one commit to be able to access HEAD^2 in case
    # of a pull request
    - uses: actions/checkout@v2
      with:
        fetch-depth: 10

    # In case of a push event, the commit we care about is simply HEAD.
    # The current branch name can be found by parsing GITHUB_REF, for example,
    # if we are on the master branch, then GITHUB_REF = refs/heads/master.
    - name: Get commit branch and commit message
      if: github.event_name == 'push'
      run: |
        echo "COMMIT_BRANCH=$(echo ${GITHUB_REF##*/})" >> $GITHUB_ENV
        echo "COMMIT_MESSAGE<<EOF" >> $GITHUB_ENV
        echo "$(git log --format=%B -n 1 HEAD)" >> $GITHUB_ENV
        echo "EOF" >> $GITHUB_ENV

    # In case of a pull_request event, the commit we care about is HEAD^2, that
    # is, the second parent of the pull request merge commit.
    # The current branch name is directly given by GITHUB_HEAD_REF
    - name: Get commit branch and commit message
      if: github.event_name == 'pull_request'
      run: |
        echo "COMMIT_BRANCH=$GITHUB_HEAD_REF" >> $GITHUB_ENV
        echo "COMMIT_MESSAGE<<EOF" >> $GITHUB_ENV
        echo "$(git log --format=%B -n 1 HEAD^2)" >> $GITHUB_ENV
        echo "EOF" >> $GITHUB_ENV
1 Like

Using the solution @dalboris I managed to work it all out for a 2+ job process where you can skip any subsequent jobs after a single prepare job. Here is the YAML:

name: Sample Workflow

on:
  push:
  pull_request:

jobs:
  pre_ci:
    name: Prepare CI environment
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Project
        uses: actions/checkout@v2
        with:
          # We need to fetch with a depth of 2 for pull_request so we can do HEAD^2
          fetch-depth: 2

        # If this workflow was triggered by a push then resolve the commit message from HEAD
        # It is stored in output steps, to be referenced with ${{ steps.push_get_commit_message.outputs.push_commit_message }}
      - name: "[Push] Get commit message"
        if: github.event_name == 'push'
        id: push_get_commit_message
        run:
          echo ::set-output name=push_commit_message::$(git log --format=%B -n 1 HEAD)

        # If this workflow was triggered by a pull request (open or synchronize!) then resolve the commit message from HEAD^2
        # It is stored in output steps, to be referenced with ${{ steps.pr_get_commit_message.outputs.pr_commit_message }}
      - name: "[Pull Request] Get commit message"
        if: github.event_name == 'pull_request'
        id: pr_get_commit_message
        run: echo ::set-output name=pr_commit_message::$(git log --format=%B -n 1 HEAD^2)

    # Finally we want to make the commit message available to other jobs. This can be done with job-level outputs
    # However as we do not know whether the commit message was set in Push or Pull Request event we need to do some
    # bash magic to resolve the one or the other
    #
    # For **Pull Request** events this will resolve to something like "$( [ -z "commit message pr" ] && echo "" || echo "commit message pr" )" which then resolves to just "commit message pr"
    #
    # For **Push** events this will resolve to something like "$( [ -z "" ] && echo "commit message push"  || echo "" )" which then resolves to just "commit message push"
    outputs:
      commit_message: $( [ -z "${{ steps.pr_get_commit_message.outputs.pr_commit_message }}" ] && echo "${{ steps.push_get_commit_message.outputs.push_commit_message }}" || echo "${{ steps.pr_get_commit_message.outputs.pr_commit_message }}" )

  # This job can be skipped by including [skip ci] in the commit message
  skippable_job:
    name: Skippable Job
    runs-on: ubuntu-latest
    if: "!contains(needs.pre_ci.outputs.commit_message, '[skip ci]')"
    needs: pre_ci
    steps:
      - name: Checkout Project
        uses: actions/checkout@v2

        # If this job doesn't get skipped you'll see the commit message appear here, it's just a debugging this
        # so be sure to remove it for your actual workflow
      - name: Log commit message
        run: echo "${{ needs.pre_ci.outputs.commit_message }}"
1 Like

This is very nice, do you know how to achieve the same with the following:
on:
pull_request:
types: [closed]
branches:
- master

oh well i think i will just use the simple code below, works fine:

git log --format=%B -n 1 <commit>

It might useful for someone interest: gh-project-context
It can provide a way to skip build in some conditions

try github.event.commits[0].message

That does not work: when the event is pull_request, commits is not provided. You do have github.even.pull_request.commits but that’s a url, not an array.

I’ve simplified the flow to a single step where we check in bash what the name of the event is:

  initialise:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Project
        uses: actions/checkout@v2
        with:
          # for pull_request so we can do HEAD^2
          fetch-depth: 2

      - name: Get commit message
        id: get_commit_message
        run: |
          if   [[ '${{ github.event_name }}' == 'push' ]]; then
            echo ::set-output name=commit_message::$(git log --format=%B -n 1 HEAD)
          elif [[ '${{ github.event_name }}' == 'pull_request' ]]; then
            echo ::set-output name=commit_message::$(git log --format=%B -n 1 HEAD^2)
          fi

    outputs:
      commit_message:
        echo "${{ steps.get_commit_message.outputs.commit_message }}"