Skip to content

Fix push_signed_commits.cjs to read file content from commit objects, not working tree#26287

Merged
pelikhan merged 3 commits intomainfrom
copilot/fix-push-signed-commits-read-from-commit-object
Apr 14, 2026
Merged

Fix push_signed_commits.cjs to read file content from commit objects, not working tree#26287
pelikhan merged 3 commits intomainfrom
copilot/fix-push-signed-commits-read-from-commit-object

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 14, 2026

When replaying a series of commits (A → B → C) via createCommitOnBranch, all commits were getting file contents from the working tree (at C), so commits A and B contained wrong file contents.

Changes

  • actions/setup/js/push_signed_commits.cjs: Replace all three fs.readFileSync(path.join(cwd, filePath)) calls (rename, copy, add/modify paths) with a new readBlobAsBase64(sha, filePath, cwd) helper that uses exec.exec with raw Buffer listeners, scoping each file read to the specific commit being replayed and preserving exact byte content for binary files
  • Remove now-unused fs and path imports
  • actions/setup/js/push_signed_commits.test.cjs: Add regression test verifying that a series of commits (A→B→C) each carry their own file content in the GraphQL payload, not the working-tree tip; update makeRealExec mock to support raw Buffer accumulation via listeners.stdout
// Before — always reads from working tree (wrong for intermediate commits)
const content = fs.readFileSync(path.join(cwd, filePath));
additions.push({ path: filePath, contents: content.toString("base64") });

// After — reads blob from the commit being processed, preserving raw bytes
async function readBlobAsBase64(sha, filePath, cwd) {
  const chunks = [];
  await exec.exec("git", ["show", `${sha}:${filePath}`], {
    cwd, silent: true,
    listeners: { stdout: (data) => { chunks.push(data); } },
  });
  return Buffer.concat(chunks).toString("base64");
}
additions.push({ path: filePath, contents: await readBlobAsBase64(sha, filePath, cwd) });

@github-actions
Copy link
Copy Markdown
Contributor

Hey @Copilot 👋 — great start on tackling the working-tree bug in push_signed_commits.cjs! The plan is clear, well-scoped, and the acceptance criteria map directly to the problem reported in #26156.

A couple of things to address before this is ready to merge:

  • Implementation missing — the PR currently has 0 code changes (only an "Initial plan" commit). The fs.readFileSync calls need to be replaced with git show <sha>:<path> as described in the plan.
  • Add tests — once the fix is in place, tests covering multi-commit push sequences (e.g., commits A→B→C with distinct file contents at each step) would confirm the fix and protect against regressions.

If you'd like a hand, here's a prompt to assign to your coding agent:

In actions/setup/js/push_signed_commits.cjs, replace all fs.readFileSync calls used to read file content for commit additions/modifications with git show <sha>:<filePath> executed via exec.getExecOutput. The result should be read as binary and base64-encoded for the GitHub GraphQL API payload. After making the fix, add unit/integration tests that verify a multi-commit push (e.g., 3 commits each modifying the same file differently) produces the correct file contents for each intermediate commit, not just the latest working-tree state.

Generated by Contribution Check · ● 1.7M ·

Replace fs.readFileSync (working tree) with git show <sha>:<path>
for all three file addition cases (rename, copy, add/modify).
Remove unused fs and path imports.

Fixes: multi-commit pushes produced wrong file contents for
intermediate commits because working tree always reflected HEAD.

Resolves #26156

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/c0841d22-80e2-4bd8-995b-019888de359e

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix push_signed_commits.cjs to read file content from commit objects Fix push_signed_commits.cjs to read file content from commit objects, not working tree Apr 14, 2026
Copilot AI requested a review from pelikhan April 14, 2026 21:19
@pelikhan pelikhan marked this pull request as ready for review April 14, 2026 21:21
Copilot AI review requested due to automatic review settings April 14, 2026 21:21
@github-actions github-actions bot mentioned this pull request Apr 14, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes pushSignedCommits replay logic so file contents are read from each commit object being replayed (A/B/C), rather than incorrectly reading from the working tree at the tip commit.

Changes:

  • Replace working-tree fs.readFileSync(...) file reads with per-commit git show <sha>:<path> reads for rename/copy/add-modify cases.
  • Remove now-unused fs and path imports.
Show a summary per file
File Description
actions/setup/js/push_signed_commits.cjs Reads file contents scoped to the commit SHA being replayed, preventing wrong content in intermediate signed commits.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (2)

