125 lines
3.3 KiB
Go
125 lines
3.3 KiB
Go
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)
|
|
}
|