mirror of https://github.com/harness/drone.git
412 lines
11 KiB
Go
412 lines
11 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"
|
|
"io"
|
|
|
|
"github.com/harness/gitness/gitrpc/internal/streamio"
|
|
"github.com/harness/gitness/gitrpc/internal/types"
|
|
"github.com/harness/gitness/gitrpc/rpc"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
type DiffParams struct {
|
|
ReadParams
|
|
BaseRef string
|
|
HeadRef string
|
|
MergeBase bool
|
|
IncludePatch bool
|
|
}
|
|
|
|
func (p DiffParams) Validate() error {
|
|
if err := p.ReadParams.Validate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if p.HeadRef == "" {
|
|
return ErrInvalidArgumentf("head ref cannot be empty")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) RawDiff(ctx context.Context, params *DiffParams, out io.Writer) error {
|
|
if err := params.Validate(); err != nil {
|
|
return err
|
|
}
|
|
diff, err := c.diffService.RawDiff(ctx, &rpc.DiffRequest{
|
|
Base: mapToRPCReadRequest(params.ReadParams),
|
|
BaseRef: params.BaseRef,
|
|
HeadRef: params.HeadRef,
|
|
MergeBase: params.MergeBase,
|
|
})
|
|
if err != nil {
|
|
return processRPCErrorf(err, "failed to fetch diff between '%s' and '%s' with err: %v",
|
|
params.BaseRef, params.HeadRef, err)
|
|
}
|
|
|
|
reader := streamio.NewReader(func() ([]byte, error) {
|
|
var resp *rpc.RawDiffResponse
|
|
resp, err = diff.Recv()
|
|
return resp.GetData(), err
|
|
})
|
|
|
|
if _, err = io.Copy(out, reader); err != nil {
|
|
return processRPCErrorf(err, "failed to fetch diff between '%s' and '%s' with err: %v",
|
|
params.BaseRef, params.HeadRef, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) CommitDiff(ctx context.Context, params *GetCommitParams, out io.Writer) error {
|
|
if err := params.Validate(); err != nil {
|
|
return err
|
|
}
|
|
diff, err := c.diffService.CommitDiff(ctx, &rpc.CommitDiffRequest{
|
|
Base: mapToRPCReadRequest(params.ReadParams),
|
|
Sha: params.SHA,
|
|
})
|
|
if err != nil {
|
|
return processRPCErrorf(err, "failed to fetch diff for commit '%s': %v", params.SHA, err)
|
|
}
|
|
|
|
reader := streamio.NewReader(func() ([]byte, error) {
|
|
var resp *rpc.CommitDiffResponse
|
|
resp, err = diff.Recv()
|
|
return resp.GetData(), err
|
|
})
|
|
|
|
if _, err = io.Copy(out, reader); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type DiffShortStatOutput struct {
|
|
Files int
|
|
Additions int
|
|
Deletions int
|
|
}
|
|
|
|
// DiffShortStat returns files changed, additions and deletions metadata.
|
|
func (c *Client) DiffShortStat(ctx context.Context, params *DiffParams) (DiffShortStatOutput, error) {
|
|
if err := params.Validate(); err != nil {
|
|
return DiffShortStatOutput{}, err
|
|
}
|
|
stat, err := c.diffService.DiffShortStat(ctx, &rpc.DiffRequest{
|
|
Base: mapToRPCReadRequest(params.ReadParams),
|
|
BaseRef: params.BaseRef,
|
|
HeadRef: params.HeadRef,
|
|
MergeBase: params.MergeBase,
|
|
})
|
|
if err != nil {
|
|
return DiffShortStatOutput{}, processRPCErrorf(err, "failed to get diff data between '%s' and '%s'",
|
|
params.BaseRef, params.HeadRef)
|
|
}
|
|
return DiffShortStatOutput{
|
|
Files: int(stat.GetFiles()),
|
|
Additions: int(stat.GetAdditions()),
|
|
Deletions: int(stat.GetDeletions()),
|
|
}, nil
|
|
}
|
|
|
|
type DiffStatsOutput struct {
|
|
Commits int
|
|
FilesChanged int
|
|
}
|
|
|
|
func (c *Client) DiffStats(ctx context.Context, params *DiffParams) (DiffStatsOutput, error) {
|
|
// declare variables which will be used in go routines,
|
|
// no need for atomic operations because writing and reading variable
|
|
// doesn't happen at the same time
|
|
var (
|
|
totalCommits int
|
|
totalFiles int
|
|
)
|
|
|
|
errGroup, groupCtx := errgroup.WithContext(ctx)
|
|
|
|
errGroup.Go(func() error {
|
|
// read total commits
|
|
|
|
options := &GetCommitDivergencesParams{
|
|
ReadParams: params.ReadParams,
|
|
Requests: []CommitDivergenceRequest{
|
|
{
|
|
From: params.HeadRef,
|
|
To: params.BaseRef,
|
|
},
|
|
},
|
|
}
|
|
|
|
rpcOutput, err := c.GetCommitDivergences(groupCtx, options)
|
|
if err != nil {
|
|
return processRPCErrorf(err, "failed to count pull request commits between '%s' and '%s'",
|
|
params.BaseRef, params.HeadRef)
|
|
}
|
|
if len(rpcOutput.Divergences) > 0 {
|
|
totalCommits = int(rpcOutput.Divergences[0].Ahead)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
errGroup.Go(func() error {
|
|
// read short stat
|
|
stat, err := c.DiffShortStat(groupCtx, &DiffParams{
|
|
ReadParams: params.ReadParams,
|
|
BaseRef: params.BaseRef,
|
|
HeadRef: params.HeadRef,
|
|
MergeBase: true, // must be true, because commitDivergences use tripple dot notation
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
totalFiles = stat.Files
|
|
return nil
|
|
})
|
|
|
|
err := errGroup.Wait()
|
|
if err != nil {
|
|
return DiffStatsOutput{}, err
|
|
}
|
|
|
|
return DiffStatsOutput{
|
|
Commits: totalCommits,
|
|
FilesChanged: totalFiles,
|
|
}, nil
|
|
}
|
|
|
|
type GetDiffHunkHeadersParams struct {
|
|
ReadParams
|
|
SourceCommitSHA string
|
|
TargetCommitSHA string
|
|
}
|
|
|
|
type DiffFileHeader struct {
|
|
OldName string
|
|
NewName string
|
|
Extensions map[string]string
|
|
}
|
|
|
|
type HunkHeader struct {
|
|
OldLine int
|
|
OldSpan int
|
|
NewLine int
|
|
NewSpan int
|
|
Text string
|
|
}
|
|
|
|
type DiffFileHunkHeaders struct {
|
|
FileHeader DiffFileHeader
|
|
HunkHeaders []HunkHeader
|
|
}
|
|
|
|
type GetDiffHunkHeadersOutput struct {
|
|
Files []DiffFileHunkHeaders
|
|
}
|
|
|
|
func (c *Client) GetDiffHunkHeaders(
|
|
ctx context.Context,
|
|
params GetDiffHunkHeadersParams,
|
|
) (GetDiffHunkHeadersOutput, error) {
|
|
if params.SourceCommitSHA == params.TargetCommitSHA {
|
|
return GetDiffHunkHeadersOutput{}, nil
|
|
}
|
|
|
|
hunkHeaders, err := c.diffService.GetDiffHunkHeaders(ctx, &rpc.GetDiffHunkHeadersRequest{
|
|
Base: mapToRPCReadRequest(params.ReadParams),
|
|
SourceCommitSha: params.SourceCommitSHA,
|
|
TargetCommitSha: params.TargetCommitSHA,
|
|
})
|
|
if err != nil {
|
|
return GetDiffHunkHeadersOutput{}, processRPCErrorf(err, "failed to get git diff hunk headers")
|
|
}
|
|
|
|
files := make([]DiffFileHunkHeaders, len(hunkHeaders.Files))
|
|
for i, file := range hunkHeaders.Files {
|
|
headers := make([]HunkHeader, len(file.HunkHeaders))
|
|
for j, header := range file.HunkHeaders {
|
|
headers[j] = mapHunkHeader(header)
|
|
}
|
|
files[i] = DiffFileHunkHeaders{
|
|
FileHeader: mapDiffFileHeader(file.FileHeader),
|
|
HunkHeaders: headers,
|
|
}
|
|
}
|
|
|
|
return GetDiffHunkHeadersOutput{
|
|
Files: files,
|
|
}, nil
|
|
}
|
|
|
|
type DiffCutOutput struct {
|
|
Header HunkHeader
|
|
LinesHeader string
|
|
Lines []string
|
|
MergeBaseSHA string
|
|
LatestSourceSHA string
|
|
}
|
|
|
|
type DiffCutParams struct {
|
|
ReadParams
|
|
SourceCommitSHA string
|
|
SourceBranch string
|
|
TargetCommitSHA string
|
|
TargetBranch string
|
|
Path string
|
|
LineStart int
|
|
LineStartNew bool
|
|
LineEnd int
|
|
LineEndNew bool
|
|
}
|
|
|
|
// DiffCut extracts diff snippet from a git diff hunk.
|
|
// The snippet is from the specific commit (specified by commit SHA), between refs
|
|
// source branch and target branch, from the specific file.
|
|
func (c *Client) DiffCut(ctx context.Context, params *DiffCutParams) (DiffCutOutput, error) {
|
|
result, err := c.diffService.DiffCut(ctx, &rpc.DiffCutRequest{
|
|
Base: mapToRPCReadRequest(params.ReadParams),
|
|
SourceCommitSha: params.SourceCommitSHA,
|
|
SourceBranch: params.SourceBranch,
|
|
TargetCommitSha: params.TargetCommitSHA,
|
|
TargetBranch: params.TargetBranch,
|
|
Path: params.Path,
|
|
LineStart: int32(params.LineStart),
|
|
LineStartNew: params.LineStartNew,
|
|
LineEnd: int32(params.LineEnd),
|
|
LineEndNew: params.LineEndNew,
|
|
})
|
|
if err != nil {
|
|
return DiffCutOutput{}, processRPCErrorf(err, "failed to get git diff sub hunk")
|
|
}
|
|
|
|
hunkHeader := types.HunkHeader{
|
|
OldLine: int(result.HunkHeader.OldLine),
|
|
OldSpan: int(result.HunkHeader.OldSpan),
|
|
NewLine: int(result.HunkHeader.NewLine),
|
|
NewSpan: int(result.HunkHeader.NewSpan),
|
|
Text: result.HunkHeader.Text,
|
|
}
|
|
|
|
return DiffCutOutput{
|
|
Header: HunkHeader(hunkHeader),
|
|
LinesHeader: result.LinesHeader,
|
|
Lines: result.Lines,
|
|
MergeBaseSHA: result.MergeBaseSha,
|
|
LatestSourceSHA: result.LatestSourceSha,
|
|
}, nil
|
|
}
|
|
|
|
type FileDiff struct {
|
|
SHA string `json:"sha"`
|
|
OldSHA string `json:"old_sha,omitempty"`
|
|
Path string `json:"path"`
|
|
OldPath string `json:"old_path,omitempty"`
|
|
Status FileDiffStatus `json:"status"`
|
|
Additions int64 `json:"additions"`
|
|
Deletions int64 `json:"deletions"`
|
|
Changes int64 `json:"changes"`
|
|
Patch []byte `json:"patch,omitempty"`
|
|
IsBinary bool `json:"is_binary"`
|
|
IsSubmodule bool `json:"is_submodule"`
|
|
}
|
|
|
|
type FileDiffStatus string
|
|
|
|
const (
|
|
// NOTE: keeping values upper case for now to stay consistent with current API.
|
|
// TODO: change drone/go-scm (and potentially new dependencies) to case insensitive.
|
|
|
|
FileDiffStatusUndefined FileDiffStatus = "UNDEFINED"
|
|
FileDiffStatusAdded FileDiffStatus = "ADDED"
|
|
FileDiffStatusModified FileDiffStatus = "MODIFIED"
|
|
FileDiffStatusDeleted FileDiffStatus = "DELETED"
|
|
FileDiffStatusRenamed FileDiffStatus = "RENAMED"
|
|
)
|
|
|
|
func (c *Client) Diff(ctx context.Context, params *DiffParams) (<-chan *FileDiff, <-chan error) {
|
|
ch := make(chan *FileDiff)
|
|
// needs to be buffered so it is not blocking on receiver side when all data is sent
|
|
cherr := make(chan error, 1)
|
|
|
|
go func() {
|
|
defer close(ch)
|
|
defer close(cherr)
|
|
|
|
if err := params.Validate(); err != nil {
|
|
cherr <- err
|
|
return
|
|
}
|
|
|
|
stream, err := c.diffService.Diff(ctx, &rpc.DiffRequest{
|
|
Base: mapToRPCReadRequest(params.ReadParams),
|
|
BaseRef: params.BaseRef,
|
|
HeadRef: params.HeadRef,
|
|
MergeBase: params.MergeBase,
|
|
IncludePatch: params.IncludePatch,
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for {
|
|
resp, err := stream.Recv()
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
cherr <- processRPCErrorf(err, "failed to get git diff file from stream")
|
|
return
|
|
}
|
|
|
|
ch <- &FileDiff{
|
|
SHA: resp.Sha,
|
|
OldSHA: resp.OldSha,
|
|
Path: resp.Path,
|
|
OldPath: resp.OldPath,
|
|
Status: mapRPCFileDiffStatus(resp.Status),
|
|
Additions: int64(resp.Additions),
|
|
Deletions: int64(resp.Deletions),
|
|
Changes: int64(resp.Changes),
|
|
Patch: resp.Patch,
|
|
IsBinary: resp.IsBinary,
|
|
IsSubmodule: resp.IsSubmodule,
|
|
}
|
|
}
|
|
}()
|
|
|
|
return ch, cherr
|
|
}
|
|
|
|
func mapRPCFileDiffStatus(status rpc.DiffResponse_FileStatus) FileDiffStatus {
|
|
switch status {
|
|
case rpc.DiffResponse_ADDED:
|
|
return FileDiffStatusAdded
|
|
case rpc.DiffResponse_DELETED:
|
|
return FileDiffStatusDeleted
|
|
case rpc.DiffResponse_MODIFIED:
|
|
return FileDiffStatusModified
|
|
case rpc.DiffResponse_RENAMED:
|
|
return FileDiffStatusRenamed
|
|
default:
|
|
return FileDiffStatusUndefined
|
|
}
|
|
}
|