🐛 fix(vscode): 摘修复按文件改动认已摘过,不靠标题

This commit is contained in:
cruldra committed 2026-09-01 20:01:55 +08:00
1 parent beb7750eac
commit 0214ca1ab5
3 files changed
+125 -8

No files matched your search

+82 -7
View File
@@ -54,6 +54,15 @@ 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) => {
resolve({ ok: !err, stdout: stdout ?? '', stderr: stderr ?? '' })
})
child.stdin?.end(input)
})
}
const tail = (s: string, n = 400): string => s.trim().slice(-n)
async function mustGit(cwd: string, args: string[], failLabel: string): Promise<string> {
@@ -145,12 +154,8 @@ export async function getReleaseStatus(o: { cwd: string, devBranch: string, pref
const current = pickCurrentRelease(o.prefix, await remoteBranchNames(o.cwd, o.prefix))
if (!current)
return base
const count = await mustGit(
o.cwd,
['rev-list', '--count', '--right-only', '--cherry-pick', '--no-merges', unreleasedRange(current, o.devBranch)],
'统计未发布提交失败',
)
return { devBranch: o.devBranch, current, unreleased: Number(count) }
const commits = await listUnreleasedCommits({ cwd: o.cwd, devBranch: o.devBranch, current })
return { devBranch: o.devBranch, current, unreleased: commits.length }
}
/** 新→旧;不 fetch,调用方自己决定何时刷新远端 */
@@ -209,6 +214,63 @@ 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) : []
}
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, 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/${current}`],
'读取发布分支改动失败',
)
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: [] })
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}`)
}
}
return keys
}
export async function listUnreleasedCommits(o: { cwd: string, devBranch: string, current: string }): Promise<UnreleasedCommit[]> {
const out = await mustGit(
o.cwd,
@@ -217,13 +279,26 @@ export async function listUnreleasedCommits(o: { cwd: string, devBranch: string,
)
if (!out)
return []
return out.split('\x1e').flatMap((chunk) => {
const commits = out.split('\x1e').flatMap((chunk) => {
const trimmed = chunk.replace(/^\n+/, '').trimEnd()
if (!trimmed)
return []
const [sha, subject, author, date, body = ''] = trimmed.split('\0')
return [{ sha, subject, author, date, body: body.trim() }]
})
const perCommitKeys = new Map<string, string[]>()
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 key of keys)
files.add(key.slice(0, key.indexOf('\0')))
}
const released = await releaseFileChangeKeys(o.cwd, o.current, files)
return commits.filter((c) => {
const keys = perCommitKeys.get(c.sha) ?? []
return keys.length === 0 || keys.some(k => !released.has(k))
})
}
/** 纯远端切分支;返回该分支相对上一个发布点的提交数(无上一个则为 0) */