drone/gitrpc/commit.go

279 lines
7.2 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gitrpc
import (
"context"
"errors"
"fmt"
"io"
"strconv"
"time"
"github.com/harness/gitness/gitrpc/rpc"
"github.com/rs/zerolog/log"
)
type GetCommitParams struct {
ReadParams
// SHA is the git commit sha
SHA string
}
type GetCommitOutput struct {
Commit Commit `json:"commit"`
}
type Commit struct {
SHA string `json:"sha"`
Title string `json:"title"`
Message string `json:"message,omitempty"`
Author Signature `json:"author"`
Committer Signature `json:"committer"`
}
type Signature struct {
Identity Identity `json:"identity"`
When time.Time `json:"when"`
}
type Identity struct {
Name string `json:"name"`
Email string `json:"email"`
}
func (c *Client) GetCommit(ctx context.Context, params *GetCommitParams) (*GetCommitOutput, error) {
if params == nil {
return nil, ErrNoParamsProvided
}
result, err := c.repoService.GetCommit(ctx, &rpc.GetCommitRequest{
Base: mapToRPCReadRequest(params.ReadParams),
Sha: params.SHA,
})
if err != nil {
return nil, processRPCErrorf(err, "failed to get commit")
}
commit, err := mapRPCCommit(result.GetCommit())
if err != nil {
return nil, fmt.Errorf("failed to map rpc commit: %w", err)
}
return &GetCommitOutput{
Commit: *commit,
}, nil
}
type ListCommitsParams struct {
ReadParams
// GitREF is a git reference (branch / tag / commit SHA)
GitREF string
// After is a git reference (branch / tag / commit SHA)
// If provided, commits only up to that reference will be returned (exlusive)
After string
Page int32
Limit int32
Path string
// Since allows to filter for commits since the provided UNIX timestamp - Optional, ignored if value is 0.
Since int64
// Until allows to filter for commits until the provided UNIX timestamp - Optional, ignored if value is 0.
Until int64
// Committer allows to filter for commits based on the committer - Optional, ignored if string is empty.
Committer string
}
type RenameDetails struct {
OldPath string
NewPath string
CommitShaBefore string
CommitShaAfter string
}
type ListCommitsOutput struct {
Commits []Commit
RenameDetails []*RenameDetails
TotalCommits int
}
func (c *Client) ListCommits(ctx context.Context, params *ListCommitsParams) (*ListCommitsOutput, error) {
if params == nil {
return nil, ErrNoParamsProvided
}
stream, err := c.repoService.ListCommits(ctx, &rpc.ListCommitsRequest{
Base: mapToRPCReadRequest(params.ReadParams),
GitRef: params.GitREF,
After: params.After,
Page: params.Page,
Limit: params.Limit,
Path: params.Path,
Since: params.Since,
Until: params.Until,
Committer: params.Committer,
})
if err != nil {
return nil, fmt.Errorf("failed to start stream for commits: %w", err)
}
// NOTE: don't use PageSize as initial slice capacity - as that theoretically could be MaxInt
output := &ListCommitsOutput{
Commits: make([]Commit, 0, 16),
}
// check for list commits header
header, err := stream.Header()
if err != nil {
return nil, processRPCErrorf(err, "failed to read list commits header from stream")
}
values := header.Get("total-commits")
if len(values) > 0 && values[0] != "" {
total, err := strconv.ParseInt(values[0], 10, 32)
if err != nil {
return nil, processRPCErrorf(err, "failed to convert header total-commits")
}
output.TotalCommits = int(total)
}
for {
var next *rpc.ListCommitsResponse
next, err = stream.Recv()
if errors.Is(err, io.EOF) {
log.Ctx(ctx).Debug().Msg("received end of stream")
break
}
if err != nil {
return nil, processRPCErrorf(err, "received unexpected error from server")
}
if next.GetCommit() == nil {
return nil, fmt.Errorf("expected commit message")
}
var commit *Commit
commit, err = mapRPCCommit(next.GetCommit())
if err != nil {
return nil, fmt.Errorf("failed to map rpc commit: %w", err)
}
output.Commits = append(output.Commits, *commit)
if next.RenameDetails != nil {
output.RenameDetails = mapRPCRenameDetails(next.RenameDetails)
}
}
return output, nil
}
type GetCommitDivergencesParams struct {
ReadParams
MaxCount int32
Requests []CommitDivergenceRequest
}
type GetCommitDivergencesOutput struct {
Divergences []CommitDivergence
}
// CommitDivergenceRequest contains the refs for which the converging commits should be counted.
type CommitDivergenceRequest struct {
// From is the ref from which the counting of the diverging commits starts.
From string
// To is the ref at which the counting of the diverging commits ends.
To string
}
// CommitDivergence contains the information of the count of converging commits between two refs.
type CommitDivergence struct {
// Ahead is the count of commits the 'From' ref is ahead of the 'To' ref.
Ahead int32
// Behind is the count of commits the 'From' ref is behind the 'To' ref.
Behind int32
}
func (c *Client) GetCommitDivergences(ctx context.Context,
params *GetCommitDivergencesParams) (*GetCommitDivergencesOutput, error) {
if params == nil {
return nil, ErrNoParamsProvided
}
// build rpc request
req := &rpc.GetCommitDivergencesRequest{
Base: mapToRPCReadRequest(params.ReadParams),
MaxCount: params.MaxCount,
Requests: make([]*rpc.CommitDivergenceRequest, len(params.Requests)),
}
for i := range params.Requests {
req.Requests[i] = &rpc.CommitDivergenceRequest{
From: params.Requests[i].From,
To: params.Requests[i].To,
}
}
resp, err := c.repoService.GetCommitDivergences(ctx, req)
if err != nil {
return nil, processRPCErrorf(err, "failed to get diverging commits from server")
}
divergences := resp.GetDivergences()
if divergences == nil {
return nil, NewError(StatusInternal, "server response divergences were nil")
}
// build output
output := &GetCommitDivergencesOutput{
Divergences: make([]CommitDivergence, len(divergences)),
}
for i := range divergences {
if divergences[i] == nil {
return nil, NewError(StatusInternal, "server returned nil divergence")
}
output.Divergences[i] = CommitDivergence{
Ahead: divergences[i].Ahead,
Behind: divergences[i].Behind,
}
}
return output, nil
}
type MergeBaseParams struct {
ReadParams
Ref1 string
Ref2 string
}
type MergeBaseOutput struct {
MergeBaseSHA string
}
func (c *Client) MergeBase(ctx context.Context,
params MergeBaseParams,
) (MergeBaseOutput, error) {
result, err := c.repoService.MergeBase(ctx, &rpc.MergeBaseRequest{
Base: mapToRPCReadRequest(params.ReadParams),
Ref1: params.Ref1,
Ref2: params.Ref2,
})
if err != nil {
return MergeBaseOutput{}, fmt.Errorf("failed to get merge base commit: %w", err)
}
return MergeBaseOutput{
MergeBaseSHA: result.MergeBaseSha,
}, nil
}