From 13aef6ea5502b088ea1066add848323ad1db4871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 22 May 2026 10:59:27 +0200 Subject: [PATCH 1/9] Switch to using REST API for branch upserts --- src/core.ts | 234 ++++++++++++++++-------------- src/github/graphql/queries.ts | 6 +- src/test/integration/node.test.ts | 24 +++ 3 files changed, 155 insertions(+), 109 deletions(-) diff --git a/src/core.ts b/src/core.ts index ffc640a..88dff78 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,13 +1,5 @@ -import { - createCommitOnBranchQuery, - createRefMutation, - getRepositoryMetadata, - updateRefMutation, -} from "./github/graphql/queries.js"; -import type { - CreateCommitOnBranchMutationVariables, - GetRepositoryMetadataQuery, -} from "./github/graphql/generated/operations.js"; +import { getRepositoryMetadata } from "./github/graphql/queries.js"; +import type { GetRepositoryMetadataQuery } from "./github/graphql/generated/operations.js"; import { CommitFilesFromBase64Args, CommitFilesResult, @@ -21,6 +13,8 @@ const getBaseRef = (base: GitBase): string => { } else if ("tag" in base) { return `refs/tags/${base.tag}`; } else { + // For explicit commit bases we don't resolve the base oid from a ref, + // but the shared metadata query still expects a valid qualified ref name. return "HEAD"; } }; @@ -45,6 +39,76 @@ const getOidFromRef = ( return ref.target.oid; }; +const createCommit = async ({ + octokit, + owner, + repo, + baseOid, + message, + fileChanges, +}: Pick< + CommitFilesFromBase64Args, + "octokit" | "owner" | "repo" | "message" | "fileChanges" +> & { + baseOid: string; +}) => { + const normalizedMessage: CommitMessage = + typeof message === "string" + ? { + headline: message.split("\n")[0]?.trim() ?? "", + body: message.split("\n").slice(1).join("\n").trim(), + } + : message; + + const additions = await Promise.all( + (fileChanges.additions ?? []).map(async ({ path, contents }) => { + const blob = await octokit.rest.git.createBlob({ + owner, + repo, + content: contents, + encoding: "base64", + }); + + return { + path, + mode: "100644" as const, + type: "blob" as const, + sha: blob.data.sha, + }; + }), + ); + + const deletions = (fileChanges.deletions ?? []).map(({ path }) => ({ + path, + mode: "100644" as const, + type: "blob" as const, + sha: null, + })); + + const baseCommit = await octokit.rest.git.getCommit({ + owner, + repo, + commit_sha: baseOid, + }); + + const tree = await octokit.rest.git.createTree({ + owner, + repo, + base_tree: baseCommit.data.tree.sha, + tree: [...additions, ...deletions], + }); + + return octokit.rest.git.createCommit({ + owner, + repo, + message: normalizedMessage.body?.trim() + ? `${normalizedMessage.headline}\n\n${normalizedMessage.body.trim()}` + : normalizedMessage.headline, + tree: tree.data.sha, + parents: [baseOid], + }); +}; + export const commitFilesFromBase64 = async ({ octokit, owner, @@ -70,115 +134,73 @@ export const commitFilesFromBase64 = async ({ log?.debug(`Repo info: ${JSON.stringify(info, null, 2)}`); if (!info) { - throw new Error(`Repository ${repositoryNameWithOwner} not found`); + throw new Error( + `Repository ${JSON.stringify(repositoryNameWithOwner)} not found`, + ); + } + if (!("commit" in base) && !info.baseRef) { + throw new Error(`Ref ${JSON.stringify(baseRef)} not found`); } - const repositoryId = info.id; /** * The commit oid to base the new commit on. * - * Used both to create / update the new branch (if necessary), - * and to ensure no changes have been made as we push the new commit. + * Used both to create the new commit, + * and to determine whether an existing branch can be updated. */ const baseOid = getOidFromRef(base, info.baseRef); - - let refId: string; - - if ("branch" in base && base.branch === branch) { - log?.debug(`Committing to the same branch as base: ${branch} (${baseOid})`); - // Get existing branch refId - - if (!info.baseRef) { - throw new Error(`Ref ${baseRef} not found`); - } - refId = info.baseRef.id; + const targetOid = info.targetBranch?.target?.oid ?? null; + const sameBranchBase = "branch" in base && base.branch === branch; + + let mode: "create" | "update" | "force-update"; + + if (sameBranchBase) { + mode = force ? "force-update" : "update"; + } else if (targetOid === null) { + // TODO: legit *creation* failure should be retried if `force === true` + mode = "create"; + } else if (force) { + mode = "force-update"; + } else if (targetOid === baseOid) { + mode = "update"; } else { - // Determine if the branch needs to be created or not - if (info.targetBranch?.target?.oid) { - // Branch already exists, check if it matches the base - if (info.targetBranch.target.oid !== baseOid) { - if (force) { - log?.debug( - `Branch ${branch} exists but does not match base ${baseOid}, forcing update to base`, - ); - const refIdUpdate = await updateRefMutation(octokit, { - input: { - refId: info.targetBranch.id, - oid: baseOid, - force: true, - }, - }); - - log?.debug( - `Updated branch with refId ${JSON.stringify(refIdUpdate, null, 2)}`, - ); - - const refIdStr = refIdUpdate.updateRef?.ref?.id; - - if (!refIdStr) { - throw new Error(`Failed to create branch ${branch}`); - } - - refId = refIdStr; - } else { - throw new Error( - `Branch ${branch} exists already and does not match base ${baseOid}, force is set to false`, - ); - } - } else { - log?.debug( - `Branch ${branch} already exists and matches base ${baseOid}`, - ); - refId = info.targetBranch.id; - } - } else { - // Create branch as it does not exist yet - log?.debug(`Creating branch ${branch} from commit ${baseOid}}`); - const refIdCreation = await createRefMutation(octokit, { - input: { - repositoryId, - name: `refs/heads/${branch}`, - oid: baseOid, - }, - }); - - log?.debug( - `Created branch with refId ${JSON.stringify(refIdCreation, null, 2)}`, - ); - - const refIdStr = refIdCreation.createRef?.ref?.id; + throw new Error( + `Branch ${branch} exists already and does not match base ${baseOid}, force is set to false`, + ); + } - if (!refIdStr) { - throw new Error(`Failed to create branch ${branch}`); - } + await log?.debug(`Creating commit on branch ${branch}`); + const newCommit = await createCommit({ + octokit, + owner, + repo, + baseOid, + message, + fileChanges, + }); - refId = refIdStr; - } + if (mode !== "create") { + const updatedRef = await octokit.rest.git.updateRef({ + owner, + repo, + ref: `heads/${branch}`, + sha: newCommit.data.sha, + force: mode === "force-update", + }); + + return { + refId: updatedRef.data.node_id ?? null, + }; } - const finalMessage: CommitMessage = - typeof message === "string" - ? { - headline: message.split("\n")[0]?.trim() ?? "", - body: message.split("\n").slice(1).join("\n").trim(), - } - : message; - - await log?.debug(`Creating commit on branch ${branch}`); - const createCommitMutation: CreateCommitOnBranchMutationVariables = { - input: { - branch: { - id: refId, - }, - expectedHeadOid: baseOid, - message: finalMessage, - fileChanges, - }, - }; - log?.debug(JSON.stringify(createCommitMutation, null, 2)); + const createdRef = await octokit.rest.git.createRef({ + owner, + repo, + ref: `refs/heads/${branch}`, + sha: newCommit.data.sha, + }); - const result = await createCommitOnBranchQuery(octokit, createCommitMutation); return { - refId: result.createCommitOnBranch?.ref?.id ?? null, + refId: createdRef.data.node_id ?? null, }; }; diff --git a/src/github/graphql/queries.ts b/src/github/graphql/queries.ts index 6665af2..d62fde3 100644 --- a/src/github/graphql/queries.ts +++ b/src/github/graphql/queries.ts @@ -1,6 +1,6 @@ -export type GitHubClient = { - graphql: (query: string, variables: any) => Promise; -}; +export type GitHubClient = ReturnType< + typeof import("@actions/github").getOctokit +>; import type { CreateCommitOnBranchMutation, diff --git a/src/test/integration/node.test.ts b/src/test/integration/node.test.ts index 304bfa3..dcdd1fe 100644 --- a/src/test/integration/node.test.ts +++ b/src/test/integration/node.test.ts @@ -93,6 +93,28 @@ describe("node", () => { } }; + const expectParentHasOid = async ({ + branch, + oid, + }: { + branch: string; + oid: string; + }) => { + const ref = ( + await getRefTreeQuery(octokit, { + ...REPO, + ref: `refs/heads/${branch}`, + path: "package.json", + }) + ).repository?.ref?.target; + + if (!ref || !("parents" in ref)) { + throw new Error("Unexpected result"); + } + + expect(ref.parents.nodes?.[0]?.oid).toEqual(oid); + }; + let testTargetCommit: string; /** * For tests, important that this commit is not an ancestor of TEST_TARGET_COMMIT, @@ -299,6 +321,8 @@ describe("node", () => { oid: BASIC_FILE_CHANGES_OID, }, }); + + await expectParentHasOid({ branch, oid: testTargetCommit }); }); it("cannot commit to existing branch when force is false", async () => { From 8aab6ff5802c06afb37bcfc44c2efae48d2aac0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 22 May 2026 11:28:30 +0200 Subject: [PATCH 2/9] Rollback to using graphql --- src/core.ts | 143 ++++++++++++++++++++++++---------------------------- 1 file changed, 65 insertions(+), 78 deletions(-) diff --git a/src/core.ts b/src/core.ts index 88dff78..515ceea 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,5 +1,13 @@ -import { getRepositoryMetadata } from "./github/graphql/queries.js"; -import type { GetRepositoryMetadataQuery } from "./github/graphql/generated/operations.js"; +import { + createCommitOnBranchQuery, + createRefMutation, + getRepositoryMetadata, + updateRefMutation, +} from "./github/graphql/queries.js"; +import type { + CreateCommitOnBranchMutationVariables, + GetRepositoryMetadataQuery, +} from "./github/graphql/generated/operations.js"; import { CommitFilesFromBase64Args, CommitFilesResult, @@ -41,15 +49,12 @@ const getOidFromRef = ( const createCommit = async ({ octokit, - owner, - repo, + refId, baseOid, message, fileChanges, -}: Pick< - CommitFilesFromBase64Args, - "octokit" | "owner" | "repo" | "message" | "fileChanges" -> & { +}: Pick & { + refId: string; baseOid: string; }) => { const normalizedMessage: CommitMessage = @@ -60,52 +65,15 @@ const createCommit = async ({ } : message; - const additions = await Promise.all( - (fileChanges.additions ?? []).map(async ({ path, contents }) => { - const blob = await octokit.rest.git.createBlob({ - owner, - repo, - content: contents, - encoding: "base64", - }); - - return { - path, - mode: "100644" as const, - type: "blob" as const, - sha: blob.data.sha, - }; - }), - ); - - const deletions = (fileChanges.deletions ?? []).map(({ path }) => ({ - path, - mode: "100644" as const, - type: "blob" as const, - sha: null, - })); - - const baseCommit = await octokit.rest.git.getCommit({ - owner, - repo, - commit_sha: baseOid, - }); - - const tree = await octokit.rest.git.createTree({ - owner, - repo, - base_tree: baseCommit.data.tree.sha, - tree: [...additions, ...deletions], - }); - - return octokit.rest.git.createCommit({ - owner, - repo, - message: normalizedMessage.body?.trim() - ? `${normalizedMessage.headline}\n\n${normalizedMessage.body.trim()}` - : normalizedMessage.headline, - tree: tree.data.sha, - parents: [baseOid], + return createCommitOnBranchQuery(octokit, { + input: { + branch: { + id: refId, + }, + expectedHeadOid: baseOid, + message: normalizedMessage, + fileChanges, + }, }); }; @@ -142,6 +110,8 @@ export const commitFilesFromBase64 = async ({ throw new Error(`Ref ${JSON.stringify(baseRef)} not found`); } + const resolvedBaseRef = info.baseRef; + /** * The commit oid to base the new commit on. * @@ -151,6 +121,7 @@ export const commitFilesFromBase64 = async ({ const baseOid = getOidFromRef(base, info.baseRef); const targetOid = info.targetBranch?.target?.oid ?? null; const sameBranchBase = "branch" in base && base.branch === branch; + const repositoryId = info.id; let mode: "create" | "update" | "force-update"; @@ -169,38 +140,54 @@ export const commitFilesFromBase64 = async ({ ); } + let refId: string; + + if (mode === "create") { + const createdRef = await createRefMutation(octokit, { + input: { + repositoryId, + name: `refs/heads/${branch}`, + oid: baseOid, + }, + }); + + const refIdStr = createdRef.createRef?.ref?.id; + + if (!refIdStr) { + throw new Error(`Failed to create branch ${branch}`); + } + + refId = refIdStr; + } else if (mode === "force-update") { + const updatedRef = await updateRefMutation(octokit, { + input: { + refId: sameBranchBase ? resolvedBaseRef!.id : info.targetBranch!.id, + oid: baseOid, + force: true, + }, + }); + + const refIdStr = updatedRef.updateRef?.ref?.id; + + if (!refIdStr) { + throw new Error(`Failed to update branch ${branch}`); + } + + refId = refIdStr; + } else { + refId = sameBranchBase ? resolvedBaseRef!.id : info.targetBranch!.id; + } + await log?.debug(`Creating commit on branch ${branch}`); const newCommit = await createCommit({ octokit, - owner, - repo, + refId, baseOid, message, fileChanges, }); - if (mode !== "create") { - const updatedRef = await octokit.rest.git.updateRef({ - owner, - repo, - ref: `heads/${branch}`, - sha: newCommit.data.sha, - force: mode === "force-update", - }); - - return { - refId: updatedRef.data.node_id ?? null, - }; - } - - const createdRef = await octokit.rest.git.createRef({ - owner, - repo, - ref: `refs/heads/${branch}`, - sha: newCommit.data.sha, - }); - return { - refId: createdRef.data.node_id ?? null, + refId: newCommit.createCommitOnBranch?.ref?.id ?? null, }; }; From d9e912068c50517a618f79afe7d83e2eb39b0c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 22 May 2026 12:25:06 +0200 Subject: [PATCH 3/9] Use a temp branch strategy --- src/core.ts | 133 +++++++++++++++++++++++++++------- src/github/graphql/queries.ts | 5 ++ 2 files changed, 110 insertions(+), 28 deletions(-) diff --git a/src/core.ts b/src/core.ts index 515ceea..8a6ae71 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,13 +1,8 @@ import { createCommitOnBranchQuery, - createRefMutation, getRepositoryMetadata, - updateRefMutation, } from "./github/graphql/queries.js"; -import type { - CreateCommitOnBranchMutationVariables, - GetRepositoryMetadataQuery, -} from "./github/graphql/generated/operations.js"; +import type { GetRepositoryMetadataQuery } from "./github/graphql/generated/operations.js"; import { CommitFilesFromBase64Args, CommitFilesResult, @@ -47,6 +42,18 @@ const getOidFromRef = ( return ref.target.oid; }; +const isAlreadyExistingRefError = ( + error: unknown, +) => + typeof error === "object" && + error !== null && + "status" in error && + "message" in error && + typeof error.status === "number" && + typeof error.message === "string" && + error.status === 422 && + error.message.includes("Reference already exists"); + const createCommit = async ({ octokit, refId, @@ -121,7 +128,6 @@ export const commitFilesFromBase64 = async ({ const baseOid = getOidFromRef(base, info.baseRef); const targetOid = info.targetBranch?.target?.oid ?? null; const sameBranchBase = "branch" in base && base.branch === branch; - const repositoryId = info.id; let mode: "create" | "update" | "force-update"; @@ -140,39 +146,110 @@ export const commitFilesFromBase64 = async ({ ); } - let refId: string; + if (mode === "force-update") { + // Use a stable temp branch name so a later run can recover and reuse it + // if an earlier run failed before cleanup completed. + const tempBranch = `changesets-ghcommit-temp/${branch}`; - if (mode === "create") { - const createdRef = await createRefMutation(octokit, { - input: { - repositoryId, - name: `refs/heads/${branch}`, - oid: baseOid, - }, + let tempRefId: string; + + try { + const createdTempRef = await octokit.rest.git.createRef({ + owner, + repo, + ref: `refs/heads/${tempBranch}`, + sha: baseOid, + }); + + const refIdStr = createdTempRef.data.node_id; + + if (!refIdStr) { + throw new Error(`Failed to create temporary branch ${tempBranch}`); + } + + tempRefId = refIdStr; + } catch (error) { + if (!isAlreadyExistingRefError(error)) { + throw error; + } + + const updatedTempRef = await octokit.rest.git.updateRef({ + owner, + repo, + ref: `heads/${tempBranch}`, + sha: baseOid, + force: true, + }); + + const refIdStr = updatedTempRef.data.node_id; + + if (!refIdStr) { + throw new Error(`Failed to update temporary branch ${tempBranch}`); + } + + tempRefId = refIdStr; + } + + await log?.debug(`Creating commit on branch ${tempBranch}`); + const tempCommit = await createCommit({ + octokit, + refId: tempRefId, + baseOid, + message, + fileChanges, }); - const refIdStr = createdRef.createRef?.ref?.id; + const tempRefTarget = tempCommit.createCommitOnBranch?.ref?.target; + const tempHeadOid = + tempRefTarget?.__typename === "Commit" ? tempRefTarget.oid : null; - if (!refIdStr) { - throw new Error(`Failed to create branch ${branch}`); + if (!tempHeadOid) { + throw new Error( + `Failed to determine head commit of temporary branch ${tempBranch}`, + ); } - refId = refIdStr; - } else if (mode === "force-update") { - const updatedRef = await updateRefMutation(octokit, { - input: { - refId: sameBranchBase ? resolvedBaseRef!.id : info.targetBranch!.id, - oid: baseOid, - force: true, - }, + const updatedTargetRef = await octokit.rest.git.updateRef({ + owner, + repo, + ref: `heads/${branch}`, + sha: tempHeadOid, + force: true, }); - const refIdStr = updatedRef.updateRef?.ref?.id; + const updatedTargetRefId = updatedTargetRef.data.node_id; - if (!refIdStr) { + if (!updatedTargetRefId) { throw new Error(`Failed to update branch ${branch}`); } + await octokit.rest.git.deleteRef({ + owner, + repo, + ref: `heads/${tempBranch}`, + }); + + return { + refId: updatedTargetRefId, + }; + } + + let refId: string; + + if (mode === "create") { + const createdRef = await octokit.rest.git.createRef({ + owner, + repo, + ref: `refs/heads/${branch}`, + sha: baseOid, + }); + + const refIdStr = createdRef.data.node_id; + + if (!refIdStr) { + throw new Error(`Failed to create branch ${branch}`); + } + refId = refIdStr; } else { refId = sameBranchBase ? resolvedBaseRef!.id : info.targetBranch!.id; diff --git a/src/github/graphql/queries.ts b/src/github/graphql/queries.ts index d62fde3..b979948 100644 --- a/src/github/graphql/queries.ts +++ b/src/github/graphql/queries.ts @@ -80,6 +80,11 @@ const CREATE_COMMIT_ON_BRANCH = /* GraphQL */ ` createCommitOnBranch(input: $input) { ref { id + target { + ... on Commit { + oid + } + } } } } From 9b026b5aa7936a111e9e3af65290b6e01514199b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 22 May 2026 12:30:35 +0200 Subject: [PATCH 4/9] add tests --- src/test/integration/node.test.ts | 60 +++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/test/integration/node.test.ts b/src/test/integration/node.test.ts index dcdd1fe..557b3c8 100644 --- a/src/test/integration/node.test.ts +++ b/src/test/integration/node.test.ts @@ -49,6 +49,8 @@ const BASIC_FILE_CONTENTS = { // const TEST_TARGET_TREE_WITH_BASIC_CHANGES = // "a3431c9b42b71115c52bc6fbf9da3682cf0ed5e8"; +const getTempBranchName = (branch: string) => `changesets-ghcommit-temp/${branch}`; + describe("node", () => { const branches: string[] = []; @@ -115,6 +117,17 @@ describe("node", () => { expect(ref.parents.nodes?.[0]?.oid).toEqual(oid); }; + const expectBranchDoesNotExist = async (branch: string) => { + await expect( + octokit.rest.git.getRef({ + ...REPO, + ref: `heads/${branch}`, + }), + ).rejects.toMatchObject({ + status: 404, + }); + }; + let testTargetCommit: string; /** * For tests, important that this commit is not an ancestor of TEST_TARGET_COMMIT, @@ -323,6 +336,53 @@ describe("node", () => { }); await expectParentHasOid({ branch, oid: testTargetCommit }); + await expectBranchDoesNotExist(getTempBranchName(branch)); + }); + + it("cleans up a pre-existing temporary branch when force is true", async () => { + const branch = `${TEST_BRANCH_PREFIX}-existing-branch-force-existing-temp`; + const tempBranch = getTempBranchName(branch); + branches.push(branch, tempBranch); + + await createRefMutation(octokit, { + input: { + repositoryId, + name: `refs/heads/${branch}`, + oid: testTargetCommit2, + }, + }); + + await createRefMutation(octokit, { + input: { + repositoryId, + name: `refs/heads/${tempBranch}`, + oid: testTargetCommit2, + }, + }); + + await commitFilesFromBuffers({ + octokit, + ...REPO, + branch, + base: { + commit: testTargetCommit, + }, + ...BASIC_FILE_CONTENTS, + force: true, + }); + + await waitForGitHubToBeReady(); + + await expectBranchHasTree({ + branch, + file: { + path: BASIC_FILE_CHANGES_PATH, + oid: BASIC_FILE_CHANGES_OID, + }, + }); + + await expectParentHasOid({ branch, oid: testTargetCommit }); + await expectBranchDoesNotExist(tempBranch); }); it("cannot commit to existing branch when force is false", async () => { From 688f2027e213e333fa6de988a2d4e57ddd4a4bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 22 May 2026 12:35:24 +0200 Subject: [PATCH 5/9] fix lint --- src/core.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/core.ts b/src/core.ts index 8a6ae71..d841cc6 100644 --- a/src/core.ts +++ b/src/core.ts @@ -42,9 +42,7 @@ const getOidFromRef = ( return ref.target.oid; }; -const isAlreadyExistingRefError = ( - error: unknown, -) => +const isAlreadyExistingRefError = (error: unknown) => typeof error === "object" && error !== null && "status" in error && @@ -190,7 +188,7 @@ export const commitFilesFromBase64 = async ({ tempRefId = refIdStr; } - await log?.debug(`Creating commit on branch ${tempBranch}`); + log?.debug(`Creating commit on branch ${tempBranch}`); const tempCommit = await createCommit({ octokit, refId: tempRefId, @@ -255,7 +253,7 @@ export const commitFilesFromBase64 = async ({ refId = sameBranchBase ? resolvedBaseRef!.id : info.targetBranch!.id; } - await log?.debug(`Creating commit on branch ${branch}`); + log?.debug(`Creating commit on branch ${branch}`); const newCommit = await createCommit({ octokit, refId, From 9e212f0976ecc97fe28c187fcd59afdd68c6477c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 22 May 2026 12:59:56 +0200 Subject: [PATCH 6/9] fmt --- tests/integration/node.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/node.test.ts b/tests/integration/node.test.ts index 98de7be..c7a992d 100644 --- a/tests/integration/node.test.ts +++ b/tests/integration/node.test.ts @@ -50,7 +50,8 @@ const BASIC_FILE_CONTENTS = { // const TEST_TARGET_TREE_WITH_BASIC_CHANGES = // "a3431c9b42b71115c52bc6fbf9da3682cf0ed5e8"; -const getTempBranchName = (branch: string) => `changesets-ghcommit-temp/${branch}`; +const getTempBranchName = (branch: string) => + `changesets-ghcommit-temp/${branch}`; describe("node", () => { const branches: string[] = []; From 1c4ad1ca05266467405911224ede9f870f3484c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 22 May 2026 13:13:54 +0200 Subject: [PATCH 7/9] fix graphql usage --- src/core.ts | 4 +--- src/github/graphql/queries.ts | 8 +++----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/core.ts b/src/core.ts index d841cc6..0230575 100644 --- a/src/core.ts +++ b/src/core.ts @@ -197,9 +197,7 @@ export const commitFilesFromBase64 = async ({ fileChanges, }); - const tempRefTarget = tempCommit.createCommitOnBranch?.ref?.target; - const tempHeadOid = - tempRefTarget?.__typename === "Commit" ? tempRefTarget.oid : null; + const tempHeadOid = tempCommit.createCommitOnBranch?.commit?.oid; if (!tempHeadOid) { throw new Error( diff --git a/src/github/graphql/queries.ts b/src/github/graphql/queries.ts index b979948..7dcd9d7 100644 --- a/src/github/graphql/queries.ts +++ b/src/github/graphql/queries.ts @@ -78,13 +78,11 @@ const DELETE_REF = /* GraphQL */ ` const CREATE_COMMIT_ON_BRANCH = /* GraphQL */ ` mutation createCommitOnBranch($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { + commit { + oid + } ref { id - target { - ... on Commit { - oid - } - } } } } From 518b901e342b8e311d0204a37d1ffaab18c59bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 22 May 2026 13:38:01 +0200 Subject: [PATCH 8/9] add changeset --- .changeset/friendly-rivers-protect.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/friendly-rivers-protect.md diff --git a/.changeset/friendly-rivers-protect.md b/.changeset/friendly-rivers-protect.md new file mode 100644 index 0000000..4fe0254 --- /dev/null +++ b/.changeset/friendly-rivers-protect.md @@ -0,0 +1,5 @@ +--- +"@changesets/ghcommit": minor +--- + +Improve force-push handling so updating an existing branch no longer temporarily resets the target branch to the base commit, avoiding cases where GitHub closes open pull requests during the update. From ff52cbb0995479d22cc813a43a0f6db4f6552a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 22 May 2026 13:50:10 +0200 Subject: [PATCH 9/9] Apply suggestion from @Andarist --- src/core.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core.ts b/src/core.ts index 0230575..5ac23fb 100644 --- a/src/core.ts +++ b/src/core.ts @@ -70,6 +70,7 @@ const createCommit = async ({ } : message; + // we have to stick to GraphQL here as with REST, each file change would become a separate API call return createCommitOnBranchQuery(octokit, { input: { branch: {