Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ All inputs are **optional**. If not set, sensible defaults will be used.
| `path` | Relative path under `GITHUB_WORKSPACE` to the repository. | `GITHUB_WORKSPACE` |
| `add-paths` | A comma or newline-separated list of file paths to commit. Paths should follow git's [pathspec](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) syntax. See [Add specific paths](#add-specific-paths). | If no paths are specified, all new and modified files are added. |
| `commit-message` | The message to use when committing changes. See [commit-message](#commit-message). | `[create-pull-request] automated change` |
| `committer` | The committer name and email address in the format `Display Name <email@address.com>`. Defaults to the GitHub Actions bot user on github.com. | `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>` |
| `committer` | The committer name and email address in the format `Display Name <email@address.com>`. Defaults to the identity of the `token` | `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>` for the GitHub Actions bot user on github.com |
| `author` | The author name and email address in the format `Display Name <email@address.com>`. Defaults to the user who triggered the workflow run. | `${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>` |
| `signoff` | Add [`Signed-off-by`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff) line by the committer at the end of the commit log message. | `false` |
| `branch` | The pull request branch name. | `create-pull-request/patch` |
Expand Down
32 changes: 32 additions & 0 deletions __test__/utils.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,38 @@ describe('utils tests', () => {
)
}
})

test('composeNoReplyIdentity produces the unchanged github.com identity', async () => {
const identity = utils.composeNoReplyIdentity(
'github-actions[bot]',
41898282,
'github.com'
)
expect(identity).toEqual(
'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
)
const parsed = utils.parseDisplayNameEmail(identity)
expect(parsed.name).toEqual('github-actions[bot]')
expect(parsed.email).toEqual(
'41898282+github-actions[bot]@users.noreply.github.com'
)
})

test('composeNoReplyIdentity derives a server-aware identity on GHES', async () => {
const identity = utils.composeNoReplyIdentity(
'github-actions[bot]',
5,
'ghe.example.com'
)
expect(identity).toEqual(
'github-actions[bot] <5+github-actions[bot]@users.noreply.ghe.example.com>'
)
const parsed = utils.parseDisplayNameEmail(identity)
expect(parsed.name).toEqual('github-actions[bot]')
expect(parsed.email).toEqual(
'5+github-actions[bot]@users.noreply.ghe.example.com'
)
})
})

