-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Replace branch-mirror-status prompt file with PowerShell script #55111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
marcpopMSFT
wants to merge
4
commits into
main
Choose a base branch
from
marcpopmsft-fix-branch-mirror-script
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+236
−88
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
af3c734
Replace branch-mirror-status prompt with PowerShell script
marcpopMSFT d85866b
Address Copilot review: validate SearchDepth, fix token capture
marcpopMSFT 67f5cf5
Retrigger CI: StaticWebAssets Helix infra failure (exit code 2, failu…
marcpopMSFT 888b8e6
Address lbussell review: add doc comments for AzDo resource ID and AP…
marcpopMSFT File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,236 @@ | ||
| #!/usr/bin/env pwsh | ||
| <# | ||
| .SYNOPSIS | ||
| Check if public GitHub release branches have been mirrored to internal Azure DevOps repos. | ||
|
|
||
| .DESCRIPTION | ||
| For each configured branch mapping (dotnet/sdk, dotnet/installer), fetches the latest | ||
| commit on the public GitHub branch and checks whether it has been mirrored to the | ||
| corresponding internal/release/* branch in Azure DevOps (dnceng org, internal project). | ||
|
|
||
| Output is always plain markdown, safe for piping to a file or rendering. | ||
|
|
||
| .PARAMETER SearchDepth | ||
| Number of AzDo commits to fetch when searching for the GitHub SHA. Default: 50. | ||
|
|
||
| .EXAMPLE | ||
| ./eng/Get-BranchMirrorStatus.ps1 | ||
| ./eng/Get-BranchMirrorStatus.ps1 -SearchDepth 100 | ||
| ./eng/Get-BranchMirrorStatus.ps1 > status.md | ||
| #> | ||
|
|
||
| [CmdletBinding()] | ||
| param( | ||
| [ValidateRange(1, [int]::MaxValue)] | ||
| [int]$SearchDepth = 50 | ||
| ) | ||
|
|
||
| $ErrorActionPreference = 'Continue' | ||
|
|
||
| $AzDoOrg = "https://dev.azure.com/dnceng" | ||
|
|
||
| # Well-known Azure DevOps token scope GUID. | ||
| # See https://learn.microsoft.com/rest/api/azure/devops/tokens | ||
| $AzDoResourceId = "499b84ac-1321-427f-aa17-267ca6975798" | ||
|
|
||
| # Branch mappings for GitHub-to-Azure-DevOps mirror status checks. | ||
| $Mappings = @( | ||
| @{ GHOrg = 'dotnet'; GHRepo = 'sdk'; GHBranch = 'release/8.0.1xx'; AzDoProject = 'internal'; AzDoRepo = 'dotnet-sdk'; AzDoBranch = 'internal/release/8.0.1xx' } | ||
|
marcpopMSFT marked this conversation as resolved.
|
||
| @{ GHOrg = 'dotnet'; GHRepo = 'sdk'; GHBranch = 'release/8.0.4xx'; AzDoProject = 'internal'; AzDoRepo = 'dotnet-sdk'; AzDoBranch = 'internal/release/8.0.4xx' } | ||
| @{ GHOrg = 'dotnet'; GHRepo = 'sdk'; GHBranch = 'release/9.0.1xx'; AzDoProject = 'internal'; AzDoRepo = 'dotnet-sdk'; AzDoBranch = 'internal/release/9.0.1xx' } | ||
| @{ GHOrg = 'dotnet'; GHRepo = 'sdk'; GHBranch = 'release/9.0.3xx'; AzDoProject = 'internal'; AzDoRepo = 'dotnet-sdk'; AzDoBranch = 'internal/release/9.0.3xx' } | ||
| @{ GHOrg = 'dotnet'; GHRepo = 'installer'; GHBranch = 'release/8.0.1xx'; AzDoProject = 'internal'; AzDoRepo = 'dotnet-installer'; AzDoBranch = 'internal/release/8.0.1xx' } | ||
| @{ GHOrg = 'dotnet'; GHRepo = 'installer'; GHBranch = 'release/8.0.4xx'; AzDoProject = 'internal'; AzDoRepo = 'dotnet-installer'; AzDoBranch = 'internal/release/8.0.4xx' } | ||
| ) | ||
|
|
||
| function Format-TableCell { | ||
| param([string]$Text, [int]$MaxLength = 80) | ||
|
|
||
| if ([string]::IsNullOrEmpty($Text)) { return '' } | ||
| $Text = $Text -replace '\|', '\\|' | ||
| $Text = $Text -replace '\r?\n', ' ' | ||
| if ($Text.Length -gt $MaxLength) { | ||
| $Text = $Text.Substring(0, $MaxLength - 3) + '...' | ||
| } | ||
| return $Text | ||
| } | ||
|
|
||
| function Get-AzDoAuthHeaders { | ||
| $tokenOutput = az account get-access-token --resource $AzDoResourceId --query accessToken -o tsv --only-show-errors | ||
| if ($LASTEXITCODE -ne 0) { | ||
| return $null | ||
| } | ||
| $token = $tokenOutput.Trim() | ||
| return @{ Authorization = "Bearer $token" } | ||
| } | ||
|
|
||
| function Test-Prerequisites { | ||
| $errors = @() | ||
|
|
||
| # Check gh CLI | ||
| if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { | ||
| $errors += "``gh`` CLI is not installed. Install from https://cli.github.com/" | ||
| } | ||
| else { | ||
| # Check status code while suppressing all output | ||
| $null = gh auth status 2>&1 | ||
|
marcpopMSFT marked this conversation as resolved.
|
||
| if ($LASTEXITCODE -ne 0) { | ||
| $errors += "``gh`` CLI is not authenticated. Run ``gh auth login``." | ||
| } | ||
| } | ||
|
|
||
| # Check az CLI and AzDo access | ||
| if (-not (Get-Command az -ErrorAction SilentlyContinue)) { | ||
| $errors += "``az`` CLI is not installed. Install from https://aka.ms/install-az-cli" | ||
| } | ||
| else { | ||
| $script:AzDoHeaders = Get-AzDoAuthHeaders | ||
| if (-not $script:AzDoHeaders) { | ||
| $errors += "Cannot acquire Azure DevOps token. Run ``az login`` and ensure you have access to the dnceng organization." | ||
| } | ||
| else { | ||
| # Probe actual AzDo access | ||
| $probeUrl = "$AzDoOrg/internal/_apis/git/repositories?api-version=7.1&`$top=1" | ||
| try { | ||
| $null = Invoke-RestMethod -Uri $probeUrl -Headers $script:AzDoHeaders -Method Get | ||
| } | ||
| catch { | ||
| $errors += "Cannot access Azure DevOps (dnceng/internal). Run ``az login`` and ensure you have access to the dnceng organization." | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if ($errors.Count -gt 0) { | ||
| Write-Output "## Prerequisites Failed" | ||
| Write-Output "" | ||
| foreach ($e in $errors) { | ||
| Write-Output "- $e" | ||
| } | ||
| exit 1 | ||
| } | ||
| } | ||
|
|
||
| function Get-GitHubLatestCommit { | ||
| param( | ||
| [string]$Org, | ||
| [string]$Repo, | ||
| [string]$Branch | ||
| ) | ||
|
|
||
| $rawJson = gh api "repos/$Org/$Repo/commits?sha=$Branch&per_page=1" 2>&1 | ||
| if ($LASTEXITCODE -ne 0) { | ||
| throw "GitHub API error for $Org/$Repo ($Branch): $rawJson" | ||
| } | ||
| $commits = @($rawJson | ConvertFrom-Json) | ||
| if ($commits.Count -eq 0) { | ||
| throw "No commits found on $Org/$Repo ($Branch)" | ||
| } | ||
| $c = $commits[0] | ||
| return @{ | ||
| sha = $c.sha | ||
| message = $c.commit.message | ||
| } | ||
| } | ||
|
|
||
| function Get-AzDoCommits { | ||
| param( | ||
| [string]$Project, | ||
| [string]$Repo, | ||
| [string]$Branch, | ||
| [int]$Top | ||
| ) | ||
|
|
||
| # https://learn.microsoft.com/rest/api/azure/devops/git/commits/get-commits | ||
| $url = "$AzDoOrg/$Project/_apis/git/repositories/$Repo/commits" + | ||
| "?searchCriteria.itemVersion.version=$Branch" + | ||
| "&searchCriteria.itemVersion.versionType=branch" + | ||
| "&`$top=$Top" + | ||
| "&api-version=7.1" | ||
|
marcpopMSFT marked this conversation as resolved.
|
||
|
|
||
| try { | ||
| $result = Invoke-RestMethod -Uri $url -Headers $script:AzDoHeaders -Method Get | ||
| } | ||
| catch { | ||
| throw "AzDo API error for $Project/$Repo ($Branch): $_" | ||
| } | ||
| return @($result.value) | ||
| } | ||
|
|
||
| function Test-MirrorStatus { | ||
| param( | ||
| [hashtable]$Mapping, | ||
| [string]$GitHubSha | ||
| ) | ||
|
|
||
| $commits = Get-AzDoCommits ` | ||
| -Project $Mapping.AzDoProject ` | ||
| -Repo $Mapping.AzDoRepo ` | ||
| -Branch $Mapping.AzDoBranch ` | ||
| -Top $SearchDepth | ||
|
|
||
| if ($commits.Count -eq 0) { | ||
| return @{ Mirrored = $false; Details = "AzDo branch has no commits or does not exist" } | ||
| } | ||
|
|
||
| # Check for direct push: any AzDo commit has the same SHA as the GitHub commit | ||
| foreach ($c in $commits) { | ||
| if ($c.commitId -eq $GitHubSha) { | ||
| return @{ Mirrored = $true; Details = "Direct push (same SHA on AzDo branch)" } | ||
| } | ||
| } | ||
|
|
||
| # Check for merge commit: any AzDo commit message references the GitHub SHA | ||
| $escapedSha = [regex]::Escape($GitHubSha) | ||
| foreach ($c in $commits) { | ||
| if ($c.comment -match $escapedSha) { | ||
| return @{ Mirrored = $true; Details = "Merge commit references SHA" } | ||
| } | ||
| } | ||
|
|
||
| # Not mirrored — report the latest AzDo commit for context | ||
| $latest = $commits[0] | ||
| $latestSha = $latest.commitId.Substring(0, 7) | ||
| $latestDate = if ($latest.author.date) { ([datetime]$latest.author.date).ToString('yyyy-MM-dd HH:mm') } else { 'unknown' } | ||
| return @{ | ||
| Mirrored = $false | ||
| Details = "AzDo tip: ``$latestSha`` ($latestDate)" | ||
| } | ||
| } | ||
|
|
||
| # --- Main --- | ||
|
|
||
| Test-Prerequisites | ||
|
|
||
| $results = @() | ||
|
|
||
| foreach ($m in $Mappings) { | ||
| $repo = "$($m.GHOrg)/$($m.GHRepo)" | ||
| $branch = $m.GHBranch | ||
|
|
||
| try { | ||
| $ghCommit = Get-GitHubLatestCommit -Org $m.GHOrg -Repo $m.GHRepo -Branch $m.GHBranch | ||
| $shortSha = $ghCommit.sha.Substring(0, 7) | ||
| $firstLine = ($ghCommit.message -split '\r?\n')[0] | ||
| $commitDisplay = "``$shortSha`` $(Format-TableCell $firstLine 60)" | ||
|
|
||
| $mirror = Test-MirrorStatus -Mapping $m -GitHubSha $ghCommit.sha | ||
| $status = if ($mirror.Mirrored) { '✅' } else { '❌' } | ||
| $details = Format-TableCell $mirror.Details | ||
|
|
||
| $results += @{ Repo = $repo; Branch = $branch; Commit = $commitDisplay; Status = $status; Details = $details } | ||
| } | ||
| catch { | ||
| $errMsg = Format-TableCell "$_" | ||
| $results += @{ Repo = $repo; Branch = $branch; Commit = 'ERROR'; Status = '⚠️'; Details = $errMsg } | ||
| } | ||
| } | ||
|
|
||
| # Emit markdown table | ||
| Write-Output "## Branch Mirror Status" | ||
| Write-Output "" | ||
| Write-Output "| Repo | Branch | Latest GH Commit | Mirrored? | Details |" | ||
| Write-Output "|---|---|---|---|---|" | ||
|
|
||
| foreach ($r in $results) { | ||
| Write-Output "| $($r.Repo) | $($r.Branch) | $($r.Commit) | $($r.Status) | $($r.Details) |" | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.