Skip to content

bug: multiple critical issues in push_signed_commits #26156

@pgaskin

Description

@pgaskin

There are multiple bugs in actions/setup/js/push_signed_commits.cjs which will silently push incorrect content.

There's probably some more, but there are the ones I noticed while doing some research of existing code before I ended up making my own action for pushing signed commits.

copies in diffs are treated like renames

} else if (status.startsWith("R") || status.startsWith("C")) {
// Rename or Copy: parts[1] = old path, parts[2] = new path
deletions.push({ path: parts[1] });
const content = fs.readFileSync(path.join(cwd, parts[2]));
additions.push({ path: parts[2], contents: content.toString("base64") });
} else {

The old file is deleted, even if it's a copy. This needs to be surrounded by another check for if it's specifically a rename versus a copy.

git diff may double-quote filenames

// File changes for this commit (supports Add/Modify/Delete/Rename/Copy)
const { stdout: nameStatusOut } = await exec.getExecOutput("git", ["diff", "--name-status", `${sha}^`, sha], { cwd });
/** @type {Array<{path: string, contents: string}>} */
const additions = [];
/** @type {Array<{path: string}>} */
const deletions = [];
for (const line of nameStatusOut.trim().split("\n").filter(Boolean)) {
const parts = line.split("\t");
const status = parts[0];

That is not handled here. The git diff command may c-quote filenames with special characters. The code should check if the names start with double-quotes and unescape them if so.

Also, git diff-tree is better than git diff since it won't be affected by the git config.

the working tree content is always used

const content = fs.readFileSync(path.join(cwd, parts[2]));
additions.push({ path: parts[2], contents: content.toString("base64") });
} else {
// Added or Modified
const content = fs.readFileSync(path.join(cwd, parts[1]));

This is incorrect since the content in the last commit may not be the same as the working area, and the content in old commits will be incorrect.

The git show REV:PATH command or git ls-tree combined with git cat-files should be used instead.

merge commits are not handled

  • git rev-list will list commits from both parents
  • createCommitOnBranch doesn't support creating commits with multiple parents.

submodule updates will fail to be pushed

They appear as a single item in git diff, point to a commit object in the tree (rather than a blob), and will be a directory in the working area.

Furthermore, the GitHub GraphQL API createCommitOnBranch mutation doesn't support submodules (this is an easy fix though, see below).

git rev-list lists by commit date by default

const { stdout: revListOut } = await exec.getExecOutput("git", ["rev-list", "--reverse", `${baseRef}..HEAD`], { cwd });
const shas = revListOut.trim().split("\n").filter(Boolean);

Commits with out-of-order commit dates (e.g., ones rebased with --committer-date-is-author-date) will not be pushed in the correct order. The --topo-order option should be used to ensure they are listed in graph order.

non-100644 file modes are silently discarded

Things like shell scripts typically have 100755 permissions, but these will be silently dropped when the file is modified and pushed since the createCommitOnBranch mutation doesn't support them.

Symlinks will be silently converted to a regular file with the contents of the target, and will fail if it's a directory.

See https://github.com/orgs/community/discussions/191953.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions