diff --git a/.github/workflows/CleanupArtifacts.yml b/.github/workflows/CleanupArtifacts.yml index 59f62b82..988a781e 100644 --- a/.github/workflows/CleanupArtifacts.yml +++ b/.github/workflows/CleanupArtifacts.yml @@ -161,7 +161,7 @@ jobs: continue-on-error: true with: name: | - ${{ join(fromJSON(steps.compute.outputs.artifacts).*, ' + ${{ join(fromJSON(steps.compute2.outputs.artifacts).*, ' ') }} - name: 🗑️ Delete other artifacts diff --git a/.github/workflows/PrepareJob.yml b/.github/workflows/PrepareJob.yml index 573d5209..6cd1fd07 100644 --- a/.github/workflows/PrepareJob.yml +++ b/.github/workflows/PrepareJob.yml @@ -397,12 +397,15 @@ jobs: git_submodule_paths=${git_submodule_paths} EOF -# TODO: why not is_release_commit? # TODO: how to support version branches and hotfix releases on version branches? + # This step searches a merged Pull Request whose base is the release branch, so it must only run for merge + # commits that actually landed there. A merge commit on the development branch originates from a Pull Request + # based on the development branch and could never be found. 'is_release_commit' is exactly 'is_merge_commit' + # restricted to the main and version branches, and it's the same flag 'TriggerTaggedRelease' keys off. - name: 🔁 Find merged PullRequest from second parent of current SHA (${{ github.sha }}) id: FindPullRequest - if: steps.Classify.outputs.is_merge_commit == 'true' + if: steps.Classify.outputs.is_release_commit == 'true' run: | set +e @@ -426,27 +429,54 @@ jobs: fi printf "Search Pull Request to '%s' and branch containing SHA %s ... " "${{ inputs.release_branch }}" "${FATHER_SHA}" - PULL_REQUESTS=$(gh pr list --base "${{ inputs.release_branch }}" --search "${FATHER_SHA}" --state "merged" --json "title,number,mergedBy,mergedAt") - if [[ $? -ne 0 || "${PULL_REQUESTS}" == "" ]]; then + PULL_REQUESTS=$(gh pr list --base "${{ inputs.release_branch }}" --search "${FATHER_SHA}" --state "merged" --json "title,number,headRefOid,mergedBy,mergedAt") + if [[ $? -ne 0 ]]; then printf "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}\n" - printf "${ANSI_LIGHT_RED}Couldn't find a merged Pull Request to '%s'. -> %s${ANSI_NOCOLOR}\n" "${{ inputs.release_branch }}" "${PULL_REQUESTS}" - printf "::error title=PullRequest::Couldn't find a merged Pull Request to '%s'. -> %s\n" "${{ inputs.release_branch }}" "${PULL_REQUESTS}" + printf "${ANSI_LIGHT_RED}Couldn't search Pull Requests to '%s'. -> %s${ANSI_NOCOLOR}\n" "${{ inputs.release_branch }}" "${PULL_REQUESTS}" + printf "::error title=PullRequest::Couldn't search Pull Requests to '%s'. -> %s\n" "${{ inputs.release_branch }}" "${PULL_REQUESTS}" exit 1 - else - printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n" + fi + printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n" - PR_TITLE="$( printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].title")" - PR_NUMBER="$( printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].number")" - PR_MERGED_BY="$(printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].mergedBy.login")" - PR_MERGED_AT="$(printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].mergedAt")" + # The search lists every Pull Request *containing* that commit, so it can report more than one - for example + # an older release Pull Request built from an ancestor. The second parent (father) of a merge commit is the + # head commit of the merged branch, so a Pull Request whose 'headRefOid' equals it is the one that created + # this merge commit. Newest first, in case a Pull Request was closed and redone from the same branch. + MATCHES=$(printf "%s\n" "${PULL_REQUESTS}" | jq --arg sha "${FATHER_SHA}" '[.[] | select(.headRefOid == $sha)] | sort_by(.mergedAt) | reverse') + MATCH_COUNT=$(printf "%s\n" "${MATCHES}" | jq 'length') - printf "${ANSI_LIGHT_BLUE}Found Pull Request:${ANSI_NOCOLOR}\n" - printf " %s\n" "Title: ${PR_TITLE}" - printf " %s\n" "Number: ${PR_NUMBER}" - printf " %s\n" "MergedBy: ${PR_MERGED_BY}" - printf " %s\n" "MergedAt: ${PR_MERGED_AT} ($(date -d"${PR_MERGED_AT}" '+%d.%m.%Y - %H:%M:%S'))" + printf "Select Pull Request with head commit %s ... " "${FATHER_SHA}" + if [[ "${MATCH_COUNT}" -eq 0 ]]; then + printf "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}\n" + printf "${ANSI_LIGHT_RED}No merged Pull Request to '%s' has head commit '%s'.${ANSI_NOCOLOR}\n" "${{ inputs.release_branch }}" "${FATHER_SHA}" + if [[ "$(printf "%s\n" "${PULL_REQUESTS}" | jq 'length')" -eq 0 ]]; then + printf "${ANSI_LIGHT_RED}The search returned no candidates at all.${ANSI_NOCOLOR}\n" + else + printf "${ANSI_LIGHT_RED}Candidates returned by the search: %s${ANSI_NOCOLOR}\n" "$(printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output '[.[] | "#\(.number) (\(.headRefOid[0:7]))"] | join(", ")')" + fi + printf "::error title=PullRequest::No merged Pull Request to '%s' has head commit '%s'.\n" "${{ inputs.release_branch }}" "${FATHER_SHA}" + exit 1 + fi + printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n" + + if [[ "${MATCH_COUNT}" -gt 1 ]]; then + # Rare, but possible: a Pull Request was closed and recreated from the same branch, so both share a head + # commit. The most recently merged one describes this release. + printf "${ANSI_LIGHT_YELLOW}%s Pull Requests share head commit '%s': %s${ANSI_NOCOLOR}\n" "${MATCH_COUNT}" "${FATHER_SHA}" "$(printf "%s\n" "${MATCHES}" | jq --raw-output '[.[] | "#\(.number)"] | join(", ")')" + printf "${ANSI_LIGHT_YELLOW}Using the most recently merged one.${ANSI_NOCOLOR}\n" fi + PR_TITLE="$( printf "%s\n" "${MATCHES}" | jq --raw-output ".[0].title")" + PR_NUMBER="$( printf "%s\n" "${MATCHES}" | jq --raw-output ".[0].number")" + PR_MERGED_BY="$(printf "%s\n" "${MATCHES}" | jq --raw-output ".[0].mergedBy.login")" + PR_MERGED_AT="$(printf "%s\n" "${MATCHES}" | jq --raw-output ".[0].mergedAt")" + + printf "${ANSI_LIGHT_BLUE}Found Pull Request:${ANSI_NOCOLOR}\n" + printf " %s\n" "Title: ${PR_TITLE}" + printf " %s\n" "Number: ${PR_NUMBER}" + printf " %s\n" "MergedBy: ${PR_MERGED_BY}" + printf " %s\n" "MergedAt: ${PR_MERGED_AT} ($(date -d"${PR_MERGED_AT}" '+%d.%m.%Y - %H:%M:%S'))" + RELEASE_TAG_PATTERN='^${{ inputs.release_tag_pattern }}$' printf "Check Pull Request title against regexp '%s' ... " "${RELEASE_TAG_PATTERN}" if [[ "${PR_TITLE}" =~ $RELEASE_TAG_PATTERN ]]; then diff --git a/doc/JobTemplate/Setup/PrepareJob.rst b/doc/JobTemplate/Setup/PrepareJob.rst index eca51072..2368dc6a 100644 --- a/doc/JobTemplate/Setup/PrepareJob.rst +++ b/doc/JobTemplate/Setup/PrepareJob.rst @@ -48,6 +48,15 @@ The job template generates various output parameters derived from 3. Compute output parameters. 4. Find associated pull-request. + Runs for :ref:`release commits ` only - a merge commit on the + main-branch or a version-branch. A merge commit on the development-branch originates from a pull-request based on + the development-branch, which is not a release. + + Merged pull-requests are searched by the merge commit's second parent, then reduced to those whose ``headRefOid`` + **equals** it, because the second parent of a merge commit is the head commit of the merged branch. The search + alone would also list pull-requests that merely *contain* that commit. If a pull-request was closed and recreated + from the same branch, both share a head commit and the most recently merged one is used. + .. topic:: Job Execution .. image:: ../../_static/pyTooling-Actions-PrepareJob.png diff --git a/myFramework/Extension/__init__.py b/myFramework/Extension/__init__.py index 954bf398..59c81e9d 100644 --- a/myFramework/Extension/__init__.py +++ b/myFramework/Extension/__init__.py @@ -36,7 +36,7 @@ __email__ = "Paebbels@gmail.com" __copyright__ = "2017-2026, Patrick Lehmann" __license__ = "Apache License, Version 2.0" -__version__ = "7.14.0" +__version__ = "7.14.1" __keywords__ = ["GitHub Actions"] __project_url__ = "https://github.com/pyTooling/Actions" __documentation_url__ = "https://pyTooling.github.io/Actions" diff --git a/myPackage/__init__.py b/myPackage/__init__.py index 3fa36ada..23f81c90 100644 --- a/myPackage/__init__.py +++ b/myPackage/__init__.py @@ -36,7 +36,7 @@ __email__ = "Paebbels@gmail.com" __copyright__ = "2017-2026, Patrick Lehmann" __license__ = "Apache License, Version 2.0" -__version__ = "7.14.0" +__version__ = "7.14.1" __keywords__ = ["GitHub Actions"] __project_url__ = "https://github.com/pyTooling/Actions" __documentation_url__ = "https://pyTooling.github.io/Actions"