fuc-new/doc/node_modules/@vuepress/plugin-git/lib/node/utils/getCommits.js
hellcat 6adbc04d4b 1
2025-10-03 17:17:08 +08:00

135 lines
4.3 KiB
JavaScript

import { spawn } from 'node:child_process';
import { logger } from './logger.js';
const INFO_SPLITTER = '[|]';
const COMMIT_SPLITTER = '\\|/';
const RE_CO_AUTHOR = /^ *Co-authored-by: ?([^<]*)<([^>]*)> */gim;
const getCoAuthorsFromCommitBody = (body) => body
? Array.from(body.matchAll(RE_CO_AUTHOR)).map(([, name, email]) => ({
name: name.trim(),
email: email.trim(),
}))
: [];
const getGitLogFormat = ({ contributors, changelog, }) => {
if (!contributors && !changelog)
// hash | author_date
return ['%H', '%ad'].join(INFO_SPLITTER);
if (contributors && !changelog)
// hash | author_date | author_name | author_email | body
return ['%H', '%ad', '%an', '%ae', '%b'].join(INFO_SPLITTER);
// hash | author_date | author_name | author_email | body | subject | ref
return ['%H', '%ad', '%an', '%ae', '%b', '%s', '%d'].join(INFO_SPLITTER);
};
/**
* Helper function to run git command using spawn and return stdout as a promise.
* Rejects if the git command exits with a non-zero code.
*/
const runGitLog = (args, cwd) => new Promise((resolve, reject) => {
const gitProcess = spawn('git', ['log', ...args], {
cwd,
stdio: ['ignore', 'pipe', 'pipe'],
});
let stdoutData = '';
let stderrData = '';
gitProcess.stdout.on('data', (chunk) => {
stdoutData += chunk.toString('utf8');
});
gitProcess.stderr.on('data', (chunk) => {
stderrData += chunk.toString('utf8');
});
gitProcess.on('error', (error) => {
reject(new Error(`Failed to spawn 'git log': ${error.message}`));
});
gitProcess.on('close', (code) => {
if (code === 0) {
resolve(stdoutData);
}
else {
reject(new Error(`'git log' failed with exit code ${code}: ${stderrData.trim()}`));
}
});
});
/**
* Get raw commits for a specific file
*
* ${commit_hash} ${author_name} ${author_email} ${author_date} ${subject} ${ref} ${body}
*
* @see {@link https://git-scm.com/docs/pretty-formats | documentation} for details.
*/
export const getRawCommits = async (filepath, cwd, options) => {
const format = getGitLogFormat(options);
try {
const stdout = await runGitLog([
'--max-count=-1',
`--format=${format}${COMMIT_SPLITTER}`,
'--date=unix',
'--follow',
'--',
filepath,
], cwd);
return stdout
.slice(0, -1 - COMMIT_SPLITTER.length)
.split(`${COMMIT_SPLITTER}\n`)
.filter(Boolean)
.map((rawString) => {
const [hash, time, author = '', email = '', body = '', message = '', refs = '',] = rawString.split(INFO_SPLITTER).map((v) => v.trim());
return {
filepath,
hash,
time: Number.parseInt(time, 10) * 1000,
message,
body,
refs,
author,
email,
coAuthors: getCoAuthorsFromCommitBody(body),
};
});
}
catch (error) {
logger.error(`Failed to get commits for ${filepath} in ${cwd}:`, error);
return [];
}
};
/**
* Merge raw commits by hash
*
* 按哈希值合并原始提交记录
*
* @param commits - Raw commits
*
* 原始提交记录
*/
export const mergeRawCommits = (commits) => {
const commitMap = new Map();
commits.forEach(({ filepath, ...commit }) => {
const _commit = commitMap.get(commit.hash);
if (_commit)
_commit.filepaths.push(filepath);
else
commitMap.set(commit.hash, { ...commit, filepaths: [filepath] });
});
const result = Array.from(commitMap.values());
return result;
};
/**
* Get merged commits for multiple file paths
*
* 获取多个文件路径的合并提交记录
*
* @param filepaths - File paths to get commits for
*
* 要获取提交记录的文件路径
*
* @param cwd - Working directory
*
* 工作目录
*
* @param options - Git plugin options
*
* Git 插件选项
*/
export const getCommits = async (filepaths, cwd, options) => {
const rawCommits = (await Promise.all(filepaths.map((filepath) => getRawCommits(filepath, cwd, options)))).flat();
return mergeRawCommits(rawCommits).sort((a, b) => b.time - a.time);
};