87

I'm trying to push to origin remote from GitHub action. The logic of my action is:

  • handle pull_request_review events and filter by comment message
  • checkout to master, merge PR branch, run some checks and push it to origin

The script is:

if [[ "${GITHUB_EVENT_NAME}" != "pull_request_review" ]]; then
  echo "unsupported event: ${GITHUB_EVENT_NAME}"
  exit 1
fi

user=$(jq -r .review.user.login ${GITHUB_EVENT_PATH})
cmd=$(jq -r .review.body ${GITHUB_EVENT_PATH})
echo "reviewer is ${user}, command is ${cmd}"

if [[ "${cmd}" == "merge" ]]; then
  head=$(jq -r .pull_request.head.ref ${GITHUB_EVENT_PATH})
  git config user.email test@test.com
  git config user.name test
  git checkout -B _tmp origin/${head}
  git checkout -B master origin/master
  git merge --no-ff _tmp
  git push origin master
fi

I'm running this script from alpine:3.10 Docker container:

FROM alpine:3.10

LABEL "com.github.actions.name"="Hello world action"
LABEL "com.github.actions.icon"="shield"
LABEL "com.github.actions.color"="green"

WORKDIR /app
COPY action.sh action.sh
RUN apk --update add bash git jq
CMD ["bash", "/app/action.sh"]

First steps are working fine (checkout and merge), but action failed to push the merge to origin because of the error:

+ git push origin master
fatal: could not read Username for 'https://github.com': No such device or address

