Make git add work consistently with subdirectories (#473)

* Make git add work consistently with subdirectories

Currently, depending on your `commitMode`,
when running the action with `cwd` set to a subdirectory of a repo,
the action will either:

- `git-cli`: add the files changed in the subdirectory only
- `github-api`: add files changed anywhere in the repository

This commit brings github-api in line with git-cli,
so that it only adds files in the current working directory.

* Explicitly set `cwd` for all exec calls

* Avoid using `ExecOptions` from `@actions/exec`

* Rename type

* remove redundant `filter`

---------

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
This commit is contained in:
Sam Lanning
2025-05-10 12:34:27 +02:00
committed by GitHub
co-authored by Mateusz Burzyński
parent 7f0270e8f4
commit 3c24abeab2
4 changed files with 74 additions and 38 deletions
+57 -34
View File
@@ -4,60 +4,75 @@ import * as github from "@actions/github";
import { commitChangesFromRepo } from "@changesets/ghcommit/git";
import { Octokit } from "./octokit";
const push = async (branch: string, { force }: { force?: boolean } = {}) => {
await exec(
"git",
["push", "origin", `HEAD:${branch}`, force && "--force"].filter<string>(
Boolean as any
)
);
type GitOptions = {
cwd: string;
};
const switchToMaybeExistingBranch = async (branch: string) => {
const push = async (branch: string, options: GitOptions) => {
await exec("git", ["push", "origin", `HEAD:${branch}`, "--force"], options);
};
const switchToMaybeExistingBranch = async (
branch: string,
options: GitOptions
) => {
let { stderr } = await getExecOutput("git", ["checkout", branch], {
ignoreReturnCode: true,
...options,
});
let isCreatingBranch = !stderr
.toString()
.includes(`Switched to a new branch '${branch}'`);
if (isCreatingBranch) {
await exec("git", ["checkout", "-b", branch]);
await exec("git", ["checkout", "-b", branch], options);
}
};
const reset = async (
pathSpec: string,
mode: "hard" | "soft" | "mixed" = "hard"
) => {
await exec("git", ["reset", `--${mode}`, pathSpec]);
const reset = async (pathSpec: string, options: GitOptions) => {
await exec("git", ["reset", `--hard`, pathSpec], options);
};
const commitAll = async (message: string) => {
await exec("git", ["add", "."]);
await exec("git", ["commit", "-m", message]);
const commitAll = async (message: string, options: GitOptions) => {
await exec("git", ["add", "."], options);
await exec("git", ["commit", "-m", message], options);
};
const checkIfClean = async (): Promise<boolean> => {
const { stdout } = await getExecOutput("git", ["status", "--porcelain"]);
const checkIfClean = async (options: GitOptions): Promise<boolean> => {
const { stdout } = await getExecOutput(
"git",
["status", "--porcelain"],
options
);
return !stdout.length;
};
export class Git {
octokit;
constructor(octokit?: Octokit) {
this.octokit = octokit;
readonly octokit: Octokit | null;
readonly cwd: string;
constructor(args: { octokit?: Octokit; cwd: string }) {
this.octokit = args.octokit ?? null;
this.cwd = args.cwd;
}
async setupUser() {
if (this.octokit) {
return;
}
await exec("git", ["config", "user.name", `"github-actions[bot]"`]);
await exec("git", [
"config",
"user.email",
`"41898282+github-actions[bot]@users.noreply.github.com"`,
]);
await exec("git", ["config", "user.name", `"github-actions[bot]"`], {
cwd: this.cwd,
});
await exec(
"git",
[
"config",
"user.email",
`"41898282+github-actions[bot]@users.noreply.github.com"`,
],
{
cwd: this.cwd,
}
);
}
async pushTag(tag: string) {
@@ -73,7 +88,7 @@ export class Git {
core.warning(`Failed to create tag ${tag}: ${err.message}`);
});
}
await exec("git", ["push", "origin", tag]);
await exec("git", ["push", "origin", tag], { cwd: this.cwd });
}
async prepareBranch(branch: string) {
@@ -81,12 +96,19 @@ export class Git {
// Preparing a new local branch is not necessary when using the API
return;
}
await switchToMaybeExistingBranch(branch);
await reset(github.context.sha);
await switchToMaybeExistingBranch(branch, { cwd: this.cwd });
await reset(github.context.sha, { cwd: this.cwd });
}
async pushChanges({ branch, message }: { branch: string; message: string }) {
if (this.octokit) {
/**
* Only add files form the current working directory
*
* This will emulate the behavior of `git add .`,
* used in {@link commitAll}.
*/
const addFromDirectory = this.cwd;
return commitChangesFromRepo({
octokit: this.octokit,
...github.context.repo,
@@ -95,12 +117,13 @@ export class Git {
base: {
commit: github.context.sha,
},
addFromDirectory,
force: true,
});
}
if (!(await checkIfClean())) {
await commitAll(message);
if (!(await checkIfClean({ cwd: this.cwd }))) {
await commitAll(message, { cwd: this.cwd });
}
await push(branch, { force: true });
await push(branch, { cwd: this.cwd });
}
}
+7 -2
View File
@@ -15,11 +15,12 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined;
return;
}
const inputCwd = core.getInput("cwd");
const inputCwd = getOptionalInput("cwd");
if (inputCwd) {
core.info("changing directory to the one given as the input");
process.chdir(inputCwd);
}
const cwd = inputCwd || process.cwd();
const octokit = setupOctokit(githubToken);
const commitMode = getOptionalInput("commitMode") ?? "git-cli";
@@ -27,7 +28,10 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined;
core.setFailed(`Invalid commit mode: ${commitMode}`);
return;
}
const git = new Git(commitMode === "github-api" ? octokit : undefined);
const git = new Git({
octokit: commitMode === "github-api" ? octokit : undefined,
cwd
});
let setupGitUser = core.getBooleanInput("setupGitUser");
@@ -100,6 +104,7 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined;
git,
octokit,
createGithubReleases: core.getBooleanInput("createGithubReleases"),
cwd,
});
if (result.published) {
+2 -2
View File
@@ -66,7 +66,7 @@ type PublishOptions = {
octokit: Octokit;
createGithubReleases: boolean;
git: Git;
cwd?: string;
cwd: string;
};
type PublishedPackage = { name: string; version: string };
@@ -85,7 +85,7 @@ export async function runPublish({
git,
octokit,
createGithubReleases,
cwd = process.cwd(),
cwd,
}: PublishOptions): Promise<PublishResult> {
let [publishCommand, ...publishArgs] = script.split(/\s+/);