package utils import ( "errors" "fmt" "io" "os" "os/exec" "regexp" "strconv" "strings" "github.com/alecthomas/chroma/v2/quick" "github.com/go-git/go-git/v5" "github.com/sergi/go-diff/diffmatchpatch" "golang.org/x/sys/execabs" "golang.org/x/term" ) // GitClone clones a git repo func GitClone(repo, dst string) (string, error) { if _, err := execabs.LookPath("git"); err == nil { cmd := exec.Command("git", "clone", "--depth", "1", repo, dst) dat, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("failed to clone repo '%s' to %s: %v", repo, dst, err) } return string(dat), nil } else { // use pure Go version of git if _, err := git.PlainClone(dst, false, &git.CloneOptions{ URL: repo, Depth: 1, Progress: os.Stderr, }); err != nil { return "", fmt.Errorf("failed to create local copy of 'appledb' repo: %v", err) } return "", nil } } // GitRefresh refreshes a git repo func GitRefresh(repoPath string) (string, error) { if _, err := execabs.LookPath("git"); err == nil { cmd := exec.Command("git", "pull", "--rebase") cmd.Dir = repoPath dat, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("failed to refresh repo '%s': %v", repoPath, err) } return string(dat), nil } else { // use pure Go version of git r, err := git.PlainOpen(repoPath) if err != nil { return "", fmt.Errorf("failed to open local copy of 'appledb' repo: %v", err) } w, err := r.Worktree() if err != nil { return "", fmt.Errorf("failed to get worktree of local copy of 'appledb' repo: %v", err) } if err = w.Pull(&git.PullOptions{ RemoteName: "origin", // Force: true, Progress: os.Stderr, }); err != nil { if !errors.Is(err, git.NoErrAlreadyUpToDate) { return "", fmt.Errorf("failed to update local copy of 'appledb' repo: %v", err) } } return "", nil } } type GitDiffConfig struct { Tool string Color bool } func GitDiff(src, dst string, conf *GitDiffConfig) (string, error) { switch conf.Tool { case "delta": return createDeltaDiffPatch(src, dst, conf) case "git": return createGitDiffPatch(src, dst, conf) case "go": return createGoDiff(src, dst, conf) default: if _, err := exec.LookPath("delta"); err == nil { return createDeltaDiffPatch(src, dst, conf) } else if _, err := exec.LookPath("git"); err == nil { return createGitDiffPatch(src, dst, conf) } return createGoDiff(src, dst, conf) } } func createGoDiff(src, dst string, conf *GitDiffConfig) (string, error) { dmp := diffmatchpatch.New() diffs := dmp.DiffMain(src, dst, false) if len(diffs) > 2 { diffs = dmp.DiffCleanupSemanticLossless(diffs) diffs = dmp.DiffCleanupEfficiency(diffs) } if len(diffs) == 1 { if diffs[0].Type == diffmatchpatch.DiffEqual { return "", nil } } return dmp.DiffPrettyText(diffs), nil } // createGitDiffPatch renders a unified diff by shelling out to the host git // binary. The output bytes (hunk boundaries, context-line algorithm) depend on // the installed git version. The diff cache (internal/diff) does not fold the // git version into its cache scope, so a cache built on one host may render // differently than a fresh run on a host with a different git; the cache is // therefore host-bound by default. See machosJob.OptionsHash for details. func createGitDiffPatch(src, dst string, conf *GitDiffConfig) (string, error) { tmpSrc, err := os.CreateTemp("", "src") if err != nil { return "", err } defer os.Remove(tmpSrc.Name()) os.WriteFile(tmpSrc.Name(), []byte(src), 0644) tmpDst, err := os.CreateTemp("", "dst") if err != nil { return "", err } defer os.Remove(tmpDst.Name()) os.WriteFile(tmpDst.Name(), []byte(dst), 0644) cmd := exec.Command("git", "diff", "--no-index", tmpSrc.Name(), tmpDst.Name()) dat, _ := cmd.CombinedOutput() out := string(dat) // strip the first 4 lines of the patch file _, out, _ = strings.Cut(out, "\n") _, out, _ = strings.Cut(out, "\n") _, out, _ = strings.Cut(out, "\n") _, out, _ = strings.Cut(out, "\n") // strip the @@ gap lines re := regexp.MustCompile("(?m)^@@ .*$") out = re.ReplaceAllString(out, "") if conf.Color { // colorize the diff b := new(strings.Builder) if err := quick.Highlight(b, out, "diff", "terminal256", "nord"); err != nil { return "", err } return b.String(), nil } return out, nil } func createDeltaDiffPatch(src, dst string, conf *GitDiffConfig) (string, error) { tmpSrc, err := os.CreateTemp("", "src") if err != nil { return "", err } defer os.Remove(tmpSrc.Name()) os.WriteFile(tmpSrc.Name(), []byte(src), 0644) tmpDst, err := os.CreateTemp("", "dst") if err != nil { return "", err } defer os.Remove(tmpDst.Name()) os.WriteFile(tmpDst.Name(), []byte(dst), 0644) width := 120 if term.IsTerminal(0) { twidth, _, err := term.GetSize(0) if err != nil { return "", err } width = twidth } cmd := exec.Command( "delta", "--dark", "--diff-so-fancy", "--side-by-side", "--file-style", "omit", "--hunk-header-style", "omit", "--syntax-theme", "Nord", "--width", strconv.Itoa(width), tmpSrc.Name(), tmpDst.Name(), ) out, _ := cmd.CombinedOutput() // if err != nil { // return "", fmt.Errorf("delta failed %s: %v", out, err) // } return string(out), nil } func ClangFormat(src, filename string, color bool) string { if _, err := exec.LookPath("clang-format"); err == nil { cmd := exec.Command( "clang-format", "-style={AlignConsecutiveDeclarations: true}", "--assume-filename", filename, ) stdin, err := cmd.StdinPipe() if err != nil { return src } go func() { defer stdin.Close() io.WriteString(stdin, src) }() out, _ := cmd.CombinedOutput() // if err != nil { // return "", fmt.Errorf("clang-format failed %s: %v", out, err) // } src = string(out) } if color { b := new(strings.Builder) if err := quick.Highlight(b, src, "c", "terminal256", "nord"); err != nil { return "" } return b.String() } return src }