git-truck 4.1.0 → 5.0.0

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.
Files changed (35) hide show
  1. package/build/client/assets/ChevronButton-DMAgHXfP.js +1 -0
  2. package/build/client/assets/{CollapsibleHeader-CowQ8BBC.js → CollapsibleHeader-D64_oUaZ.js} +1 -1
  3. package/build/client/assets/Tooltip-DD8MeSQ5.js +5 -0
  4. package/build/client/assets/api.commits-B87eK7HE.js +1 -0
  5. package/build/client/assets/api.commits-C8MoPAnE.js +1 -0
  6. package/build/client/assets/api.progress-BjsGOmGF.js +1 -0
  7. package/build/client/assets/api.progress-ClQ--CbT.js +1 -0
  8. package/build/client/assets/browse-Dt072k2G.js +1 -0
  9. package/build/client/assets/browse-NZo8Wkxj.js +1 -0
  10. package/build/client/assets/clear-cache-BdOk-5Hg.js +1 -0
  11. package/build/client/assets/clear-cache-Dpk-kNlJ.js +1 -0
  12. package/build/client/assets/{manifest-27f3fdf9.js → manifest-fcb52500.js} +1 -1
  13. package/build/client/assets/{root-CBAhjBmF.js → root-Bw7JpNPD.js} +1 -1
  14. package/build/client/assets/root-ulNnm43y.css +2 -0
  15. package/build/client/assets/{ui-apdBVMs3.js → ui-BlLnrEiL.js} +1 -1
  16. package/build/client/assets/util-DXfdT-4_.js +44 -0
  17. package/build/client/assets/view-BO8_n49o.js +4 -0
  18. package/build/client/assets/{viewParams-BGn6CoZr.js → viewParams-CYa4lbK3.js} +1 -1
  19. package/build/server/assets/{server-build-CZoDK47O.js → server-build-ChRJnQIf.js} +525 -197
  20. package/build/server/index.js +1 -1
  21. package/cli.mjs +190 -43
  22. package/package.json +1 -1
  23. package/build/client/assets/ChevronButton-DK6ES4LI.js +0 -1
  24. package/build/client/assets/Tooltip-4EXAWNsE.js +0 -5
  25. package/build/client/assets/api.commits-B4POWHc4.js +0 -1
  26. package/build/client/assets/api.commits-BwXLd7eY.js +0 -1
  27. package/build/client/assets/api.progress-9Sjpquqz.js +0 -1
  28. package/build/client/assets/api.progress-PbuJDh_8.js +0 -1
  29. package/build/client/assets/browse-D4C7blMn.js +0 -1
  30. package/build/client/assets/browse-f4khjgf7.js +0 -1
  31. package/build/client/assets/clear-cache-BzRoZVxS.js +0 -1
  32. package/build/client/assets/clear-cache-_qCibnyW.js +0 -1
  33. package/build/client/assets/root-C1A3dI8J.css +0 -2
  34. package/build/client/assets/util-Cy1DhIdh.js +0 -44
  35. package/build/client/assets/view-xdV5WFuZ.js +0 -4
@@ -19927,7 +19927,7 @@ function handleBrowserRequest(request, responseStatusCode, responseHeaders, reac
19927
19927
  }
19928
19928
  var package_default = {
19929
19929
  name: "git-truck",
19930
- version: "4.1.0",
19930
+ version: "5.0.0",
19931
19931
  "private": false,
19932
19932
  description: "Visualizing a Git repository",
19933
19933
  license: "MIT",
@@ -20133,6 +20133,7 @@ function Icon({ path, size = "1rem", color = "currentColor", style = {}, classNa
20133
20133
  }
20134
20134
  //#endregion
20135
20135
  //#region node_modules/@mdi/js/mdi.js
20136
+ var mdiAccount = "M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z";
20136
20137
  var mdiAccountGroup = "M12,5.5A3.5,3.5 0 0,1 15.5,9A3.5,3.5 0 0,1 12,12.5A3.5,3.5 0 0,1 8.5,9A3.5,3.5 0 0,1 12,5.5M5,8C5.56,8 6.08,8.15 6.53,8.42C6.38,9.85 6.8,11.27 7.66,12.38C7.16,13.34 6.16,14 5,14A3,3 0 0,1 2,11A3,3 0 0,1 5,8M19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14C17.84,14 16.84,13.34 16.34,12.38C17.2,11.27 17.62,9.85 17.47,8.42C17.92,8.15 18.44,8 19,8M5.5,18.25C5.5,16.18 8.41,14.5 12,14.5C15.59,14.5 18.5,16.18 18.5,18.25V20H5.5V18.25M0,20V18.5C0,17.11 1.89,15.94 4.45,15.6C3.86,16.28 3.5,17.22 3.5,18.25V20H0M24,20H20.5V18.25C20.5,17.22 20.14,16.28 19.55,15.6C22.11,15.94 24,17.11 24,18.5V20Z";
20137
20138
  var mdiAccountMultiple = "M16 17V19H2V17S2 13 9 13 16 17 16 17M12.5 7.5A3.5 3.5 0 1 0 9 11A3.5 3.5 0 0 0 12.5 7.5M15.94 13A5.32 5.32 0 0 1 18 17V19H22V17S22 13.37 15.94 13M15 4A3.39 3.39 0 0 0 13.07 4.59A5 5 0 0 1 13.07 10.41A3.39 3.39 0 0 0 15 11A3.5 3.5 0 0 0 15 4Z";
20138
20139
  var mdiAccountMultipleCheck = "M19 17V19H7V17S7 13 13 13 19 17 19 17M16 8A3 3 0 1 0 13 11A3 3 0 0 0 16 8M19.2 13.06A5.6 5.6 0 0 1 21 17V19H24V17S24 13.55 19.2 13.06M18 5A2.91 2.91 0 0 0 17.11 5.14A5 5 0 0 1 17.11 10.86A2.91 2.91 0 0 0 18 11A3 3 0 0 0 18 5M7.34 8.92L8.5 10.33L3.75 15.08L1 12.08L2.16 10.92L3.75 12.5L7.34 8.92";
@@ -20150,6 +20151,7 @@ var mdiCheckboxBlank = "M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0
20150
20151
  var mdiCheckboxBlankOutline = "M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M19,5V19H5V5H19Z";
20151
20152
  var mdiCheckboxIntermediate = "M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M19,19H5V5H19V19M17,17H7V7H17V17Z";
20152
20153
  var mdiCheckboxMarked = "M10,17L5,12L6.41,10.58L10,14.17L17.59,6.58L19,8M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z";
20154
+ var mdiCheckboxMarkedOutline = "M19,19H5V5H15V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V11H19M7.91,10.08L6.5,11.5L11,16L21,6L19.59,4.58L11,13.17L7.91,10.08Z";
20153
20155
  var mdiChevronDoubleRight = "M5.59,7.41L7,6L13,12L7,18L5.59,16.59L10.17,12L5.59,7.41M11.59,7.41L13,6L19,12L13,18L11.59,16.59L16.17,12L11.59,7.41Z";
20154
20156
  var mdiChevronDown = "M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z";
20155
20157
  var mdiChevronRight = "M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z";
@@ -24553,7 +24555,7 @@ function CheckboxWithLabel({ children, checked, intermediate, onChange, classNam
24553
24555
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
24554
24556
  type: "checkbox",
24555
24557
  checked,
24556
- className: "peer hidden",
24558
+ className: "peer sr-only",
24557
24559
  onChange
24558
24560
  }),
24559
24561
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -39089,7 +39091,7 @@ var viewSearchParamsConfig = {
39089
39091
  timeUnit: parseAsStringLiteral(TimeUnitValues).withOptions({ shallow: false }),
39090
39092
  start: parseAsInteger.withOptions({ shallow: false }),
39091
39093
  end: parseAsInteger.withOptions({ shallow: false }),
39092
- includeCoauthors: parseAsBoolean.withDefault(false).withOptions({ shallow: false })
39094
+ includeCoauthors: parseAsBoolean.withDefault(true).withOptions({ shallow: false })
39093
39095
  };
39094
39096
  var viewSerializer = createSerializer(viewSearchParamsConfig);
39095
39097
  var loadViewSearchParams = createLoader(viewSearchParamsConfig);
@@ -42890,7 +42892,6 @@ function PointLegend() {
42890
42892
  const totalWeight = legendData.totalWeight;
42891
42893
  const matchesSearch = (label) => label.toLowerCase().includes(searchValue.toLowerCase());
42892
42894
  const filteredItems = searchValue.length > 0 ? items.filter(([label]) => matchesSearch(label)) : items;
42893
- if (items.length === 0) return null;
42894
42895
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
42895
42896
  className: "flex flex-col gap-2",
42896
42897
  children: [
@@ -42947,9 +42948,14 @@ function PointLegendTable({ items, totalWeight, metricType }) {
42947
42948
  className: "flex flex-col gap-2",
42948
42949
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
42949
42950
  className: cn$1("grid grid-cols-[max-content_1fr_max-content_minmax(calc(12*var(--spacing)),max-content)_max-content] items-center justify-between gap-x-2"),
42950
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PointLegendHeader, { metricType }), items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
42951
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PointLegendHeader, { metricType }), items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
42951
42952
  className: "text-tertiary-text dark:text-tertiary-text-dark col-span-full flex items-center justify-center text-sm",
42952
- children: "No items matched your search"
42953
+ children: [
42954
+ "No ",
42955
+ mapMetricToCategoryNoun[metricType],
42956
+ " match your current filters",
42957
+ " "
42958
+ ]
42953
42959
  }) : items.map(([label, info]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PointLegendEntry, {
42954
42960
  label,
42955
42961
  info,
@@ -43070,9 +43076,12 @@ function ContributorDistribution({ className = "", contributorDistribution, line
43070
43076
  headerHeight: 55,
43071
43077
  children: (shownItems) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
43072
43078
  className: cn$1("grid grid-cols-[max-content_1fr_max-content_minmax(calc(12*var(--spacing)),max-content)] items-center justify-between gap-x-2"),
43073
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContributorDistributionHeader, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContributorDistFragment, {
43079
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContributorDistributionHeader, {}), shownItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContributorDistFragment, {
43074
43080
  items: shownItems,
43075
43081
  contribSum
43082
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
43083
+ className: "text-tertiary-text dark:text-tertiary-text-dark col-span-full flex items-center justify-center text-sm",
43084
+ children: "No contributors match your current filters"
43076
43085
  })]
43077
43086
  }) })
43078
43087
  })
@@ -51525,6 +51534,11 @@ var Metrics = {
51525
51534
  TOP_CONTRIBUTOR: TopContributorMetric,
51526
51535
  CONTRIBUTORS: ContributorsMetric
51527
51536
  };
51537
+ var mapMetricToCategoryNoun = {
51538
+ CONTRIBUTORS: "contributors",
51539
+ TOP_CONTRIBUTOR: "contributors",
51540
+ FILE_TYPE: "file types"
51541
+ };
51528
51542
  var isContributorMetric = (metricType) => metricType === "CONTRIBUTORS" || metricType === "TOP_CONTRIBUTOR";
51529
51543
  function createMetricData(data, colorSeed, predefinedContributorColors, topContributorCutoff) {
51530
51544
  return createMetricDataForNode(data, data.databaseInfo.fileTree, colorSeed, predefinedContributorColors, topContributorCutoff);
@@ -51648,8 +51662,8 @@ var LayoutGroups = {
51648
51662
  };
51649
51663
  //#endregion
51650
51664
  //#region src/shared/constants.ts
51651
- var gitLogRegex = /"author\s+<\|(?<author>.*?)\|>\s+email\s+<\|(?<authorEmail>.*?)\|>\s+date\s+<\|(?<dateCommitter>\d+)\s(?<dateAuthor>\d+)\|>\s+message\s+<\|(?<message>[\s\S]*?)\|>\s+body\s+<\|(?<body>[\s\S]*?)\|>\s+hash\s+<\|(?<hash>.+?)\|>"\s*(?<contributions>(?:(?:\d+|-)\s+(?:\d+|-)\s+.+\s?)*)(?<modes>(?:\s.+\s*)*)/gmu;
51652
- var gitLogRegexSimple = /"<\|(?<author>.*?)\|><\|(?<authorEmail>.*?)\|><\|(?<dateCommitter>\d+)\s(?<dateAuthor>\d+)\|>\s*<\|(?<trailers>[\s\S]*?)\|><\|(?<hash>.+?)\|>"\s*(?<contributions>(?:(?:\d+|-)\s+(?:\d+|-)\s+.+\s?)*)(?<modes>(?:\s.+\s*)*)/gmu;
51665
+ var gitLogRegex = /"parents\s+<\|(?<parents>.*?)\|> author\s+<\|(?<author>.*?)\|>\s+email\s+<\|(?<authorEmail>.*?)\|>\s+date\s+<\|(?<dateCommitter>\d+)\s(?<dateAuthor>\d+)\|>\s+message\s+<\|(?<message>[\s\S]*?)\|>\s+body\s+<\|(?<body>[\s\S]*?)\|>\s+hash\s+<\|(?<hash>.+?)\|>"\s*(?<contributions>(?:(?:\d+|-)\s+(?:\d+|-)\s+.+\s?)*)(?<modes>(?:\s.+\s*)*)/gmu;
51666
+ var gitLogRegexSimple = /"<\|(?<parents>.*?)\|><\|(?<author>.*?)\|><\|(?<authorEmail>.*?)\|><\|(?<dateCommitter>\d+)\s(?<dateAuthor>\d+)\|>\s*<\|(?<trailers>[\s\S]*?)\|><\|(?<hash>.+?)\|>"\s*(?<contributions>(?:(?:\d+|-)\s+(?:\d+|-)\s+.+\s?)*)(?<modes>(?:\s.+\s*)*)/gmu;
51653
51667
  var contribRegex = /(?<insertions>\d+|-)\s+(?<deletions>\d+|-)\s+(?<file>.+)/gm;
51654
51668
  var treeRegex = /^\S+? (?<type>\S+) (?<hash>\S+)\s+(?<size>\S+)\s+(?<path>.+)/gm;
51655
51669
  var modeRegex = /\s(?<mode>\w+)\s\w+\s\d+\s(?<file>.+)/gmu;
@@ -52291,59 +52305,61 @@ var DB = class {
52291
52305
  async initTables() {
52292
52306
  await this.connection.run(`
52293
52307
  CREATE TABLE IF NOT EXISTS commits (
52294
- hash VARCHAR,
52295
- author VARCHAR,
52296
- authorEmail VARCHAR,
52297
- committerTime UINTEGER,
52298
- authorTime UINTEGER
52308
+ hash VARCHAR NOT NULL,
52309
+ author VARCHAR NOT NULL,
52310
+ authorEmail VARCHAR NOT NULL,
52311
+ committerTime UINTEGER NOT NULL,
52312
+ authorTime UINTEGER NOT NULL,
52313
+ parentHash VARCHAR NOT NULL,
52314
+ secondaryParentHash VARCHAR,
52299
52315
  );
52300
52316
  CREATE TABLE IF NOT EXISTS fileChanges (
52301
- commitHash VARCHAR,
52302
- insertions UINTEGER,
52303
- deletions UINTEGER,
52304
- filePath VARCHAR,
52317
+ commitHash VARCHAR NOT NULL,
52318
+ insertions UINTEGER NOT NULL,
52319
+ deletions UINTEGER NOT NULL,
52320
+ filePath VARCHAR NOT NULL,
52305
52321
  );
52306
52322
  CREATE TABLE IF NOT EXISTS commitTrailers (
52307
- commitHash VARCHAR,
52308
- name VARCHAR,
52309
- email VARCHAR,
52310
- trailerType VARCHAR
52323
+ commitHash VARCHAR NOT NULL,
52324
+ name VARCHAR NOT NULL,
52325
+ email VARCHAR NOT NULL,
52326
+ trailerType VARCHAR NOT NULL
52311
52327
  );
52312
52328
  CREATE TABLE IF NOT EXISTS contributorGroups (
52313
- displayName VARCHAR,
52314
- email VARCHAR,
52315
- name VARCHAR
52329
+ displayName VARCHAR NOT NULL,
52330
+ email VARCHAR NOT NULL,
52331
+ name VARCHAR NOT NULL
52316
52332
  );
52317
52333
  CREATE TABLE IF NOT EXISTS renames (
52318
52334
  fromName VARCHAR,
52319
52335
  toName VARCHAR,
52320
- timestamp UINTEGER,
52321
- timestampAuthor UINTEGER
52336
+ timestamp UINTEGER NOT NULL,
52337
+ timestampAuthor UINTEGER NOT NULL
52322
52338
  );
52323
52339
 
52324
52340
  CREATE SEQUENCE IF NOT EXISTS hiddenFiles_id_sequence START 1;
52325
52341
 
52326
52342
  CREATE TABLE IF NOT EXISTS hiddenFiles (
52327
- id INTEGER DEFAULT nextval('hiddenFiles_id_sequence'),
52328
- path VARCHAR
52343
+ id INTEGER NOT NULL DEFAULT nextval('hiddenFiles_id_sequence'),
52344
+ path VARCHAR NOT NULL
52329
52345
  );
52330
52346
  CREATE TABLE IF NOT EXISTS metadata (
52331
- field VARCHAR,
52347
+ field VARCHAR NOT NULL,
52332
52348
  intValue UBIGINT,
52333
52349
  stringValue VARCHAR
52334
52350
  );
52335
52351
  CREATE TABLE IF NOT EXISTS temporaryRenames (
52336
52352
  fromName VARCHAR,
52337
52353
  toName VARCHAR,
52338
- timestamp UINTEGER,
52339
- timestampEnd UINTEGER
52354
+ timestamp UINTEGER NOT NULL,
52355
+ timestampEnd UINTEGER NOT NULL
52340
52356
  );
52341
52357
  CREATE TABLE IF NOT EXISTS files (
52342
- path VARCHAR,
52343
- name VARCHAR,
52344
- hash VARCHAR,
52345
- type VARCHAR,
52346
- byteSize UINTEGER
52358
+ path VARCHAR NOT NULL,
52359
+ name VARCHAR NOT NULL,
52360
+ hash VARCHAR NOT NULL,
52361
+ type VARCHAR NOT NULL,
52362
+ byteSize UINTEGER NOT NULL
52347
52363
  );
52348
52364
 
52349
52365
  `);
@@ -52371,13 +52387,15 @@ var DB = class {
52371
52387
  async initViews(start, end) {
52372
52388
  await this.connection.run(`
52373
52389
  CREATE OR REPLACE VIEW commits_unioned AS
52374
- SELECT c.hash, CASE WHEN cg.displayName IS NOT NULL THEN cg.displayName ELSE c.author END AS author, c.authorEmail, c.committerTime, c.authorTime FROM
52390
+ SELECT c.hash, CASE WHEN cg.displayName IS NOT NULL THEN cg.displayName ELSE c.author END AS author, c.authorEmail,
52391
+ c.committerTime, c.authorTime, c.parentHash, c.secondaryParentHash FROM
52375
52392
  commits c LEFT JOIN (SELECT displayName, email, name FROM contributorGroups) cg
52376
52393
  ON (c.author = cg.name AND c.authorEmail = cg.email)
52377
52394
  WHERE c.committerTime BETWEEN ${start} AND ${end};
52378
52395
 
52379
52396
  CREATE OR REPLACE VIEW fileChanges_commits AS
52380
- SELECT f.commitHash, f.insertions, f.deletions, f.filePath, author, c.authorEmail, c.committerTime, c.authorTime FROM
52397
+ SELECT f.commitHash, f.insertions, f.deletions, f.filePath, author, c.authorEmail, c.committerTime, c.authorTime,
52398
+ c.parentHash, c.secondaryParentHash FROM
52381
52399
  fileChanges f JOIN commits_unioned c on f.commitHash = c.hash;
52382
52400
 
52383
52401
  CREATE OR REPLACE VIEW commitTrailers_unioned AS
@@ -52387,6 +52405,7 @@ var DB = class {
52387
52405
 
52388
52406
  CREATE OR REPLACE VIEW fileChanges_commits_renamed AS
52389
52407
  SELECT f.commitHash, f.insertions, f.deletions, f.author, f.authorEmail, f.committerTime, f.authorTime,
52408
+ f.parentHash, f.secondaryParentHash,
52390
52409
  CASE
52391
52410
  WHEN r.toName IS NOT NULL THEN r.toName
52392
52411
  ELSE f.filePath
@@ -52554,11 +52573,11 @@ var DB = class {
52554
52573
  }
52555
52574
  async getCommits(path, count) {
52556
52575
  const isblob = await this.getObjectTypeFromPath(path) === "blob";
52557
- return (await this.usingPreparedStatement(isblob ? `SELECT distinct commitHash, author, authorEmail, committerTime, authorTime, message, body
52576
+ return (await this.usingPreparedStatement(isblob ? `SELECT distinct parentHash, secondaryParentHash, commitHash, author, authorEmail, committerTime, authorTime, message, body
52558
52577
  FROM fileChanges_commits_renamed_cached
52559
52578
  WHERE filePath = ?
52560
52579
  ORDER BY committerTime DESC, commitHash
52561
- LIMIT ?;` : `SELECT distinct commitHash, author, authorEmail, committerTime, authorTime, message, body
52580
+ LIMIT ?;` : `SELECT distinct parentHash, secondaryParentHash, commitHash, author, authorEmail, committerTime, authorTime, message, body
52562
52581
  FROM fileChanges_commits_renamed_cached
52563
52582
  WHERE starts_with(filePath, ?) = true
52564
52583
  ORDER BY committerTime DESC, commitHash
@@ -52575,7 +52594,9 @@ var DB = class {
52575
52594
  authorTime: row["authorTime"],
52576
52595
  body: row["body"],
52577
52596
  hash: row["commitHash"],
52578
- message: row["message"]
52597
+ message: row["message"],
52598
+ parentHash: String(row["parentHash"] ?? ""),
52599
+ secondaryParentHash: typeof row["secondaryParentHash"] === "string" ? row["secondaryParentHash"] : null
52579
52600
  }));
52580
52601
  }
52581
52602
  async getCommitHashes(path, count, authors = []) {
@@ -52647,6 +52668,28 @@ var DB = class {
52647
52668
  }, "getCommitHashes(root)");
52648
52669
  }
52649
52670
  }
52671
+ async getCommitHashesForRepo(count, authors = []) {
52672
+ const authorPlaceholders = authors.map(() => "?").join(",");
52673
+ const baseQuery = `SELECT DISTINCT hash
52674
+ FROM commits_unioned`;
52675
+ const query = authors.length > 0 ? baseQuery + ` WHERE (
52676
+ author IN (${authorPlaceholders})
52677
+ OR hash IN (
52678
+ SELECT commitHash FROM commitTrailers_unioned
52679
+ WHERE trailerType = 'coauthor' AND name IN (${authorPlaceholders})
52680
+ )
52681
+ )
52682
+ ORDER BY committerTime DESC, hash
52683
+ LIMIT ?;` : baseQuery + ` ORDER BY committerTime DESC, hash
52684
+ LIMIT ?;`;
52685
+ return this.usingPreparedStatement(query, async (statement) => {
52686
+ let paramIndex = 1;
52687
+ for (const author of authors) statement.bindVarchar(paramIndex++, author);
52688
+ for (const author of authors) statement.bindVarchar(paramIndex++, author);
52689
+ statement.bindUInteger(paramIndex++, count);
52690
+ return (await statement.runAndReadAll()).getRowObjects().map((row) => row["hash"]);
52691
+ }, "getCommitHashesForRepo");
52692
+ }
52650
52693
  async getCommitCountForPath({ objectPath, startSecs, endSecs, contributors = [] }) {
52651
52694
  const isblob = await this.getObjectTypeFromPath(objectPath) === "blob";
52652
52695
  const authorPlaceholders = contributors.map(() => "?").join(",");
@@ -52706,6 +52749,24 @@ var DB = class {
52706
52749
  }, "getCommitCountForPath(root)");
52707
52750
  }
52708
52751
  }
52752
+ async getCommitCountForRepo(contributors = []) {
52753
+ const authorPlaceholders = contributors.map(() => "?").join(",");
52754
+ const baseQuery = `SELECT COUNT(DISTINCT hash) AS count FROM commits_unioned`;
52755
+ const query = contributors.length > 0 ? baseQuery + ` WHERE (
52756
+ author IN (${authorPlaceholders})
52757
+ OR hash IN (
52758
+ SELECT commitHash FROM commitTrailers_unioned
52759
+ WHERE trailerType = 'coauthor' AND name IN (${authorPlaceholders})
52760
+ )
52761
+ );` : baseQuery + ";";
52762
+ return this.usingPreparedStatement(query, async (statement) => {
52763
+ let paramIndex = 1;
52764
+ for (const contributor of contributors) statement.bindVarchar(paramIndex++, contributor);
52765
+ for (const contributor of contributors) statement.bindVarchar(paramIndex++, contributor);
52766
+ const res = (await statement.runAndReadAll()).getRowObjects();
52767
+ return Number(res[0]["count"]);
52768
+ }, "getCommitCountForRepo");
52769
+ }
52709
52770
  async getCommitCountPerFile() {
52710
52771
  const res = await this.query(`SELECT filePath, count(DISTINCT commitHash) AS count
52711
52772
  FROM fileChanges_commits_renamed_cached
@@ -52830,6 +52891,20 @@ var DB = class {
52830
52891
  `);
52831
52892
  return res.map((row) => row["contributor"]);
52832
52893
  }
52894
+ async getUniqueContributorsForRepo() {
52895
+ return (await this.query(`
52896
+ SELECT DISTINCT contributor
52897
+ FROM (
52898
+ SELECT author AS contributor
52899
+ FROM commits_unioned
52900
+ UNION
52901
+ SELECT name AS contributor
52902
+ FROM commitTrailers_unioned
52903
+ WHERE trailerType = 'coauthor'
52904
+ AND commitHash IN (SELECT hash FROM commits_unioned)
52905
+ );
52906
+ `)).map((row) => row["contributor"]);
52907
+ }
52833
52908
  async getContributionsForPath({ objectPath, startSecs, endSecs }) {
52834
52909
  return await this.usingPreparedStatement(`SELECT SUM(insertions + deletions) AS totalContributions
52835
52910
  FROM fileChanges_commits_renamed_cached
@@ -53013,7 +53088,7 @@ var DB = class {
53013
53088
  };
53014
53089
  }
53015
53090
  async getCommitCount() {
53016
- const res = await this.query(`SELECT count(distinct commitHash) AS count FROM fileChanges_commits_renamed_cached;
53091
+ const res = await this.query(`SELECT count(distinct hash) AS count FROM commits_unioned;
53017
53092
  `);
53018
53093
  return Number(res[0]["count"]);
53019
53094
  }
@@ -53154,6 +53229,19 @@ var DB = class {
53154
53229
  const existsValue = res[0]?.["exists_in_range"];
53155
53230
  return existsValue === true || existsValue === 1 || existsValue === 1n || existsValue === "1";
53156
53231
  }
53232
+ async repoHasCommitsInSelectedRange() {
53233
+ const existsValue = (await this.query(`
53234
+ SELECT EXISTS(SELECT 1 FROM commits_unioned) AS exists_in_range;
53235
+ `))[0]?.["exists_in_range"];
53236
+ return existsValue === true || existsValue === 1 || existsValue === 1n || existsValue === "1";
53237
+ }
53238
+ async getLastCommitTimeForRepo() {
53239
+ const res = await this.query(`
53240
+ SELECT MAX(committerTime) AS lastChange
53241
+ FROM commits_unioned;
53242
+ `);
53243
+ return Number(res[0]?.["lastChange"] ?? 0);
53244
+ }
53157
53245
  getTimeStringFormat(timerange) {
53158
53246
  const durationDays = (timerange[1] - timerange[0]) / (3600 * 24);
53159
53247
  if (durationDays < 150) return "day";
@@ -53187,12 +53275,39 @@ var DB = class {
53187
53275
  final.push({
53188
53276
  date: interval.label,
53189
53277
  count: bucket?.count ?? 0,
53278
+ authoredCommitCount: bucket?.count ?? 0,
53190
53279
  timestamp: interval.timestamp,
53191
- contributors: bucket?.contributors ?? {}
53280
+ contributors: bucket?.contributors ?? {},
53281
+ contributorRoleCounts: Object.fromEntries(Object.entries(bucket?.contributors ?? {}).map(([author, count]) => [author, {
53282
+ authoredCommitCount: count,
53283
+ coauthoredCommitCount: 0
53284
+ }]))
53192
53285
  });
53193
53286
  }
53194
53287
  return final.sort((a, b) => a.timestamp - b.timestamp);
53195
53288
  }
53289
+ mapCommitRoleCountsToIntervals({ intervals, rows, timerange, timeUnit }) {
53290
+ const roleCountsByLookup = new Map(rows.filter((row) => !row.author).map((row) => [row.lookup, {
53291
+ authoredCommitCount: row.authoredCommitCount,
53292
+ coauthoredCommitCount: row.coauthoredCommitCount
53293
+ }]));
53294
+ const lookupByTimestamp = new Map(getTimeIntervals(timeUnit, timerange[0], timerange[1]).map((interval) => [interval.timestamp, interval.lookup]));
53295
+ return intervals.map((interval) => {
53296
+ const lookup = lookupByTimestamp.get(interval.timestamp);
53297
+ const intervalRoleCounts = lookup ? rows.filter((row) => row.lookup === lookup) : [];
53298
+ const roleCounts = lookup ? roleCountsByLookup.get(lookup) : void 0;
53299
+ const contributorRoleCounts = Object.fromEntries(intervalRoleCounts.filter((row) => row.author).map((row) => [row.author, {
53300
+ authoredCommitCount: row.authoredCommitCount,
53301
+ coauthoredCommitCount: row.coauthoredCommitCount
53302
+ }]));
53303
+ return {
53304
+ ...interval,
53305
+ authoredCommitCount: roleCounts?.authoredCommitCount ?? interval.authoredCommitCount,
53306
+ coauthoredCommitCount: roleCounts?.coauthoredCommitCount ?? 0,
53307
+ contributorRoleCounts: Object.keys(contributorRoleCounts).length > 0 ? contributorRoleCounts : interval.contributorRoleCounts
53308
+ };
53309
+ });
53310
+ }
53196
53311
  async getCommitCountPerTime(timerange, timeUnit = this.getTimeStringFormat(timerange), options = {}) {
53197
53312
  const query = this.getTimeQueryFromTimeUnit(timeUnit ?? this.getTimeStringFormat(timerange));
53198
53313
  const mapped = (options.includeCoauthors ? await this.query(`WITH authored_commits AS (
@@ -53244,7 +53359,69 @@ var DB = class {
53244
53359
  count: Number(x["count"])
53245
53360
  };
53246
53361
  });
53247
- return [this.mapCommitCountRowsToIntervals(mapped, timerange, timeUnit), timeUnit];
53362
+ const intervals = this.mapCommitCountRowsToIntervals(mapped, timerange, timeUnit);
53363
+ if (!options.includeCoauthors) return [intervals, timeUnit];
53364
+ const roleCounts = (await this.query(`
53365
+ SELECT strftime(date, '%Y-%m-%d') AS bucket_lookup,
53366
+ COUNT(DISTINCT hash) AS authoredCommitCount,
53367
+ COUNT(DISTINCT coauthoredCommitHash) AS coauthoredCommitCount
53368
+ FROM (
53369
+ SELECT date_trunc('${timeUnit}', to_timestamp(c.committerTime)) AS date,
53370
+ c.hash,
53371
+ co.commitHash AS coauthoredCommitHash
53372
+ FROM commits c
53373
+ LEFT JOIN commitTrailers_unioned co ON c.hash = co.commitHash AND co.trailerType = 'coauthor'
53374
+ WHERE c.committerTime BETWEEN ${timerange[0]} AND ${timerange[1]}
53375
+ )
53376
+ GROUP BY date
53377
+ ORDER BY date ASC;`)).map((row) => ({
53378
+ lookup: row["bucket_lookup"],
53379
+ authoredCommitCount: Number(row["authoredCommitCount"]),
53380
+ coauthoredCommitCount: Number(row["coauthoredCommitCount"])
53381
+ }));
53382
+ const contributorRoleCounts = (await this.query(`
53383
+ WITH authored_commits AS (
53384
+ SELECT c.hash, c.committerTime, COALESCE(cg.displayName, c.author) AS author
53385
+ FROM commits c
53386
+ LEFT JOIN (SELECT displayName, email, name FROM contributorGroups) cg
53387
+ ON (c.author = cg.name AND c.authorEmail = cg.email)
53388
+ WHERE c.committerTime BETWEEN ${timerange[0]} AND ${timerange[1]}
53389
+ ),
53390
+ contributor_roles AS (
53391
+ SELECT date_trunc('${timeUnit}', to_timestamp(committerTime)) AS date,
53392
+ author,
53393
+ COUNT(DISTINCT hash) AS authoredCommitCount,
53394
+ 0 AS coauthoredCommitCount
53395
+ FROM authored_commits
53396
+ GROUP BY date, author
53397
+ UNION ALL
53398
+ SELECT date_trunc('${timeUnit}', to_timestamp(c.committerTime)) AS date,
53399
+ co.name AS author,
53400
+ 0 AS authoredCommitCount,
53401
+ COUNT(DISTINCT c.hash) AS coauthoredCommitCount
53402
+ FROM authored_commits c
53403
+ JOIN commitTrailers_unioned co ON c.hash = co.commitHash
53404
+ WHERE co.trailerType = 'coauthor' AND co.name <> c.author
53405
+ GROUP BY date, co.name
53406
+ )
53407
+ SELECT strftime(date, '%Y-%m-%d') AS bucket_lookup,
53408
+ author,
53409
+ SUM(authoredCommitCount) AS authoredCommitCount,
53410
+ SUM(coauthoredCommitCount) AS coauthoredCommitCount
53411
+ FROM contributor_roles
53412
+ GROUP BY date, author
53413
+ ORDER BY date ASC;`)).map((row) => ({
53414
+ lookup: row["bucket_lookup"],
53415
+ author: row["author"],
53416
+ authoredCommitCount: Number(row["authoredCommitCount"]),
53417
+ coauthoredCommitCount: Number(row["coauthoredCommitCount"])
53418
+ }));
53419
+ return [this.mapCommitRoleCountsToIntervals({
53420
+ intervals,
53421
+ rows: [...roleCounts, ...contributorRoleCounts],
53422
+ timerange,
53423
+ timeUnit
53424
+ }), timeUnit];
53248
53425
  }
53249
53426
  async getCommitCountPerTimeForClickedObject({ timerange, timeUnit = this.getTimeStringFormat(timerange), objectPath, includeCoauthors = false }) {
53250
53427
  const query = this.getTimeQueryFromTimeUnit(timeUnit);
@@ -53327,7 +53504,105 @@ var DB = class {
53327
53504
  count: Number(x["count"])
53328
53505
  };
53329
53506
  });
53330
- return this.mapCommitCountRowsToIntervals(mapped, timerange, timeUnit);
53507
+ const intervals = this.mapCommitCountRowsToIntervals(mapped, timerange, timeUnit);
53508
+ if (!includeCoauthors) return intervals;
53509
+ const roleCounts = (await this.usingPreparedStatement(`WITH object_commits AS (
53510
+ SELECT DISTINCT commitHash, committerTime
53511
+ FROM (
53512
+ SELECT c.hash AS commitHash,
53513
+ c.committerTime,
53514
+ COALESCE(r.toName, f.filePath) AS resolvedPath
53515
+ FROM commits c
53516
+ JOIN fileChanges f ON c.hash = f.commitHash
53517
+ LEFT JOIN temporaryRenames r ON f.filePath = r.fromName AND (
53518
+ c.committerTime BETWEEN r.timestamp AND r.timestampEnd
53519
+ )
53520
+ )
53521
+ WHERE resolvedPath = ? OR resolvedPath LIKE ?
53522
+ )
53523
+ SELECT strftime(date, '%Y-%m-%d') AS bucket_lookup,
53524
+ COUNT(DISTINCT commitHash) AS authoredCommitCount,
53525
+ COUNT(DISTINCT coauthoredCommitHash) AS coauthoredCommitCount
53526
+ FROM (
53527
+ SELECT date_trunc(?, to_timestamp(oc.committerTime)) AS date,
53528
+ oc.commitHash,
53529
+ co.commitHash AS coauthoredCommitHash
53530
+ FROM object_commits oc
53531
+ LEFT JOIN commitTrailers_unioned co ON oc.commitHash = co.commitHash AND co.trailerType = 'coauthor'
53532
+ )
53533
+ GROUP BY date
53534
+ ORDER BY date ASC;`, async (statement) => {
53535
+ statement.bindVarchar(1, objectPath);
53536
+ statement.bindVarchar(2, `${objectPath}/%`);
53537
+ statement.bindVarchar(3, timeUnit);
53538
+ return (await statement.runAndReadAll()).getRowObjects();
53539
+ }, "getCommitCountPerTimeForClickedObjectRoleCounts")).map((row) => {
53540
+ return {
53541
+ lookup: row["bucket_lookup"],
53542
+ authoredCommitCount: Number(row["authoredCommitCount"]),
53543
+ coauthoredCommitCount: Number(row["coauthoredCommitCount"])
53544
+ };
53545
+ });
53546
+ const contributorRoleCounts = (await this.usingPreparedStatement(`WITH object_commits AS (
53547
+ SELECT DISTINCT commitHash, committerTime, author
53548
+ FROM (
53549
+ SELECT c.hash AS commitHash,
53550
+ c.committerTime,
53551
+ COALESCE(cg.displayName, c.author) AS author,
53552
+ COALESCE(r.toName, f.filePath) AS resolvedPath
53553
+ FROM commits c
53554
+ JOIN fileChanges f ON c.hash = f.commitHash
53555
+ LEFT JOIN (SELECT displayName, email, name FROM contributorGroups) cg
53556
+ ON (c.author = cg.name AND c.authorEmail = cg.email)
53557
+ LEFT JOIN temporaryRenames r ON f.filePath = r.fromName AND (
53558
+ c.committerTime BETWEEN r.timestamp AND r.timestampEnd
53559
+ )
53560
+ )
53561
+ WHERE resolvedPath = ? OR resolvedPath LIKE ?
53562
+ ),
53563
+ contributor_roles AS (
53564
+ SELECT date_trunc(?, to_timestamp(committerTime)) AS date,
53565
+ author,
53566
+ COUNT(DISTINCT commitHash) AS authoredCommitCount,
53567
+ 0 AS coauthoredCommitCount
53568
+ FROM object_commits
53569
+ GROUP BY date, author
53570
+ UNION ALL
53571
+ SELECT date_trunc(?, to_timestamp(oc.committerTime)) AS date,
53572
+ co.name AS author,
53573
+ 0 AS authoredCommitCount,
53574
+ COUNT(DISTINCT oc.commitHash) AS coauthoredCommitCount
53575
+ FROM object_commits oc
53576
+ JOIN commitTrailers_unioned co ON oc.commitHash = co.commitHash
53577
+ WHERE co.trailerType = 'coauthor' AND co.name <> oc.author
53578
+ GROUP BY date, co.name
53579
+ )
53580
+ SELECT strftime(date, '%Y-%m-%d') AS bucket_lookup,
53581
+ author,
53582
+ SUM(authoredCommitCount) AS authoredCommitCount,
53583
+ SUM(coauthoredCommitCount) AS coauthoredCommitCount
53584
+ FROM contributor_roles
53585
+ GROUP BY date, author
53586
+ ORDER BY date ASC;`, async (statement) => {
53587
+ statement.bindVarchar(1, objectPath);
53588
+ statement.bindVarchar(2, `${objectPath}/%`);
53589
+ statement.bindVarchar(3, timeUnit);
53590
+ statement.bindVarchar(4, timeUnit);
53591
+ return (await statement.runAndReadAll()).getRowObjects();
53592
+ }, "getCommitCountPerTimeForClickedObjectContributorRoleCounts")).map((row) => {
53593
+ return {
53594
+ lookup: row["bucket_lookup"],
53595
+ author: row["author"],
53596
+ authoredCommitCount: Number(row["authoredCommitCount"]),
53597
+ coauthoredCommitCount: Number(row["coauthoredCommitCount"])
53598
+ };
53599
+ });
53600
+ return this.mapCommitRoleCountsToIntervals({
53601
+ intervals,
53602
+ rows: [...roleCounts, ...contributorRoleCounts],
53603
+ timerange,
53604
+ timeUnit
53605
+ });
53331
53606
  }
53332
53607
  async updateColorSeed(seed) {
53333
53608
  await this.run(`DELETE FROM metadata
@@ -53361,37 +53636,33 @@ var DB = class {
53361
53636
  async addCommits(commits) {
53362
53637
  await this.usingTableAppender("commits", async (appender) => {
53363
53638
  for (const [hash, commit] of commits) {
53364
- if (!commit) throw new Error(`Commit with hash ${hash} is undefined`);
53365
53639
  appender.appendVarchar(hash);
53366
53640
  appender.appendVarchar(commit.author.name);
53367
53641
  appender.appendVarchar(commit.author.email);
53368
53642
  appender.appendUInteger(commit.committerTime);
53369
53643
  appender.appendUInteger(commit.authorTime);
53644
+ appender.appendVarchar(commit.parentHash);
53645
+ if (commit.secondaryParentHash !== null) appender.appendVarchar(commit.secondaryParentHash);
53646
+ else appender.appendNull();
53370
53647
  appender.endRow();
53371
53648
  }
53372
53649
  });
53373
53650
  await this.usingTableAppender("commitTrailers", async (appender) => {
53374
- for (const [hash, commit] of commits) {
53375
- if (!commit) throw new Error(`Commit with hash ${hash} is undefined`);
53376
- for (const coauthor of commit.coauthors) {
53377
- appender.appendVarchar(hash);
53378
- appender.appendVarchar(coauthor.name);
53379
- appender.appendVarchar(coauthor.email);
53380
- appender.appendVarchar("coauthor");
53381
- appender.endRow();
53382
- }
53651
+ for (const [hash, commit] of commits) for (const coauthor of commit.coauthors) {
53652
+ appender.appendVarchar(hash);
53653
+ appender.appendVarchar(coauthor.name);
53654
+ appender.appendVarchar(coauthor.email);
53655
+ appender.appendVarchar("coauthor");
53656
+ appender.endRow();
53383
53657
  }
53384
53658
  });
53385
53659
  await this.usingTableAppender("fileChanges", async (appender) => {
53386
- for (const [hash, commit] of commits) {
53387
- if (!commit) throw new Error(`Commit with hash ${hash} is undefined`);
53388
- for (const change of commit.fileChanges) {
53389
- appender.appendVarchar(commit.hash);
53390
- appender.appendUInteger(change.insertions);
53391
- appender.appendUInteger(change.deletions);
53392
- appender.appendVarchar(change.path);
53393
- appender.endRow();
53394
- }
53660
+ for (const [hash, commit] of commits) for (const change of commit.fileChanges) {
53661
+ appender.appendVarchar(hash);
53662
+ appender.appendUInteger(change.insertions);
53663
+ appender.appendUInteger(change.deletions);
53664
+ appender.appendVarchar(change.path);
53665
+ appender.endRow();
53395
53666
  }
53396
53667
  });
53397
53668
  }
@@ -53649,7 +53920,7 @@ var GitService = class GitService {
53649
53920
  "log",
53650
53921
  "--no-walk",
53651
53922
  "--numstat",
53652
- "--format=\"author <|%aN|> email <|%aE|> date <|%ct %at|> message <|%s|> body <|%b|> hash <|%H|>\"",
53923
+ "--format=\"parents <|%P|> author <|%aN|> email <|%aE|> date <|%ct %at|> message <|%s|> body <|%b|> hash <|%H|>\"",
53653
53924
  ...commits
53654
53925
  ];
53655
53926
  return (await runProcess(this.repositoryPath, "git", args)).trim();
@@ -53794,7 +54065,7 @@ var GitService = class GitService {
53794
54065
  this.branch,
53795
54066
  "--summary",
53796
54067
  "--numstat",
53797
- "--format=\"author <|%aN|> email <|%aE|> date <|%ct %at|> message <|%s|> body <|%b|> hash <|%H|>\""
54068
+ "--format=\"parents <|%P|> author <|%aN|> email <|%aE|> date <|%ct %at|> message <|%s|> body <|%b|> hash <|%H|>\""
53798
54069
  ];
53799
54070
  return (await runProcess(this.repositoryPath, "git", args)).trim();
53800
54071
  }
@@ -53806,7 +54077,7 @@ var GitService = class GitService {
53806
54077
  this.branch,
53807
54078
  "--summary",
53808
54079
  "--numstat",
53809
- "--format=\"<|%aN|><|%aE|><|%ct %at|><|%(trailers)|><|%H|>\""
54080
+ "--format=\"<|%P|><|%aN|><|%aE|><|%ct %at|><|%(trailers)|><|%H|>\""
53810
54081
  ];
53811
54082
  return (await runProcess(this.repositoryPath, "git", args, instance, index)).trim();
53812
54083
  }
@@ -54349,6 +54620,7 @@ var Analysis = class {
54349
54620
  for (const match of matches) {
54350
54621
  this.throwIfAborted();
54351
54622
  const groups = match.groups ?? {};
54623
+ const parents = groups.parents.split(" ").filter((p) => p !== "");
54352
54624
  const contributorName = groups.author;
54353
54625
  const authorEmail = groups.authorEmail;
54354
54626
  const committerTime = Number(groups.dateCommitter);
@@ -54401,7 +54673,9 @@ var Analysis = class {
54401
54673
  authorTime,
54402
54674
  hash,
54403
54675
  coauthors,
54404
- fileChanges
54676
+ fileChanges,
54677
+ parentHash: parents[0] ?? "",
54678
+ secondaryParentHash: parents[1] ?? null
54405
54679
  });
54406
54680
  }
54407
54681
  renamedFiles.push(...FileModifications.map((modification) => {
@@ -54425,6 +54699,7 @@ var Analysis = class {
54425
54699
  for (const match of matches) {
54426
54700
  this.throwIfAborted();
54427
54701
  const groups = match.groups ?? {};
54702
+ const parents = groups.parents.split(" ").filter((p) => p !== "");
54428
54703
  const name = groups.author;
54429
54704
  const authorEmail = groups.authorEmail;
54430
54705
  const message = groups.message;
@@ -54463,7 +54738,9 @@ var Analysis = class {
54463
54738
  fileChanges,
54464
54739
  message,
54465
54740
  body,
54466
- coauthors
54741
+ coauthors,
54742
+ parentHash: parents[0] ?? "",
54743
+ secondaryParentHash: parents[1] ?? null
54467
54744
  });
54468
54745
  }
54469
54746
  return commits;
@@ -55249,7 +55526,7 @@ async function loader$9({ request }) {
55249
55526
  _usingCtx$4.u(await instance.withTimeInterval(start, end));
55250
55527
  return {
55251
55528
  clickedObjectPath: selectedObjectPath,
55252
- commitCountPerTimeIntervalForClickedObject: await instance.db.getCommitCountPerTimeForClickedObject({
55529
+ commitCountPerTimeIntervalForClickedObject: selectedObjectPath === instance.repositoryName ? (await instance.db.getCommitCountPerTime([start, end], timeUnit, { includeCoauthors }))[0] : await instance.db.getCommitCountPerTimeForClickedObject({
55253
55530
  timerange: [start, end],
55254
55531
  timeUnit,
55255
55532
  objectPath: selectedObjectPath,
@@ -55365,8 +55642,9 @@ var loader$6 = async ({ request }) => {
55365
55642
  branch
55366
55643
  });
55367
55644
  _usingCtx$3.u(await instance.withTimeInterval(start, end));
55645
+ const isRepoRoot = objectPath === instance.repositoryName;
55368
55646
  const getCommits = async () => {
55369
- const commitHashes = await instance.db.getCommitHashes(objectPath, count, contributors);
55647
+ const commitHashes = isRepoRoot ? await instance.db.getCommitHashesForRepo(count, contributors) : await instance.db.getCommitHashes(objectPath, count, contributors);
55370
55648
  if (commitHashes.length < 1) return [];
55371
55649
  const gitLogResult = await instance.gitService.gitLogSpecificCommits(commitHashes);
55372
55650
  const fullCommits = await instance.getFullCommits(gitLogResult);
@@ -55397,7 +55675,7 @@ var loader$6 = async ({ request }) => {
55397
55675
  return {
55398
55676
  objectPath,
55399
55677
  currentCommitCount: count,
55400
- totalCommitCount: await instance.db.getCommitCountForPath({
55678
+ totalCommitCount: isRepoRoot ? await instance.db.getCommitCountForRepo(contributors) : await instance.db.getCommitCountForPath({
55401
55679
  objectPath,
55402
55680
  startSecs: start,
55403
55681
  endSecs: end,
@@ -55430,7 +55708,14 @@ async function loader$5({ request }) {
55430
55708
  });
55431
55709
  const selectedObjectPath = objectPath ?? instance.repositoryName;
55432
55710
  _usingCtx$2.u(await instance.withTimeInterval(start, end));
55711
+ const isRepoRoot = selectedObjectPath === instance.repositoryName;
55433
55712
  const objIsBlob = isBlob(await instance.db.getObjectFromPath(selectedObjectPath));
55713
+ const amountOfCommits = isRepoRoot ? await instance.db.getCommitCountForRepo() : await instance.db.getCommitCountForPath({
55714
+ objectPath: selectedObjectPath,
55715
+ startSecs: start,
55716
+ endSecs: end,
55717
+ contributors: []
55718
+ });
55434
55719
  const topContributorData = await instance.db.getContributorDistributionForPath({
55435
55720
  objectPath: selectedObjectPath,
55436
55721
  startSecs: start,
@@ -55438,22 +55723,17 @@ async function loader$5({ request }) {
55438
55723
  });
55439
55724
  return {
55440
55725
  path: selectedObjectPath,
55441
- existsInRange: await instance.db.pathExistsInSelectedRange(selectedObjectPath, objIsBlob),
55726
+ existsInRange: isRepoRoot ? await instance.db.repoHasCommitsInSelectedRange() : await instance.db.pathExistsInSelectedRange(selectedObjectPath, objIsBlob),
55442
55727
  topContributor: topContributorData,
55443
55728
  multiTopContributors: topContributorData.length > 1 && topContributorData[0].contribs === topContributorData[1].contribs,
55444
- amountOfCommits: await instance.db.getCommitCountForPath({
55445
- objectPath: selectedObjectPath,
55446
- startSecs: start,
55447
- endSecs: end,
55448
- contributors: []
55449
- }),
55450
- contributors: await instance.db.getUniqueContributorsForPath(selectedObjectPath),
55729
+ amountOfCommits,
55730
+ contributors: isRepoRoot ? await instance.db.getUniqueContributorsForRepo() : await instance.db.getUniqueContributorsForPath(selectedObjectPath),
55451
55731
  contributions: await instance.db.getContributionsForPath({
55452
55732
  objectPath: selectedObjectPath,
55453
55733
  startSecs: start,
55454
55734
  endSecs: end
55455
55735
  }),
55456
- lastChanged: await instance.db.getLastChangedForPath({
55736
+ lastChanged: isRepoRoot ? await instance.db.getLastCommitTimeForRepo() : await instance.db.getLastChangedForPath({
55457
55737
  objectPath: selectedObjectPath,
55458
55738
  startSecs: start,
55459
55739
  endSecs: end
@@ -55566,18 +55846,19 @@ function GitTruckInfo({ installedVersion, latestVersion }) {
55566
55846
  }
55567
55847
  //#endregion
55568
55848
  //#region src/components/GlobalInfo.tsx
55569
- function AnalysisInfo({ className = "", trigger }) {
55849
+ function AnalysisInfo({ className = "", trigger, triggerClassName = "" }) {
55570
55850
  const client = useIsClient();
55571
55851
  const { databaseInfo } = useData();
55572
55852
  const isoString = new Date(databaseInfo.lastRunInfo.time).toISOString();
55573
55853
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
55574
55854
  className: cn$1("flex flex-col gap-2", className),
55575
55855
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
55576
- className: "relative flex w-full items-center justify-between gap-2",
55856
+ className: "relative flex w-full min-w-0 items-center justify-between gap-2",
55577
55857
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Popover, {
55578
55858
  triggerOnHover: true,
55579
55859
  popoverTitle: "Analysis details",
55580
55860
  trigger: () => trigger,
55861
+ triggerClassName,
55581
55862
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
55582
55863
  className: "text-secondary-text dark:text-secondary-text-dark grid auto-rows-fr grid-cols-2 items-center gap-0 text-sm",
55583
55864
  children: [
@@ -55638,7 +55919,7 @@ function ClickedObjectButton() {
55638
55919
  if (!clickedObject || !data) return null;
55639
55920
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
55640
55921
  type: "button",
55641
- className: "btn btn--primary",
55922
+ className: "btn btn--primary max-w-max min-w-0 gap-1",
55642
55923
  title: isRepoRoot ? clickedObject.name : isZoomPath ? `Deselect and zoom out of ${clickedObject.name}` : `Deselect ${clickedObject.name}`,
55643
55924
  style: {
55644
55925
  backgroundColor: objectColor ?? void 0,
@@ -55654,7 +55935,10 @@ function ClickedObjectButton() {
55654
55935
  },
55655
55936
  children: [
55656
55937
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { path: isRepoRoot ? mdiSourceRepository : clickedObject.type === "tree" ? mdiFolder : mdiFile }),
55657
- clickedObject.name,
55938
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
55939
+ className: "truncate text-xs",
55940
+ children: clickedObject.name
55941
+ }),
55658
55942
  !isRepoRoot ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { path: isZoomPath ? mdiMagnifyMinusOutline : mdiClose }) : null
55659
55943
  ]
55660
55944
  });
@@ -55729,14 +56013,17 @@ function Breadcrumb({ className = "", zoom = false }) {
55729
56013
  zoomPath
55730
56014
  ]);
55731
56015
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
55732
- className: cn$1("text-secondary-text dark:text-secondary-text-dark flex h-8 items-center justify-stretch gap-1 overflow-x-auto", className),
56016
+ className: cn$1("text-secondary-text dark:text-secondary-text-dark flex h-8 min-w-0 items-center justify-stretch gap-1 overflow-x-auto", className),
55733
56017
  children: [breadcrumbSegments.map(({ type, segment, fullPath, parentPath, showAnalysisInfo: isRepo }, i) => {
55734
56018
  const title = isRepo ? "Reset zoom to repository root" : type === "browse" ? `Browse ${segment} directory` : clickedObjectIsZoomPath ? "Zoom to parent" : `Zoom to ${segment} directory`;
55735
56019
  const isFirst = i === 0;
55736
- const content = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [isRepo ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { path: mdiSourceRepository }) : zoom && type === "browse" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { path: mdiSourceRepositoryMultiple }) : null, segment] });
56020
+ const content = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [isRepo ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { path: mdiSourceRepository }) : zoom && type === "browse" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { path: mdiSourceRepositoryMultiple }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
56021
+ className: "min-w-0 truncate",
56022
+ children: segment
56023
+ })] });
55737
56024
  const button = type === "filler" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
55738
56025
  title: fullPath,
55739
- className: "text-tertiary-text dark:text-tertiary-text-dark pointer-events-none flex w-max items-center gap-2 truncate text-sm font-bold opacity-80",
56026
+ className: "text-tertiary-text dark:text-tertiary-text-dark pointer-events-none flex w-max items-center gap-2 overflow-hidden text-sm font-bold opacity-80",
55740
56027
  children: content
55741
56028
  }) : type === "browse" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
55742
56029
  to: href("/browse") + browseSerializer({
@@ -55746,12 +56033,12 @@ function Breadcrumb({ className = "", zoom = false }) {
55746
56033
  path: fullPath
55747
56034
  }),
55748
56035
  title,
55749
- className: "text-secondary-text dark:text-secondary-text-dark flex cursor-pointer items-center gap-1 truncate text-sm font-bold",
56036
+ className: "text-secondary-text dark:text-secondary-text-dark flex min-w-0 cursor-pointer items-center gap-1 overflow-hidden text-sm font-bold",
55750
56037
  onClick: () => setClickedObjectPath(null),
55751
56038
  children: content
55752
56039
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
55753
56040
  title,
55754
- className: "text-secondary-text dark:text-secondary-text-dark flex cursor-pointer items-center gap-1 truncate text-sm font-bold",
56041
+ className: "text-secondary-text dark:text-secondary-text-dark flex min-w-0 cursor-pointer items-center gap-1 overflow-hidden text-sm font-bold",
55755
56042
  onClick: () => {
55756
56043
  if (!data) throw Error("Attempting to access data when none is loaded");
55757
56044
  if (zoomPath === fullPath) if (clickedObjectIsZoomPath) {
@@ -55774,7 +56061,11 @@ function Breadcrumb({ className = "", zoom = false }) {
55774
56061
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [!isFirst ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, {
55775
56062
  path: mdiChevronRight,
55776
56063
  size: "1.25rem"
55777
- }) : null, isRepo ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnalysisInfo, { trigger: button }) : button] }, fullPath);
56064
+ }) : null, isRepo ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnalysisInfo, {
56065
+ className: "min-w-0",
56066
+ trigger: button,
56067
+ triggerClassName: "min-w-0 overflow-hidden"
56068
+ }) : button] }, fullPath);
55778
56069
  }), clickedObject && clickedObject.path !== zoomPath && clickedObject.path !== data?.repo.repositoryName ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, {
55779
56070
  path: mdiChevronDoubleRight,
55780
56071
  className: "mx-1",
@@ -56625,7 +56916,6 @@ function Chart() {
56625
56916
  const selectedCategories = useSelectedCategories();
56626
56917
  const hasSelection = useHasSelection();
56627
56918
  const isCategorySelected = useIsCategorySelected();
56628
- const selectedCategoryNames = (0, import_react.useMemo)(() => new Set(selectedCategories.map((category) => category.slice(metricType.length + 1))), [metricType, selectedCategories]);
56629
56919
  const clickedObjectPath = useClickedObjectPath();
56630
56920
  const setClickedObjectPath = useSetClickedObjectPath();
56631
56921
  const clickedObjectIsZoomPath = useClickedObjectIsZoomPath();
@@ -56723,14 +57013,14 @@ function Chart() {
56723
57013
  const colorsByPath = (0, import_react.useMemo)(() => {
56724
57014
  const colors = /* @__PURE__ */ new Map();
56725
57015
  const categoryMap = metricsData.get(metricType)?.categoriesMap;
56726
- const noCategoriesSelected = selectedCategoryNames.size === 0;
56727
- for (const node of nodes) colors.set(node.data.path, getColorsForObject(node.data, categoryMap, selectedCategoryNames, noCategoriesSelected));
57016
+ for (const node of nodes) colors.set(node.data.path, getColorsForObject(node.data, categoryMap, selectedCategories, hasSelection));
56728
57017
  return colors;
56729
57018
  }, [
57019
+ hasSelection,
56730
57020
  metricType,
56731
57021
  metricsData,
56732
57022
  nodes,
56733
- selectedCategoryNames
57023
+ selectedCategories
56734
57024
  ]);
56735
57025
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
56736
57026
  className: cn$1("relative grid overflow-hidden", { "pb-4": chartType === "BUBBLE_CHART" }),
@@ -56980,8 +57270,8 @@ function NodeText({ d, isSearchMatch, colors, children = null }) {
56980
57270
  function isCircularNode(d) {
56981
57271
  return typeof d.r === "number";
56982
57272
  }
56983
- function getColorsForObject(obj, categoryMap, selectedCategoryNames, noCategoriesSelected) {
56984
- const colors = categoryMap?.get(obj.path)?.filter(({ category }) => noCategoriesSelected || selectedCategoryNames.has(category)).map(({ color }) => color) ?? [];
57273
+ function getColorsForObject(obj, categoryMap, selectedCategoryNames, hasSelection) {
57274
+ const colors = categoryMap?.get(obj.path)?.filter(({ category }) => !hasSelection || selectedCategoryNames.includes(category)).map(({ color }) => color) ?? [];
56985
57275
  return colors.length > 0 ? colors : [missingInMapColor];
56986
57276
  }
56987
57277
  function createPartitionedHiearchy({ databaseInfo, tree, size, chartType, sizeMetricType, renderCutOff, lastChangedBuckets }) {
@@ -61797,7 +62087,7 @@ function SearchCard() {
61797
62087
  const helpText = `Search within ${zoomPath ? zoomPath.split(getSeparator(zoomPath)).at(-1) : databaseInfo.repo}`;
61798
62088
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
61799
62089
  ref: searchRootRef,
61800
- className: "w-sidepanel not-focus-within:has-placeholder-shown:w-button pointer-events-none absolute top-[calc(2*var(--spacing)+2px)] right-2 z-10 flex flex-col gap-2 transition-[left,width,translate] duration-75 **:pointer-events-auto not-focus-within:has-placeholder-shown:static not-focus-within:has-placeholder-shown:translate-x-0",
62090
+ className: "not-focus-within:has-placeholder-shown:w-button pointer-events-none absolute top-[calc(2*var(--spacing)+2px)] right-4 z-10 flex w-[calc(var(--spacing-sidepanel)-4*var(--spacing))] flex-col gap-2 transition-[left,width,translate] duration-75 **:pointer-events-auto not-focus-within:has-placeholder-shown:static not-focus-within:has-placeholder-shown:translate-x-0",
61801
62091
  onSubmit: (event) => {
61802
62092
  event.preventDefault();
61803
62093
  closeSearch();
@@ -61944,9 +62234,10 @@ function getBarTooltipLabel(intervalStart, unit) {
61944
62234
  }
61945
62235
  function BarChart({ scale, intervals, className }) {
61946
62236
  const { databaseInfo } = useData();
61947
- const [{ start, end }, setQs] = useQueryStates({
62237
+ const [{ start, end, includeCoauthors }, setQs] = useQueryStates({
61948
62238
  start: viewSearchParamsConfig.start.withDefault(databaseInfo.timerange[0]),
61949
- end: viewSearchParamsConfig.end.withDefault(databaseInfo.timerange[1])
62239
+ end: viewSearchParamsConfig.end.withDefault(databaseInfo.timerange[1]),
62240
+ includeCoauthors: viewSearchParamsConfig.includeCoauthors
61950
62241
  });
61951
62242
  const { metricType } = useOptions();
61952
62243
  const metricIsContributorMetric = isContributorMetric(metricType);
@@ -62020,17 +62311,17 @@ function BarChart({ scale, intervals, className }) {
62020
62311
  const isInRange = intervalEnd > start && intervalStart < end;
62021
62312
  const clickedObjInterval = commitCountPerTimeIntervalForClickedObject.find((t) => t.timestamp >= intervalStart && t.timestamp < intervalEnd);
62022
62313
  const sortedContributors = Object.entries(clickedObjInterval?.contributors ?? {}).sort((a, b) => b[1] - a[1]);
62023
- const authorsToStack = clickedObjInterval && selectedCategories.length > 0 && metricIsContributorMetric ? sortedContributors.filter(([author]) => selectedContributors.has(author)) : [];
62024
- const selectedContributorCount = authorsToStack.reduce((total, [, authorCount]) => total + authorCount, 0);
62025
- const displayedCount = selectedCategories.length > 0 && metricIsContributorMetric ? selectedContributorCount : clickedObjInterval?.count ?? 0;
62026
- const hasFileActivity = displayedCount > 0;
62027
- const clickedCountLogged = scale === "log" ? Math.log10(displayedCount + 1) : displayedCount;
62314
+ const stackedContributors = clickedObjInterval && selectedCategories.length > 0 && metricIsContributorMetric ? sortedContributors.filter(([author]) => selectedContributors.has(author)) : [];
62315
+ const selectedContributorCount = stackedContributors.reduce((total, [, authorCount]) => total + authorCount, 0);
62316
+ const selectedCount = selectedCategories.length > 0 && metricIsContributorMetric ? selectedContributorCount : clickedObjInterval?.count ?? 0;
62317
+ const hasFileActivity = selectedCount > 0;
62318
+ const clickedCountLogged = scale === "log" ? Math.log10(selectedCount + 1) : selectedCount;
62028
62319
  const clickedBarHeight = BAR_HEIGHT - yScale(clickedCountLogged);
62029
62320
  const clickedBarY = yScale(clickedCountLogged);
62030
62321
  const tooltipLabel = getBarTooltipLabel(intervalStart, unit);
62031
62322
  let currentY = clickedBarY + clickedBarHeight;
62032
- const stackedSlices = authorsToStack.map(([author, contributorCount]) => {
62033
- const sliceHeight = clickedBarHeight * (contributorCount / displayedCount);
62323
+ const stackedSlices = stackedContributors.map(([author, contributorCount]) => {
62324
+ const sliceHeight = clickedBarHeight * (contributorCount / selectedCount);
62034
62325
  const sliceY = currentY - sliceHeight;
62035
62326
  currentY = sliceY;
62036
62327
  return {
@@ -62041,11 +62332,16 @@ function BarChart({ scale, intervals, className }) {
62041
62332
  };
62042
62333
  });
62043
62334
  const gradientColors = clickedObjInterval && selectedCategories.length === 0 ? sortedContributors.map(([author]) => contributorColors.get(author) ?? "#c0c0c0") : [];
62044
- const tooltipContributors = (selectedCategories.length > 0 && metricIsContributorMetric ? authorsToStack : sortedContributors).map(([author, commitCount]) => ({
62045
- name: author,
62046
- color: contributorColors.get(author) ?? "#c0c0c0",
62047
- commitCount
62048
- }));
62335
+ const tooltipContributors = (selectedCategories.length > 0 && metricIsContributorMetric ? stackedContributors : sortedContributors).map(([author, commitCount]) => {
62336
+ const roleCounts = clickedObjInterval?.contributorRoleCounts[author];
62337
+ return {
62338
+ name: author,
62339
+ color: contributorColors.get(author) ?? "#c0c0c0",
62340
+ commitCount,
62341
+ authoredCommitCount: roleCounts?.authoredCommitCount ?? commitCount,
62342
+ coauthoredCommitCount: includeCoauthors ? roleCounts?.coauthoredCommitCount ?? 0 : null
62343
+ };
62344
+ });
62049
62345
  const tooltip = {
62050
62346
  label: tooltipLabel,
62051
62347
  totalCommitCount: d.count,
@@ -62315,22 +62611,26 @@ function TimelineHeader({ dropdownButtons }) {
62315
62611
  const clickedObject = useClickedObject();
62316
62612
  const data = useData();
62317
62613
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
62318
- className: "card__title flex w-full items-center justify-between gap-4",
62319
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", {
62320
- className: "flex items-center gap-2",
62321
- title: isTree(clickedObject) ? "Commits that changed this folder" : "Commits that changed this file",
62322
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
62323
- className: "truncate",
62324
- children: "Commit activity"
62325
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ClickedObjectButton, {})]
62326
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
62327
- className: "flex items-center justify-end gap-4",
62328
- children: [
62329
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimeUnitForm, {}),
62330
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimeRangePresetButtons, { unit: data.databaseInfo.commitCountPerTimeIntervalUnit }),
62331
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingsButton$1, { metricMenuItems: dropdownButtons })
62332
- ]
62333
- })]
62614
+ className: "card__title grid w-full min-w-0 grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-4",
62615
+ children: [
62616
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
62617
+ className: "flex min-w-0 items-center gap-2",
62618
+ title: isTree(clickedObject) ? "Commits that changed this folder" : "Commits that changed this file",
62619
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
62620
+ className: "truncate",
62621
+ children: "Commit activity"
62622
+ })
62623
+ }),
62624
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ClickedObjectButton, {}),
62625
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
62626
+ className: "flex shrink-0 items-center justify-end gap-2",
62627
+ children: [
62628
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimeUnitForm, {}),
62629
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimeRangePresetButtons, { unit: data.databaseInfo.commitCountPerTimeIntervalUnit }),
62630
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingsButton$1, { metricMenuItems: dropdownButtons })
62631
+ ]
62632
+ })
62633
+ ]
62334
62634
  });
62335
62635
  }
62336
62636
  function SettingsButton$1({ metricMenuItems }) {
@@ -62344,7 +62644,7 @@ function SettingsButton$1({ metricMenuItems }) {
62344
62644
  className: "btn btn--text btn--icon flex items-center",
62345
62645
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, {
62346
62646
  path: mdiDotsVertical,
62347
- size: "1em"
62647
+ size: 1
62348
62648
  })
62349
62649
  })
62350
62650
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Content2, {
@@ -62437,14 +62737,14 @@ function Timeline({ className }) {
62437
62737
  icon: mdiMathLog,
62438
62738
  label: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
62439
62739
  className: "flex w-full items-center justify-between gap-2",
62440
- children: ["Use log scale", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { path: commitCountScale === "log" ? mdiCheckboxMarked : mdiCheckboxBlank })]
62740
+ children: ["Use log scale", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { path: commitCountScale === "log" ? mdiCheckboxMarkedOutline : mdiCheckboxBlankOutline })]
62441
62741
  }),
62442
62742
  onClick: () => setCommitCountScale((prev) => prev === "log" ? "linear" : "log")
62443
62743
  }, {
62444
62744
  icon: mdiAccountMultiple,
62445
62745
  label: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
62446
62746
  className: "flex w-full items-center justify-between gap-2",
62447
- children: ["Include co-authors ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { path: includeCoAuthors ? mdiCheckboxMarked : mdiCheckboxBlank })]
62747
+ children: ["Include co-authors ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { path: includeCoAuthors ? mdiCheckboxMarkedOutline : mdiCheckboxBlankOutline })]
62448
62748
  }),
62449
62749
  onClick: () => setIncludeCoAuthors((prev) => !prev)
62450
62750
  }] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -62532,7 +62832,7 @@ function TimeSlider({ startUnits, endUnits, minMs, maxMs, domainInUnits, unit, d
62532
62832
  left: false,
62533
62833
  right: false,
62534
62834
  children: ({ tracks, getTrackProps }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: tracks.map(({ id, source, target }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
62535
- className: cn$1("bg-blue-primary/20 pointer-events-none absolute right-0 bottom-0 left-0 h-5 cursor-pointer rounded opacity-0 transition-[backdrop-filter]", { "backdrop-blur-2xl": isPending }),
62835
+ className: cn$1("bg-blue-primary/20 pointer-events-none absolute right-0 bottom-0 left-0 h-5 cursor-pointer rounded transition-[backdrop-filter]", { "backdrop-blur-2xl": isPending }),
62536
62836
  style: {
62537
62837
  left: `${source.percent}%`,
62538
62838
  width: `${target.percent - source.percent}%`
@@ -62802,7 +63102,7 @@ function autoBuildContributorGroups(ungroupedContributors) {
62802
63102
  return Array.from(groupsByRoot.values()).filter((g) => g.length > 1).map((members) => ({
62803
63103
  displayName: pickContributorGroupDisplayName(members),
62804
63104
  members
62805
- }));
63105
+ })).toSorted((a, b) => a.displayName.localeCompare(b.displayName));
62806
63106
  }
62807
63107
  //#endregion
62808
63108
  //#region src/components/modals/GroupContributorsModal.tsx
@@ -62881,7 +63181,7 @@ function GroupContributorsModalContent({ open, contributorGroups, contributors,
62881
63181
  })
62882
63182
  }, uniqueId(contributor) + isContributorSelected);
62883
63183
  });
62884
- const groupedContributorsEntries = localContributorGroups.toSorted((a, b) => a.displayName.localeCompare(b.displayName)).map(({ displayName, members }, aliasGroupIndex) => {
63184
+ const groupedContributorsEntries = localContributorGroups.map(({ displayName, members }, aliasGroupIndex) => {
62885
63185
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
62886
63186
  className: "card bg-primary-bg dark:bg-primary-bg-dark group m-0 flex h-full flex-col justify-between p-2",
62887
63187
  title: displayName,
@@ -63185,6 +63485,7 @@ function BarTooltipContent({ hoveredBarTooltip }) {
63185
63485
  const [label, totalCommitLine, clickedCommitLine] = getHoveredBarTooltipLines(hoveredBarTooltip);
63186
63486
  const contributorsToShow = hoveredBarTooltip.contributors.slice(0, maxContributorsToShow);
63187
63487
  const extraContributorCount = hoveredBarTooltip.contributors.length - maxContributorsToShow;
63488
+ const shouldShowCoauthoredColumn = contributorsToShow.some((contributor) => contributor.coauthoredCommitCount !== null);
63188
63489
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
63189
63490
  className: "flex w-max flex-col gap-1",
63190
63491
  children: [
@@ -63207,26 +63508,49 @@ function BarTooltipContent({ hoveredBarTooltip }) {
63207
63508
  }), clickedCommitLine]
63208
63509
  }) : null,
63209
63510
  contributorsToShow.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
63210
- className: "mt-2 flex flex-col gap-1",
63511
+ className: "mt-2",
63211
63512
  children: [
63212
63513
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
63213
- className: "font-bold opacity-80",
63214
- children: "Committers"
63514
+ className: "text-center text-sm font-bold opacity-80",
63515
+ children: "Commit distribution"
63215
63516
  }),
63216
- contributorsToShow.map((contributor) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
63217
- className: "flex items-center gap-1",
63517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
63518
+ className: cn$1("grid max-w-2xs grid-flow-row gap-x-2 gap-y-1 text-left", shouldShowCoauthoredColumn ? "grid-cols-[auto_auto_auto_auto]" : "grid-cols-[auto_auto_auto]"),
63218
63519
  children: [
63219
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LegendDot, {
63220
- dotColor: contributor.color,
63221
- shape: "square"
63520
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { path: mdiAccount }),
63521
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
63522
+ className: "pr-2 font-bold opacity-80",
63523
+ children: "Contributor"
63222
63524
  }),
63223
- " ",
63224
- contributor.name,
63225
- " (",
63226
- formatCommitCount(contributor.commitCount),
63227
- ")"
63525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
63526
+ className: "text-right font-bold opacity-80",
63527
+ children: "Authored"
63528
+ }),
63529
+ shouldShowCoauthoredColumn ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
63530
+ className: "text-right font-bold opacity-80",
63531
+ children: "Co-authored"
63532
+ }) : null,
63533
+ contributorsToShow.map((contributor) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [
63534
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LegendDot, {
63535
+ dotColor: contributor.color,
63536
+ shape: "square"
63537
+ }),
63538
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
63539
+ className: "min-w-0 truncate",
63540
+ title: contributor.name,
63541
+ children: contributor.name
63542
+ }),
63543
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
63544
+ className: "text-right tabular-nums",
63545
+ children: contributor.authoredCommitCount.toLocaleString()
63546
+ }),
63547
+ shouldShowCoauthoredColumn ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
63548
+ className: "text-right tabular-nums",
63549
+ children: (contributor.coauthoredCommitCount ?? 0).toLocaleString()
63550
+ }) : null
63551
+ ] }, contributor.name))
63228
63552
  ]
63229
- }, contributor.name)),
63553
+ }),
63230
63554
  extraContributorCount > 0 ? `and ${extraContributorCount} more...` : null
63231
63555
  ]
63232
63556
  }) : null
@@ -63352,12 +63676,13 @@ function FileChangesEntry(props) {
63352
63676
  function CommitListEntry(props) {
63353
63677
  const [, contributorColors] = useMetrics();
63354
63678
  const authorColor = contributorColors.get(props.value.author.name) ?? "#c0c0c0";
63679
+ const uniqueCoAuthors = props.value.coauthors.filter((coauthor, i, all) => coauthor.name != props.value.author.name && coauthor.email != props.value.author.email && all.findIndex((c) => c.name === coauthor.name && c.email === coauthor.email) === i);
63355
63680
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
63356
63681
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
63357
63682
  className: "flex w-min min-w-5.5 items-start",
63358
63683
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
63359
63684
  className: "flex-end flex flex-row-reverse items-center",
63360
- children: [props.value.coauthors.length > 0 ? props.value.coauthors.filter((coauthor) => coauthor.name != props.value.author.name).slice(0, 2).map((coauthor) => {
63685
+ children: [uniqueCoAuthors.length > 0 ? uniqueCoAuthors.slice(0, 2).map((coauthor) => {
63361
63686
  const coauthorColor = contributorColors.get(coauthor.name) ?? "#c0c0c0";
63362
63687
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LegendDot, {
63363
63688
  title: coauthor.name,
@@ -63414,11 +63739,11 @@ function CommitListEntry(props) {
63414
63739
  ]
63415
63740
  })
63416
63741
  }),
63417
- props.value.coauthors.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GenericEntry, {
63742
+ uniqueCoAuthors.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GenericEntry, {
63418
63743
  keyString: "Co-authors",
63419
63744
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
63420
63745
  className: "flex flex-col gap-y-1",
63421
- children: props.value.coauthors.map((coauthor) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
63746
+ children: uniqueCoAuthors.map((coauthor) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
63422
63747
  className: "grid grid-cols-[max-content_max-content_auto] items-center gap-1 text-sm",
63423
63748
  children: [
63424
63749
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LegendDot, { dotColor: contributorColors.get(coauthor.name) ?? "grey" }),
@@ -63839,7 +64164,7 @@ var drivingTruck_32x_default = "/assets/drivingTruck_32x-C9OqTj6H.gif";
63839
64164
  //#region src/components/CompactLoadingIndicator.tsx
63840
64165
  function CompactLoadingIndicator() {
63841
64166
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
63842
- className: cn$1("flex gap-2", "transition-opacity", { "opacity-0": !useIsLoading() }),
64167
+ className: cn$1("flex gap-2 pr-1", "transition-opacity", { "opacity-0": !useIsLoading() }),
63843
64168
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
63844
64169
  className: "font-bold",
63845
64170
  children: "Loading..."
@@ -63886,7 +64211,7 @@ function Legend() {
63886
64211
  }),
63887
64212
  description: Panel.description ?? "Description not provided.",
63888
64213
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Panel.content, {})
63889
- }, Panel.id ?? `panel-${i}`))] });
64214
+ }, `${metricType}:${Panel.id}`))] });
63890
64215
  }
63891
64216
  //#endregion
63892
64217
  //#region src/routes/view.tsx
@@ -64269,9 +64594,9 @@ var view_default = withComponentProps(function Repo({ loaderData: { parentDirect
64269
64594
  ]
64270
64595
  }),
64271
64596
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("nav", {
64272
- className: "grid grid-cols-[1fr_auto_1fr] items-center justify-between gap-2 [grid-area:cheader]",
64597
+ className: "grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-2 [grid-area:cheader]",
64273
64598
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
64274
- className: cn$1("flex items-center"),
64599
+ className: cn$1("flex min-w-0 items-center"),
64275
64600
  children: [matchesLarge && !leftExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
64276
64601
  title: "Show left panel",
64277
64602
  className: "btn btn--text aspect-square",
@@ -64285,7 +64610,10 @@ var view_default = withComponentProps(function Repo({ loaderData: { parentDirect
64285
64610
  path: "M11,2 H13 V22 H11 Z",
64286
64611
  size: 1,
64287
64612
  className: "fill-primary-text dark:fill-primary-text-dark mx-0 align-middle opacity-20"
64288
- })] }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Breadcrumb, { zoom: true })]
64613
+ })] }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Breadcrumb, {
64614
+ zoom: true,
64615
+ className: "flex-1"
64616
+ })]
64289
64617
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CompactLoadingIndicator, {})]
64290
64618
  }),
64291
64619
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -64993,19 +65321,19 @@ var server_manifest_default = {
64993
65321
  "hasClientMiddleware": false,
64994
65322
  "hasDefaultExport": true,
64995
65323
  "hasErrorBoundary": true,
64996
- "module": "/assets/root-CBAhjBmF.js",
65324
+ "module": "/assets/root-Bw7JpNPD.js",
64997
65325
  "imports": [
64998
65326
  "/assets/react-YTRJEZSD.js",
64999
65327
  "/assets/jsx-runtime-GK6fC0y1.js",
65000
65328
  "/assets/react-dom-QjzVRb79.js",
65001
- "/assets/util-Cy1DhIdh.js",
65002
- "/assets/clear-cache-_qCibnyW.js",
65329
+ "/assets/util-DXfdT-4_.js",
65330
+ "/assets/clear-cache-BdOk-5Hg.js",
65003
65331
  "/assets/dist-Q88wN5JZ.js",
65004
- "/assets/ChevronButton-DK6ES4LI.js",
65005
- "/assets/CollapsibleHeader-CowQ8BBC.js",
65006
- "/assets/viewParams-BGn6CoZr.js"
65332
+ "/assets/ChevronButton-DMAgHXfP.js",
65333
+ "/assets/CollapsibleHeader-D64_oUaZ.js",
65334
+ "/assets/viewParams-CYa4lbK3.js"
65007
65335
  ],
65008
- "css": ["/assets/root-C1A3dI8J.css"],
65336
+ "css": ["/assets/root-ulNnm43y.css"],
65009
65337
  "clientActionModule": void 0,
65010
65338
  "clientLoaderModule": void 0,
65011
65339
  "clientMiddlewareModule": void 0,
@@ -65087,11 +65415,11 @@ var server_manifest_default = {
65087
65415
  "hasClientMiddleware": false,
65088
65416
  "hasDefaultExport": false,
65089
65417
  "hasErrorBoundary": false,
65090
- "module": "/assets/api.progress-PbuJDh_8.js",
65418
+ "module": "/assets/api.progress-BjsGOmGF.js",
65091
65419
  "imports": [
65092
- "/assets/api.progress-9Sjpquqz.js",
65420
+ "/assets/api.progress-ClQ--CbT.js",
65093
65421
  "/assets/dist-Q88wN5JZ.js",
65094
- "/assets/viewParams-BGn6CoZr.js",
65422
+ "/assets/viewParams-CYa4lbK3.js",
65095
65423
  "/assets/react-YTRJEZSD.js"
65096
65424
  ],
65097
65425
  "css": [],
@@ -65113,11 +65441,11 @@ var server_manifest_default = {
65113
65441
  "hasClientMiddleware": false,
65114
65442
  "hasDefaultExport": false,
65115
65443
  "hasErrorBoundary": false,
65116
- "module": "/assets/api.commits-B4POWHc4.js",
65444
+ "module": "/assets/api.commits-C8MoPAnE.js",
65117
65445
  "imports": [
65118
- "/assets/api.commits-BwXLd7eY.js",
65446
+ "/assets/api.commits-B87eK7HE.js",
65119
65447
  "/assets/dist-Q88wN5JZ.js",
65120
- "/assets/viewParams-BGn6CoZr.js",
65448
+ "/assets/viewParams-CYa4lbK3.js",
65121
65449
  "/assets/react-YTRJEZSD.js"
65122
65450
  ],
65123
65451
  "css": [],
@@ -65160,9 +65488,9 @@ var server_manifest_default = {
65160
65488
  "hasClientMiddleware": false,
65161
65489
  "hasDefaultExport": false,
65162
65490
  "hasErrorBoundary": false,
65163
- "module": "/assets/clear-cache-BzRoZVxS.js",
65491
+ "module": "/assets/clear-cache-Dpk-kNlJ.js",
65164
65492
  "imports": [
65165
- "/assets/clear-cache-_qCibnyW.js",
65493
+ "/assets/clear-cache-BdOk-5Hg.js",
65166
65494
  "/assets/react-YTRJEZSD.js",
65167
65495
  "/assets/jsx-runtime-GK6fC0y1.js"
65168
65496
  ],
@@ -65231,15 +65559,15 @@ var server_manifest_default = {
65231
65559
  "hasClientMiddleware": false,
65232
65560
  "hasDefaultExport": true,
65233
65561
  "hasErrorBoundary": false,
65234
- "module": "/assets/browse-f4khjgf7.js",
65562
+ "module": "/assets/browse-NZo8Wkxj.js",
65235
65563
  "imports": [
65236
- "/assets/browse-D4C7blMn.js",
65564
+ "/assets/browse-Dt072k2G.js",
65237
65565
  "/assets/react-YTRJEZSD.js",
65238
65566
  "/assets/jsx-runtime-GK6fC0y1.js",
65239
- "/assets/util-Cy1DhIdh.js",
65240
- "/assets/clear-cache-_qCibnyW.js",
65567
+ "/assets/util-DXfdT-4_.js",
65568
+ "/assets/clear-cache-BdOk-5Hg.js",
65241
65569
  "/assets/dist-Q88wN5JZ.js",
65242
- "/assets/viewParams-BGn6CoZr.js",
65570
+ "/assets/viewParams-CYa4lbK3.js",
65243
65571
  "/assets/react-dom-QjzVRb79.js"
65244
65572
  ],
65245
65573
  "css": [],
@@ -65282,22 +65610,22 @@ var server_manifest_default = {
65282
65610
  "hasClientMiddleware": false,
65283
65611
  "hasDefaultExport": true,
65284
65612
  "hasErrorBoundary": false,
65285
- "module": "/assets/view-xdV5WFuZ.js",
65613
+ "module": "/assets/view-BO8_n49o.js",
65286
65614
  "imports": [
65287
65615
  "/assets/react-YTRJEZSD.js",
65288
65616
  "/assets/jsx-runtime-GK6fC0y1.js",
65289
- "/assets/util-Cy1DhIdh.js",
65290
- "/assets/Tooltip-4EXAWNsE.js",
65291
- "/assets/clear-cache-_qCibnyW.js",
65617
+ "/assets/util-DXfdT-4_.js",
65618
+ "/assets/Tooltip-DD8MeSQ5.js",
65619
+ "/assets/clear-cache-BdOk-5Hg.js",
65292
65620
  "/assets/dist-Q88wN5JZ.js",
65293
- "/assets/viewParams-BGn6CoZr.js",
65621
+ "/assets/viewParams-CYa4lbK3.js",
65294
65622
  "/assets/react-dom-QjzVRb79.js",
65295
- "/assets/browse-D4C7blMn.js",
65296
- "/assets/CollapsibleHeader-CowQ8BBC.js",
65623
+ "/assets/browse-Dt072k2G.js",
65624
+ "/assets/CollapsibleHeader-D64_oUaZ.js",
65297
65625
  "/assets/api.abort-D0XxKxsc.js",
65298
- "/assets/api.commits-BwXLd7eY.js",
65299
- "/assets/ChevronButton-DK6ES4LI.js",
65300
- "/assets/api.progress-9Sjpquqz.js"
65626
+ "/assets/api.commits-B87eK7HE.js",
65627
+ "/assets/ChevronButton-DMAgHXfP.js",
65628
+ "/assets/api.progress-ClQ--CbT.js"
65301
65629
  ],
65302
65630
  "css": [],
65303
65631
  "clientActionModule": void 0,
@@ -65318,19 +65646,19 @@ var server_manifest_default = {
65318
65646
  "hasClientMiddleware": false,
65319
65647
  "hasDefaultExport": true,
65320
65648
  "hasErrorBoundary": false,
65321
- "module": "/assets/ui-apdBVMs3.js",
65649
+ "module": "/assets/ui-BlLnrEiL.js",
65322
65650
  "imports": [
65323
65651
  "/assets/react-YTRJEZSD.js",
65324
65652
  "/assets/jsx-runtime-GK6fC0y1.js",
65325
- "/assets/util-Cy1DhIdh.js",
65326
- "/assets/Tooltip-4EXAWNsE.js",
65327
- "/assets/clear-cache-_qCibnyW.js",
65328
- "/assets/ChevronButton-DK6ES4LI.js",
65329
- "/assets/browse-D4C7blMn.js",
65653
+ "/assets/util-DXfdT-4_.js",
65654
+ "/assets/Tooltip-DD8MeSQ5.js",
65655
+ "/assets/clear-cache-BdOk-5Hg.js",
65656
+ "/assets/ChevronButton-DMAgHXfP.js",
65657
+ "/assets/browse-Dt072k2G.js",
65330
65658
  "/assets/dist-Q88wN5JZ.js",
65331
- "/assets/viewParams-BGn6CoZr.js",
65659
+ "/assets/viewParams-CYa4lbK3.js",
65332
65660
  "/assets/react-dom-QjzVRb79.js",
65333
- "/assets/api.progress-9Sjpquqz.js"
65661
+ "/assets/api.progress-ClQ--CbT.js"
65334
65662
  ],
65335
65663
  "css": [],
65336
65664
  "clientActionModule": void 0,
@@ -65360,8 +65688,8 @@ var server_manifest_default = {
65360
65688
  "hydrateFallbackModule": void 0
65361
65689
  }
65362
65690
  },
65363
- "url": "/assets/manifest-27f3fdf9.js",
65364
- "version": "27f3fdf9",
65691
+ "url": "/assets/manifest-fcb52500.js",
65692
+ "version": "fcb52500",
65365
65693
  "sri": void 0
65366
65694
  };
65367
65695
  //#endregion