mirror of https://github.com/harness/drone.git
306 lines
7.5 KiB
Go
306 lines
7.5 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 git
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/harness/gitness/errors"
|
|
"github.com/harness/gitness/git/api"
|
|
"github.com/harness/gitness/git/parser"
|
|
"github.com/harness/gitness/git/sha"
|
|
)
|
|
|
|
const (
|
|
maxOversizeFiles = 10
|
|
maxCommitterMismatches = 10
|
|
maxMissingLFSObjects = 10
|
|
)
|
|
|
|
type FindOversizeFilesParams struct {
|
|
// TODO: remove. Kept for backward compatibility
|
|
RepoUID string
|
|
GitObjectDirs []string
|
|
|
|
SizeLimit int64
|
|
}
|
|
|
|
type FileInfo struct {
|
|
SHA sha.SHA
|
|
Size int64
|
|
}
|
|
|
|
type FindOversizeFilesOutput struct {
|
|
FileInfos []FileInfo
|
|
Total int64
|
|
}
|
|
|
|
type FindCommitterMismatchParams struct {
|
|
PrincipalEmail string
|
|
}
|
|
|
|
type CommitInfo struct {
|
|
SHA sha.SHA
|
|
Committer string
|
|
}
|
|
|
|
type FindCommitterMismatchOutput struct {
|
|
CommitInfos []CommitInfo
|
|
Total int64
|
|
}
|
|
|
|
type FindLFSPointersParams struct {
|
|
ReadParams
|
|
}
|
|
|
|
type LFSInfo struct {
|
|
ObjID string
|
|
SHA sha.SHA
|
|
}
|
|
|
|
type FindLFSPointersOutput struct {
|
|
LFSInfos []LFSInfo
|
|
Total int64
|
|
}
|
|
|
|
type ProcessPreReceiveObjectsParams struct {
|
|
ReadParams
|
|
FindOversizeFilesParams *FindOversizeFilesParams
|
|
FindCommitterMismatchParams *FindCommitterMismatchParams
|
|
FindLFSPointersParams *FindLFSPointersParams
|
|
}
|
|
|
|
type ProcessPreReceiveObjectsOutput struct {
|
|
FindOversizeFilesOutput *FindOversizeFilesOutput
|
|
FindCommitterMismatchOutput *FindCommitterMismatchOutput
|
|
FindLFSPointersOutput *FindLFSPointersOutput
|
|
}
|
|
|
|
func (s *Service) ProcessPreReceiveObjects(
|
|
ctx context.Context,
|
|
params ProcessPreReceiveObjectsParams,
|
|
) (ProcessPreReceiveObjectsOutput, error) {
|
|
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
|
|
|
|
var objects []parser.BatchCheckObject
|
|
for _, gitObjDir := range params.AlternateObjectDirs {
|
|
objs, err := s.listGitObjDir(ctx, repoPath, gitObjDir)
|
|
if err != nil {
|
|
return ProcessPreReceiveObjectsOutput{}, err
|
|
}
|
|
objects = append(objects, objs...)
|
|
}
|
|
|
|
var output ProcessPreReceiveObjectsOutput
|
|
|
|
if params.FindOversizeFilesParams != nil {
|
|
output.FindOversizeFilesOutput = findOversizeFiles(
|
|
objects, params.FindOversizeFilesParams,
|
|
)
|
|
}
|
|
|
|
if params.FindCommitterMismatchParams != nil {
|
|
out, err := findCommitterMismatch(
|
|
ctx,
|
|
objects,
|
|
repoPath,
|
|
params.ReadParams.AlternateObjectDirs,
|
|
params.FindCommitterMismatchParams,
|
|
)
|
|
if err != nil {
|
|
return ProcessPreReceiveObjectsOutput{}, err
|
|
}
|
|
output.FindCommitterMismatchOutput = out
|
|
}
|
|
|
|
if params.FindLFSPointersParams != nil {
|
|
out, err := s.findLFSPointers(
|
|
ctx,
|
|
objects,
|
|
repoPath,
|
|
params.ReadParams.AlternateObjectDirs,
|
|
params.FindLFSPointersParams,
|
|
)
|
|
if err != nil {
|
|
return ProcessPreReceiveObjectsOutput{}, err
|
|
}
|
|
|
|
output.FindLFSPointersOutput = out
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
func findOversizeFiles(
|
|
objects []parser.BatchCheckObject,
|
|
findOversizeFilesParams *FindOversizeFilesParams,
|
|
) *FindOversizeFilesOutput {
|
|
var fileInfos []FileInfo
|
|
|
|
var total int64 // limit the total num of objects returned
|
|
for _, obj := range objects {
|
|
if obj.Type == string(TreeNodeTypeBlob) && obj.Size > findOversizeFilesParams.SizeLimit {
|
|
if total < maxOversizeFiles {
|
|
fileInfos = append(fileInfos, FileInfo{
|
|
SHA: obj.SHA,
|
|
Size: obj.Size,
|
|
})
|
|
}
|
|
total++
|
|
}
|
|
}
|
|
|
|
return &FindOversizeFilesOutput{
|
|
FileInfos: fileInfos,
|
|
Total: total,
|
|
}
|
|
}
|
|
|
|
func findCommitterMismatch(
|
|
ctx context.Context,
|
|
objects []parser.BatchCheckObject,
|
|
repoPath string,
|
|
alternateObjectDirs []string,
|
|
findCommitterEmailsMismatchParams *FindCommitterMismatchParams,
|
|
) (*FindCommitterMismatchOutput, error) {
|
|
var commitSHAs []string
|
|
for _, obj := range objects {
|
|
if obj.Type == string(TreeNodeTypeCommit) {
|
|
commitSHAs = append(commitSHAs, obj.SHA.String())
|
|
}
|
|
}
|
|
|
|
writer, reader, cancel := api.CatFileBatch(ctx, repoPath, alternateObjectDirs)
|
|
defer cancel()
|
|
defer writer.Close()
|
|
|
|
var total int64
|
|
var commitInfos []CommitInfo
|
|
for _, commitSHA := range commitSHAs {
|
|
_, writeErr := writer.Write([]byte(commitSHA + "\n"))
|
|
if writeErr != nil {
|
|
return nil, fmt.Errorf("failed to write to cat-file batch: %w", writeErr)
|
|
}
|
|
|
|
output, err := api.ReadBatchHeaderLine(reader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read cat-file batch header: %w", err)
|
|
}
|
|
|
|
limitedReader := io.LimitReader(reader, output.Size+1) // plus eol
|
|
|
|
data, err := io.ReadAll(limitedReader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read: %w", err)
|
|
}
|
|
text := strings.Split(string(data), "\n")
|
|
|
|
for _, line := range text {
|
|
if !strings.HasPrefix(line, "committer ") {
|
|
continue
|
|
}
|
|
|
|
committerEmail := line[strings.Index(line, "<")+1 : strings.Index(line, ">")]
|
|
if !strings.EqualFold(committerEmail, findCommitterEmailsMismatchParams.PrincipalEmail) {
|
|
if total < maxCommitterMismatches {
|
|
sha, err := sha.New(commitSHA)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create new sha: %w", err)
|
|
}
|
|
commitInfos = append(commitInfos, CommitInfo{
|
|
SHA: sha,
|
|
Committer: committerEmail,
|
|
})
|
|
}
|
|
total++
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
return &FindCommitterMismatchOutput{
|
|
CommitInfos: commitInfos,
|
|
Total: total,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) findLFSPointers(
|
|
ctx context.Context,
|
|
objects []parser.BatchCheckObject,
|
|
repoPath string,
|
|
alternateObjectDirs []string,
|
|
_ *FindLFSPointersParams,
|
|
) (*FindLFSPointersOutput, error) {
|
|
var candidateObjects []parser.BatchCheckObject
|
|
for _, obj := range objects {
|
|
if obj.Type == string(TreeNodeTypeBlob) && obj.Size <= parser.LfsPointerMaxSize {
|
|
candidateObjects = append(candidateObjects, obj)
|
|
}
|
|
}
|
|
|
|
if len(candidateObjects) == 0 {
|
|
return &FindLFSPointersOutput{}, nil
|
|
}
|
|
|
|
// check the short-listed objects for lfs-pointers content
|
|
writer, reader, cancel := api.CatFileBatch(ctx, repoPath, alternateObjectDirs)
|
|
defer cancel()
|
|
|
|
var total int64
|
|
var lfsInfos []LFSInfo
|
|
for _, obj := range candidateObjects {
|
|
_, writeErr := writer.Write([]byte(obj.SHA.String() + "\n"))
|
|
if writeErr != nil {
|
|
return nil, fmt.Errorf("failed to write to cat-file batch: %w", writeErr)
|
|
}
|
|
|
|
// first line is always the object type, sha, and size
|
|
_, readErr := reader.ReadString('\n')
|
|
if readErr != nil {
|
|
return nil, fmt.Errorf("failed to read cat-file output: %w", readErr)
|
|
}
|
|
|
|
data, readErr := io.ReadAll(io.LimitReader(reader, obj.Size))
|
|
if readErr != nil {
|
|
return nil, fmt.Errorf("failed to read cat-file output: %w", readErr)
|
|
}
|
|
|
|
objID, err := parser.GetLFSObjectID(data)
|
|
if err != nil && !errors.Is(err, parser.ErrInvalidLFSPointer) {
|
|
return nil, fmt.Errorf("failed to parse cat-file output to get LFS object ID for sha %q: %w", obj.SHA, err)
|
|
}
|
|
if err == nil {
|
|
if total < maxMissingLFSObjects {
|
|
lfsInfos = append(lfsInfos, LFSInfo{ObjID: objID, SHA: obj.SHA})
|
|
}
|
|
total++
|
|
}
|
|
|
|
// skip the trailing new line
|
|
_, readErr = reader.ReadString('\n')
|
|
if readErr != nil {
|
|
return nil, fmt.Errorf("failed to read trailing newline after object: %w", readErr)
|
|
}
|
|
}
|
|
|
|
return &FindLFSPointersOutput{
|
|
LFSInfos: lfsInfos,
|
|
Total: total,
|
|
}, nil
|
|
}
|