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