From 253b00dde11c5e080816bb91240c4c271f00eb65 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Fri, 5 Jun 2026 08:49:23 -0700 Subject: [PATCH 1/9] Correct eng directory --- .github/skills/backport-changes/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/skills/backport-changes/SKILL.md b/.github/skills/backport-changes/SKILL.md index d01c4bf31c..9d41be76f9 100644 --- a/.github/skills/backport-changes/SKILL.md +++ b/.github/skills/backport-changes/SKILL.md @@ -26,7 +26,7 @@ disable-model-invocation: true - Dockerfile/template changes - structural changes to how images are built - Image component updates - MinGit, PowerShell, and other tools - Infrastructure and tooling changes - build scripts, CI/CD updates - - Automated `eng/common` updates - standard engineering infrastructure + - Automated `eng/docker-tools` updates - standard engineering infrastructure - Do not backport: - Version-only updates for daily/preview builds (no Dockerfile changes) - Changes already on the release branch From 68bec94cc9b391081fdc192dcb829a4eeb62cef4 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Fri, 5 Jun 2026 09:17:27 -0700 Subject: [PATCH 2/9] Improve backport-changes skill with verification and cleanup steps Add post-cherry-pick verification (Get-BackportDiff.ps1, diffs HEAD against nightly), label cleanup (Remove-BackportLabels.ps1), expected-divergence guidance, explicit PR step, and a merge-commit guideline to the workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/backport-changes/SKILL.md | 22 +++++- .../scripts/Get-BackportDiff.ps1 | 71 +++++++++++++++++++ .../scripts/Remove-BackportLabels.ps1 | 20 ++++++ 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 .github/skills/backport-changes/scripts/Get-BackportDiff.ps1 create mode 100644 .github/skills/backport-changes/scripts/Remove-BackportLabels.ps1 diff --git a/.github/skills/backport-changes/SKILL.md b/.github/skills/backport-changes/SKILL.md index 9d41be76f9..e1c7e73805 100644 --- a/.github/skills/backport-changes/SKILL.md +++ b/.github/skills/backport-changes/SKILL.md @@ -13,10 +13,13 @@ disable-model-invocation: true - The most recently created public release branch corresponds to the current release. - Do not assume `main` is the release branch. 2. **Get candidates for backport**: Run `pwsh scripts/Get-BackportPRs.ps1` to find PRs to backport. -3. **Analyze PRs** - classify each PR using the backport guidelines below and present the analysis table to the user +3. **Analyze PRs** - classify each PR using the backport guidelines below and present the analysis table to the user. 4. **Cherry-pick** - confirm the plan with the user, create a working branch based off of the public release branch, then run `git cherry-pick ` (in the order they were merged). - If any templates or manifests changed, regenerate Dockerfiles and READMEs. Confirm that the diff looks correct. -5. **Resolve conflicts** - follow the conflict resolution table below; stop and consult the user for anything not covered +5. **Resolve conflicts** - follow the conflict resolution table below; stop and consult the user for anything not covered. +6. **Verify nothing was missed** - from your working branch with all cherry-picks committed and a clean working tree, run `pwsh scripts/Get-BackportDiff.ps1` to diff `HEAD` against `nightly`. Confirm every reported difference is an expected divergence (see below). Investigate anything that is not. +7. **Open the PR** - push the working branch and open a PR targeting the public release branch. +8. **Clean up labels** - remove the `needs-backport` label from each backported PR by running `pwsh scripts/Remove-BackportLabels.ps1 -PRs `. Leave the label on PRs you intentionally did not backport. ## Backport guidelines @@ -30,6 +33,7 @@ disable-model-invocation: true - Do not backport: - Version-only updates for daily/preview builds (no Dockerfile changes) - Changes already on the release branch + - Merge commits (e.g. "Merge main to nightly") - that content reaches the release branch via the main cut, not the backport - Experimental or incomplete features - Requires extra consideration: - Daily builds of .NET or appliance images - only backport if they include Dockerfile changes beyond simple version updates. @@ -55,3 +59,17 @@ After analyzing PRs, present results in a table: | `manifest.json` | Take changes from nightly, ensure `latest` tags are on the correct (non-preview) versions, then regenerate Dockerfiles and READMEs. | | `manifest.versions.json` | Keep the latest/most up-to-date versions. | | Other files | Stop and consult the user. | + +## Expected divergences from nightly + +When verifying (step 6), the following differences between the release branch and `nightly` are expected and do **not** indicate a missed backport. Note that a single in-progress feature on `nightly` usually shows up as a *cluster* of related files (Dockerfiles, templates, tag metadata, version entries, and test baselines) — recognize the feature, don't classify file-by-file. + +- **Preview version drift** - `nightly` carries newer daily/preview build numbers that are intentionally not backported. Shows up across `src/*` Dockerfiles, `README*.md`, `eng/mcr-tags-metadata-templates/*`, and version entries in `manifest.versions.json`. +- **Appliance image versions** (aspire-dashboard, monitor, yarp) - updated separately during release staging, not via backport. New appliances still in preview (e.g. yarp) may be absent from the release branch entirely. +- **New images or distros still in development on `nightly`** - e.g. a new Linux distro like Azure Linux 4.0. These appear as a coupled set: new `src/*` Dockerfiles, structural `eng/dockerfile-templates/*` changes, new `eng/mcr-tags-metadata-templates/*` tag groups, `*|repo` entries in `manifest.versions.json`, and added/removed `VerifyInternalDockerfilesOutput` baselines. Expected only while the feature is not yet shipping in this release — otherwise investigate as a missed backport (see below). +- **Internal Dockerfile test baselines** - `tests/.../Baselines/GeneratedArtifactTests/VerifyInternalDockerfilesOutput/*` churn (added/removed/modified) tracks the image set above and the internal build flavor; expected alongside the image and template differences. +- **Nightly-only changes not yet on `main`** - content merged to `nightly` that has not reached `main` yet; the release branch matches `main` here and will sync on the next merge-main-to-nightly. +- **`manifest.versions.json` `branch` field** - `main` on the release branch vs `nightly`. +- **`manifest.json` repo remapping** - the release branch uses public repo names while `nightly` uses nightly ones (e.g. `dotnet/nightly/*` → `dotnet/*`, `core-nightly` → `core`, including the `syndicated*Repo` variables). + +Anything outside these categories should be investigated as a potential missed backport. Structural `eng/dockerfile-templates/*` changes are only expected when they are tied to a feature that is intentionally not shipping in this release; an isolated template change with no accompanying in-development feature normally **should** be backported. diff --git a/.github/skills/backport-changes/scripts/Get-BackportDiff.ps1 b/.github/skills/backport-changes/scripts/Get-BackportDiff.ps1 new file mode 100644 index 0000000000..fced2a1f05 --- /dev/null +++ b/.github/skills/backport-changes/scripts/Get-BackportDiff.ps1 @@ -0,0 +1,71 @@ +#!/usr/bin/env pwsh + +# Shows the file-level diff between HEAD and the nightly branch on the public +# (GitHub) remote. Run this from your backport working branch (with cherry-picks +# applied and committed) to verify the backport: every reported difference should +# be an expected divergence (see SKILL.md). Investigate anything that is not. +# The working tree must be clean. + +function Get-RemoteName { + param( + [Parameter(Mandatory = $true)] + [string] $UrlPattern + ) + + $matchingRemotes = @(git remote | Where-Object { + $remoteName = $_ + $remoteUrl = git remote get-url $remoteName 2>$null + $remoteUrl -match $UrlPattern + }) + + if ($matchingRemotes.Count -eq 0) { + throw "Unable to find a remote with a URL matching '$UrlPattern'." + } + + if ($matchingRemotes.Count -gt 1) { + throw "Found multiple remotes with URLs matching '$UrlPattern': $($matchingRemotes -join ', ')." + } + + return $matchingRemotes[0] +} + +# Detect the public (GitHub) remote so we can fetch and diff against its nightly. +$gitHubRemote = Get-RemoteName -UrlPattern 'github\.com[:/]dotnet/' + +# Require a clean working tree so that all cherry-picks and regenerated files are +# committed. This lets us diff HEAD (including new files) rather than the working +# tree, which would miss untracked files. +$status = git status --porcelain +if (-not [string]::IsNullOrWhiteSpace($status)) { + throw "Working tree is not clean. Commit or stash your changes before verifying the backport, then re-run this script." +} + +Write-Host "Fetching latest from '$gitHubRemote'..." +git fetch $gitHubRemote 2>&1 | Out-Null + +$nightlyRef = "$gitHubRemote/nightly" +if (-not (git rev-parse --verify --quiet "$nightlyRef")) { + throw "Branch '$nightlyRef' was not found on remote '$gitHubRemote'." +} + +Write-Host "" +Write-Host "# Backport verification diff" +Write-Host "" +Write-Host "Comparing HEAD against nightly on the public remote." +Write-Host "" +Write-Host "- Nightly: ``$nightlyRef``" +Write-Host "" +Write-Host "Every difference below should be an expected divergence (see SKILL.md)." +Write-Host "Investigate anything that is not." +Write-Host "" +Write-Host "## Changed files (HEAD vs nightly)" +Write-Host "" + +$nameStatus = git diff --name-status "$nightlyRef" HEAD +if ([string]::IsNullOrWhiteSpace($nameStatus)) { + Write-Host "_No differences._" +} else { + Write-Host '```' + Write-Host ($nameStatus -join "`n") + Write-Host '```' +} diff --git a/.github/skills/backport-changes/scripts/Remove-BackportLabels.ps1 b/.github/skills/backport-changes/scripts/Remove-BackportLabels.ps1 new file mode 100644 index 0000000000..174c78926c --- /dev/null +++ b/.github/skills/backport-changes/scripts/Remove-BackportLabels.ps1 @@ -0,0 +1,20 @@ +#!/usr/bin/env pwsh + +# Removes the 'needs-backport' label from the given pull requests in +# dotnet/dotnet-docker. Run this after successfully backporting changes to clear +# the label from PRs that have been handled. + +param( + [Parameter(Mandatory = $true)] + [int[]] $PRs +) + +$label = "needs-backport" + +foreach ($pr in $PRs) { + Write-Host "Removing '$label' from #$pr..." + gh pr edit $pr --repo dotnet/dotnet-docker --remove-label $label + if ($LASTEXITCODE -ne 0) { + Write-Warning "Failed to remove '$label' from #$pr." + } +} From c74b292f1e5662971e324f99c3a9f468a1ed4a0c Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Fri, 5 Jun 2026 09:36:47 -0700 Subject: [PATCH 3/9] Remove specific distro reference --- .github/skills/backport-changes/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/skills/backport-changes/SKILL.md b/.github/skills/backport-changes/SKILL.md index e1c7e73805..9bf4a52b29 100644 --- a/.github/skills/backport-changes/SKILL.md +++ b/.github/skills/backport-changes/SKILL.md @@ -66,9 +66,9 @@ When verifying (step 6), the following differences between the release branch an - **Preview version drift** - `nightly` carries newer daily/preview build numbers that are intentionally not backported. Shows up across `src/*` Dockerfiles, `README*.md`, `eng/mcr-tags-metadata-templates/*`, and version entries in `manifest.versions.json`. - **Appliance image versions** (aspire-dashboard, monitor, yarp) - updated separately during release staging, not via backport. New appliances still in preview (e.g. yarp) may be absent from the release branch entirely. -- **New images or distros still in development on `nightly`** - e.g. a new Linux distro like Azure Linux 4.0. These appear as a coupled set: new `src/*` Dockerfiles, structural `eng/dockerfile-templates/*` changes, new `eng/mcr-tags-metadata-templates/*` tag groups, `*|repo` entries in `manifest.versions.json`, and added/removed `VerifyInternalDockerfilesOutput` baselines. Expected only while the feature is not yet shipping in this release — otherwise investigate as a missed backport (see below). +- **New images or distros still in development on `nightly`** - e.g. support for a new pre-release Linux distro. These appear as a coupled set: new `src/*` Dockerfiles, structural `eng/dockerfile-templates/*` changes, new `eng/mcr-tags-metadata-templates/*` tag groups, `*|repo` entries in `manifest.versions.json`, and added/removed `VerifyInternalDockerfilesOutput` baselines. Expected only while the feature is not yet shipping in this release — otherwise investigate as a missed backport (see below). - **Internal Dockerfile test baselines** - `tests/.../Baselines/GeneratedArtifactTests/VerifyInternalDockerfilesOutput/*` churn (added/removed/modified) tracks the image set above and the internal build flavor; expected alongside the image and template differences. -- **Nightly-only changes not yet on `main`** - content merged to `nightly` that has not reached `main` yet; the release branch matches `main` here and will sync on the next merge-main-to-nightly. +- **Out-of-release-scope content** - files that are never part of a servicing backport, such as `samples/*` (not generated, not part of the released image set). Differences here are expected. This is **not** a catch-all for "anything only on `nightly`" — nightly content that belongs in the release (new images ready to ship, component updates, tooling/infra, structural template changes) is precisely what a backport must carry, so treat it as a potential miss unless it falls under one of the categories above. - **`manifest.versions.json` `branch` field** - `main` on the release branch vs `nightly`. - **`manifest.json` repo remapping** - the release branch uses public repo names while `nightly` uses nightly ones (e.g. `dotnet/nightly/*` → `dotnet/*`, `core-nightly` → `core`, including the `syndicated*Repo` variables). From 1529c081b30f02a98a30d62b5dceeee75facb3e7 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Fri, 5 Jun 2026 10:27:34 -0700 Subject: [PATCH 4/9] Add PR body template to backport-changes skill Add pr-body-template.md and document the PR title format (Backport changes from nightly to \) in the workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/backport-changes/SKILL.md | 2 ++ .github/skills/backport-changes/pr-body-template.md | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 .github/skills/backport-changes/pr-body-template.md diff --git a/.github/skills/backport-changes/SKILL.md b/.github/skills/backport-changes/SKILL.md index 9bf4a52b29..db17e52519 100644 --- a/.github/skills/backport-changes/SKILL.md +++ b/.github/skills/backport-changes/SKILL.md @@ -19,6 +19,8 @@ disable-model-invocation: true 5. **Resolve conflicts** - follow the conflict resolution table below; stop and consult the user for anything not covered. 6. **Verify nothing was missed** - from your working branch with all cherry-picks committed and a clean working tree, run `pwsh scripts/Get-BackportDiff.ps1` to diff `HEAD` against `nightly`. Confirm every reported difference is an expected divergence (see below). Investigate anything that is not. 7. **Open the PR** - push the working branch and open a PR targeting the public release branch. + - Title: `Backport changes from nightly to $targetReleaseBranch` (e.g. `Backport changes from nightly to release/2026-05B`). + - Body: use [pr-body-template.md](pr-body-template.md), replacing the example PR numbers with the list of PRs you backported (one `- #` per line). 8. **Clean up labels** - remove the `needs-backport` label from each backported PR by running `pwsh scripts/Remove-BackportLabels.ps1 -PRs `. Leave the label on PRs you intentionally did not backport. ## Backport guidelines diff --git a/.github/skills/backport-changes/pr-body-template.md b/.github/skills/backport-changes/pr-body-template.md new file mode 100644 index 0000000000..37554b490f --- /dev/null +++ b/.github/skills/backport-changes/pr-body-template.md @@ -0,0 +1,4 @@ +This pull request backports the following changes: + +- #1234 +- #2345 From 8644d6501351470b91d6097407f233e131f5fe88 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Fri, 5 Jun 2026 11:08:36 -0700 Subject: [PATCH 5/9] Require draft PR and full-changeset approval in backport-changes skill Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/backport-changes/SKILL.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/skills/backport-changes/SKILL.md b/.github/skills/backport-changes/SKILL.md index db17e52519..2edc3f5ad3 100644 --- a/.github/skills/backport-changes/SKILL.md +++ b/.github/skills/backport-changes/SKILL.md @@ -18,9 +18,10 @@ disable-model-invocation: true - If any templates or manifests changed, regenerate Dockerfiles and READMEs. Confirm that the diff looks correct. 5. **Resolve conflicts** - follow the conflict resolution table below; stop and consult the user for anything not covered. 6. **Verify nothing was missed** - from your working branch with all cherry-picks committed and a clean working tree, run `pwsh scripts/Get-BackportDiff.ps1` to diff `HEAD` against `nightly`. Confirm every reported difference is an expected divergence (see below). Investigate anything that is not. -7. **Open the PR** - push the working branch and open a PR targeting the public release branch. +7. **Open the PR** - confirm the full changeset (commits, diff, title, and body) with the user, then push the working branch and open a **draft** PR targeting the public release branch (`gh pr create --draft --base `). - Title: `Backport changes from nightly to $targetReleaseBranch` (e.g. `Backport changes from nightly to release/2026-05B`). - Body: use [pr-body-template.md](pr-body-template.md), replacing the example PR numbers with the list of PRs you backported (one `- #` per line). + - Do not push or submit the PR until the user approves the entire changeset. 8. **Clean up labels** - remove the `needs-backport` label from each backported PR by running `pwsh scripts/Remove-BackportLabels.ps1 -PRs `. Leave the label on PRs you intentionally did not backport. ## Backport guidelines From 8fafe255d274ca7917dcbc5f31865ae8ce9b2b49 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Fri, 5 Jun 2026 11:15:57 -0700 Subject: [PATCH 6/9] Split confirm/PR into separate steps --- .github/skills/backport-changes/SKILL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/skills/backport-changes/SKILL.md b/.github/skills/backport-changes/SKILL.md index 2edc3f5ad3..5763859ab1 100644 --- a/.github/skills/backport-changes/SKILL.md +++ b/.github/skills/backport-changes/SKILL.md @@ -18,11 +18,11 @@ disable-model-invocation: true - If any templates or manifests changed, regenerate Dockerfiles and READMEs. Confirm that the diff looks correct. 5. **Resolve conflicts** - follow the conflict resolution table below; stop and consult the user for anything not covered. 6. **Verify nothing was missed** - from your working branch with all cherry-picks committed and a clean working tree, run `pwsh scripts/Get-BackportDiff.ps1` to diff `HEAD` against `nightly`. Confirm every reported difference is an expected divergence (see below). Investigate anything that is not. -7. **Open the PR** - confirm the full changeset (commits, diff, title, and body) with the user, then push the working branch and open a **draft** PR targeting the public release branch (`gh pr create --draft --base `). +7. **Confirm changes** - confirm the full set of changes with the user. If the user requests additional changes, get confirmation again before moving to the next step. +8. **Open a draft PR** - push the working branch and open a **draft** PR targeting the public release branch (`gh pr create --draft --base `). - Title: `Backport changes from nightly to $targetReleaseBranch` (e.g. `Backport changes from nightly to release/2026-05B`). - Body: use [pr-body-template.md](pr-body-template.md), replacing the example PR numbers with the list of PRs you backported (one `- #` per line). - - Do not push or submit the PR until the user approves the entire changeset. -8. **Clean up labels** - remove the `needs-backport` label from each backported PR by running `pwsh scripts/Remove-BackportLabels.ps1 -PRs `. Leave the label on PRs you intentionally did not backport. +9. **Clean up labels** - remove the `needs-backport` label from each backported PR by running `pwsh scripts/Remove-BackportLabels.ps1 -PRs `. Leave the label on PRs you intentionally did not backport. ## Backport guidelines From 24ca06ac89f5586bfdd5c24319c0c825f73e052a Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Fri, 5 Jun 2026 11:18:02 -0700 Subject: [PATCH 7/9] Add standardized working-branch step to backport-changes skill Create the working branch as step 2 (right after determining the release branch), and move plan confirmation to the cherry-pick step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/backport-changes/SKILL.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/skills/backport-changes/SKILL.md b/.github/skills/backport-changes/SKILL.md index 5763859ab1..23f4229f33 100644 --- a/.github/skills/backport-changes/SKILL.md +++ b/.github/skills/backport-changes/SKILL.md @@ -12,17 +12,18 @@ disable-model-invocation: true 1. **Determine the release branch**: Run `pwsh ../shared/Get-ReleaseBranches.ps1` to find the latest public release branch. - The most recently created public release branch corresponds to the current release. - Do not assume `main` is the release branch. -2. **Get candidates for backport**: Run `pwsh scripts/Get-BackportPRs.ps1` to find PRs to backport. -3. **Analyze PRs** - classify each PR using the backport guidelines below and present the analysis table to the user. -4. **Cherry-pick** - confirm the plan with the user, create a working branch based off of the public release branch, then run `git cherry-pick ` (in the order they were merged). +2. **Create a working branch** - fetch the public remote and create a new branch based on the up-to-date release branch tip (e.g. `git fetch ` then `git switch -c backport-$releaseName /release/$releaseName`, naming the branch `backport-2026-05B`). +3. **Get candidates for backport**: Run `pwsh scripts/Get-BackportPRs.ps1` to find PRs to backport. +4. **Analyze PRs** - classify each PR using the backport guidelines below and present the analysis table to the user. +5. **Cherry-pick** - confirm the backport plan with the user, then run `git cherry-pick ` (in the order they were merged). - If any templates or manifests changed, regenerate Dockerfiles and READMEs. Confirm that the diff looks correct. -5. **Resolve conflicts** - follow the conflict resolution table below; stop and consult the user for anything not covered. -6. **Verify nothing was missed** - from your working branch with all cherry-picks committed and a clean working tree, run `pwsh scripts/Get-BackportDiff.ps1` to diff `HEAD` against `nightly`. Confirm every reported difference is an expected divergence (see below). Investigate anything that is not. -7. **Confirm changes** - confirm the full set of changes with the user. If the user requests additional changes, get confirmation again before moving to the next step. -8. **Open a draft PR** - push the working branch and open a **draft** PR targeting the public release branch (`gh pr create --draft --base `). +6. **Resolve conflicts** - follow the conflict resolution table below; stop and consult the user for anything not covered. +7. **Verify nothing was missed** - from your working branch with all cherry-picks committed and a clean working tree, run `pwsh scripts/Get-BackportDiff.ps1` to diff `HEAD` against `nightly`. Confirm every reported difference is an expected divergence (see below). Investigate anything that is not. +8. **Confirm changes** - confirm the full set of changes with the user. If the user requests additional changes, get confirmation again before moving to the next step. +9. **Open a draft PR** - push the working branch and open a **draft** PR targeting the public release branch (`gh pr create --draft --base `). - Title: `Backport changes from nightly to $targetReleaseBranch` (e.g. `Backport changes from nightly to release/2026-05B`). - Body: use [pr-body-template.md](pr-body-template.md), replacing the example PR numbers with the list of PRs you backported (one `- #` per line). -9. **Clean up labels** - remove the `needs-backport` label from each backported PR by running `pwsh scripts/Remove-BackportLabels.ps1 -PRs `. Leave the label on PRs you intentionally did not backport. +10. **Clean up labels** - remove the `needs-backport` label from each backported PR by running `pwsh scripts/Remove-BackportLabels.ps1 -PRs `. Leave the label on PRs you intentionally did not backport. ## Backport guidelines From 680272c904e858e9827d7143be0afbd60a9d4e05 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Fri, 5 Jun 2026 11:36:43 -0700 Subject: [PATCH 8/9] Add script/instructions for creating new working branch --- .github/skills/backport-changes/SKILL.md | 22 ++++---- .../scripts/Get-BackportDiff.ps1 | 25 +-------- .../scripts/New-BackportBranch.ps1 | 53 +++++++++++++++++++ .github/skills/shared/Get-ReleaseBranches.ps1 | 23 +------- .github/skills/shared/GitHelpers.ps1 | 51 ++++++++++++++++++ 5 files changed, 117 insertions(+), 57 deletions(-) create mode 100644 .github/skills/backport-changes/scripts/New-BackportBranch.ps1 create mode 100644 .github/skills/shared/GitHelpers.ps1 diff --git a/.github/skills/backport-changes/SKILL.md b/.github/skills/backport-changes/SKILL.md index 23f4229f33..5f3a4e02b4 100644 --- a/.github/skills/backport-changes/SKILL.md +++ b/.github/skills/backport-changes/SKILL.md @@ -9,21 +9,19 @@ disable-model-invocation: true ## Workflow -1. **Determine the release branch**: Run `pwsh ../shared/Get-ReleaseBranches.ps1` to find the latest public release branch. - - The most recently created public release branch corresponds to the current release. - - Do not assume `main` is the release branch. -2. **Create a working branch** - fetch the public remote and create a new branch based on the up-to-date release branch tip (e.g. `git fetch ` then `git switch -c backport-$releaseName /release/$releaseName`, naming the branch `backport-2026-05B`). -3. **Get candidates for backport**: Run `pwsh scripts/Get-BackportPRs.ps1` to find PRs to backport. -4. **Analyze PRs** - classify each PR using the backport guidelines below and present the analysis table to the user. -5. **Cherry-pick** - confirm the backport plan with the user, then run `git cherry-pick ` (in the order they were merged). +1. **Set up the working branch**: Run `pwsh scripts/New-BackportBranch.ps1` to automatically create a new branch based on the latest release branch. + - Pass `-ReleaseBranch release/` to target a specific release branch instead of the latest. +2. **Get candidates for backport**: Run `pwsh scripts/Get-BackportPRs.ps1` to find PRs to backport. +3. **Analyze PRs** - classify each PR using the backport guidelines below and present the analysis table to the user. +4. **Cherry-pick** - confirm the backport plan with the user, then run `git cherry-pick ` (in the order they were merged). - If any templates or manifests changed, regenerate Dockerfiles and READMEs. Confirm that the diff looks correct. -6. **Resolve conflicts** - follow the conflict resolution table below; stop and consult the user for anything not covered. -7. **Verify nothing was missed** - from your working branch with all cherry-picks committed and a clean working tree, run `pwsh scripts/Get-BackportDiff.ps1` to diff `HEAD` against `nightly`. Confirm every reported difference is an expected divergence (see below). Investigate anything that is not. -8. **Confirm changes** - confirm the full set of changes with the user. If the user requests additional changes, get confirmation again before moving to the next step. -9. **Open a draft PR** - push the working branch and open a **draft** PR targeting the public release branch (`gh pr create --draft --base `). +5. **Resolve conflicts** - follow the conflict resolution table below; stop and consult the user for anything not covered. +6. **Verify nothing was missed** - from your working branch with all cherry-picks committed and a clean working tree, run `pwsh scripts/Get-BackportDiff.ps1` to diff `HEAD` against `nightly`. Confirm every reported difference is an expected divergence (see below). Investigate anything that is not. +7. **Confirm changes** - confirm the full set of changes with the user. If the user requests additional changes, get confirmation again before moving to the next step. +8. **Open a draft PR** - push the working branch and open a **draft** PR targeting the public release branch (`gh pr create --draft --base `). - Title: `Backport changes from nightly to $targetReleaseBranch` (e.g. `Backport changes from nightly to release/2026-05B`). - Body: use [pr-body-template.md](pr-body-template.md), replacing the example PR numbers with the list of PRs you backported (one `- #` per line). -10. **Clean up labels** - remove the `needs-backport` label from each backported PR by running `pwsh scripts/Remove-BackportLabels.ps1 -PRs `. Leave the label on PRs you intentionally did not backport. +9. **Clean up labels** - remove the `needs-backport` label from each backported PR by running `pwsh scripts/Remove-BackportLabels.ps1 -PRs `. Leave the label on PRs you intentionally did not backport. ## Backport guidelines diff --git a/.github/skills/backport-changes/scripts/Get-BackportDiff.ps1 b/.github/skills/backport-changes/scripts/Get-BackportDiff.ps1 index fced2a1f05..61823bb0a5 100644 --- a/.github/skills/backport-changes/scripts/Get-BackportDiff.ps1 +++ b/.github/skills/backport-changes/scripts/Get-BackportDiff.ps1 @@ -6,31 +6,10 @@ # be an expected divergence (see SKILL.md). Investigate anything that is not. # The working tree must be clean. -function Get-RemoteName { - param( - [Parameter(Mandatory = $true)] - [string] $UrlPattern - ) - - $matchingRemotes = @(git remote | Where-Object { - $remoteName = $_ - $remoteUrl = git remote get-url $remoteName 2>$null - $remoteUrl -match $UrlPattern - }) - - if ($matchingRemotes.Count -eq 0) { - throw "Unable to find a remote with a URL matching '$UrlPattern'." - } - - if ($matchingRemotes.Count -gt 1) { - throw "Found multiple remotes with URLs matching '$UrlPattern': $($matchingRemotes -join ', ')." - } - - return $matchingRemotes[0] -} +. "$PSScriptRoot/../../shared/GitHelpers.ps1" # Detect the public (GitHub) remote so we can fetch and diff against its nightly. -$gitHubRemote = Get-RemoteName -UrlPattern 'github\.com[:/]dotnet/' +$gitHubRemote = Get-PublicRemoteName # Require a clean working tree so that all cherry-picks and regenerated files are # committed. This lets us diff HEAD (including new files) rather than the working diff --git a/.github/skills/backport-changes/scripts/New-BackportBranch.ps1 b/.github/skills/backport-changes/scripts/New-BackportBranch.ps1 new file mode 100644 index 0000000000..10eaa6b70a --- /dev/null +++ b/.github/skills/backport-changes/scripts/New-BackportBranch.ps1 @@ -0,0 +1,53 @@ +#!/usr/bin/env pwsh + +# Determines the latest public release branch and creates a working branch for +# backporting based on it. Combines steps 1 and 2 of the backport workflow: +# detect the public remote, fetch it, find the latest release branch, and create +# a `backport-` branch off its up-to-date tip. + +[CmdletBinding()] +param( + # Optional release branch override (e.g. "release/2026-05B"). Defaults to the + # most recently created public release branch. + [string] $ReleaseBranch +) + +. "$PSScriptRoot/../../shared/GitHelpers.ps1" + +# Require a clean working tree so uncommitted changes are not carried onto the +# new branch. +$status = git status --porcelain +if (-not [string]::IsNullOrWhiteSpace($status)) { + throw "Working tree is not clean. Commit or stash your changes before creating the backport branch." +} + +if ([string]::IsNullOrWhiteSpace($ReleaseBranch)) { + # Get-LatestPublicReleaseBranch resolves the public remote, fetches it, and + # returns a remote-qualified ref (e.g. "upstream/release/2026-06B"). + $startPoint = Get-LatestPublicReleaseBranch + Write-Host "Latest public release branch: $startPoint" +} else { + $remote = Get-PublicRemoteName + Write-Host "Fetching latest from '$remote'..." + git fetch $remote 2>&1 | Out-Null + $startPoint = "$remote/$ReleaseBranch" +} + +if (-not (git rev-parse --verify --quiet "$startPoint")) { + throw "Start point '$startPoint' was not found on the public remote." +} + +$releaseName = $startPoint -replace '^.*release/', '' +$workingBranch = "backport-$releaseName" + +if (git rev-parse --verify --quiet "refs/heads/$workingBranch") { + throw "Branch '$workingBranch' already exists. Delete it or check it out before re-running." +} + +# Use --no-track so the working branch does not adopt the public release branch +# as its upstream. +git switch --no-track -c $workingBranch $startPoint + +Write-Host "" +Write-Host "Created working branch '$workingBranch' based on '$startPoint'." +Write-Host "Next: get backport candidates with scripts/Get-BackportPRs.ps1." diff --git a/.github/skills/shared/Get-ReleaseBranches.ps1 b/.github/skills/shared/Get-ReleaseBranches.ps1 index 8545b40371..c394087084 100755 --- a/.github/skills/shared/Get-ReleaseBranches.ps1 +++ b/.github/skills/shared/Get-ReleaseBranches.ps1 @@ -4,28 +4,7 @@ # by fetching from GitHub (public) and Azure DevOps (internal) and listing by # branch creation date. -function Get-RemoteName { - param( - [Parameter(Mandatory = $true)] - [string] $UrlPattern - ) - - $matchingRemotes = @(git remote | Where-Object { - $remoteName = $_ - $remoteUrl = git remote get-url $remoteName 2>$null - $remoteUrl -match $UrlPattern - }) - - if ($matchingRemotes.Count -eq 0) { - throw "Unable to find a remote with a URL matching '$UrlPattern'." - } - - if ($matchingRemotes.Count -gt 1) { - throw "Found multiple remotes with URLs matching '$UrlPattern': $($matchingRemotes -join ', ')." - } - - return $matchingRemotes[0] -} +. "$PSScriptRoot/GitHelpers.ps1" # Show basic documentation Write-Host "# Release branches" diff --git a/.github/skills/shared/GitHelpers.ps1 b/.github/skills/shared/GitHelpers.ps1 new file mode 100644 index 0000000000..9b52a1816c --- /dev/null +++ b/.github/skills/shared/GitHelpers.ps1 @@ -0,0 +1,51 @@ +#!/usr/bin/env pwsh + +# Shared git helpers for the release/backport skills. Dot-source this file: +# . "$PSScriptRoot/GitHelpers.ps1" + +# Finds the single git remote whose URL matches the given pattern. +function Get-RemoteName { + param( + [Parameter(Mandatory = $true)] + [string] $UrlPattern + ) + + $matchingRemotes = @(git remote | Where-Object { + $remoteName = $_ + $remoteUrl = git remote get-url $remoteName 2>$null + $remoteUrl -match $UrlPattern + }) + + if ($matchingRemotes.Count -eq 0) { + throw "Unable to find a remote with a URL matching '$UrlPattern'." + } + + if ($matchingRemotes.Count -gt 1) { + throw "Found multiple remotes with URLs matching '$UrlPattern': $($matchingRemotes -join ', ')." + } + + return $matchingRemotes[0] +} + +# Finds the public (GitHub dotnet) remote. +function Get-PublicRemoteName { + return Get-RemoteName -UrlPattern 'github\.com[:/]dotnet/' +} + +# Fetches the public remote and returns the most recently created public release +# branch as a remote-qualified ref (e.g. "upstream/release/2026-06B"). The remote +# prefix is kept intentionally so callers use the remote-tracking branch rather +# than a possibly-stale local branch. +function Get-LatestPublicReleaseBranch { + $remote = Get-PublicRemoteName + git fetch $remote 2>&1 | Out-Null + + $branch = git branch -r --list "$remote/release/*" --sort=-creatordate ` + | Select-Object -First 1 + + if ([string]::IsNullOrWhiteSpace($branch)) { + throw "No release branches found on remote '$remote'." + } + + return $branch.Trim() +} From ad8320c953c2814bef4c9f054089f51e6ef211e6 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Fri, 5 Jun 2026 11:40:57 -0700 Subject: [PATCH 9/9] Include branch date/timestamp --- .../skills/backport-changes/scripts/New-BackportBranch.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/skills/backport-changes/scripts/New-BackportBranch.ps1 b/.github/skills/backport-changes/scripts/New-BackportBranch.ps1 index 10eaa6b70a..7ba105bc3e 100644 --- a/.github/skills/backport-changes/scripts/New-BackportBranch.ps1 +++ b/.github/skills/backport-changes/scripts/New-BackportBranch.ps1 @@ -38,7 +38,9 @@ if (-not (git rev-parse --verify --quiet "$startPoint")) { } $releaseName = $startPoint -replace '^.*release/', '' -$workingBranch = "backport-$releaseName" +# Include a timestamp so re-running the skill always produces a distinct branch name. +$timestamp = Get-Date -Format 'yyyyMMdd-HHmmss' +$workingBranch = "backport-$releaseName-$timestamp" if (git rev-parse --verify --quiet "refs/heads/$workingBranch") { throw "Branch '$workingBranch' already exists. Delete it or check it out before re-running."