Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions eng/pipelines/dotnet-sqlclient-ci-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ parameters:
type: boolean
default: false

# When true, add a gate stage that detects redundant PR triggers (e.g. Draft <-> Open state
# transitions with no new commits). When a redundant trigger is detected, all subsequent stages
# are skipped and the pipeline succeeds without performing any work.
- name: skipRedundantTrigger
type: boolean
default: false

# The target frameworks to build and run tests for on Windows.
#
# These are _not_ the target frameworks to build the driver packages for.
Expand Down Expand Up @@ -123,6 +130,29 @@ variables:
value: SqlServer.Artifacts

stages:
# When skipRedundantTrigger is enabled, add a gate stage that detects PR triggers caused by
# state-only transitions (Draft <-> Open). If the trigger is redundant, the no-op stage's
# condition evaluates to false (skipped), which causes secrets_stage to implicitly skip via
# its default succeeded() condition, cascading to all downstream stages.
- ${{ if eq(parameters.skipRedundantTrigger, true) }}:
- template: /eng/pipelines/stages/detect-redundant-trigger-stage.yml@self
parameters:
debug: ${{ parameters.debug }}

- stage: skip_redundant_trigger_stage
displayName: Skip Redundant Trigger
dependsOn: detect_redundant_trigger_stage
condition: eq(dependencies.detect_redundant_trigger_stage.outputs['detect_redundant_trigger.detect.meaningful'], 'true')
jobs:
- job: proceed
displayName: Build is meaningful
pool:
vmImage: ubuntu-latest
steps:
- checkout: none
- bash: echo "PR Trigger gate passed — proceeding with build and test."
displayName: Confirm

# Generate secrets used throughout the pipeline.
- template: /eng/pipelines/stages/generate-secrets-ci-stage.yml@self
parameters:
Expand Down
1 change: 1 addition & 0 deletions eng/pipelines/sqlclient-pr-package-ref-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ extends:
buildPlatforms: ${{ parameters.buildPlatforms }}
referenceType: Package
debug: ${{ parameters.debug }}
skipRedundantTrigger: true
targetFrameworks: ${{ parameters.targetFrameworks }}
targetFrameworksUnix: ${{ parameters.targetFrameworksUnix }}
testJobTimeout: ${{ parameters.testJobTimeout }}
Expand Down
1 change: 1 addition & 0 deletions eng/pipelines/sqlclient-pr-project-ref-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ extends:
buildPlatforms: ${{ parameters.buildPlatforms }}
referenceType: Project
debug: ${{ parameters.debug }}
skipRedundantTrigger: true
targetFrameworks: ${{ parameters.targetFrameworks }}
targetFrameworksUnix: ${{ parameters.targetFrameworksUnix }}
testJobTimeout: ${{ parameters.testJobTimeout }}
Expand Down
128 changes: 128 additions & 0 deletions eng/pipelines/stages/detect-redundant-trigger-stage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
####################################################################################################
# Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this
# file to you under the MIT license. See the LICENSE file in the project root for more information.
####################################################################################################

# This stage gates PR pipeline runs to avoid redundant builds triggered by state-only transitions
# (e.g. Draft <-> Open). It compares the current merge commit (Build.SourceVersion) against the
# most recent successful build for the same PR in this pipeline definition.
#
# If the merge commit is unchanged and the prior build succeeded, the build is considered redundant
# and the output variable 'meaningful' is set to 'false'. Downstream stages should check this
# variable and skip when it is 'false'.
#
# Output variable reference (for use in stage conditions):
#
# dependencies.detect_redundant_trigger_stage.outputs['detect_redundant_trigger.detect.meaningful']
#
# This stage defines:
#
# Stage name: detect_redundant_trigger_stage
# Job name: detect_redundant_trigger
# Step name: detect
# Variable: meaningful (true|false)

parameters:

# True to emit debug information.
- name: debug
type: boolean
default: false

stages:

- stage: detect_redundant_trigger_stage
displayName: Detect Redundant Trigger
jobs:

- job: detect_redundant_trigger
displayName: Detect Redundant Trigger
pool:
vmImage: ubuntu-latest

