1package cmd
2
3import (
4 "fmt"
5 "strings"
6 "time"
7
8 gansi "github.com/charmbracelet/glamour/ansi"
9 "github.com/charmbracelet/lipgloss"
10 "github.com/charmbracelet/soft-serve/git"
11 "github.com/charmbracelet/soft-serve/pkg/backend"
12 "github.com/charmbracelet/soft-serve/pkg/ui/common"
13 "github.com/charmbracelet/soft-serve/pkg/ui/styles"
14 "github.com/spf13/cobra"
15)
16
17// commitCommand returns a command that prints the contents of a commit.
18func commitCommand(renderer *lipgloss.Renderer) *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(renderer)
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 renderDiff(patch string, color bool) string {
112 c := patch
113
114 if color {
115 var s strings.Builder
116 var pr strings.Builder
117
118 diffChroma := &gansi.CodeBlockElement{
119 Code: patch,
120 Language: "diff",
121 }
122
123 err := diffChroma.Render(&pr, common.StyleRenderer())
124
125 if err != nil {
126 s.WriteString(fmt.Sprintf("\n%s", err.Error()))
127 } else {
128 s.WriteString(fmt.Sprintf("\n%s", pr.String()))
129 }
130
131 c = s.String()
132 }
133
134 return c
135}
136
137func renderStats(diff *git.Diff, commonStyle *styles.Styles, color bool) string {
138 style := commonStyle.Log
139 c := diff.Stats().String()
140
141 if color {
142 s := strings.Split(c, "\n")
143
144 for i, line := range s {
145 ch := strings.Split(line, "|")
146 if len(ch) > 1 {
147 adddel := ch[len(ch)-1]
148 adddel = strings.ReplaceAll(adddel, "+", style.CommitStatsAdd.Render("+"))
149 adddel = strings.ReplaceAll(adddel, "-", style.CommitStatsDel.Render("-"))
150 s[i] = strings.Join(ch[:len(ch)-1], "|") + "|" + adddel
151 }
152 }
153
154 return strings.Join(s, "\n")
155 }
156
157 return c
158}