90 lines
2.8 KiB
TypeScript
90 lines
2.8 KiB
TypeScript
import { parseDiffFromFile, parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs"
|
|
import { parsePatch } from "diff"
|
|
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
|
|
|
|
type LegacyDiff = {
|
|
file: string
|
|
patch?: string
|
|
before?: string
|
|
after?: string
|
|
additions: number
|
|
deletions: number
|
|
status?: "added" | "deleted" | "modified"
|
|
}
|
|
|
|
type SnapshotDiff = SnapshotFileDiff & { file: string }
|
|
type ReviewDiff = SnapshotDiff | VcsFileDiff | LegacyDiff
|
|
export type DiffSource = Pick<LegacyDiff, "file" | "patch" | "before" | "after">
|
|
|
|
export type ViewDiff = {
|
|
file: string
|
|
additions: number
|
|
deletions: number
|
|
status?: "added" | "deleted" | "modified"
|
|
fileDiff: FileDiffMetadata
|
|
}
|
|
|
|
const diffCacheLimit = 16
|
|
const patchFileDiffCache = new Map<string, FileDiffMetadata>()
|
|
|
|
export function resolveFileDiff(diff: DiffSource) {
|
|
if (typeof diff.patch === "string") return fileDiffFromPatch(diff.file, diff.patch)
|
|
return fileDiffFromContent(
|
|
diff.file,
|
|
typeof diff.before === "string" ? diff.before : "",
|
|
typeof diff.after === "string" ? diff.after : "",
|
|
)
|
|
}
|
|
|
|
export function normalize(diff: ReviewDiff): ViewDiff {
|
|
return {
|
|
file: diff.file,
|
|
additions: diff.additions,
|
|
deletions: diff.deletions,
|
|
status: diff.status,
|
|
fileDiff: resolveFileDiff(diff),
|
|
}
|
|
}
|
|
|
|
export function text(diff: ViewDiff, side: "deletions" | "additions") {
|
|
if (side === "deletions") return diff.fileDiff.deletionLines.join("")
|
|
return diff.fileDiff.additionLines.join("")
|
|
}
|
|
|
|
function fileDiffFromPatch(file: string, patch: string) {
|
|
const key = `${file}\0${patch}`
|
|
const hit = patchFileDiffCache.get(key)
|
|
if (hit) {
|
|
patchFileDiffCache.delete(key)
|
|
patchFileDiffCache.set(key, hit)
|
|
return hit
|
|
}
|
|
|
|
const input = patchInput(file, patch)
|
|
const value = (input ? parsePatchFiles(input)[0]?.files[0] : undefined) ?? emptyFileDiff(file)
|
|
patchFileDiffCache.set(key, value)
|
|
while (patchFileDiffCache.size > diffCacheLimit) patchFileDiffCache.delete(patchFileDiffCache.keys().next().value!)
|
|
return value
|
|
}
|
|
|
|
function patchInput(file: string, patch: string) {
|
|
try {
|
|
const parsed = parsePatch(patch)[0]
|
|
if (!parsed) return
|
|
if (parsed.index || parsed.oldFileName || parsed.newFileName) return patch
|
|
if (!parsed.hunks.length) return
|
|
return `Index: ${file}\n===================================================================\n--- ${file}\t\n+++ ${file}\t\n${patch}`
|
|
} catch {
|
|
return
|
|
}
|
|
}
|
|
|
|
function fileDiffFromContent(file: string, before: string, after: string) {
|
|
if (!before && !after) return emptyFileDiff(file)
|
|
return parseDiffFromFile({ name: file, contents: before }, { name: file, contents: after })
|
|
}
|
|
|
|
function emptyFileDiff(file: string) {
|
|
return parseDiffFromFile({ name: file, contents: "" }, { name: file, contents: "" })
|
|
}
|