♻️ 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:
@@ -3,8 +3,9 @@
|
||||
* 单个文件改动前后的原文,交给 webview 内联渲染(主从式 diff)。
|
||||
*
|
||||
* 数据全程实时拉取,只把「已查看」确认态落到 `.spx/pr-review-confirmed.json`
|
||||
* (键含 head sha,新 push 自然重置)。diff 左右两侧用 merge_base → head 的原文,
|
||||
* 与 Gitea 网页 diff 口径一致;getRawFile 对 404 兜底空串,故新增/删除/改名天然成立。
|
||||
* (按文件变更指纹失效:作者推新提交后,仅真改动过的文件掉勾,没动的保留)。diff
|
||||
* 左右两侧用 merge_base → head 的原文,与 Gitea 网页 diff 口径一致;getRawFile 对
|
||||
* 404 兜底空串,故新增/删除/改名天然成立。
|
||||
*/
|
||||
|
||||
import type { KanbanWebviewPanel } from '../KanbanPanel'
|
||||
@@ -17,7 +18,7 @@ import { detectRepo } from '../../git/remote'
|
||||
import { getPullRequest, getRawFile, listPullRequestFiles } from '../../gitea/api'
|
||||
import { readStateJsonComment } from '../../gitea/stateJson'
|
||||
import { readFileSummaries, readSummarySession, writeFileSummary, writeSummarySession } from '../../sessions/prFileSummaryStore'
|
||||
import { readConfirmed, writeConfirmed } from '../../sessions/prReviewStore'
|
||||
import { readConfirmedSigs, writeConfirmedSigs } from '../../sessions/prReviewStore'
|
||||
import { makeNonce } from '../KanbanPanel'
|
||||
|
||||
/**
|
||||
@@ -103,27 +104,33 @@ function langFromPath(path: string): string {
|
||||
return map[ext] ?? ext
|
||||
}
|
||||
|
||||
/** 文件「变更指纹」:内容一改,status/additions/deletions 几乎必变其一;用于判定已查看是否仍有效。 */
|
||||
function fileSig(f: { status: string, additions: number, deletions: number }): string {
|
||||
return `${f.status}|${f.additions}|${f.deletions}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉取工单对应 PR 的全部改动文件清单 + head/mergeBase ref + 已确认集合,推给 webview。
|
||||
* 拉取工单对应 PR 的全部改动文件清单 + 已查看集合 + 已生成说明,推给 webview。
|
||||
* 无 PR/缺上下文回空(前端按空态处理);真出错才带 error。
|
||||
*
|
||||
* 已查看按「变更指纹」判定:取每个文件当前的 status|additions|deletions,与落盘记录的
|
||||
* 指纹比对,仅相符的文件仍算已查看——作者推新提交后只有真改动过的文件掉勾。比对后顺手把
|
||||
* 已失配/已消失的条目从盘上剪掉。
|
||||
*/
|
||||
export async function handleGetPrFiles(panel: KanbanWebviewPanel, issueNumber: number): Promise<void> {
|
||||
try {
|
||||
const ctx = await resolveRepoContext(panel)
|
||||
if (!ctx) {
|
||||
panel.postMessage({ type: 'pr-files/show', issueNumber, headSha: '', mergeBase: '', files: [], confirmed: [], summaries: {} })
|
||||
panel.postMessage({ type: 'pr-files/show', issueNumber, files: [], confirmed: [], summaries: {} })
|
||||
return
|
||||
}
|
||||
|
||||
const index = await resolvePrNumber(ctx, issueNumber)
|
||||
if (index === undefined) {
|
||||
panel.postMessage({ type: 'pr-files/show', issueNumber, headSha: '', mergeBase: '', files: [], confirmed: [], summaries: {} })
|
||||
panel.postMessage({ type: 'pr-files/show', issueNumber, files: [], confirmed: [], summaries: {} })
|
||||
return
|
||||
}
|
||||
|
||||
const pr = await getPullRequest({ host: ctx.host, owner: ctx.owner, repo: ctx.repo, token: ctx.token, index })
|
||||
// 三点 diff 的左侧 ref:优先 merge_base,缺失则退回 base sha。
|
||||
const mergeBase = pr.mergeBase || pr.baseSha
|
||||
const files = await listPullRequestFiles({
|
||||
host: ctx.host,
|
||||
owner: ctx.owner,
|
||||
@@ -131,29 +138,34 @@ export async function handleGetPrFiles(panel: KanbanWebviewPanel, issueNumber: n
|
||||
token: ctx.token,
|
||||
index,
|
||||
})
|
||||
|
||||
const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath
|
||||
const [confirmed, summaries] = workspaceRoot
|
||||
? await Promise.all([
|
||||
readConfirmed(workspaceRoot, issueNumber, pr.headSha),
|
||||
readFileSummaries(workspaceRoot, issueNumber),
|
||||
])
|
||||
: [[], {}]
|
||||
panel.postMessage({
|
||||
type: 'pr-files/show',
|
||||
issueNumber,
|
||||
headSha: pr.headSha,
|
||||
mergeBase,
|
||||
files,
|
||||
confirmed,
|
||||
summaries,
|
||||
})
|
||||
let confirmed: string[] = []
|
||||
let summaries: Record<string, string> = {}
|
||||
if (workspaceRoot) {
|
||||
const [stored, sums] = await Promise.all([
|
||||
readConfirmedSigs(workspaceRoot, issueNumber),
|
||||
readFileSummaries(workspaceRoot, issueNumber),
|
||||
])
|
||||
summaries = sums
|
||||
// 当前每文件的变更指纹;仅指纹相符的已查看条目仍有效。
|
||||
const cur = new Map(files.map(f => [f.path, fileSig(f)]))
|
||||
confirmed = Object.keys(stored).filter(p => cur.get(p) === stored[p])
|
||||
// 有失配/消失才写盘,剪掉陈旧条目,避免无谓磁盘写入。
|
||||
if (confirmed.length !== Object.keys(stored).length) {
|
||||
const pruned: Record<string, string> = {}
|
||||
for (const p of confirmed)
|
||||
pruned[p] = stored[p]
|
||||
await writeConfirmedSigs(workspaceRoot, issueNumber, pruned)
|
||||
}
|
||||
}
|
||||
|
||||
panel.postMessage({ type: 'pr-files/show', issueNumber, files, confirmed, summaries })
|
||||
}
|
||||
catch (err) {
|
||||
panel.postMessage({
|
||||
type: 'pr-files/show',
|
||||
issueNumber,
|
||||
headSha: '',
|
||||
mergeBase: '',
|
||||
files: [],
|
||||
confirmed: [],
|
||||
summaries: {},
|
||||
@@ -212,19 +224,18 @@ export async function handleGetPrFileDiff(
|
||||
}
|
||||
|
||||
/**
|
||||
* 持久化某 PR 里已确认(已审阅)的文件路径集合到 `.spx/pr-review-confirmed.json`,
|
||||
* 键含 head sha。拿不到工作区根则静默跳过;写盘失败兜底打日志、不抛——确认态丢失
|
||||
* 不该中断 UI。
|
||||
* 持久化某 PR 的「已查看文件 → 变更指纹」映射到 `.spx/pr-review-confirmed.json`。
|
||||
* 拿不到工作区根则静默跳过;写盘失败兜底打日志、不抛——确认态丢失不该中断 UI。
|
||||
*/
|
||||
export async function handleSetPrReviewConfirmed(
|
||||
panel: KanbanWebviewPanel,
|
||||
args: { issueNumber: number, headSha: string, confirmed: string[] },
|
||||
_panel: KanbanWebviewPanel,
|
||||
args: { issueNumber: number, confirmed: Record<string, string> },
|
||||
): Promise<void> {
|
||||
try {
|
||||
const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath
|
||||
if (!workspaceRoot)
|
||||
return
|
||||
await writeConfirmed(workspaceRoot, args.issueNumber, args.headSha, args.confirmed)
|
||||
await writeConfirmedSigs(workspaceRoot, args.issueNumber, args.confirmed)
|
||||
}
|
||||
catch (err) {
|
||||
console.warn('handleSetPrReviewConfirmed 写盘失败', err)
|
||||
|
||||
@@ -100,7 +100,7 @@ export type ExtensionToWebview
|
||||
| { type: 'issue/remove', issueNumber: number }
|
||||
| { type: 'profiles/show', data: ProfilesData }
|
||||
| { type: 'managed-sessions/show', data: ManagedSessionsShowData }
|
||||
| { 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 }
|
||||
|
||||
@@ -176,4 +176,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> }
|
||||
|
||||
Reference in New Issue
Block a user