82c4c604da95cc46767072f99f73da61233014cb

Author
Ayman Bagabas <ayman.bagabas@gmail.com>
Committer
Ayman Bagabas <ayman.bagabas@gmail.com>
Date

Message

feat(cmd): support custom hooks

Diff

This diff is truncated to protect this page.

  1diff --git a/cmd/soft/hook.go b/cmd/soft/hook.go
  2index a24fc6b33c20ee7e1e83147beb6112f7f0b368a2..fc78221722e4c0b5801cdc24fe30e9456786d325 100644
  3--- a/cmd/soft/hook.go
  4+++ b/cmd/soft/hook.go
  5@@ -5,7 +5,10 @@ import (
  6 	"bytes"
  7 	"context"
  8 	"fmt"
  9+	"io"
 10 	"os"
 11+	"os/exec"
 12+	"path/filepath"
 13 	"strings"
 14 
 15 	"github.com/charmbracelet/soft-serve/server/backend"
 16@@ -34,6 +37,16 @@ var (
 17 				return fmt.Errorf("could not parse config: %w", err)
 18 			}
 19 
 20+			customHooksPath := filepath.Join(filepath.Dir(configPath), "hooks")
 21+			if _, err := os.Stat(customHooksPath); err != nil && os.IsNotExist(err) {
 22+				os.MkdirAll(customHooksPath, os.ModePerm)
 23+				// Generate update hook example without executable permissions
 24+				hookPath := filepath.Join(customHooksPath, "update.sample")
 25+				if err := os.WriteFile(hookPath, []byte(updateHookExample), 0744); err != nil {
 26+					return fmt.Errorf("failed to generate update hook example: %w", err)
 27+				}
 28+			}
 29+
 30 			// Set up the backend
 31 			// TODO: support other backends
 32 			sb, err := sqlite.NewSqliteBackend(cmd.Context(), cfg)
 33@@ -57,21 +70,24 @@ var (
 34 		// This is set in the server before invoking git-receive-pack/git-upload-pack
 35 		repoName := os.Getenv("SOFT_SERVE_REPO_NAME")
 36 
 37-		in := cmd.InOrStdin()
 38-		out := cmd.OutOrStdout()
 39-		err := cmd.ErrOrStderr()
 40+		stdin := cmd.InOrStdin()
 41+		stdout := cmd.OutOrStdout()
 42+		stderr := cmd.ErrOrStderr()
 43 
 44 		cmdName := cmd.Name()
 45+		customHookPath := filepath.Join(filepath.Dir(configPath), "hooks", cmdName)
 46+
 47+		var buf bytes.Buffer
 48+		opts := make([]backend.HookArg, 0)
 49+
 50 		switch cmdName {
 51 		case hooks.PreReceiveHook, hooks.PostReceiveHook:
 52-			var buf bytes.Buffer
 53-			opts := make([]backend.HookArg, 0)
 54-			scanner := bufio.NewScanner(in)
 55+			scanner := bufio.NewScanner(stdin)
 56 			for scanner.Scan() {
 57 				buf.Write(scanner.Bytes())
 58 				fields := strings.Fields(scanner.Text())
 59 				if len(fields) != 3 {
 60-					return fmt.Errorf("invalid pre-receive hook input: %s", scanner.Text())
 61+					return fmt.Errorf("invalid hook input: %s", scanner.Text())
 62 				}
 63 				opts = append(opts, backend.HookArg{
 64 					OldSha:  fields[0],
 65@@ -82,22 +98,30 @@ var (
 66 
 67 			switch cmdName {
 68 			case hooks.PreReceiveHook:
 69-				hks.PreReceive(out, err, repoName, opts)
 70+				hks.PreReceive(stdout, stderr, repoName, opts)
 71 			case hooks.PostReceiveHook:
 72-				hks.PostReceive(out, err, repoName, opts)
 73+				hks.PostReceive(stdout, stderr, repoName, opts)
 74 			}
 75 		case hooks.UpdateHook:
 76 			if len(args) != 3 {
 77 				return fmt.Errorf("invalid update hook input: %s", args)
 78 			}
 79 
 80-			hks.Update(out, err, repoName, backend.HookArg{
 81+			hks.Update(stdout, stderr, repoName, backend.HookArg{
 82 				OldSha:  args[0],
 83 				NewSha:  args[1],
 84 				RefName: args[2],
 85 			})
 86 		case hooks.PostUpdateHook:
 87-			hks.PostUpdate(out, err, repoName, args...)
 88+			hks.PostUpdate(stdout, stderr, repoName, args...)
 89+		}
 90+
 91+		// Custom hooks
 92+		if stat, err := os.Stat(customHookPath); err == nil && !stat.IsDir() && stat.Mode()&0o111 != 0 {
 93+			// If the custom hook is executable, run it
 94+			if err := runCommand(cmd.Context(), &buf, stdout, stderr, customHookPath, args...); err != nil {
 95+				return fmt.Errorf("failed to run custom hook: %w", err)
 96+			}
 97 		}
 98 
 99 		return nil
100@@ -138,3 +162,54 @@ func init() {
101 		postUpdateCmd,
102 	)
103 }
104+