feat: dossiers-ngi sticky bar vs-main +green/−red diffstat
This commit is contained in:
parent
a4ec0305bc
commit
31af42538a
6 changed files with 134 additions and 32 deletions
|
|
@ -58,6 +58,7 @@ Per branch in JSON / sticky bar:
|
|||
| `tip_time` | committer time of tip (`%cI`) |
|
||||
| `built_at` | when this edition freeze was written |
|
||||
| `build_seconds` | wall time of that freeze |
|
||||
| `vs_main_plus` / `vs_main_minus` | `git diff --numstat` tip vs `main` tip (sticky bar +grün/−rot; omitted on `main`) |
|
||||
|
||||
Rebuild runs **only when** `.edition-tip` ≠ current tip (timer never forces).
|
||||
Each run also: `git fetch --prune`, `worktree prune`, drop stale
|
||||
|
|
|
|||
|
|
@ -234,6 +234,30 @@ def tip_recorded(editions: Path, slug: str) -> str:
|
|||
return p.read_text(encoding="utf-8").strip()
|
||||
|
||||
|
||||
|
||||
def diff_vs_main(repo: Path, main_tip: str, tip: str) -> tuple[int, int]:
|
||||
"""Insertions/deletions of tip vs main tip (git diff --numstat)."""
|
||||
if not main_tip or not tip or main_tip == tip:
|
||||
return 0, 0
|
||||
cp = git(repo, "diff", "--numstat", main_tip, tip, check=False)
|
||||
if cp.returncode != 0:
|
||||
return 0, 0
|
||||
plus = minus = 0
|
||||
for line in (cp.stdout or "").splitlines():
|
||||
parts = line.split(" ")
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
a, d = parts[0], parts[1]
|
||||
if a == "-" or d == "-":
|
||||
continue
|
||||
try:
|
||||
plus += int(a)
|
||||
minus += int(d)
|
||||
except ValueError:
|
||||
continue
|
||||
return plus, minus
|
||||
|
||||
|
||||
def tip_commit_time(repo: Path, tip: str) -> str:
|
||||
"""Committer ISO time of tip (empty on failure)."""
|
||||
cp = git(repo, "log", "-1", "--format=%cI", tip, check=False)
|
||||
|
|
@ -531,6 +555,10 @@ def build_payload(repo: Path, tips: dict[str, str], rebuild: bool) -> dict:
|
|||
sync_flask_alias(ed_root, tips)
|
||||
|
||||
tip_times = {name: tip_commit_time(repo, tip) for name, tip in tips.items()}
|
||||
main_tip = tips.get("main", "")
|
||||
vs_main = {
|
||||
name: diff_vs_main(repo, main_tip, tip) for name, tip in tips.items()
|
||||
}
|
||||
branches_out = []
|
||||
for row in rows:
|
||||
name = row["branch"]
|
||||
|
|
@ -556,6 +584,11 @@ def build_payload(repo: Path, tips: dict[str, str], rebuild: bool) -> dict:
|
|||
entry["built_at"] = meta["built_at"]
|
||||
if "build_seconds" in meta:
|
||||
entry["build_seconds"] = meta["build_seconds"]
|
||||
# sticky bar: +grün / −rot vs main (omit on main itself)
|
||||
if name != "main" and main_tip:
|
||||
plus, minus = vs_main.get(name, (0, 0))
|
||||
entry["vs_main_plus"] = plus
|
||||
entry["vs_main_minus"] = minus
|
||||
branches_out.append(entry)
|
||||
|
||||
payload = {
|
||||
|
|
|
|||
|
|
@ -76,3 +76,12 @@ a.cascade-tree-row.is-active:hover {
|
|||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.cascade-bar .diff-plus {
|
||||
color: #3fb950;
|
||||
font-weight: 600;
|
||||
}
|
||||
.cascade-bar .diff-minus {
|
||||
color: #f85149;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,23 +37,49 @@
|
|||
return Math.round(n) + "s";
|
||||
}
|
||||
|
||||
function metaBits(row) {
|
||||
function metaHtml(row) {
|
||||
var tip = row.tip || "";
|
||||
var tipTime = row.tip_time || "";
|
||||
var builtAt = row.built_at || "";
|
||||
var buildSec = row.build_seconds;
|
||||
var bits = [];
|
||||
if (tip) bits.push("@" + tip);
|
||||
if (tipTime) bits.push("tip " + shortTime(tipTime));
|
||||
var parts = [];
|
||||
if (tip) parts.push(esc("@" + tip));
|
||||
if (tipTime) parts.push(esc("tip " + shortTime(tipTime)));
|
||||
if (buildSec != null && buildSec !== "")
|
||||
bits.push("build " + fmtBuildSec(buildSec));
|
||||
if (builtAt) bits.push("at " + shortTime(builtAt));
|
||||
return bits;
|
||||
parts.push(esc("build " + fmtBuildSec(buildSec)));
|
||||
if (builtAt) parts.push(esc("at " + shortTime(builtAt)));
|
||||
// vs main: +grün / −rot (omit on main / when both zero absent)
|
||||
var plus = row.vs_main_plus;
|
||||
var minus = row.vs_main_minus;
|
||||
if (plus != null || minus != null) {
|
||||
var p = plus == null ? 0 : Number(plus);
|
||||
var m = minus == null ? 0 : Number(minus);
|
||||
if (isFinite(p) && isFinite(m) && (p > 0 || m > 0 || row.branch !== "main")) {
|
||||
parts.push(
|
||||
'<span class="diff-plus">+' +
|
||||
esc(String(p)) +
|
||||
'</span> <span class="diff-minus">−' +
|
||||
esc(String(m)) +
|
||||
"</span>"
|
||||
);
|
||||
}
|
||||
}
|
||||
return parts.join(" · ");
|
||||
}
|
||||
|
||||
function rowTitle(row, buildSec) {
|
||||
var tip = row.tip || "";
|
||||
var tipTime = row.tip_time || "";
|
||||
var plus = row.vs_main_plus;
|
||||
var minus = row.vs_main_minus;
|
||||
var vs = "";
|
||||
if (plus != null || minus != null) {
|
||||
vs =
|
||||
" vs main +" +
|
||||
String(plus == null ? 0 : plus) +
|
||||
"/−" +
|
||||
String(minus == null ? 0 : minus);
|
||||
}
|
||||
return (
|
||||
"Switch build to " +
|
||||
(row.branch || "") +
|
||||
|
|
@ -61,7 +87,8 @@
|
|||
(tipTime ? " tip=" + tipTime : "") +
|
||||
(buildSec != null && buildSec !== ""
|
||||
? " build=" + fmtBuildSec(buildSec)
|
||||
: "")
|
||||
: "") +
|
||||
vs
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -125,15 +152,15 @@
|
|||
var a = rows[i];
|
||||
if (a.getAttribute("data-edition") !== (row.edition || "")) return false;
|
||||
if (a.getAttribute("data-branch") !== (row.branch || "")) return false;
|
||||
var bits = metaBits(row);
|
||||
var tipHtml = metaHtml(row);
|
||||
var tipEl = a.querySelector(".tip");
|
||||
var wantTip = bits.length ? " " + bits.join(" · ") : "";
|
||||
var wantTip = tipHtml ? " " + tipHtml : "";
|
||||
if (tipEl) {
|
||||
if (tipEl.textContent !== wantTip) tipEl.textContent = wantTip;
|
||||
if (tipEl.innerHTML !== wantTip) tipEl.innerHTML = wantTip;
|
||||
} else if (wantTip) {
|
||||
tipEl = document.createElement("span");
|
||||
tipEl.className = "tip";
|
||||
tipEl.textContent = wantTip;
|
||||
tipEl.innerHTML = wantTip;
|
||||
a.appendChild(tipEl);
|
||||
}
|
||||
var defEl = a.querySelector(".cascade-default");
|
||||
|
|
@ -189,15 +216,13 @@
|
|||
var tipTime = row.tip_time || "";
|
||||
var buildSec = row.build_seconds;
|
||||
var isDefault = branch === defaultBranch;
|
||||
var bits = metaBits(row);
|
||||
var tipHtml = metaHtml(row);
|
||||
var label =
|
||||
esc(prefix) +
|
||||
"<code>" +
|
||||
esc(branch) +
|
||||
"</code>" +
|
||||
(bits.length
|
||||
? '<span class="tip"> ' + esc(bits.join(" · ")) + "</span>"
|
||||
: "") +
|
||||
(tipHtml ? '<span class="tip"> ' + tipHtml + "</span>" : "") +
|
||||
(isDefault ? '<span class="cascade-default"> · default</span>' : "");
|
||||
html.push(
|
||||
'<a class="cascade-tree-row" role="treeitem" href="/_cascade/use/' +
|
||||
|
|
|
|||
|
|
@ -76,3 +76,12 @@ a.cascade-tree-row.is-active:hover {
|
|||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.cascade-bar .diff-plus {
|
||||
color: #3fb950;
|
||||
font-weight: 600;
|
||||
}
|
||||
.cascade-bar .diff-minus {
|
||||
color: #f85149;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,23 +37,49 @@
|
|||
return Math.round(n) + "s";
|
||||
}
|
||||
|
||||
function metaBits(row) {
|
||||
function metaHtml(row) {
|
||||
var tip = row.tip || "";
|
||||
var tipTime = row.tip_time || "";
|
||||
var builtAt = row.built_at || "";
|
||||
var buildSec = row.build_seconds;
|
||||
var bits = [];
|
||||
if (tip) bits.push("@" + tip);
|
||||
if (tipTime) bits.push("tip " + shortTime(tipTime));
|
||||
var parts = [];
|
||||
if (tip) parts.push(esc("@" + tip));
|
||||
if (tipTime) parts.push(esc("tip " + shortTime(tipTime)));
|
||||
if (buildSec != null && buildSec !== "")
|
||||
bits.push("build " + fmtBuildSec(buildSec));
|
||||
if (builtAt) bits.push("at " + shortTime(builtAt));
|
||||
return bits;
|
||||
parts.push(esc("build " + fmtBuildSec(buildSec)));
|
||||
if (builtAt) parts.push(esc("at " + shortTime(builtAt)));
|
||||
// vs main: +grün / −rot (omit on main / when both zero absent)
|
||||
var plus = row.vs_main_plus;
|
||||
var minus = row.vs_main_minus;
|
||||
if (plus != null || minus != null) {
|
||||
var p = plus == null ? 0 : Number(plus);
|
||||
var m = minus == null ? 0 : Number(minus);
|
||||
if (isFinite(p) && isFinite(m) && (p > 0 || m > 0 || row.branch !== "main")) {
|
||||
parts.push(
|
||||
'<span class="diff-plus">+' +
|
||||
esc(String(p)) +
|
||||
'</span> <span class="diff-minus">−' +
|
||||
esc(String(m)) +
|
||||
"</span>"
|
||||
);
|
||||
}
|
||||
}
|
||||
return parts.join(" · ");
|
||||
}
|
||||
|
||||
function rowTitle(row, buildSec) {
|
||||
var tip = row.tip || "";
|
||||
var tipTime = row.tip_time || "";
|
||||
var plus = row.vs_main_plus;
|
||||
var minus = row.vs_main_minus;
|
||||
var vs = "";
|
||||
if (plus != null || minus != null) {
|
||||
vs =
|
||||
" vs main +" +
|
||||
String(plus == null ? 0 : plus) +
|
||||
"/−" +
|
||||
String(minus == null ? 0 : minus);
|
||||
}
|
||||
return (
|
||||
"Switch build to " +
|
||||
(row.branch || "") +
|
||||
|
|
@ -61,7 +87,8 @@
|
|||
(tipTime ? " tip=" + tipTime : "") +
|
||||
(buildSec != null && buildSec !== ""
|
||||
? " build=" + fmtBuildSec(buildSec)
|
||||
: "")
|
||||
: "") +
|
||||
vs
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -125,15 +152,15 @@
|
|||
var a = rows[i];
|
||||
if (a.getAttribute("data-edition") !== (row.edition || "")) return false;
|
||||
if (a.getAttribute("data-branch") !== (row.branch || "")) return false;
|
||||
var bits = metaBits(row);
|
||||
var tipHtml = metaHtml(row);
|
||||
var tipEl = a.querySelector(".tip");
|
||||
var wantTip = bits.length ? " " + bits.join(" · ") : "";
|
||||
var wantTip = tipHtml ? " " + tipHtml : "";
|
||||
if (tipEl) {
|
||||
if (tipEl.textContent !== wantTip) tipEl.textContent = wantTip;
|
||||
if (tipEl.innerHTML !== wantTip) tipEl.innerHTML = wantTip;
|
||||
} else if (wantTip) {
|
||||
tipEl = document.createElement("span");
|
||||
tipEl.className = "tip";
|
||||
tipEl.textContent = wantTip;
|
||||
tipEl.innerHTML = wantTip;
|
||||
a.appendChild(tipEl);
|
||||
}
|
||||
var defEl = a.querySelector(".cascade-default");
|
||||
|
|
@ -189,15 +216,13 @@
|
|||
var tipTime = row.tip_time || "";
|
||||
var buildSec = row.build_seconds;
|
||||
var isDefault = branch === defaultBranch;
|
||||
var bits = metaBits(row);
|
||||
var tipHtml = metaHtml(row);
|
||||
var label =
|
||||
esc(prefix) +
|
||||
"<code>" +
|
||||
esc(branch) +
|
||||
"</code>" +
|
||||
(bits.length
|
||||
? '<span class="tip"> ' + esc(bits.join(" · ")) + "</span>"
|
||||
: "") +
|
||||
(tipHtml ? '<span class="tip"> ' + tipHtml + "</span>" : "") +
|
||||
(isDefault ? '<span class="cascade-default"> · default</span>' : "");
|
||||
html.push(
|
||||
'<a class="cascade-tree-row" role="treeitem" href="/_cascade/use/' +
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue