UNPKG

git-contributor-stats

Version:

CLI to compute contributor and repository statistics from a Git repository (commits, lines added/deleted, frequency, heatmap, bus-factor), with filters and multiple output formats.

122 lines (121 loc) 4.65 kB
import path from "node:path"; import { b as buildAliasResolver, a as aggregateBasic, p as pickSortMetric, c as computeMeta, d as analyze } from "../chunks/analytics-SL4YC1kG.mjs"; import { i as isGitRepo, b as buildGitLogArgs, r as runGit, p as parseGitLog } from "../chunks/git-BxSpsWYT.mjs"; import { p as parseDateInput, a as countTotalLines, t as tryLoadJSON } from "../chunks/utils-CFufYn8A.mjs"; import "node:child_process"; import "node:fs"; function loadAliasConfig(opts, repo) { let aliasConfig = opts.aliasConfig ?? void 0; let aliasPath = null; if (!aliasConfig) { if (opts.aliasFile) { aliasPath = path.resolve(process.cwd(), opts.aliasFile); aliasConfig = tryLoadJSON(aliasPath); } else { const defaultAlias = path.join(repo, ".git-contributor-stats-aliases.json"); aliasConfig = tryLoadJSON(defaultAlias); if (aliasConfig) aliasPath = defaultAlias; } } return { config: aliasConfig, path: aliasPath }; } function normalizePaths(paths) { if (Array.isArray(paths)) { return paths; } if (paths) { return [paths]; } return []; } function getRepoBranch(repo, optsBranch) { if (optsBranch) return optsBranch; const result = runGit(repo, ["rev-parse", "--abbrev-ref", "HEAD"]); return result.stdout?.trim() || null; } async function getContributorStats(opts = {}) { const repo = path.resolve(process.cwd(), opts.repo || "."); if (!isGitRepo(repo)) throw new Error(`Not a Git repository: ${repo}`); const debug = (...msg) => { if (opts.verbose) console.error("[debug]", ...msg); }; const { config: aliasConfig, path: aliasPath } = loadAliasConfig(opts, repo); if (aliasPath) debug(`aliasFile=${aliasPath}`); debug(`repo=${repo}`); const { resolve: aliasResolveFn, canonicalDetails } = buildAliasResolver(aliasConfig); const since = parseDateInput(opts.since); const until = parseDateInput(opts.until); const paths = normalizePaths(opts.paths); const gitArgs = buildGitLogArgs({ branch: opts.branch, since, until, author: opts.author, includeMerges: !!opts.includeMerges, paths }); debug("git", gitArgs.map((a) => a.includes(" ") ? `'${a}'` : a).join(" ")); const result = runGit(repo, gitArgs); if (!result.ok) throw new Error(result.error || "Failed to run git log"); const commits = parseGitLog(result.stdout); debug(`parsed commits: ${commits.length}`); const groupBy = (opts.groupBy || "email").toLowerCase() === "name" ? "name" : "email"; const labelBy = opts.labelBy?.toLowerCase() === "email" ? "email" : "name"; const similarityThreshold = opts.similarity ?? 0.85; let contributorsBasic = aggregateBasic(commits, { groupBy, aliasResolver: aliasResolveFn, canonicalDetails, similarity: similarityThreshold }); const sorter = pickSortMetric(opts.sortBy || "changes"); contributorsBasic.sort(sorter); if (opts.top && Number.isFinite(opts.top) && opts.top > 0) { contributorsBasic = contributorsBasic.slice(0, opts.top); } const meta = computeMeta(contributorsBasic); const analysis = analyze(commits, similarityThreshold, aliasResolveFn, canonicalDetails, groupBy); let topContributors = analysis.topContributors; if (opts.sortBy && opts.sortBy !== "commits") { const metricSorter = pickSortMetric(opts.sortBy); topContributors = [...topContributors].sort(metricSorter); } if (opts.top && Number.isFinite(opts.top) && opts.top > 0) { topContributors = topContributors.slice(0, opts.top); } let totalLines = 0; if (opts.countLines !== false) { totalLines = await countTotalLines(repo, runGit); } const repoRootResult = runGit(repo, ["rev-parse", "--show-toplevel"]); const repoRoot = repoRootResult.ok ? repoRootResult.stdout?.trim() ?? repo : repo; const branch = getRepoBranch(repo, opts.branch); return { meta: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), repo: repoRoot, branch, since: since || null, until: until || null }, totalCommits: analysis.totalCommits, totalLines, contributors: analysis.contributors, topContributors, topStats: analysis.topStats, commitFrequency: analysis.commitFrequency, heatmap: analysis.heatmap, heatmapContributors: analysis.heatmapContributors, busFactor: { busFactor: analysis.busFactor.busFactor ?? 0, candidates: analysis.busFactor.candidates ?? [], details: analysis.busFactor.details, filesSingleOwner: analysis.busFactor.filesSingleOwner }, basic: { meta, groupBy, labelBy } }; } export { getContributorStats as g }; //# sourceMappingURL=stats.mjs.map