wukong-gitlog-cli 1.0.35 → 1.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/git.mjs +31 -28
- package/src/utils/authorNormalizer.mjs +22 -29
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [1.0.36](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.35...v1.0.36) (2025-12-12)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* 🐛 createAuthorNormalizer ([12f70ba](https://github.com/tomatobybike/wukong-gitlog-cli/commit/12f70ba10013cbba988d3ae1d7fed6f0949c6cbc))
|
|
11
|
+
|
|
5
12
|
### [1.0.35](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.34...v1.0.35) (2025-12-12)
|
|
6
13
|
|
|
7
14
|
|
package/package.json
CHANGED
package/src/git.mjs
CHANGED
|
@@ -166,7 +166,10 @@ export async function getGitLogsQuick(opts) {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
/**
|
|
170
|
+
* 高性能 git log + numstat 获取 commit
|
|
171
|
+
*/
|
|
172
|
+
export async function getGitLogsFast(opts = {}) {
|
|
170
173
|
const { author, email, since, until, limit, merges } = opts
|
|
171
174
|
|
|
172
175
|
const pretty = `${[
|
|
@@ -174,16 +177,11 @@ export async function getGitLogsFast(opts) {
|
|
|
174
177
|
'%an', // author name
|
|
175
178
|
'%ae', // email
|
|
176
179
|
'%ad', // date
|
|
177
|
-
'%s',
|
|
178
|
-
'%B'
|
|
179
|
-
].join('%x1f')}%x1e`
|
|
180
|
+
'%s', // subject
|
|
181
|
+
'%B' // body
|
|
182
|
+
].join('%x1f') }%x1e`
|
|
180
183
|
|
|
181
|
-
const args = [
|
|
182
|
-
'log',
|
|
183
|
-
`--pretty=format:${pretty}`,
|
|
184
|
-
'--date=iso-local',
|
|
185
|
-
'--numstat'
|
|
186
|
-
]
|
|
184
|
+
const args = ['log', `--pretty=format:${pretty}`, '--date=iso-local', '--numstat']
|
|
187
185
|
|
|
188
186
|
if (author) args.push(`--author=${author}`)
|
|
189
187
|
if (email) args.push(`--author=${email}`)
|
|
@@ -193,21 +191,18 @@ export async function getGitLogsFast(opts) {
|
|
|
193
191
|
if (limit) args.push('-n', `${limit}`)
|
|
194
192
|
|
|
195
193
|
const { stdout } = await $`git ${args}`.quiet()
|
|
194
|
+
const raw = stdout.replace(/\r/g, '')
|
|
196
195
|
|
|
197
196
|
const commits = []
|
|
198
|
-
const raw = stdout.replace(/\r/g, '')
|
|
199
197
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
/([0-9a-f]+)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([\s\S]*?)(?=(?:[0-9a-f]{7,40}\x1f)|\x1e$)/g
|
|
198
|
+
// 匹配每个 commit header + numstat
|
|
199
|
+
// eslint-disable-next-line no-control-regex
|
|
200
|
+
const commitRegex = /([0-9a-f]+)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([\s\S]*?)(?=(?:[0-9a-f]{7,40}\x1f)|\x1e$)/g
|
|
204
201
|
let match
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const [_, hash, authorName, emailAddr, date, subject, bodyAndNumstat] =
|
|
208
|
-
|
|
209
|
-
const [, changeId] =
|
|
210
|
-
bodyAndNumstat.match(/Change-Id:\s*(I[0-9a-fA-F]+)/) || []
|
|
202
|
+
|
|
203
|
+
for (const ns of raw.matchAll(commitRegex)) {
|
|
204
|
+
const [_, hash, authorName, emailAddr, date, subject, bodyAndNumstat] = ns
|
|
205
|
+
const [, changeId] = bodyAndNumstat.match(/Change-Id:\s*(I[0-9a-fA-F]+)/) || []
|
|
211
206
|
|
|
212
207
|
const c = {
|
|
213
208
|
hash,
|
|
@@ -224,13 +219,12 @@ export async function getGitLogsFast(opts) {
|
|
|
224
219
|
files: []
|
|
225
220
|
}
|
|
226
221
|
|
|
227
|
-
// 匹配 numstat
|
|
222
|
+
// 匹配 numstat
|
|
228
223
|
const numstatRegex = /^(\d+)\s+(\d+)\s+(.+)$/gm
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
const file = nsMatch[3]
|
|
224
|
+
for (const m of bodyAndNumstat.matchAll(numstatRegex)) {
|
|
225
|
+
const added = parseInt(m[1], 10) || 0
|
|
226
|
+
const deleted = parseInt(m[2], 10) || 0
|
|
227
|
+
const file = m[3]
|
|
234
228
|
c.added += added
|
|
235
229
|
c.deleted += deleted
|
|
236
230
|
c.changed += added + deleted
|
|
@@ -240,8 +234,17 @@ export async function getGitLogsFast(opts) {
|
|
|
240
234
|
commits.push(c)
|
|
241
235
|
}
|
|
242
236
|
|
|
237
|
+
// 最终统一覆盖 author,保证所有 commit 都使用中文名(如存在)
|
|
238
|
+
const finalMap = normalizer.getMap()
|
|
239
|
+
for (const c of commits) {
|
|
240
|
+
if (c.email && finalMap[c.email]) {
|
|
241
|
+
c.author = finalMap[c.email]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
243
245
|
return {
|
|
244
246
|
commits,
|
|
245
|
-
authorMap:
|
|
247
|
+
authorMap: finalMap,
|
|
248
|
+
originalMap: normalizer.getOriginalMap()
|
|
246
249
|
}
|
|
247
250
|
}
|
|
@@ -1,26 +1,20 @@
|
|
|
1
|
-
// authorNormalizer.js
|
|
2
1
|
/**
|
|
3
2
|
* 按邮箱自动归一化作者名字
|
|
4
|
-
* -
|
|
5
|
-
* -
|
|
6
|
-
* - 自动清理空格与不可见字符
|
|
3
|
+
* - 中文名优先覆盖
|
|
4
|
+
* - 同邮箱多个英文名保持第一个
|
|
7
5
|
* - 保留原始 author 以便 debug
|
|
8
6
|
*/
|
|
9
7
|
export function createAuthorNormalizer() {
|
|
10
8
|
const map = {} // email -> canonical name
|
|
11
|
-
const originalMap = {} // email ->
|
|
9
|
+
const originalMap = {} // email -> Set of original author names
|
|
12
10
|
|
|
13
11
|
function isChinese(str) {
|
|
14
12
|
return /[\u4e00-\u9fa5]/.test(str)
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
function cleanName(str) {
|
|
18
|
-
|
|
19
|
-
return str
|
|
20
|
-
// eslint-disable-next-line no-misleading-character-class
|
|
21
|
-
.replace(/[\u200B\u200C\u200D\uFEFF]/g, '') // 去零宽空格
|
|
22
|
-
.replace(/\s+/g, ' ') // 多空格 -> 单空格
|
|
23
|
-
.trim()
|
|
16
|
+
// eslint-disable-next-line no-misleading-character-class
|
|
17
|
+
return (str || '').replace(/[\u200B\u200C\u200D\uFEFF]/g, '').trim()
|
|
24
18
|
}
|
|
25
19
|
|
|
26
20
|
function cleanEmail(str) {
|
|
@@ -28,31 +22,30 @@ export function createAuthorNormalizer() {
|
|
|
28
22
|
}
|
|
29
23
|
|
|
30
24
|
function getAuthor(name, email) {
|
|
31
|
-
const
|
|
32
|
-
const
|
|
25
|
+
const n = cleanName(name)
|
|
26
|
+
const e = cleanEmail(email)
|
|
33
27
|
|
|
34
|
-
if (!
|
|
28
|
+
if (!e) return n || 'Unknown'
|
|
35
29
|
|
|
36
|
-
// 记录原始 author
|
|
37
|
-
if (!originalMap[
|
|
38
|
-
if (
|
|
30
|
+
// 记录原始 author
|
|
31
|
+
if (!originalMap[e]) originalMap[e] = new Set()
|
|
32
|
+
if (n) originalMap[e].add(n)
|
|
39
33
|
|
|
40
|
-
const
|
|
34
|
+
const prev = map[e]
|
|
41
35
|
|
|
42
|
-
//
|
|
43
|
-
if (
|
|
44
|
-
map[
|
|
45
|
-
return
|
|
36
|
+
// 中文名优先覆盖
|
|
37
|
+
if (isChinese(n)) {
|
|
38
|
+
map[e] = n
|
|
39
|
+
return n
|
|
46
40
|
}
|
|
47
41
|
|
|
48
|
-
//
|
|
49
|
-
if (isChinese(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
// 已有中文名 → 返回中文
|
|
43
|
+
if (prev && isChinese(prev)) return prev
|
|
44
|
+
|
|
45
|
+
// 首次出现英文名 → 记录
|
|
46
|
+
if (!prev) map[e] = n
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
return canonical
|
|
48
|
+
return map[e]
|
|
56
49
|
}
|
|
57
50
|
|
|
58
51
|
return {
|