@hawkeyexl/manni 4.0.2 → 4.0.3

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/README.md CHANGED
@@ -102,7 +102,7 @@ actually reads rather than hand-written:
102
102
  ```yaml
103
103
  repos:
104
104
  - repo: https://github.com/hawkeyexl/manni
105
- rev: v4.0.2
105
+ rev: v4.0.3
106
106
  hooks:
107
107
  - id: manni-meta
108
108
  ```
@@ -63,7 +63,7 @@ import {
63
63
  terminalConfirm,
64
64
  toJsonText,
65
65
  warn
66
- } from "./chunk-TK5PYPFU.js";
66
+ } from "./chunk-TQG3WCJY.js";
67
67
 
68
68
  // src/meta/cli.ts
69
69
  import { existsSync } from "node:fs";
@@ -1533,4 +1533,4 @@ export {
1533
1533
  reportConfig,
1534
1534
  buildProgram
1535
1535
  };
1536
- //# sourceMappingURL=chunk-KSH7XS2R.js.map
1536
+ //# sourceMappingURL=chunk-DEP5AMVA.js.map
@@ -90,7 +90,7 @@ import {
90
90
  writeEncryptionKey,
91
91
  writeFileAtomic,
92
92
  writeTextAtomic
93
- } from "./chunk-TK5PYPFU.js";
93
+ } from "./chunk-TQG3WCJY.js";
94
94
 
95
95
  // src/meta/core/reencrypt.ts
96
96
  import { extname } from "node:path";
@@ -13905,4 +13905,4 @@ export {
13905
13905
  renderTemplates,
13906
13906
  renderTools
13907
13907
  };
