Skip to content

Sign commit using GitHub API#1091

Merged
int128 merged 40 commits intomainfrom
int128/Sign-commit-using-GitHub-API
Apr 30, 2026
Merged

Sign commit using GitHub API#1091
int128 merged 40 commits intomainfrom
int128/Sign-commit-using-GitHub-API

Conversation

@int128
Copy link
Copy Markdown
Owner

@int128 int128 commented Jan 1, 2026

No description provided.

@int128 int128 force-pushed the int128/Sign-commit-using-GitHub-API branch from 3207d65 to 30976e0 Compare January 2, 2026 01:48
@int128 int128 force-pushed the int128/Sign-commit-using-GitHub-API branch from 87768ff to c28be95 Compare January 2, 2026 08:47
@int128 int128 force-pushed the int128/Sign-commit-using-GitHub-API branch from 079aa5d to 4c2b81c Compare January 2, 2026 08:50
@int128 int128 force-pushed the int128/Sign-commit-using-GitHub-API branch from e6b8af8 to 05ee8dc Compare January 3, 2026 02:04
@int128 int128 force-pushed the int128/Sign-commit-using-GitHub-API branch from 81beb13 to 45cdbc6 Compare January 3, 2026 02:07
@int128 int128 force-pushed the int128/Sign-commit-using-GitHub-API branch from 2559dc9 to 5380682 Compare January 3, 2026 02:15
Generated-by: update-generated-files-action
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

This PR introduces a new “commit signing” step that rewrites the current commit via the GitHub REST API and integrates that step into both pull-request and non-PR event flows.

Changes:

  • Add signCurrentCommit helper that recreates the current commit via Octokit and swaps HEAD to the new commit.
  • Invoke signing in handlePullRequestEvent and handleOtherEvent, and thread octokit into PR-event handling.
  • Add git.deleteRef helper and update tests to mock the new signing module / new function signature.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/sign.ts New helper that recreates the current commit through the GitHub API and updates local checkout.
src/pull_request_event.ts Passes Octokit through and signs commits during PR handling (including a merge-fallback path).
src/other_event.ts Signs commits in the non-PR flow before attempting fast-forward update / PR creation.
src/run.ts Updates PR-event call to pass octokit.
src/git.ts Adds deleteRef helper used by signing cleanup.
tests/pull_request_event.test.ts Updates tests for the new handlePullRequestEvent signature and mocks signing.
tests/other_event.test.ts Mocks signing to avoid side effects in existing tests.

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

