From 752f6995a4b4217b5aa6c54d41a62997d64651b9 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 24 Nov 2025 08:15:50 +0000 Subject: [PATCH] fix: preserve untracked files during checkpoint rollback Fixes #9530 - Rollback no longer deletes files that were not created or tracked by Roo Code. Previously, git clean -d -f was removing ALL untracked files during rollback, including files manually created by users outside of Roo Code. This fix removes the git clean command, allowing git reset --hard to handle the rollback properly, which only affects tracked files. - Removed git clean command from restoreCheckpoint method - Added test to verify manually created files are preserved during rollback - All existing tests pass --- .../checkpoints/ShadowCheckpointService.ts | 2 +- .../__tests__/ShadowCheckpointService.spec.ts | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/services/checkpoints/ShadowCheckpointService.ts b/src/services/checkpoints/ShadowCheckpointService.ts index fee08b2fa4..536f4f31f7 100644 --- a/src/services/checkpoints/ShadowCheckpointService.ts +++ b/src/services/checkpoints/ShadowCheckpointService.ts @@ -342,7 +342,7 @@ export abstract class ShadowCheckpointService extends EventEmitter { } const start = Date.now() - await this.git.clean("f", ["-d", "-f"]) + // Only reset tracked files to checkpoint state, preserve untracked files await this.git.reset(["--hard", commitHash]) // Remove all checkpoints after the specified commitHash. diff --git a/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts b/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts index 5172a37369..0b8193d05e 100644 --- a/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts +++ b/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts @@ -267,6 +267,48 @@ describe.each([[RepoPerTaskCheckpointService, "RepoPerTaskCheckpointService"]])( expect(await fs.readFile(testFile, "utf-8")).toBe("Changed tracked file") }) + it("preserves manually created files during rollback (fixes issue #9530)", async () => { + // This test simulates the exact scenario from the bug report: + // 1. Create a checkpoint + // 2. User manually creates a file outside of Roo Code + // 3. Rollback to the checkpoint + // 4. The manually created file should NOT be deleted + + // Create initial checkpoint + await fs.writeFile(testFile, "Initial content") + const commit1 = await service.saveCheckpoint("Initial checkpoint") + expect(commit1?.commit).toBeTruthy() + + // Make some changes and create another checkpoint + await fs.writeFile(testFile, "Modified by Roo Code") + const commit2 = await service.saveCheckpoint("Second checkpoint") + expect(commit2?.commit).toBeTruthy() + + // Simulate user manually creating a file AFTER all checkpoints + // This file is never tracked by any checkpoint + const manuallyCreatedFile = path.join(service.workspaceDir, "manually-created.txt") + await fs.writeFile(manuallyCreatedFile, "I was created manually, not by Roo Code!") + + // Now rollback to the first checkpoint + await service.restoreCheckpoint(commit1!.commit) + + // The tracked file should be restored to its initial state + expect(await fs.readFile(testFile, "utf-8")).toBe("Initial content") + + // IMPORTANT: The manually created file should still exist + // This is the fix for issue #9530 - manually created files should not be deleted + expect(await fs.readFile(manuallyCreatedFile, "utf-8")).toBe("I was created manually, not by Roo Code!") + + // Rollback to the second checkpoint + await service.restoreCheckpoint(commit2!.commit) + + // The tracked file should be at its second state + expect(await fs.readFile(testFile, "utf-8")).toBe("Modified by Roo Code") + + // The manually created file should still persist + expect(await fs.readFile(manuallyCreatedFile, "utf-8")).toBe("I was created manually, not by Roo Code!") + }) + it("handles file deletions correctly", async () => { await fs.writeFile(testFile, "I am tracked!") const untrackedFile = path.join(service.workspaceDir, "new.txt")