├── .github └── workflows │ └── main.yml ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml └── entrypoint.sh /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: publish-to-git 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@master 15 | - uses: actions/setup-node@v1 16 | - uses: ./ 17 | id: publish 18 | with: 19 | branch: master 20 | github_token: '${{ secrets.GITHUB_TOKEN }}' 21 | source_folder: .github 22 | target_folder: test 23 | dry_run: true 24 | - shell: bash 25 | run: | 26 | test -n "${{ steps.publish.outputs.commit_hash }}" 27 | test -n "${{ steps.publish.outputs.working_directory }}" 28 | 29 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | 3 | RUN apk add --no-cache rsync git bash 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | 7 | ENTRYPOINT ["/bin/bash", "/entrypoint.sh"] 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sean Middleditch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | publish-to-git 2 | ============== 3 | 4 | [GitHub Action](https://github.com/features/actions) for publishing a directory 5 | and its contents to another git repository. 6 | 7 | This can be especially useful for publishing static website, such as with 8 | [GitHub Pages](https://pages.github.com/), from built files in other job 9 | steps, such as [Doxygen](http://www.doxygen.nl/) generated HTML files. 10 | 11 | **NOTE**: GitHub currently requires the use of a Personal Access Token for 12 | pushing to other repositories. Pushing to the current repository should work 13 | with the always-available GitHub Token (available via 14 | `{{ secrets.GITHUB_TOKEN }}`. If pushing to another repository, a Personal 15 | Access Token will need to be [created](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) and assigned to the 16 | workflow [secrets](https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables). 17 | 18 | Inputs 19 | ------ 20 | 21 | - `repository`: Destination repository (default: current repository). 22 | - `branch`: Destination branch (required). 23 | - `host`: Destination git host (default: `github.com`). 24 | - `github_token`: GitHub Token (required; use `secrets.GITHUB_TOKEN`). 25 | - `github_pat`: Personal Access Token or other https credentials. 26 | - `source_folder`: Source folder in workspace to copy (default: workspace root). 27 | - `target_folder`: Target folder in destination branch to copy to (default: repository root). 28 | - `commit_author`: Override commit author (default: `{github.actor}@users.noreply.github.com`). 29 | - `commit_message`: Set commit message (default: `[workflow] Publish from [repository]:[branch]/[folder]`). 30 | - `dry_run`: Does not push if non-empty (default: empty). 31 | - `working_directory`: Location to checkout repository (default: random location in `${HOME}`) 32 | - `initial_source_folder`: Source folder in workspace to copy if branch didn't exist (default: `source_folder` value) 33 | - `initial_commit_message`: Commit message if branch didn't exist (default: `Initial commit`) 34 | 35 | Outputs 36 | ------- 37 | 38 | - `commit_hash`: SHA hash of the new commit. 39 | - `working_directory`: Working directory of git clone of repository. 40 | 41 | License 42 | ------- 43 | 44 | MIT License. See [LICENSE](LICENSE) for details. 45 | 46 | Usage Example 47 | ------------- 48 | 49 | ```yaml 50 | jobs: 51 | publish: 52 | - uses: actions/checkout@master 53 | - run: | 54 | sh scripts/build-doxygen-html.sh --out static/html 55 | - uses: seanmiddleditch/gha-publish-to-git@master 56 | with: 57 | branch: gh-pages 58 | github_token: '${{ secrets.GITHUB_TOKEN }}' 59 | github_pat: '${{ secrets.GH_PAT }}' 60 | source_folder: static/html 61 | if: success() && github.event == 'push' 62 | ``` 63 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: publish-to-git 3 | description: 'Publish files to a git repository' 4 | branding: 5 | icon: 'git-commit' 6 | color: 'blue' 7 | inputs: 8 | repository: 9 | description: 'Destination repository (default: current repository)' 10 | default: '' 11 | branch: 12 | description: 'Destination branch' 13 | required: true 14 | host: 15 | description: 'Destination git host' 16 | default: 'github.com' 17 | github_token: 18 | description: 'GitHub Token (use `secrets.GITHUB_TOKEN`)' 19 | required: true 20 | github_pat: 21 | description: 'Personal Access Token or other https credentials' 22 | default: '' 23 | source_folder: 24 | description: 'Source folder in workspace to copy (default: workspace root)' 25 | default: '' 26 | target_folder: 27 | description: 'Target folder in destination branch to copy to (default: repository root)' 28 | default: '' 29 | commit_author: 30 | description: 'User Name (default: [github.actor]@users.noreply.github.com)' 31 | default: '' 32 | commit_message: 33 | description: 'Commit message (default: [workflow] Publish from [repository]:[branch]/[folder])' 34 | default: '' 35 | dry_run: 36 | description: 'Do not push to repository (set to non-empty string to make dry-run)' 37 | default: '' 38 | working_directory: 39 | description: 'Working directory for clone (default: random location in `${HOME}`)' 40 | default: '' 41 | initial_source_folder: 42 | description: 'Source folder in workspace to copy if branch didnt exist (default: source_folder value)' 43 | default: '' 44 | initial_commit_message: 45 | description: 'Commit message if branch didnt exist' 46 | default: 'Initial commit' 47 | outputs: 48 | commit_hash: 49 | description: 'Hash of the new commit' 50 | working_directory: 51 | description: 'Working directory of temporary repository' 52 | runs: 53 | using: 'docker' 54 | image: 'Dockerfile' 55 | args: 56 | - ${{ inputs.repository }} 57 | - ${{ inputs.branch }} 58 | - ${{ inputs.host }} 59 | - ${{ inputs.github_token }} 60 | - ${{ inputs.github_pat }} 61 | - ${{ inputs.source_folder }} 62 | - ${{ inputs.target_folder }} 63 | - ${{ inputs.commit_author }} 64 | - ${{ inputs.commit_message }} 65 | - ${{ inputs.dry_run }} 66 | - ${{ inputs.working_directory }} 67 | - ${{ inputs.initial_source_folder }} 68 | - ${{ inputs.initial_commit_message }} 69 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | # Name the Docker inputs. 4 | # 5 | INPUT_REPOSITORY="$1" 6 | INPUT_BRANCH="$2" 7 | INPUT_HOST="$3" 8 | INPUT_GITHUB_TOKEN="$4" 9 | INPUT_GITHUB_PAT="$5" 10 | INPUT_SOURCE_FOLDER="$6" 11 | INPUT_TARGET_FOLDER="$7" 12 | INPUT_COMMIT_AUTHOR="$8" 13 | INPUT_COMMIT_MESSAGE="$9" 14 | INPUT_DRYRUN="${10}" 15 | INPUT_WORKDIR="${11}" 16 | INPUT_INITIAL_SOURCE_FOLDER="${12}" 17 | INPUT_INITIAL_COMMIT_MESSAGE="${13}" 18 | 19 | # Check for required inputs. 20 | # 21 | [ -z "$INPUT_BRANCH" ] && echo >&2 "::error::'branch' is required" && exit 1 22 | [ -z "$INPUT_GITHUB_TOKEN" -a -z "$INPUT_GITHUB_PAT" ] && echo >&2 "::error::'github_token' or 'github_pat' is required" && exit 1 23 | 24 | # Set state from inputs or defaults. 25 | # 26 | REPOSITORY="${INPUT_REPOSITORY:-${GITHUB_REPOSITORY}}" 27 | BRANCH="${INPUT_BRANCH}" 28 | HOST="${INPUT_GIT_HOST:-github.com}" 29 | TOKEN="${INPUT_GITHUB_PAT:-${INPUT_GITHUB_TOKEN}}" 30 | REMOTE="${INPUT_REMOTE:-https://${TOKEN}@${HOST}/${REPOSITORY}.git}" 31 | 32 | SOURCE_FOLDER="${INPUT_SOURCE_FOLDER:-.}" 33 | INITIAL_SOURCE_FOLDER="${INPUT_INITIAL_SOURCE_FOLDER:-${SOURCE_FOLDER}}" 34 | TARGET_FOLDER="${INPUT_TARGET_FOLDER}" 35 | 36 | REF="${GITHUB_BASE_REF:-${GITHUB_REF}}" 37 | REF_BRANCH=$(echo "${REF}" | rev | cut -d/ -f1 | rev) 38 | [ -z "$REF_BRANCH" ] && echo 2>&1 "No ref branch" && exit 1 39 | 40 | COMMIT_AUTHOR="${INPUT_AUTHOR:-${GITHUB_ACTOR} <${GITHUB_ACTOR}@users.noreply.github.com>}" 41 | COMMIT_MESSAGE="${INPUT_COMMIT_MESSAGE:-[${GITHUB_WORKFLOW}] Publish from ${GITHUB_REPOSITORY}:${REF_BRANCH}/${SOURCE_FOLDER}}" 42 | INITIAL_COMMIT_MESSAGE="${INPUT_INITIAL_COMMIT_MESSAGE}" 43 | 44 | # Calculate the real source path. 45 | # 46 | SOURCE_PATH="$(realpath "${SOURCE_FOLDER}")" 47 | INITIAL_SOURCE_PATH="$(realpath "${INITIAL_SOURCE_FOLDER}")" 48 | [ -z "${SOURCE_PATH}" ] && exit 1 49 | [ -z "${INITIAL_SOURCE_PATH}" ] && exit 1 50 | echo "::debug::SOURCE_PATH=${SOURCE_PATH}" 51 | echo "::debug::INITIAL_SOURCE_PATH=${INITIAL_SOURCE_PATH}" 52 | 53 | # Let's start doing stuff. 54 | echo "Publishing ${SOURCE_FOLDER} to ${REMOTE}:${BRANCH}/${TARGET_FOLDER}" 55 | 56 | # Create a working directory; the workspace may be filled with other important 57 | # files. 58 | # 59 | WORK_DIR="${INPUT_WORKDIR:-$(mktemp -d "${HOME}/gitrepo.XXXXXX")}" 60 | [ -z "${WORK_DIR}" ] && echo >&2 "::error::Failed to create temporary working directory" && exit 1 61 | cd "${WORK_DIR}" 62 | 63 | # Initialize git repo and configure for remote access. 64 | # 65 | echo "Initializing repository with remote ${REMOTE}" 66 | git init || exit 1 67 | git config --local user.email "${GITHUB_ACTOR}@users.noreply.github.com" || exit 1 68 | git config --local user.name "${GITHUB_ACTOR}" || exit 1 69 | git remote add origin "${REMOTE}" || exit 1 70 | 71 | # Fetch initial (current contents). 72 | # 73 | echo "Fetching ${REMOTE}:${BRANCH}" 74 | if [ "$(git ls-remote --heads "${REMOTE}" "${BRANCH}" | wc -l)" == 0 ] ; then 75 | echo "Initialising ${BRANCH} branch" 76 | git checkout --orphan ${BRANCH} 77 | TARGET_PATH="${WORK_DIR}/${TARGET_FOLDER}" 78 | echo "Populating ${TARGET_PATH}" 79 | mkdir -p "${TARGET_PATH}" || exit 1 80 | rsync -a --quiet --delete --exclude ".git" "${INITIAL_SOURCE_PATH}/" "${TARGET_PATH}" || exit 1 81 | 82 | echo "Creating initial commit" 83 | git add "${TARGET_PATH}" || exit 1 84 | git commit -m "${INITIAL_COMMIT_MESSAGE}" --author "${COMMIT_AUTHOR}" || exit 1 85 | COMMIT_HASH="$(git rev-parse HEAD)" 86 | echo "Created commit ${COMMIT_HASH}" 87 | 88 | if [ -z "${INPUT_DRYRUN}" ] ; then 89 | echo "Pushing to ${REMOTE}:${BRANCH}" 90 | git push origin "${BRANCH}" || exit 1 91 | else 92 | echo "[DRY-RUN] Not pushing to ${REMOTE}:${BRANCH}" 93 | fi 94 | else 95 | git fetch --depth 1 origin "${BRANCH}" || exit 1 96 | git checkout -b "${BRANCH}" || exit 1 97 | git pull origin "${BRANCH}" || exit 1 98 | fi 99 | 100 | # Create the target directory (if necessary) and copy files from source. 101 | # 102 | TARGET_PATH="${WORK_DIR}/${TARGET_FOLDER}" 103 | echo "Populating ${TARGET_PATH}" 104 | mkdir -p "${TARGET_PATH}" || exit 1 105 | rsync -a --quiet --delete --exclude ".git" "${SOURCE_PATH}/" "${TARGET_PATH}" || exit 1 106 | 107 | # Check changes 108 | # 109 | if [ -z "$(git status -s)" ] ; then 110 | echo "No changes, script exited" 111 | exit 0 112 | fi 113 | 114 | # Create commit with changes. 115 | # 116 | echo "Creating commit" 117 | git add "${TARGET_PATH}" || exit 1 118 | git commit -m "${COMMIT_MESSAGE}" --author "${COMMIT_AUTHOR}" || exit 1 119 | COMMIT_HASH="$(git rev-parse HEAD)" 120 | echo "Created commit ${COMMIT_HASH}" 121 | 122 | # Publish output variables. 123 | # 124 | echo "::set-output name=commit_hash::${COMMIT_HASH}" 125 | echo "::set-output name=working_directory::${WORK_DIR}" 126 | 127 | # Push if not a dry-run. 128 | # 129 | if [ -z "${INPUT_DRYRUN}" ] ; then 130 | echo "Pushing to ${REMOTE}:${BRANCH}" 131 | git push origin "${BRANCH}" || exit 1 132 | else 133 | echo "[DRY-RUN] Not pushing to ${REMOTE}:${BRANCH}" 134 | fi 135 | --------------------------------------------------------------------------------