1package cmd
2
3import (
4 "fmt"
5 "strings"
6 "time"
7
8 gansi "github.com/charmbracelet/glamour/v2/ansi"
9 "github.com/charmbracelet/soft-serve/git"
10 "github.com/charmbracelet/soft-serve/pkg/backend"
11 "github.com/charmbracelet/soft-serve/pkg/ui/common"
12 "github.com/charmbracelet/soft-serve/pkg/ui/styles"
13 "github.com/spf13/cobra"
14)
15
16// commitCommand returns a command that prints the contents of a commit.
17func commitCommand() *cobra.Command {
18 var color bool
19 var patchOnly bool
20
21 cmd := &cobra.Command{
22 Use: "commit repo SHA",
23 Short: "Print out the contents of a diff",
24 Args: cobra.ExactArgs(2),
25 PersistentPreRunE: checkIfReadable,
26 RunE: func(cmd *cobra.Command, args []string) error {
27 ctx := cmd.Context()
28 be := backend.FromContext(ctx)
29 repoName := args[0]
30 commitSHA := args[1]
31
32 rr, err := be.Repository(ctx, repoName)
33 if err != nil {
34 return err //nolint:wrapcheck
35 }
36
37 r, err := rr.Open()
38 if err != nil {
39 return err //nolint:wrapcheck
40 }
41
42 commit, err := r.CommitByRevision(commitSHA)
43 if err != nil {
44 return err //nolint:wrapcheck
45 }
46
47 patch, err := r.Patch(commit)
48 if err != nil {
49 return err //nolint:wrapcheck
50 }
51
52 diff, err := r.Diff(commit)
53 if err != nil {
54 return err //nolint:wrapcheck
55 }
56
57 commonStyle := styles.DefaultStyles()
58 style := commonStyle.Log
59
60 s := strings.Builder{}
61 commitLine := "commit " + commitSHA
62 authorLine := "Author: " + commit.Author.Name
63 dateLine := "Date: " + commit.Committer.When.UTC().Format(time.UnixDate)
64 msgLine := strings.ReplaceAll(commit.Message, "\r\n", "\n")
65 statsLine := renderStats(diff, commonStyle, color)
66 diffLine := renderDiff(patch, color)
67
68 if patchOnly {
69 cmd.Println(
70 diffLine,
71 )
72 return nil
73 }
74
75 if color {
76 s.WriteString(fmt.Sprintf("%s\n%s\n%s\n%s\n",
77 style.CommitHash.Render(commitLine),
78 style.CommitAuthor.Render(authorLine),
79 style.CommitDate.Render(dateLine),
80 style.CommitBody.Render(msgLine),
81 ))
82 } else {
83 s.WriteString(fmt.Sprintf("%s\n%s\n%s\n%s\n",
84 commitLine,
85 authorLine,
86 dateLine,
87 msgLine,
88 ))
89 }
90
91 s.WriteString(fmt.Sprintf("\n%s\n%s",
92 statsLine,
93 diffLine,
94 ))
95
96 cmd.Println(
97 s.String(),
98 )
99
100 return nil
101 },
102 }
103
104 cmd.Flags().BoolVarP(&color, "color", "c", false, "Colorize output")
105 cmd.Flags().BoolVarP(&patchOnly, "patch", "p", false, "Output patch only")
106
107 return cmd
108}
109
110func renderDiff(patch string, color bool) string {
111 c := patch
112
113 if color {
114 var s strings.Builder
115 var pr strings.Builder
116
117 diffChroma := &gansi.CodeBlockElement{
118 Code: patch,
119 Language: "diff",
120 }
121
122 err := diffChroma.Render(&pr, common.StyleRenderer())
123
124 if err != nil {
125 s.WriteString(fmt.Sprintf("\n%s", err.Error()))
126 } else {
127 s.WriteString(fmt.Sprintf("\n%s", pr.String()))
128 }
129
130 c = s.String()
131 }
132
133 return c
134}
135
136func renderStats(diff *git.Diff, commonStyle *styles.Styles, color bool) string {
137 style := commonStyle.Log
138 c := diff.Stats().String()
139
140 if color {
141 s := strings.Split(c, "\n")
142
143 for i, line := range s {
144 ch := strings.Split(line, "|")
145 if len(ch) > 1 {
146 adddel := ch[len(ch)-1]
147 adddel = strings.ReplaceAll(adddel, "+", style.CommitStatsAdd.Render("+"))
148 adddel = strings.ReplaceAll(adddel, "-", style.CommitStatsDel.Render("-"))
149 s[i] = strings.Join(ch[:len(ch)-1], "|") + "|" + adddel
150 }
151 }
152
153 return strings.Join(s, "\n")
154 }
155
156 return c
157}