safe-outputs: resolve base branch from origin/HEAD and harden full patch base selection#34066
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
@copilot do a deep review of the changes |
… expected git errors Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Completed a deep review and followed up with a surgical fix in |
|
@copilot merge main and recompile |
…uts-base-branch-checkout Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done — merged |
There was a problem hiding this comment.
Pull request overview
This PR hardens safe-outputs branch/base resolution for side-repo (multi-checkout) workflows so patches and checkouts anchor to the repository’s default branch (via origin/HEAD) rather than the currently checked-out working branch, and improves full-mode patch base selection to avoid unsafe fallbacks when a suitable local base ref exists.
Changes:
- Resolve side-repo
base_branchfromrefs/remotes/origin/HEAD(default branch metadata) instead of the checked-out branch. - In
generateGitPatchfull mode, whenorigin/<base>is missing, compute merge-base against a local<base>branch when available before falling through to broader strategies. - Add/adjust regression tests for origin/HEAD behavior and the new local-base fallback in full-mode patch generation.
Show a summary per file
| File | Description |
|---|---|
| actions/setup/js/safe_outputs_handlers.test.cjs | Updates side-repo patch expectations to reflect default-branch anchoring via origin/HEAD. |
| actions/setup/js/safe_outputs_handlers.cjs | Adjusts handler commentary to match the new base-branch resolution strategy for side-repos. |
| actions/setup/js/get_base_branch.test.cjs | Adds regression tests for origin/HEAD-based default branch resolution and fallback behavior. |
| actions/setup/js/get_base_branch.cjs | Changes side-repo base-branch inference to use refs/remotes/origin/HEAD with suppressed expected git errors. |
| actions/setup/js/generate_git_patch.test.cjs | Adds a full-mode regression test ensuring local base branch fallback when origin/<base> is absent. |
| actions/setup/js/generate_git_patch.cjs | Implements full-mode merge-base fallback to local <base> when origin/<base> cannot be resolved. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 6/6 changed files
- Comments generated: 2
| it("should resolve repository default branch from origin/HEAD instead of checked-out feature branch", async () => { | ||
| const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), "gh-aw-get-base-branch-")); | ||
| try { | ||
| execSync("git init -b main", { cwd: repoDir, stdio: "pipe" }); | ||
| execSync("git config user.email 'test@example.com'", { cwd: repoDir, stdio: "pipe" }); | ||
| execSync("git config user.name 'Test User'", { cwd: repoDir, stdio: "pipe" }); | ||
| fs.writeFileSync(path.join(repoDir, "README.md"), "base\n"); | ||
| execSync("git add README.md", { cwd: repoDir, stdio: "pipe" }); | ||
| execSync("git commit -m 'base commit'", { cwd: repoDir, stdio: "pipe" }); | ||
| execSync("git checkout -b feature/FOO-123", { cwd: repoDir, stdio: "pipe" }); | ||
|
|
||
| // Simulate checkout metadata where repository default branch is main. | ||
| const mainSha = execSync("git rev-parse main", { cwd: repoDir, stdio: "pipe" }).toString().trim(); | ||
| execSync("git remote add origin https://github.com/test-owner/test-repo.git", { cwd: repoDir, stdio: "pipe" }); | ||
| execSync(`git update-ref refs/remotes/origin/main ${mainSha}`, { cwd: repoDir, stdio: "pipe" }); | ||
| execSync("git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main", { cwd: repoDir, stdio: "pipe" }); | ||
|
|
||
| const { getBaseBranch } = await import("./get_base_branch.cjs"); | ||
| const result = await getBaseBranch({ owner: "test-owner", repo: "test-repo" }, { preferCheckedOutBranch: true, cwd: repoDir }); | ||
|
|
||
| expect(result).toBe("main"); |
| * lookup (step 6b) use this instead of context.repo, which is needed for | ||
| * cross-repo scenarios where the target repo differs from the workflow repository. | ||
| * @param {{preferCheckedOutBranch?: boolean, cwd?: string}|null} [options] - Optional resolution hints. | ||
| * When preferCheckedOutBranch is true and cwd is set, git is queried directly for the | ||
| * checked-out branch in that repository before falling back to the repository default branch. | ||
| * When preferCheckedOutBranch is true and cwd is set, git is queried for | ||
| * refs/remotes/origin/HEAD in that repository to derive the repository default branch | ||
| * before falling back to payload/API-based default branch resolution. |
Reviewed #34108 against this PR. No additional fix is needed here: this PR only changes side-repo default-branch resolution and full-mode merge-base fallback, while |
|
@copilot review all pull request review comments |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Addressed the valid review feedback in |
safe-outputscould derivebase_branchfrom the currently checked-out branch in side-repo flows, causing wrong workflow checkout targets and misbased patch generation. In some multi-repo cases this cascaded into fallback diffing against an unrelated ancient merge-base, inflating patches to repository-history scale.Base branch resolution: use repo default branch metadata, not working branch
get_base_branchside-repo git path to resolve fromrefs/remotes/origin/HEAD(default branch) instead ofrev-parse --abbrev-ref HEAD.Patch base selection: reduce unsafe fallback surface in full mode
generate_git_patchfull mode, whenorigin/<base>is unavailable, merge-base now falls back to localrefs/heads/<base>before dropping to later strategies.Behavioral regression coverage
origin/HEAD -> origin/mainresolvesbase_branch=main.origin/HEADfalls back to payload default branch.origin/<base>is absent.