commit.go

  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}