Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/code/claude_docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ func NewClaudeDocker(workspace *models.Workspace, cfg *config.Config) (Code, err
if err != nil {
return nil, fmt.Errorf("failed to create MCP config: %w", err)
}
// Set MCP config path in workspace for tracking and cleanup
workspace.MCPConfigPath = mcpConfigPath
log.Infof("MCP config file created at: %s", mcpConfigPath)

// Check if corresponding container is already running
Expand Down
22 changes: 20 additions & 2 deletions internal/code/mcp_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"

"github.com/qiniu/codeagent/internal/config"
Expand Down Expand Up @@ -129,8 +130,24 @@ func (g *MCPConfigGenerator) CreateTempConfig() (string, error) {
return "", err
}

// 创建临时文件在/tmp目录中
tempFile, err := os.CreateTemp(g.config.Workspace.BaseDir, "codeagent-mcp-*.json")
// 创建MCP配置文件在workspace目录中,与代码仓和session目录保持一致
var mcpConfigDir string
if g.workspace.SessionPath != "" {
// 如果有session目录,将MCP配置放在session目录的同级
mcpConfigDir = filepath.Dir(g.workspace.SessionPath)
} else {
// 如果没有session目录,将MCP配置放在代码仓的同级
mcpConfigDir = filepath.Dir(g.workspace.Path)
}

// 确保目录存在
if err := os.MkdirAll(mcpConfigDir, 0755); err != nil {
return "", fmt.Errorf("failed to create MCP config directory: %w", err)
}

// 创建临时文件在workspace相关目录中
// TODO(CarlJi): mcp config file name should be align with the workspace name
tempFile, err := os.CreateTemp(mcpConfigDir, "codeagent-mcp-*.json")
if err != nil {
return "", fmt.Errorf("failed to create temp file: %w", err)
}
Expand All @@ -146,5 +163,6 @@ func (g *MCPConfigGenerator) CreateTempConfig() (string, error) {
return "", err
}

log.Infof("Created MCP config file: %s", tempFile.Name())
return tempFile.Name(), nil
}
80 changes: 80 additions & 0 deletions internal/workspace/git_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type GitService interface {
CreateAndCheckoutBranch(repoPath, branchName string) error
CheckoutBranch(repoPath, branchName string) error
CreateTrackingBranch(repoPath, branchName string) error
FetchAndCheckoutPR(repoPath string, prNumber int) error
}

