package git import ( "bufio" "fmt" "os" "path/filepath" "regexp" "strings" ) // DetectRepo attempts to detect the repository owner/name from the current git directory. // It reads .git/config and parses the origin remote URL. func DetectRepo() (owner, name string, err error) { // Find .git/config file gitConfigPath, err := findGitConfig() if err != nil { return "", "", err } // Parse .git/config remoteURL, err := parseGitConfig(gitConfigPath) if err != nil { return "", "", err } // Extract owner/name from URL return parseRemoteURL(remoteURL) } // findGitConfig searches for .git/config starting from the current directory func findGitConfig() (string, error) { cwd, err := os.Getwd() if err != nil { return "", fmt.Errorf("failed to get current directory: %w", err) } dir := cwd for { gitConfigPath := filepath.Join(dir, ".git", "config") if _, err := os.Stat(gitConfigPath); err == nil { return gitConfigPath, nil } parent := filepath.Dir(dir) if parent == dir { // Reached root directory return "", fmt.Errorf("not in a git repository (or any parent up to mount point)") } dir = parent } } // parseGitConfig reads .git/config and extracts the origin remote URL func parseGitConfig(configPath string) (string, error) { file, err := os.Open(configPath) if err != nil { return "", fmt.Errorf("failed to open git config: %w", err) } defer func() { _ = file.Close() }() scanner := bufio.NewScanner(file) inOriginSection := false for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) // Check if we're entering the [remote "origin"] section if strings.HasPrefix(line, "[remote") && strings.Contains(line, "origin") { inOriginSection = true continue } // Check if we're leaving the section if inOriginSection && strings.HasPrefix(line, "[") { inOriginSection = false continue } // Extract URL from the origin section if inOriginSection && strings.HasPrefix(line, "url") { parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { return strings.TrimSpace(parts[1]), nil } } } if err := scanner.Err(); err != nil { return "", fmt.Errorf("error reading git config: %w", err) } return "", fmt.Errorf("no origin remote found in git config") } // parseRemoteURL extracts owner/name from various git URL formats: // - https://codeberg.org/owner/name.git // - git@codeberg.org:owner/name.git // - ssh://git@codeberg.org/owner/name.git func parseRemoteURL(url string) (owner, name string, err error) { url = strings.TrimSpace(url) // Remove .git suffix url = strings.TrimSuffix(url, ".git") // Pattern for HTTPS URLs: https://host/owner/name httpsRegex := regexp.MustCompile(`https?://[^/]+/([^/]+)/([^/]+)`) if matches := httpsRegex.FindStringSubmatch(url); len(matches) == 3 { return matches[1], matches[2], nil } // Pattern for SSH URLs: git@host:owner/name sshRegex := regexp.MustCompile(`git@[^:]+:([^/]+)/(.+)`) if matches := sshRegex.FindStringSubmatch(url); len(matches) == 3 { return matches[1], matches[2], nil } // Pattern for SSH URLs with protocol: ssh://git@host/owner/name sshProtocolRegex := regexp.MustCompile(`ssh://git@[^/]+/([^/]+)/(.+)`) if matches := sshProtocolRegex.FindStringSubmatch(url); len(matches) == 3 { return matches[1], matches[2], nil } return "", "", fmt.Errorf("unable to parse repository from URL: %s", url) }