13908
- //# sourceMappingURL=chunk-L26XRFBJ.js.map
13908
+ //# sourceMappingURL=chunk-JYRY7SKD.js.map
@@ -8,7 +8,7 @@ var __export = (target, all) => {
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@hawkeyexl/manni",
11
- version: "4.0.2",
11
+ version: "4.0.3",
12
12
  description: "One command line for documentation that is meant to be checked. `manni meta` validates the presence and format of document metadata (Markdown frontmatter and more) against JSON Schema, built for CI. Formerly docmeta.",
13
13
  type: "module",
14
14
  bin: {
@@ -14684,15 +14684,24 @@ async function deriveFromGit(inputs, opts) {
14684
14684
  continue;
14685
14685
  }
14686
14686
  try {
14687
- const histories = bucket.length <= threshold ? await perFileHistories(run2, bucket) : await bulkHistories(run2, bucket);
14688
- if (histories === null) {
14687
+ const found = await histories(
14688
+ run2,
14689
+ bucket.map((e) => e.rel),
14690
+ threshold
14691
+ );
14692
+ if (found === null) {
14693
+ reasons.push(`git could not read the history at ${root}`);
14694
+ continue;
14695
+ }
14696
+ await readOtherParentBlobs(run2, found);
14697
+ const blobs = await fetchBlobs(run2, neededBlobs(found));
14698
+ if (!await followTakenBodies(run2, bucket, found, blobs, threshold)) {
14689
14699
  reasons.push(`git could not read the history at ${root}`);
14690
14700
  continue;
14691
14701
  }
14692
- const blobs = await fetchBlobs(run2, neededBlobs(histories));
14693
- const manifestStamps = await stampsInManifests(run2, root, bucket, histories, blobs);
14702
+ const manifestStamps = await stampsInManifests(run2, root, bucket, found, blobs);
14694
14703
  for (const entry of bucket) {
14695
- const history = histories.get(entry.rel) ?? [];
14704
+ const history = found.get(entry.rel) ?? [];
14696
14705
  const facts = judge(
14697
14706
  entry.input,
14698
14707
  root,
@@ -14753,7 +14762,7 @@ function groupByRoot(inputs) {
14753
14762
  }
14754
14763
  return byRoot;
14755
14764
  }
14756
- var RECORD_FORMAT = "--format=%x1e%H%x00%aI%x00%an%x00%ae%x00%(trailers:key=Co-authored-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Reviewed-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Generated-by,valueonly,unfold,separator=%x1f)";
14765
+ var RECORD_FORMAT = "--format=%x1e%H%x00%aI%x00%an%x00%ae%x00%(trailers:key=Co-authored-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Reviewed-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Generated-by,valueonly,unfold,separator=%x1f)%x00%P";
14757
14766
  var LOG_ARGS = [
14758
14767
  "-c",
14759
14768
  "core.quotePath=false",
@@ -14764,31 +14773,70 @@ var LOG_ARGS = [
14764
14773
  "--no-abbrev",
14765
14774
  RECORD_FORMAT
14766
14775
  ];
14767
- async function perFileHistories(run2, bucket) {
14768
- const histories = /* @__PURE__ */ new Map();
14769
- for (const entry of bucket) {
14770
- const out = await run2([...LOG_ARGS, "--follow", "--", pathspec(entry.rel)]);
14776
+ function histories(run2, rels, threshold, rev = "HEAD") {
14777
+ return rels.length <= threshold ? perFileHistories(run2, rels, rev) : bulkHistories(run2, rels, rev);
14778
+ }
14779
+ async function perFileHistories(run2, rels, rev) {
14780
+ const found = /* @__PURE__ */ new Map();
14781
+ for (const rel of rels) {
14782
+ const out = await run2([...LOG_ARGS, rev, "--follow", "--", pathspec(rel)]);
14771
14783
  if (out.tooLarge) throw new HistoryTooLarge();
14772
14784
  if (out.code !== 0) {
14773
- if (await isUnbornHead(run2)) return histories;
14785
+ if (await isUnbornHead(run2)) return found;
14774
14786
  return null;
14775
14787
  }
14776
- const attributed = attribute(parseLog(text(out)), [entry.rel]);
14777
- histories.set(entry.rel, attributed.get(entry.rel) ?? []);
14788
+ const attributed = attribute(parseLog(text(out)), [rel]);
14789
+ found.set(rel, attributed.get(rel) ?? []);
14778
14790
  }
14779
- return histories;
14791
+ return found;
14780
14792
  }
14781
- async function bulkHistories(run2, bucket) {
14782
- const out = await run2(LOG_ARGS);
14793
+ async function bulkHistories(run2, rels, rev) {
14794
+ const out = await run2([...LOG_ARGS, rev]);
14783
14795
  if (out.tooLarge) throw new HistoryTooLarge();
14784
14796
  if (out.code !== 0) {
14785
14797
  if (await isUnbornHead(run2)) return /* @__PURE__ */ new Map();
14786
14798
  return null;
14787
14799
  }
14788
- return attribute(
14789
- parseLog(text(out)),
14790
- bucket.map((e) => e.rel)
14791
- );
14800
+ return attribute(parseLog(text(out)), [...rels]);
14801
+ }
14802
+ async function followTakenBodies(run2, bucket, found, blobs, threshold) {
14803
+ for (; ; ) {
14804
+ const byParent = /* @__PURE__ */ new Map();
14805
+ for (const entry of bucket) {
14806
+ const history = found.get(entry.rel) ?? [];
14807
+ const taken = takenMerge(entry.input.extracted.fenced, history, blobs);
14808
+ if (taken === void 0) continue;
14809
+ const group = byParent.get(taken.parent) ?? [];
14810
+ group.push({ rel: entry.rel, keep: history.slice(0, taken.index), path: taken.path });
14811
+ byParent.set(taken.parent, group);
14812
+ }
14813
+ if (byParent.size === 0) return true;
14814
+ const walked = /* @__PURE__ */ new Map();
14815
+ for (const [parent, group] of byParent) {
14816
+ const paths = [...new Set(group.map((g) => g.path))];
14817
+ const side = await histories(run2, paths, threshold, parent);
14818
+ if (side === null) return false;
14819
+ for (const g of group) {
14820
+ const tail = side.get(g.path) ?? [];
14821
+ walked.set(g.rel, tail);
14822
+ found.set(g.rel, [...g.keep, ...tail]);
14823
+ }
14824
+ }
14825
+ await readOtherParentBlobs(run2, walked);
14826
+ const missing = new Set([...neededBlobs(walked)].filter((sha) => !blobs.has(sha)));
14827
+ for (const [sha, content] of await fetchBlobs(run2, missing)) blobs.set(sha, content);
14828
+ }
14829
+ }
14830
+ function takenMerge(fenced, history, blobs) {
14831
+ const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
14832
+ const index = history.findIndex((h) => !sameBody(blobBody(h.newBlob), blobBody(h.oldBlob)));
14833
+ const newest = history[index];
14834
+ if (newest === void 0) return void 0;
14835
+ const body = blobBody(newest.newBlob);
14836
+ const k = newest.otherParentBlobs.findIndex((b) => b !== null && sameBody(body, blobBody(b)));
14837
+ const parent = newest.otherParents[k];
14838
+ if (k === -1 || parent === void 0) return void 0;
14839
+ return { index, parent, path: newest.pathAtCommit };
14792
14840
  }
14793
14841
  function pathspec(rel) {
14794
14842
  return /[*?[\]]/.test(rel) ? `:(literal)${rel}` : rel;
@@ -14821,6 +14869,7 @@ function parseLog(text2) {
14821
14869
  coAuthors: splitTrailers(fields[4]),
14822
14870
  reviewedBy: splitTrailers(fields[5]),
14823
14871
  generatedBy: splitTrailers(fields[6]),
14872
+ parents: (fields[7] ?? "").trim().split(" ").filter((p) => p !== ""),
14824
14873
  raw
14825
14874
  });
14826
14875
  }
@@ -14891,10 +14940,10 @@ function unquotePath(p) {
14891
14940
  return Buffer.from(bytes).toString("utf8");
14892
14941
  }
14893
14942
  function attribute(records, wanted) {
14894
- const histories = /* @__PURE__ */ new Map();
14943
+ const histories2 = /* @__PURE__ */ new Map();
14895
14944
  const tracked = /* @__PURE__ */ new Map();
14896
14945
  for (const rel of wanted) {
14897
- histories.set(rel, []);
14946
+ histories2.set(rel, []);
14898
14947
  tracked.set(rel, rel);
14899
14948
  }
14900
14949
  for (const record of records) {
@@ -14904,7 +14953,7 @@ function attribute(records, wanted) {
14904
14953
  for (const [rel, current] of tracked) {
14905
14954
  const entry = byNewPath.get(current);
14906
14955
  if (!entry) continue;
14907
- histories.get(rel)?.push({
14956
+ histories2.get(rel)?.push({
14908
14957
  sha: record.sha,
14909
14958
  authorDate: record.authorDate,
14910
14959
  authorName: record.authorName,
@@ -14914,23 +14963,52 @@ function attribute(records, wanted) {
14914
14963
  generatedBy: record.generatedBy,
14915
14964
  oldBlob: entry.oldBlob === NULL_SHA ? null : entry.oldBlob,
14916
14965
  newBlob: entry.newBlob === NULL_SHA ? null : entry.newBlob,
14917
- pathAtCommit: entry.newPath
14966
+ pathAtCommit: entry.newPath,
14967
+ otherParents: record.parents.slice(1),
14968
+ otherParentBlobs: []
14918
14969
  });
14919
14970
  if (entry.oldPath !== entry.newPath) tracked.set(rel, entry.oldPath);
14920
14971
  }
14921
14972
  }
14922
- return histories;
14973
+ return histories2;
14923
14974
  }
14924
- function neededBlobs(histories) {
14975
+ function neededBlobs(histories2) {
14925
14976
  const shas = /* @__PURE__ */ new Set();
14926
- for (const history of histories.values()) {
14977
+ for (const history of histories2.values()) {
14927
14978
  for (const h of history) {
14928
14979
  if (h.oldBlob !== null) shas.add(h.oldBlob);
14929
14980
  if (h.newBlob !== null) shas.add(h.newBlob);
14981
+ for (const b of h.otherParentBlobs) if (b !== null) shas.add(b);
14930
14982
  }
14931
14983
  }
14932
14984
  return shas;
14933
14985
  }
14986
+ async function readOtherParentBlobs(run2, histories2) {
14987
+ const merges = [];
14988
+ const specs = [];
14989
+ for (const history of histories2.values()) {
14990
+ for (const h of history) {
14991
+ if (h.otherParents.length === 0) continue;
14992
+ merges.push(h);
14993
+ for (const parent of h.otherParents) specs.push(`${parent}:${h.pathAtCommit}`);
14994
+ }
14995
+ }
14996
+ if (specs.length === 0) return;
14997
+ const out = await run2(["cat-file", "--batch-check"], `${specs.join("\n")}
14998
+ `);
14999
+ if (out.tooLarge) throw new HistoryTooLarge();
15000
+ if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
15001
+ const lines = text(out).split("\n");
15002
+ let i = 0;
15003
+ for (const h of merges) {
15004
+ h.otherParentBlobs = h.otherParents.map(() => blobOfCheckLine(lines[i++]));
15005
+ }
15006
+ }
15007
+ function blobOfCheckLine(line) {
15008
+ const [sha, kind] = (line ?? "").split(" ");
15009
+ if (sha === void 0 || kind !== "blob" || !/^[0-9a-f]{40,64}$/.test(sha)) return null;
15010
+ return sha;
15011
+ }
14934
15012
  async function fetchBlobs(run2, shas) {
14935
15013
  const blobs = /* @__PURE__ */ new Map();
14936
15014
  if (shas.size === 0) return blobs;
@@ -14964,19 +15042,23 @@ var git = (value, evidence) => ({
14964
15042
  function factCommits(input, history, blobs) {
14965
15043
  const fenced = input.extracted.fenced;
14966
15044
  const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
14967
- const bodyChanging = [...history].reverse().filter((h) => !sameBody(blobBody(h.newBlob), blobBody(h.oldBlob)));
15045
+ const bodyChanging = [...history].reverse().filter((h) => {
15046
+ const body = blobBody(h.newBlob);
15047
+ if (sameBody(body, blobBody(h.oldBlob))) return false;
15048
+ return !h.otherParentBlobs.some((b) => b !== null && sameBody(body, blobBody(b)));
15049
+ });
14968
15050
  return {
14969
15051
  bodyChanging,
14970
15052
  oldest: history[history.length - 1],
14971
15053
  newestBody: bodyChanging[bodyChanging.length - 1]
14972
15054
  };
14973
15055
  }
14974
- async function stampsInManifests(run2, root, bucket, histories, blobs) {
15056
+ async function stampsInManifests(run2, root, bucket, histories2, blobs) {
14975
15057
  const reads = [];
14976
15058
  for (const entry of bucket) {
14977
15059
  const refs = entry.input.stampManifests;
14978
15060
  if (refs === void 0) continue;
14979
- const { oldest, newestBody } = factCommits(entry.input, histories.get(entry.rel) ?? [], blobs);
15061
+ const { oldest, newestBody } = factCommits(entry.input, histories2.get(entry.rel) ?? [], blobs);
14980
15062
  for (const field of STAMPED_DATE_FIELDS) {
14981
15063
  const ref = refs[field];
14982
15064
  const at2 = field === "created" ? oldest : newestBody;
@@ -25113,4 +25195,4 @@ export {
25113
25195
  renderQuery,
25114
25196
  renderQueryCsv
25115
25197
  };
25116
- //# sourceMappingURL=chunk-TK5PYPFU.js.map
25198
+ //# sourceMappingURL=chunk-TQG3WCJY.js.map