type gitService struct{}
Expand All @@ -46,18 +47,25 @@ func (g *gitService) CloneRepository(repoURL, clonePath, branch string, createNe
if createNewBranch {
// Clone the default branch first, then create new branch
cmd = exec.Command("git", "clone", "--depth", "50", repoURL, clonePath)
log.Infof("Executing Git command: %s", cmd.String())
} else {
// Try to clone specific branch directly
cmd = exec.Command("git", "clone", "--depth", "50", "--branch", branch, repoURL, clonePath)
log.Infof("Executing Git command: %s", cmd.String())
}

output, err := cmd.CombinedOutput()
if err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", cmd.String(), string(output), err)
if !createNewBranch {
// If direct branch clone failed, try cloning default branch first
log.Warnf("Failed to clone specific branch %s directly, cloning default branch: %v", branch, err)
cmd = exec.Command("git", "clone", "--depth", "50", repoURL, clonePath)
log.Infof("Executing fallback Git command: %s", cmd.String())
output, err = cmd.CombinedOutput()
if err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", cmd.String(), string(output), err)
}
}
if err != nil {
return GitError("clone", clonePath, fmt.Errorf("%s: %w", string(output), err))
Expand Down Expand Up @@ -103,8 +111,10 @@ func (g *gitService) CloneRepository(repoURL, clonePath, branch string, createNe
func (g *gitService) GetRemoteURL(repoPath string) (string, error) {
cmd := exec.Command("git", "remote", "get-url", "origin")
cmd.Dir = repoPath
log.Infof("Executing Git command: %s", cmd.String())
output, err := cmd.Output()
if err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", cmd.String(), string(output), err)
return "", GitError("get_remote_url", repoPath, err)
}
return strings.TrimSpace(string(output)), nil
Expand All @@ -114,8 +124,10 @@ func (g *gitService) GetRemoteURL(repoPath string) (string, error) {
func (g *gitService) GetCurrentBranch(repoPath string) (string, error) {
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
cmd.Dir = repoPath
log.Infof("Executing Git command: %s", cmd.String())
output, err := cmd.Output()
if err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", cmd.String(), string(output), err)
return "", GitError("get_current_branch", repoPath, err)
}
return strings.TrimSpace(string(output)), nil
Expand All @@ -125,8 +137,10 @@ func (g *gitService) GetCurrentBranch(repoPath string) (string, error) {
func (g *gitService) GetCurrentCommit(repoPath string) (string, error) {
cmd := exec.Command("git", "rev-parse", "HEAD")
cmd.Dir = repoPath
log.Infof("Executing Git command: %s", cmd.String())
output, err := cmd.Output()
if err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", cmd.String(), string(output), err)
return "", fmt.Errorf("failed to get current commit: %w", err)
}
return strings.TrimSpace(string(output)), nil
Expand All @@ -136,8 +150,10 @@ func (g *gitService) GetCurrentCommit(repoPath string) (string, error) {
func (g *gitService) GetBranchCommit(repoPath, branch string) (string, error) {
cmd := exec.Command("git", "rev-parse", fmt.Sprintf("origin/%s", branch))
cmd.Dir = repoPath
log.Infof("Executing Git command: %s", cmd.String())
output, err := cmd.Output()
if err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", cmd.String(), string(output), err)
return "", fmt.Errorf("failed to get branch commit for %s: %w", branch, err)
}
return strings.TrimSpace(string(output)), nil
Expand Down Expand Up @@ -183,7 +199,9 @@ func (g *gitService) ValidateBranch(repoPath, expectedBranch string) bool {
func (g *gitService) ConfigureSafeDirectory(repoPath string) error {
cmd := exec.Command("git", "config", "--local", "--add", "safe.directory", repoPath)
cmd.Dir = repoPath
log.Infof("Executing Git command: %s", cmd.String())
if output, err := cmd.CombinedOutput(); err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", cmd.String(), string(output), err)
return GitError("config_safe_directory", repoPath, fmt.Errorf("%s: %w", string(output), err))
}
return nil
Expand All @@ -193,7 +211,9 @@ func (g *gitService) ConfigureSafeDirectory(repoPath string) error {
func (g *gitService) ConfigurePullStrategy(repoPath string) error {
cmd := exec.Command("git", "config", "--local", "pull.rebase", "true")
cmd.Dir = repoPath
log.Infof("Executing Git command: %s", cmd.String())
if output, err := cmd.CombinedOutput(); err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", cmd.String(), string(output), err)
return fmt.Errorf("failed to configure pull strategy: %w, output: %s", err, string(output))
}
return nil
Expand All @@ -203,7 +223,9 @@ func (g *gitService) ConfigurePullStrategy(repoPath string) error {
func (g *gitService) CreateAndCheckoutBranch(repoPath, branchName string) error {
cmd := exec.Command("git", "checkout", "-b", branchName)
cmd.Dir = repoPath
log.Infof("Executing Git command: %s", cmd.String())
if output, err := cmd.CombinedOutput(); err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", cmd.String(), string(output), err)
return fmt.Errorf("failed to create new branch %s: %w, output: %s", branchName, err, string(output))
}
return nil
Expand All @@ -213,7 +235,9 @@ func (g *gitService) CreateAndCheckoutBranch(repoPath, branchName string) error
func (g *gitService) CheckoutBranch(repoPath, branchName string) error {
cmd := exec.Command("git", "checkout", branchName)
cmd.Dir = repoPath
log.Infof("Executing Git command: %s", cmd.String())
if output, err := cmd.CombinedOutput(); err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", cmd.String(), string(output), err)
return fmt.Errorf("failed to checkout branch %s: %w, output: %s", branchName, err, string(output))
}
return nil
Expand All @@ -223,8 +247,64 @@ func (g *gitService) CheckoutBranch(repoPath, branchName string) error {
func (g *gitService) CreateTrackingBranch(repoPath, branchName string) error {
cmd := exec.Command("git", "checkout", "-b", branchName, fmt.Sprintf("origin/%s", branchName))
cmd.Dir = repoPath
log.Infof("Executing Git command: %s", cmd.String())
if output, err := cmd.CombinedOutput(); err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", cmd.String(), string(output), err)
return fmt.Errorf("failed to create tracking branch %s: %w, output: %s", branchName, err, string(output))
}
return nil
}

// FetchAndCheckoutPR fetches and checks out PR content using GitHub's PR refs
// Always uses force mode to handle updates, force pushes, and ensure latest content
func (g *gitService) FetchAndCheckoutPR(repoPath string, prNumber int) error {
log.Infof("Fetching PR #%d content using GitHub PR refs (force mode)", prNumber)

prBranchName := fmt.Sprintf("pr-%d", prNumber)
currentBranch, err := g.GetCurrentBranch(repoPath)

// If we're already on the PR branch, use a lightweight in-place update
if err == nil && currentBranch == prBranchName {
log.Infof("Already on PR branch %s, performing in-place sync", prBranchName)

// Step 1: First fetch the PR content to FETCH_HEAD without creating/updating local branch
fetchCmd := exec.Command("git", "fetch", "origin", fmt.Sprintf("pull/%d/head", prNumber))
fetchCmd.Dir = repoPath
log.Infof("Executing Git command: %s", fetchCmd.String())
if output, err := fetchCmd.CombinedOutput(); err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", fetchCmd.String(), string(output), err)
return fmt.Errorf("failed to fetch PR #%d content: %w, output: %s", prNumber, err, string(output))
}

// Step 2: Reset current branch to the fetched content
resetCmd := exec.Command("git", "reset", "--hard", "FETCH_HEAD")
resetCmd.Dir = repoPath
log.Infof("Executing Git command: %s", resetCmd.String())
if output, err := resetCmd.CombinedOutput(); err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", resetCmd.String(), string(output), err)
return fmt.Errorf("failed to reset PR branch to latest content: %w, output: %s", err, string(output))
}

log.Infof("Successfully updated PR #%d branch in-place", prNumber)
return nil
}

fetchCmd := exec.Command("git", "fetch", "origin", fmt.Sprintf("pull/%d/head:%s", prNumber, prBranchName), "--force")
fetchCmd.Dir = repoPath
log.Infof("Executing Git command: %s", fetchCmd.String())
if output, err := fetchCmd.CombinedOutput(); err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", fetchCmd.String(), string(output), err)
return fmt.Errorf("failed to fetch PR #%d: %w, output: %s", prNumber, err, string(output))
}

checkoutCmd := exec.Command("git", "checkout", prBranchName)
checkoutCmd.Dir = repoPath
log.Infof("Executing Git command: %s", checkoutCmd.String())
if output, err := checkoutCmd.CombinedOutput(); err != nil {
log.Errorf("Git command failed: %s, output: %s, error: %v", checkoutCmd.String(), string(output), err)
return fmt.Errorf("failed to checkout PR branch %s: %w, output: %s", prBranchName, err, string(output))
}

log.Infof("Successfully fetched and checked out PR #%d content to branch: %s", prNumber, prBranchName)
return nil
}
Loading
Loading