diff --git a/.agents/skills/merge-skia-update/SKILL.md b/.agents/skills/merge-skia-update/SKILL.md index 026ae0c046a9..e13f7302a362 100644 --- a/.agents/skills/merge-skia-update/SKILL.md +++ b/.agents/skills/merge-skia-update/SKILL.md @@ -46,32 +46,39 @@ Only after the maintainer sees the exact selected pair, ask them to confirm: If they do not confirm, stop. Do not independently re-audit reviews, labels, milestones, CI, packages, DEPS, bindings, or release notes. -## 2. Preserve the previous release line when targeting main +## 2. Preserve the previous release line for milestone bumps Run the script with the branch names already resolved by the AI: ```powershell pwsh .agents/skills/merge-skia-update/scripts/Prepare-SkiaReleaseBranches.ps1 ` -SkiaSharpBaseBranch ` + -SkiaSharpHeadBranch ` -SkiaBaseBranch ``` The script determines the release action from the parent base branch: -- `main`: reads the committed SkiaSharp package version from - `scripts/VERSIONS.txt` and derives `release/A.B.x` for the current line; +- `main`: compares the committed `chrome_milestone` on the parent base and head; + when the milestone increases, it reads the base's committed SkiaSharp package + version from `scripts/VERSIONS.txt` and derives `release/A.B.x` for the + previous line; +- `main` with no milestone change: reports a same-milestone sync and exits + without creating refs; - `release/A.B.x`: reports a servicing sync and exits without creating refs. -The script always defaults to a dry run. When targeting `main`, it reads the -current SkiaSharp base tip and the exact mono/skia commit referenced by its -`externals/skia` gitlink. It requires the supplied mono/skia base branch to -point at that commit and preflights the derived release branch in both +The script always defaults to a dry run. For a milestone bump targeting `main`, +it reads the current SkiaSharp base tip and the exact mono/skia commit referenced +by its `externals/skia` gitlink. It requires the supplied mono/skia base branch +to point at that commit and preflights the derived release branch in both repositories. -Show the output and obtain confirmation. Rerun with the same inputs plus -`-Push`. The script checks the source and destination refs again, creates the -mono/skia release branch first, then the mono/SkiaSharp release branch, and -verifies both. It never moves an existing branch. +When the script reports a milestone bump, show the output and obtain +confirmation. Rerun with the same inputs plus `-Push`. The script checks the +source and destination refs again, creates the mono/skia release branch first, +then the mono/SkiaSharp release branch, and verifies both. It never moves an +existing branch. When it reports a same-milestone or servicing sync, continue +without a push confirmation. ## 3. Prepare the mono/skia merge @@ -136,8 +143,8 @@ and body to the maintainer. Tell the maintainer to merge it manually using the repository's normal merge method. Do not wait for another PR CI run: the repin script proved that the -merged mono/skia tree is identical to the reviewed PR tree. Main CI validates -the resulting merge. +merged mono/skia tree is identical to the reviewed PR tree. CI on the resolved +parent base branch validates the resulting merge. Stop. Continue only after the maintainer says mono/SkiaSharp is merged. @@ -146,12 +153,12 @@ Stop. Continue only after the maintainer says mono/SkiaSharp is merged. Confirm: - both PRs are merged; -- mono/SkiaSharp main points its gitlink and cgmanifest at the actual mono/skia - merge commit; -- main CI has started for the resulting SkiaSharp commit. +- the resolved mono/SkiaSharp parent base branch points its gitlink and + cgmanifest at the actual mono/skia merge commit; +- CI has started on that parent base branch for the resulting SkiaSharp commit. Report both merged SHAs and the preserved release branch, if created. Do not -wait for main CI to finish. +wait for the parent base branch CI to finish. ## Stop conditions @@ -166,6 +173,7 @@ Stop when: - a supplied native base branch does not match the parent base gitlink; - an existing release branch points at a different SHA; - a release source changes between dry run and `-Push`; +- the parent head milestone regresses; - mono/skia was not merged with a two-parent merge commit; - that merge does not contain the parent PR's reviewed native commit; - the merged and reviewed native trees differ; diff --git a/.agents/skills/merge-skia-update/evals/evals.json b/.agents/skills/merge-skia-update/evals/evals.json index 92f4087df180..0579c4e3b360 100644 --- a/.agents/skills/merge-skia-update/evals/evals.json +++ b/.agents/skills/merge-skia-update/evals/evals.json @@ -9,7 +9,7 @@ "expectations": [ "Does not repeat the deep review.", "Verifies that the two supplied PRs reciprocally link to each other.", - "Passes AI-resolved branch names to Prepare-SkiaReleaseBranches.ps1 without -Push before applying.", + "Passes AI-resolved parent base/head and native base branch names to Prepare-SkiaReleaseBranches.ps1 without -Push before applying.", "Invokes pr-commit-message for mono/skia.", "Does not merge either PR." ] @@ -41,16 +41,29 @@ { "id": 3, "prompt": "The M160 bump is approved. Create the previous release branches before I merge.", - "expected_output": "The AI passes the resolved main and native base branches; the script derives release/A.B.x from the current SkiaSharp package version committed on main, then uses the parent base tip and its exact externals/skia gitlink to create matching branches, native first, after a confirmed dry run.", + "expected_output": "The AI passes the resolved parent base/head and native base branches; the script verifies that the parent milestone increases, derives release/A.B.x from the current SkiaSharp package version committed on main, then uses the parent base tip and its exact externals/skia gitlink to create matching branches, native first, after a confirmed dry run.", "files": [], "expectations": [ - "The AI resolves the base branches but does not calculate or pass the release line.", + "The AI resolves the parent base/head and native base branches but does not calculate or pass the release line.", + "The script confirms that chrome_milestone increases from the parent base to the parent head.", "The script derives release/A.B.x from scripts/VERSIONS.txt on the exact parent base commit.", "Uses the native SHA referenced by the parent base.", "Preflights both destinations before either write.", "Never moves an existing release branch." ] }, + { + "id": 11, + "prompt": "This approved Skia update targets main but stays on the current milestone. Prepare the merge.", + "expected_output": "Passes the resolved parent base/head and native base branches to the preparation script, which compares their committed chrome_milestone values, reports a same-milestone sync, creates no release refs, and continues to the native merge-message handoff.", + "files": [], + "expectations": [ + "Does not create a release branch for a same-milestone main update.", + "Does not ask for a release-branch push confirmation.", + "Generates the native merge message.", + "Does not merge the native PR." + ] + }, { "id": 4, "prompt": "This is an approved same-milestone servicing sync. Prepare the merge.", @@ -115,12 +128,12 @@ { "id": 8, "prompt": "The repin is pushed. Wait for all PR CI before giving me the SkiaSharp merge message.", - "expected_output": "Generates the SkiaSharp merge message without waiting for another PR CI run because tree equality proved the native content unchanged; it notes that main CI validates the resulting merge.", + "expected_output": "Generates the SkiaSharp merge message without waiting for another PR CI run because tree equality proved the native content unchanged; it notes that CI on the resolved parent base branch validates the resulting merge.", "files": [], "expectations": [ "Does not rerun the deep review.", "Does not wait for refreshed PR CI.", - "Generates the parent merge message.", + "Generates the parent merge message and refers to CI on the resolved parent base branch rather than assuming main.", "Leaves the parent merge to the maintainer." ] } diff --git a/.agents/skills/merge-skia-update/scripts/Prepare-SkiaReleaseBranches.ps1 b/.agents/skills/merge-skia-update/scripts/Prepare-SkiaReleaseBranches.ps1 index 478b395351c2..3cf2e9ed81b7 100644 --- a/.agents/skills/merge-skia-update/scripts/Prepare-SkiaReleaseBranches.ps1 +++ b/.agents/skills/merge-skia-update/scripts/Prepare-SkiaReleaseBranches.ps1 @@ -6,6 +6,9 @@ param( [Parameter(Mandatory)] [string] $SkiaSharpBaseBranch, + [Parameter(Mandatory)] + [string] $SkiaSharpHeadBranch, + [Parameter(Mandatory)] [string] $SkiaBaseBranch, @@ -35,6 +38,29 @@ function Get-GitHubRepository { return $match.Groups['repository'].Value } +function Get-SkiaMilestone { + param([string] $Commit) + + $manifestText = git show "${Commit}:cgmanifest.json" | Out-String + $manifest = $manifestText | ConvertFrom-Json + $registrations = @( + $manifest.registrations | Where-Object { + $_.component.type -eq 'other' -and + $_.component.other.name -eq 'skia' -and + $null -ne $_.chrome_milestone + } + ) + if ($registrations.Count -ne 1) { + throw "Cannot derive the Skia milestone from cgmanifest.json at $Commit." + } + + $milestone = [int] $registrations[0].chrome_milestone + if ($milestone -le 0) { + throw "Invalid Skia milestone '$($registrations[0].chrome_milestone)' in cgmanifest.json at $Commit." + } + return $milestone +} + function Assert-Destination { param( [string] $Repository, @@ -59,6 +85,7 @@ function New-RemoteBranch { $repoRoot = git rev-parse --show-toplevel Set-Location $repoRoot git check-ref-format --branch $SkiaSharpBaseBranch | Out-Null +git check-ref-format --branch $SkiaSharpHeadBranch | Out-Null git check-ref-format --branch $SkiaBaseBranch | Out-Null $parentUrl = git remote get-url origin @@ -75,6 +102,20 @@ if ($SkiaSharpBaseBranch -ne 'main') { throw "SkiaSharp base branch must be main or release/A.B.x; got $SkiaSharpBaseBranch." } +git fetch --no-tags origin "+refs/heads/${SkiaSharpHeadBranch}:refs/remotes/origin/${SkiaSharpHeadBranch}" | Out-Null +$parentHeadSha = git rev-parse "refs/remotes/origin/${SkiaSharpHeadBranch}^{commit}" + +$baseMilestone = Get-SkiaMilestone $parentBaseSha +$headMilestone = Get-SkiaMilestone $parentHeadSha +if ($headMilestone -lt $baseMilestone) { + throw "Skia milestone regresses from m$baseMilestone on $SkiaSharpBaseBranch to m$headMilestone on $SkiaSharpHeadBranch." +} +if ($headMilestone -eq $baseMilestone) { + Write-Host "Same-milestone sync: m$baseMilestone -> m$headMilestone." + Write-Host 'No release branches are needed.' + exit 0 +} + $versions = (git show "${parentBaseSha}:scripts/VERSIONS.txt" | Out-String).Trim() $versionMatch = [regex]::Match($versions, '(?m)^SkiaSharp\s+nuget\s+(?\d+)\.(?\d+)\.\d+(?:[-+]\S+)?\s*$') if (-not $versionMatch.Success) { @@ -104,6 +145,7 @@ if ($nativeBranchSha -ne $nativeBaseSha) { $nativeExisting = Assert-Destination $nativeRepository $nativeUrl $ReleaseBranch $nativeBaseSha $parentExisting = Assert-Destination $parentRepository $parentUrl $ReleaseBranch $parentBaseSha +Write-Host "Milestone bump: m$baseMilestone -> m$headMilestone" Write-Host "Release branch: $ReleaseBranch" Write-Host "$nativeRepository source: $SkiaBaseBranch @ $nativeBaseSha$(if ($nativeExisting) { ' (release branch already exists)' })" Write-Host "$parentRepository source: $SkiaSharpBaseBranch @ $parentBaseSha$(if ($parentExisting) { ' (release branch already exists)' })" @@ -115,8 +157,9 @@ if (-not $Push) { # Re-read every source and destination immediately before the first write. $parentBaseNow = Get-RemoteBranchSha $parentUrl $SkiaSharpBaseBranch +$parentHeadNow = Get-RemoteBranchSha $parentUrl $SkiaSharpHeadBranch $nativeBaseNow = Get-RemoteBranchSha $nativeUrl $SkiaBaseBranch -if ($parentBaseNow -ne $parentBaseSha -or $nativeBaseNow -ne $nativeBaseSha) { +if ($parentBaseNow -ne $parentBaseSha -or $parentHeadNow -ne $parentHeadSha -or $nativeBaseNow -ne $nativeBaseSha) { throw 'A source branch changed after preflight. Run the script without -Push again.' } $nativeExisting = Assert-Destination $nativeRepository $nativeUrl $ReleaseBranch $nativeBaseSha