Skip to content

Commit

Permalink
feat: add allow-empty and skip-empty options
Browse files Browse the repository at this point in the history
  • Loading branch information
qoomon committed Mar 20, 2024
1 parent 6feec74 commit fb12704
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 163 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ inputs:
description: 'Amend the last commit'
default: false
allow-empty:
description: 'Allow empty commit'
description: 'Allow an empty commit'
default: false
skip-empty:
description: 'Skip action, if nothing to commit'
default: false

token:
Expand Down
9 changes: 6 additions & 3 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ inputs:
required: true
amend:
description: 'Amend the last commit'
default: false
default: 'false'
allow-empty:
description: 'Allow empty commit'
default: false
description: 'Allow an empty commit'
default: 'false'
skip-empty:
description: 'Skip action, if nothing to commit'
default: 'false'

token:
description: 'A GitHub access token'
Expand Down
18 changes: 14 additions & 4 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,23 @@ export const action = () => run(async () => {
message: getInput('message', {required: true})!,
amend: getInput('amend') === 'true',
allowEmpty: getInput('allow-empty') === 'true',
skipEmpty: getInput('skip-empty') === 'true',
}

process.chdir(input.workingDirectory)

const cacheDetails = await getCacheDetails()
console.log('cacheDetails', cacheDetails)
if (cacheDetails.files.length === 0) {
if (input.skipEmpty) {
core.info('nothing to commit, working tree clean')
return
} else if (!input.allowEmpty) {
core.setFailed('nothing to commit, working tree clean')
return
}
}

const commitArgs = [
'--message', input.message,
]
Expand All @@ -27,10 +40,6 @@ export const action = () => run(async () => {
'-c', 'user.email=41898282+github-actions[bot]@users.noreply.github.com',
'commit', ...commitArgs,
])
if (commitResult.status !== 0) {
core.info(commitResult.stderr.toString())
return
}

const octokit = github.getOctokit(input.token)
const headCommit = await getCommitDetails('HEAD')
Expand All @@ -39,6 +48,7 @@ export const action = () => run(async () => {
const githubCommit = await createCommit(octokit, repository, {
subject: headCommit.subject,
body: headCommit.body,
tree: headCommit.tree,
parents: headCommit.parents,
files: headCommit.files.map((file) => ({
path: file.path,
Expand Down
16 changes: 0 additions & 16 deletions lib/actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as core from '@actions/core'
import {InputOptions} from '@actions/core'
import YAML from 'yaml'
import {HttpClientError} from '@actions/http-client'
import * as _exec from '@actions/exec'
import {ExecOptions} from '@actions/exec'
Expand Down Expand Up @@ -33,21 +32,6 @@ export function getInput(name: string, options?: InputOptions): string | null {
return core.getInput(name, options) || null
}

/**
* Gets the yaml value of an input.
* Unless trimWhitespace is set to false in InputOptions, the value is also trimmed.
* Returns null if the value is not defined.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns parsed input as object
*/
export function getYamlInput(name: string, options?: InputOptions): unknown | null {
const input = getInput(name, options)
if (input === null) return null
return YAML.parse(input)
}

/**
* Execute a command and get the output.
* @param commandLine - command to execute (can include additional args). Must be correctly escaped.
Expand Down
11 changes: 7 additions & 4 deletions lib/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export async function getCommitDetails(ref: string = 'HEAD'): Promise<CommitDeta

const fieldsSeparator = '---'
const showOutputLines = await exec('git show --raw --cc', [
'--diff-filter=AMD', // A: added, M: modified, D: deleted
'--format=' + [
'commit:%H',
'tree:%T',
Expand Down Expand Up @@ -123,7 +122,9 @@ export async function getCommitDetails(ref: string = 'HEAD'): Promise<CommitDeta
}

result.files = showOutputFileLines
.map(parseRawFileDiffLine) satisfies RawFileDiff[] as (RawFileDiff & { status: 'A' | 'M' | 'D' })[]
.map(parseRawFileDiffLine)
.filter(({status}) => ['A', 'M', 'D'].includes(status)) as
(ReturnType<typeof parseRawFileDiffLine> & { status: 'A' | 'M' | 'D' })[]

return result
}
Expand All @@ -135,11 +136,13 @@ export async function getCommitDetails(ref: string = 'HEAD'): Promise<CommitDeta
export async function getCacheDetails(): Promise<CacheDetails> {
const result = <CacheDetails>{}

const diffOutputFileLines = await exec('git diff --cached --raw --cc --diff-filter=AMD')
const diffOutputFileLines = await exec('git diff --cached --raw --cc')
.then(({stdout}) => stdout.toString().split('\n').filter(Boolean))

result.files = diffOutputFileLines
.map(parseRawFileDiffLine) satisfies RawFileDiff[] as (RawFileDiff & { status: 'A' | 'M' | 'D' })[]
.map(parseRawFileDiffLine)
.filter(({status}) => ['A', 'M', 'D'].includes(status)) as
(ReturnType<typeof parseRawFileDiffLine> & { status: 'A' | 'M' | 'D' })[]

return result
}
Expand Down
89 changes: 49 additions & 40 deletions lib/github.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import * as github from '@actions/github'
import pLimit from 'p-limit'

const GITHUB_API_RATE_LIMITER = pLimit(10)

/**
* Create a commit authored and committed by octokit token identity.
Expand All @@ -12,51 +15,56 @@ export async function createCommit(octokit: ReturnType<typeof github.getOctokit>
owner: string,
repo: string
}, args: CreateCommitArgs) {
console.debug('creating file blobs ...')
const commitTreeBlobs = await Promise.all(args.files.map(async ({path, mode, status, loadContent}) => {
switch (status) {
case 'A':
case 'M': {
console.debug(' ', path, '...')
const content = await loadContent()
const blob = await octokit.rest.git.createBlob({
...repository,
content: content.toString('base64'),
encoding: 'base64',
}).then(({data}) => data)
console.debug(' ', path, '>', blob.sha)
return <TreeFile>{
path,
mode,
sha: blob.sha,
type: 'blob',
}
}
case 'D':
return <TreeFile>{
path,
mode: '100644',
sha: null,
type: 'blob',
}
default:
throw new Error(`Unexpected file status: ${status}`)
}
}))
console.debug('creating commit ...')

console.debug('creating commit tree ...')
const commitTree = await octokit.rest.git.createTree({
...repository,
base_tree: args.parents[0],
tree: commitTreeBlobs,
}).then(({data}) => data)
console.debug('commit tree', '>', commitTree.sha)
let commitTreeSha = args.tree
if (args.files.length > 0) {
console.debug(' creating commit tree ...')

console.debug(' creating file blobs ...')
const commitTreeBlobs = await Promise.all(args.files.map(async ({path, mode, status, loadContent}) => {
switch (status) {
case 'A':
case 'M': {
console.debug(' ', path, '...')
const content = await loadContent()
const blob = await GITHUB_API_RATE_LIMITER(() => octokit.rest.git.createBlob({
...repository,
content: content.toString('base64'),
encoding: 'base64',
})).then(({data}) => data)
console.debug(' ', path, '->', blob.sha)
return <TreeFile>{
path,
mode,
sha: blob.sha,
type: 'blob',
}
}
case 'D':
return {
path,
mode: '100644',
sha: null,
type: 'blob',
} satisfies TreeFile
default:
throw new Error(`Unexpected file status: ${status}`)
}
}))

commitTreeSha = await octokit.rest.git.createTree({
...repository,
base_tree: args.parents[0],
tree: commitTreeBlobs,
}).then(({data}) => data.sha)
console.debug(' commit tree', '->', commitTreeSha)
}

console.debug('creating commit ...')
const commit = await octokit.rest.git.createCommit({
...repository,
parents: args.parents,
tree: commitTree.sha,
tree: commitTreeSha,
message: args.subject + '\n\n' + args.body,

// DO NOT set author or committer otherwise commit will not be signed
Expand Down Expand Up @@ -98,6 +106,7 @@ export type CreateCommitArgs = {
subject: string
body: string
parents: string[]
tree: string,
files: {
path: string
mode: '100644' | '100755' | '040000' | '160000' | '120000' | string
Expand Down
Loading

0 comments on commit fb12704

Please sign in to comment.