UNPKG

t-comm

Version:

专业、稳定、纯粹的工具库

350 lines (347 loc) 10.9 kB
import { execCommand } from '../node/node-command.mjs'; import { getFs } from '../nodejs/fs.mjs'; import { getPath } from '../nodejs/path.mjs'; import { isWindows } from '../validate/validate.mjs'; import '@babel/runtime/helpers/typeof'; import '../nodejs/child_process.mjs'; var DEFAULT_COMMIT_INFO = { author: 'UNKNOWN', message: 'UNKNOWN', hash: 'UNKNOWN', date: 'UNKNOWN', timeStamp: '0', branch: '' }; var localInfo = { curBranch: '', gotCurBranch: false, commitInfo: Object.assign({}, DEFAULT_COMMIT_INFO), gotCommitInfo: false, gitAuthor: '', gotGitAuthor: false, commitMessage: '', gotCommitMessage: false, lastRoot: '' }; // 是否在 git 仓库的判断结果缓存:key 为绝对路径,value 为 boolean var gitRepoCache = new Map(); /** * 判断指定路径(默认 `process.cwd()`)是否在 git 仓库中。 * * 实现方式:从给定目录起向上递归查找 `.git`(目录或文件,后者用于 worktree / submodule 场景)。 * 不会执行任何 git 命令,**纯文件系统判断**,零副作用、不会刷屏报错。 * * 结果会按绝对路径缓存,重复调用近乎零开销。 * * @param root 起始目录,默认为 `process.cwd()` * @returns 是否在 git 仓库内 * * @example * ```ts * if (isInGitRepo()) { * const branch = getGitCurBranch(); * } * ``` */ function isInGitRepo(root) { var fs = getFs(); var path = getPath(); var cur; try { cur = path.resolve(root || process.cwd()); } catch (_) { return false; } if (gitRepoCache.has(cur)) { return gitRepoCache.get(cur); } var startDir = cur; // 沿父目录向上找,直到根目录(path.dirname('/') === '/') // eslint-disable-next-line no-constant-condition while (true) { if (gitRepoCache.has(cur)) { var cached = gitRepoCache.get(cur); gitRepoCache.set(startDir, cached); return cached; } try { if (fs.existsSync(path.join(cur, '.git'))) { gitRepoCache.set(cur, true); gitRepoCache.set(startDir, true); return true; } } catch (_) { // 忽略权限等异常,继续向上找 } var parent = path.dirname(cur); if (parent === cur) { gitRepoCache.set(startDir, false); return false; } cur = parent; } } /** * 获取当前分支 * @returns {string} 分支名称 * * @example * * getGitCurBranch() * * // => master */ function getGitCurBranch(root) { var useCache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; if (useCache && localInfo.lastRoot === root && localInfo.gotCurBranch) { return localInfo.curBranch; } // 不在 git 仓库时直接返回空字符串,避免 git 命令产生大量 stderr/堆栈刷屏 if (!isInGitRepo(root)) { localInfo.curBranch = ''; localInfo.gotCurBranch = true; localInfo.lastRoot = root || ''; return ''; } var res = ''; try { res = execCommand('git symbolic-ref --short -q HEAD', root, { stdio: ['ignore', 'pipe', 'ignore'] }); } catch (_err) { // 不打印日志,调用方按返回值判断即可(execCommand 内部失败时已自行处理 stdio) } localInfo.curBranch = res; localInfo.gotCurBranch = true; localInfo.lastRoot = root || ''; return res; } /** * 获取提交信息 * @param {string} root 根路径 * @param {boolean} mergeCommit 是否包含 merge 的提交 * @param {boolean} splitMessage 是否去掉提交信息的前缀 * @returns {string} 提交信息 * * @example * ```ts * getGitCommitMessage() * // '优化一部分文档' * ``` */ function getGitCommitMessage(root) { var mergeCommit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var splitMessage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var useCache = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; if (useCache && localInfo.lastRoot === root && localInfo.gotCommitMessage) { return localInfo.commitMessage; } // 不在 git 仓库时直接返回空字符串,避免 git 命令报错 if (!isInGitRepo(root)) { localInfo.commitMessage = ''; localInfo.gotCommitMessage = true; localInfo.lastRoot = root || ''; return ''; } var command; if (isWindows()) { // Windows兼容版本:去掉cat管道 command = "git log ".concat(mergeCommit ? '' : '--no-merges', " -1 --format=%s"); } else { // Unix/Linux/macOS版本:保持原有命令 command = "git log ".concat(mergeCommit ? '' : '--no-merges', " -1 --format=%s | cat"); } var infoMessage = execCommand(command, root); var result = ''; if (splitMessage) { result = (infoMessage.split(':')[1] || infoMessage.split(':')[1] || '').trim(); } else { result = infoMessage; } localInfo.gotCommitMessage = true; localInfo.commitMessage = result; localInfo.lastRoot = root || ''; return result; } /** * 获取提交信息 * @param {string} root 根路径 * @param {boolean} mergeCommit 是否包含 merge 的提交 * @param {boolean} splitMessage 是否去掉提交信息的前缀 * @returns {Object} 提交对象 * * @example * ```ts * getGitCommitInfo() * { * author: 'novlan1', * message: ' 优化一部分文档', * hash: '0cb71f9', * date: '2022-10-02 10:34:31 +0800', * timeStamp: '1664678071', * branch: 'master' * } * ``` */ function getGitCommitInfo(root) { var mergeCommit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var splitMessage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var useCache = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; if (useCache && localInfo.lastRoot === root && localInfo.gotCommitInfo) { return localInfo.commitInfo; } // 不在 git 仓库时直接返回默认信息(branch=''),避免后续每个子命令都报错 if (!isInGitRepo(root)) { var fallback = Object.assign({}, DEFAULT_COMMIT_INFO); localInfo.commitInfo = fallback; localInfo.gotCommitInfo = true; localInfo.lastRoot = root || ''; return fallback; } var command; if (isWindows()) { // Windows兼容版本:避免使用Perl和管道操作 command = "git log ".concat(mergeCommit ? '' : '--no-merges', " -1 --date=iso --pretty=format:'{\"author\": \"%aN\", \"hash\": \"%h\", \"date\": \"%ad\", \"timeStamp\": \"%at\"}'"); } else { // Unix/Linux/macOS版本:保持原有命令 command = "\n git log ".concat(mergeCommit ? '' : '--no-merges', " -1 --date=iso --pretty=format:'{\"author\": \"%aN\", \"hash\": \"%h\", \"date\": \"%ad\", \"timeStamp\": \"%at\"},' $@ | perl -pe 'BEGIN{print \"[\"}; END{print \"]\n\"}' | perl -pe 's/},]/}]/'"); } var stdout = execCommand(command, root); var info = Object.assign({}, DEFAULT_COMMIT_INFO); try { if (isWindows()) { // Windows版本:直接解析JSON,去掉数组格式 if (stdout) { info = JSON.parse(stdout.trim()); } } else { // Unix/Linux/macOS版本:原有解析逻辑 info = JSON.parse(stdout)[0]; } } catch (err) { console.warn('[getGitCommitInfo] parse error', stdout); } info.branch = getGitCurBranch(root, false); info.message = getGitCommitMessage(root, mergeCommit, splitMessage, false); localInfo.gotCommitInfo = true; localInfo.commitInfo = info; localInfo.lastRoot = root || ''; return info; } /** * 获取最新tag * @returns {string} 最新tag * @example * ```ts * getGitLastTag(); * // 'v1.2.3' * * // 指定仓库路径 * getGitLastTag('/path/to/repo'); * ``` */ function getGitLastTag(root) { if (!isInGitRepo(root)) return ''; var fakeFirstTag = execCommand('git tag -l', root); if (!fakeFirstTag) return ''; // 不能使用`git tag | head -1`,这个命令不准 var command = 'git describe --abbrev=0'; var tag = execCommand(command, root); return tag || ''; } /** * 获取tag到head的提交数目 * @param {string} tag git标签 * @returns {string} tag至今的提交数目 * @example * ```ts * getGitCommitsBeforeTag('v1.0.0'); * // '12' * * // 指定仓库路径 * getGitCommitsBeforeTag('v1.0.0', '/path/to/repo'); * ``` */ function getGitCommitsBeforeTag(tag, root) { if (!tag) return '0'; if (!isInGitRepo(root)) return '0'; var command; if (isWindows()) { // Windows兼容:使用git rev-list替代wc命令 command = "git rev-list --count ".concat(tag, "...HEAD --no-merges"); } else { // Unix/Linux/macOS版本:保持原有命令 command = "git log ".concat(tag, "...HEAD --no-merges --oneline | wc -l"); } var commits = execCommand(command, root); return commits || '0'; } /** * 获取打标签的时间 * @private * @param {string} tag git标签 * @returns {string} 标签时间 * @example * ```ts * getGitTagTime('v1.0.0'); * // '2023-09-12 10:30:45 +0800' * ``` */ function getGitTagTime(tag, root) { if (!tag) return ''; if (!isInGitRepo(root)) return ''; var command; if (isWindows()) { // Windows兼容:去掉cat管道,直接使用git log command = "git log -1 --format=%ai ".concat(tag); } else { // Unix/Linux/macOS版本:保持原有命令 command = "git log -1 --format=%ai ".concat(tag, " | cat"); } return execCommand(command, root); } /** * 获取当前用户 * @param isPriorGit - 是否优先使用git用户信息 * @returns user * @example * ```ts * // 默认优先 process.env.VUE_APP_AUTHOR,其次 git config user.name * getGitAuthor(); * // 'novlan1' * * // 优先使用 git 配置中的 user.name * getGitAuthor(true); * ``` */ function getGitAuthor() { var isPriorGit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var root = arguments.length > 1 ? arguments[1] : undefined; var useCache = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; if (useCache && localInfo.lastRoot === root && localInfo.gotGitAuthor) { return localInfo.gitAuthor; } var envAuthor = process.env.VUE_APP_AUTHOR; var gitAuthor = ''; // 不在 git 仓库时跳过 git 命令,仅使用环境变量;避免无意义的 stderr 刷屏 if (isInGitRepo(root)) { try { gitAuthor = execCommand('git config user.name', root, { stdio: ['ignore', 'pipe', 'ignore'] }); } catch (_err) { // 静默:调用方可通过返回值或环境变量兜底 } } var result = ''; if (isPriorGit) { result = gitAuthor || envAuthor || ''; } else { result = envAuthor || gitAuthor || ''; } localInfo.gitAuthor = result; localInfo.gotGitAuthor = true; localInfo.lastRoot = root || ''; return result; } export { getGitAuthor, getGitCommitInfo, getGitCommitMessage, getGitCommitsBeforeTag, getGitCurBranch, getGitLastTag, getGitTagTime, isInGitRepo };