Add commit command (#331)

Andy Lu created

* Add commit command

* fix bad refactor

* fix: actually support the color flag

* Squashed commit of the following:

commit 814ee9f8fa7014dd4fafab12974e2edd4b8f7d73
Author: Andy Lu <luandy64@gmail.com>
Date:   Fri Jun 30 23:07:49 2023 -0400

    Run gofmt

commit 3db029502c74461b57e776a0ebc9d27c944acc5d
Author: Andy Lu <luandy64@gmail.com>
Date:   Fri Jun 30 23:06:31 2023 -0400

    Pull out statsLine and diffLine, add a commit body line

commit eb1da4296e0181bae842622e0dc98ca8ca845049
Author: Andy Lu <luandy64@gmail.com>
Date:   Fri Jun 30 23:05:36 2023 -0400

    Add flag for printing the patch only

commit e76702e92b085f02427aae1c7052f235454242fd
Author: Andy Lu <luandy64@gmail.com>
Date:   Fri Jun 30 22:50:55 2023 -0400

    Add commit, author, date, and stats

commit 0359b1dc49417d11f8f8b8529f8b942417d79128
Author: Andy Lu <luandy64@gmail.com>
Date:   Fri Jun 30 21:43:35 2023 -0400

    Add commit, author, date, and stats

    Missing color on stats

* Whitespace cleanup

Change summary

server/cmd/commit.go | 166 ++++++++++++++++++++++++++++++++++++++++++++++
server/cmd/repo.go   |   1 
2 files changed, 167 insertions(+)

Detailed changes

server/cmd/commit.go 🔗

@@ -0,0 +1,166 @@
+package cmd
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	gansi "github.com/charmbracelet/glamour/ansi"
+	"github.com/charmbracelet/soft-serve/git"
+	"github.com/charmbracelet/soft-serve/server/ui/common"
+	"github.com/charmbracelet/soft-serve/server/ui/styles"
+	"github.com/muesli/termenv"
+	"github.com/spf13/cobra"
+)
+
+// commitCommand returns a command that prints the contents of a commit.
+func commitCommand() *cobra.Command {
+	var color bool
+	var patchOnly bool
+
+	cmd := &cobra.Command{
+		Use:               "commit SHA",
+		Short:             "Print out the contents of a diff",
+		Args:              cobra.ExactArgs(2),
+		PersistentPreRunE: checkIfReadable,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			cfg, _ := fromContext(cmd)
+			repoName := args[0]
+			commitSHA := args[1]
+
+			rr, err := cfg.Backend.Repository(repoName)
+			if err != nil {
+				return err
+			}
+
+			r, err := rr.Open()
+			if err != nil {
+				return err
+			}
+
+			raw_commit, err := r.CommitByRevision(commitSHA)
+
+			commit := &git.Commit{
+				Commit: raw_commit,
+				Hash:   git.Hash(commitSHA),
+			}
+
+			patch, err := r.Patch(commit)
+			if err != nil {
+				return err
+			}
+
+			diff, err := r.Diff(commit)
+			if err != nil {
+				return err
+			}
+
+			commonStyle := styles.DefaultStyles()
+			style := commonStyle.Log
+
+			s := strings.Builder{}
+			commitLine := "commit " + commitSHA
+			authorLine := "Author: " + commit.Author.Name
+			dateLine := "Date:   " + commit.Committer.When.Format(time.UnixDate)
+			msgLine := strings.ReplaceAll(commit.Message, "\r\n", "\n")
+			statsLine := renderStats(diff, commonStyle, color)
+			diffLine := renderDiff(patch, color)
+
+			if patchOnly {
+				cmd.Println(
+					diffLine,
+				)
+				return nil
+			}
+
+			if color {
+				s.WriteString(fmt.Sprintf("%s\n%s\n%s\n%s\n",
+					style.CommitHash.Render(commitLine),
+					style.CommitAuthor.Render(authorLine),
+					style.CommitDate.Render(dateLine),
+					style.CommitBody.Render(msgLine),
+				))
+			} else {
+				s.WriteString(fmt.Sprintf("%s\n%s\n%s\n%s\n",
+					commitLine,
+					authorLine,
+					dateLine,
+					msgLine,
+				))
+			}
+
+			s.WriteString(fmt.Sprintf("\n%s\n%s",
+				statsLine,
+				diffLine,
+			))
+
+			cmd.Println(
+				s.String(),
+			)
+
+			return nil
+		},
+	}
+
+	cmd.Flags().BoolVarP(&color, "color", "c", false, "Colorize output")
+	cmd.Flags().BoolVarP(&patchOnly, "patch", "p", false, "Output patch only")
+
+	return cmd
+}
+
+func renderCtx() gansi.RenderContext {
+	return gansi.NewRenderContext(gansi.Options{
+		ColorProfile: termenv.TrueColor,
+		Styles:       common.StyleConfig(),
+	})
+}
+
+func renderDiff(patch string, color bool) string {
+
+	c := string(patch)
+
+	if color {
+		var s strings.Builder
+		var pr strings.Builder
+
+		diffChroma := &gansi.CodeBlockElement{
+			Code:     patch,
+			Language: "diff",
+		}
+
+		err := diffChroma.Render(&pr, renderCtx())
+
+		if err != nil {
+			s.WriteString(fmt.Sprintf("\n%s", err.Error()))
+		} else {
+			s.WriteString(fmt.Sprintf("\n%s", pr.String()))
+		}
+
+		c = s.String()
+	}
+
+	return c
+}
+
+func renderStats(diff *git.Diff, commonStyle *styles.Styles, color bool) string {
+	style := commonStyle.Log
+	c := diff.Stats().String()
+
+	if color {
+		s := strings.Split(c, "\n")
+
+		for i, line := range s {
+			ch := strings.Split(line, "|")
+			if len(ch) > 1 {
+				adddel := ch[len(ch)-1]
+				adddel = strings.ReplaceAll(adddel, "+", style.CommitStatsAdd.Render("+"))
+				adddel = strings.ReplaceAll(adddel, "-", style.CommitStatsDel.Render("-"))
+				s[i] = strings.Join(ch[:len(ch)-1], "|") + "|" + adddel
+			}
+		}
+
+		return strings.Join(s, "\n")
+	}
+
+	return c
+}

server/cmd/repo.go 🔗

@@ -18,6 +18,7 @@ func repoCommand() *cobra.Command {
 		blobCommand(),
 		branchCommand(),
 		collabCommand(),
+		commitCommand(),
 		createCommand(),
 		deleteCommand(),
 		descriptionCommand(),