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
41 changes: 40 additions & 1 deletion apps/server/src/git/Layers/GitCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const makeIsolatedGitCore = (gitService: GitServiceShape) =>
return {
status: (input) => core.status(input),
statusDetails: (cwd) => core.statusDetails(cwd),
prepareCommitContext: (cwd) => core.prepareCommitContext(cwd),
prepareCommitContext: (cwd, filePaths?) => core.prepareCommitContext(cwd, filePaths),
commit: (cwd, subject, body) => core.commit(cwd, subject, body),
pushCurrentBranch: (cwd, fallbackBranch) => core.pushCurrentBranch(cwd, fallbackBranch),
pullCurrentBranch: (cwd) => core.pullCurrentBranch(cwd),
Expand Down Expand Up @@ -1711,6 +1711,45 @@ it.layer(TestLayer)("git integration", (it) => {
}),
);

it.effect("prepareCommitContext stages only selected files when filePaths provided", () =>
Effect.gen(function* () {
const tmp = yield* makeTmpDir();
yield* initRepoWithCommit(tmp);
const core = yield* GitCore;

yield* writeTextFile(path.join(tmp, "a.txt"), "file a\n");
yield* writeTextFile(path.join(tmp, "b.txt"), "file b\n");

const context = yield* core.prepareCommitContext(tmp, ["a.txt"]);
expect(context).not.toBeNull();
expect(context!.stagedSummary).toContain("a.txt");
expect(context!.stagedSummary).not.toContain("b.txt");

yield* core.commit(tmp, "Add only a.txt", "");

// b.txt should still be untracked after commit
const statusAfter = yield* git(tmp, ["status", "--porcelain"]);
expect(statusAfter).toContain("b.txt");
expect(statusAfter).not.toContain("a.txt");
}),
);

it.effect("prepareCommitContext stages everything when filePaths is undefined", () =>
Effect.gen(function* () {
const tmp = yield* makeTmpDir();
yield* initRepoWithCommit(tmp);
const core = yield* GitCore;

yield* writeTextFile(path.join(tmp, "a.txt"), "file a\n");
yield* writeTextFile(path.join(tmp, "b.txt"), "file b\n");

const context = yield* core.prepareCommitContext(tmp);
expect(context).not.toBeNull();
expect(context!.stagedSummary).toContain("a.txt");
expect(context!.stagedSummary).toContain("b.txt");
}),
);

it.effect("pushes with upstream setup and then skips when up to date", () =>
Effect.gen(function* () {
const tmp = yield* makeTmpDir();
Expand Down
16 changes: 14 additions & 2 deletions apps/server/src/git/Layers/GitCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -766,9 +766,21 @@ const makeGitCore = Effect.gen(function* () {
})),
);

const prepareCommitContext: GitCoreShape["prepareCommitContext"] = (cwd) =>
const prepareCommitContext: GitCoreShape["prepareCommitContext"] = (cwd, filePaths) =>
Effect.gen(function* () {
yield* runGit("GitCore.prepareCommitContext.addAll", cwd, ["add", "-A"]);
if (filePaths && filePaths.length > 0) {
yield* runGit("GitCore.prepareCommitContext.reset", cwd, ["reset"]).pipe(
Effect.catch(() => Effect.void),
);
yield* runGit("GitCore.prepareCommitContext.addSelected", cwd, [
"add",
"-A",
"--",
...filePaths,
]);
} else {
yield* runGit("GitCore.prepareCommitContext.addAll", cwd, ["add", "-A"]);
}

const stagedSummary = yield* runGitStdout("GitCore.prepareCommitContext.stagedSummary", cwd, [
"diff",
Expand Down
26 changes: 26 additions & 0 deletions apps/server/src/git/Layers/GitManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ function runStackedAction(
action: "commit" | "commit_push" | "commit_push_pr";
commitMessage?: string;
featureBranch?: boolean;
filePaths?: readonly string[];
},
) {
return manager.runStackedAction(input);
Expand Down Expand Up @@ -767,6 +768,31 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
}),
);

it.effect("commits only selected files when filePaths is provided", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* initRepo(repoDir);
fs.writeFileSync(path.join(repoDir, "a.txt"), "file a\n");
fs.writeFileSync(path.join(repoDir, "b.txt"), "file b\n");

const { manager } = yield* makeManager();
const result = yield* runStackedAction(manager, {
cwd: repoDir,
action: "commit",
filePaths: ["a.txt"],
});

expect(result.commit.status).toBe("created");

// b.txt should remain in the working tree
const statusStdout = yield* runGit(repoDir, ["status", "--porcelain"]).pipe(
Effect.map((r) => r.stdout),
);
expect(statusStdout).toContain("b.txt");
expect(statusStdout).not.toContain("a.txt");
}),
);

it.effect("creates feature branch, commits, and pushes with featureBranch option", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
Expand Down
15 changes: 13 additions & 2 deletions apps/server/src/git/Layers/GitManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,9 +638,10 @@ export const makeGitManager = Effect.gen(function* () {
commitMessage?: string;
/** When true, also produce a semantic feature branch name. */
includeBranch?: boolean;
filePaths?: readonly string[];
}) =>
Effect.gen(function* () {
const context = yield* gitCore.prepareCommitContext(input.cwd);
const context = yield* gitCore.prepareCommitContext(input.cwd, input.filePaths);
if (!context) {
return null;
}
Expand Down Expand Up @@ -680,6 +681,7 @@ export const makeGitManager = Effect.gen(function* () {
branch: string | null,
commitMessage?: string,
preResolvedSuggestion?: CommitAndBranchSuggestion,
filePaths?: readonly string[],
) =>
Effect.gen(function* () {
const suggestion =
Expand All @@ -688,6 +690,7 @@ export const makeGitManager = Effect.gen(function* () {
cwd,
branch,
...(commitMessage ? { commitMessage } : {}),
...(filePaths ? { filePaths } : {}),
}));
if (!suggestion) {
return { status: "skipped_no_changes" as const };
Expand Down Expand Up @@ -964,12 +967,18 @@ export const makeGitManager = Effect.gen(function* () {
},
);

const runFeatureBranchStep = (cwd: string, branch: string | null, commitMessage?: string) =>
const runFeatureBranchStep = (
cwd: string,
branch: string | null,
commitMessage?: string,
filePaths?: readonly string[],
) =>
Effect.gen(function* () {
const suggestion = yield* resolveCommitAndBranchSuggestion({
cwd,
branch,
...(commitMessage ? { commitMessage } : {}),
...(filePaths ? { filePaths } : {}),
includeBranch: true,
});
if (!suggestion) {
Expand Down Expand Up @@ -1018,6 +1027,7 @@ export const makeGitManager = Effect.gen(function* () {
input.cwd,
initialStatus.branch,
input.commitMessage,
input.filePaths,
);
branchStep = result.branchStep;
commitMessageForStep = result.resolvedCommitMessage;
Expand All @@ -1033,6 +1043,7 @@ export const makeGitManager = Effect.gen(function* () {
currentBranch,
commitMessageForStep,
preResolvedCommitSuggestion,
input.filePaths,
);

const push = wantsPush
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/git/Services/GitCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export interface GitCoreShape {
*/
readonly prepareCommitContext: (
cwd: string,
filePaths?: readonly string[],
) => Effect.Effect<GitPreparedCommitContext | null, GitCommandError>;

/**
Expand Down
Loading