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