repo-dive 0.18.0 → 0.18.1
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/dist/cli.js
CHANGED
|
@@ -44237,7 +44237,7 @@ var runWith = (command, config) => {
|
|
|
44237
44237
|
};
|
|
44238
44238
|
var package_default = {
|
|
44239
44239
|
name: "repo-dive",
|
|
44240
|
-
version: "0.18.
|
|
44240
|
+
version: "0.18.1",
|
|
44241
44241
|
description: "Dive into a git repo's history: per-commit snapshots, an indexed metrics catalog and an interactive dashboard",
|
|
44242
44242
|
keywords: [
|
|
44243
44243
|
"git",
|
|
@@ -44395,7 +44395,7 @@ var captureStream = (stream) => stream.pipe(decodeText$1(), mkString);
|
|
|
44395
44395
|
* the invocation explicit.
|
|
44396
44396
|
*/
|
|
44397
44397
|
var runCommand = fnUntraced(function* (command, args, options) {
|
|
44398
|
-
const handle = yield* make$33(command, [...args], { stdin: "ignore" });
|
|
44398
|
+
const handle = yield* make$33(command, [...args], { stdin: options?.input === void 0 ? "ignore" : make$38(new TextEncoder().encode(options.input)) });
|
|
44399
44399
|
const { stdout, stderr, exitCode } = yield* all({
|
|
44400
44400
|
stdout: captureStream(handle.stdout),
|
|
44401
44401
|
stderr: captureStream(handle.stderr),
|
|
@@ -53596,6 +53596,135 @@ var inferAliases = (resolveContributor, authors) => {
|
|
|
53596
53596
|
return (email, name) => resolveContributor(anchorEmailByKey.get(keyOf(email)) ?? email, name);
|
|
53597
53597
|
};
|
|
53598
53598
|
//#endregion
|
|
53599
|
+
//#region src/cli/shared/indexing/parse-identity.ts
|
|
53600
|
+
/**
|
|
53601
|
+
* Splits a `Co-authored-by:` trailer into its parts:
|
|
53602
|
+
* `"Claude Fable 5 <noreply@anthropic.com>"` → name + email.
|
|
53603
|
+
*
|
|
53604
|
+
* Keeping the email (rather than the display name alone) is what lets a
|
|
53605
|
+
* co-author resolve through the very same `resolveContributor` an author goes
|
|
53606
|
+
* through — so alias groups, `displayName`/`url` overrides and kind derivation
|
|
53607
|
+
* apply identically whether a person wrote a commit or helped with one.
|
|
53608
|
+
*/
|
|
53609
|
+
var parseIdentity = (identity) => {
|
|
53610
|
+
const open = identity.indexOf("<");
|
|
53611
|
+
const close = identity.lastIndexOf(">");
|
|
53612
|
+
if (open === -1 || close < open) return {
|
|
53613
|
+
name: identity.trim(),
|
|
53614
|
+
email: ""
|
|
53615
|
+
};
|
|
53616
|
+
return {
|
|
53617
|
+
name: identity.slice(0, open).trim(),
|
|
53618
|
+
email: identity.slice(open + 1, close).trim()
|
|
53619
|
+
};
|
|
53620
|
+
};
|
|
53621
|
+
//#endregion
|
|
53622
|
+
//#region src/cli/shared/indexing/read-mailmap.ts
|
|
53623
|
+
var CheckMailmapOutputError = class extends TaggedError$1("CheckMailmapOutputError") {
|
|
53624
|
+
get message() {
|
|
53625
|
+
return `git check-mailmap answered ${this.contactCount} contacts with ${this.lineCount} lines`;
|
|
53626
|
+
}
|
|
53627
|
+
};
|
|
53628
|
+
/**
|
|
53629
|
+
* The line `git check-mailmap` looks an identity up by: `Name <email>`, or
|
|
53630
|
+
* `<email>` alone, which matches the `.mailmap` entries that don't name the
|
|
53631
|
+
* old identity. `undefined` for what it can't look up: no email (a bare
|
|
53632
|
+
* `Co-authored-by: Some Name`), an email with a `>` in it, which git cuts
|
|
53633
|
+
* short there, or a line break, which would split one contact in two.
|
|
53634
|
+
*/
|
|
53635
|
+
var contactOf = (name, email) => {
|
|
53636
|
+
if (email === "" || /[>\n]/.test(email) || name.includes("\n")) return;
|
|
53637
|
+
return name === "" ? `<${email}>` : `${name} <${email}>`;
|
|
53638
|
+
};
|
|
53639
|
+
/**
|
|
53640
|
+
* Looks up the identities `facts` name in the repository's current `.mailmap`
|
|
53641
|
+
* and returns a function that rewrites a fact through it, or `undefined` when
|
|
53642
|
+
* the `.mailmap` changes none of them.
|
|
53643
|
+
*
|
|
53644
|
+
* Collectors record authors under `author` as a bare email and co-authors
|
|
53645
|
+
* under `coAuthor` as the trailer's `Name <email>`, which is looked up by name
|
|
53646
|
+
* too, the way `git shortlog --group=trailer:co-authored-by` does. Neither is
|
|
53647
|
+
* mapped when collected — file survival reads the raw `%ae`, trailers are
|
|
53648
|
+
* kept verbatim — except line survival, which `git blame` maps through the
|
|
53649
|
+
* `.mailmap` of the day. Mapping them all again here, through the `.mailmap`
|
|
53650
|
+
* that `listCommits` reads commit authors through, brings entries added since
|
|
53651
|
+
* a snapshot was collected to every chart without a re-scan.
|
|
53652
|
+
*
|
|
53653
|
+
* Only additions reach back that way: line survival collected under an entry
|
|
53654
|
+
* keeps the address `git blame` mapped it to, so an entry removed or changed
|
|
53655
|
+
* since takes a re-scan to undo there. That address is looked up once more as
|
|
53656
|
+
* well, which only moves it if the `.mailmap` maps it in turn — a chain git
|
|
53657
|
+
* itself never follows.
|
|
53658
|
+
*/
|
|
53659
|
+
var readMailmap = fn("readMailmap")(function* (repoRoot, facts) {
|
|
53660
|
+
const authors = /* @__PURE__ */ new Set();
|
|
53661
|
+
const coAuthors = /* @__PURE__ */ new Set();
|
|
53662
|
+
for (const fact of facts) {
|
|
53663
|
+
const author = fact.categories?.["author"];
|
|
53664
|
+
const coAuthor = fact.categories?.["coAuthor"];
|
|
53665
|
+
if (author !== void 0) authors.add(author);
|
|
53666
|
+
else if (coAuthor !== void 0) coAuthors.add(coAuthor);
|
|
53667
|
+
}
|
|
53668
|
+
const lookups = [];
|
|
53669
|
+
for (const value of authors) {
|
|
53670
|
+
const contact = contactOf("", value);
|
|
53671
|
+
if (contact !== void 0) lookups.push({
|
|
53672
|
+
key: "author",
|
|
53673
|
+
value,
|
|
53674
|
+
contact
|
|
53675
|
+
});
|
|
53676
|
+
}
|
|
53677
|
+
for (const value of coAuthors) {
|
|
53678
|
+
const { name, email } = parseIdentity(value);
|
|
53679
|
+
const contact = contactOf(name, email);
|
|
53680
|
+
if (contact !== void 0) lookups.push({
|
|
53681
|
+
key: "coAuthor",
|
|
53682
|
+
value,
|
|
53683
|
+
contact
|
|
53684
|
+
});
|
|
53685
|
+
}
|
|
53686
|
+
if (lookups.length === 0) return;
|
|
53687
|
+
const answers = (yield* runGit([
|
|
53688
|
+
"-C",
|
|
53689
|
+
repoRoot,
|
|
53690
|
+
"check-mailmap",
|
|
53691
|
+
"--stdin"
|
|
53692
|
+
], { input: lookups.map(({ contact }) => `${contact}\n`).join("") })).split("\n");
|
|
53693
|
+
if (answers.length !== lookups.length + 1) return yield* new CheckMailmapOutputError({
|
|
53694
|
+
contactCount: lookups.length,
|
|
53695
|
+
lineCount: answers.length - 1
|
|
53696
|
+
});
|
|
53697
|
+
/** Category value → what replaces it, for the values the `.mailmap` changes. */
|
|
53698
|
+
const replacements = {
|
|
53699
|
+
author: /* @__PURE__ */ new Map(),
|
|
53700
|
+
coAuthor: /* @__PURE__ */ new Map()
|
|
53701
|
+
};
|
|
53702
|
+
for (const [index, { key, value, contact }] of lookups.entries()) {
|
|
53703
|
+
const answer = answers[index] ?? contact;
|
|
53704
|
+
if (key === "coAuthor") {
|
|
53705
|
+
if (answer !== contact) replacements.coAuthor.set(value, answer);
|
|
53706
|
+
continue;
|
|
53707
|
+
}
|
|
53708
|
+
const { email } = parseIdentity(answer);
|
|
53709
|
+
if (email !== value) replacements.author.set(value, email);
|
|
53710
|
+
}
|
|
53711
|
+
if (replacements.author.size === 0 && replacements.coAuthor.size === 0) return;
|
|
53712
|
+
return (fact) => {
|
|
53713
|
+
for (const key of ["author", "coAuthor"]) {
|
|
53714
|
+
const value = fact.categories?.[key];
|
|
53715
|
+
const replacement = value === void 0 ? void 0 : replacements[key].get(value);
|
|
53716
|
+
if (replacement !== void 0) return {
|
|
53717
|
+
...fact,
|
|
53718
|
+
categories: {
|
|
53719
|
+
...fact.categories,
|
|
53720
|
+
[key]: replacement
|
|
53721
|
+
}
|
|
53722
|
+
};
|
|
53723
|
+
}
|
|
53724
|
+
return fact;
|
|
53725
|
+
};
|
|
53726
|
+
});
|
|
53727
|
+
//#endregion
|
|
53599
53728
|
//#region src/cli/shared/indexing/remote.ts
|
|
53600
53729
|
/**
|
|
53601
53730
|
* Turns whatever `remote.origin.url` holds into a URL a browser can open, or
|
|
@@ -53653,27 +53782,6 @@ var kindGroupLabels = {
|
|
|
53653
53782
|
bot: "Bots",
|
|
53654
53783
|
ai: "AI agents"
|
|
53655
53784
|
};
|
|
53656
|
-
/**
|
|
53657
|
-
* Splits a `Co-authored-by:` trailer into its parts:
|
|
53658
|
-
* `"Claude Fable 5 <noreply@anthropic.com>"` → name + email.
|
|
53659
|
-
*
|
|
53660
|
-
* Keeping the email (rather than the display name alone) is what lets a
|
|
53661
|
-
* co-author resolve through the very same `resolveContributor` an author goes
|
|
53662
|
-
* through — so alias groups, `displayName`/`url` overrides and kind derivation
|
|
53663
|
-
* apply identically whether a person wrote a commit or helped with one.
|
|
53664
|
-
*/
|
|
53665
|
-
var parseIdentity = (identity) => {
|
|
53666
|
-
const open = identity.indexOf("<");
|
|
53667
|
-
const close = identity.lastIndexOf(">");
|
|
53668
|
-
if (open === -1 || close < open) return {
|
|
53669
|
-
name: identity.trim(),
|
|
53670
|
-
email: ""
|
|
53671
|
-
};
|
|
53672
|
-
return {
|
|
53673
|
-
name: identity.slice(0, open).trim(),
|
|
53674
|
-
email: identity.slice(open + 1, close).trim()
|
|
53675
|
-
};
|
|
53676
|
-
};
|
|
53677
53785
|
var sumMetric = (commit, metric, filter) => {
|
|
53678
53786
|
let total = 0;
|
|
53679
53787
|
for (const facts of commit.factsByCollector.values()) for (const fact of facts) if (fact.metric === metric && (filter === void 0 || filter(fact.categories ?? {}))) total += fact.value;
|
|
@@ -53878,8 +53986,8 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl, lineageOf) => {
|
|
|
53878
53986
|
* config merges them back into one.
|
|
53879
53987
|
*/
|
|
53880
53988
|
const contributorFor = (resolved, observedName) => {
|
|
53881
|
-
const name = nameOf(resolved, observedName);
|
|
53882
53989
|
const email = resolved.canonicalEmail.toLowerCase();
|
|
53990
|
+
const name = (resolved.kind === "human" ? nameByCanonicalEmail.get(email) : void 0) ?? nameOf(resolved, observedName);
|
|
53883
53991
|
const key = resolved.kind === "human" ? email : `${name.toLowerCase()}\u{1F}${email}`;
|
|
53884
53992
|
const contributor = contributorMap.get(key) ?? {
|
|
53885
53993
|
index: contributorMap.size,
|
|
@@ -54052,7 +54160,11 @@ var runIndex = fn("runIndex")(function* ({ repoPath }) {
|
|
|
54052
54160
|
incompleteOutputs
|
|
54053
54161
|
};
|
|
54054
54162
|
}), { concurrency: 16 });
|
|
54055
|
-
const
|
|
54163
|
+
const remapIdentities = yield* readMailmap(repoRoot, readOutcomes.values().flatMap(({ facts }) => facts.factsByCollector.values().flatMap((collectorFacts) => collectorFacts.values())));
|
|
54164
|
+
const commitFacts = readOutcomes.map(({ facts }) => remapIdentities === void 0 ? facts : {
|
|
54165
|
+
...facts,
|
|
54166
|
+
factsByCollector: new Map(facts.factsByCollector.entries().map(([collector, collectorFacts]) => [collector, collectorFacts.map(remapIdentities)]))
|
|
54167
|
+
});
|
|
54056
54168
|
const unknownCollectorDirs = readOutcomes.reduce((total, outcome) => total + outcome.unknownCollectorDirs, 0);
|
|
54057
54169
|
const offMainlineSnapshots = readOutcomes.reduce((total, outcome) => total + outcome.offMainlineSnapshots, 0);
|
|
54058
54170
|
const incompleteOutputs = readOutcomes.reduce((total, outcome) => total + outcome.incompleteOutputs, 0);
|