package git import ( "bytes" "fmt" "os" "os/exec" "strings" "time" ) // Service defines the interface for git operations type Service interface { CheckConnection(url string) error FetchContents(url string) error GetModifications(days int) (map[string]string, error) } type gitService struct { repoDir string } // NewGitService creates a new Git service func NewGitService() Service { return &gitService{ repoDir: "./cloned_repo", } } func prepareSSHKey() (string, bool) { b, err := os.ReadFile("/root/.ssh/id_rsa") if err != nil { return "", false } // Fix literal escaped newlines and CRLF issues that cause libcrypto errors content := strings.ReplaceAll(string(b), "\\n", "\n") content = strings.ReplaceAll(content, "\r", "") // Ensure there is a trailing newline if !strings.HasSuffix(content, "\n") { content += "\n" } tmpPath := "/tmp/id_rsa" if err := os.WriteFile(tmpPath, []byte(content), 0600); err != nil { return "", false } return tmpPath, true } func (s *gitService) CheckConnection(url string) error { cmd := exec.Command("git", "ls-remote", url) if keyPath, ok := prepareSSHKey(); ok { cmd.Env = append(os.Environ(), fmt.Sprintf("GIT_SSH_COMMAND=ssh -i %s -o StrictHostKeyChecking=no", keyPath)) } if err := cmd.Run(); err != nil { return fmt.Errorf("failed to check git connection: %w", err) } fmt.Println("Git connection checked successfully") return nil } func (s *gitService) FetchContents(url string) error { // Remove the repo directory if it already exists from a previous run fmt.Println("Removing repo directory") _ = os.RemoveAll(s.repoDir) fmt.Println("Cloning repository") var cmd *exec.Cmd if keyPath, ok := prepareSSHKey(); ok { cmd = exec.Command("git", "clone", url, s.repoDir) cmd.Env = append(os.Environ(), fmt.Sprintf("GIT_SSH_COMMAND=ssh -i %s -o StrictHostKeyChecking=no", keyPath)) } else { cmd = exec.Command("git", "clone", url, s.repoDir) } var stderr bytes.Buffer cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return fmt.Errorf("failed to fetch contents: %w, stderr: %s", err, stderr.String()) } fmt.Println("Repository cloned successfully") return nil } func (s *gitService) GetModifications(days int) (map[string]string, error) { mods := make(map[string]string) // Determine the commit to diff against (the latest commit *before* 'days' ago) since := time.Now().AddDate(0, 0, -days).Format(time.RFC3339) cmdBase := exec.Command("git", "rev-list", "-1", "--before", since, "HEAD") cmdBase.Dir = s.repoDir out, err := cmdBase.Output() baseCommit := strings.TrimSpace(string(out)) if err != nil || baseCommit == "" { // If there is no commit before 'days' ago, diff against the empty tree // (this gets all files created in the repository's entire history if it's newer than 'days') baseCommit = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" } // Get the list of modified files between the base commit and HEAD cmdFiles := exec.Command("git", "diff", "--name-only", baseCommit, "HEAD") cmdFiles.Dir = s.repoDir filesOut, err := cmdFiles.Output() if err != nil { return nil, fmt.Errorf("failed to get modified files: %w", err) } files := strings.Split(strings.TrimSpace(string(filesOut)), "\n") for _, file := range files { if file == "" { continue } // Filter only .md files if !strings.HasSuffix(file, ".md") { continue } // Remove first folder from file path file = strings.Join(strings.Split(file, "/")[1:], "/") // Get the specific diff for this file cmdDiff := exec.Command("git", "diff", baseCommit, "HEAD", "--", file) cmdDiff.Dir = s.repoDir diffOut, err := cmdDiff.Output() if err != nil { return nil, fmt.Errorf("failed to get diff for file %s: %w", file, err) } mods[file] = string(diffOut) } return mods, nil }