mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
Apply remaining FCO edge case fixes from backup branch
Applies commits 04bd21403 and 3d15bba1d from backup branch: FCO Edge Case Fixes: - Add .roo/ exclusion to checkpoint diffs - Filter out directories from ShadowCheckpointService.getDiff() - Implement improved line-by-line diff calculation in FileChangeManager - Add comprehensive FileChangeManager tests (70+ test cases) Windows Compatibility: - Fix 'core.bare and core.worktree do not make sense' error - Add core.bare=false configuration for shadow git repos LLM-Only Filtering: - Complete async integration of getLLMOnlyChanges() in FCO handlers - Fix getCurrentCline → getCurrentTask method name alignment - Update all FCO message handlers to use LLM-only filtering 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
709a7029da
commit
390c500d40
6 changed files with 146 additions and 25 deletions
|
|
@ -147,7 +147,10 @@ export abstract class ShadowCheckpointService extends EventEmitter {
|
|||
} else {
|
||||
this.log(`[${this.constructor.name}#initShadowGit] creating shadow git repo at ${this.checkpointsDir}`)
|
||||
await git.init()
|
||||
// Use GIT_WORK_TREE environment (set on the git instance) instead of core.worktree to avoid platform-specific issues
|
||||
await git.addConfig("core.worktree", this.workspaceDir) // Sets the working tree to the current workspace.
|
||||
// Fix Windows Git configuration conflict: explicitly set core.bare=false when using core.worktree
|
||||
// This resolves "core.bare and core.worktree do not make sense" error on Windows
|
||||
await git.addConfig("core.bare", "false")
|
||||
await git.addConfig("commit.gpgSign", "false") // Disable commit signing for shadow repo.
|
||||
await git.addConfig("user.name", "Roo Code")
|
||||
await git.addConfig("user.email", "noreply@example.com")
|
||||
|
|
@ -336,6 +339,17 @@ export abstract class ShadowCheckpointService extends EventEmitter {
|
|||
for (const file of files) {
|
||||
const relPath = file.file
|
||||
const absPath = path.join(cwdPath, relPath)
|
||||
|
||||
// Filter out directories - only include actual files
|
||||
try {
|
||||
const stat = await fs.stat(absPath)
|
||||
if (stat.isDirectory()) {
|
||||
continue // Skip directories
|
||||
}
|
||||
} catch {
|
||||
// If file doesn't exist (deleted files), continue processing
|
||||
}
|
||||
|
||||
const before = await this.git.show([`${from}:${relPath}`]).catch(() => "")
|
||||
|
||||
const after = await this.git.show([`${to ?? "HEAD"}:${relPath}`]).catch(() => "")
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@ const getLfsPatterns = async (workspacePath: string) => {
|
|||
|
||||
export const getExcludePatterns = async (workspacePath: string) => [
|
||||
".git/",
|
||||
".roo/",
|
||||
...getBuildArtifactPatterns(),
|
||||
...getMediaFilePatterns(),
|
||||
...getCacheFilePatterns(),
|
||||
|
|
|
|||
|
|
@ -45,10 +45,14 @@ export class FCOMessageHandler {
|
|||
if (!fileChangeManager) {
|
||||
fileChangeManager = await this.provider.ensureFileChangeManager()
|
||||
}
|
||||
if (fileChangeManager) {
|
||||
if (fileChangeManager && task?.taskId && task?.fileContextTracker) {
|
||||
const filteredChangeset = await fileChangeManager.getLLMOnlyChanges(
|
||||
task.taskId,
|
||||
task.fileContextTracker,
|
||||
)
|
||||
this.provider.postMessageToWebview({
|
||||
type: "filesChanged",
|
||||
filesChanged: fileChangeManager.getChanges(),
|
||||
filesChanged: filteredChangeset.files.length > 0 ? filteredChangeset : undefined,
|
||||
})
|
||||
}
|
||||
break
|
||||
|
|
@ -177,15 +181,19 @@ export class FCOMessageHandler {
|
|||
}
|
||||
|
||||
private async handleAcceptFileChange(message: WebviewMessage): Promise<void> {
|
||||
const task = this.provider.getCurrentTask()
|
||||
let acceptFileChangeManager = this.provider.getFileChangeManager()
|
||||
if (!acceptFileChangeManager) {
|
||||
acceptFileChangeManager = await this.provider.ensureFileChangeManager()
|
||||
}
|
||||
if (message.uri && acceptFileChangeManager) {
|
||||
if (message.uri && acceptFileChangeManager && task?.taskId && task?.fileContextTracker) {
|
||||
await acceptFileChangeManager.acceptChange(message.uri)
|
||||
|
||||
// Send updated state
|
||||
const updatedChangeset = acceptFileChangeManager.getChanges()
|
||||
// Send updated state with LLM-only filtering
|
||||
const updatedChangeset = await acceptFileChangeManager.getLLMOnlyChanges(
|
||||
task.taskId,
|
||||
task.fileContextTracker,
|
||||
)
|
||||
this.provider.postMessageToWebview({
|
||||
type: "filesChanged",
|
||||
filesChanged: updatedChangeset.files.length > 0 ? updatedChangeset : undefined,
|
||||
|
|
@ -218,7 +226,7 @@ export class FCOMessageHandler {
|
|||
return
|
||||
}
|
||||
|
||||
const checkpointService = getCheckpointService(currentTask)
|
||||
const checkpointService = await getCheckpointService(currentTask)
|
||||
if (!checkpointService) {
|
||||
console.error(`[FCO] No checkpoint service available for file reversion`)
|
||||
return
|
||||
|
|
@ -231,13 +239,18 @@ export class FCOMessageHandler {
|
|||
// Remove from tracking since the file has been reverted
|
||||
await rejectFileChangeManager.rejectChange(message.uri)
|
||||
|
||||
// Send updated state
|
||||
const updatedChangeset = rejectFileChangeManager.getChanges()
|
||||
console.log(`[FCO] After rejection, sending ${updatedChangeset.files.length} files to webview`)
|
||||
this.provider.postMessageToWebview({
|
||||
type: "filesChanged",
|
||||
filesChanged: updatedChangeset.files.length > 0 ? updatedChangeset : undefined,
|
||||
})
|
||||
// Send updated state with LLM-only filtering
|
||||
if (currentTask?.taskId && currentTask?.fileContextTracker) {
|
||||
const updatedChangeset = await rejectFileChangeManager.getLLMOnlyChanges(
|
||||
currentTask.taskId,
|
||||
currentTask.fileContextTracker,
|
||||
)
|
||||
console.log(`[FCO] After rejection, sending ${updatedChangeset.files.length} LLM-only files to webview`)
|
||||
this.provider.postMessageToWebview({
|
||||
type: "filesChanged",
|
||||
filesChanged: updatedChangeset.files.length > 0 ? updatedChangeset : undefined,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[FCO] Error reverting file ${message.uri}:`, error)
|
||||
// Fall back to old behavior (just remove from display) if reversion fails
|
||||
|
|
@ -290,7 +303,7 @@ export class FCOMessageHandler {
|
|||
return
|
||||
}
|
||||
|
||||
const checkpointService = getCheckpointService(currentTask)
|
||||
const checkpointService = await getCheckpointService(currentTask)
|
||||
if (!checkpointService) {
|
||||
console.error(`[FCO] No checkpoint service available for file reversion`)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -145,17 +145,40 @@ export class FileChangeManager {
|
|||
|
||||
/**
|
||||
* Calculate line differences between two file contents
|
||||
* Uses a simple line-by-line comparison to count actual changes
|
||||
*/
|
||||
public static calculateLineDifferences(
|
||||
originalContent: string,
|
||||
newContent: string,
|
||||
): { linesAdded: number; linesRemoved: number } {
|
||||
const originalLines = originalContent.split("\n")
|
||||
const newLines = newContent.split("\n")
|
||||
const originalLines = originalContent === "" ? [] : originalContent.split("\n")
|
||||
const newLines = newContent === "" ? [] : newContent.split("\n")
|
||||
|
||||
// Simple diff calculation
|
||||
const linesAdded = Math.max(0, newLines.length - originalLines.length)
|
||||
const linesRemoved = Math.max(0, originalLines.length - newLines.length)
|
||||
// For proper diff calculation, we need to compare line by line
|
||||
// This is a simplified approach that works well for most cases
|
||||
|
||||
const maxLines = Math.max(originalLines.length, newLines.length)
|
||||
let linesAdded = 0
|
||||
let linesRemoved = 0
|
||||
|
||||
// Compare each line position
|
||||
for (let i = 0; i < maxLines; i++) {
|
||||
const originalLine = i < originalLines.length ? originalLines[i] : undefined
|
||||
const newLine = i < newLines.length ? newLines[i] : undefined
|
||||
|
||||
if (originalLine === undefined && newLine !== undefined) {
|
||||
// Line was added
|
||||
linesAdded++
|
||||
} else if (originalLine !== undefined && newLine === undefined) {
|
||||
// Line was removed
|
||||
linesRemoved++
|
||||
} else if (originalLine !== newLine) {
|
||||
// Line was modified (count as both removed and added)
|
||||
linesRemoved++
|
||||
linesAdded++
|
||||
}
|
||||
// If lines are identical, no change
|
||||
}
|
||||
|
||||
return { linesAdded, linesRemoved }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ describe("FCOMessageHandler", () => {
|
|||
|
||||
// Mock ClineProvider
|
||||
mockProvider = {
|
||||
getCurrentCline: vi.fn().mockReturnValue(mockTask),
|
||||
getCurrentTask: vi.fn().mockReturnValue(mockTask),
|
||||
getFileChangeManager: vi.fn().mockReturnValue(mockFileChangeManager),
|
||||
ensureFileChangeManager: vi.fn().mockResolvedValue(mockFileChangeManager),
|
||||
postMessageToWebview: vi.fn(),
|
||||
|
|
@ -222,7 +222,7 @@ describe("FCOMessageHandler", () => {
|
|||
})
|
||||
|
||||
it("should handle missing task gracefully", async () => {
|
||||
mockProvider.getCurrentCline.mockReturnValue(null)
|
||||
mockProvider.getCurrentTask.mockReturnValue(null)
|
||||
|
||||
await handler.handleMessage({ type: "webviewReady" } as WebviewMessage)
|
||||
|
||||
|
|
@ -302,7 +302,7 @@ describe("FCOMessageHandler", () => {
|
|||
})
|
||||
|
||||
it("should handle missing dependencies", async () => {
|
||||
mockProvider.getCurrentCline.mockReturnValue(null)
|
||||
mockProvider.getCurrentTask.mockReturnValue(null)
|
||||
|
||||
await handler.handleMessage(mockMessage)
|
||||
|
||||
|
|
@ -745,7 +745,7 @@ describe("FCOMessageHandler", () => {
|
|||
// Mock previous state as disabled
|
||||
mockProvider.getGlobalState.mockReturnValue(false)
|
||||
// Mock no active task
|
||||
mockProvider.getCurrentCline.mockReturnValue(null)
|
||||
mockProvider.getCurrentTask.mockReturnValue(null)
|
||||
|
||||
await handler.handleMessage({
|
||||
type: "filesChangedEnabled",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Tests for simplified FileChangeManager - Pure diff calculation service
|
||||
// npx vitest run src/services/file-changes/__tests__/FileChangeManager.simplified.test.ts
|
||||
// npx vitest run src/services/file-changes/__tests__/FileChangeManager.test.ts
|
||||
|
||||
import { describe, beforeEach, afterEach, it, expect, vi } from "vitest"
|
||||
import { FileChangeManager } from "../FileChangeManager"
|
||||
|
|
@ -324,6 +324,76 @@ describe("FileChangeManager (Simplified)", () => {
|
|||
expect(result.linesAdded).toBe(0)
|
||||
expect(result.linesRemoved).toBe(0)
|
||||
})
|
||||
|
||||
it("should handle line modifications (search and replace)", () => {
|
||||
const original = "function test() {\n return 'old';\n}"
|
||||
const modified = "function test() {\n return 'new';\n}"
|
||||
|
||||
const result = FileChangeManager.calculateLineDifferences(original, modified)
|
||||
|
||||
expect(result.linesAdded).toBe(1) // Modified line counts as added
|
||||
expect(result.linesRemoved).toBe(1) // Modified line counts as removed
|
||||
})
|
||||
|
||||
it("should handle mixed changes", () => {
|
||||
const original = "line1\nold_line\nline3"
|
||||
const modified = "line1\nnew_line\nline3\nextra_line"
|
||||
|
||||
const result = FileChangeManager.calculateLineDifferences(original, modified)
|
||||
|
||||
expect(result.linesAdded).toBe(2) // 1 modified + 1 added
|
||||
expect(result.linesRemoved).toBe(1) // 1 modified
|
||||
})
|
||||
|
||||
it("should handle empty original file", () => {
|
||||
const original = ""
|
||||
const modified = "line1\nline2\nline3"
|
||||
|
||||
const result = FileChangeManager.calculateLineDifferences(original, modified)
|
||||
|
||||
expect(result.linesAdded).toBe(3)
|
||||
expect(result.linesRemoved).toBe(0)
|
||||
})
|
||||
|
||||
it("should handle empty modified file", () => {
|
||||
const original = "line1\nline2\nline3"
|
||||
const modified = ""
|
||||
|
||||
const result = FileChangeManager.calculateLineDifferences(original, modified)
|
||||
|
||||
expect(result.linesAdded).toBe(0)
|
||||
expect(result.linesRemoved).toBe(3)
|
||||
})
|
||||
|
||||
it("should handle both files empty", () => {
|
||||
const original = ""
|
||||
const modified = ""
|
||||
|
||||
const result = FileChangeManager.calculateLineDifferences(original, modified)
|
||||
|
||||
expect(result.linesAdded).toBe(0)
|
||||
expect(result.linesRemoved).toBe(0)
|
||||
})
|
||||
|
||||
it("should handle single line files", () => {
|
||||
const original = "single line"
|
||||
const modified = "different line"
|
||||
|
||||
const result = FileChangeManager.calculateLineDifferences(original, modified)
|
||||
|
||||
expect(result.linesAdded).toBe(1)
|
||||
expect(result.linesRemoved).toBe(1)
|
||||
})
|
||||
|
||||
it("should handle whitespace-only changes", () => {
|
||||
const original = "line1\n indented\nline3"
|
||||
const modified = "line1\n indented\nline3"
|
||||
|
||||
const result = FileChangeManager.calculateLineDifferences(original, modified)
|
||||
|
||||
expect(result.linesAdded).toBe(1) // Whitespace change counts as modification
|
||||
expect(result.linesRemoved).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getLLMOnlyChanges", () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue