diff --git a/.gitignore b/.gitignore index eee06e7..8ab173f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .gradle .idea/ +tests/build/** \ No newline at end of file diff --git a/LICENSE b/LICENSE index d645695..f5173c3 100644 --- a/LICENSE +++ b/LICENSE @@ -200,3 +200,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +-------------------------------------------------------------------------- + +The following files are licensed under the MIT License from https://github.com/fmahnke/shell-semver: + + post-release/increment_version.sh + release-notes/increment_version.sh + +See licenses/LICENSE-MIT.txt for the full license terms. \ No newline at end of file diff --git a/README.md b/README.md index 484618b..a4d818d 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. --> -# Grails github-actions +# grails-github-actions -Custom GitHub actions used by the Apache Grails projects. +Custom GitHub actions used by the Apache Grails projects. Please see the READMEs in each action for usage instructions. ## Usages Used by: https://github.com/search?q=org%3Aapache+%22uses%3A+apache%2Fgrails-github-actions%2F%22+language%3Ayml&type=code diff --git a/deploy-github-pages/README.md b/deploy-github-pages/README.md index 1561942..269ba68 100644 --- a/deploy-github-pages/README.md +++ b/deploy-github-pages/README.md @@ -14,5 +14,51 @@ See the License for the specific language governing permissions and limitations under the License. --> -# Deploy to Github Pages +# Deploy Documentation to Github Pages +A GitHub Action to copy documentation files to a specified documentation branch. Works by creating a subfolder named the same as the documentation branch, checking out the documentation branch to that folder, staging files, and then pushing them. Action is configured via environment variables. + +For releases, the following folders are published: +- `latest` - a folder that's intended to be updated to the latest release documentation. +- `X.0.x` - where `X` is a major version number. For example, `1.0.x`. This folder is intended to be updated to the latest version of a a major version. +- `X.X.X` - where `X.X.X` is a specific version number. For example, `1.0.0`. This folder is intended to store documentation for a specific version. + +For snapshots, the following folders are published: +- `snapshot` - a folder that's intended to be updated to the latest snapshot documentation. + +## Requirements +If using the default `GITHUB_TOKEN`, this action requires permission `contents: write`. Otherwise, the provided `GH_TOKEN` must be able to commit to the documentation branch. + +## Environment Variables +* (required) `GRADLE_PUBLISH_RELEASE` - if this documentation is for a release set to `true`, otherwise set to `false`. +* (required) `SOURCE_FOLDER` - the folder in the action working directory that contains the documentation files to be copied. This should be a relative path from the root of the repository. +* (required for release) `VERSION` - the version of the documentation being deployed. Must be a [Semantic Version](https://semver.org/). This is required for release documentation publishing only. +* (optional) `DOCUMENTATION_BRANCH` - the branch to which the documentation files will be copied. Defaults to `gh-pages`. +* (optional) `GH_TOKEN` - the GitHub token to use for authentication. If not provided, the action will use the default GitHub token available in the environment. +* (optional) `TARGET_SUBFOLDER` - if specified, a nested subfolder will be created with this name in any documentation folder. +* (optional) `LAST_RELEASE_FOLDER` - the `latest` folder name for a release. Defaults to `latest`. +* (optional) `LAST_SNAPSHOT_FOLDER` - the `snapshot` folder name for a snapshot. Defaults to `snapshot`. +* (optional) `SKIP_RELEASE_FOLDER` - if set to `true`, the action will not create a specific release version folder. Defaults to `false`. +* (optional) `SKIP_SNAPSHOT_FOLDER` - if set to `true`, the action will not publish any snapshot documentation. Defaults to `false`. +* (optional) `TARGET_REPOSITORY` - the target repository to which the documentation files will be copied. If not provided, the action will use the repository from which it is run. Format of `owner/repository` is expected. + +## Example Usage + +Snapshot Usage: +```yaml + - name: "🚀 Publish to Github Pages" + uses: apache/grails-github-actions/deploy-github-pages@asf + env: + GRADLE_PUBLISH_RELEASE: 'false' + SOURCE_FOLDER: build/docs +``` + +Release Usage: +```yaml + - name: "🚀 Publish to Github Pages" + uses: apache/grails-github-actions/deploy-github-pages@asf + env: + GRADLE_PUBLISH_RELEASE: 'true' + SOURCE_FOLDER: build/docs + VERSION: ${{ needs.publish.outputs.release_version }} +``` \ No newline at end of file diff --git a/deploy-github-pages/entrypoint.sh b/deploy-github-pages/entrypoint.sh index cfe2a35..8faab00 100755 --- a/deploy-github-pages/entrypoint.sh +++ b/deploy-github-pages/entrypoint.sh @@ -18,7 +18,7 @@ # under the License. # -# Action will create a folder gh-pages and set that folder to track the gh-pages branch +# Action will publish the specified documentation to the specified documentation branch set_value_or_error() { local value="$1" @@ -93,7 +93,7 @@ publish_artifacts() { fi fi - echo "Publishing ${SOURCE_FOLDER} to gh-pages:${PUBLISH_PATH}" + echo "Publishing ${SOURCE_FOLDER} to ${DOCUMENTATION_BRANCH}:${PUBLISH_PATH}" mkdir -p "${PUBLISH_PATH}" cp -r "../${SOURCE_FOLDER}/." "${PUBLISH_PATH}" git add --verbose "${PUBLISH_PATH}"/* @@ -132,10 +132,12 @@ is_highest_version() { set -e +set_value_or_error "${DOCUMENTATION_BRANCH}" 'gh-pages' 'DOCUMENTATION_BRANCH' + # GH_TOKEN - the token to access the github repository, can be GITHUB_TOKEN if the same repo and permissions are set correctly -set_value_or_error "${GH_TOKEN}" "" "GH_TOKEN" +set_value_or_error "${GH_TOKEN}" "${GITHUB_TOKEN}" "GH_TOKEN" -# GITHUB_USER_NAME - the username to commit to gh-pages branch, defaults to GITHUB_ACTOR (assumes permissions are set correctly) +# GITHUB_USER_NAME - the username to commit to documentation branch, defaults to GITHUB_ACTOR (assumes permissions are set correctly) set_value_or_error "${GITHUB_USER_NAME}" "${GITHUB_ACTOR}" "GITHUB_USER_NAME" # LAST_RELEASE_FOLDER - when a release is performed, instead of just copying it to a version number, copy it to this static folder name @@ -198,9 +200,11 @@ if [[ "$SKIP_SNAPSHOT_FOLDER" == "true" && "$GRADLE_PUBLISH_RELEASE" == "false" exit 0 fi -GIT_REPO_URL="https://${GITHUB_USER_NAME}:${GH_TOKEN}@github.com/${TARGET_REPOSITORY}.git" +set_value_or_error "${GITHUB_URL_BASE}" "github.com" "GITHUB_URL_BASE" +set_value_or_error "${GIT_TRANSFER_PROTOCOL}" "https" "GIT_TRANSFER_PROTOCOL" +GIT_REPO_URL="${GIT_TRANSFER_PROTOCOL}://${GITHUB_USER_NAME}:${GH_TOKEN}@${GITHUB_URL_BASE}/${TARGET_REPOSITORY}.git" -# Initialize a Git Repository under a separate location from the existing checkout that will be the gh-pages branch +# Initialize a Git Repository under a separate location from the existing checkout that will be the documentation branch cd "${GITHUB_WORKSPACE}" git init git config --global user.email "${GITHUB_USER_NAME}@users.noreply.github.com" @@ -209,28 +213,35 @@ git config --global http.version HTTP/1.1 git config --global http.postBuffer 157286400 # Create or checkout the documentation branch -if git ls-remote --heads "${GIT_REPO_URL}" gh-pages | grep -q "refs/heads/gh-pages"; then - echo "gh-pages branch found, cloning" - git clone "${GIT_REPO_URL}" gh-pages --branch gh-pages --single-branch - cd gh-pages +if git ls-remote --heads "${GIT_REPO_URL}" "${DOCUMENTATION_BRANCH}" | grep -q "refs/heads/${DOCUMENTATION_BRANCH}"; then + echo "::group::Checkout documentation branch" + echo "documentation branch found, cloning" + git clone "${GIT_REPO_URL}" "${DOCUMENTATION_BRANCH}" --branch "${DOCUMENTATION_BRANCH}" --single-branch + cd ${DOCUMENTATION_BRANCH} + echo "::endgroup::" else - echo "Creating gh-pages branch as it does not exist" - mkdir gh-pages - cd gh-pages + echo "::group::Creating documentation branch" + echo "Creating documentation branch ${DOCUMENTATION_BRANCH} as it does not exist" + mkdir "${DOCUMENTATION_BRANCH}" + cd "${DOCUMENTATION_BRANCH}" git init - git checkout -b gh-pages - git remote add origin "${GIT_REPO_URL}" + git checkout -b "${DOCUMENTATION_BRANCH}" + git remote add origin "${GIT_REPO_URL}" + echo "::endgroup::" fi # grails repos have a convention that they create a ghpages.html to replace the root index.html if [[ -f "../${SOURCE_FOLDER}/ghpages.html" ]]; then + echo "::group::Staging root index.html" echo "${SOURCE_FOLDER}/ghpages.html detected, replacing root index.html" cp "../${SOURCE_FOLDER}/ghpages.html" index.html git add index.html + echo "::endgroup::" fi # stage the documents if [[ "$GRADLE_PUBLISH_RELEASE" == "false" ]]; then + echo "::group::Publishing Snapshot" echo "Snapshot detected" # Subfolder support @@ -242,11 +253,12 @@ if [[ "$GRADLE_PUBLISH_RELEASE" == "false" ]]; then fi publish_artifacts + echo "::endgroup::" else echo "Release detected" # Publish to the specific version folder - echo "::group::Publishing Specific Version: ${VERSION}" + echo "::group::Publishing Specific Release Version: ${VERSION}" BASE_PUBLISH_PATH="./${VERSION}" if [ -n "${TARGET_SUBFOLDER}" ]; then PUBLISH_PATH="./${VERSION}/${TARGET_SUBFOLDER}" @@ -254,13 +266,13 @@ else PUBLISH_PATH="./${VERSION}" fi publish_artifacts - echo "Published documentation to ${PUBLISH_PATH}" + echo "Published release documentation to ${PUBLISH_PATH}" echo "::endgroup::" # Publish to the generic version folder genericVersionFolder="${VERSION%.*}" genericVersionFolder="${genericVersionFolder}.x" - echo "::group::Publishing Generic Version: ${genericVersionFolder}" + echo "::group::Publishing Generic Release Version: ${genericVersionFolder}" BASE_PUBLISH_PATH="./${genericVersionFolder}" if [ -n "${TARGET_SUBFOLDER}" ]; then PUBLISH_PATH="./${genericVersionFolder}/${TARGET_SUBFOLDER}" @@ -268,7 +280,7 @@ else PUBLISH_PATH="./${genericVersionFolder}" fi publish_artifacts - echo "Published documentation to ${genericVersionFolder}" + echo "Published release documentation to ${genericVersionFolder}" echo "::endgroup::" # Publish to the latest release folder if needed @@ -285,17 +297,23 @@ else echo "Published a copy of documentation to ${PUBLISH_PATH}" echo "::endgroup::" else + echo "::group::Skipped Latest Release Documentation - not highest" echo "Skipping documentation copy to '${LAST_RELEASE_FOLDER}' because ${genericVersionFolder} is NOT the highest." + echo "::endgroup::" fi - else + else + echo "::group::Skipped Latest Release Documentation" echo "Skipping documentation copy to ${LAST_RELEASE_FOLDER}" + echo "::endgroup::" fi fi +echo "::group::Committing Changes" echo "Detected the following delta for commit:" git status echo "Committing changes." -git commit -m "Deploying to gh-pages - $(date +"%T")" --quiet --allow-empty -git push "${GIT_REPO_URL}" gh-pages +git commit -m "Deploying to documentation branch - $(date +"%T")" --quiet --allow-empty +git push "${GIT_REPO_URL}" "${DOCUMENTATION_BRANCH}" echo "Deployment successful!" +echo "::endgroup::" diff --git a/licenses/LICENSE-MIT.txt b/licenses/LICENSE-MIT.txt new file mode 100644 index 0000000..28e850b --- /dev/null +++ b/licenses/LICENSE-MIT.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Fritz Mahnke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/post-release/README.md b/post-release/README.md index 5dc4050..25b6b45 100644 --- a/post-release/README.md +++ b/post-release/README.md @@ -14,19 +14,42 @@ See the License for the specific language governing permissions and limitations under the License. --> -# Grails post-release action +# Handles Repository Setup for the next Release Version -Performs some actions after doing a release +A GitHub Action that handles steps necessary to close out a GitHub Release process. This includes: + +1. Creating a branch of named like `merge-back-TAGNAME` that will: + * Include the tag changes to prevent orphaned changes. + * Include changing the version in `gradle.properties` or `version` to the next version. +2. Optionally closing the current milestone associated with the release. +3. Optionally running an additional script as part of the close process to transform files in the repository. + +Please note that the next version is derived from the provided `RELEASE_VERSION` using a script that assumes a [Semantic Version](https://semver.org/). ## Requirements 1. Github Actions must be allowed to create pull requests in the repository. You can configure this in the repository settings under "Actions" -> "General" -> "Workflow permissions". -2. The Github Workflow step must have the `pull-requests: write` permission. +2. Requires the permission `contents: write` to create a branch and commit changes to the repository. +3. Optionally requires the permission `pull-requests: write` to open the pull request to merge back changes from the tag. If this permission is not set, a Pull Request will not be created. +4. Optionally requires the permission `issues: write` if milestone closing is required. + +## Environment Variables +* (optional) `RELEASE_VERSION` - The version of the release being closed. If not set, it will be derived from the `GITHUB_REF`, which as part of a release will be the tag name. +* (optional) `RELEASE_TAG_PREFIX` - The prefix of the release tag. If not set, it will default to `v` (e.g., `v1.0.0`). +* (optional) `RELEASE_SCRIPT_PATH` - An optional path to a custom shell script that will be executed after the version replacement in `gradle.properties`, but prior to commiting the project changes. -## Example usage +## Example Usage +Basic Usage: ```yaml -uses: apache/grails-github-actions/post-release@asf -with: - token: ${{ secrets.GITHUB_TOKEN }} + - name: "⚙️ Run post-release" + uses: apache/grails-github-actions/post-release@asf ``` + +Running a custom script `myScript.sh` that's checked in under `.github/scripts`: +```yaml + - name: "⚙️ Run post-release" + uses: apache/grails-github-actions/post-release@asf + env: + RELEASE_SCRIPT_PATH: '.github/scripts/myScript.sh' +``` \ No newline at end of file diff --git a/post-release/increment_version.sh b/post-release/increment_version.sh index b829672..71783ba 100755 --- a/post-release/increment_version.sh +++ b/post-release/increment_version.sh @@ -1,26 +1,6 @@ #!/bin/bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - # Increment a version string using Semantic Versioning (SemVer) terminology. -# Source: https://github.com/fmahnke/shell-semver # Parse command line options. diff --git a/pre-release/README.md b/pre-release/README.md index 5c6cf13..e94fec1 100644 --- a/pre-release/README.md +++ b/pre-release/README.md @@ -14,14 +14,41 @@ See the License for the specific language governing permissions and limitations under the License. --> -# Grails pre-release action +# Handles Tag updates to ensure the Release Version is set -Performs some actions before doing a release +A GitHub action that handles any pre-release steps as part of a GitHub release process. This includes: -## Example usage +1. Updating the `projectVersion` or `version` in `gradle.properties` to match the tag version. +2. Optionally running an additional script as part of the `projectVersion` change to transform files in the repository. +3. Commiting changes from 1 & 2 above and force pushing an update to the tag. +4. Moving the release back from `draft` to `published` because of the tag update. +Please note that this action allows users to simply create a GitHub release and this action handles matching the version of the GitHub release to the checked in version. + +## Requirements + +1. Requires the permission `contents: write` to update the tag & release. + +## Environment Variables +* (required) `RELEASE_VERSION` - The version of the release being created. +* (optional) `RELEASE_TAG_PREFIX` - The prefix of the release tag. If not set, it will default to `v` (e.g., `v1.0.0`). +* (optional) `RELEASE_SCRIPT_PATH` - An optional path to a custom shell script that will be executed after the version replacement in `gradle.properties`, but prior to commiting the project changes. + +## Example Usage + +Basic Usage: ```yaml -uses: grails-github-actions/pre-release@asf -with: - token: ${{ secrets.GITHUB_TOKEN }} + - name: '⚙️ Run pre-release' + uses: apache/grails-github-actions/pre-release@asf + env: + RELEASE_VERSION: ${{ steps.release_version.outputs.value }} ``` + +Running a custom script `myScript.sh` that's checked in under `.github/scripts`: +```yaml + - name: '⚙️ Run pre-release' + uses: apache/grails-github-actions/pre-release@asf + env: + RELEASE_VERSION: ${{ steps.release_version.outputs.value }} + RELEASE_SCRIPT_PATH: '.github/scripts/myScript.sh' +``` \ No newline at end of file diff --git a/release-notes/increment_version.sh b/release-notes/increment_version.sh index f8d868a..6abe1fb 100755 --- a/release-notes/increment_version.sh +++ b/release-notes/increment_version.sh @@ -1,26 +1,6 @@ #!/bin/bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - # Increment a version string using Semantic Versioning (SemVer) terminology. -# Source: https://github.com/fmahnke/shell-semver # Parse command line options. diff --git a/tests/gradle/rat-root-config.gradle b/tests/gradle/rat-root-config.gradle index 279664e..2f5c16d 100644 --- a/tests/gradle/rat-root-config.gradle +++ b/tests/gradle/rat-root-config.gradle @@ -28,6 +28,7 @@ tasks.named('rat') { '**/build/**', // build directories 'buildSrc/build/**', // build directories '**/.gradle/**', '**/wrapper/**', 'gradlew*', // gradle wrapper files excluded from src zip + '**/increment_version.sh' // documented as MIT code in the license ] // logger.lifecycle("Excludes for RAT task: ${allExcludes.join(', \n')}") excludes = allExcludes diff --git a/tests/src/test/groovy/org/apache/grails/github/DeployGithubPagesSpec.groovy b/tests/src/test/groovy/org/apache/grails/github/DeployGithubPagesSpec.groovy new file mode 100644 index 0000000..ac239d8 --- /dev/null +++ b/tests/src/test/groovy/org/apache/grails/github/DeployGithubPagesSpec.groovy @@ -0,0 +1,447 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.grails.github + +import org.apache.grails.github.mocks.GitHubDockerAction +import org.apache.grails.github.mocks.GitHubRelease +import org.apache.grails.github.mocks.GitHubRepoMock +import org.apache.grails.github.mocks.cli.GitHubCliMock +import org.testcontainers.containers.Network +import spock.lang.Specification + +class DeployGithubPagesSpec extends Specification { + + private Map getProjectFiles() { + [ + 'gradle.properties': 'projectVersion=7.0.0-SNAPSHOT', + 'docs/index.html': 'Welcome to the Grails Documentation', + 'docs/ghpages.html': 'Welcome to the Grails GitHub Pages', + ] + } + private Map getDefaultEnvironment(GitHubDockerAction action, GitHubRepoMock gitRepo) { + def env = action.getDefaultEnvironment() + + env['GITHUB_USER_NAME'] = gitRepo.getUsername() + env['GH_TOKEN'] = gitRepo.getToken() + env['GIT_TRANSFER_PROTOCOL'] = 'http' + env['GITHUB_URL_BASE'] = gitRepo.getInternalUrlBase() + + env + } + + def "gh-pages branch is created if does not exist"() { + given: + Network net = Network.newNetwork() + + and: + GitHubRelease release = new GitHubRelease(version: '7.0.0-RC1', tagName: 'rel-7.0.0-RC1', targetBranch: '7.0.x', targetVersion: '7.0.0-SNAPSHOT') + GitHubDockerAction action = new GitHubDockerAction('deploy-github-pages', release, new GitHubCliMock()) + + GitHubRepoMock gitRepo = new GitHubRepoMock(action.workspacePath, net) + gitRepo.init() + gitRepo.populateRepository('7.0.0-SNAPSHOT', null, [], getProjectFiles()) + gitRepo.stageRepositoryForAction('main') + + and: + def env = getDefaultEnvironment(action, gitRepo) + env['GRADLE_PUBLISH_RELEASE'] = 'false' // snapshot + env['SOURCE_FOLDER'] = 'docs' + env['VERSION'] = '7.0.0-SNAPSHOT' + + and: + action.createContainer(env, net) + + when: + action.runAction() + + then: + action.actionExitCode == 0L + action.actionLogs + + and: 'gh-pages branch created' + action.getActionGroupLogs('Creating documentation branch').contains('Creating documentation branch gh-pages as it does not exist') + gitRepo.branchExists('gh-pages') + + and: 'files published to snapshot' + gitRepo.getFileContents('index.html', 'gh-pages') == 'Welcome to the Grails GitHub Pages' + gitRepo.getFileContents('snapshot/index.html', 'gh-pages') == 'Welcome to the Grails Documentation' + + and: 'main did not change' + gitRepo.getFileContents('gradle.properties', 'main') == 'projectVersion=7.0.0-SNAPSHOT' + + and: 'main did not add any folders' + gitRepo.getFolders( 'main') == ['docs'] + + and: 'gh-pages added expected folders' + gitRepo.getFolders('gh-pages') == ['snapshot'] + + cleanup: + System.out.println("Container logs:\n${action.actionLogs}" as String) + gitRepo?.close() + action.close() + } + + def "ghpages_html is set as root index_html"() { + given: + Network net = Network.newNetwork() + + and: + GitHubRelease release = new GitHubRelease(version: '7.0.0-RC1', tagName: 'rel-7.0.0-RC1', targetBranch: '7.0.x', targetVersion: '7.0.0-SNAPSHOT') + GitHubDockerAction action = new GitHubDockerAction('deploy-github-pages', release, new GitHubCliMock()) + + GitHubRepoMock gitRepo = new GitHubRepoMock(action.workspacePath, net) + gitRepo.init() + gitRepo.populateRepository('7.0.0-SNAPSHOT', null, [], getProjectFiles()) + gitRepo.createDivergedBranch([ + 'index.html': 'will be replaced', + 'snapshot/index.html': 'will also be replaced' + ], 'gh-pages') + gitRepo.stageRepositoryForAction('main') + + and: + def env = getDefaultEnvironment(action, gitRepo) + env['GRADLE_PUBLISH_RELEASE'] = 'false' // snapshot + env['SOURCE_FOLDER'] = 'docs' + env['VERSION'] = '7.0.0-SNAPSHOT' + + and: + action.createContainer(env, net) + + when: + action.runAction() + + then: + action.actionExitCode == 0L + action.actionLogs + + and: 'gh-pages branch created' + !action.isLogGroupPresent('Creating documentation branch') + action.getActionGroupLogs('Checkout documentation branch').contains('documentation branch found, cloning') + + and: 'ghpages copied' + action.getActionGroupLogs('Staging root index.html') + + and: 'files published to snapshot' + gitRepo.getFileContents('index.html', 'gh-pages') == 'Welcome to the Grails GitHub Pages' + gitRepo.getFileContents('snapshot/index.html', 'gh-pages') == 'Welcome to the Grails Documentation' + + and: 'main did not change' + gitRepo.getFileContents('gradle.properties', 'main') == 'projectVersion=7.0.0-SNAPSHOT' + + and: 'main did not add any folders' + gitRepo.getFolders( 'main') == ['docs'] + + and: 'gh-pages replaced expected folders' + gitRepo.getFolders('gh-pages') == ['snapshot'] + + cleanup: + System.out.println("Container logs:\n${action.actionLogs}" as String) + gitRepo?.close() + action.close() + } + + def "snapshot - snapshot publishing disabled"() { + given: + Network net = Network.newNetwork() + + and: + GitHubRelease release = new GitHubRelease(version: '7.0.0-RC1', tagName: 'rel-7.0.0-RC1', targetBranch: '7.0.x', targetVersion: '7.0.0-SNAPSHOT') + GitHubDockerAction action = new GitHubDockerAction('deploy-github-pages', release, new GitHubCliMock()) + + GitHubRepoMock gitRepo = new GitHubRepoMock(action.workspacePath, net) + gitRepo.init() + gitRepo.populateRepository('7.0.0-SNAPSHOT', null, [], getProjectFiles()) + gitRepo.stageRepositoryForAction('main') + + and: + def env = getDefaultEnvironment(action, gitRepo) + env['GRADLE_PUBLISH_RELEASE'] = 'false' // snapshot + env['SKIP_SNAPSHOT_FOLDER'] = 'true' + env['SOURCE_FOLDER'] = 'docs' + env['VERSION'] = '7.0.0-SNAPSHOT' + + and: + action.createContainer(env, net) + + when: + action.runAction() + + then: + action.actionExitCode == 0L + action.actionLogs + + and: + action.actionLogs.contains('Snapshot detected and snapshot publishing is disabled. Skipping documentation deployment.') + + and: + !gitRepo.branchExists('gh-pages') + + and: 'main did not change' + gitRepo.getFileContents('gradle.properties', 'main') == 'projectVersion=7.0.0-SNAPSHOT' + + and: 'main did not add any folders' + gitRepo.getFolders( 'main') == ['docs'] + + cleanup: + System.out.println("Container logs:\n${action.actionLogs}" as String) + gitRepo?.close() + action.close() + } + + def "snapshot - published with subfolder"() { + given: + Network net = Network.newNetwork() + + and: + GitHubRelease release = new GitHubRelease(version: '7.0.0-RC1', tagName: 'rel-7.0.0-RC1', targetBranch: '7.0.x', targetVersion: '7.0.0-SNAPSHOT') + GitHubDockerAction action = new GitHubDockerAction('deploy-github-pages', release, new GitHubCliMock()) + + GitHubRepoMock gitRepo = new GitHubRepoMock(action.workspacePath, net) + gitRepo.init() + gitRepo.populateRepository('7.0.0-SNAPSHOT', null, [], getProjectFiles()) + gitRepo.stageRepositoryForAction('main') + + and: + def env = getDefaultEnvironment(action, gitRepo) + env['GRADLE_PUBLISH_RELEASE'] = 'false' // snapshot + env['SOURCE_FOLDER'] = 'docs' + env['TARGET_SUBFOLDER'] = 'nested' + env['VERSION'] = '7.0.0-SNAPSHOT' + + and: + action.createContainer(env, net) + + when: + action.runAction() + + then: + action.actionExitCode == 0L + action.actionLogs + + and: 'gh-pages branch created' + action.getActionGroupLogs('Creating documentation branch') + gitRepo.branchExists('gh-pages') + + and: 'files published to snapshot' + gitRepo.getFileContents('index.html', 'gh-pages') == 'Welcome to the Grails GitHub Pages' + gitRepo.getFolders('snapshot', 'gh-pages') == ['nested'] + gitRepo.getFileContents('snapshot/nested/index.html', 'gh-pages') == 'Welcome to the Grails Documentation' + + and: 'main did not change' + gitRepo.getFileContents('gradle.properties', 'main') == 'projectVersion=7.0.0-SNAPSHOT' + + and: 'main did not add any folders' + gitRepo.getFolders( 'main') == ['docs'] + + and: 'gh-pages added expected folders' + gitRepo.getFolders('gh-pages') == ['snapshot'] + + cleanup: + System.out.println("Container logs:\n${action.actionLogs}" as String) + gitRepo?.close() + action.close() + } + + def "release - published without subfolder"() { + given: + Network net = Network.newNetwork() + + and: + GitHubRelease release = new GitHubRelease(version: '7.0.0-RC1', tagName: 'rel-7.0.0-RC1', targetBranch: '7.0.x', targetVersion: '7.0.0-SNAPSHOT') + GitHubDockerAction action = new GitHubDockerAction('deploy-github-pages', release, new GitHubCliMock()) + + GitHubRepoMock gitRepo = new GitHubRepoMock(action.workspacePath, net) + gitRepo.init() + gitRepo.populateRepository('7.0.0-SNAPSHOT', null, [], getProjectFiles()) + gitRepo.stageRepositoryForAction('main') + + and: + def env = getDefaultEnvironment(action, gitRepo) + env['GRADLE_PUBLISH_RELEASE'] = 'true' + env['SKIP_SNAPSHOT_FOLDER'] = 'true' // should be ignored because this is a release + env['SOURCE_FOLDER'] = 'docs' + env['VERSION'] = '7.0.0-RC1' + + and: + action.createContainer(env, net) + + when: + action.runAction() + + then: + action.actionExitCode == 0L + action.actionLogs + + and: + !action.actionLogs.contains('Snapshot detected and snapshot publishing is disabled. Skipping documentation deployment.') + + and: + action.getActionGroupLogs('Publishing Specific Release Version: 7.0.0-RC1') + action.getActionGroupLogs('Publishing Generic Release Version: 7.0.x') + action.getActionGroupLogs('Overwriting latest with the latest release documentation') + + and: + gitRepo.branchExists('gh-pages') + + and: + gitRepo.getFolders('gh-pages') == ['7.0.0-RC1', 'latest', '7.0.x'] + gitRepo.getFileContents('index.html', 'gh-pages') == 'Welcome to the Grails GitHub Pages' + + and: + gitRepo.getFileContents('latest/index.html', 'gh-pages') == 'Welcome to the Grails Documentation' + gitRepo.getFileContents('7.0.x/index.html', 'gh-pages') == 'Welcome to the Grails Documentation' + gitRepo.getFileContents('7.0.0-RC1/index.html', 'gh-pages') == 'Welcome to the Grails Documentation' + + and: 'main did not change' + gitRepo.getFileContents('gradle.properties', 'main') == 'projectVersion=7.0.0-SNAPSHOT' + + and: 'main did not add any folders' + gitRepo.getFolders( 'main') == ['docs'] + + cleanup: + System.out.println("Container logs:\n${action.actionLogs}" as String) + gitRepo?.close() + action.close() + } + + def "release - published with subfolder"() { + given: + Network net = Network.newNetwork() + + and: + GitHubRelease release = new GitHubRelease(version: '7.0.0-RC1', tagName: 'rel-7.0.0-RC1', targetBranch: '7.0.x', targetVersion: '7.0.0-SNAPSHOT') + GitHubDockerAction action = new GitHubDockerAction('deploy-github-pages', release, new GitHubCliMock()) + + GitHubRepoMock gitRepo = new GitHubRepoMock(action.workspacePath, net) + gitRepo.init() + gitRepo.populateRepository('7.0.0-SNAPSHOT', null, [], getProjectFiles()) + gitRepo.stageRepositoryForAction('main') + + and: + def env = getDefaultEnvironment(action, gitRepo) + env['GRADLE_PUBLISH_RELEASE'] = 'true' + env['SKIP_SNAPSHOT_FOLDER'] = 'true' // should be ignored because this is a release + env['SOURCE_FOLDER'] = 'docs' + env['VERSION'] = '7.0.0-RC1' + env['TARGET_SUBFOLDER'] = 'nested' + + and: + action.createContainer(env, net) + + when: + action.runAction() + + then: + action.actionExitCode == 0L + action.actionLogs + + and: + !action.actionLogs.contains('Snapshot detected and snapshot publishing is disabled. Skipping documentation deployment.') + + and: + action.getActionGroupLogs('Publishing Specific Release Version: 7.0.0-RC1') + action.getActionGroupLogs('Publishing Generic Release Version: 7.0.x') + action.getActionGroupLogs('Overwriting latest with the latest release documentation') + + and: + gitRepo.branchExists('gh-pages') + + and: + gitRepo.getFolders('gh-pages') == ['7.0.0-RC1', 'latest', '7.0.x'] + gitRepo.getFileContents('index.html', 'gh-pages') == 'Welcome to the Grails GitHub Pages' + + and: + gitRepo.getFileContents('latest/nested/index.html', 'gh-pages') == 'Welcome to the Grails Documentation' + gitRepo.getFileContents('7.0.x/nested/index.html', 'gh-pages') == 'Welcome to the Grails Documentation' + gitRepo.getFileContents('7.0.0-RC1/nested/index.html', 'gh-pages') == 'Welcome to the Grails Documentation' + + and: 'main did not change' + gitRepo.getFileContents('gradle.properties', 'main') == 'projectVersion=7.0.0-SNAPSHOT' + + and: 'main did not add any folders' + gitRepo.getFolders( 'main') == ['docs'] + + cleanup: + System.out.println("Container logs:\n${action.actionLogs}" as String) + gitRepo?.close() + action.close() + } + + def "release - skip publishing to latest"() { + given: + Network net = Network.newNetwork() + + and: + GitHubRelease release = new GitHubRelease(version: '7.0.0-RC1', tagName: 'rel-7.0.0-RC1', targetBranch: '7.0.x', targetVersion: '7.0.0-SNAPSHOT') + GitHubDockerAction action = new GitHubDockerAction('deploy-github-pages', release, new GitHubCliMock()) + + GitHubRepoMock gitRepo = new GitHubRepoMock(action.workspacePath, net) + gitRepo.init() + gitRepo.populateRepository('7.0.0-SNAPSHOT', null, [], getProjectFiles()) + gitRepo.stageRepositoryForAction('main') + + and: + def env = getDefaultEnvironment(action, gitRepo) + env['GRADLE_PUBLISH_RELEASE'] = 'true' + env['SKIP_SNAPSHOT_FOLDER'] = 'true' // should be ignored because this is a release + env['SKIP_RELEASE_FOLDER'] = 'true' + env['SOURCE_FOLDER'] = 'docs' + env['VERSION'] = '7.0.0-RC1' + + and: + action.createContainer(env, net) + + when: + action.runAction() + + then: + action.actionExitCode == 0L + action.actionLogs + + and: + !action.actionLogs.contains('Snapshot detected and snapshot publishing is disabled. Skipping documentation deployment.') + + and: + action.getActionGroupLogs('Publishing Specific Release Version: 7.0.0-RC1') + action.getActionGroupLogs('Publishing Generic Release Version: 7.0.x') + !action.isLogGroupPresent('Overwriting latest with the latest release documentation') + + and: + gitRepo.branchExists('gh-pages') + + and: + gitRepo.getFolders('gh-pages') == ['7.0.0-RC1', '7.0.x'] + gitRepo.getFileContents('index.html', 'gh-pages') == 'Welcome to the Grails GitHub Pages' + + and: + gitRepo.getFileContents('7.0.x/index.html', 'gh-pages') == 'Welcome to the Grails Documentation' + gitRepo.getFileContents('7.0.0-RC1/index.html', 'gh-pages') == 'Welcome to the Grails Documentation' + + and: 'main did not change' + gitRepo.getFileContents('gradle.properties', 'main') == 'projectVersion=7.0.0-SNAPSHOT' + + and: 'main did not add any folders' + gitRepo.getFolders( 'main') == ['docs'] + + cleanup: + System.out.println("Container logs:\n${action.actionLogs}" as String) + gitRepo?.close() + action.close() + } +} diff --git a/tests/src/test/groovy/org/apache/grails/github/mocks/GitHubDockerAction.groovy b/tests/src/test/groovy/org/apache/grails/github/mocks/GitHubDockerAction.groovy index 6bfd555..7e91678 100644 --- a/tests/src/test/groovy/org/apache/grails/github/mocks/GitHubDockerAction.groovy +++ b/tests/src/test/groovy/org/apache/grails/github/mocks/GitHubDockerAction.groovy @@ -93,6 +93,7 @@ class GitHubDockerAction implements Closeable { env['GITHUB_OUTPUT'] = '/github/github-output.txt' env['PATH'] = '/github/path:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' env['GITHUB_API_URL'] = apiMock.urlForContainer + env['GITHUB_URL_BASE'] = 'github.com' env } @@ -131,6 +132,14 @@ class GitHubDockerAction implements Closeable { containerLogBuffer.toUtf8String() } + boolean isLogGroupPresent(String groupName) { + String allLogs = actionLogs + + def markingString = "::group::$groupName" as String + int idx = allLogs.indexOf(markingString) + return idx >= 0 + } + @Memoized String getActionGroupLogs(String groupName) { String allLogs = actionLogs diff --git a/tests/src/test/groovy/org/apache/grails/github/mocks/GitHubRepoMock.groovy b/tests/src/test/groovy/org/apache/grails/github/mocks/GitHubRepoMock.groovy index fedf616..b466ac1 100644 --- a/tests/src/test/groovy/org/apache/grails/github/mocks/GitHubRepoMock.groovy +++ b/tests/src/test/groovy/org/apache/grails/github/mocks/GitHubRepoMock.groovy @@ -20,6 +20,16 @@ package org.apache.grails.github.mocks import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.PushCommand +import org.eclipse.jgit.dircache.DirCache +import org.eclipse.jgit.dircache.DirCacheBuilder +import org.eclipse.jgit.dircache.DirCacheEntry +import org.eclipse.jgit.lib.CommitBuilder +import org.eclipse.jgit.lib.Constants +import org.eclipse.jgit.lib.FileMode +import org.eclipse.jgit.lib.ObjectId +import org.eclipse.jgit.lib.ObjectInserter +import org.eclipse.jgit.lib.PersonIdent +import org.eclipse.jgit.lib.RefUpdate import org.eclipse.jgit.lib.Repository import org.eclipse.jgit.storage.file.FileRepositoryBuilder import org.eclipse.jgit.transport.RefSpec @@ -65,7 +75,7 @@ class GitHubRepoMock implements Closeable { Container.ExecResult create = gitHost.execInContainer( 'su-exec', 'git', 'gitea', 'admin', 'user', 'create', - '--admin', '--username', 'ci', '--password', 'pass', + '--admin', '--username', username, '--password', token, '--email', 'ci@example.com', '--must-change-password=false' ) if (create.getExitCode() != 0) { @@ -74,7 +84,7 @@ class GitHubRepoMock implements Closeable { String base = "http://${gitHost.getHost()}:${gitHost.getMappedPort(3000)}" as String HttpClient http = HttpClient.newHttpClient() - String basic = "Basic ${Base64.getEncoder().encodeToString('ci:pass'.getBytes(StandardCharsets.UTF_8))}" as String + String basic = "Basic ${Base64.getEncoder().encodeToString("$username:$token".getBytes(StandardCharsets.UTF_8))}" as String HttpRequest createRepo = HttpRequest.newBuilder( URI.create("$base/api/v1/user/repos" as String)) @@ -86,20 +96,103 @@ class GitHubRepoMock implements Closeable { response.statusCode() == 201 } + String getUsername() { + 'acme' + } + + String getToken() { + 'pass' + } + + String getInternalUrlBase() { + 'gitea:3000' + } + String getExternalGitHostUrl() { - "http://ci:pass@${gitHost.getHost()}:${gitHost.getMappedPort(3000)}/ci/widgets.git" + "http://$username:$token@${gitHost.getHost()}:${gitHost.getMappedPort(3000)}/$username/widgets.git" + } + + Map getProjectVersionFiles(String storedProjectVersion) { + [ + 'README.md' : '# demo\n', + 'gradle.properties': "projectVersion=$storedProjectVersion\n" + ] } - void populateRepository(String storedProjectVersion, String tag, List additionalBranchNames = ['7.0.x']) { + void createDivergedBranch(Map files, String divergedBranch, String defaultBranch = 'main') { Path temp = Files.createTempDirectory('ref-project-version') + try { + cloneRepo(temp, defaultBranch) { Git git -> + Repository repo = git.getRepository() + + try (ObjectInserter oi = repo.newObjectInserter()) { + DirCache inCore = DirCache.newInCore() + DirCacheBuilder builder = inCore.builder() + + def entries = files.collect { k, v -> + String gitPath = k.replaceAll('^\\./+', '').replace('\\', '/').replaceAll('/+', '/') + [path: gitPath, bytes: v.getBytes(StandardCharsets.UTF_8)] + }.sort { a, b -> a.path <=> b.path } + + entries.each { e -> + ObjectId blobId = oi.insert(Constants.OBJ_BLOB, e.bytes) + DirCacheEntry dce = new DirCacheEntry(e.path) + dce.setFileMode(FileMode.REGULAR_FILE) + dce.setObjectId(blobId) + builder.add(dce) + } + builder.finish() + + ObjectId treeId = inCore.writeTree(oi) + + PersonIdent who = new PersonIdent('diverged', 'diverged@example.com') + CommitBuilder cb = new CommitBuilder() + cb.setTreeId(treeId) + cb.setAuthor(who) + cb.setCommitter(who) + cb.setMessage('initial diverged commit') + ObjectId commitId = oi.insert(cb) + oi.flush() + + String refName = Constants.R_HEADS + divergedBranch + RefUpdate ru = repo.updateRef(refName) + ru.setExpectedOldObjectId(ObjectId.zeroId()) // fail if it already exists + ru.setNewObjectId(commitId) + ru.setRefLogMessage("create orphan branch $divergedBranch", false) + RefUpdate.Result result = ru.update() + assert result in [RefUpdate.Result.NEW, RefUpdate.Result.FAST_FORWARD, RefUpdate.Result.FORCED]: + "Ref update failed: $result" + + repo.updateRef(Constants.HEAD).link(refName) + + RefSpec spec = new RefSpec("${refName}:${refName}") + + def push = git.push().setRemote('origin').setRefSpecs(spec) + assert push.call() + } + } + } + finally { + temp.toFile().deleteDir() + } + } + + void populateRepository(String storedProjectVersion, String tag, List additionalBranchNames = ['7.0.x'], Map files = null) { + if (!files) { + files = getProjectVersionFiles(storedProjectVersion) + } + + Path temp = Files.createTempDirectory('populate-repo') try { try (Repository repo = FileRepositoryBuilder.create(temp.resolve('.git').toFile())) { repo.create() try (Git git = Git.open(temp.toFile())) { - // Example project - Files.writeString(temp.resolve('README.md'), '# demo\n') - Files.writeString(temp.resolve('gradle.properties'), "projectVersion=${storedProjectVersion}\n") + for (Map.Entry entry : files.entrySet()) { + def fileToCreate = temp.resolve(entry.key) + fileToCreate.toFile().parentFile.mkdirs() + Files.writeString(fileToCreate, entry.value) + } assert git.add().addFilepattern('.').call() assert git.commit() .setAuthor('CI', 'ci@example.com') @@ -113,18 +206,20 @@ class GitHubRepoMock implements Closeable { assert git.branchCreate().setName(additionalBranch).setStartPoint('main').call() } - def tagRef = git.tag().setName(tag) - .setMessage('My Release Message') - .call() - assert tagRef + if (tag) { + def tagRef = git.tag().setName(tag) + .setMessage('My Release Message') + .call() + assert tagRef + } assert git.remoteAdd().setName('origin').setUri(new URIish(externalGitHostUrl)).call() List specs = ['main', additionalBranchNames].flatten().unique().collect { String branch -> - new RefSpec("refs/heads/${branch}:refs/heads/${branch}" as String) + new RefSpec("${Constants.R_HEADS + branch}:${Constants.R_HEADS + branch}" as String) } - PushCommand push = git.push() + def push = git.push() .setRemote('origin') .setRefSpecs(specs) .setPushAll() @@ -143,35 +238,41 @@ class GitHubRepoMock implements Closeable { } } + String getInternalGitHostUrl() { + "http://$username:$token@$internalUrlBase/$username/widgets.git" + } + void stageRepositoryForAction(String refName) { cloneRepo(actionRepoPath, refName) { Git git -> - String internalGitHostUrl = 'http://ci:pass@gitea:3000/ci/widgets.git' assert git.remoteSetUrl().setRemoteName('origin').setRemoteUri(new URIish(internalGitHostUrl)).call() } } - void setProjectVersion(String refName, String newProjectVersion) { - Path temp = Files.createTempDirectory('ref-project-version') + void storeFiles(Map files, String refName) { + Path temp = Files.createTempDirectory('store-files') try { cloneRepo(temp, refName) { Git git -> - temp.resolve('gradle.properties').toFile().text = "projectVersion=${newProjectVersion}\n" - - git.add().addFilepattern('gradle.properties').call() + for (Map.Entry fileEntry : files.entrySet()) { + def fileToCreate = temp.resolve(fileEntry.key) + fileToCreate.toFile().parentFile.mkdirs() + fileToCreate.toFile().text = fileEntry.value + } + git.add().addFilepattern('.').call() assert git.add().addFilepattern('.').call() assert git.commit() .setAuthor('CI', 'ci@example.com') .setCommitter('CI', 'ci@example.com') .setSign(false) - .setMessage("set project version = $newProjectVersion").call() + .setMessage("store files :${files.keySet().join(',')}").call() - def tagRef = git.repository.findRef("refs/tags/$refName") + def tagRef = git.repository.findRef("${Constants.R_TAGS + refName}") if (tagRef != null) { // It's a tag: delete and recreate tag at new commit git.tagDelete().setTags(refName).call() git.tag().setName(refName).setMessage("Updated tag $refName").call() def pushResult = git.push() .setRemote('origin') - .setRefSpecs(new RefSpec("refs/tags/$refName:refs/tags/$refName")) + .setRefSpecs(new RefSpec("${Constants.R_TAGS + refName}:${Constants.R_TAGS + refName}")) .setPushTags() .setForce(true) .call() @@ -179,7 +280,7 @@ class GitHubRepoMock implements Closeable { } else { PushCommand push = git.push() .setRemote('origin') - .setRefSpecs(new RefSpec("refs/heads/$refName:refs/heads/$refName" as String)) + .setRefSpecs(new RefSpec("${Constants.R_HEADS + refName}:${Constants.R_HEADS + refName}" as String)) .setPushAll() .setPushTags() def pushResult = push.call() @@ -192,13 +293,17 @@ class GitHubRepoMock implements Closeable { } } + void setProjectVersion(String refName, String newProjectVersion) { + storeFiles(['gradle.properties': "projectVersion=${newProjectVersion}\n"], refName) + } + boolean branchExists(String branchName) { Path temp = Files.createTempDirectory('ref-project-version') try { cloneRepo(temp, branchName) return true } - catch(e) { + catch (e) { return false } finally { @@ -207,21 +312,21 @@ class GitHubRepoMock implements Closeable { } private void cloneRepo(Path targetDirectory, String checkoutRef, Closure c = null) { - try(Git git = Git.cloneRepository().setURI(externalGitHostUrl).setDirectory(targetDirectory.toFile()).setCloneAllBranches(true).call()) { + try (Git git = Git.cloneRepository().setURI(externalGitHostUrl).setDirectory(targetDirectory.toFile()).setCloneAllBranches(true).call()) { git.fetch().setTagOpt(TagOpt.FETCH_TAGS).call() - if(checkoutRef != 'main') { + if (checkoutRef != 'main') { def repo = git.getRepository() def ref = repo.findRef(checkoutRef) if (ref == null) { // Try as remote branch - ref = repo.findRef("refs/remotes/origin/$checkoutRef") + ref = repo.findRef("${Constants.R_REMOTES + 'origin/' + checkoutRef}") } if (ref == null) { throw new IllegalArgumentException("Ref 'checkoutRef' does not exist in remote repository") } - if (ref.getName().startsWith('refs/tags/')) { + if (ref.getName().startsWith(Constants.R_TAGS)) { // Checkout tag in detached HEAD git.checkout().setName(checkoutRef).call() } else { @@ -237,32 +342,58 @@ class GitHubRepoMock implements Closeable { } } - String getRefProjectVersion(String refName) { - Path temp = Files.createTempDirectory('ref-project-version') + List getFolders(String basePath = null, String refName) { + Path temp = Files.createTempDirectory('list-folders') try { - String matched = null + List folders = [] cloneRepo(temp, refName) { Git git -> - Path gradleProperties = temp.resolve('gradle.properties') - if (!Files.exists(gradleProperties)) { + Path expectedBasePath = basePath ? temp.resolve(basePath) : temp + if (!Files.exists(expectedBasePath)) { throw new IllegalStateException( - "Could not resolve 'gradle.properties' at $refName. Ref or file may not exist (or ref wasn’t fetched)."); + "Could not resolve '$basePath' at $refName. Ref or path may not exist (or ref wasn’t fetched)."); } - String contents = gradleProperties.toFile().text - def matcher = (contents =~ /projectVersion=(.+)/) - if (matcher) { - matched = matcher[0][1] as String - } else { - throw new IllegalStateException("Could not find projectVersion in 'gradle.properties' for ref $refName" as String) + folders = expectedBasePath.toFile().listFiles({ File pathname -> + pathname.isDirectory() && pathname.name != '.git' + } as FileFilter).collect { it.name } + } + return folders + } + finally { + temp.toFile().deleteDir() + } + } + + String getFileContents(String filename, String refName) { + Path temp = Files.createTempDirectory('file-contents') + try { + String contents = null + cloneRepo(temp, refName) { Git git -> + Path fileToReturn = temp.resolve(filename) + if (!Files.exists(fileToReturn)) { + throw new IllegalStateException( + "Could not resolve '$filename' at $refName. Ref or file may not exist (or ref wasn’t fetched)."); } + + contents = fileToReturn.toFile().text } - return matched + return contents } finally { temp.toFile().deleteDir() } } + String getRefProjectVersion(String refName) { + String contents = getFileContents('gradle.properties', refName) + def matcher = (contents =~ /projectVersion=(.+)/) + if (matcher) { + return matcher[0][1] as String + } else { + throw new IllegalStateException("Could not find projectVersion in 'gradle.properties' for ref $refName" as String) + } + } + void close() { gitHost?.stop() }