Comment thread src/git.ts
Comment on lines +146 to +149
export const deleteRef = async (ref: string, context: Context) =>
await exec.exec('git', [...gitTokenConfigFlags(context), 'push', 'origin', '--quiet', '--delete', ref], {
env: {
...process.env,
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

deleteRef is used for best-effort cleanup, but it currently always throws on non-zero exit codes (e.g., ref already deleted / missing permissions), which can fail the whole run during cleanup. Consider adding an option to ignore return codes (or handling common non-fatal cases) so callers like signing cleanup can avoid masking the main error.

Suggested change
export const deleteRef = async (ref: string, context: Context) =>
await exec.exec('git', [...gitTokenConfigFlags(context), 'push', 'origin', '--quiet', '--delete', ref], {
env: {
...process.env,
export const deleteRef = async (ref: string, context: Context, options?: exec.ExecOptions) =>
await exec.exec('git', [...gitTokenConfigFlags(context), 'push', 'origin', '--quiet', '--delete', ref], {
...options,
env: {
...process.env,
...options?.env,

Copilot uses AI. Check for mistakes.
Comment thread src/sign.ts
Comment on lines +5 to +8
export const signCurrentCommit = async (context: Context, octokit: Octokit) => {
const unsignedCommitSHA = await git.getCurrentSHA()
const signingBranch = `signing--${unsignedCommitSHA}`
await git.push({ localRef: unsignedCommitSHA, remoteRef: `refs/heads/${signingBranch}`, dryRun: false }, context)
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

signCurrentCommit always performs a real remote push (dryRun: false) and then deletes the remote ref, which breaks the action’s dry-run contract (it will still create/delete branches and mutate remote state). Consider threading a dryRun flag into signCurrentCommit and making it a no-op (or warning-only) when dryRun is enabled, and ensure any cleanup steps also respect dryRun.

Suggested change
export const signCurrentCommit = async (context: Context, octokit: Octokit) => {
const unsignedCommitSHA = await git.getCurrentSHA()
const signingBranch = `signing--${unsignedCommitSHA}`
await git.push({ localRef: unsignedCommitSHA, remoteRef: `refs/heads/${signingBranch}`, dryRun: false }, context)
export const signCurrentCommit = async (context: Context, octokit: Octokit, dryRun = false) => {
if (dryRun) {
console.warn('Dry-run enabled: skipping signCurrentCommit because it would push and delete remote refs.')
return
}
const unsignedCommitSHA = await git.getCurrentSHA()
const signingBranch = `signing--${unsignedCommitSHA}`
await git.push({ localRef: unsignedCommitSHA, remoteRef: `refs/heads/${signingBranch}`, dryRun }, context)

Copilot uses AI. Check for mistakes.
Comment thread src/sign.ts
repo: context.repo.repo,
message: unsignedCommit.message,
tree: unsignedCommit.tree.sha,
parents: unsignedCommit.parents.map((parent) => parent.sha),
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

The commit created via octokit.rest.git.createCommit does not set author/committer. That will typically change the resulting commit metadata (and potentially timestamps) compared to the locally created commit, which can be surprising and may break expectations about attribution. Consider copying author and committer fields from the fetched unsignedCommit (or otherwise explicitly setting them) when creating the new commit.

Suggested change
parents: unsignedCommit.parents.map((parent) => parent.sha),
parents: unsignedCommit.parents.map((parent) => parent.sha),
...(unsignedCommit.author
? {
author: {
name: unsignedCommit.author.name,
email: unsignedCommit.author.email,
date: unsignedCommit.author.date,
},
}
: {}),
...(unsignedCommit.committer
? {
committer: {
name: unsignedCommit.committer.name,
email: unsignedCommit.committer.email,
date: unsignedCommit.committer.date,
},
}
: {}),

Copilot uses AI. Check for mistakes.
Comment thread src/sign.ts
await git.fetch({ refs: [signedCommit.sha], depth: 2 }, context)
await git.checkout(signedCommit.sha)
} finally {
await git.deleteRef(`refs/heads/${signingBranch}`, context)
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

If any operation inside the try fails, an error thrown by git.deleteRef in the finally block will override the original failure and make debugging harder. Consider wrapping the cleanup in its own try/catch and logging a warning on cleanup failure so the primary error is preserved.

Suggested change
await git.deleteRef(`refs/heads/${signingBranch}`, context)
try {
await git.deleteRef(`refs/heads/${signingBranch}`, context)
} catch (error) {
console.warn(`Failed to delete temporary signing branch refs/heads/${signingBranch}`, error)
}

Copilot uses AI. Check for mistakes.
Comment thread src/pull_request_event.ts
})
}

await core.group(`Signing the commit`, () => signCurrentCommit(context, octokit))
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

Signing is executed unconditionally before the dryRun short-circuit. With inputs.dryRun === true, this will still perform remote mutations (branch create/update/delete and REST API calls) even though later steps try to avoid pushing. Consider skipping signing when dryRun is enabled, or refactoring so signing respects dryRun end-to-end.

Suggested change
await core.group(`Signing the commit`, () => signCurrentCommit(context, octokit))
if (inputs.dryRun) {
core.warning(`[dry-run] Skipping commit signing.`)
} else {
await core.group(`Signing the commit`, () => signCurrentCommit(context, octokit))
}

Copilot uses AI. Check for mistakes.
Comment thread src/other_event.ts
await core.group(`Committing the workspace changes`, async () => {
await git.commit([inputs.commitMessage, inputs.commitMessageFooter, getAutoGeneratedTrailer(context)])
})
await core.group(`Signing the commit`, () => signCurrentCommit(context, octokit))
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

signCurrentCommit is called even when inputs.dryRun is true, which will still mutate remote state (temporary branch, updateRef, delete) despite the rest of the flow supporting dry-run. Consider gating signing behind !inputs.dryRun (or making signing itself dry-run aware).

Suggested change
await core.group(`Signing the commit`, () => signCurrentCommit(context, octokit))
if (inputs.dryRun) {
core.warning(`[dry-run] Skipping commit signing`)
} else {
await core.group(`Signing the commit`, () => signCurrentCommit(context, octokit))
}

Copilot uses AI. Check for mistakes.
Comment thread src/sign.ts
Comment on lines +5 to +10
export const signCurrentCommit = async (context: Context, octokit: Octokit) => {
const unsignedCommitSHA = await git.getCurrentSHA()
const signingBranch = `signing--${unsignedCommitSHA}`
await git.push({ localRef: unsignedCommitSHA, remoteRef: `refs/heads/${signingBranch}`, dryRun: false }, context)
try {
const { data: unsignedCommit } = await octokit.rest.git.getCommit({
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

New signing behavior in signCurrentCommit is not covered by tests. Since this is a new side-effectful workflow step (git push/delete + Octokit REST calls), consider adding a dedicated Vitest suite that mocks git and octokit.rest.git to assert the expected sequence and ensure error/cleanup behavior is correct (including dry-run).

Copilot uses AI. Check for mistakes.
@int128 int128 marked this pull request as ready for review April 30, 2026 08:32
@int128 int128 merged commit 96c81cd into main Apr 30, 2026
7 checks passed
@int128 int128 deleted the int128/Sign-commit-using-GitHub-API branch April 30, 2026 08:33
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.

2 participants