mirror of https://github.com/harness/drone.git
209 lines
5.9 KiB
Go
209 lines
5.9 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 gitea
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/harness/gitness/gitrpc/internal/parser"
|
|
"github.com/harness/gitness/gitrpc/internal/types"
|
|
|
|
"code.gitea.io/gitea/modules/git"
|
|
)
|
|
|
|
func (g Adapter) RawDiff(
|
|
ctx context.Context,
|
|
repoPath string,
|
|
baseRef string,
|
|
headRef string,
|
|
mergeBase bool,
|
|
w io.Writer,
|
|
) error {
|
|
args := make([]string, 0, 8)
|
|
args = append(args, "diff", "-M", "--full-index")
|
|
if mergeBase {
|
|
args = append(args, "--merge-base")
|
|
}
|
|
args = append(args, baseRef, headRef)
|
|
|
|
cmd := git.NewCommand(ctx, args...)
|
|
cmd.SetDescription(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath))
|
|
errbuf := bytes.Buffer{}
|
|
if err := cmd.Run(&git.RunOpts{
|
|
Dir: repoPath,
|
|
Stderr: &errbuf,
|
|
Stdout: w,
|
|
}); err != nil {
|
|
if errbuf.Len() > 0 {
|
|
err = &runStdError{err: err, stderr: errbuf.String()}
|
|
}
|
|
return processGiteaErrorf(err, "git diff failed between '%s' and '%s' with err: %v", baseRef, headRef, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CommitDiff will stream diff for provided ref.
|
|
func (g Adapter) CommitDiff(ctx context.Context, repoPath, sha string, w io.Writer) error {
|
|
args := make([]string, 0, 8)
|
|
args = append(args, "show", "--full-index", "--pretty=format:%b", sha)
|
|
|
|
stderr := new(bytes.Buffer)
|
|
cmd := git.NewCommand(ctx, args...)
|
|
if err := cmd.Run(&git.RunOpts{
|
|
Dir: repoPath,
|
|
Stdout: w,
|
|
Stderr: stderr,
|
|
}); err != nil {
|
|
return processGiteaErrorf(err, "commit diff error: %v", stderr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g Adapter) DiffShortStat(
|
|
ctx context.Context,
|
|
repoPath string,
|
|
baseRef string,
|
|
headRef string,
|
|
useMergeBase bool,
|
|
) (types.DiffShortStat, error) {
|
|
separator := ".."
|
|
if useMergeBase {
|
|
separator = "..."
|
|
}
|
|
|
|
shortstatArgs := []string{baseRef + separator + headRef}
|
|
if len(baseRef) == 0 || baseRef == git.EmptySHA {
|
|
shortstatArgs = []string{git.EmptyTreeSHA, headRef}
|
|
}
|
|
numFiles, totalAdditions, totalDeletions, err := git.GetDiffShortStat(ctx, repoPath, shortstatArgs...)
|
|
if err != nil {
|
|
return types.DiffShortStat{}, processGiteaErrorf(err, "failed to get diff short stat between %s and %s",
|
|
baseRef, headRef)
|
|
}
|
|
return types.DiffShortStat{
|
|
Files: numFiles,
|
|
Additions: totalAdditions,
|
|
Deletions: totalDeletions,
|
|
}, nil
|
|
}
|
|
|
|
// GetDiffHunkHeaders for each file in diff output returns file name (old and new to detect renames),
|
|
// and all hunk headers. The diffs are generated with unified=0 parameter to create minimum sized hunks.
|
|
// Hunks' body is ignored.
|
|
// The purpose of this function is to get data based on which code comments could be repositioned.
|
|
func (g Adapter) GetDiffHunkHeaders(
|
|
ctx context.Context,
|
|
repoPath, targetRef, sourceRef string,
|
|
) ([]*types.DiffFileHunkHeaders, error) {
|
|
pipeRead, pipeWrite := io.Pipe()
|
|
stderr := &bytes.Buffer{}
|
|
go func() {
|
|
var err error
|
|
|
|
defer func() {
|
|
// If running of the command below fails, make the pipe reader also fail with the same error.
|
|
_ = pipeWrite.CloseWithError(err)
|
|
}()
|
|
|
|
cmd := git.NewCommand(ctx,
|
|
"diff", "--patch", "--no-color", "--unified=0", sourceRef, targetRef)
|
|
err = cmd.Run(&git.RunOpts{
|
|
Dir: repoPath,
|
|
Stdout: pipeWrite,
|
|
Stderr: stderr, // We capture stderr output in a buffer.
|
|
})
|
|
}()
|
|
|
|
fileHunkHeaders, err := parser.GetHunkHeaders(pipeRead)
|
|
|
|
// First check if there's something in the stderr buffer, if yes that's the error
|
|
if errStderr := parseDiffStderr(stderr); errStderr != nil {
|
|
return nil, errStderr
|
|
}
|
|
|
|
// Next check if reading the git diff output caused an error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return fileHunkHeaders, nil
|
|
}
|
|
|
|
// DiffCut parses full file git diff output and returns lines specified with the parameters.
|
|
// The purpose of this function is to get diff data with which code comments could be generated.
|
|
func (g Adapter) DiffCut(
|
|
ctx context.Context,
|
|
repoPath, targetRef, sourceRef, path string,
|
|
params types.DiffCutParams,
|
|
) (types.HunkHeader, types.Hunk, error) {
|
|
pipeRead, pipeWrite := io.Pipe()
|
|
stderr := &bytes.Buffer{}
|
|
go func() {
|
|
var err error
|
|
|
|
defer func() {
|
|
// If running of the command below fails, make the pipe reader also fail with the same error.
|
|
_ = pipeWrite.CloseWithError(err)
|
|
}()
|
|
|
|
cmd := git.NewCommand(ctx,
|
|
"diff", "--merge-base", "--patch", "--no-color", "--unified=100000000",
|
|
targetRef, sourceRef, "--", path)
|
|
err = cmd.Run(&git.RunOpts{
|
|
Dir: repoPath,
|
|
Stdout: pipeWrite,
|
|
Stderr: stderr, // We capture stderr output in a buffer.
|
|
})
|
|
}()
|
|
|
|
diffCutHeader, linesHunk, err := parser.DiffCut(pipeRead, params)
|
|
|
|
// First check if there's something in the stderr buffer, if yes that's the error
|
|
if errStderr := parseDiffStderr(stderr); errStderr != nil {
|
|
return types.HunkHeader{}, types.Hunk{}, errStderr
|
|
}
|
|
|
|
// Next check if reading the git diff output caused an error
|
|
if err != nil {
|
|
return types.HunkHeader{}, types.Hunk{}, err
|
|
}
|
|
|
|
return diffCutHeader, linesHunk, nil
|
|
}
|
|
|
|
func parseDiffStderr(stderr *bytes.Buffer) error {
|
|
errRaw := stderr.String() // assume there will never be a lot of output to stdout
|
|
if len(errRaw) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if idx := strings.IndexByte(errRaw, '\n'); idx > 0 {
|
|
errRaw = errRaw[:idx] // get only the first line of the output
|
|
}
|
|
|
|
errRaw = strings.TrimPrefix(errRaw, "fatal: ") // git errors start with the "fatal: " prefix
|
|
|
|
if strings.Contains(errRaw, "bad revision") {
|
|
return types.ErrSHADoesNotMatch
|
|
}
|
|
|
|
return errors.New(errRaw)
|
|
}
|