⚡ perf(vscode): 摘修复指纹一次算完,不再逐文件起进程
This commit is contained in:
1 parent
dd3b7aaa24
commit
90600cf070
2 files changed
+45
-57
No files matched your search
+1
-1
@@ -2,7 +2,7 @@
|
||||
"publisher": "clurdra",
|
||||
"name": "superpowers-vscode-clurdra",
|
||||
"displayName": "Superpowers-clurdra",
|
||||
"version": "0.2.115",
|
||||
"version": "0.2.116",
|
||||
"packageManager": "pnpm@10.27.0",
|
||||
"description": "Superpowers specs and plans Kanban explorer",
|
||||
"author": "clurdra",
|
||||
|
||||
+44
-56
@@ -48,7 +48,7 @@ interface Run { ok: boolean, stdout: string, stderr: string }
|
||||
|
||||
function runGit(cwd: string, args: string[], timeoutMs = 30_000): Promise<Run> {
|
||||
return new Promise((resolve) => {
|
||||
execFile('git', ['-C', cwd, ...args], { timeout: timeoutMs, encoding: 'utf8' }, (err, stdout, stderr) => {
|
||||
execFile('git', ['-C', cwd, ...args], { timeout: timeoutMs, encoding: 'utf8', maxBuffer: 512 * 1024 * 1024 }, (err, stdout, stderr) => {
|
||||
resolve({ ok: !err, stdout: stdout ?? '', stderr: stderr ?? '' })
|
||||
})
|
||||
})
|
||||
@@ -56,7 +56,7 @@ function runGit(cwd: string, args: string[], timeoutMs = 30_000): Promise<Run> {
|
||||
|
||||
function runGitStdin(args: string[], input: string, timeoutMs = 30_000): Promise<Run> {
|
||||
return new Promise((resolve) => {
|
||||
const child = execFile('git', args, { timeout: timeoutMs, encoding: 'utf8' }, (err, stdout, stderr) => {
|
||||
const child = execFile('git', args, { timeout: timeoutMs, encoding: 'utf8', maxBuffer: 512 * 1024 * 1024 }, (err, stdout, stderr) => {
|
||||
resolve({ ok: !err, stdout: stdout ?? '', stderr: stderr ?? '' })
|
||||
})
|
||||
child.stdin?.end(input)
|
||||
@@ -218,61 +218,46 @@ export function attachPulls<T extends { sha: string }>(
|
||||
})
|
||||
}
|
||||
|
||||
/** 发布分支上已有的提交标题。拆开摘过的会留下同标题多条,整包再摘必冲突。 */
|
||||
async function commitFiles(cwd: string, sha: string): Promise<string[]> {
|
||||
const out = await mustGit(cwd, ['diff-tree', '--no-commit-id', '--name-only', '-r', sha], '读取提交文件列表失败')
|
||||
return out ? out.split('\n').filter(Boolean) : []
|
||||
}
|
||||
type FileChangeKeys = Map<string, string[]>
|
||||
|
||||
async function fileChangeId(cwd: string, sha: string, file: string): Promise<string | null> {
|
||||
const shown = await runGit(cwd, ['show', '--pretty=format:', '--full-index', sha, '--', file])
|
||||
if (!shown.ok || !shown.stdout.trim())
|
||||
return null
|
||||
const id = await runGitStdin(['patch-id', '--stable'], shown.stdout)
|
||||
const hash = id.stdout.trim().split(/\s+/)[0]
|
||||
return hash || null
|
||||
}
|
||||
|
||||
async function commitFileChangeKeys(cwd: string, sha: string): Promise<string[]> {
|
||||
const files = await commitFiles(cwd, sha)
|
||||
const keys: string[] = []
|
||||
for (const file of files) {
|
||||
const id = await fileChangeId(cwd, sha, file)
|
||||
if (id)
|
||||
keys.push(`${file}\0${id}`)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
/** 发布分支上每个文件已有的改动指纹。用来认出「一次提交被拆成多次摘过去」的情况。 */
|
||||
async function releaseFileChangeKeys(cwd: string, current: string, devBranch: string, files: Set<string>): Promise<Set<string>> {
|
||||
const keys = new Set<string>()
|
||||
if (files.size === 0)
|
||||
return keys
|
||||
const log = await mustGit(
|
||||
cwd,
|
||||
['log', '--no-merges', '--name-only', '--format=%n%H', `origin/${devBranch}..origin/${current}`],
|
||||
'读取发布分支改动失败',
|
||||
)
|
||||
/** 一次 `git log -p` 切出每个提交每个文件的改动,再一次 `git patch-id` 算完指纹。 */
|
||||
async function fileChangeKeysByCommit(cwd: string, logArgs: string[], onlyFiles?: Set<string>): Promise<FileChangeKeys> {
|
||||
const out = new Map<string, string[]>()
|
||||
const log = await mustGit(cwd, ['log', '-p', '--no-merges', '--format=commit %H', ...logArgs], '读取提交改动失败')
|
||||
if (!log)
|
||||
return out
|
||||
const blocks: Array<{ sha: string, file: string, text: string[] }> = []
|
||||
let sha = ''
|
||||
const hits: Array<{ sha: string, files: string[] }> = []
|
||||
for (const line of log.split('\n')) {
|
||||
if (/^[0-9a-f]{40}$/.test(line)) {
|
||||
sha = line
|
||||
hits.push({ sha, files: [] })
|
||||
const commit = /^commit ([0-9a-f]{40})$/.exec(line)
|
||||
if (commit) {
|
||||
sha = commit[1]
|
||||
out.set(sha, [])
|
||||
continue
|
||||
}
|
||||
if (sha && files.has(line))
|
||||
hits[hits.length - 1]?.files.push(line)
|
||||
}
|
||||
for (const hit of hits) {
|
||||
for (const file of hit.files) {
|
||||
const id = await fileChangeId(cwd, hit.sha, file)
|
||||
if (id)
|
||||
keys.add(`${file}\0${id}`)
|
||||
const header = /^diff --git "?a\/.*? "?b\/(.+?)"?$/.exec(line)
|
||||
if (header) {
|
||||
blocks.push({ sha, file: header[1], text: [line] })
|
||||
continue
|
||||
}
|
||||
blocks[blocks.length - 1]?.text.push(line)
|
||||
}
|
||||
return keys
|
||||
const wanted = blocks.filter(b => !onlyFiles || onlyFiles.has(b.file))
|
||||
if (wanted.length === 0)
|
||||
return out
|
||||
const stream = wanted
|
||||
.map((b, i) => `commit ${i.toString(16).padStart(40, '0')}\n${b.text.join('\n')}\n`)
|
||||
.join('')
|
||||
const ids = await runGitStdin(['patch-id', '--stable'], stream)
|
||||
for (const line of ids.stdout.split('\n')) {
|
||||
const [id, marker] = line.trim().split(/\s+/)
|
||||
if (!id || !marker)
|
||||
continue
|
||||
const block = wanted[Number.parseInt(marker, 16)]
|
||||
if (block)
|
||||
out.get(block.sha)?.push(`${block.file}\0${id}`)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
export async function listUnreleasedCommits(o: { cwd: string, devBranch: string, current: string }): Promise<UnreleasedCommit[]> {
|
||||
@@ -290,17 +275,20 @@ export async function listUnreleasedCommits(o: { cwd: string, devBranch: string,
|
||||
const [sha, subject, author, date, body = ''] = trimmed.split('\0')
|
||||
return [{ sha, subject, author, date, body: body.trim() }]
|
||||
})
|
||||
const perCommitKeys = new Map<string, string[]>()
|
||||
const perCommit = await fileChangeKeysByCommit(o.cwd, ['--right-only', '--cherry-pick', unreleasedRange(o.current, o.devBranch)])
|
||||
const files = new Set<string>()
|
||||
for (const c of commits) {
|
||||
const keys = await commitFileChangeKeys(o.cwd, c.sha)
|
||||
perCommitKeys.set(c.sha, keys)
|
||||
for (const keys of perCommit.values()) {
|
||||
for (const key of keys)
|
||||
files.add(key.slice(0, key.indexOf('\0')))
|
||||
}
|
||||
const released = await releaseFileChangeKeys(o.cwd, o.current, o.devBranch, files)
|
||||
const onRelease = await fileChangeKeysByCommit(o.cwd, [`origin/${o.devBranch}..origin/${o.current}`], files)
|
||||
const released = new Set<string>()
|
||||
for (const keys of onRelease.values()) {
|
||||
for (const key of keys)
|
||||
released.add(key)
|
||||
}
|
||||
return commits.filter((c) => {
|
||||
const keys = perCommitKeys.get(c.sha) ?? []
|
||||
const keys = perCommit.get(c.sha) ?? []
|
||||
return keys.length === 0 || keys.some(k => !released.has(k))
|
||||
})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user