steps:

- checkout: none

- bash: |
set -euo pipefail

# If this is not a PR-triggered run (e.g., manual queue), always proceed.
if [[ "$(Build.Reason)" != "PullRequest" ]]; then
echo "##[section]Build is MEANINGFUL — not a PR trigger (Build.Reason=$(Build.Reason))."
echo "##vso[task.setvariable variable=meaningful;isOutput=true]true"
exit 0
fi

# The current merge commit SHA for this PR build.
CURRENT_SOURCE_VERSION="$(Build.SourceVersion)"

# The Azure DevOps REST API base URL.
ORG_URL="$(System.CollectionUri)"
PROJECT="$(System.TeamProject)"
DEFINITION_ID="$(System.DefinitionId)"
BUILD_ID="$(Build.BuildId)"

# The branch name used for PR builds (refs/pull/{number}/merge).
BRANCH="$(Build.SourceBranch)"

echo "Current source version: $CURRENT_SOURCE_VERSION"
echo "Branch: $BRANCH"
echo "Definition ID: $DEFINITION_ID"
echo "Build ID: $BUILD_ID"

# Query the most recent successful build for this PR in this pipeline,
# excluding the current build.
API_URL="${ORG_URL}${PROJECT}/_apis/build/builds"
API_URL="${API_URL}?definitions=${DEFINITION_ID}"
API_URL="${API_URL}&branchName=${BRANCH}"
API_URL="${API_URL}&resultFilter=succeeded"
API_URL="${API_URL}&\$top=5"
API_URL="${API_URL}&queryOrder=finishTimeDescending"
API_URL="${API_URL}&api-version=7.1"

# Call the API using the mapped env var (avoids macro expansion/logging).
# On HTTP error, default to meaningful=true so the build proceeds.
HTTP_CODE=$(curl -s -o /tmp/api_response.json -w "%{http_code}" \
-H "Authorization: Bearer $SYSTEM_ACCESSTOKEN" "$API_URL")

if [[ "$HTTP_CODE" -lt 200 || "$HTTP_CODE" -ge 300 ]]; then
echo "##[warning]Builds API returned HTTP $HTTP_CODE — defaulting to meaningful build."
echo "##vso[task.setvariable variable=meaningful;isOutput=true]true"
exit 0
Comment on lines +86 to +92
fi

RESPONSE=$(cat /tmp/api_response.json)

# Find the first successful build that is not the current build.
LAST_SUCCESSFUL_VERSION=$(echo "$RESPONSE" | \
jq -r --argjson buildId "$BUILD_ID" \
'[.value[] | select(.id != $buildId)] | .[0].sourceVersion // empty')

echo "Last successful source version: ${LAST_SUCCESSFUL_VERSION:-<none>}"

if [[ -n "$LAST_SUCCESSFUL_VERSION" && "$LAST_SUCCESSFUL_VERSION" == "$CURRENT_SOURCE_VERSION" ]]; then
echo ""
echo "##[section]Build is REDUNDANT — source version unchanged and prior build succeeded."
echo "This is likely a Draft/Open state transition. Skipping build."
echo "##vso[task.setvariable variable=meaningful;isOutput=true]false"
else
Comment thread
paulmedynski marked this conversation as resolved.
echo ""
echo "##[section]Build is MEANINGFUL — proceeding."
if [[ -z "$LAST_SUCCESSFUL_VERSION" ]]; then
echo " Reason: No prior successful build found for this PR."
elif [[ "$LAST_SUCCESSFUL_VERSION" != "$CURRENT_SOURCE_VERSION" ]]; then
echo " Reason: Source version changed (new commits or target branch update)."
fi
echo "##vso[task.setvariable variable=meaningful;isOutput=true]true"
fi
name: detect
displayName: Check for redundant PR trigger
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)

- ${{ if eq(parameters.debug, true) }}:
- bash: |
echo "API Response (first 2000 chars):"
head -c 2000 /tmp/api_response.json 2>/dev/null || echo "<no response captured>"
displayName: Debug - Show API Response
Loading