Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKarthik Nayak <knayak@gitlab.com>2023-12-04 22:59:46 +0300
committerKarthik Nayak <knayak@gitlab.com>2023-12-05 11:57:07 +0300
commit3a1521bfcc8dd5167f34f750026ea5827a094279 (patch)
treebd4a36795c22505afe406da74b7b37d51fe88960
parentfc648f1822fb37e3072b96fdfe9af7e7f3b5b620 (diff)
hook: Add `ProcReceiveHook` to `GitLabHookManager`kn-proc-receive-registry
Add a new function `ProcReceiveHook` to `GitLabHookManager`. The `ProcReceiveHook` is used to intercept git-receive-pack(1)'s execute-commands code. This allows us to intercept the reference updates and avoid writing directly to the disk. The intercepted updates are then bundled into `procReceiveHookInvocation` and added to the registry. The RPC which invoked git-receive-pack(1) in the first place picks up the invocation from the RPC and accepts/rejects individual references. We also add the `ProcReceiveRegistry` to the `GitLabHookManager` so that the `ProcReceiveHook` has access to the registry.
-rw-r--r--internal/cli/gitaly/serve.go11
-rw-r--r--internal/cli/gitaly/subcmd_check.go11
-rw-r--r--internal/gitaly/hook/manager.go31
-rw-r--r--internal/gitaly/hook/postreceive_test.go14
-rw-r--r--internal/gitaly/hook/prereceive_test.go15
-rw-r--r--internal/gitaly/hook/procreceive.go164
-rw-r--r--internal/gitaly/hook/procreceive_test.go267
-rw-r--r--internal/gitaly/hook/transactions_test.go4
-rw-r--r--internal/gitaly/hook/update_test.go4
-rw-r--r--internal/gitaly/server/auth_test.go2
-rw-r--r--internal/gitaly/service/hook/testhelper_test.go2
-rw-r--r--internal/gitaly/service/operations/merge_branch_test.go4
-rw-r--r--internal/testhelper/testserver/gitaly.go10
13 files changed, 509 insertions, 30 deletions
diff --git a/internal/cli/gitaly/serve.go b/internal/cli/gitaly/serve.go
index 9db91a758..beee3407a 100644
--- a/internal/cli/gitaly/serve.go
+++ b/internal/cli/gitaly/serve.go
@@ -253,7 +253,16 @@ func run(cfg config.Cfg, logger log.Logger) error {
}
prometheus.MustRegister(gitlabClient)
- hookManager = hook.NewManager(cfg, locator, logger, gitCmdFactory, transactionManager, gitlabClient, hook.NewTransactionRegistry(txRegistry))
+ hookManager = hook.NewManager(
+ cfg,
+ locator,
+ logger,
+ gitCmdFactory,
+ transactionManager,
+ gitlabClient,
+ hook.NewTransactionRegistry(txRegistry),
+ hook.NewProcReceiveRegistry(),
+ )
}
conns := client.NewPool(
diff --git a/internal/cli/gitaly/subcmd_check.go b/internal/cli/gitaly/subcmd_check.go
index 5ca3419d1..7f1cbb397 100644
--- a/internal/cli/gitaly/subcmd_check.go
+++ b/internal/cli/gitaly/subcmd_check.go
@@ -74,5 +74,14 @@ func checkAPI(cfg config.Cfg, logger log.Logger) (*gitlab.CheckInfo, error) {
}
defer cleanup()
- return hook.NewManager(cfg, config.NewLocator(cfg), logger, gitCmdFactory, nil, gitlabAPI, hook.NewTransactionRegistry(storagemgr.NewTransactionRegistry())).Check(context.Background())
+ return hook.NewManager(
+ cfg,
+ config.NewLocator(cfg),
+ logger,
+ gitCmdFactory,
+ nil,
+ gitlabAPI,
+ hook.NewTransactionRegistry(storagemgr.NewTransactionRegistry()),
+ hook.NewProcReceiveRegistry(),
+ ).Check(context.Background())
}
diff --git a/internal/gitaly/hook/manager.go b/internal/gitaly/hook/manager.go
index dd4b2a5d5..1cdd18e33 100644
--- a/internal/gitaly/hook/manager.go
+++ b/internal/gitaly/hook/manager.go
@@ -78,13 +78,14 @@ func NewTransactionRegistry(txRegistry *storagemgr.TransactionRegistry) Transact
// GitLabHookManager is a hook manager containing Git hook business logic. It
// uses the GitLab API to authenticate and track ongoing hook calls.
type GitLabHookManager struct {
- cfg config.Cfg
- locator storage.Locator
- logger log.Logger
- gitCmdFactory git.CommandFactory
- txManager transaction.Manager
- gitlabClient gitlab.Client
- txRegistry TransactionRegistry
+ cfg config.Cfg
+ locator storage.Locator
+ logger log.Logger
+ gitCmdFactory git.CommandFactory
+ txManager transaction.Manager
+ gitlabClient gitlab.Client
+ txRegistry TransactionRegistry
+ procReceiveRegistry *ProcReceiveRegistry
}
// NewManager returns a new hook manager
@@ -96,14 +97,16 @@ func NewManager(
txManager transaction.Manager,
gitlabClient gitlab.Client,
txRegistry TransactionRegistry,
+ procReceiveRegistry *ProcReceiveRegistry,
) *GitLabHookManager {
return &GitLabHookManager{
- cfg: cfg,
- locator: locator,
- logger: logger,
- gitCmdFactory: gitCmdFactory,
- txManager: txManager,
- gitlabClient: gitlabClient,
- txRegistry: txRegistry,
+ cfg: cfg,
+ locator: locator,
+ logger: logger,
+ gitCmdFactory: gitCmdFactory,
+ txManager: txManager,
+ gitlabClient: gitlabClient,
+ txRegistry: txRegistry,
+ procReceiveRegistry: procReceiveRegistry,
}
}
diff --git a/internal/gitaly/hook/postreceive_test.go b/internal/gitaly/hook/postreceive_test.go
index 808035831..31fc8948a 100644
--- a/internal/gitaly/hook/postreceive_test.go
+++ b/internal/gitaly/hook/postreceive_test.go
@@ -84,7 +84,7 @@ func TestPostReceive_customHook(t *testing.T) {
txManager := transaction.NewTrackingManager()
hookManager := NewManager(cfg, locator, testhelper.SharedLogger(t), gitCmdFactory, txManager, gitlab.NewMockClient(
t, gitlab.MockAllowed, gitlab.MockPreReceive, gitlab.MockPostReceive,
- ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()), NewProcReceiveRegistry())
receiveHooksPayload := &git.UserDetails{
UserID: "1234",
@@ -381,7 +381,15 @@ func TestPostReceive_gitlab(t *testing.T) {
},
}
- hookManager := NewManager(cfg, config.NewLocator(cfg), testhelper.SharedLogger(t), gittest.NewCommandFactory(t, cfg), transaction.NewManager(cfg, testhelper.SharedLogger(t), backchannel.NewRegistry()), &gitlabAPI, NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ hookManager := NewManager(
+ cfg,
+ config.NewLocator(cfg),
+ testhelper.SharedLogger(t),
+ gittest.NewCommandFactory(t, cfg),
+ transaction.NewManager(cfg, testhelper.SharedLogger(t), backchannel.NewRegistry()),
+ &gitlabAPI, NewTransactionRegistry(storagemgr.NewTransactionRegistry()),
+ NewProcReceiveRegistry(),
+ )
gittest.WriteCustomHook(t, repoPath, "post-receive", []byte("#!/bin/sh\necho hook called\n"))
@@ -419,7 +427,7 @@ func TestPostReceive_quarantine(t *testing.T) {
hookManager := NewManager(cfg, config.NewLocator(cfg), testhelper.SharedLogger(t), gittest.NewCommandFactory(t, cfg), nil, gitlab.NewMockClient(
t, gitlab.MockAllowed, gitlab.MockPreReceive, gitlab.MockPostReceive,
- ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()), NewProcReceiveRegistry())
gittest.WriteCustomHook(t, repoPath, "post-receive", []byte(fmt.Sprintf(
`#!/bin/sh
diff --git a/internal/gitaly/hook/prereceive_test.go b/internal/gitaly/hook/prereceive_test.go
index 77f6bb4a1..1bfab9447 100644
--- a/internal/gitaly/hook/prereceive_test.go
+++ b/internal/gitaly/hook/prereceive_test.go
@@ -43,7 +43,7 @@ func TestPrereceive_customHooks(t *testing.T) {
txManager := transaction.NewTrackingManager()
hookManager := NewManager(cfg, locator, testhelper.SharedLogger(t), gitCmdFactory, txManager, gitlab.NewMockClient(
t, gitlab.MockAllowed, gitlab.MockPreReceive, gitlab.MockPostReceive,
- ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()), NewProcReceiveRegistry())
receiveHooksPayload := &git.UserDetails{
UserID: "1234",
@@ -228,7 +228,7 @@ func TestPrereceive_quarantine(t *testing.T) {
hookManager := NewManager(cfg, config.NewLocator(cfg), testhelper.SharedLogger(t), gittest.NewCommandFactory(t, cfg), nil, gitlab.NewMockClient(
t, gitlab.MockAllowed, gitlab.MockPreReceive, gitlab.MockPostReceive,
- ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()), NewProcReceiveRegistry())
//nolint:gitaly-linters
gittest.WriteCustomHook(t, repoPath, "pre-receive", []byte(fmt.Sprintf(
@@ -419,7 +419,16 @@ func TestPrereceive_gitlab(t *testing.T) {
},
}
- hookManager := NewManager(cfg, config.NewLocator(cfg), testhelper.SharedLogger(t), gittest.NewCommandFactory(t, cfg), transaction.NewManager(cfg, testhelper.SharedLogger(t), backchannel.NewRegistry()), &gitlabAPI, NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ hookManager := NewManager(
+ cfg,
+ config.NewLocator(cfg),
+ testhelper.SharedLogger(t),
+ gittest.NewCommandFactory(t, cfg),
+ transaction.NewManager(cfg, testhelper.SharedLogger(t), backchannel.NewRegistry()),
+ &gitlabAPI,
+ NewTransactionRegistry(storagemgr.NewTransactionRegistry()),
+ NewProcReceiveRegistry(),
+ )
gittest.WriteCustomHook(t, repoPath, "pre-receive", []byte("#!/bin/sh\necho called\n"))
diff --git a/internal/gitaly/hook/procreceive.go b/internal/gitaly/hook/procreceive.go
new file mode 100644
index 000000000..44b5ae7c6
--- /dev/null
+++ b/internal/gitaly/hook/procreceive.go
@@ -0,0 +1,164 @@
+package hook
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git/pktline"
+ "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
+)
+
+// ProcReceiveHook is used to intercept git-receive-pack(1)'s execute-commands code.
+// This allows us to intercept the reference updates and avoid writing directly to
+// the disk. The intercepted updates are then bundled into `procReceiveHookInvocation`
+// and added to the registry. The RPC which invoked git-receive-pack(1) in the first
+// place picks up the invocation from the RPC and accepts/rejects individual references.
+func (m *GitLabHookManager) ProcReceiveHook(ctx context.Context, repo *gitalypb.Repository, env []string, stdin io.Reader, stdout, stderr io.Writer) error {
+ payload, err := git.HooksPayloadFromEnv(env)
+ if err != nil {
+ return fmt.Errorf("extracting hooks payload: %w", err)
+ }
+
+ // This hook only works when there is a transaction present.
+ if payload.TransactionID == 0 {
+ return fmt.Errorf("no transaction found in payload")
+ }
+
+ scanner := pktline.NewScanner(stdin)
+
+ // Version and feature negotiation.
+ if !scanner.Scan() {
+ return fmt.Errorf("expected input: %w", scanner.Err())
+ }
+
+ data, err := pktline.Payload(scanner.Bytes())
+ if err != nil {
+ return fmt.Errorf("receiving header: %w", err)
+ }
+
+ var featureRequests *procReceiveFeatureRequests
+ after, ok := bytes.CutPrefix(data, []byte("version=1\000"))
+ if !ok {
+ return fmt.Errorf("unsupported version: %s", data)
+ }
+
+ featureRequests, err = parseFeatureRequest(after)
+ if err != nil {
+ return fmt.Errorf("parsing feature request: %w", err)
+ }
+
+ if !scanner.Scan() {
+ return fmt.Errorf("expected input: %w", scanner.Err())
+ }
+
+ if !pktline.IsFlush(scanner.Bytes()) {
+ return fmt.Errorf("expected pkt flush")
+ }
+
+ if err := scanner.Err(); err != nil {
+ return fmt.Errorf("parsing stdin: %w", err)
+ }
+
+ if _, err := pktline.WriteString(stdout, fmt.Sprintf("version=1\000%s", featureRequests)); err != nil {
+ return fmt.Errorf("writing version: %w", err)
+ }
+
+ if err := pktline.WriteFlush(stdout); err != nil {
+ return fmt.Errorf("flushing version: %w", err)
+ }
+
+ updates := []ReferenceUpdate{}
+ for scanner.Scan() {
+ bytes := scanner.Bytes()
+
+ // When all reference updates are transmitted, we expect a flush.
+ if pktline.IsFlush(bytes) {
+ break
+ }
+
+ data, err := pktline.Payload(bytes)
+ if err != nil {
+ return fmt.Errorf("receiving reference update: %w", err)
+ }
+
+ update, err := parseRefUpdate(data)
+ if err != nil {
+ return fmt.Errorf("parse reference update: %w", err)
+ }
+ updates = append(updates, update)
+ }
+
+ invocation := newProcReceiveHookInvocation(
+ featureRequests.atomic,
+ payload.TransactionID,
+ updates,
+ func(referenceName git.ReferenceName) error {
+ if _, err := pktline.WriteString(stdout, fmt.Sprintf("ok %s", referenceName)); err != nil {
+ return fmt.Errorf("write ref %s ok: %w", referenceName, err)
+ }
+
+ return nil
+ },
+ func(referenceName git.ReferenceName, reason string) error {
+ if _, err := pktline.WriteString(stdout, fmt.Sprintf("ng %s %s", referenceName, reason)); err != nil {
+ return fmt.Errorf("write ref %s ng: %w", referenceName, err)
+ }
+
+ return nil
+ },
+ func() error {
+ if err := pktline.WriteFlush(stdout); err != nil {
+ return fmt.Errorf("flushing updates: %w", err)
+ }
+
+ return nil
+ })
+
+ m.procReceiveRegistry.set(invocation)
+
+ return nil
+}
+
+func parseRefUpdate(data []byte) (ReferenceUpdate, error) {
+ var update ReferenceUpdate
+
+ split := bytes.Split(data, []byte(" "))
+ if len(split) != 3 {
+ return update, fmt.Errorf("unknown ref update format: %s", split)
+ }
+
+ update.Ref = git.ReferenceName(split[2])
+ update.OldOID = git.ObjectID(split[0])
+ update.NewOID = git.ObjectID(split[1])
+
+ return update, nil
+}
+
+type procReceiveFeatureRequests struct {
+ atomic bool
+}
+
+func (r *procReceiveFeatureRequests) String() string {
+ s := ""
+ if r.atomic {
+ s = "atomic"
+ }
+
+ return s
+}
+
+// parseFeatureRequest parses the features requested.
+func parseFeatureRequest(data []byte) (*procReceiveFeatureRequests, error) {
+ var featureRequests procReceiveFeatureRequests
+
+ for _, feature := range bytes.Split(data, []byte(" ")) {
+ if bytes.Equal(feature, []byte("atomic")) {
+ featureRequests.atomic = true
+ }
+ }
+
+ return &featureRequests, nil
+}
diff --git a/internal/gitaly/hook/procreceive_test.go b/internal/gitaly/hook/procreceive_test.go
new file mode 100644
index 000000000..a5cf5823c
--- /dev/null
+++ b/internal/gitaly/hook/procreceive_test.go
@@ -0,0 +1,267 @@
+package hook
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/featureflag"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git/gittest"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git/pktline"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage/storagemgr"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/transaction"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/gitlab"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testcfg"
+)
+
+func TestProcReceiveHook(t *testing.T) {
+ t.Parallel()
+
+ ctx := testhelper.Context(t)
+ cfg := testcfg.Build(t)
+
+ repo, _ := gittest.CreateRepository(t, ctx, cfg, gittest.CreateRepositoryConfig{
+ SkipCreationViaService: true,
+ })
+
+ gitCmdFactory := gittest.NewCommandFactory(t, cfg)
+ locator := config.NewLocator(cfg)
+
+ txManager := transaction.NewTrackingManager()
+
+ receiveHooksPayload := &git.UserDetails{
+ UserID: "1234",
+ Username: "user",
+ Protocol: "web",
+ }
+
+ payload, err := git.NewHooksPayload(
+ cfg,
+ repo,
+ gittest.DefaultObjectHash,
+ nil,
+ receiveHooksPayload,
+ git.PreReceiveHook,
+ featureflag.FromContext(ctx),
+ 1,
+ ).Env()
+ require.NoError(t, err)
+
+ procReceiveRegistry := NewProcReceiveRegistry()
+
+ hookManager := NewManager(
+ cfg,
+ locator,
+ testhelper.SharedLogger(t),
+ gitCmdFactory,
+ txManager,
+ gitlab.NewMockClient(t, gitlab.MockAllowed, gitlab.MockPreReceive, gitlab.MockPostReceive),
+ NewTransactionRegistry(storagemgr.NewTransactionRegistry()),
+ procReceiveRegistry,
+ )
+
+ type setupData struct {
+ env []string
+ ctx context.Context
+ stdin string
+ expectedErr error
+ expectedStdout string
+ expectedUpdates []ReferenceUpdate
+ expectedAtomic bool
+ invocationSteps func(invocation ProcReceiveHookInvocation) error
+ }
+
+ for _, tc := range []struct {
+ desc string
+ setup func(t *testing.T, ctx context.Context) setupData
+ }{
+ {
+ desc: "no payload",
+ setup: func(t *testing.T, ctx context.Context) setupData {
+ return setupData{
+ env: []string{},
+ ctx: ctx,
+ expectedErr: fmt.Errorf("extracting hooks payload: %w", errors.New("no hooks payload found in environment")),
+ }
+ },
+ },
+ {
+ desc: "invalid version",
+ setup: func(t *testing.T, ctx context.Context) setupData {
+ var stdin bytes.Buffer
+ _, err = pktline.WriteString(&stdin, "version=2")
+ require.NoError(t, err)
+
+ return setupData{
+ env: []string{payload},
+ ctx: ctx,
+ stdin: stdin.String(),
+ expectedErr: errors.New("unsupported version: version=2"),
+ }
+ },
+ },
+ {
+ desc: "single reference with atomic",
+ setup: func(t *testing.T, ctx context.Context) setupData {
+ var stdin bytes.Buffer
+ _, err = pktline.WriteString(&stdin, "version=1\000push-options atomic")
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdin)
+ require.NoError(t, err)
+ _, err = pktline.WriteString(&stdin, fmt.Sprintf("%s %s %s",
+ gittest.DefaultObjectHash.ZeroOID, gittest.DefaultObjectHash.EmptyTreeOID, "refs/heads/main"))
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdin)
+ require.NoError(t, err)
+
+ var stdout bytes.Buffer
+ _, err = pktline.WriteString(&stdout, "version=1\000atomic")
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdout)
+ _, err = pktline.WriteString(&stdout, "ok refs/heads/main")
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdout)
+
+ return setupData{
+ env: []string{payload},
+ ctx: ctx,
+ stdin: stdin.String(),
+ expectedStdout: stdout.String(),
+ expectedAtomic: true,
+ expectedUpdates: []ReferenceUpdate{
+ {
+ Ref: "refs/heads/main",
+ OldOID: gittest.DefaultObjectHash.ZeroOID,
+ NewOID: gittest.DefaultObjectHash.EmptyTreeOID,
+ },
+ },
+ invocationSteps: func(invocation ProcReceiveHookInvocation) error {
+ require.NoError(t, invocation.AcceptUpdate("refs/heads/main"))
+ return invocation.Close()
+ },
+ }
+ },
+ },
+ {
+ desc: "single reference without atomic",
+ setup: func(t *testing.T, ctx context.Context) setupData {
+ var stdin bytes.Buffer
+ _, err = pktline.WriteString(&stdin, "version=1\000push-options")
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdin)
+ require.NoError(t, err)
+ _, err = pktline.WriteString(&stdin, fmt.Sprintf("%s %s %s",
+ gittest.DefaultObjectHash.ZeroOID, gittest.DefaultObjectHash.EmptyTreeOID, "refs/heads/main"))
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdin)
+ require.NoError(t, err)
+
+ var stdout bytes.Buffer
+ _, err = pktline.WriteString(&stdout, "version=1\000")
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdout)
+ _, err = pktline.WriteString(&stdout, "ok refs/heads/main")
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdout)
+
+ return setupData{
+ env: []string{payload},
+ ctx: ctx,
+ stdin: stdin.String(),
+ expectedStdout: stdout.String(),
+ expectedUpdates: []ReferenceUpdate{
+ {
+ Ref: "refs/heads/main",
+ OldOID: gittest.DefaultObjectHash.ZeroOID,
+ NewOID: gittest.DefaultObjectHash.EmptyTreeOID,
+ },
+ },
+ invocationSteps: func(invocation ProcReceiveHookInvocation) error {
+ require.NoError(t, invocation.AcceptUpdate("refs/heads/main"))
+ return invocation.Close()
+ },
+ }
+ },
+ },
+ {
+ desc: "multiple references",
+ setup: func(t *testing.T, ctx context.Context) setupData {
+ var stdin bytes.Buffer
+ _, err = pktline.WriteString(&stdin, "version=1\000push-options")
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdin)
+ require.NoError(t, err)
+ _, err = pktline.WriteString(&stdin, fmt.Sprintf("%s %s %s",
+ gittest.DefaultObjectHash.ZeroOID, gittest.DefaultObjectHash.EmptyTreeOID, "refs/heads/main"))
+ require.NoError(t, err)
+ _, err = pktline.WriteString(&stdin, fmt.Sprintf("%s %s %s",
+ gittest.DefaultObjectHash.ZeroOID, gittest.DefaultObjectHash.EmptyTreeOID, "refs/heads/branch"))
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdin)
+ require.NoError(t, err)
+
+ var stdout bytes.Buffer
+ _, err = pktline.WriteString(&stdout, "version=1\000")
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdout)
+ _, err = pktline.WriteString(&stdout, "ok refs/heads/main")
+ _, err = pktline.WriteString(&stdout, "ng refs/heads/branch for fun")
+ require.NoError(t, err)
+ err = pktline.WriteFlush(&stdout)
+
+ return setupData{
+ env: []string{payload},
+ ctx: ctx,
+ stdin: stdin.String(),
+ expectedStdout: stdout.String(),
+ expectedUpdates: []ReferenceUpdate{
+ {
+ Ref: "refs/heads/main",
+ OldOID: gittest.DefaultObjectHash.ZeroOID,
+ NewOID: gittest.DefaultObjectHash.EmptyTreeOID,
+ },
+ {
+ Ref: "refs/heads/branch",
+ OldOID: gittest.DefaultObjectHash.ZeroOID,
+ NewOID: gittest.DefaultObjectHash.EmptyTreeOID,
+ },
+ },
+ invocationSteps: func(invocation ProcReceiveHookInvocation) error {
+ require.NoError(t, invocation.AcceptUpdate("refs/heads/main"))
+ require.NoError(t, invocation.RejectUpdate("refs/heads/branch", "for fun"))
+ return invocation.Close()
+ },
+ }
+ },
+ },
+ } {
+ tc := tc
+
+ t.Run(tc.desc, func(t *testing.T) {
+ setup := tc.setup(t, ctx)
+
+ var stdout, stderr bytes.Buffer
+ err := hookManager.ProcReceiveHook(setup.ctx, repo, setup.env, strings.NewReader(setup.stdin), &stdout, &stderr)
+ if err != nil || setup.expectedErr != nil {
+ require.Equal(t, setup.expectedErr, err)
+ return
+ }
+
+ invocation := procReceiveRegistry.Get(1)
+ require.Equal(t, setup.expectedAtomic, invocation.Atomic())
+
+ updates := invocation.ReferenceUpdates()
+ require.Equal(t, setup.expectedUpdates, updates)
+
+ require.NoError(t, setup.invocationSteps(invocation))
+ require.Equal(t, setup.expectedStdout, stdout.String())
+ })
+ }
+}
diff --git a/internal/gitaly/hook/transactions_test.go b/internal/gitaly/hook/transactions_test.go
index 568cef0f8..8bcaa97f3 100644
--- a/internal/gitaly/hook/transactions_test.go
+++ b/internal/gitaly/hook/transactions_test.go
@@ -38,7 +38,7 @@ func TestHookManager_stopCalled(t *testing.T) {
var mockTxMgr transaction.MockManager
hookManager := NewManager(cfg, config.NewLocator(cfg), testhelper.SharedLogger(t), gittest.NewCommandFactory(t, cfg), &mockTxMgr, gitlab.NewMockClient(
t, gitlab.MockAllowed, gitlab.MockPreReceive, gitlab.MockPostReceive,
- ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()), NewProcReceiveRegistry())
hooksPayload, err := git.NewHooksPayload(
cfg,
@@ -144,7 +144,7 @@ func TestHookManager_contextCancellationCancelsVote(t *testing.T) {
hookManager := NewManager(cfg, config.NewLocator(cfg), testhelper.SharedLogger(t), gittest.NewCommandFactory(t, cfg), &mockTxMgr, gitlab.NewMockClient(
t, gitlab.MockAllowed, gitlab.MockPreReceive, gitlab.MockPostReceive,
- ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()), NewProcReceiveRegistry())
hooksPayload, err := git.NewHooksPayload(
cfg,
diff --git a/internal/gitaly/hook/update_test.go b/internal/gitaly/hook/update_test.go
index 8d8ab8e30..8f446dd16 100644
--- a/internal/gitaly/hook/update_test.go
+++ b/internal/gitaly/hook/update_test.go
@@ -41,7 +41,7 @@ func TestUpdate_customHooks(t *testing.T) {
txManager := transaction.NewTrackingManager()
hookManager := NewManager(cfg, locator, testhelper.SharedLogger(t), gitCmdFactory, txManager, gitlab.NewMockClient(
t, gitlab.MockAllowed, gitlab.MockPreReceive, gitlab.MockPostReceive,
- ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()), NewProcReceiveRegistry())
receiveHooksPayload := &git.UserDetails{
UserID: "1234",
@@ -258,7 +258,7 @@ func TestUpdate_quarantine(t *testing.T) {
hookManager := NewManager(cfg, config.NewLocator(cfg), testhelper.SharedLogger(t), gittest.NewCommandFactory(t, cfg), nil, gitlab.NewMockClient(
t, gitlab.MockAllowed, gitlab.MockPreReceive, gitlab.MockPostReceive,
- ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ ), NewTransactionRegistry(storagemgr.NewTransactionRegistry()), NewProcReceiveRegistry())
//nolint:gitaly-linters
gittest.WriteCustomHook(t, repoPath, "update", []byte(fmt.Sprintf(
diff --git a/internal/gitaly/server/auth_test.go b/internal/gitaly/server/auth_test.go
index 1c8ff4d0d..835442db9 100644
--- a/internal/gitaly/server/auth_test.go
+++ b/internal/gitaly/server/auth_test.go
@@ -195,7 +195,7 @@ func runServer(t *testing.T, cfg config.Cfg) string {
gitCmdFactory := gittest.NewCommandFactory(t, cfg)
hookManager := hook.NewManager(cfg, locator, logger, gitCmdFactory, txManager, gitlab.NewMockClient(
t, gitlab.MockAllowed, gitlab.MockPreReceive, gitlab.MockPostReceive,
- ), hook.NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ ), hook.NewTransactionRegistry(storagemgr.NewTransactionRegistry()), hook.NewProcReceiveRegistry())
catfileCache := catfile.NewCache(cfg)
t.Cleanup(catfileCache.Stop)
diskCache := cache.New(cfg, locator, logger)
diff --git a/internal/gitaly/service/hook/testhelper_test.go b/internal/gitaly/service/hook/testhelper_test.go
index 90064ec67..16b9d1f14 100644
--- a/internal/gitaly/service/hook/testhelper_test.go
+++ b/internal/gitaly/service/hook/testhelper_test.go
@@ -59,7 +59,7 @@ func runHooksServerWithTransactionRegistry(tb testing.TB, cfg config.Cfg, opts [
return testserver.RunGitalyServer(tb, cfg, func(srv *grpc.Server, deps *service.Dependencies) {
if txRegistry != nil {
- deps.GitalyHookManager = gitalyhook.NewManager(deps.GetCfg(), deps.GetLocator(), deps.GetLogger(), deps.GetGitCmdFactory(), deps.GetTxManager(), deps.GetGitlabClient(), txRegistry)
+ deps.GitalyHookManager = gitalyhook.NewManager(deps.GetCfg(), deps.GetLocator(), deps.GetLogger(), deps.GetGitCmdFactory(), deps.GetTxManager(), deps.GetGitlabClient(), txRegistry, gitalyhook.NewProcReceiveRegistry())
}
hookServer := NewServer(deps)
diff --git a/internal/gitaly/service/operations/merge_branch_test.go b/internal/gitaly/service/operations/merge_branch_test.go
index aaf450194..e0e681722 100644
--- a/internal/gitaly/service/operations/merge_branch_test.go
+++ b/internal/gitaly/service/operations/merge_branch_test.go
@@ -1148,7 +1148,9 @@ func testUserMergeBranchAllowed(t *testing.T, ctx context.Context) {
},
gitlab.MockPreReceive,
gitlab.MockPostReceive,
- ), hook.NewTransactionRegistry(storagemgr.NewTransactionRegistry()))
+ ), hook.NewTransactionRegistry(storagemgr.NewTransactionRegistry()),
+ hook.NewProcReceiveRegistry(),
+ )
ctx, cfg, client := setupOperationsServiceWithCfg(
t, ctx, cfg,
diff --git a/internal/testhelper/testserver/gitaly.go b/internal/testhelper/testserver/gitaly.go
index 9af5d742b..d7210b6b8 100644
--- a/internal/testhelper/testserver/gitaly.go
+++ b/internal/testhelper/testserver/gitaly.go
@@ -324,7 +324,15 @@ func (gsd *gitalyServerDeps) createDependencies(tb testing.TB, cfg config.Cfg) *
}
if gsd.hookMgr == nil {
- gsd.hookMgr = hook.NewManager(cfg, gsd.locator, gsd.logger, gsd.gitCmdFactory, gsd.txMgr, gsd.gitlabClient, hook.NewTransactionRegistry(gsd.transactionRegistry))
+ gsd.hookMgr = hook.NewManager(
+ cfg, gsd.locator,
+ gsd.logger,
+ gsd.gitCmdFactory,
+ gsd.txMgr,
+ gsd.gitlabClient,
+ hook.NewTransactionRegistry(gsd.transactionRegistry),
+ hook.NewProcReceiveRegistry(),
+ )
}
if gsd.catfileCache == nil {