├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── cron-check-dependencies.yml │ ├── manual-sync-common-files.yml │ ├── auto-create-pull-request.yml │ ├── auto-create-release.yml │ └── manual-update-version.yml ├── .dockerignore ├── .gitignore ├── .shellcheckrc ├── .editorconfig ├── .hadolint.yaml ├── .yamllint.yml ├── Taskfile.yml ├── Dockerfile ├── LICENSE ├── .pre-commit-config.yaml ├── action.yml ├── Taskfile.docker.yml ├── entrypoint.sh ├── Taskfile.variables.yml ├── Taskfile.cicd.yml └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Owner of everything 2 | * @ChristophShyper @devops-infra/christophshyper 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Exclude 2 | * 3 | 4 | # Include 5 | !Dockerfile 6 | !entrypoint.sh 7 | !LICENSE 8 | !README.md 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | /.idea/ 3 | *.iml 4 | 5 | # Custom 6 | .tmp/ 7 | .venv 8 | .venv/ 9 | .envrc 10 | .env 11 | .tmp 12 | -------------------------------------------------------------------------------- /.shellcheckrc: -------------------------------------------------------------------------------- 1 | # shellcheck configuration 2 | shell=bash 3 | check-sourced=true 4 | external-sources=true 5 | source-path=SCRIPTDIR 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent coding styles 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /.hadolint.yaml: -------------------------------------------------------------------------------- 1 | failure-threshold: error 2 | format: tty 3 | strict-labels: false 4 | no-color: false 5 | no-fail: false 6 | disable-ignore-pragma: false 7 | trustedRegistries: 8 | - docker.io 9 | - ghcr.io 10 | 11 | # ignored: [string] 12 | # label-schema: 13 | # author: text 14 | # contact: email 15 | # created: rfc3339 16 | # version: semver 17 | # documentation: url 18 | # git-revision: hash 19 | # license: spdx 20 | # override: 21 | # error: [string] 22 | # warning: [string] 23 | # info: [string] 24 | # style: [string] 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: github-actions 5 | directory: / 6 | schedule: 7 | interval: weekly 8 | assignees: 9 | - ChristophShyper 10 | 11 | # Enable version updates for Docker 12 | - package-ecosystem: docker 13 | directory: / 14 | schedule: 15 | interval: weekly 16 | assignees: 17 | - ChristophShyper 18 | 19 | # # Enable version updates for pip 20 | # - package-ecosystem: pip 21 | # directory: / 22 | # schedule: 23 | # interval: daily 24 | # assignees: 25 | # - ChristophShyper 26 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | extends: default 2 | rules: 3 | empty-lines: 4 | max: 2 5 | document-end: 6 | present: false 7 | document-start: 8 | present: false 9 | indentation: 10 | spaces: 2 11 | indent-sequences: true 12 | check-multi-line-strings: false 13 | line-length: 14 | max: 140 15 | allow-non-breakable-inline-mappings: true 16 | new-line-at-end-of-file: enable 17 | new-lines: 18 | type: unix 19 | quoted-strings: 20 | required: only-when-needed 21 | extra-allowed: ['true', 'false'] 22 | trailing-spaces: {} 23 | truthy: 24 | allowed-values: ['true', 'false', 'yes', 'no'] 25 | check-keys: false 26 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | silent: true 4 | 5 | includes: 6 | variables: ./Taskfile.variables.yml 7 | cicd: 8 | taskfile: ./Taskfile.cicd.yml 9 | flatten: true 10 | docker: 11 | taskfile: ./Taskfile.docker.yml 12 | flatten: true 13 | 14 | 15 | tasks: 16 | default: 17 | desc: List tasks 18 | cmds: 19 | - task help 20 | 21 | help: 22 | desc: Detailed help 23 | cmds: 24 | - | 25 | echo "Tasks:" 26 | task --list 27 | echo "" 28 | echo "Environment:" 29 | echo " DOCKER_NAME={{.DOCKER_NAME}} DOCKER_USERNAME={{.DOCKER_USERNAME}}" 30 | echo " GHRC_NAME={{.GHRC_NAME}} GITHUB_USERNAME={{.GITHUB_USERNAME}}" 31 | echo " LAST_RELEASE={{.LAST_RELEASE}}" VERSION={{.VERSION}} VERSION_FULL={{.VERSION_FULL}} 32 | echo " BRANCH={{.GIT_BRANCH}} GIT_SHORT_SHA={{.GIT_SHORT_SHA}}" GIT_SHA={{.GIT_SHA}} 33 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:questing-20251029 2 | 3 | # Disable interactive mode 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | 6 | # Copy all needed files 7 | COPY entrypoint.sh / 8 | 9 | # Install needed packages 10 | SHELL ["/bin/bash", "-euxo", "pipefail", "-c"] 11 | # hadolint ignore=DL3008 12 | RUN chmod +x /entrypoint.sh ;\ 13 | apt-get update -y ;\ 14 | apt-get install --no-install-recommends -y \ 15 | gpg-agent \ 16 | software-properties-common ;\ 17 | echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections ;\ 18 | add-apt-repository ppa:git-core/ppa ;\ 19 | apt-get update -y ;\ 20 | apt-get install --no-install-recommends -y \ 21 | git ;\ 22 | # Install git-lfs without post-install configuration to avoid dpkg errors \ 23 | apt-get download git-lfs ;\ 24 | dpkg --unpack git-lfs*.deb ;\ 25 | rm -f /var/lib/dpkg/info/git-lfs.postinst ;\ 26 | dpkg --configure git-lfs ;\ 27 | apt-get install -f --no-install-recommends -y ;\ 28 | rm git-lfs*.deb ;\ 29 | apt-get clean ;\ 30 | rm -rf /var/lib/apt/lists/* 31 | 32 | # Finish up 33 | WORKDIR /github/workspace 34 | ENTRYPOINT ["/entrypoint.sh"] 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Krzysztof Szyper aka ChristophShyper (https://shyper.pro/) 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 NONINFINGEMENT. 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 | -------------------------------------------------------------------------------- /.github/workflows/cron-check-dependencies.yml: -------------------------------------------------------------------------------- 1 | name: (Cron) Check dependencies 2 | 3 | on: 4 | schedule: 5 | # Run every Monday at 08:00 UTC 6 | - cron: 0 8 * * 1 7 | 8 | permissions: 9 | contents: read 10 | packages: write 11 | 12 | jobs: 13 | dependency-check: 14 | name: Test dependencies 15 | runs-on: ubuntu-24.04-arm 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | with: 20 | fetch-depth: 0 21 | fetch-tags: true 22 | 23 | - name: Install Task 24 | uses: arduino/setup-task@v2.0.0 25 | with: 26 | version: 3.x 27 | 28 | - name: Install Docker Buildx 29 | uses: docker/setup-buildx-action@v3 30 | with: 31 | install: true 32 | 33 | - name: Install QEMU 34 | uses: docker/setup-qemu-action@v3 35 | with: 36 | image: tonistiigi/binfmt:latest 37 | platforms: amd64,arm64 38 | 39 | - name: Run linters 40 | run: task lint 41 | 42 | - name: Get Docker commands 43 | run: task docker:cmds 44 | 45 | - name: Build and push test image 46 | env: 47 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | run: task docker:push 50 | 51 | - name: Inspect image 52 | run: task docker:push:inspect 53 | -------------------------------------------------------------------------------- /.github/workflows/manual-sync-common-files.yml: -------------------------------------------------------------------------------- 1 | name: (Manual) Sync Common Files 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | type: 7 | description: File type to sync 8 | required: true 9 | default: all 10 | type: choice 11 | options: 12 | - all 13 | - configs 14 | - ignores 15 | - taskfiles 16 | - workflows 17 | 18 | permissions: 19 | contents: write 20 | pull-requests: write 21 | 22 | jobs: 23 | update: 24 | name: Sync common files and create pull request 25 | runs-on: ubuntu-24.04-arm 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v6 29 | with: 30 | fetch-depth: 0 31 | fetch-tags: true 32 | 33 | - name: Install Task 34 | uses: arduino/setup-task@v2.0.0 35 | with: 36 | version: 3.x 37 | 38 | - name: Sync files and get PR template 39 | id: version 40 | run: | 41 | task sync:${{ github.event.inputs.type }} 42 | task git:set-config 43 | task git:get-pr-template 44 | 45 | - name: Push to release branch 46 | uses: devops-infra/action-commit-push@v1 47 | with: 48 | github_token: ${{ secrets.GITHUB_TOKEN }} 49 | commit_message: ":art: Sync common files with action-template repository" 50 | target_branch: ${{ format('release/{0}', steps.version.outputs.REL_VERSION) }} 51 | 52 | - name: Create Pull Request 53 | uses: devops-infra/action-pull-request@v1 54 | with: 55 | github_token: ${{ secrets.GITHUB_TOKEN }} 56 | assignee: ${{ github.actor }} 57 | template: .tmp/PULL_REQUEST_TEMPLATE.md 58 | get_diff: true 59 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v6.0.0 4 | hooks: 5 | - id: check-added-large-files 6 | - id: check-case-conflict 7 | - id: check-executables-have-shebangs 8 | - id: check-illegal-windows-names 9 | - id: check-json 10 | - id: check-merge-conflict 11 | - id: check-shebang-scripts-are-executable 12 | - id: check-symlinks 13 | - id: check-xml 14 | - id: check-yaml 15 | - id: destroyed-symlinks 16 | - id: detect-private-key 17 | - id: end-of-file-fixer 18 | - id: mixed-line-ending 19 | args: [--fix=lf] 20 | - id: no-commit-to-branch 21 | args: [--branch, master, --branch, main] 22 | - id: pretty-format-json 23 | args: [--autofix] 24 | - id: trailing-whitespace 25 | - repo: local 26 | hooks: 27 | - id: actionlint 28 | name: actionlint 29 | entry: bash -lc 'docker run --rm -v "$PWD:/work" -w /work rhysd/actionlint:latest -color' 30 | language: system 31 | pass_filenames: false 32 | - id: hadolint 33 | name: hadolint 34 | entry: bash -lc 'docker run --rm -v "$PWD:/work" -w /work hadolint/hadolint:latest-debian hadolint Dockerfile' 35 | language: system 36 | pass_filenames: false 37 | - id: shellcheck 38 | name: shellcheck 39 | entry: bash -lc 'docker run --rm -v "$PWD:/work" -w /work koalaman/shellcheck:stable -x -S style entrypoint.sh' 40 | language: system 41 | pass_filenames: false 42 | - id: yamllint 43 | name: yamllint 44 | entry: bash -lc 'docker run --rm -v "$PWD:/work" -w /work cytopia/yamllint -c .yamllint.yml .' 45 | language: system 46 | pass_filenames: false 47 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Action for committing changes to repository 2 | author: Krzysztof Szyper / ChristophShyper / shyper.pro 3 | description: Powerful GitHub Action for automatically committing and pushing changes back to your repository 4 | inputs: 5 | github_token: 6 | description: Personal Access Token for GitHub for pushing the code 7 | required: true 8 | default: "" 9 | add_timestamp: 10 | description: Whether to add timestamp to a new branch name 11 | required: false 12 | default: "false" 13 | amend: 14 | description: Whether to make amendment to the previous commit (--amend). Can be combined with commit_message to change the message. 15 | required: false 16 | default: "false" 17 | commit_prefix: 18 | description: Prefix added to commit message 19 | required: false 20 | default: "" 21 | commit_message: 22 | description: Commit message to set 23 | required: false 24 | default: "" 25 | force: 26 | description: Whether to use force push (--force). Use only when you need to overwrite remote changes. Potentially dangerous. 27 | required: false 28 | default: "false" 29 | force_with_lease: 30 | description: Whether to use force push with lease (--force-with-lease). Safer than force as it checks for remote changes. 31 | required: false 32 | default: "false" 33 | no_edit: 34 | description: Whether to not edit commit message when using amend 35 | required: false 36 | default: "false" 37 | organization_domain: 38 | description: Name of GitHub Enterprise organization 39 | required: false 40 | default: github.com 41 | target_branch: 42 | description: Name of a new branch to push the code into 43 | required: false 44 | default: "" 45 | outputs: 46 | files_changed: 47 | description: List of changed files 48 | branch_name: 49 | description: Name of the branch code was pushed into 50 | runs: 51 | using: docker 52 | image: docker://devopsinfra/action-commit-push:v1.0.3 53 | env: 54 | GITHUB_TOKEN: ${{ inputs.github_token }} 55 | branding: 56 | color: purple 57 | icon: upload-cloud 58 | -------------------------------------------------------------------------------- /.github/workflows/auto-create-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: (Auto) Create Pull Request 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - master 7 | - dependabot/** 8 | 9 | permissions: 10 | contents: read 11 | packages: write 12 | pull-requests: write 13 | 14 | jobs: 15 | lint: 16 | name: Lint 17 | runs-on: ubuntu-24.04-arm 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v6 21 | with: 22 | fetch-depth: 0 23 | fetch-tags: true 24 | 25 | - name: Install Task 26 | uses: arduino/setup-task@v2.0.0 27 | with: 28 | version: 3.x 29 | 30 | - name: Run linters 31 | run: task lint 32 | 33 | build-and-push: 34 | name: Build and push 35 | runs-on: ubuntu-24.04-arm 36 | needs: [lint] 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v6 40 | with: 41 | fetch-depth: 0 42 | fetch-tags: true 43 | 44 | - name: Install Task 45 | uses: arduino/setup-task@v2.0.0 46 | with: 47 | version: 3.x 48 | 49 | - name: Install Docker Buildx 50 | uses: docker/setup-buildx-action@v3 51 | with: 52 | install: true 53 | 54 | - name: Install QEMU 55 | uses: docker/setup-qemu-action@v3 56 | with: 57 | image: tonistiigi/binfmt:latest 58 | platforms: amd64,arm64 59 | 60 | - name: Get Docker commands 61 | run: task docker:cmds 62 | 63 | - name: Build and push test image 64 | env: 65 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | run: task docker:push 68 | 69 | - name: Inspect image 70 | run: task docker:push:inspect 71 | 72 | pull-request: 73 | name: Pull Request 74 | runs-on: ubuntu-24.04-arm 75 | steps: 76 | - name: Checkout 77 | uses: actions/checkout@v6 78 | with: 79 | fetch-depth: 0 80 | fetch-tags: true 81 | 82 | - name: Install Task 83 | uses: arduino/setup-task@v2.0.0 84 | with: 85 | version: 3.x 86 | 87 | - name: Get template 88 | run: task git:get-pr-template 89 | 90 | - name: Create Pull Request 91 | uses: devops-infra/action-pull-request@v1 92 | with: 93 | github_token: ${{ secrets.GITHUB_TOKEN }} 94 | assignee: ${{ github.actor }} 95 | template: .tmp/PULL_REQUEST_TEMPLATE.md 96 | get_diff: true 97 | -------------------------------------------------------------------------------- /.github/workflows/auto-create-release.yml: -------------------------------------------------------------------------------- 1 | name: (Auto) Create release 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | push: 7 | branches: 8 | - release/** 9 | 10 | permissions: 11 | contents: write 12 | packages: write 13 | 14 | jobs: 15 | release: 16 | if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') 17 | name: Create Release 18 | runs-on: ubuntu-24.04-arm 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v6 22 | with: 23 | fetch-depth: 0 24 | fetch-tags: true 25 | 26 | - name: Install Task 27 | uses: arduino/setup-task@v2.0.0 28 | with: 29 | version: 3.x 30 | 31 | - name: Create and push git tags 32 | id: version 33 | env: 34 | VERSION_SUFFIX: "" 35 | run: | 36 | task lint 37 | task git:set-config 38 | task version:tag-release 39 | echo "REL_VERSION=$(task version:get)" >> "$GITHUB_OUTPUT" 40 | 41 | - name: Install Docker Buildx 42 | uses: docker/setup-buildx-action@v3 43 | with: 44 | install: true 45 | 46 | - name: Install QEMU 47 | uses: docker/setup-qemu-action@v3 48 | with: 49 | image: tonistiigi/binfmt:latest 50 | platforms: amd64,arm64 51 | 52 | - name: Get Docker commands 53 | env: 54 | VERSION_SUFFIX: "" 55 | run: task docker:cmds 56 | 57 | - name: Build and Push 58 | env: 59 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | VERSION_SUFFIX: "" 62 | run: task docker:push 63 | 64 | - name: Inspect image 65 | env: 66 | VERSION_SUFFIX: "" 67 | run: task docker:push:inspect 68 | 69 | - name: Create GitHub release 70 | uses: softprops/action-gh-release@v2 71 | with: 72 | tag_name: ${{ steps.version.outputs.REL_VERSION }} 73 | name: ${{ steps.version.outputs.REL_VERSION }} 74 | draft: false 75 | prerelease: false 76 | generate_release_notes: true 77 | env: 78 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 79 | 80 | - name: Update Docker hub description 81 | uses: peter-evans/dockerhub-description@v5 82 | with: 83 | username: ${{ vars.DOCKER_USERNAME }} 84 | password: ${{ secrets.DOCKER_TOKEN }} 85 | repository: ${{ vars.DOCKER_ORG_NAME }}/${{ github.event.repository.name }} 86 | short-description: ${{ github.event.repository.description }} 87 | -------------------------------------------------------------------------------- /.github/workflows/manual-update-version.yml: -------------------------------------------------------------------------------- 1 | name: (Manual) Update Version 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | type: 7 | description: Bump type 8 | required: true 9 | default: patch 10 | type: choice 11 | options: 12 | - patch 13 | - minor 14 | - major 15 | - set 16 | version: 17 | description: Explicit version when type="set" (e.g., v1.2.3) 18 | required: false 19 | default: '' 20 | 21 | permissions: 22 | contents: write 23 | pull-requests: write 24 | 25 | jobs: 26 | update: 27 | name: Update version and push release branch 28 | runs-on: ubuntu-24.04-arm 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v6 32 | with: 33 | fetch-depth: 0 34 | fetch-tags: true 35 | 36 | - name: Install Task 37 | uses: arduino/setup-task@v2.0.0 38 | with: 39 | version: 3.x 40 | 41 | - name: Update version 42 | id: version 43 | env: 44 | BUMP_TYPE: ${{ github.event.inputs.type }} 45 | INPUT_VERSION: ${{ github.event.inputs.version }} 46 | run: | 47 | set -eux 48 | case "${BUMP_TYPE}" in 49 | set) 50 | if [ -z "${INPUT_VERSION}" ]; then 51 | echo "Missing version for type=set" 52 | exit 1 53 | fi 54 | task version:set VERSION_OVERRIDE="${INPUT_VERSION}" 55 | ;; 56 | patch) 57 | task version:update:patch 58 | ;; 59 | minor) 60 | task version:update:minor 61 | ;; 62 | major) 63 | task version:update:major 64 | ;; 65 | *) 66 | echo "Unknown type: ${BUMP_TYPE}" 67 | exit 1 68 | ;; 69 | esac 70 | echo "REL_VERSION=$(task version:get)" >> "$GITHUB_OUTPUT" 71 | 72 | - name: Get template 73 | env: 74 | VERSION_SUFFIX: "" 75 | run: | 76 | task git:set-config 77 | task git:get-pr-template 78 | 79 | - name: Push to release branch 80 | uses: devops-infra/action-commit-push@v1 81 | with: 82 | github_token: ${{ secrets.GITHUB_TOKEN }} 83 | commit_message: ":rocket: Bump version to ${{ steps.version.outputs.REL_VERSION }}" 84 | target_branch: ${{ format('release/{0}', steps.version.outputs.REL_VERSION) }} 85 | 86 | - name: Create Pull Request 87 | uses: devops-infra/action-pull-request@v1 88 | with: 89 | github_token: ${{ secrets.GITHUB_TOKEN }} 90 | assignee: ${{ github.actor }} 91 | template: .tmp/PULL_REQUEST_TEMPLATE.md 92 | get_diff: true 93 | -------------------------------------------------------------------------------- /Taskfile.docker.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | silent: true 4 | 5 | tasks: 6 | docker:login: 7 | desc: Login to hub.docker.com and ghcr.io 8 | cmds: 9 | - echo "Logging into Docker Hub as {{.DOCKER_USERNAME}}" 10 | - echo "${DOCKER_TOKEN}" | docker login -u "{{.DOCKER_USERNAME}}" --password-stdin 11 | - echo "Logging into GHCR as {{.GITHUB_USERNAME}}" 12 | - echo "${GITHUB_TOKEN}" | docker login ghcr.io -u "{{.GITHUB_USERNAME}}" --password-stdin 13 | 14 | docker:cmds: 15 | desc: Show full docker build command 16 | cmds: 17 | - echo -e '{{.DOCKER_BUILD_START}} {{.DOCKER_BUILD_FINISH}}' | {{.SED}} 's/--/ \\\n --/g' 18 | 19 | docker:build: 20 | desc: Build Docker image 21 | cmds: 22 | - docker buildx create --use 23 | - '{{.DOCKER_BUILD_START}} {{.DOCKER_BUILD_FINISH}}' 24 | 25 | docker:build:inspect: 26 | desc: Inspect built Docker image 27 | cmds: 28 | - | 29 | image_inspect_out=$(docker image inspect {{.DOCKER_NAME}}:{{.VERSION_FULL}}{{.VERSION_SUFFIX}} | jq -r) 30 | echo -e "\nℹ️ Docker image inspect:" 31 | echo "$image_inspect_out" | jq 32 | 33 | docker:push: 34 | desc: Build and push Docker images 35 | deps: 36 | - task: docker:login 37 | cmds: 38 | - docker buildx create --use 39 | - '{{.DOCKER_BUILD_START}} --push {{.DOCKER_BUILD_FINISH}}' 40 | 41 | docker:push:inspect: 42 | desc: Inspect built Docker image 43 | cmds: 44 | - | 45 | set -eu 46 | image="{{.DOCKER_NAME}}:{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 47 | 48 | echo -e "\nℹ️ Trying local image inspect: $image" 49 | set +e 50 | image_inspect_out=$(docker image inspect "$image" 2>/dev/null || true) 51 | rc=$? 52 | set -e 53 | 54 | # Validate that docker inspect returned a non-empty array with an Id 55 | has_local=0 56 | if [ "$rc" -eq 0 ] && [ -n "$image_inspect_out" ]; then 57 | if echo "$image_inspect_out" | jq -e 'type=="array" and (length > 0) and \ 58 | (.[0].Id != null and .[0].Id != "")' >/dev/null 2>&1; then 59 | has_local=1 60 | fi 61 | fi 62 | 63 | if [ "$has_local" -eq 1 ]; then 64 | echo -e "\n✅ Local image found. Docker image inspect:" 65 | echo "$image_inspect_out" | jq 66 | image_sha=$(echo "$image_inspect_out" | jq -r '.[0].Id // empty') 67 | if [ -n "$image_sha" ]; then 68 | echo -e "\nℹ️ Docker manifest inspect (local):" 69 | docker manifest inspect "${image}@${image_sha}" | jq || true 70 | fi 71 | exit 0 72 | fi 73 | 74 | echo -e "\nℹ️ Local image not found or inspect returned empty; inspecting remote with buildx imagetools..." 75 | set +e 76 | raw=$(docker buildx imagetools inspect --raw "$image" 2>/dev/null || true) 77 | set -e 78 | 79 | if [ -z "$raw" ]; then 80 | echo "❌ Failed to inspect remote image with buildx imagetools: $image" 81 | exit 1 82 | fi 83 | 84 | echo -e "\n✅ Remote manifest/index (raw):" 85 | echo "$raw" | jq 86 | 87 | echo -e "\nℹ️ Attempting to pull and inspect per-platform manifests:" 88 | echo "$raw" | jq -r '.manifests[]?.digest' | while IFS= read -r digest; do 89 | if [ -z "$digest" ] || [ "$digest" = "null" ]; then 90 | continue 91 | fi 92 | ref="${image%@*}@${digest}" 93 | echo -e "\nℹ️ Pulling $ref (may fail for some registries)..." 94 | set +e 95 | docker pull "$ref" >/dev/null 2>&1 || true 96 | pulled_rc=$? 97 | set -e 98 | if [ "$pulled_rc" -eq 0 ]; then 99 | echo "ℹ️ Inspecting pulled image $ref" 100 | docker image inspect "$ref" | jq || true 101 | else 102 | echo "⚠️ Could not pull $ref; skipping image inspect" 103 | fi 104 | done 105 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Return code 6 | RET_CODE=0 7 | 8 | echo "Inputs:" 9 | echo " add_timestamp: ${INPUT_ADD_TIMESTAMP}" 10 | echo " amend: ${INPUT_AMEND}" 11 | echo " commit_prefix: ${INPUT_COMMIT_PREFIX}" 12 | echo " commit_message: ${INPUT_COMMIT_MESSAGE}" 13 | echo " force: ${INPUT_FORCE}" 14 | echo " force_with_lease: ${INPUT_FORCE_WITH_LEASE}" 15 | echo " no_edit: ${INPUT_NO_EDIT}" 16 | echo " organization_domain: ${INPUT_ORGANIZATION_DOMAIN}" 17 | echo " target_branch: ${INPUT_TARGET_BRANCH}" 18 | 19 | # Require github_token 20 | if [[ -z "${GITHUB_TOKEN}" ]]; then 21 | # shellcheck disable=SC2016 22 | MESSAGE='Missing env var "github_token: ${{ secrets.GITHUB_TOKEN }}".' 23 | echo -e "[ERROR] ${MESSAGE}" 24 | exit 1 25 | fi 26 | 27 | # Set git credentials 28 | git config --global safe.directory "${GITHUB_WORKSPACE}" 29 | git config --global safe.directory /github/workspace 30 | git remote set-url origin "https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@${INPUT_ORGANIZATION_DOMAIN}/${GITHUB_REPOSITORY}" 31 | git config --global user.name "${GITHUB_ACTOR}" 32 | git config --global user.email "${GITHUB_ACTOR}@users.noreply.${INPUT_ORGANIZATION_DOMAIN}" 33 | 34 | # Get changed files 35 | git add -A 36 | FILES_CHANGED=$(git diff --staged --name-status) 37 | if [[ -n ${FILES_CHANGED} ]]; then 38 | echo -e "\n[INFO] Files changed:\n${FILES_CHANGED}" 39 | else 40 | echo -e "\n[INFO] No files changed." 41 | fi 42 | 43 | # Setting branch name 44 | BRANCH="${INPUT_TARGET_BRANCH:-$(git symbolic-ref --short -q HEAD)}" 45 | # Add timestamp to branch name 46 | if [[ "${INPUT_ADD_TIMESTAMP}" == "true" ]]; then 47 | TIMESTAMP=$(date -u +"%Y-%m-%dT%H-%M-%SZ") 48 | if [[ -n ${BRANCH} ]]; then 49 | BRANCH="${BRANCH}-${TIMESTAMP}" 50 | else 51 | BRANCH="${TIMESTAMP}" 52 | fi 53 | fi 54 | echo -e "\n[INFO] Target branch: ${BRANCH}" 55 | 56 | # Enhanced branch handling with proper remote synchronization 57 | if [[ -n "${INPUT_TARGET_BRANCH}" || "${INPUT_ADD_TIMESTAMP}" == "true" ]]; then 58 | # Fetch latest changes from remote 59 | echo "[INFO] Fetching latest changes from remote..." 60 | git fetch origin || { 61 | echo "[WARNING] Could not fetch from remote. Proceeding with local operations." 62 | } 63 | 64 | # Check if remote branch exists 65 | REMOTE_BRANCH_EXISTS=$(git ls-remote --heads origin "${BRANCH}" 2>/dev/null | wc -l) 66 | 67 | # Improved main branch detection 68 | MAIN_BRANCH="main" 69 | if git show-ref --verify --quiet "refs/remotes/origin/main"; then 70 | MAIN_BRANCH="main" 71 | elif git show-ref --verify --quiet "refs/remotes/origin/master"; then 72 | MAIN_BRANCH="master" 73 | else 74 | # Try to get default branch from remote HEAD 75 | MAIN_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main") 76 | fi 77 | echo "[INFO] Detected main branch: ${MAIN_BRANCH}" 78 | 79 | if [[ ${REMOTE_BRANCH_EXISTS} -gt 0 ]]; then 80 | echo "[INFO] Remote branch '${BRANCH}' exists, checking out and updating..." 81 | # Check if local branch exists 82 | if git show-ref --verify --quiet "refs/heads/${BRANCH}"; then 83 | echo "[INFO] Local branch '${BRANCH}' exists, switching to it..." 84 | git checkout "${BRANCH}" || { 85 | echo "[ERROR] Failed to checkout branch ${BRANCH}" 86 | exit 1 87 | } 88 | else 89 | echo "[INFO] Creating local branch '${BRANCH}' from remote..." 90 | git checkout -b "${BRANCH}" "origin/${BRANCH}" || { 91 | echo "[ERROR] Failed to create local branch from remote" 92 | exit 1 93 | } 94 | fi 95 | 96 | # Ensure branch is up-to-date with main/master (only if they're different branches) 97 | if [[ "${BRANCH}" != "${MAIN_BRANCH}" ]] && git show-ref --verify --quiet "refs/remotes/origin/${MAIN_BRANCH}"; then 98 | echo "[INFO] Rebasing branch onto ${MAIN_BRANCH}..." 99 | git rebase "origin/${MAIN_BRANCH}" || { 100 | echo "[WARNING] Rebase onto ${MAIN_BRANCH} failed. This may indicate conflicts." 101 | echo "[INFO] Attempting to abort the rebase and continue without sync..." 102 | git rebase --abort 2>/dev/null || true 103 | echo "[INFO] Branch will remain at its current state without sync to ${MAIN_BRANCH}" 104 | } 105 | fi 106 | else 107 | echo "[INFO] Remote branch '${BRANCH}' does not exist, creating new branch..." 108 | # Ensure starting from the latest main/master 109 | if git show-ref --verify --quiet "refs/remotes/origin/${MAIN_BRANCH}"; then 110 | echo "[INFO] Creating branch from latest ${MAIN_BRANCH}..." 111 | git checkout -b "${BRANCH}" "origin/${MAIN_BRANCH}" || { 112 | echo "[ERROR] Failed to create branch from ${MAIN_BRANCH}" 113 | exit 1 114 | } 115 | else 116 | echo "[INFO] Creating branch from current HEAD..." 117 | git checkout -b "${BRANCH}" || { 118 | echo "[ERROR] Failed to create branch from HEAD" 119 | exit 1 120 | } 121 | fi 122 | fi 123 | fi 124 | 125 | # Create an auto commit 126 | COMMIT_PARAMS=() 127 | COMMIT_PARAMS+=("--allow-empty") 128 | 129 | # Commit if there are changes OR if we're amending (even without changes) 130 | if [[ -n ${FILES_CHANGED} || "${INPUT_AMEND}" == "true" ]]; then 131 | if [[ -n ${FILES_CHANGED} ]]; then 132 | echo "[INFO] Committing changes." 133 | fi 134 | 135 | if [[ "${INPUT_AMEND}" == "true" ]]; then 136 | COMMIT_PARAMS+=("--amend") 137 | echo "[INFO] Amending previous commit." 138 | fi 139 | 140 | if [[ "${INPUT_NO_EDIT}" == "true" ]]; then 141 | COMMIT_PARAMS+=("--no-edit") 142 | echo "[INFO] Using existing commit message (--no-edit)." 143 | git commit "${COMMIT_PARAMS[@]}" 144 | elif [[ -n "${INPUT_COMMIT_MESSAGE}" || -n "${INPUT_COMMIT_PREFIX}" ]]; then 145 | COMMIT_MESSAGE="${INPUT_COMMIT_PREFIX}${INPUT_COMMIT_MESSAGE}" 146 | if [[ "${INPUT_AMEND}" == "true" ]]; then 147 | echo "[INFO] Setting new commit message: ${COMMIT_MESSAGE}" 148 | fi 149 | 150 | if [[ -n ${FILES_CHANGED} ]]; then 151 | git commit "${COMMIT_PARAMS[@]}" -am "${COMMIT_MESSAGE}" -m "$(echo -e "Files changed:\n${FILES_CHANGED}")" 152 | else 153 | git commit "${COMMIT_PARAMS[@]}" -m "${COMMIT_MESSAGE}" 154 | fi 155 | elif [[ -n ${FILES_CHANGED} ]]; then 156 | git commit "${COMMIT_PARAMS[@]}" -am "Files changed:" -m "${FILES_CHANGED}" 157 | else 158 | # Amending without files changed and no new message - keep existing message 159 | COMMIT_PARAMS+=("--no-edit") 160 | git commit "${COMMIT_PARAMS[@]}" 161 | fi 162 | fi 163 | 164 | # Push 165 | if [[ "${INPUT_FORCE}" == "true" ]]; then 166 | echo "[INFO] Force pushing changes using --force" 167 | git push --force origin "${BRANCH}" 168 | elif [[ "${INPUT_FORCE_WITH_LEASE}" == "true" ]]; then 169 | echo "[INFO] Force pushing changes with lease" 170 | git push --force-with-lease origin "${BRANCH}" 171 | elif [[ -n ${FILES_CHANGED} || "${INPUT_AMEND}" == "true" || -n "${INPUT_TARGET_BRANCH}" ]]; then 172 | echo "[INFO] Pushing changes" 173 | # Check if branch has upstream tracking 174 | if git rev-parse --abbrev-ref "${BRANCH}@{upstream}" >/dev/null 2>&1; then 175 | echo "[INFO] Branch has upstream, pushing normally" 176 | git push origin "${BRANCH}" 177 | else 178 | echo "[INFO] Branch has no upstream, setting upstream on push" 179 | git push -u origin "${BRANCH}" 180 | fi 181 | fi 182 | 183 | # Finish 184 | { 185 | echo "files_changed<> "${GITHUB_OUTPUT}" 190 | if [[ ${RET_CODE} != "0" ]]; then 191 | echo -e "\n[ERROR] Check log for errors." 192 | exit 1 193 | else 194 | # Pass in other cases 195 | echo -e "\n[INFO] No errors found." 196 | exit 0 197 | fi 198 | -------------------------------------------------------------------------------- /Taskfile.variables.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | silent: true 4 | 5 | env: 6 | DOCKER_BUILDKIT: '1' 7 | TERM: xterm-256color 8 | 9 | vars: 10 | # System context 11 | SED: 12 | sh: | 13 | if [ "$(uname -s)" = "Darwin" ]; then 14 | if command -v gsed >/dev/null 2>&1; then 15 | echo gsed 16 | else 17 | if command -v brew >/dev/null 2>&1; then 18 | # Quietly ensure gnu-sed is installed 19 | brew list gnu-sed >/dev/null 2>&1 || { 20 | HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_ANALYTICS=1 brew update >/dev/null 2>&1 || true 21 | HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_ANALYTICS=1 brew install gnu-sed >/dev/null 2>&1 22 | } 23 | echo gsed 24 | else 25 | echo sed 26 | fi 27 | fi 28 | else 29 | echo sed 30 | fi 31 | PROJECT_DIR_NAME: 32 | sh: basename "$PWD" 33 | 34 | # Container metadata 35 | DOCKER_IMAGE: '{{.DOCKER_IMAGE | default .PROJECT_DIR_NAME}}' 36 | GITHUB_REPO: '{{.GITHUB_REPO | default .PROJECT_DIR_NAME}}' 37 | DOCKER_ORG_NAME: '{{.DOCKER_ORG_NAME | default "devopsinfra"}}' 38 | GITHUB_ORG_NAME: '{{.GITHUB_ORG_NAME | default "devops-infra"}}' 39 | DOCKER_USERNAME: '{{.DOCKER_USERNAME | default "christophshyper"}}' 40 | GITHUB_USERNAME: '{{.GITHUB_USERNAME | default "ChristophShyper"}}' 41 | DOCKER_NAME: '{{.DOCKER_ORG_NAME}}/{{.DOCKER_IMAGE}}' 42 | GITHUB_NAME: '{{.GITHUB_ORG_NAME}}/{{.GITHUB_REPO}}' 43 | GHRC_NAME: ghcr.io/{{.GITHUB_ORG_NAME}}/{{.GITHUB_REPO}} 44 | DEFAULT_BRANCH: master 45 | VERSION_FROM_ACTION_YML: 46 | sh: 'grep "image: docker://{{.DOCKER_NAME}}:" action.yml | cut -d ":" -f 4' 47 | AUTHOR_FROM_ACTION_YML: 48 | sh: | 49 | grep -e "^author:" action.yml | head -1 | awk -F": " '{print $2}' 50 | NAME_FROM_ACTION_YML: 51 | sh: | 52 | grep -e "^name:" action.yml | head -1 | awk -F": " '{print $2}' 53 | DESCRIPTION_FROM_ACTION_YML: 54 | sh: | 55 | grep -e "^description:" action.yml | head -1 | awk -F": " '{print $2}' 56 | LABEL_AUTHOR: '{{.LABEL_AUTHOR | default .AUTHOR_FROM_ACTION_YML}}' 57 | LABEL_NAME: '{{.LABEL_NAME | default .NAME_FROM_ACTION_YML}}' 58 | LABEL_DESCRIPTION: '{{.LABEL_DESCRIPTION | default .DESCRIPTION_FROM_ACTION_YML}}' 59 | LABEL_REPO_URL: '{{ default (printf "https://github.com/%s/%s" .GITHUB_ORG_NAME .DOCKER_IMAGE) .LABEL_REPO_URL }}' 60 | LABEL_DOCS_URL: >- 61 | {{ default (printf "https://github.com/%s/%s/blob/%s/README.md" .GITHUB_ORG_NAME .DOCKER_IMAGE 62 | .DEFAULT_BRANCH) .LABEL_DOCS_URL }} 63 | LABEL_HOMEPAGE: '{{.LABEL_HOMEPAGE | default "https://shyper.pro"}}' 64 | LABEL_VENDOR: '{{.LABEL_VENDOR | default "DevOps-Infra"}}' 65 | LABEL_LICENSE: '{{.LABEL_LICENSE | default "MIT"}}' 66 | 67 | # Build context 68 | CONTEXT: '{{.CONTEXT | default "."}}' 69 | DOCKERFILE: '{{.DOCKERFILE | default "Dockerfile"}}' 70 | PLATFORMS: '{{.PLATFORMS | default "linux/amd64,linux/arm64"}}' 71 | BUILD_DATE: 72 | sh: date -u +"%Y-%m-%dT%H:%M:%SZ" 73 | LAST_RELEASE: 74 | sh: | 75 | curl --silent "https://api.github.com/repos/{{.GITHUB_ORG_NAME}}/{{.GITHUB_REPO}}/releases/latest" | \ 76 | grep '"tag_name":' | \ 77 | {{.SED}} -E 's/.*"([^"]+)".*/\1/' 78 | VERSION_SUFFIX: 79 | sh: | 80 | if [ "${VERSION_SUFFIX+x}" = "x" ]; then 81 | printf "%s" "${VERSION_SUFFIX}" 82 | else 83 | printf "%s" "-test" 84 | fi 85 | VERSION_OVERRIDE: 86 | sh: echo "__TAKEN_FROM_ACTION_YML__" 87 | VERSION: 88 | sh: | 89 | if [ "${VERSION_OVERRIDE}" = "__TAKEN_FROM_ACTION_YML__" ]; then 90 | echo "{{.VERSION_FROM_ACTION_YML}}" 91 | elif [ -z "${VERSION_OVERRIDE}" ]; then 92 | echo "{{.LAST_RELEASE}}" 93 | else 94 | echo "${VERSION_OVERRIDE}" 95 | fi 96 | VERSION_NO_V: 97 | sh: v="{{.VERSION}}"; echo "${v#v}" 98 | MAJOR: 99 | sh: echo "{{.VERSION_NO_V}}" | awk -F\. '{print $1}' 100 | MINOR: 101 | sh: echo "{{.VERSION_NO_V}}" | awk -F\. '{print $2}' 102 | PATCH: 103 | sh: echo "{{.VERSION_NO_V}}" | awk -F\. '{print $3}' 104 | VERSION_MAJOR: v{{.MAJOR}} 105 | VERSION_MINOR: v{{.MAJOR}}.{{.MINOR}} 106 | VERSION_FULL: v{{.MAJOR}}.{{.MINOR}}.{{.PATCH}} 107 | NEXT_PATCH: 108 | sh: echo $(( {{.PATCH}} + 1 )) 109 | NEXT_MINOR: 110 | sh: echo $(( {{.MINOR}} + 1 )) 111 | NEXT_MAJOR: 112 | sh: echo $(( {{.MAJOR}} + 1 )) 113 | MAJOR_FROM_ACTION_YML: 114 | sh: echo "{{.VERSION_FROM_ACTION_YML}}" | awk -F\. '{print $1}' 115 | MINOR_FROM_ACTION_YML: 116 | sh: echo "{{.VERSION_FROM_ACTION_YML}}" | awk -F\. '{print $1"."$2}' 117 | 118 | # Git metadata 119 | GIT_SHA: 120 | sh: git rev-parse HEAD 2>/dev/null || echo 0000000000000000000000000000000000000000 121 | GIT_SHORT_SHA: 122 | sh: git rev-parse --short HEAD 2>/dev/null || echo 0000000 123 | GIT_BRANCH: 124 | sh: | 125 | if [ -n "${GITHUB_REF:-}" ]; then 126 | echo "${GITHUB_REF#refs/heads/}" 127 | else 128 | git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown" 129 | fi 130 | 131 | # Labels for http://label-schema.org/rc1/#build-time-labels 132 | # And for https://github.com/opencontainers/image-spec/blob/master/annotations.md 133 | ANNOTATIONS: >- 134 | --annotation index:org.label-schema.schema-version="1.0" 135 | --annotation index:org.label-schema.build-date="{{.BUILD_DATE}}" 136 | --annotation index:org.label-schema.name="{{.LABEL_NAME}}" 137 | --annotation index:org.label-schema.description="{{.LABEL_DESCRIPTION}}" 138 | --annotation index:org.label-schema.usage="{{.LABEL_DOCS_URL}}" 139 | --annotation index:org.label-schema.url="{{.LABEL_HOMEPAGE}}" 140 | --annotation index:org.label-schema.vcs-url="{{.LABEL_REPO_URL}}" 141 | --annotation index:org.label-schema.vcs-ref="{{.GIT_SHA}}" 142 | --annotation index:org.label-schema.vendor="{{.LABEL_VENDOR}}" 143 | --annotation index:org.label-schema.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 144 | --annotation index:org.opencontainers.image.created="{{.BUILD_DATE}}" 145 | --annotation index:org.opencontainers.image.authors="{{.LABEL_AUTHOR}}" 146 | --annotation index:org.opencontainers.image.url="{{.LABEL_HOMEPAGE}}" 147 | --annotation index:org.opencontainers.image.documentation="{{.LABEL_DOCS_URL}}" 148 | --annotation index:org.opencontainers.image.source="{{.LABEL_REPO_URL}}" 149 | --annotation index:org.opencontainers.image.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 150 | --annotation index:org.opencontainers.image.revision="{{.GIT_SHA}}" 151 | --annotation index:org.opencontainers.image.vendor="{{.LABEL_VENDOR}}" 152 | --annotation index:org.opencontainers.image.licenses="{{.LABEL_LICENSE}}" 153 | --annotation index:org.opencontainers.image.title="{{.LABEL_NAME}}" 154 | --annotation index:org.opencontainers.image.description="{{.LABEL_DESCRIPTION}}" 155 | --annotation manifest:org.label-schema.schema-version="1.0" 156 | --annotation manifest:org.label-schema.build-date="{{.BUILD_DATE}}" 157 | --annotation manifest:org.label-schema.name="{{.LABEL_NAME}}" 158 | --annotation manifest:org.label-schema.description="{{.LABEL_DESCRIPTION}}" 159 | --annotation manifest:org.label-schema.usage="{{.LABEL_DOCS_URL}}" 160 | --annotation manifest:org.label-schema.url="{{.LABEL_HOMEPAGE}}" 161 | --annotation manifest:org.label-schema.vcs-url="{{.LABEL_REPO_URL}}" 162 | --annotation manifest:org.label-schema.vcs-ref="{{.GIT_SHA}}" 163 | --annotation manifest:org.label-schema.vendor="{{.LABEL_VENDOR}}" 164 | --annotation manifest:org.label-schema.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 165 | --annotation manifest:org.opencontainers.image.created="{{.BUILD_DATE}}" 166 | --annotation manifest:org.opencontainers.image.authors="{{.LABEL_AUTHOR}}" 167 | --annotation manifest:org.opencontainers.image.url="{{.LABEL_HOMEPAGE}}" 168 | --annotation manifest:org.opencontainers.image.documentation="{{.LABEL_DOCS_URL}}" 169 | --annotation manifest:org.opencontainers.image.source="{{.LABEL_REPO_URL}}" 170 | --annotation manifest:org.opencontainers.image.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 171 | --annotation manifest:org.opencontainers.image.revision="{{.GIT_SHA}}" 172 | --annotation manifest:org.opencontainers.image.vendor="{{.LABEL_VENDOR}}" 173 | --annotation manifest:org.opencontainers.image.licenses="{{.LABEL_LICENSE}}" 174 | --annotation manifest:org.opencontainers.image.title="{{.LABEL_NAME}}" 175 | --annotation manifest:org.opencontainers.image.description="{{.LABEL_DESCRIPTION}}" 176 | LABELS: >- 177 | --label org.label-schema.schema-version="1.0" 178 | --label org.label-schema.build-date="{{.BUILD_DATE}}" 179 | --label org.label-schema.name="{{.LABEL_NAME}}" 180 | --label org.label-schema.description="{{.LABEL_DESCRIPTION}}" 181 | --label org.label-schema.usage="{{.LABEL_DOCS_URL}}" 182 | --label org.label-schema.url="{{.LABEL_HOMEPAGE}}" 183 | --label org.label-schema.vcs-url="{{.LABEL_REPO_URL}}" 184 | --label org.label-schema.vcs-ref="{{.GIT_SHA}}" 185 | --label org.label-schema.vendor="{{.LABEL_VENDOR}}" 186 | --label org.label-schema.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 187 | --label org.opencontainers.image.created="{{.BUILD_DATE}}" 188 | --label org.opencontainers.image.authors="{{.LABEL_AUTHOR}}" 189 | --label org.opencontainers.image.url="{{.LABEL_HOMEPAGE}}" 190 | --label org.opencontainers.image.documentation="{{.LABEL_DOCS_URL}}" 191 | --label org.opencontainers.image.source="{{.LABEL_REPO_URL}}" 192 | --label org.opencontainers.image.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 193 | --label org.opencontainers.image.revision="{{.GIT_SHA}}" 194 | --label org.opencontainers.image.vendor="{{.LABEL_VENDOR}}" 195 | --label org.opencontainers.image.licenses="{{.LABEL_LICENSE}}" 196 | --label org.opencontainers.image.title="{{.LABEL_NAME}}" 197 | --label org.opencontainers.image.description="{{.LABEL_DESCRIPTION}}" 198 | TAGS: >- 199 | --tag {{.DOCKER_NAME}}:{{.VERSION_FULL}}{{.VERSION_SUFFIX}} 200 | --tag {{.DOCKER_NAME}}:{{.VERSION_MINOR}}{{.VERSION_SUFFIX}} 201 | --tag {{.DOCKER_NAME}}:{{.VERSION_MAJOR}}{{.VERSION_SUFFIX}} 202 | --tag {{.DOCKER_NAME}}:latest{{.VERSION_SUFFIX}} 203 | --tag {{.GHRC_NAME}}:{{.VERSION_FULL}}{{.VERSION_SUFFIX}} 204 | --tag {{.GHRC_NAME}}:{{.VERSION_MINOR}}{{.VERSION_SUFFIX}} 205 | --tag {{.GHRC_NAME}}:{{.VERSION_MAJOR}}{{.VERSION_SUFFIX}} 206 | --tag {{.GHRC_NAME}}:latest{{.VERSION_SUFFIX}} 207 | DOCKER_BUILD_START: 208 | sh: | 209 | if docker buildx version >/dev/null 2>&1; then 210 | echo "docker buildx build --platform {{.PLATFORMS}}" 211 | else 212 | echo "docker build" 213 | fi 214 | DOCKER_BUILD_FINISH: '{{.ANNOTATIONS}} {{.LABELS}} {{.TAGS}} --file={{.DOCKERFILE}} .' 215 | -------------------------------------------------------------------------------- /Taskfile.cicd.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | silent: true 4 | 5 | vars: 6 | PR_TEMPLATE: https://raw.githubusercontent.com/devops-infra/.github/master/PULL_REQUEST_TEMPLATE.md 7 | TEMPLATE_REPO_BASE_URL: https://raw.githubusercontent.com/devops-infra/template-action/refs/heads/master 8 | 9 | tasks: 10 | pre-commit: 11 | desc: Run all pre-commit hooks 12 | cmds: 13 | - pre-commit run --all-files 14 | 15 | pre-commit:install: 16 | desc: Install pre-commit hooks 17 | cmds: 18 | - pre-commit install 19 | 20 | lint: 21 | desc: Run all linters (Dockerfile, shell scripts, workflows, YAML) 22 | cmds: 23 | - task: lint:actionlint 24 | - task: lint:hadolint 25 | - task: lint:shellcheck 26 | - task: lint:yamllint 27 | 28 | lint:actionlint: 29 | desc: Lint GitHub Actions workflows with actionlint 30 | cmds: 31 | - | 32 | echo "▶️ Running actionlint..." 33 | set +e 34 | docker run --rm -i -v "$PWD:/work" -w /work rhysd/actionlint:latest -color 35 | rc=$? 36 | set -e 37 | if [ "$rc" -eq 0 ]; then 38 | echo "✅ actionlint passed" 39 | else 40 | echo "❌ actionlint failed" 41 | exit $rc 42 | fi 43 | 44 | lint:hadolint: 45 | desc: Lint Dockerfile with hadolint 46 | cmds: 47 | - | 48 | echo "▶️ Running hadolint..." 49 | set +e 50 | docker run --rm -i -v "$PWD:/work" -w /work hadolint/hadolint:latest-debian < Dockerfile 51 | rc=$? 52 | set -e 53 | if [ "$rc" -eq 0 ]; then 54 | echo "✅ hadolint passed" 55 | else 56 | echo "❌ hadolint failed" 57 | exit $rc 58 | fi 59 | 60 | lint:shellcheck: 61 | desc: Lint shell scripts with shellcheck 62 | cmds: 63 | - | 64 | echo "▶️ Running shellcheck..." 65 | set +e 66 | docker run --rm -i -v "$PWD:/work" -w /work koalaman/shellcheck:stable -x -S style entrypoint.sh 67 | rc=$? 68 | set -e 69 | if [ "$rc" -eq 0 ]; then 70 | echo "✅ shellcheck passed" 71 | else 72 | echo "❌ shellcheck failed" 73 | exit $rc 74 | fi 75 | 76 | lint:yamllint: 77 | desc: Lint YAML files with yamllint 78 | cmds: 79 | - | 80 | echo "▶️ Running yamllint..." 81 | set +e 82 | docker run --rm -i -v "$PWD:/work" -w /work cytopia/yamllint -c .yamllint.yml . 83 | rc=$? 84 | set -e 85 | if [ "$rc" -eq 0 ]; then 86 | echo "✅ yamllint passed" 87 | else 88 | echo "❌ yamllint failed" 89 | exit $rc 90 | fi 91 | 92 | version:get: 93 | desc: Get current version 94 | cmds: 95 | - echo "{{.VERSION}}" 96 | 97 | version:set: 98 | desc: Update version in README.md and action.yml 99 | cmds: 100 | - | 101 | # check if VERSION if different than VERSION_FROM_ACTION_YML 102 | if [ "{{.VERSION}}" = "{{.VERSION_FROM_ACTION_YML}}" ]; then 103 | echo "❌ ERROR: VERSION is same as VERSION_FROM_ACTION_YML ({{.VERSION}})" 104 | exit 1 105 | fi 106 | - echo Updating full version from {{.VERSION_FROM_ACTION_YML}} to {{.VERSION}} 107 | - echo Updating minor version from {{.MINOR_FROM_ACTION_YML}} to {{.VERSION_MINOR}} 108 | - echo Updating major version from {{.MAJOR_FROM_ACTION_YML}} to {{.VERSION_MAJOR}} 109 | - "{{.SED}} -i 's#{{.DOCKER_NAME}}:{{.VERSION_FROM_ACTION_YML}}#{{.DOCKER_NAME}}:{{.VERSION}}#g' action.yml" 110 | - "{{.SED}} -i 's#{{.DOCKER_NAME}}@{{.VERSION_FROM_ACTION_YML}}#{{.DOCKER_NAME}}@{{.VERSION}}#g' README.md" 111 | - "{{.SED}} -i 's#{{.GITHUB_NAME}}@{{.VERSION_FROM_ACTION_YML}}#{{.GITHUB_NAME}}@{{.VERSION}}#g' README.md" 112 | - "{{.SED}} -i 's#{{.DOCKER_NAME}}@{{.MINOR_FROM_ACTION_YML}}#{{.DOCKER_NAME}}@{{.VERSION_MINOR}}#g' README.md" 113 | - "{{.SED}} -i 's#{{.GITHUB_NAME}}@{{.MINOR_FROM_ACTION_YML}}#{{.GITHUB_NAME}}@{{.VERSION_MINOR}}#g' README.md" 114 | - "{{.SED}} -i 's#{{.DOCKER_NAME}}@{{.MAJOR_FROM_ACTION_YML}}#{{.DOCKER_NAME}}@{{.VERSION_MAJOR}}#g' README.md" 115 | - "{{.SED}} -i 's#{{.GITHUB_NAME}}@{{.MAJOR_FROM_ACTION_YML}}#{{.GITHUB_NAME}}@{{.VERSION_MAJOR}}#g' README.md" 116 | 117 | version:update:patch: 118 | desc: Increment patch version (e.g., 1.2.3 -> 1.2.4) 119 | cmds: 120 | - task version:set VERSION=v{{.MAJOR}}.{{.MINOR}}.{{.NEXT_PATCH}} 121 | 122 | version:update:minor: 123 | desc: Increment minor version (e.g., 1.2.3 -> 1.3.0) 124 | cmds: 125 | - task version:set VERSION=v{{.MAJOR}}.{{.NEXT_MINOR}}.0 126 | 127 | version:update:major: 128 | desc: Increment major version (e.g., 1.2.3 -> 2.0.0) 129 | cmds: 130 | - task version:set VERSION=v{{.NEXT_MAJOR}}.0.0 131 | 132 | version:tag-release: 133 | internal: true 134 | desc: Create set of git tags 135 | cmds: 136 | - | 137 | set -eu 138 | if (set -o | grep -q pipefail) 2>/dev/null; then set -o pipefail; fi 139 | 140 | REMOTE='origin' 141 | FULL='{{.VERSION_FULL}}' 142 | MINOR='{{.VERSION_MINOR}}' 143 | MAJOR='{{.VERSION_MAJOR}}' 144 | 145 | # Validate vX.Y.Z 146 | if ! printf "%s" "$FULL" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then 147 | echo "❌ ERROR: VERSION '$FULL' must match vX.Y.Z" >&2 148 | exit 1 149 | fi 150 | 151 | tag_sha() { git rev-parse "refs/tags/$1" 2>/dev/null || true; } 152 | remote_tag_sha() { git ls-remote --tags "$REMOTE" "refs/tags/$1" 2>/dev/null | awk '{print $1}' || true; } 153 | 154 | echo "ℹ️ INFO: Tags - Full: $FULL | Minor: $MINOR | Major: $MAJOR" 155 | 156 | # Full tag: must NOT exist on remote; fail fast if it does 157 | full_remote_sha="$(remote_tag_sha "$FULL")" 158 | if [ -n "$full_remote_sha" ]; then 159 | echo "❌ ERROR: Full tag '$FULL' already exists on remote; aborting" >&2 160 | exit 1 161 | fi 162 | 163 | # Create full tag locally (if missing) and push 164 | if git rev-parse --quiet --verify "refs/tags/$FULL" >/dev/null 2>&1; then 165 | echo "ℹ️ INFO: Full tag '$FULL' exists locally but not on remote; pushing" 166 | else 167 | echo "ℹ️ INFO: Creating full tag '$FULL'" 168 | git tag --annotate "$FULL" --message "$FULL" 169 | fi 170 | git push "$REMOTE" "refs/tags/$FULL" 171 | echo "✅ OK: Pushed full tag '$FULL'" 172 | 173 | # Minor tag: create or update 174 | git tag --force --annotate "$MINOR" --message "$FULL" 175 | minor_local_sha="$(tag_sha "$MINOR")" 176 | minor_remote_sha="$(remote_tag_sha "$MINOR")" 177 | if [ -z "$minor_remote_sha" ]; then 178 | git push "$REMOTE" "refs/tags/$MINOR" 179 | echo "✅ OK: Created and pushed minor tag '$MINOR' -> $minor_local_sha" 180 | else 181 | if [ "$minor_local_sha" != "$minor_remote_sha" ]; then 182 | echo "⚠️ WARN: Updating remote minor tag '$MINOR' to $minor_local_sha (was $minor_remote_sha)" 183 | git push --force "$REMOTE" "refs/tags/$MINOR" 184 | else 185 | echo "ℹ️ INFO: Minor tag '$MINOR' already up-to-date" 186 | fi 187 | fi 188 | 189 | # Major tag: create or update 190 | git tag --force --annotate "$MAJOR" --message "$FULL" 191 | major_local_sha="$(tag_sha "$MAJOR")" 192 | major_remote_sha="$(remote_tag_sha "$MAJOR")" 193 | if [ -z "$major_remote_sha" ]; then 194 | git push "$REMOTE" "refs/tags/$MAJOR" 195 | echo "✅ OK: Created and pushed major tag '$MAJOR' -> $major_local_sha" 196 | else 197 | if [ "$major_local_sha" != "$major_remote_sha" ]; then 198 | echo "⚠️ WARN: Updating remote major tag '$MAJOR' to $major_local_sha (was $major_remote_sha)" 199 | git push --force "$REMOTE" "refs/tags/$MAJOR" 200 | else 201 | echo "ℹ️ INFO: Major tag '$MAJOR' already up-to-date" 202 | fi 203 | fi 204 | 205 | git:get-pr-template: 206 | desc: Get pull request template 207 | cmds: 208 | - mkdir -p .tmp 209 | - curl -LsS {{.PR_TEMPLATE}} -o .tmp/PULL_REQUEST_TEMPLATE.md 210 | 211 | git:set-config: 212 | desc: Set git user config 213 | cmds: 214 | - git config user.name "github-actions[bot]" 215 | - git config user.email "github-actions[bot]@users.noreply.github.com" 216 | 217 | sync:all: 218 | desc: Sync all files with template-action 219 | cmds: 220 | - task sync:configs 221 | - task sync:ignores 222 | - task sync:taskfiles 223 | - task sync:workflows 224 | 225 | sync:configs: 226 | desc: Sync configuration files with template-action 227 | cmds: 228 | - | 229 | echo "▶️ Syncing configuration files from template-action..." 230 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.editorconfig -o ./.editorconfig 231 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.hadolint.yaml -o ./.hadolint.yaml 232 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.pre-commit-config.yaml -o ./.pre-commit-config.yaml 233 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.shellcheckrc -o ./.shellcheckrc 234 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.yamllint.yml -o ./.yamllint.yml 235 | git add .editorconfig .hadolint.yaml .pre-commit-config.yaml .shellcheckrc .yamllint.yml 236 | echo "✅ Synced configuration files" 237 | 238 | sync:ignores: 239 | desc: Sync ignore files with template-action 240 | cmds: 241 | - | 242 | echo "▶️ Syncing ignore files from template-action..." 243 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.gitignore -o ./.gitignore 244 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.dockerignore -o ./.dockerignore 245 | git add .gitignore .dockerignore 246 | echo "✅ Synced ignore files" 247 | 248 | sync:taskfiles: 249 | desc: Sync Taskfiles with template-action 250 | cmds: 251 | - | 252 | echo "▶️ Syncing Taskfiles from template-action..." 253 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/Taskfile.yml -o ./Taskfile.yml 254 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/Taskfile.cicd.yml -o ./Taskfile.cicd.yml 255 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/Taskfile.docker.yml -o ./Taskfile.docker.yml 256 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/Taskfile.variables.yml -o ./Taskfile.variables.yml 257 | git add Taskfile*.yml 258 | echo "✅ Synced Taskfiles" 259 | 260 | sync:workflows: 261 | desc: Sync GitHub workflows with template-action 262 | cmds: 263 | - | 264 | echo "▶️ Syncing GitHub workflows from template-action..." 265 | mkdir -p .github/workflows 266 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/workflows/auto-create-pull-request.yml \ 267 | -o ./.github/workflows/auto-create-pull-request.yml 268 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/workflows/auto-create-release.yml \ 269 | -o ./.github/workflows/auto-create-release.yml 270 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/workflows/cron-check-dependencies.yml \ 271 | -o ./.github/workflows/cron-check-dependencies.yml 272 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/workflows/manual-sync-common-files.yml \ 273 | -o ./.github/workflows/manual-sync-common-files.yml 274 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/workflows/manual-update-version.yml \ 275 | -o ./.github/workflows/manual-update-version.yml 276 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/dependabot.yml \ 277 | -o ./.github/dependabot.yml 278 | git add .github/workflows/ 279 | echo "✅ Synced GitHub workflows" 280 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 GitHub Action for committing changes to repository 2 | **Powerful GitHub Action for automatically committing and pushing changes back to your repository.** 3 | 4 | 5 | ## 📦 Available on 6 | - **Docker Hub:** [devopsinfra/action-commit-push:latest](https://hub.docker.com/repository/docker/devopsinfra/action-commit-push) 7 | - **GitHub Packages:** [ghcr.io/devops-infra/action-commit-push:latest](https://github.com/orgs/devops-infra/packages/container/package/action-commit-push) 8 | 9 | 10 | ## ✨ Features 11 | - **📝 Custom commit messages:** Add custom prefixes and messages to commits 12 | - **🌿 Branch management:** Create new branches automatically with optional timestamps 13 | - **⏰ Timestamp support:** Add timestamps to branch names for cron-based updates 14 | - **🔄 Integration-ready:** Works seamlessly with other DevOps workflows 15 | - **💪 Force push options:** Support for `--force` and `--force-with-lease` when needed 16 | - **🔀 Pull request integration:** Perfect companion for automated PR workflows 17 | 18 | 19 | ## 🔗 Related Actions 20 | **Perfect for automation workflows and integrates seamlessly with [devops-infra/action-pull-request](https://github.com/devops-infra/action-pull-request).** 21 | 22 | 23 | ## 📊 Badges 24 | [ 25 | ![GitHub repo](https://img.shields.io/badge/GitHub-devops--infra%2Faction--commit--push-blueviolet.svg?style=plastic&logo=github) 26 | ![GitHub last commit](https://img.shields.io/github/last-commit/devops-infra/action-commit-push?color=blueviolet&logo=github&style=plastic&label=Last%20commit) 27 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/devops-infra/action-commit-push?color=blueviolet&label=Code%20size&style=plastic&logo=github) 28 | ![GitHub license](https://img.shields.io/github/license/devops-infra/action-commit-push?color=blueviolet&logo=github&style=plastic&label=License) 29 | ](https://github.com/devops-infra/action-commit-push "shields.io") 30 |
31 | [ 32 | ![DockerHub](https://img.shields.io/badge/DockerHub-devopsinfra%2Faction--commit--push-blue.svg?style=plastic&logo=docker) 33 | ![Docker version](https://img.shields.io/docker/v/devopsinfra/action-commit-push?color=blue&label=Version&logo=docker&style=plastic&sort=semver) 34 | ![Image size](https://img.shields.io/docker/image-size/devopsinfra/action-commit-push/latest?label=Image%20size&style=plastic&logo=docker) 35 | ![Docker Pulls](https://img.shields.io/docker/pulls/devopsinfra/action-commit-push?color=blue&label=Pulls&logo=docker&style=plastic) 36 | ](https://hub.docker.com/r/devopsinfra/action-commit-push "shields.io") 37 | 38 | 39 | ## 🏷️ Version Tags: vX, vX.Y, vX.Y.Z 40 | This action supports three tag levels for flexible versioning: 41 | - `vX`: latest patch of the major version (e.g., `v1`). 42 | - `vX.Y`: latest patch of the minor version (e.g., `v1.2`). 43 | - `vX.Y.Z`: fixed to a specific release (e.g., `v1.2.3`). 44 | 45 | 46 | ## 📖 API Reference 47 | 48 | ```yaml 49 | - name: Run the Action 50 | uses: devops-infra/action-commit-push@v1.0.3 51 | with: 52 | github_token: "${{ secrets.GITHUB_TOKEN }}" 53 | add_timestamp: true 54 | amend: false 55 | commit_prefix: "[AUTO]" 56 | commit_message: "Automatic commit" 57 | force: false 58 | force_with_lease: false 59 | no_edit: false 60 | organization_domain: github.com 61 | target_branch: update/version 62 | ``` 63 | 64 | 65 | ### 🔧 Input Parameters 66 | | Input Variable | Required | Default | Description | 67 | |-----------------------|----------|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| 68 | | `github_token` | Yes | `""` | Personal Access Token for GitHub for pushing the code. | 69 | | `add_timestamp` | No | `false` | Whether to add the timestamp to a new branch name. Uses format `%Y-%m-%dT%H-%M-%SZ`. | 70 | | `amend` | No | `false` | Whether to make an amendment to the previous commit (`--amend`). Can be combined with `commit_message` to change the commit message. | 71 | | `commit_prefix` | No | `""` | Prefix added to commit message. Combines with `commit_message`. | 72 | | `commit_message` | No | `""` | Commit message to set. Combines with `commit_prefix`. Can be used with `amend` to change the commit message. | 73 | | `force` | No | `false` | Whether to use force push (`--force`). Use only when you need to overwrite remote changes. Potentially dangerous. | 74 | | `force_with_lease` | No | `false` | Whether to use force push with lease (`--force-with-lease`). Safer than `force` as it checks for remote changes. Set `fetch-depth: 0` for `actions/checkout`. | 75 | | `no_edit` | No | `false` | Whether to not edit commit message when using amend (`--no-edit`). | 76 | | `organization_domain` | No | `github.com` | GitHub Enterprise domain name. | 77 | | `target_branch` | No | *current branch* | Name of a new branch to push the code into. Creates branch if not existing. | 78 | 79 | 80 | ### 📤 Output Parameters 81 | | Output | Description | 82 | |-----------------|--------------------------------------------------------------------------| 83 | | `files_changed` | List of changed files, as returned by `git diff --staged --name-status`. | 84 | | `branch_name` | Name of the branch code was pushed into. | 85 | 86 | 87 | ## 💻 Usage Examples 88 | 89 | ### 📝 Basic Example 90 | Commit and push changes to the currently checked out branch. 91 | 92 | ```yaml 93 | name: Run the Action 94 | on: 95 | push 96 | jobs: 97 | change-and-push: 98 | runs-on: ubuntu-latest 99 | steps: 100 | - name: Checkout repository 101 | uses: actions/checkout@v5 102 | - name: Change something 103 | run: | 104 | find . -type f -name "*.md" -print0 | xargs -0 sed -i "s/foo/bar/g" 105 | 106 | - name: Commit and push changes 107 | uses: devops-infra/action-commit-push@v1.0.3 108 | with: 109 | github_token: ${{ secrets.GITHUB_TOKEN }} 110 | commit_message: "Replace foo with bar" 111 | ``` 112 | 113 | ### 🔀 Advanced Example 114 | Commit and push changes to a new branch and create a pull request using [devops-infra/action-pull-request](https://github.com/devops-infra/action-pull-request). 115 | 116 | ```yaml 117 | name: Push changes and create PR 118 | on: 119 | push 120 | jobs: 121 | change-and-push: 122 | runs-on: ubuntu-latest 123 | steps: 124 | - name: Checkout repository 125 | uses: actions/checkout@v5 126 | - name: Change something 127 | run: | 128 | find . -type f -name "*.md" -print0 | xargs -0 sed -i "s/foo/bar/g" 129 | 130 | - name: Commit and push changes 131 | uses: devops-infra/action-commit-push@v.11.4 132 | with: 133 | github_token: ${{ secrets.GITHUB_TOKEN }} 134 | commit_prefix: "[AUTO-COMMIT] " 135 | commit_message: "Replace foo with bar" 136 | 137 | - name: Create pull request 138 | uses: devops-infra/action-pull-request@v1 139 | with: 140 | github_token: ${{ secrets.GITHUB_TOKEN }} 141 | body: "**Automated pull request**

Replaced foo with bar" 142 | title: ${{ github.event.commits[0].message }} 143 | ``` 144 | 145 | 146 | ### 💪 Force Push Example 147 | When you need to amend the previous commit and force push (useful when adding automatic changes to manual commit). 148 | 149 | ```yaml 150 | name: Amend and force push 151 | on: 152 | workflow_dispatch: 153 | inputs: 154 | new_commit_message: 155 | description: 'New commit message' 156 | required: true 157 | default: 'Updated commit message' 158 | 159 | jobs: 160 | amend-commit: 161 | runs-on: ubuntu-latest 162 | steps: 163 | - name: Checkout repository with full history 164 | uses: actions/checkout@v5 165 | with: 166 | fetch-depth: 0 # Required for force_with_lease 167 | - name: Make some changes 168 | run: | 169 | echo "Additional content" >> README.md 170 | 171 | - name: Amend and force push with lease 172 | uses: devops-infra/action-commit-push@v1.0.3 173 | with: 174 | github_token: ${{ secrets.GITHUB_TOKEN }} 175 | commit_message: ${{ github.event.inputs.new_commit_message }} 176 | amend: true 177 | force_with_lease: true # Safer force push option 178 | ``` 179 | 180 | ## 📝 Amend Options 181 | When using `amend: true`, you have several options for handling the commit message: 182 | 183 | 1. **Change the commit message**: Set `commit_message` to provide a new message 184 | ```yaml 185 | - uses: devops-infra/action-commit-push@v1.0.3 186 | with: 187 | github_token: ${{ secrets.GITHUB_TOKEN }} 188 | commit_message: "Fixed typo in documentation" 189 | amend: true 190 | force_with_lease: true 191 | ``` 192 | 193 | 2. **Keep existing message**: Set `no_edit: true` to keep the original commit message 194 | ```yaml 195 | - uses: devops-infra/action-commit-push@v1.0.3 196 | with: 197 | github_token: ${{ secrets.GITHUB_TOKEN }} 198 | amend: true 199 | no_edit: true 200 | force_with_lease: true 201 | ``` 202 | 203 | 3. **Default behavior**: If neither is set, uses "Files changed:" with file list (when files are modified) 204 | 205 | **💡 Note:** Amending works even without file changes - useful for just changing commit messages! 206 | 207 | 208 | ## ⚠️ Force Push Options 209 | This action provides two force push options for different scenarios: 210 | 211 | ### 🛡️ `force_with_lease` (Recommended) 212 | - Uses `git push --force-with-lease` 213 | - **Safer option** that checks if someone else has pushed changes to the remote branch 214 | - Prevents accidentally overwriting other people's work 215 | - **Required:** Set `fetch-depth: 0` in your `actions/checkout` step 216 | - **Use case:** Amending commits, rebasing, or other history modifications 217 | 218 | ### ⚡ `force` (Use with Caution) 219 | - Uses `git push --force` 220 | - **Potentially dangerous** as it will overwrite remote changes unconditionally 221 | - No safety checks - will overwrite any remote changes 222 | - **Use case:** Only when you're absolutely certain you want to overwrite remote changes 223 | 224 | **⚠️ Important:** Never use both options simultaneously. `force_with_lease` takes precedence if both are set to `true`. 225 | 226 | 227 | ### 🎯 Use specific version 228 | Run the Action with a specific version tag. 229 | 230 | ```yaml 231 | name: Run the Action 232 | on: 233 | push: 234 | branches-ignore: master 235 | jobs: 236 | action-commit-push: 237 | runs-on: ubuntu-latest 238 | steps: 239 | - uses: actions/checkout@v5 240 | 241 | - uses: devops-infra/action-commit-push@v1.0.3 242 | id: Pin patch version 243 | 244 | - uses: devops-infra/action-commit-push@v1.0 245 | id: Pin minor version 246 | 247 | - uses: devops-infra/action-commit-push@v1 248 | id: Pin major version 249 | ``` 250 | 251 | 252 | ## 🤝 Contributing 253 | Contributions are welcome! See [CONTRIBUTING](https://github.com/devops-infra/.github/blob/master/CONTRIBUTING.md). 254 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 255 | 256 | 257 | ## 📄 License 258 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 259 | 260 | 261 | ## 💬 Support 262 | If you have any questions or need help, please: 263 | - 📝 Create an [issue](https://github.com/devops-infra/action-commit-push/issues) 264 | - 🌟 Star this repository if you find it useful! 265 | --------------------------------------------------------------------------------