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
|
package gitaly
import (
"context"
"fmt"
"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/gitaly/vendored/gitalypb"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/encoding"
protoenc "google.golang.org/grpc/encoding/proto"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
const (
DefaultBranch = "HEAD"
)
var (
_ encoding.Codec = (*pollingCodec)(nil)
)
// PollerInterface does the following:
// - polls ref advertisement for updates to the repository
// - detects which is the main branch, if branch or tag name is not specified
// - compares the commit id the branch or tag is referring to with the last processed one
// - returns the information about the change
type PollerInterface interface {
// Poll performs a poll on the repository.
// revision can be a branch name or a tag.
// Poll returns a wrapped context.Canceled, context.DeadlineExceeded or gRPC error if ctx signals done and interrupts a running gRPC call.
// Poll returns *Error when an error occurs.
Poll(ctx context.Context, repo *gitalypb.Repository, lastProcessedCommitID, refName string) (*PollInfo, error)
}
type Poller struct {
Client gitalypb.CommitServiceClient
Features map[string]string
}
type PollInfo struct {
CommitID string
UpdateAvailable bool
// RefNotFound is true when no commits were found for the provided ref.
RefNotFound bool
}
// Poll searched the given repository for the given fullRefName and returns a PollInfo containing a resolved Commit Object ID.
// Valid fullRefNames are:
// * `refs/heads/*` => for branches
// * `refs/tags/*` => for tags
// * `HEAD` => for the repository's default branch
func (p *Poller) Poll(ctx context.Context, repo *gitalypb.Repository, lastProcessedCommitID, fullRefName string) (*PollInfo, error) {
ctx = appendFeatureFlagsToContext(ctx, p.Features)
commit, err := p.Client.FindCommit(ctx, &gitalypb.FindCommitRequest{
Repository: repo,
Revision: []byte(fullRefName),
}, grpc.ForceCodec(pollingCodec{}))
if err != nil {
switch status.Code(err) { //nolint:exhaustive
case codes.NotFound:
return nil, NewNotFoundError("FindCommit", fullRefName)
default:
return nil, NewRPCError(err, "FindCommit", fullRefName)
}
}
if commit.Commit == nil {
return &PollInfo{
RefNotFound: true,
}, nil
}
return &PollInfo{
CommitID: commit.Commit.Id,
UpdateAvailable: commit.Commit.Id != lastProcessedCommitID,
}, nil
}
// pollingCodec avoids unmarshaling fields of the FindCommitResponse message that are not needed.
// This reduces CPU usage and generates much less garbage on the heap.
type pollingCodec struct {
}
func (c pollingCodec) Marshal(v any) ([]byte, error) {
return proto.Marshal(v.(proto.Message))
}
func (c pollingCodec) Unmarshal(data []byte, v any) error {
typedV, ok := v.(*gitalypb.FindCommitResponse)
if !ok {
return fmt.Errorf("unexpected message type: %T", v)
}
var pollingV FindCommitResponseForPolling
err := proto.UnmarshalOptions{
DiscardUnknown: true, // don't care about other fields, that's the point of this codec!
}.Unmarshal(data, &pollingV)
if err != nil {
return err
}
if pollingV.Commit != nil {
typedV.Commit = &gitalypb.GitCommit{
Id: pollingV.Commit.Id,
}
}
return nil
}
func (c pollingCodec) Name() string {
// Pretend to be a codec for protobuf.
return protoenc.Name
}
|