diff --git a/src/github/create-commit.ts b/src/github/create-commit.ts index acb01b1f..ee70a14e 100644 --- a/src/github/create-commit.ts +++ b/src/github/create-commit.ts @@ -32,6 +32,7 @@ export interface CreateCommitOptions { * @param {string} refHead the base of the new commit(s) * @param {string} treeSha the tree SHA that this commit will point to * @param {string} message the message of the new commit + * @param {CreateCommitOptions} options additional options like author or commit signer * @returns {Promise} the new commit SHA * @see https://docs.github.com/en/rest/git/commits?apiVersion=2022-11-28#create-a-commit */ @@ -44,15 +45,37 @@ export async function createCommit( options: CreateCommitOptions = {} ): Promise { try { - const signature = options.signer - ? await options.signer.generateSignature({ - message, - tree: treeSha, - parents: [refHead], - author: options.author, - committer: options.committer, - }) - : undefined; + let signature: string | undefined; + let author: Required | undefined = undefined; + let committer: Required | undefined = undefined; + + const commitDate = new Date(); + // Attach author/commit date. + if (options.author) { + author = { + ...options.author, + date: options.author.date ?? commitDate, + }; + } + if (options.committer) { + committer = { + ...options.committer, + date: options.committer.date ?? commitDate, + }; + } + + if (options.signer) { + signature = await options.signer.generateSignature({ + message, + tree: treeSha, + parents: [refHead], + author, + committer, + }); + } else { + signature = undefined; + } + const { data: {sha, url}, } = await octokit.git.createCommit({ @@ -62,8 +85,10 @@ export async function createCommit( tree: treeSha, parents: [refHead], signature, - author: options.author, - committer: options.committer, + author: author ? {...author, date: author.date.toISOString()} : author, + committer: committer + ? {...committer, date: committer.date.toISOString()} + : committer, }); logger.info(`Successfully created commit. See commit at ${url}`); return sha; diff --git a/src/index.ts b/src/index.ts index e04a55aa..b926c0c3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,7 +22,12 @@ import { FileDiffContent, CreateReviewCommentUserOptions, } from './types'; -export {Changes, CommitData, CommitSigner} from './types'; +export { + Changes, + CommitData, + CommitDataWithRequiredDate, + CommitSigner, +} from './types'; import {Octokit} from '@octokit/rest'; import {logger, setupLogger} from './logger'; import { diff --git a/src/types.ts b/src/types.ts index 46c8e35c..87836025 100644 --- a/src/types.ts +++ b/src/types.ts @@ -206,6 +206,7 @@ export interface Logger { export interface UserData { name: string; email: string; + date?: Date; } export interface CommitData { @@ -216,6 +217,11 @@ export interface CommitData { committer?: UserData; } +export interface CommitDataWithRequiredDate extends CommitData { + author?: Required; + committer?: Required; +} + export interface CommitSigner { - generateSignature(commit: CommitData): Promise; + generateSignature(commit: CommitDataWithRequiredDate): Promise; } diff --git a/test/commit-and-push.ts b/test/commit-and-push.ts index 4099c3fe..0471aff6 100644 --- a/test/commit-and-push.ts +++ b/test/commit-and-push.ts @@ -15,7 +15,7 @@ /* eslint-disable node/no-unsupported-features/node-builtins */ import * as assert from 'assert'; -import {describe, it, before, afterEach} from 'mocha'; +import {describe, it, before, afterEach, beforeEach} from 'mocha'; import {octokit, setup} from './util'; import * as sinon from 'sinon'; import {GetResponseTypeFromEndpointMethod} from '@octokit/types'; @@ -31,6 +31,7 @@ import { } from '../src/types'; import {createCommit} from '../src/github/create-commit'; import {CommitError} from '../src/errors'; +import {SinonFakeTimers} from 'sinon'; type GetCommitResponse = GetResponseTypeFromEndpointMethod< typeof octokit.git.getCommit @@ -190,8 +191,13 @@ describe('Push', () => { describe('Commit', () => { const sandbox = sinon.createSandbox(); + let clock: SinonFakeTimers; + beforeEach(() => { + clock = sinon.useFakeTimers(); + }); afterEach(() => { sandbox.restore(); + clock.restore(); }); const origin: RepoDomain = { owner: 'Foo', @@ -225,6 +231,43 @@ describe('Commit', () => { parents: [head], }); }); + it('Uses current date when date in author/committer is undefined and commit signing is enabled', async () => { + // setup + const createCommitResponseData = await import( + './fixtures/create-commit-response.json' + ); + const createCommitResponse = { + headers: {}, + status: 201, + url: 'http://fake-url.com', + data: createCommitResponseData, + } as unknown as CreateCommitResponse; + const stubCreateCommit = sandbox + .stub(octokit.git, 'createCommit') + .resolves(createCommitResponse); + // tests + const sha = await createCommit(octokit, origin, head, treeSha, message, { + author: { + name: 'Fake Author', + email: 'fake.email@example.com', + }, + signer: new FakeCommitSigner('fake-signature'), + }); + assert.strictEqual(sha, createCommitResponse.data.sha); + sandbox.assert.calledOnceWithMatch(stubCreateCommit, { + owner: origin.owner, + repo: origin.repo, + message, + tree: treeSha, + parents: [head], + author: { + name: 'Fake Author', + email: 'fake.email@example.com', + date: new Date(clock.now).toISOString(), + }, + signature: 'fake-signature', + }); + }); }); describe('Update branch reference', () => {