fix(version): 按完整提交区分同号历史开发说明
build / build (push) Successful in 1m4s

历史分支中存在两条开发号 0.1.1;归档选择按 SHA 匹配,保留原编号与冲突标签。
增加同号前置说明遮蔽及错 SHA 不复用回归,标签计划使用相同精确选择方式。
已运行:定向 Go 测试、快照工具临时仓正反例及整仓 make verify;未部署。
This commit is contained in:
姚章浩
2026-10-18 18:18:43 +08:00
parent 013ce4a24e
commit 74ba737b1e
5 changed files with 38 additions and 10 deletions
+6
View File
@@ -76,6 +76,12 @@
> `0.1.12`),由 `scripts/version.py` 从 git **算出来**;开发交付的独立说明写入本文件“开发快照”章节,并打 `snapshot/vX.Y.Z` 注释标签 ⇒ 每次开发交付均可追溯,
> 但"发布版本"仍然只能由人定(CI 不会自动升版本、不会打 tag)。
## 开发快照 snapshot/v0.1.27 - 2026-10-18
### Fixed
- 并行分支存在同号开发提交时,历史变更说明与补记标签计划按完整 SHA 选择,不再被前一条同号记录遮蔽;不强改推导版本号或移动既有标签。
## 开发快照 snapshot/v0.1.26 - 2026-10-18
### Fixed
+15 -1
View File
@@ -6,6 +6,12 @@ import (
// Development headings cannot replace the formal release baseline.
func ExtractDevelopmentNotes(changelog, version string) string {
return developmentNotes(changelog, version, "")
}
func ExtractDevelopmentNotesForCommit(changelog, version, sha string) string {
return developmentNotes(changelog, version, sha)
}
func developmentNotes(changelog, version, sha string) string {
if !SemverValid(version) {
return ""
}
@@ -20,7 +26,15 @@ func ExtractDevelopmentNotes(changelog, version string) string {
break
}
}
return strings.TrimSpace(strings.Join(lines[i+1:end], "\n"))
body := strings.TrimSpace(strings.Join(lines[i+1:end], "\n"))
if sha == "" {
return body
}
for _, field := range lines[i+1 : end] {
if strings.TrimSpace(field) == "目标提交:"+sha {
return body
}
}
}
}
return ""
@@ -55,6 +55,14 @@ func TestHistoricDevelopmentArchiveBindsExactCommit(t *testing.T) {
t.Fatal("archived notes missing")
}
}
// Parallel branch histories may legitimately have the same derived number.
input.DeliveryArchive = strings.ReplaceAll(archive, shaB, strings.Repeat("c", 40)) + "\n" + archive
result = DeriveVersions(input)
for _, v := range result.Records {
if v.CommitSHA == shaB && !strings.Contains(v.ReleaseNotes, "historic delivery notes") {
t.Fatal("same-number archive hid notes for exact commit")
}
}
input.DeliveryArchive = strings.ReplaceAll(archive, shaB, strings.Repeat("c", 40))
result = DeriveVersions(input)
for _, v := range result.Records {
+1 -4
View File
@@ -198,10 +198,7 @@ func DeriveVersions(in DeriveInput) DeriveResult {
} else if kind == governance.RecordKindDevelopment {
record.ReleaseNotes = governance.ExtractDevelopmentNotes(changelog, label)
if record.ReleaseNotes == "" {
archived := governance.ExtractDevelopmentNotes(in.DeliveryArchive, label)
if strings.Contains(archived, "目标提交:"+commit.SHA) {
record.ReleaseNotes = archived
}
record.ReleaseNotes = governance.ExtractDevelopmentNotesForCommit(in.DeliveryArchive, label, commit.SHA)
}
if record.ReleaseNotes == "" {
for _, tag := range tagsByTarget[commit.SHA] {
+8 -5
View File
@@ -8,10 +8,11 @@ def git(root,*args):
p=subprocess.run(['git',*args],cwd=root,text=True,capture_output=True)
if p.returncode:raise RuntimeError('git operation failed: '+args[0])
return p.stdout.strip()
def notes(text,version):
heading=re.search(r'^## 开发快照 snapshot/v'+re.escape(version)+r' - \d{4}-\d{2}-\d{2}\s*$',text,re.M)
if not heading:return ''
tail=text[heading.end():];return re.split(r'^##\s',tail,maxsplit=1,flags=re.M)[0].strip()
def notes(text,version,sha=None):
for heading in re.finditer(r'^## 开发快照 snapshot/v'+re.escape(version)+r' - \d{4}-\d{2}-\d{2}\s*$',text,re.M):
body=re.split(r'^##\s',text[heading.end():],maxsplit=1,flags=re.M)[0].strip()
if sha is None or ('目标提交:'+sha) in body.splitlines():return body
return ''
def plan(root,sha,backfill=False):
if not sha or not SHA.fullmatch(sha):raise ValueError('exact 40-character commit SHA required')
if git(root,'cat-file','-t',sha)!='commit':raise ValueError('snapshot target must be a commit')
@@ -24,7 +25,7 @@ def plan(root,sha,backfill=False):
if n<=0:raise ValueError('formal baseline cannot be tagged as a development delivery')
body=notes(text,version)
if not body and backfill:
body=notes(git(root,'show','HEAD:CHANGELOG.md'),version)
body=notes(git(root,'show','HEAD:CHANGELOG.md'),version,sha)
if '目标提交:'+sha not in body:raise ValueError('backfilled notes must bind the exact historic SHA')
if not body:raise ValueError('this development version has no independent CHANGELOG entry')
if not re.search(r'^### (Added|Changed|Fixed|Security)\s*$',body,re.M):raise ValueError('snapshot notes have no change category')
@@ -60,6 +61,8 @@ def self_test():
(r/'CHANGELOG.md').write_text('## 开发快照 snapshot/v0.1.1 - 2026-10-18\n\n### Fixed\n\n- specific fixture fix\n\n'+base);git(r,'add','CHANGELOG.md');git(r,'commit','-m','fix: fixture change')
sha=git(r,'rev-parse','HEAD');before=git(r,'tag');p=plan(r,sha)
assert p['tag']=='snapshot/v0.1.1' and 'specific fixture fix' in p['message']
duplicate='## 开发快照 snapshot/v0.1.1 - 2026-10-18\n\n目标提交:'+('a'*40)+'\n\n### Fixed\n\n- wrong branch\n\n## 开发快照 snapshot/v0.1.1 - 2026-10-18\n\n目标提交:'+sha+'\n\n### Fixed\n\n- exact branch'
assert 'exact branch' in notes(duplicate,'0.1.1',sha) and 'wrong branch' not in notes(duplicate,'0.1.1',sha)
assert before==git(r,'tag'),'planning wrote a tag'
try:plan(r,'a'*7)
except ValueError:pass