') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); emrg: write install-info cache once per process in resolve_git_gh by argszero · Pull Request #717 · argszero/emrg · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (746) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (747) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (229: 44 daemon_client + 19 conn-manager + 22 app-commands + 107 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
CI: `uv run pytest` + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
14 changes: 13 additions & 1 deletion emrg/server/git_utils.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,12 @@
# restarted without PATH git and cycles were silently skipped for 18 min).
_GIT_MISSING_WARNED = False

# In-process memo of the last (git, gh) paths written to install-info.json —
# resolve_git_gh() is called on every git_cmd(), and each call used to do an
# atomic tmp+os.replace disk write even when nothing changed. Tool paths are
# stable within a process lifetime, so write once and skip the rest.
_LAST_CACHED_PATHS: tuple[str, str] | None = None


# ── Non-interactive subprocess environment (rant 2026-08-07T10:17:27) ──
#
Expand DownExpand Up@@ -228,6 +234,7 @@ def resolve_git_gh() -> tuple[str, str]:
Returns (git_path, gh_path). Missing executables yield '' (callers decide
how to degrade).
"""
global _LAST_CACHED_PATHS
git = _cached_tool_path("git")
gh = _cached_tool_path("gh")
if git and Path(git).exists():
Expand All@@ -240,7 +247,12 @@ def resolve_git_gh() -> tuple[str, str]:
gh = _tool_in_install("gh") or (shutil.which("gh") or "")

if git:
_cache_tool_paths(git, gh)
# Write the cache only when the resolved pair changed since the last
# write (or nothing cached yet) — avoids an atomic install-info.json
# write on every git_cmd() call. Paths are stable per process.
if _LAST_CACHED_PATHS != (git, gh):
_cache_tool_paths(git, gh)
_LAST_CACHED_PATHS = (git, gh)
else:
# git is the failure mode that silently disables evolution (2026-08-12
# incident) — warn regardless of whether gh resolved. Also skip the
Expand Down
24 changes: 24 additions & 0 deletions tests/test_git_utils.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -349,3 +349,27 @@ def fake_cache(git: str, gh: str) -> None:
warns = [r for r in caplog.records if r.levelno >= logging.WARNING]
assert len(warns) == 1
assert "git executable not found" in warns[0].getMessage()


def test_resolve_git_gh_writes_cache_once_per_process(monkeypatch):
"""Cache write happens once per process, not on every git_cmd() call.

resolve_git_gh() runs on every git_cmd(); before this fix each call did an
atomic install-info.json write even when the resolved paths were unchanged.
Tool paths are stable within a process lifetime — write once, skip the rest.
"""
from emrg.server import git_utils as gu

calls = []
monkeypatch.setattr(gu, "_cached_tool_path", lambda tool: None)
monkeypatch.setattr(gu, "_tool_in_install", lambda tool: None)
monkeypatch.setattr(gu.shutil, "which", lambda tool: "/usr/bin/git" if tool == "git" else "/usr/bin/gh")
monkeypatch.setattr(gu, "_cache_tool_paths", lambda g, h: calls.append((g, h)))
monkeypatch.setattr(gu, "_LAST_CACHED_PATHS", None)

gu.resolve_git_gh()
gu.resolve_git_gh() # unchanged paths → no second write
gu.resolve_git_gh()

assert len(calls) == 1, f"expected exactly 1 cache write, got {len(calls)}: {calls}"
assert calls[0] == ("/usr/bin/git", "/usr/bin/gh")
Loading