| author | |
| committer | |
| log | 4ee6bfd50ef4a089440d5c3662db2298da9cc50e |
| tree | 06527ddd71ba6e525a67dfb45b66f4d689584fea |
| parent | 3b0093d7164a59bea721e2582e385e984561cd41 |
| signature | Signed by PGP key B5690EEEBB952194 |
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 | 3116 | }), |
| 3117 | 3117 | ); |
| 3118 | 3118 | |
| 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 | 3166 | it.effect("uses custom commit message when provided", () => |
| 3120 | 3167 | Effect.gen(function* () { |
| 3121 | 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 | 1985 | : null; |
| 1986 | 1986 | const { commitSha } = yield* gitCore.commit(cwd, suggestion.subject, suggestion.body, { |
| 1987 | 1987 | timeoutMs: COMMIT_TIMEOUT_MS, |
| 1988 | stage: filePaths ? { filePaths } : {}, | |
| 1988 | 1989 | ...(commitProgress ? { progress: commitProgress } : {}), |
| 1989 | 1990 | }); |
| 1990 | 1991 | if (currentHookName !== null) { |
apps/server/src/vcs/GitVcsDriver.ts+2| ... | ... | @@ -174,6 +174,8 @@ export interface GitCommitProgress { |
| 174 | 174 | export interface GitCommitOptions { |
| 175 | 175 | readonly timeoutMs?: number; |
| 176 | 176 | readonly progress?: GitCommitProgress; |
| 177 | /** Stage the current working tree immediately before committing. */ | |
| 178 | readonly stage?: { readonly filePaths?: readonly string[] }; | |
| 177 | 179 | } |
| 178 | 180 | |
| 179 | 181 | export interface GitDeleteLocalBranchInput { |
apps/server/src/vcs/GitVcsDriverCore.test.ts+92-9| ... | ... | @@ -3052,7 +3052,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 3052 | 3052 | }); |
| 3053 | 3053 | |
| 3054 | 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 | 3056 | Effect.gen(function* () { |
| 3057 | 3057 | const cwd = yield* makeTmpDir(); |
| 3058 | 3058 | yield* initRepoWithCommit(cwd); |
| ... | ... | @@ -3064,8 +3064,9 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 3064 | 3064 | const context = yield* driver.prepareCommitContext(cwd, ["a.txt"]); |
| 3065 | 3065 | assert.include(context?.stagedSummary ?? "", "a.txt"); |
| 3066 | 3066 | assert.notInclude(context?.stagedSummary ?? "", "b.txt"); |
| 3067 | assert.equal(yield* git(cwd, ["diff", "--cached", "--name-only"]), ""); | |
| 3067 | 3068 | |
| 3068 | const commit = yield* driver.commit(cwd, "Add a", ""); | |
| 3069 | const commit = yield* driver.commit(cwd, "Add a", "", { stage: { filePaths: ["a.txt"] } }); | |
| 3069 | 3070 | assert.match(commit.commitSha, /^[a-f0-9]{40}$/); |
| 3070 | 3071 | assert.equal(yield* git(cwd, ["log", "-1", "--pretty=%s"]), "Add a"); |
| 3071 | 3072 | |
| ... | ... | @@ -3084,14 +3085,96 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 3084 | 3085 | yield* writeTextFile(cwd, "selected[1].txt", "literal\n"); |
| 3085 | 3086 | yield* writeTextFile(cwd, "selected1.txt", "pattern match\n"); |
| 3086 | 3087 | |
| 3087 | yield* driver.prepareCommitContext(cwd, ["selected[1].txt"]); | |
| 3088 | const context = yield* driver.prepareCommitContext(cwd, ["selected[1].txt"]); | |
| 3088 | 3089 | |
| 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 | ); | |
| 3090 | 3100 | |
| 3091 | 3101 | const status = yield* git(cwd, ["status", "--porcelain"]); |
| 3092 | 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 | }); |
| 3096 | 3179 | |
| 3097 | 3180 | describe("remote operations", () => { |
| ... | ... | @@ -3271,7 +3354,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 3271 | 3354 | refName: "feature/push", |
| 3272 | 3355 | }); |
| 3273 | 3356 | yield* writeTextFile(cwd, "feature.txt", "feature\n"); |
| 3274 | yield* (yield* GitVcsDriver.GitVcsDriver).prepareCommitContext(cwd); | |
| 3357 | yield* git(cwd, ["add", "-A"]); | |
| 3275 | 3358 | yield* (yield* GitVcsDriver.GitVcsDriver).commit(cwd, "Add feature", ""); |
| 3276 | 3359 | |
| 3277 | 3360 | const pushed = yield* (yield* GitVcsDriver.GitVcsDriver).pushCurrentBranch(cwd, null); |
| ... | ... | @@ -3343,7 +3426,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 3343 | 3426 | yield* git(cwd, ["remote", "add", "origin", remote]); |
| 3344 | 3427 | yield* git(cwd, ["push", "-u", "origin", "main"]); |
| 3345 | 3428 | yield* writeTextFile(cwd, "upstream.txt", "upstream\n"); |
| 3346 | yield* driver.prepareCommitContext(cwd); | |
| 3429 | yield* git(cwd, ["add", "-A"]); | |
| 3347 | 3430 | yield* driver.commit(cwd, "Add upstream update", ""); |
| 3348 | 3431 | |
| 3349 | 3432 | const pushed = yield* driver.pushCurrentBranch(cwd, null); |
| ... | ... | @@ -3384,7 +3467,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 3384 | 3467 | const devSha = yield* git(cwd, ["rev-parse", "HEAD"]); |
| 3385 | 3468 | yield* git(cwd, ["checkout", "-b", "feature/x", "origin/dev"]); |
| 3386 | 3469 | yield* writeTextFile(cwd, "feature.txt", "feature\n"); |
| 3387 | yield* driver.prepareCommitContext(cwd); | |
| 3470 | yield* git(cwd, ["add", "-A"]); | |
| 3388 | 3471 | yield* driver.commit(cwd, "Add feature", ""); |
| 3389 | 3472 | |
| 3390 | 3473 | const pushed = yield* driver.pushCurrentBranch(cwd, null); |
| ... | ... | @@ -3418,7 +3501,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 3418 | 3501 | yield* git(cwd, ["checkout", "-b", "feature/y", "origin/main"]); |
| 3419 | 3502 | yield* git(cwd, ["config", "branch.feature/y.gh-merge-base", "release/v2"]); |
| 3420 | 3503 | yield* writeTextFile(cwd, "feature.txt", "feature\n"); |
| 3421 | yield* driver.prepareCommitContext(cwd); | |
| 3504 | yield* git(cwd, ["add", "-A"]); | |
| 3422 | 3505 | yield* driver.commit(cwd, "Add feature", ""); |
| 3423 | 3506 | |
| 3424 | 3507 | const pushed = yield* driver.pushCurrentBranch(cwd, null); |
| ... | ... | @@ -3456,7 +3539,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 3456 | 3539 | "upstream/effect-atom", |
| 3457 | 3540 | ); |
| 3458 | 3541 | yield* writeTextFile(cwd, "alias.txt", "alias\n"); |
| 3459 | yield* driver.prepareCommitContext(cwd); | |
| 3542 | yield* git(cwd, ["add", "-A"]); | |
| 3460 | 3543 | yield* driver.commit(cwd, "Add alias update", ""); |
| 3461 | 3544 | |
| 3462 | 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 | 2025 | })), |
| 2026 | 2026 | ); |
| 2027 | 2027 | |
| 2028 | const prepareCommitContext: GitVcsDriver.GitVcsDriver["Service"]["prepareCommitContext"] = | |
| 2029 | Effect.fn("prepareCommitContext")(function* (cwd, filePaths) { | |
| 2030 | if (filePaths && filePaths.length > 0) { | |
| 2031 | yield* runGit("GitVcsDriver.prepareCommitContext.reset", cwd, ["reset"]).pipe( | |
| 2032 | Effect.catchTags({ | |
| 2033 | GitCommandError: () => Effect.void, | |
| 2034 | }), | |
| 2028 | const stageCommitChanges = Effect.fnUntraced(function* ( | |
| 2029 | cwd: string, | |
| 2030 | filePaths?: readonly string[], | |
| 2031 | env?: NodeJS.ProcessEnv, | |
| 2032 | ) { | |
| 2033 | const config = env | |
| 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 | 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 | } |
| 2046 | ||
| 2047 | const stagedSummary = yield* runGitStdout( | |
| 2048 | "GitVcsDriver.prepareCommitContext.stagedSummary", | |
| 2056 | yield* runGit( | |
| 2057 | "GitVcsDriver.commit.addSelected", | |
| 2049 | 2058 | cwd, |
| 2050 | ["diff", "--cached", "--name-status"], | |
| 2051 | ).pipe(Effect.map((stdout) => stdout.trim())); | |
| 2052 | if (stagedSummary.length === 0) { | |
| 2053 | return null; | |
| 2054 | } | |
| 2055 | ||
| 2056 | const stagedPatch = yield* runGitStdoutWithOptions( | |
| 2057 | "GitVcsDriver.prepareCommitContext.stagedPatch", | |
| 2059 | [...config, "--literal-pathspecs", "add", "-A", "--", ...filePaths], | |
| 2060 | env ? { env } : {}, | |
| 2061 | ); | |
| 2062 | } else { | |
| 2063 | yield* runGit( | |
| 2064 | "GitVcsDriver.commit.addAll", | |
| 2058 | 2065 | cwd, |
| 2059 | ["diff", "--no-ext-diff", "--cached", "--patch", "--minimal"], | |
| 2060 | { | |
| 2061 | maxOutputBytes: PREPARED_COMMIT_PATCH_MAX_OUTPUT_BYTES, | |
| 2062 | appendTruncationMarker: true, | |
| 2063 | }, | |
| 2066 | [...config, "add", "-A"], | |
| 2067 | env ? { env } : {}, | |
| 2064 | 2068 | ); |
| 2069 | } | |
| 2070 | }); | |
| 2065 | 2071 | |
| 2066 | return { | |
| 2067 | stagedSummary, | |
| 2068 | stagedPatch, | |
| 2069 | }; | |
| 2072 | const prepareCommitContext: GitVcsDriver.GitVcsDriver["Service"]["prepareCommitContext"] = | |
| 2073 | Effect.fn("prepareCommitContext")(function* (cwd, filePaths) { | |
| 2074 | return yield* Effect.scoped( | |
| 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 | }); |
| 2071 | 2155 | |
| 2072 | 2156 | const commit: GitVcsDriver.GitVcsDriver["Service"]["commit"] = Effect.fn("commit")(function* ( |
| ... | ... | @@ -2075,6 +2159,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* |
| 2075 | 2159 | body, |
| 2076 | 2160 | options?: GitVcsDriver.GitCommitOptions, |
| 2077 | 2161 | ) { |
| 2162 | if (options?.stage !== undefined) { | |
| 2163 | yield* stageCommitChanges(cwd, options.stage.filePaths); | |
| 2164 | } | |
| 2078 | 2165 | const args = ["commit", "-m", subject]; |
| 2079 | 2166 | const trimmedBody = body.trim(); |
| 2080 | 2167 | if (trimmedBody.length > 0) { |