describe('retryWithBackoff', () => {
Expand Down
7 changes: 3 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ inputs:
committer:
description: >
The committer name and email address in the format `Display Name <email@address.com>`.
Defaults to the GitHub Actions bot user.
default: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
Defaults to the identity of the `token` (e.g. `github-actions[bot]` for `GITHUB_TOKEN`,
your app's bot for a GitHub App token, or the user for a PAT).
author:
description: >
The author name and email address in the format `Display Name <email@address.com>`.
Defaults to the user who triggered the workflow run.
default: '${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>'
Defaults to the user who triggered the workflow run, with a server-aware no-reply email.
signoff:
description: 'Add `Signed-off-by` line by the committer at the end of the commit log message.'
default: false
Expand Down
46 changes: 46 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,31 @@ function createPullRequest(inputs) {
core.info(`Pull request branch to create or update set to '${inputs.branch}'`);
// Configure the committer and author
core.startGroup('Configuring the committer and author');
// Resolve the default committer from the token identity
if (!inputs.committer) {
try {
const user = yield ghBranch.getServerUser();
inputs.committer = utils.composeNoReplyIdentity(user.login, user.id, baseRemote.hostname);
core.info(`Committer not supplied; derived from token as '${inputs.committer}'`);
}
catch (e) {
core.warning(`Failed to derive committer from token: ${utils.getErrorMessage(e)}`);
inputs.committer =
'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>';
}
}
// Resolve the author default from the triggering actor, with a server-aware
// no-reply email domain.
if (!inputs.author) {
const actor = process.env['GITHUB_ACTOR'];
const actorId = process.env['GITHUB_ACTOR_ID'];
if (actor && actorId) {
inputs.author = utils.composeNoReplyIdentity(actor, actorId, baseRemote.hostname);
}
else {
inputs.author = inputs.committer;
}
}
const parsedAuthor = utils.parseDisplayNameEmail(inputs.author);
const parsedCommitter = utils.parseDisplayNameEmail(inputs.committer);
git.setIdentityGitOptions([
Expand Down Expand Up @@ -1406,6 +1431,21 @@ class GitHubHelper {
repo: repo
};
}
getServerUser() {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
// Resolve the identity behind the authenticated token.
// The GraphQL `viewer` resolves installation tokens (the default
// `GITHUB_TOKEN` and GitHub App tokens) as well as PAT tokens.
// REST `users.getAuthenticated()` is NOT used since it does not work for
// installation tokens.
const resp = yield this.octokit.graphql(`query { viewer { login databaseId } }`);
if (!((_a = resp === null || resp === void 0 ? void 0 : resp.viewer) === null || _a === void 0 ? void 0 : _a.login) || !((_b = resp === null || resp === void 0 ? void 0 : resp.viewer) === null || _b === void 0 ? void 0 : _b.databaseId)) {
throw new Error('GraphQL viewer did not return login and databaseId');
}
return { login: resp.viewer.login, id: resp.viewer.databaseId };
});
}
getPullNumber(baseRepository, headBranch, baseBranch) {
return __awaiter(this, void 0, void 0, function* () {
var _a, e_1, _b, _c;
Expand Down Expand Up @@ -1929,6 +1969,7 @@ exports.getRepoPath = getRepoPath;
exports.getRemoteUrl = getRemoteUrl;
exports.secondsSinceEpoch = secondsSinceEpoch;
exports.randomString = randomString;
exports.composeNoReplyIdentity = composeNoReplyIdentity;
exports.parseDisplayNameEmail = parseDisplayNameEmail;
exports.fileExistsSync = fileExistsSync;
exports.readFile = readFile;
Expand Down Expand Up @@ -1980,6 +2021,11 @@ function secondsSinceEpoch() {
function randomString() {
return Math.random().toString(36).substr(2, 7);
}
function composeNoReplyIdentity(login, id, hostname) {
// The GitHub no-reply commit identity, e.g.
// `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>`.
return `${login} <${id}+${login}@users.noreply.${hostname}>`;
}
function parseDisplayNameEmail(displayNameEmail) {
// Parse the name and email address from a string in the following format
// Display Name <email@address.com>
Expand Down
35 changes: 35 additions & 0 deletions src/create-pull-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,41 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {

// Configure the committer and author
core.startGroup('Configuring the committer and author')
// Resolve the default committer from the token identity
if (!inputs.committer) {
try {
const user = await ghBranch.getServerUser()
inputs.committer = utils.composeNoReplyIdentity(
user.login,
user.id,
baseRemote.hostname
)
core.info(
`Committer not supplied; derived from token as '${inputs.committer}'`
)
} catch (e) {
core.warning(
`Failed to derive committer from token: ${utils.getErrorMessage(e)}`
)
inputs.committer =
'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
}
}
// Resolve the author default from the triggering actor, with a server-aware
// no-reply email domain.
if (!inputs.author) {
const actor = process.env['GITHUB_ACTOR']
const actorId = process.env['GITHUB_ACTOR_ID']
if (actor && actorId) {
inputs.author = utils.composeNoReplyIdentity(
actor,
actorId,
baseRemote.hostname
)
} else {
inputs.author = inputs.committer
}
}
const parsedAuthor = utils.parseDisplayNameEmail(inputs.author)
const parsedCommitter = utils.parseDisplayNameEmail(inputs.committer)
git.setIdentityGitOptions([
Expand Down
20 changes: 20 additions & 0 deletions src/github-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ interface CommitResponse {
verified: boolean
}

interface TokenIdentity {
login: string
id: number
}

type TreeObject = {
path: string
mode: '100644' | '100755' | '040000' | '160000' | '120000'
Expand Down Expand Up @@ -70,6 +75,21 @@ export class GitHubHelper {
}
}

async getServerUser(): Promise<TokenIdentity> {
// Resolve the identity behind the authenticated token.
// The GraphQL `viewer` resolves installation tokens (the default
// `GITHUB_TOKEN` and GitHub App tokens) as well as PAT tokens.
// REST `users.getAuthenticated()` is NOT used since it does not work for
// installation tokens.
const resp = await this.octokit.graphql<{
viewer: {login: string; databaseId: number}
}>(`query { viewer { login databaseId } }`)
if (!resp?.viewer?.login || !resp?.viewer?.databaseId) {
throw new Error('GraphQL viewer did not return login and databaseId')
}
return {login: resp.viewer.login, id: resp.viewer.databaseId}
}

private async getPullNumber(
baseRepository: string,
headBranch: string,
Expand Down
10 changes: 10 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ interface DisplayNameEmail {
email: string
}

export function composeNoReplyIdentity(
login: string,
id: number | string,
hostname: string
): string {
// The GitHub no-reply commit identity, e.g.
// `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>`.
return `${login} <${id}+${login}@users.noreply.${hostname}>`
}

export function parseDisplayNameEmail(
displayNameEmail: string
): DisplayNameEmail {
Expand Down