12 "github.com/go-git/go-git/v5"
13 "github.com/go-git/go-git/v5/plumbing/object"
16type DiffStats struct {
21func GenerateGitDiff(filePath string, contentBefore string, contentAfter string) (string, error) {
22 tempDir, err := os.MkdirTemp("", "git-diff-temp")
24 return "", fmt.Errorf("failed to create temp dir: %w", err)
26 defer os.RemoveAll(tempDir)
28 repo, err := git.PlainInit(tempDir, false)
30 return "", fmt.Errorf("failed to initialize git repo: %w", err)
33 wt, err := repo.Worktree()
35 return "", fmt.Errorf("failed to get worktree: %w", err)
38 fullPath := filepath.Join(tempDir, filePath)
39 if err = os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil {
40 return "", fmt.Errorf("failed to create directories: %w", err)
42 if err = os.WriteFile(fullPath, []byte(contentBefore), 0o644); err != nil {
43 return "", fmt.Errorf("failed to write 'before' content: %w", err)
46 _, err = wt.Add(filePath)
48 return "", fmt.Errorf("failed to add file to git: %w", err)
51 beforeCommit, err := wt.Commit("Before", &git.CommitOptions{
52 Author: &object.Signature{
54 Email: "coder@opencode.ai",
59 return "", fmt.Errorf("failed to commit 'before' version: %w", err)
62 if err = os.WriteFile(fullPath, []byte(contentAfter), 0o644); err != nil {
63 return "", fmt.Errorf("failed to write 'after' content: %w", err)
66 _, err = wt.Add(filePath)
68 return "", fmt.Errorf("failed to add updated file to git: %w", err)
71 afterCommit, err := wt.Commit("After", &git.CommitOptions{
72 Author: &object.Signature{
74 Email: "coder@opencode.ai",
79 return "", fmt.Errorf("failed to commit 'after' version: %w", err)
82 beforeCommitObj, err := repo.CommitObject(beforeCommit)
84 return "", fmt.Errorf("failed to get 'before' commit: %w", err)
87 afterCommitObj, err := repo.CommitObject(afterCommit)
89 return "", fmt.Errorf("failed to get 'after' commit: %w", err)
92 patch, err := beforeCommitObj.Patch(afterCommitObj)
94 return "", fmt.Errorf("failed to generate patch: %w", err)
97 return patch.String(), nil
100func GenerateGitDiffWithStats(filePath string, contentBefore string, contentAfter string) (string, DiffStats, error) {
101 tempDir, err := os.MkdirTemp("", "git-diff-temp")
103 return "", DiffStats{}, fmt.Errorf("failed to create temp dir: %w", err)
105 defer os.RemoveAll(tempDir)
107 repo, err := git.PlainInit(tempDir, false)
109 return "", DiffStats{}, fmt.Errorf("failed to initialize git repo: %w", err)
112 wt, err := repo.Worktree()
114 return "", DiffStats{}, fmt.Errorf("failed to get worktree: %w", err)
117 fullPath := filepath.Join(tempDir, filePath)
118 if err = os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil {
119 return "", DiffStats{}, fmt.Errorf("failed to create directories: %w", err)
121 if err = os.WriteFile(fullPath, []byte(contentBefore), 0o644); err != nil {
122 return "", DiffStats{}, fmt.Errorf("failed to write 'before' content: %w", err)
125 _, err = wt.Add(filePath)
127 return "", DiffStats{}, fmt.Errorf("failed to add file to git: %w", err)
130 beforeCommit, err := wt.Commit("Before", &git.CommitOptions{
131 Author: &object.Signature{
133 Email: "coder@opencode.ai",
138 return "", DiffStats{}, fmt.Errorf("failed to commit 'before' version: %w", err)
141 if err = os.WriteFile(fullPath, []byte(contentAfter), 0o644); err != nil {
142 return "", DiffStats{}, fmt.Errorf("failed to write 'after' content: %w", err)
145 _, err = wt.Add(filePath)
147 return "", DiffStats{}, fmt.Errorf("failed to add updated file to git: %w", err)
150 afterCommit, err := wt.Commit("After", &git.CommitOptions{
151 Author: &object.Signature{
153 Email: "coder@opencode.ai",
158 return "", DiffStats{}, fmt.Errorf("failed to commit 'after' version: %w", err)
161 beforeCommitObj, err := repo.CommitObject(beforeCommit)
163 return "", DiffStats{}, fmt.Errorf("failed to get 'before' commit: %w", err)
166 afterCommitObj, err := repo.CommitObject(afterCommit)
168 return "", DiffStats{}, fmt.Errorf("failed to get 'after' commit: %w", err)
171 patch, err := beforeCommitObj.Patch(afterCommitObj)
173 return "", DiffStats{}, fmt.Errorf("failed to generate patch: %w", err)
177 for _, fileStat := range patch.Stats() {
178 stats.Additions += fileStat.Addition
179 stats.Removals += fileStat.Deletion
182 return patch.String(), stats, nil
185func FormatDiff(diffText string, width int) (string, error) {
186 if isSplitDiffsAvailable() {
187 return formatWithSplitDiffs(diffText, width)
190 return formatSimple(diffText), nil
193func isSplitDiffsAvailable() bool {
194 _, err := exec.LookPath("node")
198func formatWithSplitDiffs(diffText string, width int) (string, error) {
203 var diffCmd *exec.Cmd
205 if _, err := exec.LookPath("git-split-diffs-opencode"); err == nil {
206 fullArgs := append([]string{"git-split-diffs-opencode"}, args...)
207 diffCmd = exec.Command(fullArgs[0], fullArgs[1:]...)
209 npxArgs := append([]string{"git-split-diffs-opencode"}, args...)
210 diffCmd = exec.Command("npx", npxArgs...)
213 diffCmd.Env = append(os.Environ(), fmt.Sprintf("DIFF_COLUMNS=%d", width))
215 diffCmd.Stdin = strings.NewReader(diffText)
218 diffCmd.Stdout = &out
220 var stderr bytes.Buffer
221 diffCmd.Stderr = &stderr
223 if err := diffCmd.Run(); err != nil {
224 return "", fmt.Errorf("git-split-diffs-opencode error: %w, stderr: %s", err, stderr.String())
227 return out.String(), nil
230func formatSimple(diffText string) string {
231 lines := strings.Split(diffText, "\n")
232 var result strings.Builder
234 for _, line := range lines {
236 result.WriteString("\n")
242 result.WriteString("\033[32m" + line + "\033[0m\n")
244 result.WriteString("\033[31m" + line + "\033[0m\n")
246 result.WriteString("\033[36m" + line + "\033[0m\n")
248 if strings.HasPrefix(line, "diff --git") {
249 result.WriteString("\033[1m" + line + "\033[0m\n")
251 result.WriteString(line + "\n")
254 result.WriteString(line + "\n")
258 if !strings.HasSuffix(diffText, "\n") {
259 output := result.String()
260 return output[:len(output)-1]
263 return result.String()
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list