152 lines
4.2 KiB
Go
152 lines
4.2 KiB
Go
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
|
|
}
|
|
|
|
originalFile := file
|
|
|
|
// Remove first folder from file path
|
|
file = strings.Join(strings.Split(file, "/")[1:], "/")
|
|
|
|
// Note: 'git diff' compares the beginning and end trees, so it has no native concept
|
|
// of ignoring intermediate commits. To skip the changes made in "refactor" commits
|
|
// without using loops, we use `git log -p` combined with `--invert-grep` to
|
|
// natively output the diffs of only the non-refactor commits for this file.
|
|
rangeStr := "HEAD"
|
|
if baseCommit != "4b825dc642cb6eb9a060e54bf8d69288fbee4904" {
|
|
rangeStr = baseCommit + "..HEAD"
|
|
}
|
|
|
|
cmdDiff := exec.Command("git", "log", "-p", "-i", "--invert-grep", "--grep=refactor", rangeStr, "--", originalFile)
|
|
cmdDiff.Dir = s.repoDir
|
|
diffOut, err := cmdDiff.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get diff for file %s: %w", originalFile, err)
|
|
}
|
|
|
|
if len(diffOut) > 0 {
|
|
mods[file] = string(diffOut)
|
|
}
|
|
}
|
|
|
|
return mods, nil
|
|
}
|