1package cmd
2
3import (
4 "fmt"
5 "regexp"
6 "strings"
7 "time"
8
9 gansi "github.com/charmbracelet/glamour/v2/ansi"
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
17var shaRE = regexp.MustCompile(`^[a-fA-F0-9]{5,40}$`)
18
19// commitCommand returns a command that prints the contents of a commit.
20func commitCommand() *cobra.Command {
21 var color bool
22 var patchOnly bool
23
24 cmd := &cobra.Command{
25 Use: "commit repo SHA",
26 Short: "Print out the contents of a diff",
27 Args: cobra.ExactArgs(2),
28 PersistentPreRunE: checkIfReadable,
29 RunE: func(cmd *cobra.Command, args []string) error {
30 ctx := cmd.Context()
31 be := backend.FromContext(ctx)
32 repoName := args[0]
33 commitSHA := args[1]
34
35 if !shaRE.MatchString(commitSHA) {
36 return fmt.Errorf("invalid commit SHA: %s", commitSHA)
37 }
38
39 rr, err := be.Repository(ctx, repoName)
40 if err != nil {
41 return err
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 cmd.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 cmd.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 renderDiff(patch string, color bool) string {
118 c := patch
119
120 if color {
121 var s strings.Builder
122 var pr strings.Builder
123
124 diffChroma := &gansi.CodeBlockElement{
125 Code: patch,
126 Language: "diff",
127 }
128
129 err := diffChroma.Render(&pr, common.StyleRenderer())
130
131 if err != nil {
132 s.WriteString(fmt.Sprintf("\n%s", err.Error()))
133 } else {
134 s.WriteString(fmt.Sprintf("\n%s", pr.String()))
135 }
136
137 c = s.String()
138 }
139
140 return c
141}
142
143func renderStats(diff *git.Diff, commonStyle *styles.Styles, color bool) string {
144 style := commonStyle.Log
145 c := diff.Stats().String()
146
147 if color {
148 s := strings.Split(c, "\n")
149
150 for i, line := range s {
151 ch := strings.Split(line, "|")
152 if len(ch) > 1 {
153 adddel := ch[len(ch)-1]
154 adddel = strings.ReplaceAll(adddel, "+", style.CommitStatsAdd.Render("+"))
155 adddel = strings.ReplaceAll(adddel, "-", style.CommitStatsDel.Render("-"))
156 s[i] = strings.Join(ch[:len(ch)-1], "|") + "|" + adddel
157 }
158 }
159
160 return strings.Join(s, "\n")
161 }
162
163 return c
164}