1414import sys
1515import time
1616import urllib .error
17+ import urllib .parse
1718import urllib .request
1819from pathlib import Path
1920from 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
548551def 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
0 commit comments