Skip to content

Commit 55e589e

Browse files
committed
feat(scripts): 🚀 添加合并 commit 时自动合并相应 tags 的功能
1 parent bf7b2d8 commit 55e589e

File tree

1 file changed

+72
-29
lines changed

1 file changed

+72
-29
lines changed

scripts/git-utils/index.ts

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface SquashOptions {
1212
force?: boolean
1313
filter?: (line: string) => unknown
1414
}
15-
// chore: release
15+
1616
/**
1717
* 合并本地最新的 N 个 Git 提交
1818
* @param n 需要合并的提交数量
@@ -23,11 +23,23 @@ interface SquashOptions {
2323
export async function squashLastNCommits(n: number, options?: SquashOptions) {
2424
validateInput(n)
2525
checkGitRepository()
26-
// checkWorkingDirectoryClean();
2726

2827
const commitCount = await getUnpushedCommitCount()
2928
const actualSquashCount = calculateActualSquashCount(n, commitCount, options?.force)
30-
return await performSquash(actualSquashCount, options?.message, options?.filter)
29+
30+
// 获取需要合并的提交的标签
31+
const commitHashes = await getCommitHashes(actualSquashCount)
32+
const tags = await getLocalUnpushedTags(commitHashes)
33+
34+
// 执行合并并打标签
35+
const squashSuccessful = await performSquash(actualSquashCount, options?.message, options?.filter)
36+
37+
if (squashSuccessful) {
38+
// 为合并后的提交打上标签
39+
await addTagsToCommit(commitHashes, tags)
40+
}
41+
42+
return squashSuccessful
3143
}
3244

3345
// 以下为内部工具函数
@@ -50,7 +62,6 @@ async function checkGitRepository(): Promise<void> {
5062
async function getUnpushedCommitCount() {
5163
try {
5264
const currentBranch = (await x('git', ['rev-parse', '--abbrev-ref', 'HEAD'], execOptions)).stdout.trim()
53-
5465
const upstreamExists = (await x(
5566
'git',
5667
['rev-parse', '--abbrev-ref', `${currentBranch}@{u}`],
@@ -64,14 +75,8 @@ async function getUnpushedCommitCount() {
6475
)
6576
}
6677

67-
try {
68-
const result = await x('git', ['rev-list', '--count', `${currentBranch}@{u}..HEAD`], execOptions)
69-
70-
return Number.parseInt(result.stdout.trim(), 10)
71-
}
72-
catch {
73-
throw new Error('Failed to count local commits')
74-
}
78+
const result = await x('git', ['rev-list', '--count', `${currentBranch}@{u}..HEAD`], execOptions)
79+
return Number.parseInt(result.stdout.trim(), 10)
7580
}
7681
catch (error) {
7782
if (error instanceof Error) {
@@ -86,8 +91,6 @@ function calculateActualSquashCount(
8691
available: number,
8792
force = false,
8893
): number {
89-
// if (available === 0) throw new Error('No commits to squash');
90-
9194
const actual = Math.min(requested, available)
9295
if (!force && actual < requested) {
9396
throw new Error(
@@ -104,15 +107,12 @@ async function performSquash(count: number, message?: string, filter?: SquashOpt
104107
}
105108

106109
try {
107-
// 生成默认提交信息
108-
const commitMessage = message
109-
|| `${await getOriginalCommitMessages(count, filter)}`
110+
const commitMessage = message || `${await getOriginalCommitMessages(count, filter)}`
110111

111112
// 回退 HEAD 但保留工作目录
112113
await x('git', ['reset', '--soft', `HEAD~${count}`], execOptions)
113114

114115
// 创建新提交
115-
// 使用参数数组格式避免 shell 解析问题
116116
await x('git', [
117117
'commit',
118118
...buildMessageArgs(commitMessage),
@@ -136,31 +136,20 @@ async function getOriginalCommitMessages(count: number, filter = (line: string)
136136
)
137137

138138
const msgList = res.stdout.split('\n')
139-
140-
const temp = msgList.filter(item => item)
141-
142139
const msg = msgList.filter(filter).join('\n')
143140

144-
console.log(temp, count, JSON.stringify(msg))
145141
return msg
146142
}
147143

148-
// 构建跨平台兼容的提交信息参数
149144
function buildMessageArgs(message: string): string[] {
150-
// 方法 1:直接传递(适用于简单消息)
151-
// return ['-m', message];
152-
153-
// 方法 2:使用临时文件(推荐用于复杂格式)
154145
const tempFile = createTempFile(message)
155146
return ['-F', tempFile]
156147
}
157148

158-
// 创建临时文件处理复杂消息
159149
function createTempFile(content: string): string {
160150
const tempPath = path.join(os.tmpdir(), `git-commit-${Date.now()}.txt`)
161151
fs.writeFileSync(tempPath, content, 'utf-8')
162152

163-
// 注册退出清理钩子
164153
process.once('exit', () => {
165154
try {
166155
fs.unlinkSync(tempPath)
@@ -170,6 +159,60 @@ function createTempFile(content: string): string {
170159
return tempPath
171160
}
172161

162+
// 获取需要合并的提交的 Hash 值
163+
async function getCommitHashes(count: number): Promise<string[]> {
164+
const result = await x('git', ['log', '--format=%H', `-n ${count}`, 'HEAD'], execOptions)
165+
return result.stdout.split('\n').filter(hash => hash)
166+
}
167+
168+
// 获取本地未推送到远程的标签
169+
async function getLocalUnpushedTags(commits: string[] = []): Promise<Record<string, string[]>> {
170+
const tagsForCommits: Record<string, string[]> = {}
171+
172+
// 获取本地所有标签
173+
const localTagsResult = await x('git', ['tag', '-l'], execOptions)
174+
const localTags = new Set(localTagsResult.stdout.split('\n').filter(tag => tag))
175+
176+
// 获取远程所有标签
177+
const remoteTagsResult = await x('git', ['ls-remote', '--tags', '--refs'], execOptions)
178+
const remoteTags = new Set(remoteTagsResult.stdout.split('\n').map(line => line.split('\t')[1]?.replace('refs/tags/', '')).filter(tag => tag))
179+
180+
// 找出那些存在于本地但没有推送到远程的标签
181+
const unpushedLocalTags = [...localTags].filter(tag => !remoteTags.has(tag))
182+
183+
// 为每个提交检查它是否有未推送的标签
184+
for (const commit of commits) {
185+
const result = await x('git', ['tag', '--points-at', commit], execOptions)
186+
const tags = result.stdout.split('\n').filter(tag => tag)
187+
188+
// 过滤出那些本地未推送的标签
189+
const unpushedTags = tags.filter(tag => unpushedLocalTags.includes(tag))
190+
tagsForCommits[commit] = unpushedTags
191+
}
192+
193+
return tagsForCommits
194+
}
195+
196+
// 为合并后的提交添加标签
197+
async function addTagsToCommit(commits: string[], tagsForCommits: Record<string, string[]>): Promise<void> {
198+
const lastCommit = commits[commits.length - 1] // 获取最后一个合并提交的哈希值
199+
200+
if (!lastCommit)
201+
return
202+
203+
for (const commit of commits) {
204+
const tags = tagsForCommits[commit] || []
205+
for (const tag of tags) {
206+
try {
207+
await x('git', ['tag', tag, lastCommit], execOptions) // 为最后的合并提交打标签
208+
}
209+
catch (error) {
210+
console.error(`Failed to tag commit ${commit} with tag ${tag}: ${error}`)
211+
}
212+
}
213+
}
214+
}
215+
173216
async function checkWorkingDirectoryClean(): Promise<void> {
174217
try {
175218
await x('git', ['diff-index', '--quiet', 'HEAD', '--'], execOptions)

0 commit comments

Comments
 (0)