Fix case repo sync + auto-create Gitea repos + add sync indicator
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m30s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m30s
- auto-sync-cases.sh: fix broken directory scan (was looking for
status subdirs that don't exist), fix env var word-splitting bug,
add safe.directory handling and error logging
- cases.py: auto-create Gitea repo on case_create, fix
documents/original → documents/originals naming mismatch
- app.py: add GET /api/cases/{case_number}/git-status endpoint
- web-ui: add SyncIndicator component in case header showing
sync status (synced/pending/no remote) with last commit time
- pyproject.toml: add httpx dependency
- CLAUDE.md: update Paperclip wakeup API docs
- settings page: switch tag input from Select to free-text with datalist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
59
web/app.py
59
web/app.py
@@ -1121,6 +1121,65 @@ async def api_case_create(req: CaseCreateRequest):
|
||||
return parsed
|
||||
|
||||
|
||||
@app.get("/api/cases/{case_number}/git-status")
|
||||
async def api_case_git_status(case_number: str):
|
||||
"""Git sync status for a case repo."""
|
||||
case_dir = config.find_case_dir(case_number)
|
||||
git_dir = case_dir / ".git"
|
||||
if not git_dir.exists():
|
||||
return {"synced": False, "error": "no_repo"}
|
||||
|
||||
env = {"PATH": os.environ.get("PATH", "/usr/bin:/bin"), "GIT_TERMINAL_PROMPT": "0"}
|
||||
|
||||
# Last commit info
|
||||
log = subprocess.run(
|
||||
["git", "log", "-1", "--format=%H%n%aI%n%s"],
|
||||
cwd=case_dir, capture_output=True, text=True, env=env,
|
||||
)
|
||||
lines = log.stdout.strip().splitlines() if log.returncode == 0 else []
|
||||
last_commit_time = lines[1] if len(lines) > 1 else None
|
||||
last_commit_msg = lines[2] if len(lines) > 2 else None
|
||||
|
||||
# Dirty files count
|
||||
status = subprocess.run(
|
||||
["git", "status", "--porcelain"],
|
||||
cwd=case_dir, capture_output=True, text=True, env=env,
|
||||
)
|
||||
dirty = len([l for l in status.stdout.splitlines() if l.strip()]) if status.returncode == 0 else 0
|
||||
|
||||
# Check if remote exists and if we're ahead
|
||||
has_remote = False
|
||||
ahead = 0
|
||||
remote_url = None
|
||||
remote_check = subprocess.run(
|
||||
["git", "remote", "get-url", "origin"],
|
||||
cwd=case_dir, capture_output=True, text=True, env=env,
|
||||
)
|
||||
if remote_check.returncode == 0:
|
||||
has_remote = True
|
||||
# Sanitize token from URL
|
||||
raw = remote_check.stdout.strip()
|
||||
remote_url = raw.split("@")[-1] if "@" in raw else raw
|
||||
|
||||
ahead_check = subprocess.run(
|
||||
["git", "rev-list", "HEAD", "--not", "--remotes", "--count"],
|
||||
cwd=case_dir, capture_output=True, text=True, env=env,
|
||||
)
|
||||
if ahead_check.returncode == 0:
|
||||
ahead = int(ahead_check.stdout.strip() or "0")
|
||||
|
||||
synced = has_remote and dirty == 0 and ahead == 0
|
||||
return {
|
||||
"synced": synced,
|
||||
"has_remote": has_remote,
|
||||
"remote_url": remote_url,
|
||||
"dirty_files": dirty,
|
||||
"commits_ahead": ahead,
|
||||
"last_commit_time": last_commit_time,
|
||||
"last_commit_msg": last_commit_msg,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/cases/{case_number}/details")
|
||||
async def api_case_get(case_number: str):
|
||||
"""Get full case details including documents."""
|
||||
|
||||
Reference in New Issue
Block a user