mirror of
https://github.com/go-i2p/go-github-sync.git
synced 2025-07-01 05:00:10 -04:00
141 lines
4.2 KiB
Go
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
|
|
}
|