tenseleyflow/shithub / aa9ab45

Browse files

S14: install hooks on repo create; thread shithubd path from web layer

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
aa9ab459f5bcea69fa4df234c0107d4fac49f26c
Parents
a6e775f
Tree
000e13d

3 changed files

StatusFile+-
M internal/repos/create.go 18 0
M internal/web/handlers/repo/repo.go 10 5
M internal/web/repo_wiring.go 19 6
internal/repos/create.gomodified
@@ -17,6 +17,7 @@ import (
17
 
17
 
18
 	"github.com/tenseleyFlow/shithub/internal/auth/audit"
18
 	"github.com/tenseleyFlow/shithub/internal/auth/audit"
19
 	"github.com/tenseleyFlow/shithub/internal/auth/throttle"
19
 	"github.com/tenseleyFlow/shithub/internal/auth/throttle"
20
+	"github.com/tenseleyFlow/shithub/internal/git/hooks"
20
 	"github.com/tenseleyFlow/shithub/internal/infra/storage"
21
 	"github.com/tenseleyFlow/shithub/internal/infra/storage"
21
 	repogit "github.com/tenseleyFlow/shithub/internal/repos/git"
22
 	repogit "github.com/tenseleyFlow/shithub/internal/repos/git"
22
 	reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
23
 	reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
@@ -41,6 +42,11 @@ type Deps struct {
41
 	Limiter *throttle.Limiter
42
 	Limiter *throttle.Limiter
42
 	Logger  *slog.Logger
43
 	Logger  *slog.Logger
43
 	Now     func() time.Time
44
 	Now     func() time.Time
45
+	// ShithubdPath is the absolute path to the running shithubd binary,
46
+	// baked into the hook shims so push -> hook -> shithubd round-trip
47
+	// works in dev and prod. Empty disables hook installation (tests
48
+	// that don't care about hooks; the full E2E happy path provides it).
49
+	ShithubdPath string
44
 }
50
 }
45
 
51
 
46
 // Params describes one repo-create request as it arrives from the
52
 // Params describes one repo-create request as it arrives from the
@@ -164,6 +170,18 @@ func Create(ctx context.Context, deps Deps, p Params) (Result, error) {
164
 		return Result{}, fmt.Errorf("repos: init bare: %w", err)
170
 		return Result{}, fmt.Errorf("repos: init bare: %w", err)
165
 	}
171
 	}
166
 
172
 
173
+	// Install push-pipeline hooks. Skipped when ShithubdPath is empty
174
+	// (test fixtures that exercise repo creation without the hook
175
+	// stack). The plumbing-driven initial commit doesn't fire hooks —
176
+	// hooks only run on user-driven pushes — so this is the right
177
+	// boundary.
178
+	if deps.ShithubdPath != "" {
179
+		if err := hooks.Install(diskPath, deps.ShithubdPath); err != nil {
180
+			_ = os.RemoveAll(diskPath)
181
+			return Result{}, fmt.Errorf("repos: install hooks: %w", err)
182
+		}
183
+	}
184
+
167
 	var commitOID string
185
 	var commitOID string
168
 	if wantInit {
186
 	if wantInit {
169
 		commitWhen := p.InitialCommitWhen
187
 		commitWhen := p.InitialCommitWhen
internal/web/handlers/repo/repo.gomodified
@@ -47,6 +47,10 @@ type Deps struct {
47
 	Audit     *audit.Recorder
47
 	Audit     *audit.Recorder
48
 	Limiter   *throttle.Limiter
48
 	Limiter   *throttle.Limiter
49
 	CloneURLs CloneURLs
49
 	CloneURLs CloneURLs
50
+	// ShithubdPath is forwarded to repos.Create so newly-init'd repos
51
+	// have hook shims pointing at the right binary. Empty in test fixtures
52
+	// that don't exercise hooks.
53
+	ShithubdPath string
50
 }
54
 }
51
 
55
 
52
 // Handlers is the registered handler set. Construct via New.
56
 // Handlers is the registered handler set. Construct via New.
@@ -128,11 +132,12 @@ func (h *Handlers) newRepoSubmit(w http.ResponseWriter, r *http.Request) {
128
 	}
132
 	}
129
 
133
 
130
 	res, err := repos.Create(r.Context(), repos.Deps{
134
 	res, err := repos.Create(r.Context(), repos.Deps{
131
-		Pool:    h.d.Pool,
135
+		Pool:         h.d.Pool,
132
-		RepoFS:  h.d.RepoFS,
136
+		RepoFS:       h.d.RepoFS,
133
-		Audit:   h.d.Audit,
137
+		Audit:        h.d.Audit,
134
-		Limiter: h.d.Limiter,
138
+		Limiter:      h.d.Limiter,
135
-		Logger:  h.d.Logger,
139
+		Logger:       h.d.Logger,
140
+		ShithubdPath: h.d.ShithubdPath,
136
 	}, repos.Params{
141
 	}, repos.Params{
137
 		OwnerUserID:   user.ID,
142
 		OwnerUserID:   user.ID,
138
 		OwnerUsername: user.Username,
143
 		OwnerUsername: user.Username,
internal/web/repo_wiring.gomodified
@@ -7,6 +7,7 @@ import (
7
 	"fmt"
7
 	"fmt"
8
 	"io/fs"
8
 	"io/fs"
9
 	"log/slog"
9
 	"log/slog"
10
+	"os"
10
 	"path/filepath"
11
 	"path/filepath"
11
 
12
 
12
 	"github.com/jackc/pgx/v5/pgxpool"
13
 	"github.com/jackc/pgx/v5/pgxpool"
@@ -43,13 +44,25 @@ func buildRepoHandlers(
43
 	if err != nil {
44
 	if err != nil {
44
 		return nil, fmt.Errorf("repo: render.New: %w", err)
45
 		return nil, fmt.Errorf("repo: render.New: %w", err)
45
 	}
46
 	}
47
+	// shithubdPath is the running binary, baked into hook shims by
48
+	// repos.Create. os.Executable can rarely fail (e.g. exec name
49
+	// stripped); when it does we fall back to "shithubd" on PATH so
50
+	// hook shims still resolve in unusual environments.
51
+	shithubdPath := "shithubd"
52
+	if exe, err := os.Executable(); err == nil {
53
+		if abs, err := filepath.Abs(exe); err == nil {
54
+			shithubdPath = abs
55
+		}
56
+	}
57
+
46
 	return repoh.New(repoh.Deps{
58
 	return repoh.New(repoh.Deps{
47
-		Logger:  logger,
59
+		Logger:       logger,
48
-		Render:  rr,
60
+		Render:       rr,
49
-		Pool:    pool,
61
+		Pool:         pool,
50
-		RepoFS:  rfs,
62
+		RepoFS:       rfs,
51
-		Audit:   audit.NewRecorder(),
63
+		Audit:        audit.NewRecorder(),
52
-		Limiter: throttle.NewLimiter(),
64
+		Limiter:      throttle.NewLimiter(),
65
+		ShithubdPath: shithubdPath,
53
 		CloneURLs: repoh.CloneURLs{
66
 		CloneURLs: repoh.CloneURLs{
54
 			BaseURL:    cfg.Auth.BaseURL,
67
 			BaseURL:    cfg.Auth.BaseURL,
55
 			SSHEnabled: false, // S12/S13 will flip this when SSH service ships.
68
 			SSHEnabled: false, // S12/S13 will flip this when SSH service ships.