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

update_with_hooks.go « operations « service « gitaly « internal - gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 446a446948c0e5b1b1462931496d57c37524b569 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package operations

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"strings"

	"gitlab.com/gitlab-org/gitaly/internal/git"
	"gitlab.com/gitlab-org/gitaly/internal/git/updateref"
	"gitlab.com/gitlab-org/gitaly/internal/gitaly/hook"
	"gitlab.com/gitlab-org/gitaly/internal/helper"
	"gitlab.com/gitlab-org/gitaly/internal/metadata/featureflag"
	"gitlab.com/gitlab-org/gitaly/internal/praefect/metadata"
	"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
)

type preReceiveError struct {
	message string
}

func (e preReceiveError) Error() string {
	return e.message
}

type updateRefError struct {
	reference string
}

func (e updateRefError) Error() string {
	return fmt.Sprintf("Could not update %s. Please refresh and try again.", e.reference)
}

func hookErrorMessage(sout string, serr string, err error) string {
	if err != nil && errors.As(err, &hook.NotAllowedError{}) {
		return err.Error()
	}

	if len(strings.TrimSpace(serr)) > 0 {
		return serr
	}

	return sout
}

func (s *Server) updateReferenceWithHooks(
	ctx context.Context,
	repo *gitalypb.Repository,
	user *gitalypb.User,
	reference git.ReferenceName,
	newrev, oldrev git.ObjectID,
) error {
	transaction, praefect, err := metadata.TransactionMetadataFromContext(ctx)
	if err != nil {
		return err
	}

	payload, err := git.NewHooksPayload(s.cfg, repo, transaction, praefect, &git.ReceiveHooksPayload{
		UserID:   user.GetGlId(),
		Username: user.GetGlUsername(),
		Protocol: "web",
	}, git.ReceivePackHooks, featureflag.RawFromContext(ctx)).Env()
	if err != nil {
		return err
	}

	if reference == "" {
		return helper.ErrInternalf("updateReferenceWithHooks: got no reference")
	}
	if err := git.ValidateObjectID(oldrev.String()); err != nil {
		return helper.ErrInternalf("updateReferenceWithHooks: got invalid old value: %w", err)
	}
	if err := git.ValidateObjectID(newrev.String()); err != nil {
		return helper.ErrInternalf("updateReferenceWithHooks: got invalid new value: %w", err)
	}

	env := []string{
		payload,
	}

	changes := fmt.Sprintf("%s %s %s\n", oldrev, newrev, reference)
	var stdout, stderr bytes.Buffer

	if err := s.hookManager.PreReceiveHook(ctx, repo, nil, env, strings.NewReader(changes), &stdout, &stderr); err != nil {
		msg := hookErrorMessage(stdout.String(), stderr.String(), err)
		return preReceiveError{message: msg}
	}
	if err := s.hookManager.UpdateHook(ctx, repo, reference.String(), oldrev.String(), newrev.String(), env, &stdout, &stderr); err != nil {
		msg := hookErrorMessage(stdout.String(), stderr.String(), err)
		return preReceiveError{message: msg}
	}

	if err := s.hookManager.ReferenceTransactionHook(ctx, hook.ReferenceTransactionPrepared, env, strings.NewReader(changes)); err != nil {
		return preReceiveError{message: err.Error()}
	}

	// We are already manually invoking the reference-transaction hook, so there is no need to
	// set up hooks again here. One could argue that it would be easier to just have git handle
	// execution of the reference-transaction hook. But unfortunately, it has proven to be
	// problematic: if we queue a deletion, and the reference to be deleted exists both as
	// packed-ref and as loose ref, then we would see two transactions: first a transaction
	// deleting the packed-ref which would otherwise get unshadowed by deleting the loose ref,
	// and only then do we see the deletion of the loose ref. So this depends on how well a repo
	// is packed, which is obviously a bad thing as Gitaly nodes may be differently packed. We
	// thus continue to manually drive the reference-transaction hook here, which doesn't have
	// this problem.
	updater, err := updateref.New(ctx, s.cfg, s.gitCmdFactory, repo, updateref.WithDisabledTransactions())
	if err != nil {
		return err
	}

	if err := updater.Update(reference, newrev.String(), oldrev.String()); err != nil {
		return err
	}

	if err := updater.Wait(); err != nil {
		return updateRefError{reference: reference.String()}
	}

	if err := s.hookManager.PostReceiveHook(ctx, repo, nil, env, strings.NewReader(changes), &stdout, &stderr); err != nil {
		msg := hookErrorMessage(stdout.String(), stderr.String(), err)
		return preReceiveError{message: msg}
	}

	return nil
}