├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml └── entrypoint.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | LABEL version="1.0.0" 4 | LABEL repository="http://github.com/cirrus-actions/rebase" 5 | LABEL homepage="http://github.com/cirrus-actions/rebase" 6 | LABEL maintainer="Cirrus Labs" 7 | LABEL "com.github.actions.name"="Automatic Rebase" 8 | LABEL "com.github.actions.description"="Automatically rebases PR on '/rebase' comment" 9 | LABEL "com.github.actions.icon"="git-pull-request" 10 | LABEL "com.github.actions.color"="purple" 11 | 12 | RUN apk --no-cache add jq bash curl git git-lfs 13 | 14 | ADD entrypoint.sh /entrypoint.sh 15 | ENTRYPOINT ["/entrypoint.sh"] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 cirrus-actions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub action to automatically rebase PRs 2 | 3 | [![Build Status](https://api.cirrus-ci.com/github/cirrus-actions/rebase.svg)](https://cirrus-ci.com/github/cirrus-actions/rebase) [![](https://images.microbadger.com/badges/version/cirrusactions/rebase.svg)](https://microbadger.com/images/cirrusactions/rebase) [![](https://images.microbadger.com/badges/image/cirrusactions/rebase.svg)](https://microbadger.com/images/cirrusactions/rebase) 4 | 5 | After installation simply comment `/rebase` to trigger the action: 6 | 7 | ![rebase-action](https://user-images.githubusercontent.com/989066/51547853-14a57b00-1e35-11e9-841d-33114f0f0bd5.gif) 8 | 9 | # Installation 10 | 11 | To configure the action simply add the following lines to your `.github/workflows/rebase.yml` workflow file: 12 | 13 | ```yaml 14 | name: Automatic Rebase 15 | on: 16 | issue_comment: 17 | types: [created] 18 | jobs: 19 | rebase: 20 | name: Rebase 21 | runs-on: ubuntu-latest 22 | if: >- 23 | github.event.issue.pull_request != '' && 24 | ( 25 | contains(github.event.comment.body, '/rebase') || 26 | contains(github.event.comment.body, '/autosquash') 27 | ) 28 | steps: 29 | - name: Checkout the latest code 30 | uses: actions/checkout@v3 31 | with: 32 | token: ${{ secrets.GITHUB_TOKEN }} 33 | fetch-depth: 0 # otherwise, you will fail to push refs to dest repo 34 | - name: Automatic Rebase 35 | uses: cirrus-actions/rebase@1.8 36 | with: 37 | autosquash: ${{ contains(github.event.comment.body, '/autosquash') || contains(github.event.comment.body, '/rebase-autosquash') }} 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | ``` 41 | 42 | > NOTE: To ensure GitHub Actions is automatically re-run after a successful rebase action use a [Personal Access Token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token) for `actions/checkout@v2` and `cirrus-actions/rebase@1.4`. See the following [discussion](https://github.community/t/triggering-a-new-workflow-from-another-workflow/16250/37) for more details. 43 | 44 | Example 45 | 46 | ```yaml 47 | 48 | ... 49 | - name: Checkout the latest code 50 | uses: actions/checkout@v3 51 | with: 52 | token: ${{ secrets.PAT_TOKEN }} 53 | fetch-depth: 0 # otherwise, you will fail to push refs to dest repo 54 | - name: Automatic Rebase 55 | uses: cirrus-actions/rebase@1.8 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} 58 | ``` 59 | 60 | You can also optionally specify the PR number of the branch to rebase, 61 | if the action you're running doesn't directly refer to a specific 62 | pull request: 63 | 64 | ```yaml 65 | - name: Automatic Rebase 66 | uses: cirrus-actions/rebase@1.8 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} 69 | PR_NUMBER: 1245 70 | ``` 71 | 72 | 73 | ## Restricting who can call the action 74 | 75 | It's possible to use `author_association` field of a comment to restrict who can call the action and skip the rebase for others. Simply add the following expression to the `if` statement in your workflow file: `github.event.comment.author_association == 'MEMBER'`. See [documentation](https://developer.github.com/v4/enum/commentauthorassociation/) for a list of all available values of `author_association`. 76 | 77 | GitHub can also optionally dismiss an existing review automatically after rebase, so you'll need to re-approve again which will trigger the test workflow. 78 | Set it up in your repository *Settings* > *Branches* > *Branch protection rules* > *Require pull request reviews before merging* > *Dismiss stale pull request approvals when new commits are pushed*. 79 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Automatic Rebase 2 | description: Automatically rebases PR on '/rebase' comment 3 | maintainer: Cirrus Labs 4 | runs: 5 | using: 'docker' 6 | image: 'Dockerfile' 7 | inputs: 8 | autosquash: 9 | description: Should the rebase autosquash fixup and squash commits 10 | required: false 11 | default: 'false' 12 | branding: 13 | icon: git-pull-request 14 | color: purple 15 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ -z "$PR_NUMBER" ]; then 6 | PR_NUMBER=$(jq -r ".pull_request.number" "$GITHUB_EVENT_PATH") 7 | if [[ "$PR_NUMBER" == "null" ]]; then 8 | PR_NUMBER=$(jq -r ".issue.number" "$GITHUB_EVENT_PATH") 9 | fi 10 | if [[ "$PR_NUMBER" == "null" ]]; then 11 | echo "Failed to determine PR Number." 12 | exit 1 13 | fi 14 | fi 15 | 16 | echo "Collecting information about PR #$PR_NUMBER of $GITHUB_REPOSITORY..." 17 | 18 | if [[ -z "$GITHUB_TOKEN" ]]; then 19 | echo "Set the GITHUB_TOKEN env variable." 20 | exit 1 21 | fi 22 | 23 | URI=https://api.github.com 24 | API_HEADER="Accept: application/vnd.github.v3+json" 25 | AUTH_HEADER="Authorization: token $GITHUB_TOKEN" 26 | 27 | MAX_RETRIES=${MAX_RETRIES:-6} 28 | RETRY_INTERVAL=${RETRY_INTERVAL:-10} 29 | REBASEABLE="" 30 | pr_resp="" 31 | for ((i = 0 ; i < $MAX_RETRIES ; i++)); do 32 | pr_resp=$(curl -X GET -s -H "${AUTH_HEADER}" -H "${API_HEADER}" \ 33 | "${URI}/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER") 34 | REBASEABLE=$(echo "$pr_resp" | jq -r .rebaseable) 35 | if [[ "$REBASEABLE" == "null" ]]; then 36 | echo "The PR is not ready to rebase, retry after $RETRY_INTERVAL seconds" 37 | sleep $RETRY_INTERVAL 38 | continue 39 | else 40 | break 41 | fi 42 | done 43 | 44 | if [[ "$REBASEABLE" != "true" ]] ; then 45 | echo "GitHub doesn't think that the PR is rebaseable!" 46 | exit 1 47 | fi 48 | 49 | BASE_REPO=$(echo "$pr_resp" | jq -r .base.repo.full_name) 50 | BASE_BRANCH=$(echo "$pr_resp" | jq -r .base.ref) 51 | 52 | USER_LOGIN=$(jq -r ".comment.user.login" "$GITHUB_EVENT_PATH") 53 | 54 | if [[ "$USER_LOGIN" == "null" ]]; then 55 | USER_LOGIN=$(jq -r ".pull_request.user.login" "$GITHUB_EVENT_PATH") 56 | fi 57 | 58 | user_resp=$(curl -X GET -s -H "${AUTH_HEADER}" -H "${API_HEADER}" \ 59 | "${URI}/users/${USER_LOGIN}") 60 | 61 | USER_NAME=$(echo "$user_resp" | jq -r ".name") 62 | if [[ "$USER_NAME" == "null" ]]; then 63 | USER_NAME=$USER_LOGIN 64 | fi 65 | USER_NAME="${USER_NAME} (Rebase PR Action)" 66 | 67 | USER_EMAIL=$(echo "$user_resp" | jq -r ".email") 68 | if [[ "$USER_EMAIL" == "null" ]]; then 69 | USER_EMAIL="$USER_LOGIN@users.noreply.github.com" 70 | fi 71 | 72 | if [[ -z "$BASE_BRANCH" ]]; then 73 | echo "Cannot get base branch information for PR #$PR_NUMBER!" 74 | exit 1 75 | fi 76 | 77 | HEAD_REPO=$(echo "$pr_resp" | jq -r .head.repo.full_name) 78 | HEAD_BRANCH=$(echo "$pr_resp" | jq -r .head.ref) 79 | 80 | echo "Base branch for PR #$PR_NUMBER is $BASE_BRANCH" 81 | 82 | USER_TOKEN=${USER_LOGIN//-/_}_TOKEN 83 | UNTRIMMED_COMMITTER_TOKEN=${!USER_TOKEN:-$GITHUB_TOKEN} 84 | COMMITTER_TOKEN="$(echo -e "${UNTRIMMED_COMMITTER_TOKEN}" | tr -d '[:space:]')" 85 | 86 | # See https://github.com/actions/checkout/issues/766 for motivation. 87 | git config --global --add safe.directory /github/workspace 88 | 89 | git remote set-url origin https://x-access-token:$COMMITTER_TOKEN@github.com/$GITHUB_REPOSITORY.git 90 | git config --global user.email "$USER_EMAIL" 91 | git config --global user.name "$USER_NAME" 92 | 93 | git remote add fork https://x-access-token:$COMMITTER_TOKEN@github.com/$HEAD_REPO.git 94 | 95 | set -o xtrace 96 | 97 | # make sure branches are up-to-date 98 | git fetch origin $BASE_BRANCH 99 | git fetch fork $HEAD_BRANCH 100 | 101 | # do the rebase 102 | git checkout -b fork/$HEAD_BRANCH fork/$HEAD_BRANCH 103 | if [[ $INPUT_AUTOSQUASH == 'true' ]]; then 104 | GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash origin/$BASE_BRANCH 105 | else 106 | git rebase origin/$BASE_BRANCH 107 | fi 108 | 109 | # push back 110 | git push --force-with-lease fork fork/$HEAD_BRANCH:$HEAD_BRANCH 111 | --------------------------------------------------------------------------------