Skip to content

Commit 820a752

Browse files
author
Jieli
committed
fix: upload Claude Code repo URLs
1 parent 3b57598 commit 820a752

2 files changed

Lines changed: 97 additions & 19 deletions

File tree

plugins/claude-code/scripts/sync.py

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import sys
1515
import time
1616
import urllib.error
17+
import urllib.parse
1718
import urllib.request
1819
from pathlib import Path
1920
from typing import Any, Callable, Mapping
@@ -107,9 +108,11 @@ def build_payload_from_hook(hook_data: dict[str, Any], base_url: str | None = No
107108
}
108109
if resolved_model and resolved_model != display_model:
109110
thread_payload["resolved_model"] = resolved_model
111+
repo_info = repo_info_from_cwd(cwd)
110112
return {
111113
"provider": PROVIDER,
112-
"repo": repo_from_cwd(cwd),
114+
"repo": repo_info["repo"],
115+
"repo_url": repo_info["repo_url"],
113116
"branch": branch,
114117
"source_url": source_url,
115118
"labels": [],
@@ -546,6 +549,10 @@ def title_from_messages(messages: list[dict[str, Any]]) -> str:
546549

547550

548551
def repo_from_cwd(cwd: str) -> str:
552+
return repo_info_from_cwd(cwd)["repo"]
553+
554+
555+
def repo_info_from_cwd(cwd: str) -> dict[str, str]:
549556
try:
550557
result = subprocess.run(
551558
["git", "config", "--get", "remote.origin.url"],
@@ -557,34 +564,64 @@ def repo_from_cwd(cwd: str) -> str:
557564
timeout=2,
558565
)
559566
except (OSError, subprocess.TimeoutExpired):
560-
return ""
567+
return {"repo": "", "repo_url": ""}
561568
if result.returncode != 0:
562-
return ""
563-
return repo_from_repository_url(result.stdout)
569+
return {"repo": "", "repo_url": ""}
570+
return repo_info_from_remote_url(result.stdout)
564571

565572

566-
def repo_from_repository_url(raw: str) -> str:
573+
def repo_info_from_remote_url(raw: str) -> dict[str, str]:
567574
value = raw.strip().rstrip("/")
568575
if value.endswith(".git"):
569576
value = value[:-4]
570-
prefixes = (
571-
"https://github.com/",
572-
"http://github.com/",
573-
"git@github.com:",
574-
"ssh://git@github.com/",
575-
)
576-
for prefix in prefixes:
577-
if value.startswith(prefix):
578-
return valid_github_repo_slug(value[len(prefix) :])
579-
return ""
577+
if not value:
578+
return {"repo": "", "repo_url": ""}
579+
if "://" not in value:
580+
return repo_info_from_scp_remote_url(value)
581+
parsed = urllib.parse.urlsplit(value)
582+
if parsed.scheme not in {"http", "https", "ssh", "git"}:
583+
return {"repo": "", "repo_url": ""}
584+
host = parsed.hostname or ""
585+
repo = valid_repo_path(parsed.path.strip("/"))
586+
if not host or not repo:
587+
return {"repo": "", "repo_url": ""}
588+
if parsed.scheme in {"http", "https"}:
589+
netloc = host_with_port(parsed)
590+
repo_url = urllib.parse.urlunsplit((parsed.scheme, netloc, f"/{repo}", "", ""))
591+
else:
592+
repo_url = f"https://{host}/{repo}"
593+
return {"repo": repo, "repo_url": repo_url}
594+
595+
596+
def repo_info_from_scp_remote_url(value: str) -> dict[str, str]:
597+
match = re.fullmatch(r"(?:[^@/:]+@)?([^:/]+):(.+)", value)
598+
if match is None:
599+
return {"repo": "", "repo_url": ""}
600+
host = match.group(1)
601+
repo = valid_repo_path(match.group(2))
602+
if not host or not repo:
603+
return {"repo": "", "repo_url": ""}
604+
return {"repo": repo, "repo_url": f"https://{host}/{repo}"}
605+
606+
607+
def host_with_port(parsed: urllib.parse.SplitResult) -> str:
608+
host = parsed.hostname or ""
609+
try:
610+
port = parsed.port
611+
except ValueError:
612+
port = None
613+
return f"{host}:{port}" if port else host
580614

581615

582-
def valid_github_repo_slug(value: str) -> str:
583-
parts = value.split("/")
584-
if len(parts) != 2:
616+
def valid_repo_path(value: str) -> str:
617+
path = value.strip("/")
618+
if path.endswith(".git"):
619+
path = path[:-4]
620+
parts = [part for part in path.split("/") if part]
621+
if len(parts) < 2:
585622
return ""
586623
if all(re.fullmatch(r"[A-Za-z0-9_.-]+", part) for part in parts):
587-
return value
624+
return "/".join(parts)
588625
return ""
589626

590627

plugins/claude-code/tests/test_plugin_scripts.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,46 @@ def test_build_payload_uses_git_remote_repo_slug(self):
292292
)
293293

294294
self.assertEqual(payload["repo"], "jieliapp/plugins")
295+
self.assertEqual(payload["repo_url"], "https://github.com/jieliapp/plugins")
296+
297+
def test_build_payload_uses_self_hosted_git_remote_repo_url(self):
298+
from sync import build_payload_from_hook
299+
300+
with tempfile.TemporaryDirectory() as tmpdir:
301+
repo = Path(tmpdir) / "work" / "project"
302+
repo.mkdir(parents=True)
303+
subprocess.run(["git", "init"], cwd=repo, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
304+
subprocess.run(
305+
["git", "remote", "add", "origin", "git@git.example.test:team/project.git"],
306+
cwd=repo,
307+
check=True,
308+
stdout=subprocess.DEVNULL,
309+
stderr=subprocess.DEVNULL,
310+
)
311+
transcript = Path(tmpdir) / "session.jsonl"
312+
transcript.write_text(
313+
json.dumps(
314+
{
315+
"type": "user",
316+
"uuid": "u-gitea",
317+
"sessionId": "cc-gitea",
318+
"cwd": str(repo),
319+
"gitBranch": "plugin/sync",
320+
"timestamp": "2026-06-06T09:00:00.000Z",
321+
"message": {"role": "user", "content": "sync this self-hosted repo"},
322+
}
323+
)
324+
+ "\n",
325+
encoding="utf-8",
326+
)
327+
328+
payload = build_payload_from_hook(
329+
{"session_id": "cc-gitea", "transcript_path": str(transcript)},
330+
base_url="https://jieli.example.test",
331+
)
332+
333+
self.assertEqual(payload["repo"], "team/project")
334+
self.assertEqual(payload.get("repo_url"), "https://git.example.test/team/project")
295335

296336
def test_build_payload_leaves_repo_empty_without_github_remote(self):
297337
from sync import build_payload_from_hook
@@ -322,6 +362,7 @@ def test_build_payload_leaves_repo_empty_without_github_remote(self):
322362
)
323363

324364
self.assertEqual(payload["repo"], "")
365+
self.assertEqual(payload["repo_url"], "")
325366

326367
def test_build_payload_uploads_structured_image_blocks(self):
327368
from sync import build_payload_from_hook

0 commit comments

Comments
 (0)