The codebase that powers boop.cat
boop.cat
1// Copyright 2025 boop.cat
2// Licensed under the Apache License, Version 2.0
3// See LICENSE file for details.
4
5package deploy
6
7import (
8 "context"
9 "fmt"
10 "os"
11 "os/exec"
12 "path/filepath"
13 "strings"
14)
15
16func ensureDir(path string) error {
17 return os.MkdirAll(path, 0755)
18}
19
20func GitClone(ctx context.Context, repoURL, branch, targetDir string, depth int, logger func(string)) error {
21 if err := os.RemoveAll(targetDir); err != nil {
22 return fmt.Errorf("failed to clear target dir: %w", err)
23 }
24 if err := ensureDir(filepath.Dir(targetDir)); err != nil {
25 return fmt.Errorf("failed to create parent dir: %w", err)
26 }
27
28 args := []string{"clone", "--no-tags", "--depth", fmt.Sprintf("%d", depth)}
29 if branch != "" {
30 args = append(args, "--branch", branch)
31 }
32 args = append(args, repoURL, targetDir)
33
34 cmd := exec.CommandContext(ctx, "git", args...)
35 cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
36
37 stdout, _ := cmd.StdoutPipe()
38 stderr, _ := cmd.StderrPipe()
39
40 if err := cmd.Start(); err != nil {
41 return err
42 }
43
44 sanitize := func(s string) string {
45
46 if strings.Contains(s, "@github.com") {
47 parts := strings.Split(s, "@github.com")
48 if len(parts) > 1 {
49
50 lastSpace := strings.LastIndex(parts[0], " ")
51 if lastSpace != -1 {
52 return parts[0][:lastSpace+1] + "***" + "@github.com" + parts[1]
53 }
54
55 lastSlash := strings.LastIndex(parts[0], "//")
56 if lastSlash != -1 {
57 return parts[0][:lastSlash+2] + "***" + "@github.com" + parts[1]
58 }
59 }
60 }
61 return s
62 }
63
64 go func() {
65 buf := make([]byte, 1024)
66 for {
67 n, err := stdout.Read(buf)
68 if n > 0 && logger != nil {
69 logger(sanitize(string(buf[:n])))
70 }
71 if err != nil {
72 break
73 }
74 }
75 }()
76
77 go func() {
78 buf := make([]byte, 1024)
79 for {
80 n, err := stderr.Read(buf)
81 if n > 0 && logger != nil {
82 logger(sanitize(string(buf[:n])))
83 }
84 if err != nil {
85 break
86 }
87 }
88 }()
89
90 return cmd.Wait()
91}
92
93func GitCheckout(targetDir, ref string) error {
94 cmd := exec.Command("git", "checkout", ref)
95 cmd.Dir = targetDir
96 return cmd.Run()
97}
98
99type CommitInfo struct {
100 SHA string
101 Message string
102 Author string
103}
104
105func GitCurrentHead(targetDir string) (*CommitInfo, error) {
106
107 shaCmd := exec.Command("git", "rev-parse", "HEAD")
108 shaCmd.Dir = targetDir
109 shaOut, err := shaCmd.Output()
110 if err != nil {
111 return nil, err
112 }
113
114 msgCmd := exec.Command("git", "log", "-1", "--pretty=%s")
115 msgCmd.Dir = targetDir
116 msgOut, _ := msgCmd.Output()
117
118 authCmd := exec.Command("git", "log", "-1", "--pretty=%an <%ae>")
119 authCmd.Dir = targetDir
120 authOut, _ := authCmd.Output()
121
122 return &CommitInfo{
123 SHA: strings.TrimSpace(string(shaOut)),
124 Message: strings.TrimSpace(string(msgOut)),
125 Author: strings.TrimSpace(string(authOut)),
126 }, nil
127}