🐛 fix(vscode): 选工单不再重新加载整个看板,直接列看板上摆着的「审查」列工单,文件数一项项补上,每一步记进日志
This commit is contained in:
1 parent
4ec38cff12
commit
b3a829e658
10 files changed
+310
-112
No files matched your search
@@ -3,12 +3,14 @@ import type { PrFileDto } from '../../gitea/api'
|
||||
import type { RepoContext } from '../../gitea/repoContext'
|
||||
import type { Issue } from '../../gitea/types'
|
||||
import type { FileNode, FileTreeProvider } from '../explorer/fileTree'
|
||||
import type { FileCount } from './reviewRules'
|
||||
import type { FileCount } from './pickList'
|
||||
import * as path from 'node:path'
|
||||
import { commands, Uri, window, workspace } from 'vscode'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { ChangeDecorations } from './changeDecorations'
|
||||
import { buildChangedTree, changeOf } from './changedFiles'
|
||||
import { pickItemText, prNumberOf, reviewableIssue, reviewablePullRequest, reviewDescription, reviewMessage } from './reviewRules'
|
||||
import { countingFiles, pickEntries, pickItemText, withFileCount } from './pickList'
|
||||
import { reviewableIssue, reviewablePullRequest, reviewDescription, reviewMessage } from './reviewRules'
|
||||
import { loadIssue, loadPullRequest, loadPullRequestFiles, requireRepo, reviewColumnIssues } from './reviewSource'
|
||||
import { localWorktree, prepareWorktree } from './reviewWorktree'
|
||||
|
||||
@@ -79,6 +81,10 @@ export class ReviewMode implements Disposable {
|
||||
}
|
||||
|
||||
/** 「选工单」:顶部弹出快速选择,读看板「审查」列的工单和各自改了几个文件时转圈。 */
|
||||
/**
|
||||
* 「选工单」:顶部弹出快速选择,列出看板上现在摆着的「审查」列工单,马上就有;
|
||||
* 各 PR 改了几个文件一起去读,读到一个换一项,都读完才停止转圈。
|
||||
*/
|
||||
async pick(): Promise<void> {
|
||||
const picker = window.createQuickPick<IssuePick>()
|
||||
picker.title = '选工单 · 看板「审查」列'
|
||||
@@ -94,26 +100,58 @@ export class ReviewMode implements Disposable {
|
||||
if (!chosen)
|
||||
return
|
||||
picker.hide()
|
||||
void this.review(chosen.issue, 'pick')
|
||||
void this.review(chosen.issue.number, 'pick')
|
||||
})
|
||||
picker.show()
|
||||
|
||||
let repo: RepoContext
|
||||
try {
|
||||
const repo = await requireRepo(this.context)
|
||||
const issues = await reviewColumnIssues(this.context, repo)
|
||||
const items = await Promise.all(issues.map(issue => this.pickItem(repo, issue)))
|
||||
if (!open)
|
||||
return
|
||||
picker.items = items
|
||||
if (items.length === 0)
|
||||
picker.placeholder = '看板「审查」列里没有工单'
|
||||
picker.busy = false
|
||||
repo = await requireRepo(this.context)
|
||||
}
|
||||
catch (err) {
|
||||
logger.add({ level: 'error', source: 'codeReader', message: '选工单:读不到当前工作区的 Gitea 仓库', details: errorText(err) })
|
||||
picker.hide()
|
||||
void window.showErrorMessage(`没法选工单:${errorText(err)}`)
|
||||
return
|
||||
}
|
||||
const board = reviewColumnIssues(this.context)
|
||||
if (!open)
|
||||
return
|
||||
if (!board) {
|
||||
logger.add({ level: 'info', source: 'codeReader', message: '选工单:看板还没加载过,没有工单可列' })
|
||||
picker.placeholder = '看板还没加载过,先打开一次看板'
|
||||
picker.busy = false
|
||||
return
|
||||
}
|
||||
logger.add({
|
||||
level: 'info',
|
||||
source: 'codeReader',
|
||||
message: `选工单:看板「审查」列 ${board.issues.length} 张(${board.from === 'loaded' ? '看板这次加载的' : '上次存下的看板快照'})`,
|
||||
details: board.issues.map(issue => `#${issue.number} PR ${issue.pr ?? '无'}`).join('\n'),
|
||||
})
|
||||
if (board.issues.length === 0)
|
||||
picker.placeholder = '看板「审查」列里没有工单'
|
||||
|
||||
let entries = pickEntries(board.issues, issue => localWorktree(this.context, repo.workspaceRoot, issue) !== undefined)
|
||||
// 换 items 时 VS Code 会把光标挪回第一项:按工单号挪回用户停着的那一项
|
||||
const render = (): void => {
|
||||
if (!open)
|
||||
return
|
||||
picker.hide()
|
||||
void window.showErrorMessage(`读不到看板「审查」列:${errorText(err)}`)
|
||||
const active = picker.activeItems[0]?.issue.number
|
||||
const items = entries.map(entry => ({ issue: entry.issue, ...pickItemText(entry) }))
|
||||
picker.items = items
|
||||
const stay = items.find(item => item.issue.number === active)
|
||||
if (stay)
|
||||
picker.activeItems = [stay]
|
||||
picker.busy = countingFiles(entries)
|
||||
}
|
||||
render()
|
||||
await Promise.all(entries.flatMap(({ issue, pr }) => pr
|
||||
? [this.countFiles(repo, issue.number, pr.number).then((files) => {
|
||||
entries = withFileCount(entries, issue.number, files)
|
||||
render()
|
||||
})]
|
||||
: []))
|
||||
}
|
||||
|
||||
/** 「显示全部」:回到工作区文件树,不再记着这张工单。 */
|
||||
@@ -148,35 +186,36 @@ export class ReviewMode implements Disposable {
|
||||
}
|
||||
|
||||
/** 弹框里的一项:有 PR 的工单去读 PR 改了几个文件(各项一起读),读不到就写读不到。 */
|
||||
private async pickItem(repo: RepoContext, issue: Issue): Promise<IssuePick> {
|
||||
const pr = prNumberOf(issue)
|
||||
if (pr === undefined)
|
||||
return { issue, ...pickItemText(issue) }
|
||||
const files: FileCount = await loadPullRequestFiles(repo, pr).then(
|
||||
list => ({ count: list.length }),
|
||||
(err: unknown) => ({ error: errorText(err) }),
|
||||
)
|
||||
const hasWorktree = localWorktree(this.context, repo.workspaceRoot, issue) !== undefined
|
||||
return { issue, ...pickItemText(issue, { number: pr, files, hasWorktree }) }
|
||||
/** 读一张工单的 PR 改了几个文件;没读到记进日志,这一项写读不到。 */
|
||||
private async countFiles(repo: RepoContext, issueNumber: number, pr: number): Promise<FileCount> {
|
||||
try {
|
||||
return { count: (await loadPullRequestFiles(repo, pr)).length }
|
||||
}
|
||||
catch (err) {
|
||||
logger.add({ level: 'warn', source: 'codeReader', message: `选工单:读不到 #${issueNumber} 的 PR #${pr} 改了几个文件`, details: errorText(err) })
|
||||
return 'failed'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 进审查:读工单和 PR、备工作副本,都好了才换树;这期间视图上转进度,标题下面那行写正在做哪一步。
|
||||
* target 在选工单时是弹框里那张工单(刚读的),刷新、重开窗口时是工单号(重读)。
|
||||
* 进审查:重读这张工单(弹框里的是看板上摆着的,可能旧了)和它的 PR、备工作副本,都好了才换树;
|
||||
* 这期间视图上转进度,标题下面那行写正在做哪一步。每一步、不进的原因、出错都记进日志。
|
||||
*/
|
||||
private async review(target: Issue | number, how: How): Promise<void> {
|
||||
const issueNumber = typeof target === 'number' ? target : target.number
|
||||
private async review(issueNumber: number, how: How): Promise<void> {
|
||||
const generation = ++this.generation
|
||||
const latest = (): boolean => generation === this.generation
|
||||
const before = this.view.message
|
||||
logger.add({ level: 'info', source: 'codeReader', message: `审查 #${issueNumber}:开始(${how})` })
|
||||
let result: Loaded | { blocked: string }
|
||||
try {
|
||||
result = await window.withProgress({ location: { viewId: this.viewId } }, () => this.load(target, (text) => {
|
||||
result = await window.withProgress({ location: { viewId: this.viewId } }, () => this.load(issueNumber, (text) => {
|
||||
logger.add({ level: 'info', source: 'codeReader', message: `审查 #${issueNumber}:${text}` })
|
||||
if (latest())
|
||||
this.view.message = text
|
||||
}))
|
||||
}
|
||||
catch (err) {
|
||||
logger.add({ level: 'error', source: 'codeReader', message: `审查 #${issueNumber} 失败`, details: errorText(err) })
|
||||
if (!latest())
|
||||
return
|
||||
this.view.message = before
|
||||
@@ -188,6 +227,7 @@ export class ReviewMode implements Disposable {
|
||||
if (!latest())
|
||||
return
|
||||
if ('blocked' in result) {
|
||||
logger.add({ level: 'info', source: 'codeReader', message: `审查 #${issueNumber} 不进:${result.blocked}` })
|
||||
this.view.message = before
|
||||
if (how !== 'restore')
|
||||
void window.showInformationMessage(result.blocked)
|
||||
@@ -195,15 +235,14 @@ export class ReviewMode implements Disposable {
|
||||
await this.exit()
|
||||
return
|
||||
}
|
||||
logger.add({ level: 'info', source: 'codeReader', message: `审查 #${issueNumber}:PR #${result.pr} 改了 ${result.files.length} 个文件`, details: result.worktree })
|
||||
await this.show(result)
|
||||
}
|
||||
|
||||
private async load(target: Issue | number, step: (text: string) => void): Promise<Loaded | { blocked: string }> {
|
||||
private async load(issueNumber: number, step: (text: string) => void): Promise<Loaded | { blocked: string }> {
|
||||
const repo = await requireRepo(this.context)
|
||||
const issueNumber = typeof target === 'number' ? target : target.number
|
||||
if (typeof target === 'number')
|
||||
step(`正在读取 #${issueNumber}…`)
|
||||
const reviewable = reviewableIssue(issueNumber, typeof target === 'number' ? await loadIssue(this.context, repo, target) : target)
|
||||
step(`正在读取 #${issueNumber}…`)
|
||||
const reviewable = reviewableIssue(issueNumber, await loadIssue(this.context, repo, issueNumber))
|
||||
if ('blocked' in reviewable)
|
||||
return reviewable
|
||||
const { issue, pr } = reviewable
|
||||
|
||||
Reference in new issue
Block a user