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
}
|