From c9d6e44db27c0cb14460cc754f8a0823bddb90b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Nov 2026 11:40:59 +0000 Subject: [PATCH] fix: check git ref directly when pushing signed commits The branches API follows branch rename redirects, so a branch name that was previously renamed resolves to the renamed branch. This caused the ref to be reported as existing, and the subsequent updateRef failed with 'Reference does not exist'. Check the git ref directly instead, which is the same ref that is then created or updated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- __test__/github-helper.unit.test.ts | 70 +++++++++++++++++++++++++++-- src/github-helper.ts | 8 ++-- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/__test__/github-helper.unit.test.ts b/__test__/github-helper.unit.test.ts index 2bb2361b2a..d72306bca1 100644 --- a/__test__/github-helper.unit.test.ts +++ b/__test__/github-helper.unit.test.ts @@ -7,7 +7,9 @@ const mockOctokit = { createBlob: jest.fn(), createTree: jest.fn(), createCommit: jest.fn(), - createRef: jest.fn() + createRef: jest.fn(), + getRef: jest.fn(), + updateRef: jest.fn() }, repos: { getBranch: jest.fn() @@ -38,8 +40,12 @@ function makeCommit(changes: Commit['changes']): Commit { } describe('github-helper unit tests', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + test('pushSignedCommits deletes a removed submodule instead of pointing it at the null sha', async () => { - const {git: gitApi, repos: reposApi} = mockOctokit.rest + const {git: gitApi} = mockOctokit.rest gitApi.createBlob.mockResolvedValue({ data: {sha: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'} }) @@ -54,7 +60,7 @@ describe('github-helper unit tests', () => { } }) gitApi.createRef.mockResolvedValue({}) - reposApi.getBranch.mockRejectedValue(new Error('Branch not found')) + gitApi.getRef.mockRejectedValue(new Error('Not Found')) const git = { showFileAtRefBase64: jest.fn().mockResolvedValue('Y29udGVudA==') } @@ -115,4 +121,62 @@ describe('github-helper unit tests', () => { } ]) }) + + test('pushSignedCommits creates the ref when the branch name only resolves via a branch rename redirect', async () => { + const {git: gitApi, repos: reposApi} = mockOctokit.rest + // The branches API follows rename redirects and returns the renamed branch + reposApi.getBranch.mockResolvedValue({data: {name: 'renamed-branch'}}) + gitApi.getRef.mockRejectedValue(new Error('Not Found')) + gitApi.createRef.mockResolvedValue({}) + + const helper = new GitHubHelper('github.com', 'token') + const baseCommit = makeCommit([]) + await helper.pushSignedCommits( + {} as GitCommandManager, + [], + baseCommit, + '/repo', + 'owner/repo', + 'branch' + ) + + expect(gitApi.getRef).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + ref: 'heads/branch' + }) + expect(gitApi.createRef).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + sha: baseCommit.sha, + ref: 'refs/heads/branch' + }) + expect(gitApi.updateRef).not.toHaveBeenCalled() + }) + + test('pushSignedCommits updates the ref when the branch exists', async () => { + const {git: gitApi} = mockOctokit.rest + gitApi.getRef.mockResolvedValue({data: {ref: 'refs/heads/branch'}}) + gitApi.updateRef.mockResolvedValue({}) + + const helper = new GitHubHelper('github.com', 'token') + const baseCommit = makeCommit([]) + await helper.pushSignedCommits( + {} as GitCommandManager, + [], + baseCommit, + '/repo', + 'owner/repo', + 'branch' + ) + + expect(gitApi.updateRef).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + sha: baseCommit.sha, + ref: 'heads/branch', + force: true + }) + expect(gitApi.createRef).not.toHaveBeenCalled() + }) }) diff --git a/src/github-helper.ts b/src/github-helper.ts index e139ed0965..dfafc977ec 100644 --- a/src/github-helper.ts +++ b/src/github-helper.ts @@ -438,10 +438,12 @@ export class GitHubHelper { newHead: string ) { const repository = this.parseRepository(branchRepository) - const branchExists = await this.octokit.rest.repos - .getBranch({ + // Check the git ref directly rather than using the branches API, which + // follows branch rename redirects and can return a different branch. + const branchExists = await this.octokit.rest.git + .getRef({ ...repository, - branch: branch + ref: `heads/${branch}` }) .then( () => true,