🐛 fix(vscode): 摘修复按文件改动认已摘过,不靠标题
This commit is contained in:
1 parent
beb7750eac
commit
0214ca1ab5
3 files changed
+125
-8
No files matched your search
@@ -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) */
|
||||
|
||||
Reference in new issue
Block a user