Skip to content

Commit

Permalink
fix!: change behavior related to branches and pulling
Browse files Browse the repository at this point in the history
I would have opened a pull request, but for some reason GitHub was
preventing me to do so...
Anyway, here's the description:

This PR makes the action run on whatever has been checked out by
previous steps.
This is better because it makes the action faster and solves some issues
that are caused by it pulling from the wrong ref (especially in PRs that
involve a fork).

The previous branch-related inputs have been removed, in favor of a new
input called `new-branch`: this will allow you to create a new branch
from the starting ref. If the branch already exists the action will try
to push anyway, but that can result in the remote rejecting the push
because it's not straightforward. For info on how to handle this
situation, please check out the README.

In conclusion, this PR makes the user responsible for handling branches,
while still keeping an easy-to-use option to create new branches for
those who need it: just make sure that you also consider when the branch
already exists.

Fixes #327
  • Loading branch information
EndBug committed Jan 20, 2022
2 parents 90c92e1 + bea30d7 commit 6fdb34e
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 359 deletions.
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@ Add a step like this to your workflow:
# Default: depends on the default_author input
author_email: mail@example.com

# The name of the branch to use, if different from the one that triggered the workflow.
# Default: the branch that triggered the run
branch: some-branch

# How the action should behave when the targeted branch is missing: "create" will create a new one on the remote, "throw" will exit
# Default: throw
branch_mode: create

# The name of the custom committer you want to use, if different from the author of the commit.
# Default: the name of the author (set with either author_name or default_author)
committer_name: Committer Name
Expand All @@ -58,6 +50,10 @@ Add a step like this to your workflow:
# Default: 'Commit from GitHub Actions (name of the workflow)'
message: 'Your commit message'

# If this input is set, the action will push the commit to a new branch with this name.
# Default: ''
new_branch: custom-new-branch

