blob: afedd6534767c9b88c00c3419921ea8d26229748 [file] [log] [blame]
package git
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"sync"
)
// CloneManager manages separate Git repository clones for each agent
// This eliminates Git concurrency issues by giving each agent its own working directory
type CloneManager struct {
baseRepoURL string
workspacePath string
agentClones map[string]string // agent name -> clone path
mu sync.RWMutex
}
// NewCloneManager creates a new CloneManager
func NewCloneManager(baseRepoURL, workspacePath string) *CloneManager {
return &CloneManager{
baseRepoURL: baseRepoURL,
workspacePath: workspacePath,
agentClones: make(map[string]string),
}
}
// GetAgentClonePath returns the Git clone path for a specific agent
// Creates the clone if it doesn't exist
func (cm *CloneManager) GetAgentClonePath(agentName string) (string, error) {
cm.mu.Lock()
defer cm.mu.Unlock()
// Check if clone already exists
if clonePath, exists := cm.agentClones[agentName]; exists {
// Verify the clone still exists on disk
if _, err := os.Stat(clonePath); err == nil {
return clonePath, nil
}
// Remove stale entry if directory doesn't exist
delete(cm.agentClones, agentName)
}
// Create new clone for the agent
clonePath := filepath.Join(cm.workspacePath, fmt.Sprintf("agent-%s", agentName))
// Ensure workspace directory exists
if err := os.MkdirAll(cm.workspacePath, 0755); err != nil {
return "", fmt.Errorf("failed to create workspace directory: %w", err)
}
// Remove existing clone directory if it exists
if err := os.RemoveAll(clonePath); err != nil {
return "", fmt.Errorf("failed to remove existing clone: %w", err)
}
// Clone the repository
if err := cm.cloneRepository(clonePath); err != nil {
return "", fmt.Errorf("failed to clone repository for agent %s: %w", agentName, err)
}
// Store the clone path
cm.agentClones[agentName] = clonePath
return clonePath, nil
}
// cloneRepository performs the actual Git clone operation
func (cm *CloneManager) cloneRepository(clonePath string) error {
ctx := context.Background()
// Clone the repository
cmd := exec.CommandContext(ctx, "git", "clone", cm.baseRepoURL, clonePath)
if err := cmd.Run(); err != nil {
return fmt.Errorf("git clone failed: %w", err)
}
return nil
}
// RefreshAgentClone pulls the latest changes for an agent's clone
func (cm *CloneManager) RefreshAgentClone(agentName string) error {
cm.mu.RLock()
clonePath, exists := cm.agentClones[agentName]
cm.mu.RUnlock()
if !exists {
return fmt.Errorf("no clone exists for agent %s", agentName)
}
ctx := context.Background()
// Change to clone directory and pull latest changes
cmd := exec.CommandContext(ctx, "git", "-C", clonePath, "pull", "origin")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to pull latest changes for agent %s: %w", agentName, err)
}
return nil
}
// CleanupAgentClone removes the clone directory for an agent
func (cm *CloneManager) CleanupAgentClone(agentName string) error {
cm.mu.Lock()
defer cm.mu.Unlock()
clonePath, exists := cm.agentClones[agentName]
if !exists {
return nil // Already cleaned up
}
// Remove the clone directory
if err := os.RemoveAll(clonePath); err != nil {
return fmt.Errorf("failed to remove clone for agent %s: %w", agentName, err)
}
// Remove from tracking
delete(cm.agentClones, agentName)
return nil
}
// CleanupAllClones removes all agent clone directories
func (cm *CloneManager) CleanupAllClones() error {
cm.mu.Lock()
defer cm.mu.Unlock()
var errors []error
for agentName, clonePath := range cm.agentClones {
if err := os.RemoveAll(clonePath); err != nil {
errors = append(errors, fmt.Errorf("failed to remove clone for agent %s: %w", agentName, err))
}
}
// Clear all tracked clones
cm.agentClones = make(map[string]string)
if len(errors) > 0 {
return fmt.Errorf("cleanup errors: %v", errors)
}
return nil
}
// GetAllAgentClones returns a map of all agent clones
func (cm *CloneManager) GetAllAgentClones() map[string]string {
cm.mu.RLock()
defer cm.mu.RUnlock()
// Return a copy to avoid race conditions
result := make(map[string]string)
for agent, path := range cm.agentClones {
result[agent] = path
}
return result
}