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/ui/common"
11 "github.com/charmbracelet/soft-serve/server/ui/styles"
12 "github.com/muesli/termenv"
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 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 cfg, _ := fromContext(cmd)
28 repoName := args[0]
29 commitSHA := args[1]
30
31 rr, err := cfg.Backend.Repository(repoName)
32 if err != nil {
33 return err
34 }
35
36 r, err := rr.Open()
37 if err != nil {
38 return err
39 }
40
41 raw_commit, err := r.CommitByRevision(commitSHA)
42
43 commit := &git.Commit{
44 Commit: raw_commit,
45 Hash: git.Hash(commitSHA),
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.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
120 c := string(patch)
121
122 if color {
123 var s strings.Builder
124 var pr strings.Builder
125
126 diffChroma := &gansi.CodeBlockElement{
127 Code: patch,
128 Language: "diff",
129 }
130
131 err := diffChroma.Render(&pr, renderCtx())
132
133 if err != nil {
134 s.WriteString(fmt.Sprintf("\n%s", err.Error()))
135 } else {
136 s.WriteString(fmt.Sprintf("\n%s", pr.String()))
137 }
138
139 c = s.String()
140 }
141
142 return c
143}
144
145func renderStats(diff *git.Diff, commonStyle *styles.Styles, color bool) string {
146 style := commonStyle.Log
147 c := diff.Stats().String()
148
149 if color {
150 s := strings.Split(c, "\n")
151
152 for i, line := range s {
153 ch := strings.Split(line, "|")
154 if len(ch) > 1 {
155 adddel := ch[len(ch)-1]
156 adddel = strings.ReplaceAll(adddel, "+", style.CommitStatsAdd.Render("+"))
157 adddel = strings.ReplaceAll(adddel, "-", style.CommitStatsDel.Render("-"))
158 s[i] = strings.Join(ch[:len(ch)-1], "|") + "|" + adddel
159 }
160 }
161
162 return strings.Join(s, "\n")
163 }
164
165 return c
166}