♻️ fix(vscode): 「已查看」改为按文件变更指纹失效,PR 新 push 只摘变动文件的勾

原先按 head sha 存已查看,作者一推新提交 head sha 变了,全部勾一起清空——
哪怕只改了一个文件也把没动的勾全冲掉。改成 GitHub「Viewed」式的内容感知:
按每个文件的变更指纹 (status|additions|deletions) 存,拉文件时与当前指纹比对,
只有真改动过的文件掉勾,没动的保留;比对后顺手剪掉盘上失配/消失的条目。

Gitea 的 ChangedFile 不返回 blob sha、compare 端点也不支持任意 commit sha,
故用上述三元组作指纹(文件一改几乎必变其一),零额外请求。

- prReviewStore: 形状改为 issueNumber → {path: sig},导出 read/writeConfirmedSigs
- prFiles: handleGetPrFiles 按指纹过滤+剪枝,顺带去掉不再需要的 getPullRequest 调用
- 消息 pr-files/show 去掉无人消费的 headSha/mergeBase;pr-review/set 改发 {path: sig}
- usePrFiles: 删 headSha,setConfirmed 把路径映射成指纹再发
This commit is contained in:
2026-06-29 16:55:11 +08:00
parent bd8ba714b3
commit 35085f72f7
6 changed files with 96 additions and 75 deletions
@@ -6,7 +6,7 @@
* - 右栏:选中文件的内联 diff(git-diff-view,只渲染当前一个文件)。
*
* 自带 usePrFiles hook,直接经 lib/vscode 收发消息,不依赖上层回调。diff 左右两侧用
* merge_base → head 的原文(与 Gitea 网页 diff 口径一致),已查看态按 head sha 落盘。
* merge_base → head 的原文(与 Gitea 网页 diff 口径一致),已查看态按文件变更指纹落盘。
*/
import type { Issue } from '../types'
+14 -11
View File
@@ -7,7 +7,7 @@
* - 订阅 `pr-files/show` / `pr-file-diff/show`,只认 issueNumber 匹配的消息
* (扩展侧可能并发多工单的回包)。
* - diffByPath 缓存:同一文件点开第二次不再发请求。
* - 已确认(已查看)按 head sha 落盘,故 setConfirmed 要带当前 headSha
* - 已查看按「文件变更指纹」落盘(status|additions|deletions),setConfirmed 把路径映射成指纹再发
*/
import type { PrFile } from '../lib/messages'
@@ -25,7 +25,6 @@ export interface FileDiff {
export interface UsePrFilesResult {
files: PrFile[]
headSha: string
filesError: string | undefined
loading: boolean
confirmed: string[]
@@ -42,7 +41,6 @@ export interface UsePrFilesResult {
export function usePrFiles(issueNumber: number | undefined): UsePrFilesResult {
const [files, setFiles] = useState<PrFile[]>([])
const [headSha, setHeadSha] = useState<string>('')
const [filesError, setFilesError] = useState<string | undefined>(undefined)
const [loading, setLoading] = useState(false)
const [confirmed, setConfirmedState] = useState<string[]>([])
@@ -50,11 +48,11 @@ export function usePrFiles(issueNumber: number | undefined): UsePrFilesResult {
const [summariesByPath, setSummariesByPath] = useState<Record<string, string>>({})
const [generatingPath, setGeneratingPath] = useState<string | null>(null)
// issueNumber / headSha 装进 ref,让订阅闭包与回调始终读到当前值。
// issueNumber / files 装进 ref,让订阅闭包与回调始终读到当前值。
const issueRef = useRef<number | undefined>(issueNumber)
issueRef.current = issueNumber
const headShaRef = useRef<string>('')
headShaRef.current = headSha
const filesRef = useRef<PrFile[]>([])
filesRef.current = files
// 已发出 diff 请求的 path 集合:防同一文件并发重复请求(缓存到达前的窗口)。
const requestedRef = useRef<Set<string>>(new Set())
@@ -65,7 +63,6 @@ export function usePrFiles(issueNumber: number | undefined): UsePrFilesResult {
if (msg.issueNumber !== issueRef.current)
return
setFiles(msg.files)
setHeadSha(msg.headSha)
setConfirmedState(msg.confirmed ?? [])
setSummariesByPath(msg.summaries ?? {})
setFilesError(msg.error)
@@ -102,7 +99,6 @@ export function usePrFiles(issueNumber: number | undefined): UsePrFilesResult {
// 切工单:清空缓存与已请求集合,重新拉文件清单。无工单时只清空。
useEffect(() => {
setFiles([])
setHeadSha('')
setFilesError(undefined)
setConfirmedState([])
setDiffByPath({})
@@ -127,13 +123,21 @@ export function usePrFiles(issueNumber: number | undefined): UsePrFilesResult {
postMessage({ type: 'pr-file-diff/get', issueNumber: num, path, previousPath })
}, [])
// 乐观更新本地确认态并落盘(键含当前 headSha);下次拉文件回包会以持久化值校正。
// 乐观更新本地确认态并落盘;把已查看路径映射成「路径 → 变更指纹」(公式须与扩展侧一致),
// 下次拉文件回包会按指纹比对校正(文件变过则自动掉勾)。
const setConfirmed = useCallback((paths: string[]): void => {
const num = issueRef.current
if (num === undefined)
return
setConfirmedState(paths)
postMessage({ type: 'pr-review/set', issueNumber: num, headSha: headShaRef.current, confirmed: paths })
const sigByPath = new Map(filesRef.current.map(f => [f.path, `${f.status}|${f.additions}|${f.deletions}`]))
const confirmed: Record<string, string> = {}
for (const p of paths) {
const sig = sigByPath.get(p)
if (sig !== undefined)
confirmed[p] = sig
}
postMessage({ type: 'pr-review/set', issueNumber: num, confirmed })
}, [])
// 触发 deepseek 增量生成该文件的改动说明;置 loading,结果经 pr-file-summary/show 回来。
@@ -147,7 +151,6 @@ export function usePrFiles(issueNumber: number | undefined): UsePrFilesResult {
return {
files,
headSha,
filesError,
loading,
confirmed,
+2 -2
View File
@@ -115,7 +115,7 @@ export type ExtensionToWebview
| { type: 'issue/remove', issueNumber: number }
| { type: 'profiles/show', data: ProfilesData }
| { type: 'managed-sessions/show', data: ManagedSessionsData }
| { type: 'pr-files/show', issueNumber: number, headSha: string, mergeBase: string, files: PrFile[], confirmed: string[], summaries: Record<string, string>, error?: string }
| { type: 'pr-files/show', issueNumber: number, files: PrFile[], confirmed: string[], summaries: Record<string, string>, error?: string }
| { type: 'pr-file-diff/show', issueNumber: number, path: string, oldContent: string, newContent: string, oldLang?: string, newLang?: string, error?: string }
| { type: 'pr-file-summary/show', issueNumber: number, path: string, summary?: string, error?: string }
@@ -191,4 +191,4 @@ export type WebviewToExtension
| { type: 'pr-files/get', issueNumber: number }
| { type: 'pr-file-diff/get', issueNumber: number, path: string, previousPath?: string }
| { type: 'pr-file-summary/generate', issueNumber: number, path: string, previousPath?: string }
| { type: 'pr-review/set', issueNumber: number, headSha: string, confirmed: string[] }
| { type: 'pr-review/set', issueNumber: number, confirmed: Record<string, string> }