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/pkg/backend"
11 "github.com/charmbracelet/soft-serve/pkg/ui/common"
12 "github.com/charmbracelet/soft-serve/pkg/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 commit, err := r.CommitByRevision(commitSHA)
44 if err != nil {
45 return err
46 }
47
48 patch, err := r.Patch(commit)
49 if err != nil {
50 return err
51 }
52
53 diff, err := r.Diff(commit)
54 if err != nil {
55 return err
56 }
57
58 commonStyle := styles.DefaultStyles()
59 style := commonStyle.Log
60
61 s := strings.Builder{}
62 commitLine := "commit " + commitSHA
63 authorLine := "Author: " + commit.Author.Name
64 dateLine := "Date: " + commit.Committer.When.UTC().Format(time.UnixDate)
65 msgLine := strings.ReplaceAll(commit.Message, "\r\n", "\n")
66 statsLine := renderStats(diff, commonStyle, color)
67 diffLine := renderDiff(patch, color)
68
69 if patchOnly {
70 cmd.Println(
71 diffLine,
72 )
73 return nil
74 }
75
76 if color {
77 s.WriteString(fmt.Sprintf("%s\n%s\n%s\n%s\n",
78 style.CommitHash.Render(commitLine),
79 style.CommitAuthor.Render(authorLine),
80 style.CommitDate.Render(dateLine),
81 style.CommitBody.Render(msgLine),
82 ))
83 } else {
84 s.WriteString(fmt.Sprintf("%s\n%s\n%s\n%s\n",
85 commitLine,
86 authorLine,
87 dateLine,
88 msgLine,
89 ))
90 }
91
92 s.WriteString(fmt.Sprintf("\n%s\n%s",
93 statsLine,
94 diffLine,
95 ))
96
97 cmd.Println(
98 s.String(),
99 )
100
101 return nil
102 },
103 }
104
105 cmd.Flags().BoolVarP(&color, "color", "c", false, "Colorize output")
106 cmd.Flags().BoolVarP(&patchOnly, "patch", "p", false, "Output patch only")
107
108 return cmd
109}
110
111func renderCtx() gansi.RenderContext {
112 return gansi.NewRenderContext(gansi.Options{
113 ColorProfile: termenv.TrueColor,
114 Styles: common.StyleConfig(),
115 })
116}
117
118func renderDiff(patch string, color bool) string {
119 c := patch
120
121 if color {
122 var s strings.Builder
123 var pr strings.Builder
124
125 diffChroma := &gansi.CodeBlockElement{
126 Code: patch,
127 Language: "diff",
128 }
129
130 err := diffChroma.Render(&pr, renderCtx())
131
132 if err != nil {
133 s.WriteString(fmt.Sprintf("\n%s", err.Error()))
134 } else {
135 s.WriteString(fmt.Sprintf("\n%s", pr.String()))
136 }
137
138 c = s.String()
139 }
140
141 return c
142}
143
144func renderStats(diff *git.Diff, commonStyle *styles.Styles, color bool) string {
145 style := commonStyle.Log
146 c := diff.Stats().String()
147
148 if color {
149 s := strings.Split(c, "\n")
150
151 for i, line := range s {
152 ch := strings.Split(line, "|")
153 if len(ch) > 1 {
154 adddel := ch[len(ch)-1]
155 adddel = strings.ReplaceAll(adddel, "+", style.CommitStatsAdd.Render("+"))
156 adddel = strings.ReplaceAll(adddel, "-", style.CommitStatsDel.Render("-"))
157 s[i] = strings.Join(ch[:len(ch)-1], "|") + "|" + adddel
158 }
159 }
160
161 return strings.Join(s, "\n")
162 }
163
164 return c
165}