mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
fix(vscode): reject stale diff reads and prune removed reviews
This commit is contained in:
@@ -155,11 +155,20 @@ export function createDiffCache(load: Loader) {
|
||||
const existing = pending.get(latest.id)
|
||||
if (existing && existing.item !== item) return existing.work
|
||||
const result = await load.detail(item.dir, latest.state.anc, latest.meta)
|
||||
if (current(item)?.id !== latest.id) return null
|
||||
if (result.image?.before?.error === "unreadable" || result.image?.after?.error === "unreadable") return result
|
||||
remember(latest.id, result)
|
||||
return result
|
||||
}
|
||||
|
||||
const settle = async (item: Item, value: Value | undefined) => {
|
||||
if (current(item)?.id === item.id && value) {
|
||||
item.resolve(value)
|
||||
return
|
||||
}
|
||||
item.resolve(item.calls.size > 0 ? await fallback(item) : null)
|
||||
}
|
||||
|
||||
const run = async (items: Item[]) => {
|
||||
for (let index = 0; index < items.length; index += MAX_BATCH_FILES) {
|
||||
const chunk = items.slice(index, index + MAX_BATCH_FILES).filter((item) => item.calls.size > 0)
|
||||
@@ -171,7 +180,7 @@ export function createDiffCache(load: Loader) {
|
||||
if (chunk.length === 1) {
|
||||
const item = chunk[0]!
|
||||
try {
|
||||
item.resolve(await load.detail(item.dir, item.anc, item.meta))
|
||||
await settle(item, await load.detail(item.dir, item.anc, item.meta))
|
||||
} catch (error) {
|
||||
item.reject(error)
|
||||
}
|
||||
@@ -190,13 +199,8 @@ export function createDiffCache(load: Loader) {
|
||||
await Promise.all(
|
||||
chunk.map(async (item) => {
|
||||
try {
|
||||
const latest = current(item)
|
||||
const entry = value?.entries.get(item.meta.file)
|
||||
if (latest?.id === item.id && entry && !value?.deferred.has(item.meta.file)) {
|
||||
item.resolve(entry)
|
||||
return
|
||||
}
|
||||
item.resolve(item.calls.size > 0 ? await fallback(item) : null)
|
||||
const entry = value?.deferred.has(item.meta.file) ? undefined : value?.entries.get(item.meta.file)
|
||||
await settle(item, entry)
|
||||
} catch (error) {
|
||||
item.reject(error)
|
||||
}
|
||||
|
||||
@@ -100,6 +100,50 @@ describe("createWorktreeDiffs", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("prunes every scope of deleted worktrees without dropping local or other-project reviews", () => {
|
||||
createRoot((dispose) => {
|
||||
const store = createWorktreeDiffs(vscode([]), () => "project-a")
|
||||
for (const id of ["gone#branch", "gone#staged", "live#branch", "local#session:s1"]) {
|
||||
store.onWorktreeDiff({
|
||||
type: "agentManager.worktreeDiff",
|
||||
projectId: "project-a",
|
||||
sessionId: id,
|
||||
diffs: [diff("a.ts")],
|
||||
})
|
||||
}
|
||||
store.onWorktreeDiff({
|
||||
type: "agentManager.worktreeDiff",
|
||||
projectId: "project-b",
|
||||
sessionId: "gone#branch",
|
||||
diffs: [diff("b.ts")],
|
||||
})
|
||||
store.onWorktreeDiffLoading({
|
||||
type: "agentManager.worktreeDiffLoading",
|
||||
projectId: "project-a",
|
||||
sessionId: "gone#branch",
|
||||
loading: true,
|
||||
})
|
||||
store.onWorktreeDiffNotice({
|
||||
type: "agentManager.worktreeDiffNotice",
|
||||
projectId: "project-a",
|
||||
sessionId: "gone#branch",
|
||||
notice: "deleted",
|
||||
})
|
||||
store.requestDiffFile("gone#branch", "a.ts")
|
||||
store.prune(new Set(["live"]))
|
||||
|
||||
expect(Object.keys(store.diffDatas()).sort()).toEqual([
|
||||
"project-a\0live#branch",
|
||||
"project-a\0local#session:s1",
|
||||
"project-b\0gone#branch",
|
||||
])
|
||||
expect(store.diffFileLoadingFor(() => "gone#branch").size).toBe(0)
|
||||
expect(store.diffNotices()["project-a\0gone#branch"]).toBeUndefined()
|
||||
expect(store.diffLoading()).toBe(false)
|
||||
dispose()
|
||||
})
|
||||
})
|
||||
|
||||
it("does not replace state when an update produces an identical diff list", () => {
|
||||
withDiffs((diffs) => {
|
||||
diffs.onWorktreeDiff({ type: "agentManager.worktreeDiff", sessionId: "s1", diffs: [diff("a.ts")] })
|
||||
|
||||
@@ -732,6 +732,28 @@ describe("createLocalDiff concurrent details", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("does not return stale singleton details after a newer summary arrives", async () => {
|
||||
await withRepo(async (dir, base) => {
|
||||
await fs.writeFile(path.join(dir, "seed.txt"), "seed\nold\n")
|
||||
const ops = new RecordingGitOps()
|
||||
const local = createLocalDiff(ops)
|
||||
await local.summary(dir, base)
|
||||
const gate = deferred<void>()
|
||||
const ready = deferred<void>()
|
||||
ops.block(show, gate.promise, ready.resolve)
|
||||
const pending = local.file(dir, base, "seed.txt")
|
||||
await ready.promise
|
||||
await fs.writeFile(path.join(dir, "seed.txt"), "seed\nnewer contents\n")
|
||||
const summary = await local.summary(dir, base)
|
||||
gate.resolve()
|
||||
|
||||
const value = await pending
|
||||
expect(value?.after).toBe("seed\nnewer contents\n")
|
||||
expect(value?.stamp).toBe(summary.find((entry) => entry.file === "seed.txt")?.stamp)
|
||||
expect(await local.file(dir, base, "seed.txt")).toBe(value)
|
||||
})
|
||||
})
|
||||
|
||||
it("shares one result for duplicate concurrent callers in a batch", async () => {
|
||||
await withRepo(async (dir, base) => {
|
||||
await setup(dir, base)
|
||||
|
||||
@@ -724,6 +724,7 @@ const AgentManagerContent: Component = () => {
|
||||
createEffect(() => {
|
||||
const ids = new Set(worktrees().map((wt) => wt.id))
|
||||
composers.prune(ids)
|
||||
untrack(() => diffs.prune(ids))
|
||||
setReviewOpenByContext((prev) => {
|
||||
const next = pruneReviewState(prev, currentProjectId() ?? "single", ids)
|
||||
if (Object.keys(next).length === Object.keys(prev).length) return prev
|
||||
|
||||
@@ -78,6 +78,21 @@ export function createWorktreeDiffs(
|
||||
setDiffFileLoading(remove)
|
||||
}
|
||||
|
||||
const prune = (ids: Set<string>) => {
|
||||
const prefix = `${project() ?? "single"}\0`
|
||||
const keys = new Set([
|
||||
...Object.keys(diffDatas()),
|
||||
...Object.keys(diffLoadings()),
|
||||
...Object.keys(diffNotices()),
|
||||
...Object.keys(diffFileLoading()),
|
||||
])
|
||||
for (const data of keys) {
|
||||
if (!data.startsWith(prefix)) continue
|
||||
const ctx = parseDiffId(data.slice(prefix.length)).ctx
|
||||
if (ctx !== "local" && !ids.has(ctx)) drop(data)
|
||||
}
|
||||
}
|
||||
|
||||
const retain = (id: string) => {
|
||||
const data = id.includes("\0") ? id : key(id)
|
||||
const entries = diffDatas()[data]
|
||||
@@ -232,6 +247,7 @@ export function createWorktreeDiffs(
|
||||
diffDataKey,
|
||||
retain,
|
||||
drop,
|
||||
prune,
|
||||
reset,
|
||||
onWorktreeDiff,
|
||||
onWorktreeDiffFile,
|
||||
|
||||
Reference in New Issue
Block a user