authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-10-04 09:20:04-03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-04 09:20:04-03:00
log4ee6bfd50ef4a089440d5c3662db2298da9cc50e
tree06527ddd71ba6e525a67dfb45b66f4d689584fea
parent3b0093d7164a59bea721e2582e385e984561cd41
signature Signed by PGP key B5690EEEBB952194

fix(server): preserve staging during commit message generation (#15532)


5 files changed, 264 insertions(+), 44 deletions(-)

apps/server/src/git/GitManager.test.ts+47
...@@ -3116,6 +3116,53 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -3116,6 +3116,53 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
3116 }),3116 }),
3117 );3117 );
31183118
3119 it.effect.each([undefined, ["README.md"]])(
3120 "a failed generation preserves staging changed while generating (paths: %s)",
3121 (filePaths) =>
3122 Effect.gen(function* () {
3123 const repoDir = yield* makeTempDir("t3code-git-manager-");
3124 yield* initRepo(repoDir);
3125 NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\nstaged\n");
3126 yield* runGit(repoDir, ["add", "README.md"]);
3127 NodeFS.appendFileSync(NodePath.join(repoDir, "README.md"), "unstaged\n");
3128 NodeFS.writeFileSync(NodePath.join(repoDir, "untracked.txt"), "untracked\n");
3129 const indexBefore = NodeFS.readFileSync(NodePath.join(repoDir, ".git/index"));
3130 const headBefore = (yield* runGit(repoDir, ["rev-parse", "HEAD"])).stdout;
3131 const gitDriver = yield* GitVcsDriver.GitVcsDriver;
3132 let indexDuring: Buffer | undefined;
3133 let indexAfterUserStage: Buffer | undefined;
3134 const { manager } = yield* makeManager({
3135 textGeneration: {
3136 generateCommitMessage: () =>
3137 Effect.gen(function* () {
3138 indexDuring = NodeFS.readFileSync(NodePath.join(repoDir, ".git/index"));
3139 yield* runGit(repoDir, ["add", "untracked.txt"]).pipe(
3140 Effect.provideService(GitVcsDriver.GitVcsDriver, gitDriver),
3141 Effect.orDie,
3142 );
3143 indexAfterUserStage = NodeFS.readFileSync(NodePath.join(repoDir, ".git/index"));
3144 return yield* new TextGenerationError({
3145 operation: "generateCommitMessage",
3146 detail: "Provider rejected generation",
3147 });
3148 }),
3149 },
3150 });
3151 const result = yield* runStackedAction(manager, {
3152 cwd: repoDir,
3153 action: "commit",
3154 ...(filePaths ? { filePaths } : {}),
3155 }).pipe(Effect.result);
3156 expect(Result.isFailure(result)).toBe(true);
3157 expect(indexDuring).toEqual(indexBefore);
3158 expect(NodeFS.readFileSync(NodePath.join(repoDir, ".git/index"))).toEqual(
3159 indexAfterUserStage,
3160 );
3161 expect((yield* runGit(repoDir, ["rev-parse", "HEAD"])).stdout).toBe(headBefore);
3162 expect((yield* runGit(repoDir, ["diff"])).stdout).toContain("+unstaged");
3163 }),
3164 );
3165
3119 it.effect("uses custom commit message when provided", () =>3166 it.effect("uses custom commit message when provided", () =>
3120 Effect.gen(function* () {3167 Effect.gen(function* () {
3121 const repoDir = yield* makeTempDir("t3code-git-manager-");3168 const repoDir = yield* makeTempDir("t3code-git-manager-");
apps/server/src/git/GitManager.ts+1
...@@ -1985,6 +1985,7 @@ export const make = Effect.gen(function* () {...@@ -1985,6 +1985,7 @@ export const make = Effect.gen(function* () {
1985 : null;1985 : null;
1986 const { commitSha } = yield* gitCore.commit(cwd, suggestion.subject, suggestion.body, {1986 const { commitSha } = yield* gitCore.commit(cwd, suggestion.subject, suggestion.body, {
1987 timeoutMs: COMMIT_TIMEOUT_MS,1987 timeoutMs: COMMIT_TIMEOUT_MS,
1988 stage: filePaths ? { filePaths } : {},
1988 ...(commitProgress ? { progress: commitProgress } : {}),1989 ...(commitProgress ? { progress: commitProgress } : {}),
1989 });1990 });
1990 if (currentHookName !== null) {1991 if (currentHookName !== null) {
apps/server/src/vcs/GitVcsDriver.ts+2
...@@ -174,6 +174,8 @@ export interface GitCommitProgress {...@@ -174,6 +174,8 @@ export interface GitCommitProgress {
174export interface GitCommitOptions {174export interface GitCommitOptions {
175 readonly timeoutMs?: number;175 readonly timeoutMs?: number;
176 readonly progress?: GitCommitProgress;176 readonly progress?: GitCommitProgress;
177 /** Stage the current working tree immediately before committing. */
178 readonly stage?: { readonly filePaths?: readonly string[] };
177}179}
178180
179export interface GitDeleteLocalBranchInput {181export interface GitDeleteLocalBranchInput {
apps/server/src/vcs/GitVcsDriverCore.test.ts+92-9
...@@ -3052,7 +3052,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {...@@ -3052,7 +3052,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
3052 });3052 });
30533053
3054 describe("commit context", () => {3054 describe("commit context", () => {
3055 it.effect("stages selected files and commits only those files", () =>3055 it.effect("prepares selected files without staging and commits only those files", () =>
3056 Effect.gen(function* () {3056 Effect.gen(function* () {
3057 const cwd = yield* makeTmpDir();3057 const cwd = yield* makeTmpDir();
3058 yield* initRepoWithCommit(cwd);3058 yield* initRepoWithCommit(cwd);
...@@ -3064,8 +3064,9 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {...@@ -3064,8 +3064,9 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
3064 const context = yield* driver.prepareCommitContext(cwd, ["a.txt"]);3064 const context = yield* driver.prepareCommitContext(cwd, ["a.txt"]);
3065 assert.include(context?.stagedSummary ?? "", "a.txt");3065 assert.include(context?.stagedSummary ?? "", "a.txt");
3066 assert.notInclude(context?.stagedSummary ?? "", "b.txt");3066 assert.notInclude(context?.stagedSummary ?? "", "b.txt");
3067 assert.equal(yield* git(cwd, ["diff", "--cached", "--name-only"]), "");
30673068
3068 const commit = yield* driver.commit(cwd, "Add a", "");3069 const commit = yield* driver.commit(cwd, "Add a", "", { stage: { filePaths: ["a.txt"] } });
3069 assert.match(commit.commitSha, /^[a-f0-9]{40}$/);3070 assert.match(commit.commitSha, /^[a-f0-9]{40}$/);
3070 assert.equal(yield* git(cwd, ["log", "-1", "--pretty=%s"]), "Add a");3071 assert.equal(yield* git(cwd, ["log", "-1", "--pretty=%s"]), "Add a");
30713072
...@@ -3084,14 +3085,96 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {...@@ -3084,14 +3085,96 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
3084 yield* writeTextFile(cwd, "selected[1].txt", "literal\n");3085 yield* writeTextFile(cwd, "selected[1].txt", "literal\n");
3085 yield* writeTextFile(cwd, "selected1.txt", "pattern match\n");3086 yield* writeTextFile(cwd, "selected1.txt", "pattern match\n");
30863087
3087 yield* driver.prepareCommitContext(cwd, ["selected[1].txt"]);3088 const context = yield* driver.prepareCommitContext(cwd, ["selected[1].txt"]);
30883089
3089 assert.equal(yield* git(cwd, ["diff", "--cached", "--name-only"]), "selected[1].txt");3090 assert.include(context?.stagedSummary ?? "", "selected[1].txt");
3091 assert.notInclude(context?.stagedSummary ?? "", "selected1.txt");
3092 assert.equal(yield* git(cwd, ["diff", "--cached", "--name-only"]), "");
3093 yield* driver.commit(cwd, "Add literal path", "", {
3094 stage: { filePaths: ["selected[1].txt"] },
3095 });
3096 assert.equal(
3097 yield* git(cwd, ["show", "--pretty=", "--name-only", "HEAD"]),
3098 "selected[1].txt",
3099 );
30903100
3091 const status = yield* git(cwd, ["status", "--porcelain"]);3101 const status = yield* git(cwd, ["status", "--porcelain"]);
3092 assert.include(status, "?? selected1.txt");3102 assert.include(status, "?? selected1.txt");
3093 }),3103 }),
3094 );3104 );
3105
3106 it.effect.each([false, true])(
3107 "preserves partial staging and split-index files (split: %s)",
3108 (split) =>
3109 Effect.gen(function* () {
3110 const cwd = yield* makeTmpDir();
3111 yield* initRepoWithCommit(cwd);
3112 if (split) yield* git(cwd, ["config", "core.splitIndex", "true"]);
3113 yield* writeTextFile(cwd, "README.md", "# test\nstaged\n");
3114 yield* git(cwd, ["add", "README.md"]);
3115 yield* writeTextFile(cwd, "README.md", "# test\nstaged\nunstaged\n");
3116 yield* writeTextFile(cwd, "untracked.txt", "untracked\n");
3117 yield* writeTextFile(cwd, ".gitignore", "ignored.txt\n");
3118 yield* writeTextFile(cwd, "ignored.txt", "ignored\n");
3119 const before = NodeFS.readFileSync(`${cwd}/.git/index`);
3120 const shared = NodeFS.readdirSync(`${cwd}/.git`).filter((name) =>
3121 name.startsWith("sharedindex."),
3122 );
3123 const driver = yield* GitVcsDriver.GitVcsDriver;
3124 for (const paths of [undefined, ["README.md"]]) {
3125 const context = yield* driver.prepareCommitContext(cwd, paths);
3126 assert.include(context?.stagedPatch ?? "", "unstaged");
3127 assert.notInclude(context?.stagedSummary ?? "", "ignored.txt");
3128 assert.deepEqual(NodeFS.readFileSync(`${cwd}/.git/index`), before);
3129 assert.deepEqual(
3130 NodeFS.readdirSync(`${cwd}/.git`).filter((name) => name.startsWith("sharedindex.")),
3131 shared,
3132 );
3133 }
3134 }),
3135 );
3136
3137 it.effect("prepares an unborn repository without creating a real index", () =>
3138 Effect.gen(function* () {
3139 const cwd = yield* makeTmpDir();
3140 const driver = yield* GitVcsDriver.GitVcsDriver;
3141 yield* driver.initRepo({ cwd });
3142 yield* git(cwd, ["config", "user.email", "test@test.com"]);
3143 yield* git(cwd, ["config", "user.name", "Test"]);
3144 yield* writeTextFile(cwd, "initial.txt", "initial\n");
3145 for (const paths of [undefined, ["initial.txt"]]) {
3146 const context = yield* driver.prepareCommitContext(cwd, paths);
3147 assert.include(context?.stagedSummary ?? "", "initial.txt");
3148 assert.isFalse(NodeFS.existsSync(`${cwd}/.git/index`));
3149 }
3150 yield* driver.commit(cwd, "Initial", "", { stage: { filePaths: ["initial.txt"] } });
3151 assert.equal(yield* git(cwd, ["show", "HEAD:initial.txt"]), "initial");
3152 }),
3153 );
3154
3155 it.effect("preserves merge metadata while preparing selected and all files", () =>
3156 Effect.gen(function* () {
3157 const cwd = yield* makeTmpDir();
3158 const { initialBranch } = yield* initRepoWithCommit(cwd);
3159 yield* git(cwd, ["checkout", "-b", "feature"]);
3160 yield* writeTextFile(cwd, "feature.txt", "feature\n");
3161 yield* git(cwd, ["add", "."]);
3162 yield* git(cwd, ["commit", "-m", "Feature"]);
3163 yield* git(cwd, ["checkout", initialBranch]);
3164 yield* git(cwd, ["merge", "--no-commit", "--no-ff", "feature"]);
3165 yield* writeTextFile(cwd, "README.md", "# test\nmerge edit\n");
3166 const names = ["index", "MERGE_HEAD", "MERGE_MSG", "ORIG_HEAD"];
3167 const before = names.map((name) => NodeFS.readFileSync(`${cwd}/.git/${name}`));
3168 const driver = yield* GitVcsDriver.GitVcsDriver;
3169 for (const paths of [undefined, ["README.md"]]) {
3170 yield* driver.prepareCommitContext(cwd, paths);
3171 assert.deepEqual(
3172 names.map((name) => NodeFS.readFileSync(`${cwd}/.git/${name}`)),
3173 before,
3174 );
3175 }
3176 }),
3177 );
3095 });3178 });
30963179
3097 describe("remote operations", () => {3180 describe("remote operations", () => {
...@@ -3271,7 +3354,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {...@@ -3271,7 +3354,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
3271 refName: "feature/push",3354 refName: "feature/push",
3272 });3355 });
3273 yield* writeTextFile(cwd, "feature.txt", "feature\n");3356 yield* writeTextFile(cwd, "feature.txt", "feature\n");
3274 yield* (yield* GitVcsDriver.GitVcsDriver).prepareCommitContext(cwd);3357 yield* git(cwd, ["add", "-A"]);
3275 yield* (yield* GitVcsDriver.GitVcsDriver).commit(cwd, "Add feature", "");3358 yield* (yield* GitVcsDriver.GitVcsDriver).commit(cwd, "Add feature", "");
32763359
3277 const pushed = yield* (yield* GitVcsDriver.GitVcsDriver).pushCurrentBranch(cwd, null);3360 const pushed = yield* (yield* GitVcsDriver.GitVcsDriver).pushCurrentBranch(cwd, null);
...@@ -3343,7 +3426,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {...@@ -3343,7 +3426,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
3343 yield* git(cwd, ["remote", "add", "origin", remote]);3426 yield* git(cwd, ["remote", "add", "origin", remote]);
3344 yield* git(cwd, ["push", "-u", "origin", "main"]);3427 yield* git(cwd, ["push", "-u", "origin", "main"]);
3345 yield* writeTextFile(cwd, "upstream.txt", "upstream\n");3428 yield* writeTextFile(cwd, "upstream.txt", "upstream\n");
3346 yield* driver.prepareCommitContext(cwd);3429 yield* git(cwd, ["add", "-A"]);
3347 yield* driver.commit(cwd, "Add upstream update", "");3430 yield* driver.commit(cwd, "Add upstream update", "");
33483431
3349 const pushed = yield* driver.pushCurrentBranch(cwd, null);3432 const pushed = yield* driver.pushCurrentBranch(cwd, null);
...@@ -3384,7 +3467,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {...@@ -3384,7 +3467,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
3384 const devSha = yield* git(cwd, ["rev-parse", "HEAD"]);3467 const devSha = yield* git(cwd, ["rev-parse", "HEAD"]);
3385 yield* git(cwd, ["checkout", "-b", "feature/x", "origin/dev"]);3468 yield* git(cwd, ["checkout", "-b", "feature/x", "origin/dev"]);
3386 yield* writeTextFile(cwd, "feature.txt", "feature\n");3469 yield* writeTextFile(cwd, "feature.txt", "feature\n");
3387 yield* driver.prepareCommitContext(cwd);3470 yield* git(cwd, ["add", "-A"]);
3388 yield* driver.commit(cwd, "Add feature", "");3471 yield* driver.commit(cwd, "Add feature", "");
33893472
3390 const pushed = yield* driver.pushCurrentBranch(cwd, null);3473 const pushed = yield* driver.pushCurrentBranch(cwd, null);
...@@ -3418,7 +3501,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {...@@ -3418,7 +3501,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
3418 yield* git(cwd, ["checkout", "-b", "feature/y", "origin/main"]);3501 yield* git(cwd, ["checkout", "-b", "feature/y", "origin/main"]);
3419 yield* git(cwd, ["config", "branch.feature/y.gh-merge-base", "release/v2"]);3502 yield* git(cwd, ["config", "branch.feature/y.gh-merge-base", "release/v2"]);
3420 yield* writeTextFile(cwd, "feature.txt", "feature\n");3503 yield* writeTextFile(cwd, "feature.txt", "feature\n");
3421 yield* driver.prepareCommitContext(cwd);3504 yield* git(cwd, ["add", "-A"]);
3422 yield* driver.commit(cwd, "Add feature", "");3505 yield* driver.commit(cwd, "Add feature", "");
34233506
3424 const pushed = yield* driver.pushCurrentBranch(cwd, null);3507 const pushed = yield* driver.pushCurrentBranch(cwd, null);
...@@ -3456,7 +3539,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {...@@ -3456,7 +3539,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
3456 "upstream/effect-atom",3539 "upstream/effect-atom",
3457 );3540 );
3458 yield* writeTextFile(cwd, "alias.txt", "alias\n");3541 yield* writeTextFile(cwd, "alias.txt", "alias\n");
3459 yield* driver.prepareCommitContext(cwd);3542 yield* git(cwd, ["add", "-A"]);
3460 yield* driver.commit(cwd, "Add alias update", "");3543 yield* driver.commit(cwd, "Add alias update", "");
34613544
3462 const pushed = yield* driver.pushCurrentBranch(cwd, null);3545 const pushed = yield* driver.pushCurrentBranch(cwd, null);
apps/server/src/vcs/GitVcsDriverCore.ts+122-35
...@@ -2025,48 +2025,132 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -2025,48 +2025,132 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
2025 })),2025 })),
2026 );2026 );
20272027
2028 const prepareCommitContext: GitVcsDriver.GitVcsDriver["Service"]["prepareCommitContext"] =2028 const stageCommitChanges = Effect.fnUntraced(function* (
2029 Effect.fn("prepareCommitContext")(function* (cwd, filePaths) {2029 cwd: string,
2030 if (filePaths && filePaths.length > 0) {2030 filePaths?: readonly string[],
2031 yield* runGit("GitVcsDriver.prepareCommitContext.reset", cwd, ["reset"]).pipe(2031 env?: NodeJS.ProcessEnv,
2032 Effect.catchTags({2032 ) {
2033 GitCommandError: () => Effect.void,2033 const config = env
2034 }),2034 ? ["-c", "core.splitIndex=false", "-c", "splitIndex.sharedIndexExpire=never"]
2035 : [];
2036 if (filePaths && filePaths.length > 0) {
2037 if (env) {
2038 const head = yield* executeGit(
2039 "GitVcsDriver.prepareCommitContext.head",
2040 cwd,
2041 ["rev-parse", "--verify", "--quiet", "HEAD"],
2042 { allowNonZeroExit: true },
2043 );
2044 // Even with GIT_INDEX_FILE, reset clears the repository's merge state.
2045 yield* runGit(
2046 "GitVcsDriver.prepareCommitContext.readTree",
2047 cwd,
2048 [...config, "read-tree", ...(head.exitCode === 0 ? ["HEAD"] : ["--empty"])],
2049 { env },
2035 );2050 );
2036 yield* runGit("GitVcsDriver.prepareCommitContext.addSelected", cwd, [
2037 "--literal-pathspecs",
2038 "add",
2039 "-A",
2040 "--",
2041 ...filePaths,
2042 ]);
2043 } else {2051 } else {
2044 yield* runGit("GitVcsDriver.prepareCommitContext.addAll", cwd, ["add", "-A"]);2052 yield* runGit("GitVcsDriver.commit.reset", cwd, ["reset"]).pipe(
2053 Effect.catchTags({ GitCommandError: () => Effect.void }),
2054 );
2045 }2055 }
20462056 yield* runGit(
2047 const stagedSummary = yield* runGitStdout(2057 "GitVcsDriver.commit.addSelected",
2048 "GitVcsDriver.prepareCommitContext.stagedSummary",
2049 cwd,2058 cwd,
2050 ["diff", "--cached", "--name-status"],2059 [...config, "--literal-pathspecs", "add", "-A", "--", ...filePaths],
2051 ).pipe(Effect.map((stdout) => stdout.trim()));2060 env ? { env } : {},
2052 if (stagedSummary.length === 0) {2061 );
2053 return null;2062 } else {
2054 }2063 yield* runGit(
20552064 "GitVcsDriver.commit.addAll",
2056 const stagedPatch = yield* runGitStdoutWithOptions(
2057 "GitVcsDriver.prepareCommitContext.stagedPatch",
2058 cwd,2065 cwd,
2059 ["diff", "--no-ext-diff", "--cached", "--patch", "--minimal"],2066 [...config, "add", "-A"],
2060 {2067 env ? { env } : {},
2061 maxOutputBytes: PREPARED_COMMIT_PATCH_MAX_OUTPUT_BYTES,
2062 appendTruncationMarker: true,
2063 },
2064 );2068 );
2069 }
2070 });
20652071
2066 return {2072 const prepareCommitContext: GitVcsDriver.GitVcsDriver["Service"]["prepareCommitContext"] =
2067 stagedSummary,2073 Effect.fn("prepareCommitContext")(function* (cwd, filePaths) {
2068 stagedPatch,2074 return yield* Effect.scoped(
2069 };2075 Effect.gen(function* () {
2076 const indexValue = yield* runGitStdout(
2077 "GitVcsDriver.prepareCommitContext.indexPath",
2078 cwd,
2079 ["rev-parse", "--git-path", "index"],
2080 );
2081 const indexPath = path.resolve(cwd, indexValue.trim());
2082 const directory = yield* fileSystem.makeTempDirectoryScoped({
2083 prefix: "t3code-commit-index-",
2084 });
2085 const tempIndexPath = path.join(directory, "index");
2086 const env = { GIT_INDEX_FILE: tempIndexPath } satisfies NodeJS.ProcessEnv;
2087 const config = [
2088 "-c",
2089 "core.splitIndex=false",
2090 "-c",
2091 "splitIndex.sharedIndexExpire=never",
2092 ];
2093 if (yield* fileSystem.exists(indexPath)) {
2094 const { mtime } = yield* fileSystem.stat(indexPath);
2095 yield* fileSystem.copyFile(indexPath, tempIndexPath);
2096 const indexTime = Option.isSome(mtime)
2097 ? Math.max(0, Math.floor(mtime.value.getTime() / 1000))
2098 : 0;
2099 yield* fileSystem.utimes(tempIndexPath, indexTime, indexTime);
2100 yield* runGit(
2101 "GitVcsDriver.prepareCommitContext.expandSplitIndex",
2102 cwd,
2103 [...config, "update-index", "--no-split-index"],
2104 { env },
2105 );
2106 } else {
2107 yield* runGit(
2108 "GitVcsDriver.prepareCommitContext.emptyIndex",
2109 cwd,
2110 [...config, "read-tree", "--empty"],
2111 { env },
2112 );
2113 }
2114 yield* stageCommitChanges(cwd, filePaths, env);
2115
2116 const stagedSummary = yield* runGitStdoutWithOptions(
2117 "GitVcsDriver.prepareCommitContext.stagedSummary",
2118 cwd,
2119 ["diff", "--cached", "--name-status"],
2120 { env },
2121 ).pipe(Effect.map((stdout) => stdout.trim()));
2122 if (stagedSummary.length === 0) {
2123 return null;
2124 }
2125
2126 const stagedPatch = yield* runGitStdoutWithOptions(
2127 "GitVcsDriver.prepareCommitContext.stagedPatch",
2128 cwd,
2129 ["diff", "--no-ext-diff", "--cached", "--patch", "--minimal"],
2130 {
2131 env,
2132 maxOutputBytes: PREPARED_COMMIT_PATCH_MAX_OUTPUT_BYTES,
2133 appendTruncationMarker: true,
2134 },
2135 );
2136
2137 return {
2138 stagedSummary,
2139 stagedPatch,
2140 };
2141 }),
2142 ).pipe(
2143 Effect.catchTags({
2144 PlatformError: (cause) =>
2145 new GitCommandError({
2146 operation: "GitVcsDriver.prepareCommitContext",
2147 command: "git",
2148 cwd,
2149 detail: "Failed to prepare the temporary commit index.",
2150 cause,
2151 }),
2152 }),
2153 );
2070 });2154 });
20712155
2072 const commit: GitVcsDriver.GitVcsDriver["Service"]["commit"] = Effect.fn("commit")(function* (2156 const commit: GitVcsDriver.GitVcsDriver["Service"]["commit"] = Effect.fn("commit")(function* (
...@@ -2075,6 +2159,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -2075,6 +2159,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
2075 body,2159 body,
2076 options?: GitVcsDriver.GitCommitOptions,2160 options?: GitVcsDriver.GitCommitOptions,
2077 ) {2161 ) {
2162 if (options?.stage !== undefined) {
2163 yield* stageCommitChanges(cwd, options.stage.filePaths);
2164 }
2078 const args = ["commit", "-m", subject];2165 const args = ["commit", "-m", subject];
2079 const trimmedBody = body.trim();2166 const trimmedBody = body.trim();
2080 if (trimmedBody.length > 0) {2167 if (trimmedBody.length > 0) {