actions/setup/js/push_signed_commits.cjs:207

  • git show failures are currently silent here: getExecOutput() typically does not throw on non-zero exit codes (it returns exitCode), so a missing path/blob could result in committing empty or partial content. Please check exitCode/stderr for the git show call and throw (to trigger the existing fallback) when it’s non-zero, and ensure the base64 is generated from raw stdout bytes (not the UTF-8 string) to avoid corrupting binary files.
          const { stdout: fileContent } = await exec.getExecOutput("git", ["show", `${sha}:${filePath}`], { cwd });
          additions.push({ path: filePath, contents: Buffer.from(fileContent).toString("base64") });

actions/setup/js/push_signed_commits.cjs:196

  • This git show read has the same raw-bytes issue as the rename path: getExecOutput() yields a UTF-8 string, so Buffer.from(copiedContent) is not byte-faithful for binary files. Please base64-encode the actual blob bytes (collect Buffer output) rather than re-encoding a JS string.
          const { stdout: copiedContent } = await exec.getExecOutput("git", ["show", `${sha}:${copiedPath}`], { cwd });
          additions.push({ path: copiedPath, contents: Buffer.from(copiedContent).toString("base64") });
  • Files reviewed: 1/1 changed files
  • Comments generated: 2

Comment on lines +206 to +207
const { stdout: fileContent } = await exec.getExecOutput("git", ["show", `${sha}:${filePath}`], { cwd });
additions.push({ path: filePath, contents: Buffer.from(fileContent).toString("base64") });
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change fixes an important correctness issue (intermediate commits were using working-tree content), but push_signed_commits.test.cjs doesn’t appear to cover the regression scenario (same path modified across multiple commits A→B→C, ensuring A/B payloads use their own blob contents). Adding a test for that case would prevent this from reoccurring.

Copilot uses AI. Check for mistakes.
Comment on lines +179 to +180
const { stdout: renamedContent } = await exec.getExecOutput("git", ["show", `${sha}:${renamedPath}`], { cwd });
additions.push({ path: renamedPath, contents: Buffer.from(renamedContent).toString("base64") });
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exec.getExecOutput() returns stdout as a UTF-8 string; wrapping that with Buffer.from(str) will corrupt non-text/binary files (and potentially any content with invalid UTF-8). Previously fs.readFileSync() preserved raw bytes. Consider capturing raw stdout bytes (e.g., by running git via a spawn API with encoding: null, or using exec.exec with stdout listeners that accumulate Buffer chunks) and base64-encoding the resulting Buffer so signed commits preserve exact file content.

This issue also appears in the following locations of the same file:

  • line 195
  • line 206

Copilot uses AI. Check for mistakes.
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review comments, add integration tests

…ed_commits

Extract readBlobAsBase64() helper that uses exec.exec with stdout
listeners accumulating raw Buffer chunks instead of getExecOutput
(which UTF-8 decodes stdout via StringDecoder and corrupts binary files).

Update makeRealExec test mock to support listeners.stdout with
encoding:null so binary content is preserved in tests.

Add regression test verifying that A→B→C multi-commit push sends
each commit's own file content, not the working-tree tip.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/cc9df24f-9c1d-48e3-bff9-22fb9ab97b4c

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 14, 2026

@copilot review comments, add integration tests

Done in ff19dce:

  • Binary corruption fixed: Extracted a readBlobAsBase64(sha, filePath, cwd) helper that uses exec.exec with a listeners.stdout callback accumulating raw Buffer chunks, then base64-encodes the concatenated result. This avoids the UTF-8 StringDecoder in getExecOutput that corrupts binary files.

  • Regression test added: New test "each commit in a series should carry its own file content, not the working-tree tip" creates commits A→B→C all modifying data.txt, then verifies each GraphQL payload carries that commit's own content ("version A\n", "version B\n", "version C\n") rather than the working-tree tip.

  • Test mock updated: makeRealExec.exec now uses encoding: null (raw Buffer) when a listeners.stdout callback is present, matching the real @actions/exec behaviour.

@pelikhan pelikhan merged commit 528c831 into main Apr 14, 2026
82 checks passed
@pelikhan pelikhan deleted the copilot/fix-push-signed-commits-read-from-commit-object branch April 14, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[plan] Fix push_signed_commits.cjs to read file content from commit objects, not working tree

3 participants