From d79e32fcf7a1d957c6f77f1632021cb9cbff3534 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Sun, 9 May 2021 15:17:28 -0700 Subject: [PATCH 1/2] utils: add new `execute` utility This new function is similar to `run` except that it requires that the arguments be passed properly quoted and pre-split through an array. It also will not permit the prefixing a command for execution. The major difference between `run` and `execute` is that `execute` will not go through a shell, so parameter expansion, variable substitution, etc do not occur. Additionally, because there is no shell involved, a single process is created rather than two processes (one for the shell, and the actual process being invoked). This is required to enable using this action on Windows, where the process redirection does not work as the shell used is different. --- src/utils/action.js | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/utils/action.js b/src/utils/action.js index 4807170d..cc036dec 100644 --- a/src/utils/action.js +++ b/src/utils/action.js @@ -1,8 +1,9 @@ -const { execSync } = require("child_process"); +const { execSync, spawnSync } = require("child_process"); const core = require("@actions/core"); const RUN_OPTIONS_DEFAULTS = { dir: null, ignoreErrors: false, prefix: "" }; +const EXECUTE_OPTIONS_DEFAULTS = { dir: null, ignoreErrors: false }; /** * Returns the value for an environment variable. If the variable is required but doesn't have a @@ -72,7 +73,44 @@ function run(cmd, options) { } } +/** + * Executes the provided binary with the given arguments. + * @param {string} command - binary to execute + * @param {{dir: string, ignoreErrors: boolean}} [options] - {@see EXECUTE_OPTIONS_DEFAULTS} + * @returns {{status: number, stdout: string, stderr: string}} - Output of the command + */ +function execute(command, args, options) { + const optionsWithDefaults = { + ...EXECUTE_OPTIONS_DEFAULTS, + ...options, + }; + + core.debug(`${command} ${args.filter(e => e).join(" ")}`); + + const process = spawnSync(command, args.filter(e => e), { + cwd: optionsWithDefaults.dir, + encoding: "utf-8", + maxBuffer: 20 * 1024 * 1024, + }); + + const output = { + status: process.status, + stdout: process.stdout.trim(), + stderr: process.stderr.trim(), + }; + + core.debug(`Exit code: ${output.status}`); + core.debug(`Stdout: ${output.stdout}`); + core.debug(`Stderr: ${output.stderr}`); + + if (!optionsWithDefaults.ignoreErrors && child.status != 0) { + throw child.error; + } + return output; +} + module.exports = { + execute, getEnv, run, }; From 7dc7a973a7e0ff6424055009759d88836122698e Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Sun, 9 May 2021 15:20:29 -0700 Subject: [PATCH 2/2] git: avoid invoking the git process through a shell Use the newly minted `execute` to directly execute the `git` process. This should be a slight bit faster as no new shell process needs to be setup. --- src/git.js | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/git.js b/src/git.js index bfd84822..e52ef1f7 100644 --- a/src/git.js +++ b/src/git.js @@ -1,6 +1,6 @@ const core = require("@actions/core"); -const { run } = require("./utils/action"); +const { execute } = require("./utils/action"); /** * Fetches and checks out the remote Git branch (if it exists, the fork repository will be used) @@ -10,27 +10,23 @@ function checkOutRemoteBranch(context) { if (context.repository.hasFork) { // Fork: Add fork repo as remote core.info(`Adding "${context.repository.forkName}" fork as remote with Git`); - run( - `git remote add fork https://${context.actor}:${context.token}@github.com/${context.repository.forkName}.git`, - ); + execute("git", ["remote", "add", "fork", `https://${context.actor}:${context.token}@github.com/${context.repository.forkName}.git`]); } else { // No fork: Update remote URL to include auth information (so auto-fixes can be pushed) core.info(`Adding auth information to Git remote URL`); - run( - `git remote set-url origin https://${context.actor}:${context.token}@github.com/${context.repository.repoName}.git`, - ); + execute("git", ["remote", "set-url", "origin", `https://${context.actor}:${context.token}@github.com/${context.repository.repoName}.git`]); } const remote = context.repository.hasFork ? "fork" : "origin"; // Fetch remote branch core.info(`Fetching remote branch "${context.branch}"`); - run(`git fetch --no-tags --depth=1 ${remote} ${context.branch}`); + execute("git", ["fetch", "--no-tags", "--depth=1", remote, context.branch]); // Switch to remote branch core.info(`Switching to the "${context.branch}" branch`); - run(`git branch --force ${context.branch} --track ${remote}/${context.branch}`); - run(`git checkout ${context.branch}`); + execute("git", ["branch", "--force", context.branch, "--track", `${remote}/${context.branch}`]); + execute("git", ["checkout", context.branch]); } /** @@ -39,7 +35,7 @@ function checkOutRemoteBranch(context) { */ function commitChanges(message) { core.info(`Committing changes`); - run(`git commit -am "${message}"`); + execute("git", ["commit", "-am", `"${message}"`]); } /** @@ -47,7 +43,7 @@ function commitChanges(message) { * @returns {string} - Head SHA */ function getHeadSha() { - const sha = run("git rev-parse HEAD").stdout; + const sha = execute("git", ["rev-parse", "HEAD"]).stdout; core.info(`SHA of last commit is "${sha}"`); return sha; } @@ -57,7 +53,7 @@ function getHeadSha() { * @returns {boolean} - Boolean indicating whether changes exist */ function hasChanges() { - const output = run("git diff-index --name-status --exit-code HEAD --", { ignoreErrors: true }); + const output = execute("git", ["diff-index", "--name-status", "--exit-code", "HEAD", "--"], { ignoreErrors: true }); const hasChangedFiles = output.status === 1; core.info(`${hasChangedFiles ? "Changes" : "No changes"} found with Git`); return hasChangedFiles; @@ -68,7 +64,7 @@ function hasChanges() { */ function pushChanges() { core.info("Pushing changes with Git"); - run("git push"); + execute("git", ["push"]); } /** @@ -78,8 +74,8 @@ function pushChanges() { */ function setUserInfo(name, email) { core.info(`Setting Git user information`); - run(`git config --global user.name "${name}"`); - run(`git config --global user.email "${email}"`); + execute("git", ["config", "--global", "user.name", `"${name}"`]); + execute("git", ["config", "--global", "user.email", `"${email}"`]); } module.exports = {