Skip to content

Commit fdfe7e8

Browse files
authored
feat: Add git pull support (#373)
1 parent d30dc2d commit fdfe7e8

3 files changed

Lines changed: 106 additions & 13 deletions

File tree

README.md

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -269,24 +269,77 @@ jobs:
269269
force_with_lease: true
270270
```
271271

272+
An example workflow to pull remote changes before pushing (useful when multiple jobs or actors push to the same branch concurrently):
273+
274+
> [!IMPORTANT]
275+
> The `pull` input requires the repository to be checked out **on a real branch** (not in detached HEAD state).
276+
> `git pull` does not work in detached HEAD state and the action will abort with an error if this is detected.
277+
> Make sure to pass an explicit `ref` to `actions/checkout` so that a branch is checked out, e.g. `ref: ${{ github.head_ref }}` for pull-request workflows or `ref: main` for push workflows.
278+
279+
```yaml
280+
jobs:
281+
build:
282+
runs-on: ubuntu-latest
283+
steps:
284+
- uses: actions/checkout@v4
285+
with:
286+
ref: ${{ github.ref_name }} # must be a branch, not a tag or SHA (detached HEAD)
287+
fetch-depth: 0
288+
- name: Create local changes
289+
run: |
290+
...
291+
- name: Commit files
292+
run: |
293+
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
294+
git config --local user.name "github-actions[bot]"
295+
git commit -a -m "Add changes"
296+
- name: Push changes
297+
uses: ad-m/github-push-action@master
298+
with:
299+
github_token: ${{ secrets.GITHUB_TOKEN }}
300+
branch: ${{ github.ref }}
301+
pull: rebase # pull --rebase before push; also accepts: merge | ff-only | true
302+
```
303+
272304
### Inputs
273305

274-
| name | value | default | description |
275-
|--------------------|---------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
276-
| github_token | string | `${{ github.token }}` | [GITHUB_TOKEN](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow) <br /> or a repo scoped <br /> [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). |
277-
| ssh | boolean | false | Determines if ssh/ Deploy Keys is used. |
278-
| branch | string | (default) | Destination branch to push changes. <br /> Can be passed in using `${{ github.ref }}`. |
279-
| force | boolean | false | Determines if force push is used. |
280-
| force_with_lease | boolean | false | Determines if force-with-lease push is used. Please specify the corresponding branch inside `ref` section of the checkout action e.g. `ref: ${{ github.head_ref }}`. Be aware, if you want to update the branch and the corresponding tag please use the `force` parameter instead of the `force_with_lease` option. |
281-
| atomic | boolean | true | Determines if atomic push is used. |
282-
| push_to_submodules | string | 'on-demand' | Determines if --recurse-submodules=<strategy> is used. The value defines the used strategy. |
283-
| push_only_tags | boolean | false | Determines if the action should only push the tags, default false |
284-
| tags | boolean | false | Determines if `--tags` is used. |
285-
| directory | string | '.' | Directory to change to before pushing. |
286-
| repository | string | '' | Repository name. <br /> Default or empty repository name represents <br /> current github repository. <br /> If you want to push to other repository, <br /> you should make a [personal access token](https://github.com/settings/tokens) <br /> and use it as the `github_token` input. |
306+
| name | value | default | description |
307+
|--------------------|---------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
308+
| github_token | string | `${{ github.token }}` | [GITHUB_TOKEN](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow) <br /> or a repo scoped <br /> [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). |
309+
| ssh | boolean | false | Determines if ssh/ Deploy Keys is used. |
310+
| branch | string | (default) | Destination branch to push changes. <br /> Can be passed in using `${{ github.ref }}`. |
311+
| force | boolean | false | Determines if force push is used. |
312+
| force_with_lease | boolean | false | Determines if force-with-lease push is used. Please specify the corresponding branch inside `ref` section of the checkout action e.g. `ref: ${{ github.head_ref }}`. Be aware, if you want to update the branch and the corresponding tag please use the `force` parameter instead of the `force_with_lease` option. |
313+
| atomic | boolean | true | Determines if atomic push is used. |
314+
| push_to_submodules | string | 'on-demand' | Determines if --recurse-submodules=<strategy> is used. The value defines the used strategy. |
315+
| push_only_tags | boolean | false | Determines if the action should only push the tags, default false |
316+
| tags | boolean | false | Determines if `--tags` is used. |
317+
| directory | string | '.' | Directory to change to before pushing. |
318+
| repository | string | '' | Repository name. <br /> Default or empty repository name represents <br /> current github repository. <br /> If you want to push to other repository, <br /> you should make a [personal access token](https://github.com/settings/tokens) <br /> and use it as the `github_token` input. |
319+
| pull | string | false | Perform a `git pull` before pushing. Accepted values: `rebase` (or `true`) uses `--rebase`, `merge` uses `--no-rebase`, `ff-only` uses `--ff-only`. Leave unset or `false` to skip the pull entirely. **Requires a real branch to be checked out** (detached HEAD state is not supported — the action will abort with an error). |
287320

288321
## Troubleshooting
289322

323+
If you see the following error when the `pull` input is enabled:
324+
325+
```log
326+
Error: 'pull' is enabled but the repository is in detached HEAD state.
327+
git pull only works when a branch is currently checked out.
328+
```
329+
330+
This means `actions/checkout` checked out a commit SHA or tag instead of a branch, leaving the repository in [detached HEAD state](https://git-scm.com/docs/git-checkout#_detached_head). `git pull` does not work in that state.
331+
332+
**Fix:** Pass an explicit branch `ref` to `actions/checkout`:
333+
334+
```yaml
335+
- uses: actions/checkout@v4
336+
with:
337+
ref: ${{ github.head_ref }} # for pull_request workflows
338+
# or
339+
# ref: ${{ github.ref_name }} # for push workflows
340+
fetch-depth: 0
341+
```
342+
290343
If you see the following error inside the output of the job, and you want to update an existing Tag:
291344
```log
292345
To https://github.com/Test/test_repository

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ inputs:
4646
description: 'Directory to change to before pushing.'
4747
required: false
4848
default: '.'
49+
pull:
50+
description: 'Determines if a pull should be performed before pushing. Accepts rebase, merge, ff-only, or true. When set to true, uses rebase. Default false. Requires the repository to be checked out on a real branch (not in detached HEAD state); the action will abort with an error if a detached HEAD is detected.'
51+
required: false
52+
default: 'false'
4953
runs:
5054
using: 'node24'
5155
main: 'start.js'

start.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ INPUT_TAGS=${INPUT_TAGS:-false}
99
INPUT_PUSH_ONLY_TAGS=${INPUT_PUSH_ONLY_TAGS:-false}
1010
INPUT_DIRECTORY=${INPUT_DIRECTORY:-"."}
1111
INPUT_PUSH_TO_SUBMODULES=${INPUT_PUSH_TO_SUBMODULES:-""}
12+
INPUT_PULL=${INPUT_PULL:-false}
1213
_ATOMIC_OPTION=""
1314
_FORCE_OPTION=""
1415
REPOSITORY=${INPUT_REPOSITORY:-$GITHUB_REPOSITORY}
@@ -46,6 +47,41 @@ fi
4647

4748
cd ${INPUT_DIRECTORY}
4849

50+
if [ "${INPUT_PULL}" != "false" ]; then
51+
if ! git symbolic-ref --quiet HEAD > /dev/null 2>&1; then
52+
echo "Error: 'pull' is enabled but the repository is in detached HEAD state."
53+
echo "git pull only works when a branch is currently checked out."
54+
echo "Either disable the 'pull' input or check out a branch explicitly"
55+
echo "(e.g. add 'ref: \${{ github.head_ref }}' to the actions/checkout step)."
56+
exit 1
57+
fi
58+
59+
if ${INPUT_SSH}; then
60+
_pull_remote="git@${INPUT_GITHUB_URL}:${REPOSITORY}.git"
61+
else
62+
_pull_remote="${INPUT_GITHUB_URL_PROTOCOL}//oauth2:${INPUT_GITHUB_TOKEN}@${INPUT_GITHUB_URL}/${REPOSITORY}.git"
63+
fi
64+
65+
case "${INPUT_PULL}" in
66+
rebase|true)
67+
_pull_strategy="--rebase"
68+
;;
69+
merge)
70+
_pull_strategy="--no-rebase"
71+
;;
72+
ff-only)
73+
_pull_strategy="--ff-only"
74+
;;
75+
*)
76+
echo "Unknown pull strategy: '${INPUT_PULL}'. Use rebase, merge, ff-only, or true."
77+
exit 1
78+
;;
79+
esac
80+
81+
echo "Pulling from ${INPUT_BRANCH} (strategy: ${INPUT_PULL}) before push..."
82+
git pull ${_pull_strategy} "${_pull_remote}" "${INPUT_BRANCH}"
83+
fi
84+
4985
if ${INPUT_SSH}; then
5086
remote_repo="git@${INPUT_GITHUB_URL}:${REPOSITORY}.git"
5187
else

0 commit comments

Comments
 (0)