GitHub Actions are great at enabling you to automate workflows directly from within a GitHub repository. The workflows are stored in a YAML definition file located within the .github/workflows directory within the repository. GitHub Actions can be used to configure workflows that can perform a variety of actions to perform build and release steps. On rare occasions, it might be necessary to have the workflow make file changes that are then committed back to the GitHub repository itself. Luckily, GitHub Actions support the use of Git CLI commands within the workflow. This article walks through the steps to configure a GitHub Actions workflow that can commit file changes made in a workflow action back to the GitHub repository the workflow is a part of.

Let’s dive in!

GitHub Action Steps to Commit Changes to Repository

There are actions available in GitHub Action workflows for performing all sorts of tasks. GitHub Actions even supports extensibility so custom actions can be built to extend it with any functionality necessary. However, no custom actions are needed to make Git commits back to the repo. A script action can be added that simply makes git CLI commands.

The following are the minimum tasks to add to a GitHub Actions workflow to affectively make file changes and commit them back to the repository:

Step 1: Checkout code

The first thing that’s needed is a task that checks out the contents of the GitHub repository. To do this, the actions/checkout action can be used.

# Checkout code
- uses: actions/checkout@v4

Step 2: Make File Changes

Once the contents of the GitHub repository have been checked out, then the files within the repository are available to the workflow. This is where you’ll normally perform all your necessary build and release actions in the workflow. This is also where you can write actions and other code that makes the necessary changes to the files that you wish to commit back to the GitHub repository.

# Add actions that modify files here
- name: Modify files
  run: |
     echo "change files here"

Step 3: Commit and Push Changes

Once the files changes are made, then an action can be added that makes the necessary git CLI commands to commit and push the changes back up to the source GitHub repository.

To do this, the usual git steps to make a commit will need to be performed:

  1. Use git config to configure the user.name and user.email of the user that is making the commit.
  2. Use git add to stage file changes to be committed.
  3. Use git commit to commit the changes to the Git repository.
  4. Use git push to push the recent commit up to the GitHub repository.

The following is an example action with these git commands all setup as necessary:

# Commit and Push Changes
- name: Commit and Push Changes
  run: |
    # configure user
    git config --global user.name "${{ github.actor }}"
    git config --global user.email "${{ github.actor }}@users.noreply.github.com"

    # stage any file changes to be committed
    git add .

    # make commit with staged changes
    git commit -m 'files changed'

    # push the commit back up to source GitHub repository
    git push

Configure GitHub Actor as Git User and Email for Commits

Before the git commit call can be made, Git needs to be configured with the name (user.name) and email (user.email) for the user that will be making the commit. To do this, the GitHub context variable that represents the username of the GitHub account that triggered the workflow needs to be referenced.

To configure the Git user.name, make a call to the git config command including a reference to set it’s value to the github.actor GitHub context variable. This is done as follows:

git config --global user.name "${{ github.actor }}"

To configure the Git user.email, make a call to the git config command including a reference to set it’s value to the github.actor GitHub context variable with the @users.noreply.github.com text appended after it. This is done as follows:

git config --global user.email "${{ github.actor }}@users.noreply.github.com"

The Git user.email config must be set to an email address that GitHub will recognize for the user. Although the GitHub context variables do not allow the workflow to access the registered email addresses of the GitHub user account. Luckily, GitHub has a default email associated with each GitHub user account that is in the format of <github-username>@users.noreply.github.com. The github.actor is the GitHub username and can be filled in to form the required email address that Git requires.

By default, the GitgHub Actions workflow will run with the privileges of the github.actor that triggered the workflow. This is where the workflow will get it’s permissions to be able to make Git commits back to the GitHub repository.

Add Step to Check of Changes Have Been Made

If no file changes have been made, then the action that calls Git to make the commit and push against the GitHub repository may fail. To keep the GitHub Actions workflow from failing if no changes are detected, then an additional action must be added immediately prior to the commit action to detect if changes have been made.

This change detection action will perform these steps:

  1. Call git diff to detect if changes have been made.
  2. Set an output variable from the action that can be referenced by the commit action to conditionally perform the commit and push only if changes have been made.

This is necessary if the workflow code makes changes to the files that end up with identical file contents, or it simply doesn’t make any file changes. If the workflow attempts to make a commit with no changes, this can result in the workflow execution to fail. This may not be the desired behavior depending on the requirements of the GitHub Actions workflow.

The following is an example action that will detect if any uncommitted changes have been made to the repository:

- name: Check for Changes
  id: check_changes
  run: |
    if [[ -n "$(git diff --exit-code)" ]]; then
      echo "Changes detected."
      echo "::set-output name=has_changes::true"
    else
      echo "No changes detected."
      echo "::set-output name=has_changes::false"
    fi

This action is named check_changes and will output a variable named has_changes that will contain a boolean true or false value whether changes have been detected or not. Following actions will then be able to reference this variable to perform conditional logic.

The easiest method to conditionally perform the action that makes the commit and push to the GitHub repository, is to add an if condition to the action itself. This condition can then perform a simple check that the has_changes output variable from the check_changes action is equal to true.

The following is an example of how to configure the if condition on the Git commit action:

- name: Commit and Push Changes
  if: steps.check_changes.outputs.has_changes == 'true'
  run: |
    # perform git actions
    ...

Once this is configured, with the check for changes, then the GitHub Actions workflow will run and only perform the Git commit and push operations if there are actually file changes to be committed.

Note that this example is truncated, but does include the if condition check needed. The full code example putting this whole GitHub Actions workflow together is below.

Full GitHub Actions Workflow to Commit Changes Back to GitHub Repository

The following is a full code example of a GitHub Actions workflow with all of the above steps that can be used to commit and push file changes made within the workflow back up to the source GitHub repository:

on:
  workflow_dispatch:

name: Make File Changes to GitHub Repo
jobs:
  make-file-changes:
    runs-on: ubuntu-latest
    steps:
    # Checkout code
    - uses: actions/checkout@v4

    # Add actions that modify files here
    - name: Modify files
      run: |
        echo "change files here"

    - name: Check for Changes
      id: check_changes
      run: |
        if [[ -n "$(git diff --exit-code)" ]]; then
          echo "Changes detected."
          echo "::set-output name=has_changes::true"
        else
          echo "No changes detected."
          echo "::set-output name=has_changes::false"
        fi

    - name: Commit and Push Changes
      if: steps.check_changes.outputs.has_changes == 'true'
      run: |
        # configure user
        git config --global user.name "${{ github.actor }}"
        git config --global user.email "${{ github.actor }}@users.noreply.github.com"

        # stage any file changes to be committed
        git add .

        # make commit with staged changes
        git commit -m 'files changed'

        # push the commit back up to source GitHub repository
        git push

I hope this helps with building your own GitHub Actions workflows!

Chris Pietschmann is a Microsoft MVP, HashiCorp Ambassador, and Microsoft Certified Trainer (MCT) with 20+ years of experience designing and building Cloud & Enterprise systems. He has worked with companies of all sizes from startups to large enterprises. He has a passion for technology and sharing what he learns with others to help enable them to learn faster and be more productive.
Microsoft MVP HashiCorp Ambassador

Discover more from Build5Nines

Subscribe now to keep reading and get access to the full archive.

Continue reading