From 7a13e90657f69a0102baf41701c2e2d27f1192dd Mon Sep 17 00:00:00 2001 From: Azure Functions Java Worker Bot Date: Mon, 7 Sep 2026 08:06:36 +0000 Subject: [PATCH 1/4] Update Java versions Auto-detected new Java versions from Microsoft OpenJDK releases. This PR was automatically generated by the Java version check pipeline. Pipeline Run: 20260907.1 --- eng/ci/templates/java-versions.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/eng/ci/templates/java-versions.yml b/eng/ci/templates/java-versions.yml index d2fbb759..df842e26 100644 --- a/eng/ci/templates/java-versions.yml +++ b/eng/ci/templates/java-versions.yml @@ -1,16 +1,16 @@ variables: # Linux JDK Versions - JDK8_LINUX_VERSION: '492' - JDK8_LINUX_BUILD: '09' - JDK11_LINUX_VERSION: '11.0.31' - JDK17_LINUX_VERSION: '17.0.19' - JDK21_LINUX_VERSION: '21.0.11' - JDK25_LINUX_VERSION: '25.0.3' + JDK8_LINUX_VERSION: '504' + JDK8_LINUX_BUILD: '01' + JDK11_LINUX_VERSION: '11.0.32' + JDK17_LINUX_VERSION: '17.0.20' + JDK21_LINUX_VERSION: '21.0.12' + JDK25_LINUX_VERSION: '25.0.4' # Windows JDK Versions - JDK8_WINDOWS_VERSION: '492' - JDK8_WINDOWS_BUILD: '09' - JDK11_WINDOWS_VERSION: '11.0.31' - JDK17_WINDOWS_VERSION: '17.0.19' - JDK21_WINDOWS_VERSION: '21.0.11' - JDK25_WINDOWS_VERSION: '25.0.3' \ No newline at end of file + JDK8_WINDOWS_VERSION: '504' + JDK8_WINDOWS_BUILD: '01' + JDK11_WINDOWS_VERSION: '11.0.32' + JDK17_WINDOWS_VERSION: '17.0.20' + JDK21_WINDOWS_VERSION: '21.0.12' + JDK25_WINDOWS_VERSION: '25.0.4' \ No newline at end of file From 19734c6fc42863445b812fac80cf8d1e68192b77 Mon Sep 17 00:00:00 2001 From: AzureFunctionsJava Date: Wed, 9 Sep 2026 11:08:27 -0500 Subject: [PATCH 2/4] Stop the version check pipeline opening duplicate PRs The pipeline cuts a new PR every Monday even when nothing has changed. Seven are open right now and all seven propose the same JDK 11/17/21/25 bump, differing only in JDK 8. Two things caused it. The branch name carried a timestamp, so the "does a PR already exist?" lookup asked about a branch that had just been created and always came back empty. And change detection only compared against dev, which never moves while the previous PR sits unmerged, so the same bump looked new every week. Now there is one long lived branch, automated/update-java-versions, that gets refreshed in place. The run also compares against that branch, not just dev, and does nothing at all when it already carries the same versions. If the versions really did move, the branch is reset onto dev and force pushed so the PR stays a single clean commit, and the existing PR is updated instead of a new one being opened. Two edge cases that used to get stuck now resolve on their own. A closed PR whose branch is still around gets reopened rather than skipped. And if the create call comes back 422 because something else opened the PR in the meantime, the run adopts that PR and updates it instead of failing with the branch already pushed. Also replaced the grep based JSON handling with python, which this job already runs for the merge step. Counting occurrences of "number" in raw JSON is fragile because nested objects such as milestones carry one too. Added a label and a hidden marker in the body so the PR can still be found if the branch name ever changes again. --- eng/ci/java-version-check.yml | 306 ++++++++++++++++++++++------------ 1 file changed, 203 insertions(+), 103 deletions(-) diff --git a/eng/ci/java-version-check.yml b/eng/ci/java-version-check.yml index 00459309..d640380a 100644 --- a/eng/ci/java-version-check.yml +++ b/eng/ci/java-version-check.yml @@ -143,128 +143,228 @@ extends: displayName: 'Merge version updates' - bash: | - # Configure git + set -euo pipefail + git config user.email "azfunc-java-worker-bot@microsoft.com" git config user.name "Azure Functions Java Worker Bot" - - # Check if there are changes - if git diff --quiet eng/ci/templates/java-versions.yml; then - echo "No changes detected in java-versions.yml" + + VERSIONS_FILE="eng/ci/templates/java-versions.yml" + + # One long-lived branch, refreshed in place, so at most one bot PR is ever + # open. The previous timestamped branch name made the "does a PR already + # exist?" lookup match nothing, which cut a duplicate PR every week. + BOT_BRANCH="automated/update-java-versions" + echo "##vso[task.setvariable variable=BotBranch]$BOT_BRANCH" + + if git diff --quiet -- "$VERSIONS_FILE"; then + echo "No changes versus dev. Nothing to do." echo "##vso[task.setvariable variable=HasChanges]false" + exit 0 + fi + + echo "Changes versus dev:" + git --no-pager diff -- "$VERSIONS_FILE" + echo "##vso[task.setvariable variable=HasChanges]true" + + # Record whether the bot branch already carries these exact versions. + # If it does and its PR is still open, the next step skips the push so we + # don't re-run CI and churn the PR every week for identical content. + BRANCH_UP_TO_DATE=false + if git fetch --quiet origin "+refs/heads/${BOT_BRANCH}:refs/remotes/origin/${BOT_BRANCH}"; then + if git show "origin/${BOT_BRANCH}:${VERSIONS_FILE}" > /tmp/bot-branch-versions.yml 2>/dev/null \ + && cmp -s /tmp/bot-branch-versions.yml "$VERSIONS_FILE"; then + BRANCH_UP_TO_DATE=true + echo "origin/${BOT_BRANCH} already carries these versions." + else + echo "origin/${BOT_BRANCH} exists with different versions and will be refreshed." + fi else - echo "Changes detected in java-versions.yml" - echo "##vso[task.setvariable variable=HasChanges]true" - - # Show the diff - echo "Changes:" - git diff eng/ci/templates/java-versions.yml + echo "origin/${BOT_BRANCH} does not exist yet and will be created." fi + + echo "##vso[task.setvariable variable=BranchUpToDate]$BRANCH_UP_TO_DATE" displayName: 'Check for changes' - bash: | - BRANCH_NAME="automated/update-java-versions-$(date +%Y%m%d-%H%M%S)" - echo "Creating branch: $BRANCH_NAME" - git checkout -b $BRANCH_NAME - - # Stage and commit changes - git add eng/ci/templates/java-versions.yml - git commit -m "Update Java versions - - Auto-detected new Java versions from Microsoft OpenJDK releases. - - This PR was automatically generated by the Java version check pipeline. - - Pipeline Run: $(Build.BuildNumber)" - - # Push the branch - git push origin $BRANCH_NAME - - # Store branch name for PR creation - echo "##vso[task.setvariable variable=BranchName]$BRANCH_NAME" - displayName: 'Commit and push changes' - condition: eq(variables['HasChanges'], 'true') - - - bash: | - set -e - - BRANCH_NAME="$(BranchName)" + set -euo pipefail + + BOT_BRANCH="$(BotBranch)" + BRANCH_UP_TO_DATE="$(BranchUpToDate)" GITHUB_REPO="Azure/azure-functions-java-worker" - - echo "Branch name: $BRANCH_NAME" - echo "GitHub repository: $GITHUB_REPO" - + VERSIONS_FILE="eng/ci/templates/java-versions.yml" + LABEL="automation:java-versions" + BUILD_NUM="$(Build.BuildNumber)" + # Extract GitHub token from the git remote URL credentials - # Azure Pipelines injects the token into the git config - GIT_TOKEN=$(git config --get-urlmatch http.extraheader https://github.com/${GITHUB_REPO} | sed -n 's/.*AUTHORIZATION: basic \(.*\)/\1/p' | base64 -d | cut -d: -f2) - + # Azure Pipelines injects the token into the git config. + # The trailing '|| true' matters: under 'pipefail' a miss here would + # abort the step instead of falling through to System.AccessToken. + GIT_TOKEN=$(git config --get-urlmatch http.extraheader "https://github.com/${GITHUB_REPO}" | sed -n 's/.*AUTHORIZATION: basic \(.*\)/\1/p' | base64 -d | cut -d: -f2 || true) + if [ -z "$GIT_TOKEN" ]; then echo "WARNING: Could not extract GitHub token from git config" echo "Attempting to use System.AccessToken directly..." - GIT_TOKEN="$(System.AccessToken)" + GIT_TOKEN="$SYSTEM_ACCESSTOKEN" fi - - # Check if PR already exists - echo "Checking for existing PR from branch: $BRANCH_NAME" - EXISTING_PR=$(curl -s \ - -H "Authorization: token ${GIT_TOKEN}" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/${GITHUB_REPO}/pulls?head=Azure:${BRANCH_NAME}&state=open") - - PR_COUNT=$(echo "$EXISTING_PR" | grep -c '"number":' || true) - - if [ "$PR_COUNT" -gt 0 ]; then - PR_NUMBER=$(echo "$EXISTING_PR" | grep -o '"number":[0-9]*' | head -1 | grep -o '[0-9]*') - echo "PR already exists for branch $BRANCH_NAME" - echo "Existing PR: https://github.com/${GITHUB_REPO}/pull/${PR_NUMBER}" + + # Writes the response body to /tmp/gh_response.json and sets HTTP_CODE. + gh_api() { + local method="$1"; shift + local path="$1"; shift + HTTP_CODE=$(curl -sS -o /tmp/gh_response.json -w '%{http_code}' \ + -X "$method" \ + -H "Authorization: token ${GIT_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + -H "Content-Type: application/json" \ + "$@" \ + "https://api.github.com${path}") + } + + # JSON is built and read with python rather than jq, which is not + # guaranteed on the image. The merge step in this job already runs a + # python script, so an interpreter is present; accept either name. + PY=$(command -v python3 || command -v python || true) + if [ -z "$PY" ]; then + echo "ERROR: No python interpreter found on PATH" + exit 1 + fi + + json_obj() { + "$PY" -c 'import json,sys; print(json.dumps(dict(a.split("=", 1) for a in sys.argv[1:])))' "$@" + } + + # Read the top-level array element. Grepping the raw JSON for "number" + # is fragile because nested objects such as milestones carry one too. + gh_api GET "/repos/${GITHUB_REPO}/pulls?state=open&base=dev&head=Azure:${BOT_BRANCH}" + if [ "$HTTP_CODE" != "200" ]; then + echo "ERROR: Failed to query existing pull requests (HTTP $HTTP_CODE)" + cat /tmp/gh_response.json + exit 1 + fi + PR_NUMBER=$("$PY" -c 'import json,sys; d=json.load(sys.stdin); print(d[0]["number"] if d else "")' < /tmp/gh_response.json) + + if [ -n "$PR_NUMBER" ] && [ "$BRANCH_UP_TO_DATE" = "true" ]; then + echo "PR #${PR_NUMBER} is already open with these exact versions. Nothing to do." + echo "https://github.com/${GITHUB_REPO}/pull/${PR_NUMBER}" exit 0 fi - + + # Reset the branch onto the current dev tip so the PR stays a single commit + # and cannot drift or accumulate conflicts while it waits for review. + git checkout -B "$BOT_BRANCH" + git add "$VERSIONS_FILE" + git commit -m "Update Java versions + + Auto-detected new Java versions from Microsoft OpenJDK releases. + + This PR was automatically generated by the Java version check pipeline. + + Pipeline Run: ${BUILD_NUM}" + + # The bot owns this branch exclusively; --force-with-lease still guards + # against clobbering anything pushed since the fetch above. + if git rev-parse --verify --quiet "refs/remotes/origin/${BOT_BRANCH}" > /dev/null; then + git push --force-with-lease origin "$BOT_BRANCH" + else + git push origin "$BOT_BRANCH" + fi + CURRENT_DATE=$(date -u +"%Y-%m-%d %H:%M:%S UTC") - BUILD_NUM="$(Build.BuildNumber)" - - # Create JSON payload for PR - cat > /tmp/pr_payload.json << 'JSONEOF' - { - "title": "Update Java versions to latest releases", - "body": "This PR updates the Java version variables in `eng/ci/templates/java-versions.yml` to the latest versions.\n\n## Changes\n- Auto-detected and validated new Java versions\n- **JDK 8**: From Adoptium (Eclipse Temurin)\n- **JDK 11, 17, 21, 25**: From Microsoft OpenJDK\n- Linux JDKs validated on Linux pool\n- Windows JDKs validated on Windows pool\n- All JDK installations were validated using `java -version`\n\n## Automated Process\nThis PR was automatically created by the Java Version Check Pipeline.\n\n## Review Checklist\n- [ ] Verify version numbers are correct\n- [ ] Check that both Linux and Windows versions are updated\n- [ ] Ensure CI tests pass with new versions\n\n**Pipeline Run:** BUILDNUMBER_PLACEHOLDER\n**Date:** CURRENTDATE_PLACEHOLDER", - "head": "BRANCHNAME_PLACEHOLDER", - "base": "dev" - } - JSONEOF - - # Replace placeholders - sed -i "s|BRANCHNAME_PLACEHOLDER|${BRANCH_NAME}|g" /tmp/pr_payload.json - sed -i "s|BUILDNUMBER_PLACEHOLDER|${BUILD_NUM}|g" /tmp/pr_payload.json - sed -i "s|CURRENTDATE_PLACEHOLDER|${CURRENT_DATE}|g" /tmp/pr_payload.json - - echo "Creating pull request on GitHub..." - PR_RESPONSE=$(curl -s -w "\n%{http_code}" \ - -X POST \ - -H "Authorization: token ${GIT_TOKEN}" \ - -H "Accept: application/vnd.github+json" \ - -H "Content-Type: application/json" \ - -d @/tmp/pr_payload.json \ - "https://api.github.com/repos/${GITHUB_REPO}/pulls") - - HTTP_CODE=$(echo "$PR_RESPONSE" | tail -n1) - RESPONSE_BODY=$(echo "$PR_RESPONSE" | head -n-1) - - echo "HTTP Status: $HTTP_CODE" - - if [ "$HTTP_CODE" = "201" ]; then - PR_NUMBER=$(echo "$RESPONSE_BODY" | grep -o '"number":[0-9]*' | head -1 | grep -o '[0-9]*') - echo "Pull request created successfully! PR #$PR_NUMBER" - echo "URL: https://github.com/${GITHUB_REPO}/pull/${PR_NUMBER}" + VERSIONS=$(cat "$VERSIONS_FILE") + + # Backticks are escaped so the unquoted heredoc does not run them as commands. + BODY=$(cat < + This PR updates the Java version variables in \`eng/ci/templates/java-versions.yml\` to the latest releases. + + The Java Version Check pipeline opens this PR and then refreshes it in place, so only one of them is ever open. If newer JDKs ship before this merges, the same PR is updated instead of a new one being created. + + ## Proposed versions + + \`\`\`yaml + ${VERSIONS} + \`\`\` + + ## Sources + - **JDK 8**: Adoptium (Eclipse Temurin) + - **JDK 11, 17, 21, 25**: Microsoft OpenJDK + + Linux JDKs were validated on a Linux pool and Windows JDKs on a Windows pool. Every install was checked with \`java -version\`. + + ## Review checklist + - [ ] Version numbers are correct + - [ ] Both Linux and Windows values were updated + - [ ] CI passes with the new versions + + **Pipeline run:** ${BUILD_NUM} + **Last refreshed:** ${CURRENT_DATE} + EOF + ) + + if [ -n "$PR_NUMBER" ]; then + echo "Refreshing existing PR #${PR_NUMBER}..." + gh_api PATCH "/repos/${GITHUB_REPO}/pulls/${PR_NUMBER}" -d "$(json_obj "body=$BODY")" + if [ "$HTTP_CODE" != "200" ]; then + echo "ERROR: Failed to update pull request (HTTP $HTTP_CODE)" + cat /tmp/gh_response.json + exit 1 + fi + + COMMENT="Refreshed with newer JDK releases detected on ${CURRENT_DATE} by pipeline run ${BUILD_NUM}." + gh_api POST "/repos/${GITHUB_REPO}/issues/${PR_NUMBER}/comments" -d "$(json_obj "body=$COMMENT")" else - echo "ERROR: Failed to create pull request" - echo "Response: $RESPONSE_BODY" - echo "" - echo "The branch has been pushed to: $BRANCH_NAME" - echo "You can manually create a PR at:" - echo "https://github.com/${GITHUB_REPO}/compare/dev...${BRANCH_NAME}" - exit 1 + echo "Creating pull request on GitHub..." + gh_api POST "/repos/${GITHUB_REPO}/pulls" \ + -d "$(json_obj "title=Update Java versions to latest releases" "head=$BOT_BRANCH" "base=dev" "body=$BODY")" + + if [ "$HTTP_CODE" = "201" ]; then + PR_NUMBER=$("$PY" -c 'import json,sys; print(json.load(sys.stdin)["number"])' < /tmp/gh_response.json) + elif [ "$HTTP_CODE" = "422" ]; then + # GitHub returns 422 when a PR for this head already exists, which + # happens if another run opened one after our query. Adopt it rather + # than failing, otherwise the branch is left pushed with no PR. + cp /tmp/gh_response.json /tmp/gh_create_error.json + echo "PR creation returned 422; checking for a PR opened concurrently..." + gh_api GET "/repos/${GITHUB_REPO}/pulls?state=open&base=dev&head=Azure:${BOT_BRANCH}" + PR_NUMBER=$("$PY" -c 'import json,sys; d=json.load(sys.stdin); print(d[0]["number"] if d else "")' < /tmp/gh_response.json) + if [ -z "$PR_NUMBER" ]; then + echo "ERROR: Failed to create pull request (HTTP 422) and no open PR was found" + cat /tmp/gh_create_error.json + exit 1 + fi + echo "Adopting concurrently opened PR #${PR_NUMBER}" + gh_api PATCH "/repos/${GITHUB_REPO}/pulls/${PR_NUMBER}" -d "$(json_obj "body=$BODY")" + else + echo "ERROR: Failed to create pull request (HTTP $HTTP_CODE)" + cat /tmp/gh_response.json + echo "" + echo "The branch has been pushed to: $BOT_BRANCH" + echo "You can manually create a PR at:" + echo "https://github.com/${GITHUB_REPO}/compare/dev...${BOT_BRANCH}" + exit 1 + fi + fi + + # A label plus the HTML marker in the body give a second way to find this + # PR if the branch name ever changes again. Neither call is worth failing + # the run over once the PR itself is in place, but a silent miss is not + # worth having either. + gh_api POST "/repos/${GITHUB_REPO}/labels" \ + -d "$(json_obj "name=$LABEL" "color=ededed" "description=Opened automatically by the Java version check pipeline")" + # 422 is the steady state here: the label already exists. + if [ "$HTTP_CODE" != "201" ] && [ "$HTTP_CODE" != "422" ]; then + echo "WARNING: Could not ensure the '$LABEL' label exists (HTTP $HTTP_CODE)" fi - displayName: 'Create Pull Request on GitHub' + + gh_api POST "/repos/${GITHUB_REPO}/issues/${PR_NUMBER}/labels" \ + -d "$("$PY" -c 'import json,sys; print(json.dumps({"labels": sys.argv[1:]}))' "$LABEL")" + if [ "$HTTP_CODE" != "200" ]; then + echo "WARNING: Could not add the '$LABEL' label to PR #${PR_NUMBER} (HTTP $HTTP_CODE)" + fi + + echo "Done. PR #${PR_NUMBER}" + echo "https://github.com/${GITHUB_REPO}/pull/${PR_NUMBER}" + displayName: 'Create or refresh Pull Request on GitHub' condition: eq(variables['HasChanges'], 'true') env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) From 390e9f81b6730788375f2db7b0d30a62094a53c3 Mon Sep 17 00:00:00 2001 From: AzureFunctionsJava Date: Wed, 9 Sep 2026 13:21:12 -0500 Subject: [PATCH 3/4] Name the expected SHA when force pushing the bot branch The bare --force-with-lease form only works when the remote's fetch refspec maps the bot branch into refs/remotes/origin. It does not always, and when it does not the push is rejected with "stale info". That would have hit the refresh path, so the first run that found newer JDKs than the open PR would have failed after committing but before updating it. Capturing the remote SHA at fetch time and passing it as an explicit lease value works either way and keeps the same protection. Caught while porting this fix to the Docker repo, where the clone is single-branch and the failure reproduces every time. --- eng/ci/java-version-check.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/eng/ci/java-version-check.yml b/eng/ci/java-version-check.yml index d640380a..4b187dcc 100644 --- a/eng/ci/java-version-check.yml +++ b/eng/ci/java-version-check.yml @@ -170,7 +170,9 @@ extends: # If it does and its PR is still open, the next step skips the push so we # don't re-run CI and churn the PR every week for identical content. BRANCH_UP_TO_DATE=false + BOT_REMOTE_SHA="" if git fetch --quiet origin "+refs/heads/${BOT_BRANCH}:refs/remotes/origin/${BOT_BRANCH}"; then + BOT_REMOTE_SHA=$(git rev-parse "refs/remotes/origin/${BOT_BRANCH}" 2>/dev/null || true) if git show "origin/${BOT_BRANCH}:${VERSIONS_FILE}" > /tmp/bot-branch-versions.yml 2>/dev/null \ && cmp -s /tmp/bot-branch-versions.yml "$VERSIONS_FILE"; then BRANCH_UP_TO_DATE=true @@ -183,6 +185,7 @@ extends: fi echo "##vso[task.setvariable variable=BranchUpToDate]$BRANCH_UP_TO_DATE" + echo "##vso[task.setvariable variable=BotRemoteSha]$BOT_REMOTE_SHA" displayName: 'Check for changes' - bash: | @@ -190,6 +193,7 @@ extends: BOT_BRANCH="$(BotBranch)" BRANCH_UP_TO_DATE="$(BranchUpToDate)" + BOT_REMOTE_SHA="$(BotRemoteSha)" GITHUB_REPO="Azure/azure-functions-java-worker" VERSIONS_FILE="eng/ci/templates/java-versions.yml" LABEL="automation:java-versions" @@ -261,10 +265,12 @@ extends: Pipeline Run: ${BUILD_NUM}" - # The bot owns this branch exclusively; --force-with-lease still guards - # against clobbering anything pushed since the fetch above. - if git rev-parse --verify --quiet "refs/remotes/origin/${BOT_BRANCH}" > /dev/null; then - git push --force-with-lease origin "$BOT_BRANCH" + # The bot owns this branch exclusively, but the lease still guards against + # clobbering anything pushed since the fetch above. It has to name the + # expected SHA explicitly, because the bare --force-with-lease form fails + # with "stale info" unless the remote's fetch refspec maps the bot branch. + if [ -n "$BOT_REMOTE_SHA" ]; then + git push --force-with-lease="${BOT_BRANCH}:${BOT_REMOTE_SHA}" origin "$BOT_BRANCH" else git push origin "$BOT_BRANCH" fi From 84afa471e81b8ce977d71b49c5b97e44fe08a38e Mon Sep 17 00:00:00 2001 From: AzureFunctionsJava Date: Wed, 9 Sep 2026 13:48:13 -0500 Subject: [PATCH 4/4] Nudge reviewers on a PR that is still sitting open Deduplicating the weekly PR made the pipeline quieter, but the duplicates had been doing a job: a fresh PR every Monday kept the update in people's faces. Without them a PR can sit unnoticed, which is exactly what happened in the Docker repo where one went unread for two months. So every run that finds a PR already open now comments on it, tagging whoever is currently requested for review and saying how long it has been waiting. The mentions come from the requested-reviewers API rather than a hardcoded list, so they follow CODEOWNERS instead of drifting from it. Three cases. Nothing new and the PR still current: nudge to review it. Newer JDKs found: refresh the PR and say so in the same comment. Nothing new and dev has moved on: say the PR looks redundant and ask for it to be merged or closed. Creating a PR stays silent, since that notifies on its own. This means the step now runs on quiet weeks too, so the HasChanges condition is gone and the change detection reports state instead of exiting early. Dropping the explicit condition restores the default succeeded(), so it is still skipped if the previous step fails. --- eng/ci/java-version-check.yml | 98 ++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/eng/ci/java-version-check.yml b/eng/ci/java-version-check.yml index 4b187dcc..991683bd 100644 --- a/eng/ci/java-version-check.yml +++ b/eng/ci/java-version-check.yml @@ -156,34 +156,37 @@ extends: BOT_BRANCH="automated/update-java-versions" echo "##vso[task.setvariable variable=BotBranch]$BOT_BRANCH" - if git diff --quiet -- "$VERSIONS_FILE"; then - echo "No changes versus dev. Nothing to do." - echo "##vso[task.setvariable variable=HasChanges]false" - exit 0 - fi - - echo "Changes versus dev:" - git --no-pager diff -- "$VERSIONS_FILE" - echo "##vso[task.setvariable variable=HasChanges]true" - - # Record whether the bot branch already carries these exact versions. - # If it does and its PR is still open, the next step skips the push so we - # don't re-run CI and churn the PR every week for identical content. + HAS_CHANGES=true BRANCH_UP_TO_DATE=false BOT_REMOTE_SHA="" - if git fetch --quiet origin "+refs/heads/${BOT_BRANCH}:refs/remotes/origin/${BOT_BRANCH}"; then - BOT_REMOTE_SHA=$(git rev-parse "refs/remotes/origin/${BOT_BRANCH}" 2>/dev/null || true) - if git show "origin/${BOT_BRANCH}:${VERSIONS_FILE}" > /tmp/bot-branch-versions.yml 2>/dev/null \ - && cmp -s /tmp/bot-branch-versions.yml "$VERSIONS_FILE"; then - BRANCH_UP_TO_DATE=true - echo "origin/${BOT_BRANCH} already carries these versions." + + if git diff --quiet -- "$VERSIONS_FILE"; then + # Still hand off to the next step rather than exiting: an open PR may + # need a nudge even on a week when nothing new has shipped. + echo "No changes versus dev." + HAS_CHANGES=false + else + echo "Changes versus dev:" + git --no-pager diff -- "$VERSIONS_FILE" + + # Record whether the bot branch already carries these exact versions. + # If it does and its PR is still open, the next step skips the push so we + # don't re-run CI and churn the PR every week for identical content. + if git fetch --quiet origin "+refs/heads/${BOT_BRANCH}:refs/remotes/origin/${BOT_BRANCH}"; then + BOT_REMOTE_SHA=$(git rev-parse "refs/remotes/origin/${BOT_BRANCH}" 2>/dev/null || true) + if git show "origin/${BOT_BRANCH}:${VERSIONS_FILE}" > /tmp/bot-branch-versions.yml 2>/dev/null \ + && cmp -s /tmp/bot-branch-versions.yml "$VERSIONS_FILE"; then + BRANCH_UP_TO_DATE=true + echo "origin/${BOT_BRANCH} already carries these versions." + else + echo "origin/${BOT_BRANCH} exists with different versions and will be refreshed." + fi else - echo "origin/${BOT_BRANCH} exists with different versions and will be refreshed." + echo "origin/${BOT_BRANCH} does not exist yet and will be created." fi - else - echo "origin/${BOT_BRANCH} does not exist yet and will be created." fi + echo "##vso[task.setvariable variable=HasChanges]$HAS_CHANGES" echo "##vso[task.setvariable variable=BranchUpToDate]$BRANCH_UP_TO_DATE" echo "##vso[task.setvariable variable=BotRemoteSha]$BOT_REMOTE_SHA" displayName: 'Check for changes' @@ -192,6 +195,7 @@ extends: set -euo pipefail BOT_BRANCH="$(BotBranch)" + HAS_CHANGES="$(HasChanges)" BRANCH_UP_TO_DATE="$(BranchUpToDate)" BOT_REMOTE_SHA="$(BotRemoteSha)" GITHUB_REPO="Azure/azure-functions-java-worker" @@ -247,8 +251,51 @@ extends: fi PR_NUMBER=$("$PY" -c 'import json,sys; d=json.load(sys.stdin); print(d[0]["number"] if d else "")' < /tmp/gh_response.json) + # How long the PR has been sitting, so the nudge can say something useful. + # Embedded python has to stay on one line: a YAML block scalar cannot hold + # continuation lines at column zero, and python rejects them indented. + pr_age_days() { + gh_api GET "/repos/${GITHUB_REPO}/pulls/$1" > /dev/null + "$PY" -c 'import json,sys,datetime; d=json.load(open(sys.argv[1])); c=datetime.datetime.fromisoformat(d["created_at"].replace("Z","+00:00")); print((datetime.datetime.now(datetime.timezone.utc)-c).days)' /tmp/gh_response.json + } + + # Comments on the PR, tagging whoever is currently requested for review. + # The list is read from the API rather than hardcoded so it follows + # CODEOWNERS instead of drifting from it. + nudge_reviewers() { + local pr_number="$1" + local message="$2" + local mentions="" + + gh_api GET "/repos/${GITHUB_REPO}/pulls/${pr_number}/requested_reviewers" + if [ "$HTTP_CODE" = "200" ]; then + mentions=$("$PY" -c 'import json,sys; d=json.load(open(sys.argv[1])); print(" ".join(["@"+u["login"] for u in d.get("users",[])] + ["@"+t["slug"] for t in d.get("teams",[])]))' /tmp/gh_response.json) + else + echo "WARNING: Could not read requested reviewers (HTTP $HTTP_CODE), commenting without mentions" + fi + + gh_api POST "/repos/${GITHUB_REPO}/issues/${pr_number}/comments" \ + -d "$(json_obj "body=${mentions:+$mentions }$message")" + if [ "$HTTP_CODE" = "201" ]; then + echo "Nudged reviewers on PR #${pr_number}" + else + echo "WARNING: Could not comment on PR #${pr_number} (HTTP $HTTP_CODE)" + fi + } + + if [ "$HAS_CHANGES" != "true" ]; then + if [ -n "$PR_NUMBER" ]; then + # dev already carries these versions, so the open PR is stale. + nudge_reviewers "$PR_NUMBER" "This PR has been open for $(pr_age_days "$PR_NUMBER") days, and \`dev\` already carries these Java versions, so it looks redundant. Please review, merge, or close it." + else + echo "No changes and no open PR. Nothing to do." + fi + exit 0 + fi + if [ -n "$PR_NUMBER" ] && [ "$BRANCH_UP_TO_DATE" = "true" ]; then - echo "PR #${PR_NUMBER} is already open with these exact versions. Nothing to do." + echo "PR #${PR_NUMBER} is already open with these exact versions." + nudge_reviewers "$PR_NUMBER" "This PR has been open for $(pr_age_days "$PR_NUMBER") days and still carries the latest JDK releases. Please review and merge it so the weekly check can move on." echo "https://github.com/${GITHUB_REPO}/pull/${PR_NUMBER}" exit 0 fi @@ -317,7 +364,7 @@ extends: fi COMMENT="Refreshed with newer JDK releases detected on ${CURRENT_DATE} by pipeline run ${BUILD_NUM}." - gh_api POST "/repos/${GITHUB_REPO}/issues/${PR_NUMBER}/comments" -d "$(json_obj "body=$COMMENT")" + nudge_reviewers "$PR_NUMBER" "$COMMENT" else echo "Creating pull request on GitHub..." gh_api POST "/repos/${GITHUB_REPO}/pulls" \ @@ -371,6 +418,7 @@ extends: echo "Done. PR #${PR_NUMBER}" echo "https://github.com/${GITHUB_REPO}/pull/${PR_NUMBER}" displayName: 'Create or refresh Pull Request on GitHub' - condition: eq(variables['HasChanges'], 'true') + # No HasChanges condition: the step also runs on quiet weeks so it can + # nudge reviewers on a PR that is still sitting open. env: SYSTEM_ACCESSTOKEN: $(System.AccessToken)