It looks like GitHub-action Docker container is not configured to push to GitHub. How can I configure it? Is it possible to use some of the env variables provided by GitHub or maybe some mounted files (like in /github/* path)?

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Kirill
  • 7,580
  • 6
  • 44
  • 95

6 Answers6

213

actions/checkout@v2 onwards

From version 2 of checkout, the detached HEAD state issue is resolved and simplifies pushing to origin.

name: Push commit
on: push
permissions:
  contents: write
jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Create report file
        run: date +%s > report.txt
      - name: Commit report
        run: |
          git config --global user.name 'Your Name'
          git config --global user.email 'your-username@users.noreply.github.com'
          git commit -am "Automated report"
          git push

If you need the push event to trigger other workflows, use a repo scoped Personal Access Token.

      - uses: actions/checkout@v3
        with:
          token: ${{ secrets.PAT }}

actions/checkout@v1 (original answer)

To add some further detail to the excellent answer by @rmunn. The problem is that the actions/checkout@v1 action leaves the git repository in a detached HEAD state. See this issue about it for more detailed information: https://github.com/actions/checkout/issues/6

Here is a complete example to demonstrate how to get the checked out repository to a usable state and push to the remote.

name: Push commit
on: push
jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: Create report file
        run: date +%s > report.txt
      - name: Commit report
        run: |
          git config --global user.name 'Your Name'
          git config --global user.email 'your-username@users.noreply.github.com'
          git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY
          git checkout "${GITHUB_REF:11}"
          git commit -am "Automated report"
          git push

To include untracked (new) files change the workflow to use the following.

          git add -A
          git commit -m "Automated report"

The above workflow should work for the majority of events. For on: pull_request workflows the merging branch (GITHUB_HEAD_REF) should be checked out to replace the default merge commit.

Important: If you have other pull request checks besides the following workflow then you must use a Personal Access Token instead of the default GITHUB_TOKEN. This is due to a deliberate limitation imposed by GitHub Actions that events raised by a workflow (such as push) cannot trigger further workflow runs. This is to prevent accidental "infinite loop" situations, and as an anti-abuse measure. Using a repo scoped Personal Access Token is an approved workaround. See this GitHub issue for further detail on the workaround.

name: Push commit on pull request
on: pull_request
jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
        with:
          ref: ${{ github.head_ref }}
      - name: Create report file
        run: date +%s > report.txt
      - name: Commit report
        run: |
          git config --global user.name 'Your Name'
          git config --global user.email 'your-username@users.noreply.github.com'
          git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
          git commit -am "Automated report"
          git push

For further examples of push to origin during an on: pull_request workflow see this blog post, GitHub Actions: How to Automate Code Formatting in Pull Requests.

peterevans
  • 34,297
  • 7
  • 84
  • 83
  • 3
    Is there a way to check if any files have been changes before authoring the commit? To avoid erroneous errors when testing or using several node versions and commit has already been pushed using the first run of the workflow. – limitlessloop Mar 10 '20 at 12:59
  • I followed your examples in the blog post, but when I used the same format for the `if` statement in v2 it was ignored by the workflow. – limitlessloop Mar 10 '20 at 13:16
  • 2
    For anyone curious, the checkout@v2 credential is stored in `git config http.https://github.com/.extraheader` – Mingwei Samuel Aug 09 '20 at 19:42
  • @limitlessloop use `git status` – Andres Mitre May 16 '21 at 18:52
  • `actions/checkout@v2` still gave me the error of a `detached HEAD`, until I added `ref: ${{ github.head_ref }}`. I updated the answer here, hope that's correct. – commonpike Jul 28 '21 at 19:27
  • 1
    @commonpike It should not be necessary for `push` events on branches. Perhaps you are mixing `push` and `pull_request` events? – peterevans Jul 29 '21 at 00:16
  • @petereveans - you are right ! do you want to revert the change I made ? I doesn't hurt on push requests, either, it seems. – commonpike Jul 29 '21 at 07:56
  • The commit fails hard if there's nothing to commit. How to prevent that exactly? – Nico Schlömer Jan 07 '22 at 13:05
19

You can use secrets.GITHUB_TOKEN as a password on your repository URL. So you might add this before your git push line:

git remote set-url --push origin https://your_username:$GITHUB_TOKEN@github.com/your/repo

This assumes that you're already passing in the GITHUB_TOKEN secret as an environment variable to your script. If you aren't, then add:

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

to your workflow step.

rmunn
  • 34,942
  • 10
  • 74
  • 105
  • Thanks. But I'm getting another error when using `https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git` as remote URL: `remote: Invalid username or password.`. This `GITHUB_TOKEN` belongs to `github-actions` app, so I can't use it as common user. Don't you know what username should I use for this token? – Kirill Sep 14 '19 at 10:21
  • Did you follow the instructions at https://help.github.com/en/articles/virtual-environments-for-github-actions#github_token-secret to pass in the token as an environment variable to your script? I don't know what you mean by "I can't use it as common user"; it's available to your script as long as you pass it as an environment variable as per those instructions. As for the username, it's the username of your repo: for example, for my https://github.com/rmunn/Testing repo, the username would be `rmunn`. – rmunn Sep 14 '19 at 10:55
  • I've found the problem: I was not able to use `env` with Docker action: it was needed to write `action.yml` config with `inputs` for token and pass it from workflow `yml` as input using `with` keyword. – Kirill Sep 15 '19 at 12:32
11

Here is an example of how to use the official github action actions/checkout@v3:

name: Example Push Action
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: |
          date > generated.txt
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add .
          git commit -m "generated"
          git push
Vadik Sirekanyan
  • 3,332
  • 1
  • 22
  • 29
8

All great responses, however I should note that there is a GitHub Action Add & Commit that substantially simplifies the process.

name: Commit Date
on: push

jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Create report file
        run: date +%s > report.txt

      - name: Commit changes
        uses: EndBug/add-and-commit@v9
        with:
          author_name: Your Name
          author_email: mail@example.com
          message: 'Your commit message'
          add: 'report.txt'
gnikit
  • 1,031
  • 15
  • 25
4

All the provided answers are correct. But I would like to add a bit more to these answers. There can be situations where there are no changes in files. So in that situation, when trying to execute the git commit command it returns an error that fails the GitHub workflow (failed workflows block the PR merge). So we have to check whether there are any changes in files before commit.

name: Config Generator
on:
  pull_request:
    branches: [ main ]

jobs:
  config-generator:
    runs-on: ubuntu-latest
    steps: 
      - uses: actions/checkout@v2
        with:
          ref: ${{ github.head_ref }}

      - name: Install jsonnet
        run: sudo apt install -y jsonnet

      - name: Generate probe configs
        run: python3 generate-configs.py

      - name: Check for modified files
        id: git-check
        run: echo ::set-output name=modified::$(if [ -n "$(git status --porcelain)" ]; then echo "true"; else echo "false"; fi)

      - name: Update changes in GitHub repository
        if: steps.git-check.outputs.modified == 'true'
        run:  |
          git config --global user.name 'Your Name' 
          git config --global user.email 'Your Email for GitHub'
          git add -A
          git commit -m '[automated commit] add configs generated using jsonnet & GitHub workflow'
          git push

The above workflow is an example for creating configuration files using jsonnet and GitHub workflow. It first installs jsonnet and runs a python script that creates the config files using jsonnet templates. As mentioned above there can be situation where there are no file changes. So it uses git status command to check whether there are any file changes. (Instead of git status, git diff can also be used. But it wont show untracked files). Rest of the git commands run only if there are file changes.

Also note that I had to use ref: ${{ github.head_ref }} to checkout to source branch even though I used checkout@v2 (still detached head problem exists in my case)

2

For auto commit without defining user or anything use EndBug/add-and-commit@v7. Here's my workflow file for auto formatting and commiting js and python

name: auto-format
on: push
jobs:
  format-python:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: black
        uses: lgeiger/black-action@v1.0.1
        with:
          args: .

      - uses: EndBug/add-and-commit@v7
        with:
          default_author: github_actions

  format-js:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: prettier
        run: npx prettier --write "./**/*.{js,html}"

      - uses: EndBug/add-and-commit@v7
        with:
          default_author: github_actions


vineet
  • 850
  • 7
  • 9