t-comm
Version:
专业、稳定、纯粹的工具库
350 lines (347 loc) • 10.9 kB
JavaScript
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 };