1# Blame and history (lib/blame.ts): each file on the default branch gets a2# blame.json beside its page, saying which commit last changed each line3# and which commits changed the file, with the review each landed in. A4# build recomputes only the files commits touched since.5. "$(dirname "$0")/../lib.sh"6user dami7repo dami/greet8clone dami dami/greet "$T/w/d"9printf 'one\ntwo\nthree\n' >"$T/w/d/a.txt"10printf '# hi\n' >"$T/w/d/README.md"11at "$T/w/d" commit "first" b.txt12c1=$(git -C "$T/w/d" rev-parse HEAD)13ok push_as dami "$T/w/d" origin main14# Line 2 changes in a review that lands.15at "$T/w/d" git switch -q -c dami/two16printf 'one\nTWO\nthree\n' >"$T/w/d/a.txt"17at "$T/w/d" git commit -q -am "shout two"18c2=$(git -C "$T/w/d" rev-parse HEAD)19ok push_as dami "$T/w/d" origin dami/two -o "title=Shout"20ok push_as dami "$T/w/d" origin dami/two:main21build dami/greet22j=$FORGE_WWW/dami/greet/blob/main/a.txt/blame.json23exists "$j"24node -e '25 const d = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));26 const sha = (i) => d.commits[i][0];27 const [c1, c2] = process.argv.slice(2);28 const by = d.ranges.map(([s, n, i]) => `${s}+${n}:${sha(i) === c1 ? "c1" : sha(i) === c2 ? "c2" : "?"}`).join(" ");29 if (by !== "1+1:c1 2+1:c2 3+1:c1") throw new Error("ranges: " + by);30 if (d.history.map(sha).join(" ") !== `${c2} ${c1}`) throw new Error("history: " + d.history.map(sha));31 const two = d.commits.find((c) => c[0] === c2);32 if (two[1] !== "shout two" || two[4] !== 1 || two[5] !== "Shout" || two[6] !== 1) throw new Error("commit row: " + JSON.stringify(two));33' "$j" "$c1" "$c2" || fail "blame.json is wrong: $(cat "$j")"34page=$FORGE_WWW/dami/greet/blob/main/a.txt/index.html35has "$page" 'data-blame-src="/dami/greet/blob/main/a.txt/blame.json" data-commit-base="/dami/greet/commit/" data-review-base="/dami/greet/reviews/"'36has "$page" '<button type="button" data-blame aria-pressed="false"'37has "$page" '<script src="/assets/blame\.[A-Za-z0-9_-]{10}\.js" defer>'38# Markdown: the rendered page has no blame; its source page does.39lacks "$FORGE_WWW/dami/greet/blob/main/README.md/index.html" 'data-blame'40has "$FORGE_WWW/dami/greet/blob/main/README.md/source/index.html" 'data-blame-src="/dami/greet/blob/main/README.md/blame.json"'41# Other branches' file pages have none.42at "$T/w/d" git switch -q -c dami/side43at "$T/w/d" commit "side" c.txt44ok push_as dami "$T/w/d" origin dami/side45build dami/greet46lacks "$FORGE_WWW/dami/greet/blob/dami/side/a.txt/index.html" 'data-blame'4748# Changed and changed back: same blob, but the lines are the revert's now.49at "$T/w/d" git switch -q main50at "$T/w/d" git reset -q --hard origin/main51printf 'one\ntwo\nthree\n' >"$T/w/d/a.txt"52at "$T/w/d" git commit -q -am "lower two"53printf 'one\nTWO\nthree\n' >"$T/w/d/a.txt"54at "$T/w/d" git commit -q -am "shout two again"55c4=$(git -C "$T/w/d" rev-parse HEAD)56ok push_as dami "$T/w/d" origin main57build dami/greet58node -e '59 const d = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));60 const i = d.ranges.find(([s]) => s === 2)[2];61 if (d.commits[i][0] !== process.argv[2]) throw new Error("line 2 is " + d.commits[i][0]);62 if (d.history.length !== 4) throw new Error("history " + d.history.length);63' "$j" "$c4" || fail "a file changed and back keeps an old blame: $(cat "$j")"64# A file no commit touched since keeps its cached blame.65cache=$(ls "$FORGE_CACHE/blame/dami/greet/" | wc -l | tr -d ' ')66[ "$cache" -ge 3 ] || fail "expected cached blames, got $cache"