| author | |
| committer | |
| log | b441875731c2fb174adbb61e1d46121be363474e |
| tree | b3058ac095a17c9edc89b5fea5f2618c65aac316 |
| parent | 8e744269a2edb2e51d3fa81e576fc29e64d5b186 |
- avoid git reset when repository has no HEAD\n- use React Query isLoading for checkpoint diff spinner\n- remove unreachable provider manager guard\n- add no-HEAD restore regression test\n\nCo-authored-by: codex <codex@users.noreply.github.com>4 files changed, 33 insertions(+), 5 deletions(-)
apps/server/src/filesystemCheckpointStore.test.ts+29| ... | @@ -35,6 +35,12 @@ async function createRepo(): Promise<string> { | ... | @@ -35,6 +35,12 @@ async function createRepo(): Promise<string> { |
| 35 | return cwd; | 35 | return cwd; |
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | async function createRepoWithoutHead(): Promise<string> { | ||
| 39 | const cwd = await mkdtemp(path.join(os.tmpdir(), "t3-fs-checkpoints-empty-test-")); | ||
| 40 | await runGit(cwd, ["init", "--initial-branch=main"]); | ||
| 41 | return cwd; | ||
| 42 | } | ||
| 43 | |||
| 38 | describe("FilesystemCheckpointStore", () => { | 44 | describe("FilesystemCheckpointStore", () => { |
| 39 | it("captures refs without mutating branch history and restores workspace state", async () => { | 45 | it("captures refs without mutating branch history and restores workspace state", async () => { |
| 40 | const cwd = await createRepo(); | 46 | const cwd = await createRepo(); |
| ... | @@ -211,4 +217,27 @@ describe("FilesystemCheckpointStore", () => { | ... | @@ -211,4 +217,27 @@ describe("FilesystemCheckpointStore", () => { |
| 211 | 217 | ||
| 212 | expect(await store.isGitRepository(cwd)).toBe(false); | 218 | expect(await store.isGitRepository(cwd)).toBe(false); |
| 213 | }); | 219 | }); |
| 220 | |||
| 221 | it("restores checkpoint without reset when repository has no HEAD commit", async () => { | ||
| 222 | const cwd = await createRepoWithoutHead(); | ||
| 223 | const store = new FilesystemCheckpointStore(); | ||
| 224 | const threadId = "thr-no-head"; | ||
| 225 | |||
| 226 | try { | ||
| 227 | await writeFile(path.join(cwd, "README.md"), "first\n"); | ||
| 228 | await store.captureCheckpoint({ cwd, threadId, turnCount: 0 }); | ||
| 229 | |||
| 230 | await writeFile(path.join(cwd, "README.md"), "second\n"); | ||
| 231 | const restored = await store.restoreCheckpoint({ | ||
| 232 | cwd, | ||
| 233 | threadId, | ||
| 234 | turnCount: 0, | ||
| 235 | }); | ||
| 236 | |||
| 237 | expect(restored).toBe(true); | ||
| 238 | expect(await readFile(path.join(cwd, "README.md"), "utf8")).toBe("first\n"); | ||
| 239 | } finally { | ||
| 240 | await rm(cwd, { recursive: true, force: true }); | ||
| 241 | } | ||
| 242 | }); | ||
| 214 | }); | 243 | }); |
apps/server/src/filesystemCheckpointStore.ts+3-1| ... | @@ -136,7 +136,9 @@ export class FilesystemCheckpointStore { | ... | @@ -136,7 +136,9 @@ export class FilesystemCheckpointStore { |
| 136 | 136 | ||
| 137 | await this.runGit(cwd, ["restore", "--source", commitOid, "--worktree", "--staged", "--", "."]); | 137 | await this.runGit(cwd, ["restore", "--source", commitOid, "--worktree", "--staged", "--", "."]); |
| 138 | await this.runGit(cwd, ["clean", "-fd", "--", "."]); | 138 | await this.runGit(cwd, ["clean", "-fd", "--", "."]); |
| 139 | await this.runGit(cwd, ["reset", "--quiet", "--", "."]); | 139 | if (await this.hasHeadCommit(cwd)) { |
| 140 | await this.runGit(cwd, ["reset", "--quiet", "--", "."]); | ||
| 141 | } | ||
| 140 | return true; | 142 | return true; |
| 141 | } | 143 | } |
| 142 | 144 |
apps/server/src/providerManager.ts-3| ... | @@ -433,9 +433,6 @@ export class ProviderManager extends EventEmitter<ProviderManagerEvents> { | ... | @@ -433,9 +433,6 @@ export class ProviderManager extends EventEmitter<ProviderManagerEvents> { |
| 433 | .listSessions() | 433 | .listSessions() |
| 434 | .find((candidate) => candidate.sessionId === sessionId); | 434 | .find((candidate) => candidate.sessionId === sessionId); |
| 435 | const candidateCwds = session?.cwd ? [session.cwd] : [process.cwd()]; | 435 | const candidateCwds = session?.cwd ? [session.cwd] : [process.cwd()]; |
| 436 | if (candidateCwds.length === 0) { | ||
| 437 | return null; | ||
| 438 | } | ||
| 439 | 436 | ||
| 440 | await this.withFilesystemLock(sessionId, async () => { | 437 | await this.withFilesystemLock(sessionId, async () => { |
| 441 | const currentCwd = this.sessionCheckpointCwds.get(sessionId); | 438 | const currentCwd = this.sessionCheckpointCwds.get(sessionId); |
apps/web/src/components/DiffPanel.tsx+1-1| ... | @@ -147,7 +147,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -147,7 +147,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 147 | const conversationCheckpointDiff = selectedTurn | 147 | const conversationCheckpointDiff = selectedTurn |
| 148 | ? undefined | 148 | ? undefined |
| 149 | : activeCheckpointDiffQuery.data?.diff; | 149 | : activeCheckpointDiffQuery.data?.diff; |
| 150 | const isLoadingCheckpointDiff = activeCheckpointDiffQuery.isPending; | 150 | const isLoadingCheckpointDiff = activeCheckpointDiffQuery.isLoading; |
| 151 | const checkpointDiffError = | 151 | const checkpointDiffError = |
| 152 | activeCheckpointDiffQuery.error instanceof Error | 152 | activeCheckpointDiffQuery.error instanceof Error |
| 153 | ? activeCheckpointDiffQuery.error.message | 153 | ? activeCheckpointDiffQuery.error.message |