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}