69 lines
2.5 KiB
Python
69 lines
2.5 KiB
Python
"""Background checkout refresh."""
|
|
|
|
import subprocess
|
|
import threading
|
|
|
|
|
|
def fetch_status(path, result, key):
|
|
"""Background worker: `git fetch` a local checkout and count how far
|
|
behind its upstream it is. Writes into result[key]; NEVER raises — any
|
|
failure is recorded as {"error": ...} so the main flow is never blocked."""
|
|
info = {"path": path}
|
|
try:
|
|
remotes = subprocess.run(
|
|
["git", "-C", path, "remote"],
|
|
capture_output=True, text=True,
|
|
).stdout.strip()
|
|
if not remotes:
|
|
info["no_upstream"] = True
|
|
result[key] = info
|
|
return
|
|
f = subprocess.run(
|
|
["git", "-C", path, "fetch", "--quiet", "origin"],
|
|
capture_output=True, text=True, timeout=300,
|
|
)
|
|
if f.returncode != 0:
|
|
lines = [ln for ln in f.stderr.strip().splitlines() if ln.strip()]
|
|
info["error"] = lines[-1] if lines else "git fetch failed"
|
|
else:
|
|
up = subprocess.run(
|
|
["git", "-C", path, "rev-parse", "--abbrev-ref",
|
|
"--symbolic-full-name", "@{upstream}"],
|
|
capture_output=True, text=True,
|
|
)
|
|
head = subprocess.run(
|
|
["git", "-C", path, "rev-parse", "--abbrev-ref", "HEAD"],
|
|
capture_output=True, text=True,
|
|
).stdout.strip()
|
|
if up.returncode != 0 or head in ("", "HEAD"):
|
|
info["no_upstream"] = True
|
|
else:
|
|
cnt = subprocess.run(
|
|
["git", "-C", path, "rev-list", "--count",
|
|
f"HEAD..{up.stdout.strip()}"],
|
|
capture_output=True, text=True,
|
|
)
|
|
if cnt.returncode == 0 and cnt.stdout.strip().isdigit():
|
|
info["behind"] = int(cnt.stdout.strip())
|
|
else:
|
|
info["no_upstream"] = True
|
|
except (subprocess.SubprocessError, OSError, ValueError) as e:
|
|
# ponytail: broad catch is the point — refresh must never be fatal
|
|
info["error"] = str(e)
|
|
result[key] = info
|
|
|
|
|
|
def start_refresh(framework_paths):
|
|
"""Spawn daemon threads to refresh local checkouts concurrently.
|
|
|
|
Returns (results_dict, threads); results are filled in as workers finish.
|
|
"""
|
|
results = {}
|
|
threads = []
|
|
for key, path in framework_paths.items():
|
|
t = threading.Thread(target=fetch_status, args=(path, results, key),
|
|
daemon=True)
|
|
t.start()
|
|
threads.append(t)
|
|
return results, threads
|