Private/Resolve-DiffRange.ps1
|
function Resolve-DiffRange { <# .SYNOPSIS Resolves the two commits the review diffs: the base and the commit that was built. .DESCRIPTION Single source of truth for the diff base of Get-ChangedFiles and Get-FileDiff, so the file list and the per-file diff sent to the model can never use different ranges. - PR build (BUILD_REASON = PullRequest) on a merge commit (refs/pull/<id>/merge): base = the merge commit's first parent, i.e. the target branch exactly as it was when the build's merge was made - what the build compiled. The target branch's current tip is irrelevant, so commits merged into the target while the build runs never enter the review. The first parent is always in the clone once the merge commit is (a shallow merge-base is not needed). - Anything else: base = merge-base of the target branch and the compare commit (three-dot). Missing merge-base (shallow clone): deepen once; still missing: skip. - Never a two-dot range against a branch tip, and never another commit than the one built: if the compare commit or the base cannot be resolved, the result is Skip with a reason. .PARAMETER BaseBranch Target branch for the non-merge fallback (e.g. 'origin/master' or 'master'). In a PR build SYSTEM_PULLREQUEST_TARGETBRANCH overrides it. .PARAMETER CompareRef The commit to review. Default: BUILD_SOURCEVERSION, else HEAD. .PARAMETER Fetch Allow fetching the target branch when it is missing (fallback path only). Get-ChangedFiles passes it; Get-FileDiff runs after it and only reads what is already there. .OUTPUTS PSCustomObject: Skip, Reason, Base, Compare, Mode ('MergeParent' | 'MergeBase'), Label, BaseBranch .NOTES Author: waldo #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [string]$BaseBranch = 'origin/master', [Parameter(Mandatory = $false)] [string]$CompareRef = '', [Parameter(Mandatory = $false)] [switch]$Fetch ) $result = [pscustomobject]@{ Skip = $false Reason = '' Base = $null Compare = $null Mode = $null Label = '' BaseBranch = $BaseBranch } if (-not $CompareRef) { $CompareRef = if ($env:BUILD_SOURCEVERSION) { $env:BUILD_SOURCEVERSION } else { 'HEAD' } } # The commit that was built must be in the clone - never review another commit instead. $compareSha = git rev-parse --verify --quiet "$CompareRef^{commit}" 2>$null if ($LASTEXITCODE -ne 0 -or -not $compareSha) { $result.Skip = $true $result.Reason = "Compare commit $CompareRef is not present in the clone" return $result } $compareSha = "$compareSha".Trim() $result.Compare = $compareSha if ($env:BUILD_REASON -eq 'PullRequest') { # A shallow boundary hides the parents of the merge commit: deepen once. $parents = @((git rev-list --parents -n 1 $compareSha 2>$null) -split ' ' | Select-Object -Skip 1) if ($parents.Count -eq 0 -and (git rev-parse --is-shallow-repository 2>$null) -eq 'true') { Write-Verbose "Merge commit $compareSha is at the shallow boundary - deepening once" git fetch --quiet --deepen=1 origin 2>&1 | Out-Null $parents = @((git rev-list --parents -n 1 $compareSha 2>$null) -split ' ' | Select-Object -Skip 1) } if ($parents.Count -ge 2) { $result.Base = $parents[0] $result.Mode = 'MergeParent' $result.Label = 'merge parent 1' return $result } Write-Verbose "PR build but $compareSha is not a merge commit - falling back to the merge-base" } # Fallback (not a PR merge commit): three-dot against the target branch. if ($env:SYSTEM_PULLREQUEST_TARGETBRANCH) { $BaseBranch = 'origin/' + ($env:SYSTEM_PULLREQUEST_TARGETBRANCH -replace '^refs/heads/', '') } $branchName = $BaseBranch -replace '^origin/', '' $candidates = @($BaseBranch, "origin/$branchName", $branchName) | Select-Object -Unique $baseTip = $null foreach ($attempt in 1..2) { foreach ($candidate in $candidates) { $sha = git rev-parse --verify --quiet "$candidate^{commit}" 2>$null if ($LASTEXITCODE -eq 0 -and $sha) { $baseTip = "$sha".Trim() $result.BaseBranch = $candidate break } } if ($baseTip -or -not $Fetch -or $attempt -eq 2) { break } Write-Verbose "Fetching $branchName from origin..." git fetch --quiet origin "${branchName}:refs/remotes/origin/${branchName}" 2>&1 | Out-Null } if (-not $baseTip) { $result.Skip = $true $result.Reason = "Target branch $BaseBranch is not present in the clone" return $result } $mergeBase = git merge-base $baseTip $compareSha 2>$null if ($LASTEXITCODE -ne 0 -or -not $mergeBase) { Write-Verbose "No merge-base of $baseTip and $compareSha in the shallow clone - deepening once" git fetch --quiet --deepen=200 origin 2>&1 | Out-Null $mergeBase = git merge-base $baseTip $compareSha 2>$null } if ($LASTEXITCODE -ne 0 -or -not $mergeBase) { $result.Skip = $true $result.Reason = "No merge-base of $($result.BaseBranch) and $compareSha (shallow clone)" return $result } $result.Base = "$mergeBase".Trim() $result.Mode = 'MergeBase' $result.Label = "merge-base with $($result.BaseBranch)" return $result } |