diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 89b166a..5a8ff8c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,14 +7,14 @@ on: jobs: build: - + runs-on: ubuntu-latest - + steps: - - uses: actions/checkout@master - - name: Verify action syntax - # The action should not publish any real changes, but should succeed. - uses: './' - with: - github_token: '${{ secrets.GITHUB_TOKEN }}' - branch: '${{ github.ref }}' + - uses: actions/checkout@v4 + - name: Verify action syntax + # The action should not publish any real changes, but should succeed. + uses: './' + with: + github_token: '${{ secrets.GITHUB_TOKEN }}' + branch: '${{ github.ref }}' \ No newline at end of file diff --git a/.github/workflows/monitoring_link.yml b/.github/workflows/monitoring_link.yml index 080935d..dcbb60b 100644 --- a/.github/workflows/monitoring_link.yml +++ b/.github/workflows/monitoring_link.yml @@ -13,7 +13,7 @@ jobs: name: Validate links runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Validate links uses: ad-m/report-link-action@master with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..44b82ce --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,23 @@ +name: Publish + +on: + release: + types: [ published ] + +jobs: + + publish: + name: Publish the release version + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository and the branch + uses: actions/checkout@v4 + + - name: Setup the release version and overwrite the existing major version tag + run: | + major_version=$(echo $GITHUB_REF_NAME | cut -d. -f1) + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git tag -fa $major_version -m "Update $major_version tag and add version $GITHUB_REF_NAME to it" + git push origin $major_version --force \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85e7c1d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea/ diff --git a/README.md b/README.md index e1c30fa..b00d06e 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,78 @@ # GitHub Action for GitHub Push -The GitHub Actions for pushing to GitHub repository local changes authorizing using GitHub token. +The GitHub Actions for pushing local changes to GitHub using an authorized GitHub token. -With ease: +## Use Cases -- update new code placed in the repository, e.g. by running a linter on it, -- track changes in script results using Git as archive, +- update new code placed in your repository, e.g. by running a linter on it, +- track changes in script results using Git as an archive, - publish page using GitHub-Pages, - mirror changes to a separate repository. +## Requirements and Prerequisites + +To ensure your GitHub Actions workflows function correctly, it's important to configure the `GITHUB_TOKEN` with the appropriate access rights for each repository. + +Follow these steps to set up the necessary permissions: +1. Navigate to your repository on GitHub. +2. Click on `Settings` located in the repository toolbar. +3. In the left sidebar, click on `Actions`. +4. Under the `Actions` settings, find and click on `General`. +5. Scroll down to the `Workflow permissions` section. +6. You will see the default permission setting for the `GITHUB_TOKEN`. Click on the `Read and write permissions` option. +7. With this setting, your workflow will be able to read the repository's contents and push back changes, which is required for using this GitHub Action. + +Make sure to save your changes before exiting the settings page. + +> [!NOTE] +> +> Granting `Read and write permissions` allows workflows to modify your repository, including adding or updating files and code. Always ensure that you trust the workflows you enable with these permissions. + +![Settings-Workflow Permissions](docs/images/Github_Settings_Workflow_Permissions.jpeg) + +The `GITHUB_TOKEN` permissions can also be configured globally for all jobs in a workflow or individually for each job. + +This example demonstrates how to set the necessary permissions for the `contents` and `pull-requests` scopes on a job level: + +```yaml +jobs: + job1: + runs-on: ubuntu-latest + permissions: # Job-level permissions configuration starts here + contents: write # 'write' access to repository contents + pull-requests: write # 'write' access to pull requests + steps: + - uses: actions/checkout@v4 +``` + +To apply permissions globally, which will affect all jobs within the workflow, you would define the `permissions` key at the root level of the workflow file, like so: + +```yaml +permissions: # Global permissions configuration starts here + contents: read # 'read' access to repository contents + pull-requests: write # 'write' access to pull requests +jobs: + job1: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 +``` + +Adjust the permission levels and scopes according to your workflow's requirements. For further details on each permission level, consult the [GitHub documentation](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token). + + ## Usage ### Example Workflow file -An example workflow to authenticate with GitHub Platform: +An example workflow to authenticate with GitHub Platform and to push the changes to a specified reference, e.g. an already available branch: ```yaml jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token. fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. @@ -31,7 +83,7 @@ jobs: run: | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" - git commit -m "Add changes" -a + git commit -a -m "Add changes" - name: Push changes uses: ad-m/github-push-action@master with: @@ -39,28 +91,211 @@ jobs: branch: ${{ github.ref }} ``` +An example workflow to use the branch parameter to push the changes to a specified branch e.g. a Pull Request branch: + +```yaml +name: Example +on: [pull_request, pull_request_target] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + - name: Commit files + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git commit -a -m "Add changes" + - name: Push changes + uses: ad-m/github-push-action@master + with: + branch: ${{ github.head_ref }} +``` + +An example workflow to use the force-with-lease parameter to force push to a repository: + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + - name: Commit files + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git commit -a -m "Add changes" + - name: Push changes + uses: ad-m/github-push-action@master + with: + force_with_lease: true +``` + +An example workflow to use a GitHub App Token together with the default token inside the checkout action. You can find more information on the topic [here](https://github.com/ad-m/github-push-action/issues/173): + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + persist-credentials: false + - name: Generate Githup App Token + id: generate_token + uses: tibdex/github-app-token@v1 + with: + app_id: ${{ secrets.APP_ID }} + installation_id: ${{ secrets.INSTALLATION_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Commit files + run: | + git config --local user.email "test@test.com" + git config --local user.name "Test" + git commit -a -m "Add changes" + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ env.TOKEN }} +``` + +An example workflow to use the non default token push to another repository. Be aware that the force-with-lease flag is in such a case not possible: + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + token: ${{ secrets.PAT_TOKEN }} + - name: Commit files + run: | + git config --local user.email "test@test.com" + git config --local user.name "Test" + git commit -a -m "Add changes" + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.PAT_TOKEN }} + repository: Test/test + force: true +``` + +An example workflow to update/ overwrite an existing tag: + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + - name: Commit files + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git tag -d $GITHUB_REF_NAME + git tag $GITHUB_REF_NAME + git commit -a -m "Add changes" + - name: Push changes + uses: ad-m/github-push-action@master + with: + force: true + tags: true +``` + +An example workflow to authenticate with GitHub Platform via Deploy Keys or in general SSH: + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} + persist-credentials: true + - name: Create local changes + run: | + ... + - name: Commit files + run: | + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git commit -a -m "Add changes" + - name: Push changes + uses: ad-m/github-push-action@master + with: + ssh: true + branch: ${{ github.ref }} +``` + +An example workflow to push to a protected branch inside your repository. Be aware that it is necessary to use a personal access token and use it inside the `actions/checkout` action. It may be a good idea to specify the force-with-lease flag in case of sync and push errors. If you want to generate an adequate personal access token, you can [follow](docs/personal-acces-token.md#creation-of-a-personal-access-token) these instructions: + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + token: ${{ secrets.PAT_TOKEN }} + - name: Commit files + run: | + git config --local user.email "test@test.com" + git config --local user.name "Test" + git commit -a -m "Add changes" + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.PAT_TOKEN }} + repository: Test/test + force_with_lease: true +``` + ### Inputs -| name | value | default | description | -| ---- | ----- | ------- | ----------- | -| 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)
or a repo scoped
[Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). | -| branch | string | (default) | Destination branch to push changes.
Can be passed in using `${{ github.ref }}`. | -| force | boolean | false | Determines if force push is used. | -| tags | boolean | false | Determines if `--tags` is used. | -| directory | string | '.' | Directory to change to before pushing. | -| repository | string | '' | Repository name.
Default or empty repository name represents
current github repository.
If you want to push to other repository,
you should make a [personal access token](https://github.com/settings/tokens)
and use it as the `github_token` input. | +| name | value | default | description | +|--------------------|---------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 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)
or a repo scoped
[Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). | +| ssh | boolean | false | Determines if ssh/ Deploy Keys is used. | +| branch | string | (default) | Destination branch to push changes.
Can be passed in using `${{ github.ref }}`. | +| force | boolean | false | Determines if force push is used. | +| 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. | +| atomic | boolean | true | Determines if atomic push is used. | +| push_to_submodules | string | 'on-demand' | Determines if --recurse-submodules= is used. The value defines the used strategy. | +| push_only_tags | boolean | false | Determines if the action should only push the tags, default false | +| tags | boolean | false | Determines if `--tags` is used. | +| directory | string | '.' | Directory to change to before pushing. | +| repository | string | '' | Repository name.
Default or empty repository name represents
current github repository.
If you want to push to other repository,
you should make a [personal access token](https://github.com/settings/tokens)
and use it as the `github_token` input. | -## Troubeshooting +## Troubleshooting -Please be aware, if your job fails and the corresponding output log looks like the following error, update your used verson of the action to `ad-m/github-push-action@master`: +If you see the following error inside the output of the job, and you want to update an existing Tag: ```log -Push to branch *************** -fatal: unsafe repository ('/github/workspace' is owned by someone else) -To add an exception for this directory, call: - - git config --global --add safe.directory /github/workspace +To https://github.com/Test/test_repository + ! [rejected] 0.0.9 -> 0.0.9 (stale info) +error: failed to push some refs to 'https://github.com/Test/test_repository' ``` +Please use the `force` instead the `force_with_lease` parameter. The update of the tag is with the `--force-with-lease` parameter not possible. + ## License The Dockerfile and associated scripts and documentation in this project are released under the [MIT License](LICENSE). diff --git a/action.yml b/action.yml index d382fad..ca7a617 100644 --- a/action.yml +++ b/action.yml @@ -7,12 +7,15 @@ branding: inputs: github_token: description: 'GitHub token or PAT token' - required: true + required: false default: ${{ github.token }} github_url: description: 'GitHub url or GitHub Enterprise url' - required: true + required: false default: ${{ github.server_url }} + ssh: + description: 'Specify if ssh should be used' + required: false repository: description: 'Repository name to push. Default or empty value represents current github repository (${GITHUB_REPOSITORY})' default: '' @@ -23,13 +26,26 @@ inputs: force: description: 'Determines if force push is used' required: false + force_with_lease: + description: 'Determines if force-with-lease push is used' + required: false + atomic: + description: 'Determines if atomic push is used, default true' + required: false + push_to_submodules: + description: 'Determines if --recurse-submodules= is used. The value defines the used strategy' + required: false + default: 'on-demand' tags: description: 'Determines if --tags is used' required: false + push_only_tags: + description: 'Determines if the action should only push the tags, default false' + required: false directory: description: 'Directory to change to before pushing.' required: false default: '.' runs: - using: 'node12' + using: 'node20' main: 'start.js' diff --git a/docs/images/Github_PAT_Fine_Gained.jpeg b/docs/images/Github_PAT_Fine_Gained.jpeg new file mode 100644 index 0000000..31c3ed4 Binary files /dev/null and b/docs/images/Github_PAT_Fine_Gained.jpeg differ diff --git a/docs/images/Github_PAT_Private_Repo.jpeg b/docs/images/Github_PAT_Private_Repo.jpeg new file mode 100644 index 0000000..50312df Binary files /dev/null and b/docs/images/Github_PAT_Private_Repo.jpeg differ diff --git a/docs/images/Github_PAT_Public_Repo.jpeg b/docs/images/Github_PAT_Public_Repo.jpeg new file mode 100644 index 0000000..3efdd4d Binary files /dev/null and b/docs/images/Github_PAT_Public_Repo.jpeg differ diff --git a/docs/images/Github_Settings_Workflow_Permissions.jpeg b/docs/images/Github_Settings_Workflow_Permissions.jpeg new file mode 100644 index 0000000..fa524e5 Binary files /dev/null and b/docs/images/Github_Settings_Workflow_Permissions.jpeg differ diff --git a/docs/personal-acces-token.md b/docs/personal-acces-token.md new file mode 100644 index 0000000..03ef4fe --- /dev/null +++ b/docs/personal-acces-token.md @@ -0,0 +1,10 @@ +# Creation of a personal access token + +1. Login to your GitHub account and navigate to the following [page](https://github.com/settings/tokens). +2. Click on the generate new token button and start the process to get a new token (classic or fine-gained) + - In the classic mode your token needs as a minimum requirement for private repositories, complete repo and admin read:org access. ![PAT Private Repo](images/Github_PAT_Private_Repo.jpeg) + + - In the classic mode and you want to use it on public repositories, your token needs public_repo access. ![PAT Public Repo](images/Github_PAT_Public_Repo.jpeg) + + - If you want to use a fine-gained token as minimum requirement, your token needs access to the repository, contents read/write, metadata read and actions read access. ![PAT Fine Gained](images/Github_PAT_Fine_Gained.jpeg) +3. Be aware, if you want to update GitHub workflow files, it's necessary that your token got workflow rights (read/write on fine-gained tokens). \ No newline at end of file diff --git a/start.js b/start.js index dc54931..aab03d5 100644 --- a/start.js +++ b/start.js @@ -1,9 +1,10 @@ 'use strict'; const spawn = require('child_process').spawn; -const path = require("path"); +const path = require('path'); +const http = require('http'); const https = require('https'); -const get = (url, options = {}) => new Promise((resolve, reject) => https +const get = (url, options = {}) => new Promise((resolve, reject) => ((new URL(url).protocol === 'http:') ? http : https) .get(url, options, (res) => { const chunks = []; res.on('data', (chunk) => chunks.push(chunk)); @@ -42,8 +43,8 @@ const trim = (value, charlist) => trimLeft(trimRight(value, charlist)); const main = async () => { let branch = process.env.INPUT_BRANCH; const repository = trim(process.env.INPUT_REPOSITORY || process.env.GITHUB_REPOSITORY); - const github_url_protocol = trim(process.env.INPUT_GITHUB_URL).split("//")[0]; - const github_url = trim(process.env.INPUT_GITHUB_URL).split("//")[1]; + const github_url_protocol = trim(process.env.INPUT_GITHUB_URL).split('//')[0]; + const github_url = trim(process.env.INPUT_GITHUB_URL).split('//')[1]; if (!branch) { const headers = { 'User-Agent': 'github.com/ad-m/github-push-action' @@ -65,6 +66,5 @@ const main = async () => { main().catch(err => { console.error(err); - console.error(err.stack); - process.exit(err.code || -1); + process.exit(-1); }) diff --git a/start.sh b/start.sh old mode 100755 new mode 100644 index 5faf53a..cda59a6 --- a/start.sh +++ b/start.sh @@ -1,29 +1,65 @@ #!/bin/sh set -e +INPUT_ATOMIC=${INPUT_ATOMIC:-true} INPUT_FORCE=${INPUT_FORCE:-false} +INPUT_FORCE_WITH_LEASE=${INPUT_FORCE_WITH_LEASE:-false} +INPUT_SSH=${INPUT_SSH:-false} INPUT_TAGS=${INPUT_TAGS:-false} -INPUT_DIRECTORY=${INPUT_DIRECTORY:-'.'} -_FORCE_OPTION='' +INPUT_PUSH_ONLY_TAGS=${INPUT_PUSH_ONLY_TAGS:-false} +INPUT_DIRECTORY=${INPUT_DIRECTORY:-"."} +INPUT_PUSH_TO_SUBMODULES=${INPUT_PUSH_TO_SUBMODULES:-""} +_ATOMIC_OPTION="" +_FORCE_OPTION="" REPOSITORY=${INPUT_REPOSITORY:-$GITHUB_REPOSITORY} echo "Push to branch $INPUT_BRANCH"; [ -z "${INPUT_GITHUB_TOKEN}" ] && { - echo 'Missing input "github_token: ${{ secrets.GITHUB_TOKEN }}".'; + echo "Missing input 'github_token: ${{ secrets.GITHUB_TOKEN }}'."; exit 1; }; +if ${INPUT_FORCE} && ${INPUT_FORCE_WITH_LEASE}; then + echo "Please, specify only force or force_with_lease and not both."; + exit 1; +fi + +if ${INPUT_ATOMIC}; then + _ATOMIC_OPTION="--atomic" +fi + if ${INPUT_FORCE}; then - _FORCE_OPTION='--force' + _FORCE_OPTION="--force" +fi + +if ${INPUT_FORCE_WITH_LEASE}; then + _FORCE_OPTION="--force-with-lease" fi if ${INPUT_TAGS}; then - _TAGS='--tags' + _TAGS="--tags" +fi + +if [ -n "${INPUT_PUSH_TO_SUBMODULES}" ]; then + _INPUT_PUSH_TO_SUBMODULES="--recurse-submodules=${INPUT_PUSH_TO_SUBMODULES}" fi cd ${INPUT_DIRECTORY} -remote_repo="${INPUT_GITHUB_URL_PROTOCOL}//${GITHUB_ACTOR}:${INPUT_GITHUB_TOKEN}@${INPUT_GITHUB_URL}/${REPOSITORY}.git" -git config --local --add safe.directory ${INPUT_DIRECTORY} +if ${INPUT_SSH}; then + remote_repo="git@${INPUT_GITHUB_URL}:${REPOSITORY}.git" +else + remote_repo="${INPUT_GITHUB_URL_PROTOCOL}//oauth2:${INPUT_GITHUB_TOKEN}@${INPUT_GITHUB_URL}/${REPOSITORY}.git" +fi + +if ! ${INPUT_FORCE_WITH_LEASE}; then + ADDITIONAL_PARAMETERS="${remote_repo} HEAD:${INPUT_BRANCH}" +elif ${INPUT_PUSH_ONLY_TAGS}; then + ADDITIONAL_PARAMETERS="${remote_repo}" +fi + +if ${INPUT_FORCE_WITH_LEASE} && ${INPUT_TAGS}; then + _ATOMIC_OPTION="" +fi -git push "${remote_repo}" HEAD:${INPUT_BRANCH} --follow-tags $_FORCE_OPTION $_TAGS; +git push $ADDITIONAL_PARAMETERS $_INPUT_PUSH_TO_SUBMODULES $_ATOMIC_OPTION --follow-tags $_FORCE_OPTION $_TAGS;