package git import ( "fmt" "os/exec" "regexp" "strings" ) // Remote holds the parsed coordinates of a git remote. type Remote struct { Host string Owner string Repo string } var ( httpsRE = regexp.MustCompile(`^https?://(?:[^@]+@)?([^/:]+)(?::\d+)?/([^/]+)/([^/]+?)(?:\.git)?/?$`) sshRE = regexp.MustCompile(`^(?:ssh://)?(?:[^@]+@)?([^/:]+)[:/]([^/]+)/([^/]+?)(?:\.git)?/?$`) ) // ParseRemoteURL parses a git remote URL into its Host, Owner, and Repo parts. // Supports https, ssh (git@host:owner/repo), and ssh:// forms. func ParseRemoteURL(url string) (*Remote, error) { trimmed := strings.TrimSpace(url) if trimmed == "" { return nil, fmt.Errorf("empty remote URL") } if m := httpsRE.FindStringSubmatch(trimmed); m != nil { return &Remote{Host: m[1], Owner: m[2], Repo: m[3]}, nil } if m := sshRE.FindStringSubmatch(trimmed); m != nil { return &Remote{Host: m[1], Owner: m[2], Repo: m[3]}, nil } return nil, fmt.Errorf("unrecognized remote URL format: %q", trimmed) } // DetectRepo resolves the origin remote URL for the given workspace root // and parses it into a Remote. func DetectRepo(workspaceRoot string) (*Remote, error) { out, err := exec.Command("git", "-C", workspaceRoot, "remote", "get-url", "origin").Output() if err != nil { return nil, fmt.Errorf("git remote get-url origin: %w", err) } return ParseRemoteURL(strings.TrimSpace(string(out))) }