# The way the action should handle pathspec errors from the add and remove commands. Three options are available:
# - ignore -> errors will be logged but the step won't fail
# - exitImmediately -> the action will stop right away, and the step will fail
Expand Down Expand Up @@ -101,7 +97,7 @@ You can also use JSON or YAML arrays (e.g. `'["first", "second"]'`, `"['first',

### Pushing

By default the action runs the following command: `git push origin ${branch input} --set-upstream`. You can use the `push` input to modify this behavior, here's what you can set it to:
By default the action runs the following command: `git push origin ${new_branch input} --set-upstream`. You can use the `push` input to modify this behavior, here's what you can set it to:

- `true`: this is the default value, it will behave as usual.
- `false`: this prevents the action from pushing at all, no `git push` command is run.
Expand All @@ -110,6 +106,20 @@ By default the action runs the following command: `git push origin ${branch inpu

One way to use this is if you want to force push to a branch of your repo: you'll need to set the `push` input to, for example, `origin yourBranch --force`.

### Creating a new branch

If you want the action to commit in a new branch, you can use the `new_branch` input.

Please note that if the branch exists, the action will still try push to it, but it's possible that the push will be rejected by the remote as non-straightforward.

If that's the case, you need to make sure that the branch you want to commit to is already checked out before you run the action.
If you're **really** sure that you want to commit to that branch, you can also force-push by setting the `push` input to something like `origin yourBranchName --set-upstream --force`.

If you want to commit files "across different branches", here are two ways to do it:

1. You can check them out in two different directories, generate your files, move them to your destination and then run `add-and-commit` in the destination directory using the `cwd` input.
2. You can manually commit those files with `git` commands as you would on your machine. There are several ways to do this depending on the scenario. One of them if to stash your changes, checkout the destination branch, and popping the stash. You can then use the `add-and-commit` action as usual. Please note that this is just an example and may not work for you, since your use case may be different.

### Tagging

You can use the `tag` option to enter the arguments for a `git add` command. In order for the action to isolate the tag name from the rest of the arguments, it should be the first word not preceded by an hyphen (e.g. `-a tag-name -m "some other stuff"` is ok).
Expand Down
12 changes: 4 additions & 8 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@ inputs:
author_email:
description: The email of the user that will be displayed as the author of the commit
required: false
branch:
description: Name of the branch to use, if different from the one that triggered the workflow
required: false
branch_mode:
description: How the action should behave when the targeted branch is missing
required: false
default: throw
commit:
description: Additional arguments for the git commit command
required: false
Expand All @@ -39,12 +32,15 @@ inputs:
message:
description: The message for the commit
required: false
new_branch:
description: The name of the branch to create.
required: false
pathspec_error_handling:
description: The way the action should handle pathspec errors from the add and remove commands.
required: false
default: ignore
pull:
description: Arguments for the git pull command. Use NO-PULL to avoid the action pulling at all.
description: Arguments for the git pull command. By default, the action does not pull.
required: false
push:
description: Whether to push the commit and, if any, its tags to the repo. It can also be used to set the git push arguments (more info in the README)
Expand Down
4 changes: 2 additions & 2 deletions lib/index.js

Large diffs are not rendered by default.

232 changes: 232 additions & 0 deletions src/io.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import * as core from '@actions/core'
import { getUserInfo, parseInputArray } from './util'

interface InputTypes {
add: string
author_name: string
author_email: string
commit: string | undefined
committer_name: string
committer_email: string
cwd: string
default_author: 'github_actor' | 'user_info' | 'github_actions'
message: string
new_branch: string | undefined
pathspec_error_handling: 'ignore' | 'exitImmediately' | 'exitAtEnd'
pull: string | undefined
push: string
remove: string | undefined
tag: string | undefined

github_token: string | undefined
}
export type input = keyof InputTypes

interface OutputTypes {
committed: 'true' | 'false'
commit_sha: string | undefined
pushed: 'true' | 'false'
tagged: 'true' | 'false'
}
export type output = keyof OutputTypes

export const outputs: OutputTypes = {
committed: 'false',
commit_sha: undefined,
pushed: 'false',
tagged: 'false'
}
// Setup default output values
Object.entries(outputs).forEach(([name, value]) => core.setOutput(name, value))

export function getInput<T extends input>(name: T, parseAsBool: true): boolean
export function getInput<T extends input>(
name: T,
parseAsBool?: false
): InputTypes[T]
export function getInput<T extends input>(
name: T,
parseAsBool = false
): InputTypes[T] | boolean {
if (parseAsBool) return core.getBooleanInput(name)
// @ts-expect-error
return core.getInput(name)
}

export function setOutput<T extends output>(name: T, value: OutputTypes[T]) {
core.debug(`Setting output: ${name}=${value}`)
outputs[name] = value
core.setOutput(name, value)
}

export function logOutputs() {
core.startGroup('Outputs')
for (const key in outputs) {
core.info(`${key}: ${outputs[key]}`)
}
core.endGroup()
}

export async function checkInputs() {
function setInput(input: input, value: string | undefined) {
if (value) return (process.env[`INPUT_${input.toUpperCase()}`] = value)
else return delete process.env[`INPUT_${input.toUpperCase()}`]
}
function setDefault(input: input, value: string) {
if (!getInput(input)) setInput(input, value)
return getInput(input)
}

// #region add, remove
if (!getInput('add') && !getInput('remove'))
throw new Error(
"Both 'add' and 'remove' are empty, the action has nothing to do."
)

if (getInput('add')) {
const parsed = parseInputArray(getInput('add'))
if (parsed.length == 1)
core.info('Add input parsed as single string, running 1 git add command.')
else if (parsed.length > 1)
core.info(
`Add input parsed as string array, running ${parsed.length} git add commands.`
)
else core.setFailed('Add input: array length < 1')
}
if (getInput('remove')) {
const parsed = parseInputArray(getInput('remove') || '')
if (parsed.length == 1)
core.info(
'Remove input parsed as single string, running 1 git rm command.'
)
else if (parsed.length > 1)
core.info(
`Remove input parsed as string array, running ${parsed.length} git rm commands.`
)
else core.setFailed('Remove input: array length < 1')
}
// #endregion

// #region default_author
const default_author_valid = ['github_actor', 'user_info', 'github_actions']
if (!default_author_valid.includes(getInput('default_author')))
throw new Error(
`'${getInput(
'default_author'
)}' is not a valid value for default_author. Valid values: ${default_author_valid.join(
', '
)}`
)
// #endregion

// #region author_name, author_email
let name, email
switch (getInput('default_author')) {
case 'github_actor': {
name = process.env.GITHUB_ACTOR
email = `${process.env.GITHUB_ACTOR}@users.noreply.github.com`
break
}

case 'user_info': {
if (!getInput('author_name') || !getInput('author_email')) {
const res = await getUserInfo(process.env.GITHUB_ACTOR)
if (!res?.name)
core.warning("Couldn't fetch author name, filling with github_actor.")
if (!res?.email)
core.warning(
"Couldn't fetch author email, filling with github_actor."
)

res?.name && (name = res?.name)
res?.email && (email = res.email)
if (name && email) break
}

!name && (name = process.env.GITHUB_ACTOR)
!email && (email = `${process.env.GITHUB_ACTOR}@users.noreply.github.com`)
break
}

case 'github_actions': {
name = 'github-actions'
email = '41898282+github-actions[bot]@users.noreply.github.com'
break
}

default:
throw new Error(
'This should not happen, please contact the author of this action. (checkInputs.author)'
)
}

setDefault('author_name', name)
setDefault('author_email', email)
core.info(
`> Using '${getInput('author_name')} <${getInput(
'author_email'
)}>' as author.`
)
// #endregion

// #region committer_name, committer_email
if (getInput('committer_name') || getInput('committer_email'))
core.info(
`> Using custom committer info: ${
getInput('committer_name') ||
getInput('author_name') + ' [from author info]'
} <${
getInput('committer_email') ||
getInput('author_email') + ' [from author info]'
}>`
)

setDefault('committer_name', getInput('author_name'))
setDefault('committer_email', getInput('author_email'))
core.debug(
`Committer: ${getInput('committer_name')} <${getInput('committer_email')}>`
)
// #endregion

// #region message
setDefault(
'message',
`Commit from GitHub Actions (${process.env.GITHUB_WORKFLOW})`
)
core.info(`> Using "${getInput('message')}" as commit message.`)
// #endregion

// #region pathspec_error_handling
const peh_valid = ['ignore', 'exitImmediately', 'exitAtEnd']
if (!peh_valid.includes(getInput('pathspec_error_handling')))
throw new Error(
`"${getInput(
'pathspec_error_handling'
)}" is not a valid value for the 'pathspec_error_handling' input. Valid values are: ${peh_valid.join(
', '
)}`
)
// #endregion

// #region push
if (getInput('push')) {
// It has to be either 'true', 'false', or any other string (use as arguments)
let value: string | boolean

try {
value = getInput('push', true)
} catch {
value = getInput('push')
}

core.debug(`Current push option: '${value}' (parsed as ${typeof value})`)
}
// #endregion

// #region github_token
if (!getInput('github_token'))
core.warning(
'No github_token has been detected, the action may fail if it needs to use the API'
)
// #endregion
}
Loading

0 comments on commit 6fdb34e

Please sign in to comment.