Files
go-github-sync/pkg/git/ops.go
2025-05-09 23:12:36 -04:00

141 lines
4.2 KiB
Go

// Package git provides Git-related operations and validation.
package git
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"i2pgit.org/go-i2p/go-github-sync/pkg/config"
"i2pgit.org/go-i2p/go-github-sync/pkg/logger"
)
// Client provides Git repository validation and operations.
type Client struct {
log *logger.Logger
httpClient *http.Client
}
// NewClient creates a new Git client.
func NewClient(log *logger.Logger) *Client {
return &Client{
log: log,
httpClient: &http.Client{
Timeout: 10 * time.Second,
},
}
}
// ValidateRepos checks if both repositories are accessible.
func (c *Client) ValidateRepos(ctx context.Context, cfg *config.Config) error {
// Validate primary repository URL
if err := c.validateRepoURL(ctx, cfg.PrimaryRepo); err != nil {
return fmt.Errorf("invalid primary repository URL: %w", err)
}
// Validate GitHub repository URL format
if !strings.Contains(cfg.MirrorRepo, "github.com") {
return fmt.Errorf("mirror repository must be a GitHub repository URL")
}
// Extract owner and repo from GitHub URL
owner, repo, err := parseGitHubURL(cfg.MirrorRepo)
if err != nil {
return fmt.Errorf("failed to parse GitHub repository URL: %w", err)
}
c.log.Debug("Parsed GitHub repository", "owner", owner, "repo", repo)
return nil
}
// validateRepoURL checks if a Git repository URL is accessible.
func (c *Client) validateRepoURL(ctx context.Context, repoURL string) error {
// For HTTP/HTTPS URLs, try to access the repository
if strings.HasPrefix(repoURL, "http://") || strings.HasPrefix(repoURL, "https://") {
// For GitHub URLs, we can check info/refs
if strings.Contains(repoURL, "github.com") {
checkURL := ensureGitExtension(repoURL) + "/info/refs?service=git-upload-pack"
return c.checkEndpoint(ctx, checkURL)
}
// For other Git servers, just try a HEAD request on the base URL
return c.checkEndpoint(ctx, ensureGitExtension(repoURL))
}
// For SSH URLs, we can't easily validate, so just check the format
if strings.HasPrefix(repoURL, "git@") || strings.HasPrefix(repoURL, "ssh://") {
// Basic validation for SSH URLs
if !strings.Contains(repoURL, ":") && !strings.Contains(repoURL, "/") {
return fmt.Errorf("invalid SSH URL format")
}
c.log.Debug("SSH URL provided, cannot fully validate accessibility", "url", repoURL)
return nil
}
return fmt.Errorf("unsupported repository URL scheme")
}
// checkEndpoint makes a HEAD request to check if an endpoint is accessible.
func (c *Client) checkEndpoint(ctx context.Context, url string) error {
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
if err != nil {
return fmt.Errorf("failed to create HTTP request: %w", err)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("failed to access repository: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("repository returned error status: %s", resp.Status)
}
return nil
}
// parseGitHubURL extracts the owner and repository from a GitHub URL.
func parseGitHubURL(githubURL string) (string, string, error) {
// Clean the URL to ensure we have the correct format
cleanURL := ensureGitExtension(githubURL)
// Parse the URL
parsedURL, err := url.Parse(cleanURL)
if err != nil {
return "", "", fmt.Errorf("invalid URL: %w", err)
}
// Handle HTTP(S) URLs
if parsedURL.Scheme == "http" || parsedURL.Scheme == "https" {
pathParts := strings.Split(strings.TrimPrefix(parsedURL.Path, "/"), "/")
if len(pathParts) < 2 {
return "", "", fmt.Errorf("invalid GitHub repository path: %s", parsedURL.Path)
}
return pathParts[0], strings.TrimSuffix(pathParts[1], ".git"), nil
}
// Handle SSH URLs
if strings.HasPrefix(githubURL, "git@github.com:") {
path := strings.TrimPrefix(githubURL, "git@github.com:")
parts := strings.Split(path, "/")
if len(parts) < 2 {
return "", "", fmt.Errorf("invalid GitHub SSH URL format")
}
return parts[0], strings.TrimSuffix(parts[1], ".git"), nil
}
return "", "", fmt.Errorf("unsupported GitHub URL format")
}
// ensureGitExtension ensures the URL ends with .git for Git operations.
func ensureGitExtension(repoURL string) string {
if !strings.HasSuffix(repoURL, ".git") {
return repoURL + ".git"
}
return repoURL
}