From d110c960f7b19b47053af5ce05676cdce02427d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:28:15 +0000 Subject: [PATCH 1/3] Initial plan From b2295ced6a9da617001929f1a465f62580e4e5cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:40:46 +0000 Subject: [PATCH 2/3] fix: detect submodule mode 160000 and fall back to git push in push_signed_commits.cjs Agent-Logs-Url: https://github.com/github/gh-aw/sessions/308925ea-5090-4ed0-9c34-5bf1dd1f6e0a Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/push_signed_commits.cjs | 20 +++++++++- actions/setup/js/push_signed_commits.test.cjs | 37 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/push_signed_commits.cjs b/actions/setup/js/push_signed_commits.cjs index ce652f233be..254c94497c1 100644 --- a/actions/setup/js/push_signed_commits.cjs +++ b/actions/setup/js/push_signed_commits.cjs @@ -157,13 +157,19 @@ async function pushSignedCommits({ githubClient, owner, repo, branch, baseRef, c core.warning(`pushSignedCommits: unexpected diff-tree output format, skipping line: ${line}`); continue; } - const dstMode = modeFields[1]; // destination file mode (e.g. 100644, 100755, 120000) + const srcMode = modeFields[0]; // source file mode (e.g. 100644, 100755, 120000, 160000) + const dstMode = modeFields[1]; // destination file mode (e.g. 100644, 100755, 120000, 160000) const status = modeFields[4]; // A=Added, M=Modified, D=Deleted, R=Renamed, C=Copied const paths = line.slice(tabIdx + 1).split("\t"); const filePath = unquoteCPath(paths[0]); if (status === "D") { + // mode 160000 = gitlink (submodule); GitHub GraphQL createCommitOnBranch does not support submodules + if (srcMode === "160000") { + core.warning(`pushSignedCommits: submodule change detected in ${filePath}, falling back to git push`); + throw new Error("submodule change detected"); + } deletions.push({ path: filePath }); } else if (status && status.startsWith("R")) { // Rename: source path is deleted, destination path is added @@ -173,6 +179,10 @@ async function pushSignedCommits({ githubClient, owner, repo, branch, baseRef, c continue; } deletions.push({ path: filePath }); + if (srcMode === "160000" || dstMode === "160000") { + core.warning(`pushSignedCommits: submodule change detected in ${renamedPath}, falling back to git push`); + throw new Error("submodule change detected"); + } if (dstMode === "120000") { core.warning(`pushSignedCommits: symlink ${renamedPath} cannot be pushed as a signed commit, falling back to git push`); throw new Error("symlink file mode requires git push fallback"); @@ -189,6 +199,10 @@ async function pushSignedCommits({ githubClient, owner, repo, branch, baseRef, c core.warning(`pushSignedCommits: copy entry missing destination path, skipping: ${line}`); continue; } + if (dstMode === "160000") { + core.warning(`pushSignedCommits: submodule change detected in ${copiedPath}, falling back to git push`); + throw new Error("submodule change detected"); + } if (dstMode === "120000") { core.warning(`pushSignedCommits: symlink ${copiedPath} cannot be pushed as a signed commit, falling back to git push`); throw new Error("symlink file mode requires git push fallback"); @@ -200,6 +214,10 @@ async function pushSignedCommits({ githubClient, owner, repo, branch, baseRef, c additions.push({ path: copiedPath, contents: content.toString("base64") }); } else { // Added or Modified + if (dstMode === "160000") { + core.warning(`pushSignedCommits: submodule change detected in ${filePath}, falling back to git push`); + throw new Error("submodule change detected"); + } if (dstMode === "120000") { core.warning(`pushSignedCommits: symlink ${filePath} cannot be pushed as a signed commit, falling back to git push`); throw new Error("symlink file mode requires git push fallback"); diff --git a/actions/setup/js/push_signed_commits.test.cjs b/actions/setup/js/push_signed_commits.test.cjs index d3c9e6f5084..5ccebb5dca0 100644 --- a/actions/setup/js/push_signed_commits.test.cjs +++ b/actions/setup/js/push_signed_commits.test.cjs @@ -718,6 +718,43 @@ describe("push_signed_commits integration tests", () => { expect(Buffer.from(callArg.fileChanges.additions[0].contents, "base64").toString()).toContain("echo hello"); }); + it("should fall back to git push and warn when commit contains a submodule entry", async () => { + execGit(["checkout", "-b", "submodule-branch"], { cwd: workDir }); + + // Create a gitlink (mode 160000) entry directly via update-index so we don't + // need a real submodule URL. git diff-tree --raw will report this as mode 160000. + // The cacheinfo format is: ,, + const headSha = execGit(["rev-parse", "HEAD"], { cwd: workDir }).stdout.trim(); + execGit(["update-index", "--add", "--cacheinfo", `160000,${headSha},mysubmodule`], { cwd: workDir }); + execGit(["commit", "-m", "Add submodule"], { cwd: workDir }); + execGit(["push", "-u", "origin", "submodule-branch"], { cwd: workDir }); + + global.exec = makeRealExec(workDir); + const githubClient = makeMockGithubClient(); + + await pushSignedCommits({ + githubClient, + owner: "test-owner", + repo: "test-repo", + branch: "submodule-branch", + // Only replay the submodule commit + baseRef: "submodule-branch^", + cwd: workDir, + }); + + // GraphQL should NOT have been called – submodule triggers fallback before mutation + expect(githubClient.graphql).not.toHaveBeenCalled(); + // Warning about submodule must be emitted + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("submodule change detected in mysubmodule")); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("falling back to git push")); + + // The commit should be present on the remote via git push fallback + const lsRemote = execGit(["ls-remote", bareDir, "refs/heads/submodule-branch"], { cwd: workDir }); + const remoteOid = lsRemote.stdout.trim().split(/\s+/)[0]; + const localOid = execGit(["rev-parse", "HEAD"], { cwd: workDir }).stdout.trim(); + expect(remoteOid).toBe(localOid); + }); + it("should not warn for regular files (mode 100644)", async () => { execGit(["checkout", "-b", "regular-file-branch"], { cwd: workDir }); fs.writeFileSync(path.join(workDir, "regular.txt"), "Regular file content\n"); From 0de51dd614fc4cb6914199fa7aa513a47efe0a85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:58:26 +0000 Subject: [PATCH 3/3] fix: update pre-scan comment to include gitlinks; improve rename warning with both paths Agent-Logs-Url: https://github.com/github/gh-aw/sessions/407bea3c-da48-4b10-814f-66cc383482c2 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/push_signed_commits.cjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/push_signed_commits.cjs b/actions/setup/js/push_signed_commits.cjs index 254c94497c1..67e44842b85 100644 --- a/actions/setup/js/push_signed_commits.cjs +++ b/actions/setup/js/push_signed_commits.cjs @@ -129,6 +129,7 @@ async function pushSignedCommits({ githubClient, owner, repo, branch, baseRef, c // The GitHub GraphQL createCommitOnBranch mutation only supports regular file mode 100644: // - Symlinks (120000) would be silently converted to regular files containing the link target path // - Executable bits (100755) are silently dropped + // - Submodules/gitlinks (160000) are not supported; the mutation does not accept commit-object entries /** @type {Map>} */ const additionsMap = new Map(); /** @type {Map>} */ @@ -180,7 +181,7 @@ async function pushSignedCommits({ githubClient, owner, repo, branch, baseRef, c } deletions.push({ path: filePath }); if (srcMode === "160000" || dstMode === "160000") { - core.warning(`pushSignedCommits: submodule change detected in ${renamedPath}, falling back to git push`); + core.warning(`pushSignedCommits: submodule change detected in ${filePath} -> ${renamedPath}, falling back to git push`); throw new Error("submodule change detected"); } if (dstMode === "120000") {