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