mirror of
https://github.com/harness/drone.git
synced 2025-05-19 10:29:55 +08:00
remove gogit (#887)
This commit is contained in:
parent
4d550a78bd
commit
a7f11126fa
@ -47,7 +47,7 @@ func (c *Controller) CodeOwners(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ownerEvaluation, err := c.codeOwners.Evaluate(ctx, repo, pr, reviewers)
|
ownerEvaluation, err := c.codeOwners.Evaluate(ctx, repo, pr, reviewers)
|
||||||
if errors.Is(codeowners.ErrNotFound, err) {
|
if errors.Is(err, codeowners.ErrNotFound) {
|
||||||
return types.CodeOwnerEvaluation{}, usererror.ErrNotFound
|
return types.CodeOwnerEvaluation{}, usererror.ErrNotFound
|
||||||
}
|
}
|
||||||
if codeowners.IsTooLargeError(err) {
|
if codeowners.IsTooLargeError(err) {
|
||||||
|
@ -26,6 +26,8 @@ import (
|
|||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -186,6 +188,12 @@ func (c *Controller) getFileContent(ctx context.Context,
|
|||||||
return nil, fmt.Errorf("failed to get file content: %w", err)
|
return nil, fmt.Errorf("failed to get file content: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := output.Content.Close(); err != nil {
|
||||||
|
log.Ctx(ctx).Warn().Err(err).Msgf("failed to close blob content reader.")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
content, err := io.ReadAll(output.Content)
|
content, err := io.ReadAll(output.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read blob content: %w", err)
|
return nil, fmt.Errorf("failed to read blob content: %w", err)
|
||||||
@ -213,6 +221,12 @@ func (c *Controller) getSymlinkContent(ctx context.Context,
|
|||||||
return nil, fmt.Errorf("failed to get symlink: %w", err)
|
return nil, fmt.Errorf("failed to get symlink: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := output.Content.Close(); err != nil {
|
||||||
|
log.Ctx(ctx).Warn().Err(err).Msgf("failed to close blob content reader.")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
content, err := io.ReadAll(output.Content)
|
content, err := io.ReadAll(output.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read blob content: %w", err)
|
return nil, fmt.Errorf("failed to read blob content: %w", err)
|
||||||
|
@ -32,7 +32,7 @@ func (c *Controller) Raw(ctx context.Context,
|
|||||||
repoRef string,
|
repoRef string,
|
||||||
gitRef string,
|
gitRef string,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
) (io.Reader, int64, error) {
|
) (io.ReadCloser, int64, error) {
|
||||||
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
|
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
@ -21,6 +21,8 @@ import (
|
|||||||
"github.com/harness/gitness/app/api/controller/repo"
|
"github.com/harness/gitness/app/api/controller/repo"
|
||||||
"github.com/harness/gitness/app/api/render"
|
"github.com/harness/gitness/app/api/render"
|
||||||
"github.com/harness/gitness/app/api/request"
|
"github.com/harness/gitness/app/api/request"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleRaw returns the raw content of a file.
|
// HandleRaw returns the raw content of a file.
|
||||||
@ -45,6 +47,12 @@ func HandleRaw(repoCtrl *repo.Controller) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := dataReader.Close(); err != nil {
|
||||||
|
log.Ctx(ctx).Warn().Err(err).Msgf("failed to close blob content reader.")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
w.Header().Add("Content-Length", fmt.Sprint(dataLength))
|
w.Header().Add("Content-Length", fmt.Sprint(dataLength))
|
||||||
|
|
||||||
render.Reader(ctx, w, http.StatusOK, dataReader)
|
render.Reader(ctx, w, http.StatusOK, dataReader)
|
||||||
|
@ -21,6 +21,8 @@ import (
|
|||||||
|
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
@ -63,6 +65,12 @@ func (f *service) Get(
|
|||||||
return nil, fmt.Errorf("failed to read blob: %w", err)
|
return nil, fmt.Errorf("failed to read blob: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := blobReader.Content.Close(); err != nil {
|
||||||
|
log.Ctx(ctx).Warn().Err(err).Msgf("failed to close blob content reader.")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
buf, err := io.ReadAll(blobReader.Content)
|
buf, err := io.ReadAll(blobReader.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not read blob content from file: %w", err)
|
return nil, fmt.Errorf("could not read blob content from file: %w", err)
|
||||||
|
@ -223,6 +223,12 @@ func (s *Service) getCodeOwnerFile(
|
|||||||
return nil, fmt.Errorf("failed to get file content: %w", err)
|
return nil, fmt.Errorf("failed to get file content: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := output.Content.Close(); err != nil {
|
||||||
|
log.Ctx(ctx).Warn().Err(err).Msgf("failed to close blob content reader.")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
content, err := io.ReadAll(output.Content)
|
content, err := io.ReadAll(output.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read blob content: %w", err)
|
return nil, fmt.Errorf("failed to read blob content: %w", err)
|
||||||
|
@ -127,7 +127,6 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
typesConfig := server.ProvideGitConfig(config)
|
typesConfig := server.ProvideGitConfig(config)
|
||||||
goGitRepoProvider := adapter.ProvideGoGitRepoProvider()
|
|
||||||
universalClient, err := server.ProvideRedis(config)
|
universalClient, err := server.ProvideRedis(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -136,7 +135,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
gitAdapter, err := git.ProvideGITAdapter(typesConfig, goGitRepoProvider, cacheCache)
|
gitAdapter, err := git.ProvideGITAdapter(typesConfig, cacheCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -26,13 +26,11 @@ import (
|
|||||||
|
|
||||||
type Adapter struct {
|
type Adapter struct {
|
||||||
traceGit bool
|
traceGit bool
|
||||||
repoProvider *GoGitRepoProvider
|
|
||||||
lastCommitCache cache.Cache[CommitEntryKey, *types.Commit]
|
lastCommitCache cache.Cache[CommitEntryKey, *types.Commit]
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
config types.Config,
|
config types.Config,
|
||||||
repoProvider *GoGitRepoProvider,
|
|
||||||
lastCommitCache cache.Cache[CommitEntryKey, *types.Commit],
|
lastCommitCache cache.Cache[CommitEntryKey, *types.Commit],
|
||||||
) (Adapter, error) {
|
) (Adapter, error) {
|
||||||
// TODO: should be subdir of gitRoot? What is it being used for?
|
// TODO: should be subdir of gitRoot? What is it being used for?
|
||||||
@ -45,7 +43,6 @@ func New(
|
|||||||
|
|
||||||
return Adapter{
|
return Adapter{
|
||||||
traceGit: config.Trace,
|
traceGit: config.Trace,
|
||||||
repoProvider: repoProvider,
|
|
||||||
lastCommitCache: lastCommitCache,
|
lastCommitCache: lastCommitCache,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,13 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/types"
|
||||||
|
|
||||||
gogitplumbing "github.com/go-git/go-git/v5/plumbing"
|
"code.gitea.io/gitea/modules/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetBlob returns the blob for the given object sha.
|
// GetBlob returns the blob for the given object sha.
|
||||||
@ -31,58 +32,60 @@ func (a Adapter) GetBlob(
|
|||||||
sha string,
|
sha string,
|
||||||
sizeLimit int64,
|
sizeLimit int64,
|
||||||
) (*types.BlobReader, error) {
|
) (*types.BlobReader, error) {
|
||||||
if repoPath == "" {
|
stdIn, stdOut, cancel := git.CatFileBatch(ctx, repoPath)
|
||||||
return nil, ErrRepositoryPathEmpty
|
|
||||||
}
|
_, err := stdIn.Write([]byte(sha + "\n"))
|
||||||
repo, err := a.repoProvider.Get(ctx, repoPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Internal("failed to open repository", err)
|
cancel()
|
||||||
|
return nil, fmt.Errorf("failed to write blob sha to git stdin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
blob, err := repo.BlobObject(gogitplumbing.NewHash(sha))
|
objectSHA, objectType, objectSize, err := git.ReadBatchLine(stdOut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gogitplumbing.ErrObjectNotFound) {
|
cancel()
|
||||||
return nil, errors.NotFound("blob sha %s not found", sha)
|
return nil, processGiteaErrorf(err, "failed to read cat-file batch line")
|
||||||
}
|
}
|
||||||
return nil, errors.Internal("failed to get blob object for sha '%s'", sha, err)
|
|
||||||
|
if string(objectSHA) != sha {
|
||||||
|
cancel()
|
||||||
|
return nil, fmt.Errorf("cat-file returned object sha '%s' but expected '%s'", objectSHA, sha)
|
||||||
|
}
|
||||||
|
if objectType != string(git.ObjectBlob) {
|
||||||
|
cancel()
|
||||||
|
return nil, errors.InvalidArgument(
|
||||||
|
"cat-file returned object type '%s' but expected '%s'", objectType, git.ObjectBlob)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectSize := blob.Size
|
|
||||||
contentSize := objectSize
|
contentSize := objectSize
|
||||||
if sizeLimit > 0 && contentSize > sizeLimit {
|
if sizeLimit > 0 && sizeLimit < contentSize {
|
||||||
contentSize = sizeLimit
|
contentSize = sizeLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, err := blob.Reader()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Internal("failed to open blob object for sha '%s'", sha, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.BlobReader{
|
return &types.BlobReader{
|
||||||
SHA: sha,
|
SHA: sha,
|
||||||
Size: objectSize,
|
Size: objectSize,
|
||||||
ContentSize: contentSize,
|
ContentSize: contentSize,
|
||||||
Content: LimitReadCloser(reader, contentSize),
|
Content: newLimitReaderCloser(stdOut, contentSize, cancel),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LimitReadCloser(r io.ReadCloser, n int64) io.ReadCloser {
|
func newLimitReaderCloser(reader io.Reader, limit int64, stop func()) limitReaderCloser {
|
||||||
return limitReadCloser{
|
return limitReaderCloser{
|
||||||
r: io.LimitReader(r, n),
|
reader: io.LimitReader(reader, limit),
|
||||||
c: r,
|
stop: stop,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// limitReadCloser implements io.ReadCloser interface.
|
type limitReaderCloser struct {
|
||||||
type limitReadCloser struct {
|
reader io.Reader
|
||||||
r io.Reader
|
stop func()
|
||||||
c io.Closer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l limitReadCloser) Read(p []byte) (n int, err error) {
|
func (l limitReaderCloser) Read(p []byte) (n int, err error) {
|
||||||
return l.r.Read(p)
|
return l.reader.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l limitReadCloser) Close() error {
|
func (l limitReaderCloser) Close() error {
|
||||||
return l.c.Close()
|
l.stop()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
// 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 adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/cache"
|
|
||||||
"github.com/harness/gitness/errors"
|
|
||||||
"github.com/harness/gitness/git/types"
|
|
||||||
|
|
||||||
gogitosfs "github.com/go-git/go-billy/v5/osfs"
|
|
||||||
gogit "github.com/go-git/go-git/v5"
|
|
||||||
gogitplumbing "github.com/go-git/go-git/v5/plumbing"
|
|
||||||
gogitcache "github.com/go-git/go-git/v5/plumbing/cache"
|
|
||||||
gogitobject "github.com/go-git/go-git/v5/plumbing/object"
|
|
||||||
gogitfilesystem "github.com/go-git/go-git/v5/storage/filesystem"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GoGitRepoProvider struct {
|
|
||||||
gitObjectCache cache.Cache[string, *gogitcache.ObjectLRU]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGoGitRepoProvider(
|
|
||||||
objectCacheMax int,
|
|
||||||
cacheDuration time.Duration,
|
|
||||||
) *GoGitRepoProvider {
|
|
||||||
c := cache.New[string, *gogitcache.ObjectLRU](gitObjectCacheGetter{
|
|
||||||
maxSize: objectCacheMax,
|
|
||||||
}, cacheDuration)
|
|
||||||
return &GoGitRepoProvider{
|
|
||||||
gitObjectCache: c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gr *GoGitRepoProvider) Get(
|
|
||||||
ctx context.Context,
|
|
||||||
path string,
|
|
||||||
) (*gogit.Repository, error) {
|
|
||||||
fs := gogitosfs.New(path)
|
|
||||||
stat, err := fs.Stat("")
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil, types.ErrRepositoryNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("failed to check repository existence: %w", err)
|
|
||||||
}
|
|
||||||
if !stat.IsDir() {
|
|
||||||
return nil, types.ErrRepositoryCorrupted
|
|
||||||
}
|
|
||||||
|
|
||||||
gitObjectCache, err := gr.gitObjectCache.Get(ctx, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get repository cache: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := gogitfilesystem.NewStorage(fs, gitObjectCache)
|
|
||||||
|
|
||||||
repo, err := gogit.Open(s, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type gitObjectCacheGetter struct {
|
|
||||||
maxSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r gitObjectCacheGetter) Find(
|
|
||||||
_ context.Context,
|
|
||||||
_ string,
|
|
||||||
) (*gogitcache.ObjectLRU, error) {
|
|
||||||
return gogitcache.NewObjectLRU(gogitcache.FileSize(r.maxSize)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Adapter) getGoGitCommit(
|
|
||||||
ctx context.Context,
|
|
||||||
repoPath string,
|
|
||||||
rev string,
|
|
||||||
) (*gogit.Repository, *gogitobject.Commit, error) {
|
|
||||||
if repoPath == "" {
|
|
||||||
return nil, nil, ErrRepositoryPathEmpty
|
|
||||||
}
|
|
||||||
repo, err := a.repoProvider.Get(ctx, repoPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("failed to open repository: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var refSHA *gogitplumbing.Hash
|
|
||||||
if rev == "" {
|
|
||||||
var head *gogitplumbing.Reference
|
|
||||||
head, err = repo.Head()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Internal("failed to get head: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
headHash := head.Hash()
|
|
||||||
refSHA = &headHash
|
|
||||||
} else {
|
|
||||||
refSHA, err = repo.ResolveRevision(gogitplumbing.Revision(rev))
|
|
||||||
if errors.Is(err, gogitplumbing.ErrReferenceNotFound) {
|
|
||||||
return nil, nil, errors.NotFound("reference not found '%s'", rev)
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, nil, errors.Internal("failed to resolve revision '%s'", rev, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refCommit, err := repo.CommitObject(*refSHA)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("failed to load commit data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo, refCommit, nil
|
|
||||||
}
|
|
@ -82,24 +82,6 @@ func mapGiteaCommit(giteaCommit *gitea.Commit) (*types.Commit, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapGiteaNodeToTreeNodeModeAndType(giteaMode gitea.EntryMode) (types.TreeNodeType, types.TreeNodeMode, error) {
|
|
||||||
switch giteaMode {
|
|
||||||
case gitea.EntryModeBlob:
|
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeFile, nil
|
|
||||||
case gitea.EntryModeSymlink:
|
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeSymlink, nil
|
|
||||||
case gitea.EntryModeExec:
|
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeExec, nil
|
|
||||||
case gitea.EntryModeCommit:
|
|
||||||
return types.TreeNodeTypeCommit, types.TreeNodeModeCommit, nil
|
|
||||||
case gitea.EntryModeTree:
|
|
||||||
return types.TreeNodeTypeTree, types.TreeNodeModeTree, nil
|
|
||||||
default:
|
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeFile,
|
|
||||||
fmt.Errorf("received unknown tree node mode from gitea: '%s'", giteaMode.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapGiteaSignature(
|
func mapGiteaSignature(
|
||||||
giteaSignature *gitea.Signature,
|
giteaSignature *gitea.Signature,
|
||||||
) (types.Signature, error) {
|
) (types.Signature, error) {
|
||||||
|
@ -16,93 +16,86 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/types"
|
||||||
|
|
||||||
gogitobject "github.com/go-git/go-git/v5/plumbing/object"
|
gitea "code.gitea.io/gitea/modules/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nolint:gocognit
|
//nolint:gocognit
|
||||||
func (a Adapter) MatchFiles(
|
func (a Adapter) MatchFiles(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
ref string,
|
rev string,
|
||||||
dirPath string,
|
treePath string,
|
||||||
pattern string,
|
pattern string,
|
||||||
maxSize int,
|
maxSize int,
|
||||||
) ([]types.FileContent, error) {
|
) ([]types.FileContent, error) {
|
||||||
if repoPath == "" {
|
nodes, err := lsDirectory(ctx, repoPath, rev, treePath)
|
||||||
return nil, ErrRepositoryPathEmpty
|
|
||||||
}
|
|
||||||
_, refCommit, err := a.getGoGitCommit(ctx, repoPath, ref)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to list files in match files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tree, err := refCommit.Tree()
|
catFileWriter, catFileReader, catFileStop := gitea.CatFileBatch(ctx, repoPath)
|
||||||
if err != nil {
|
defer catFileStop()
|
||||||
return nil, fmt.Errorf("failed to get tree for the commit: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dirPath != "" {
|
|
||||||
tree, err = tree.Tree(dirPath)
|
|
||||||
if errors.Is(err, gogitobject.ErrDirectoryNotFound) {
|
|
||||||
return nil, &types.PathNotFoundError{Path: dirPath}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to navigate to %s directory: %w", dirPath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var files []types.FileContent
|
var files []types.FileContent
|
||||||
for i := range tree.Entries {
|
for i := range nodes {
|
||||||
fileEntry := tree.Entries[i]
|
if nodes[i].NodeType != types.TreeNodeTypeBlob {
|
||||||
ok, err := path.Match(pattern, fileEntry.Name)
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := nodes[i].Name
|
||||||
|
ok, err := path.Match(pattern, fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to match file name against pattern: %w", err)
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
name := fileEntry.Name
|
_, err = catFileWriter.Write([]byte(nodes[i].Sha + "\n"))
|
||||||
|
|
||||||
f, err := tree.TreeEntryFile(&fileEntry)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get tree entry file %s: %w", name, err)
|
return nil, fmt.Errorf("failed to ask for file content from cat file batch: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, err := f.Reader()
|
_, _, size, err := gitea.ReadBatchLine(catFileReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open tree entry file %s: %w", name, err)
|
return nil, fmt.Errorf("failed to read cat-file batch header: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := path.Join(dirPath, name)
|
reader := io.LimitReader(catFileReader, size+1) // plus eol
|
||||||
|
|
||||||
content, err := func(r io.ReadCloser) ([]byte, error) {
|
if size > int64(maxSize) {
|
||||||
defer func() {
|
_, err = io.Copy(io.Discard, reader)
|
||||||
_ = r.Close()
|
if err != nil {
|
||||||
}()
|
return nil, fmt.Errorf("failed to discard a large file: %w", err)
|
||||||
return io.ReadAll(io.LimitReader(reader, int64(maxSize)))
|
}
|
||||||
}(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read file content %s: %w", name, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(content) == maxSize {
|
data, err := io.ReadAll(reader)
|
||||||
// skip truncated files
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read cat-file content: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) > 0 {
|
||||||
|
data = data[:len(data)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
files = append(files, types.FileContent{
|
files = append(files, types.FileContent{
|
||||||
Path: filePath,
|
Path: nodes[i].Path,
|
||||||
Content: content,
|
Content: data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = catFileWriter.Close()
|
||||||
|
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
@ -44,10 +44,8 @@ var (
|
|||||||
|
|
||||||
func setupGit(t *testing.T) adapter.Adapter {
|
func setupGit(t *testing.T) adapter.Adapter {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
gogitProvider := adapter.ProvideGoGitRepoProvider()
|
|
||||||
git, err := adapter.New(
|
git, err := adapter.New(
|
||||||
types.Config{Trace: true},
|
types.Config{Trace: true},
|
||||||
gogitProvider,
|
|
||||||
adapter.NewInMemoryLastCommitCache(5*time.Minute),
|
adapter.NewInMemoryLastCommitCache(5*time.Minute),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -15,14 +15,16 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/types"
|
"github.com/harness/gitness/git/types"
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
gitea "code.gitea.io/gitea/modules/git"
|
||||||
@ -32,108 +34,173 @@ func cleanTreePath(treePath string) string {
|
|||||||
return strings.Trim(path.Clean("/"+treePath), "/")
|
return strings.Trim(path.Clean("/"+treePath), "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTreeNode returns the tree node at the given path as found for the provided reference.
|
func parseTreeNodeMode(s string) (types.TreeNodeType, types.TreeNodeMode, error) {
|
||||||
// Note: ref can be Branch / Tag / CommitSHA.
|
switch s {
|
||||||
func (a Adapter) GetTreeNode(
|
case "100644":
|
||||||
ctx context.Context,
|
return types.TreeNodeTypeBlob, types.TreeNodeModeFile, nil
|
||||||
repoPath string,
|
case "120000":
|
||||||
ref string,
|
return types.TreeNodeTypeBlob, types.TreeNodeModeSymlink, nil
|
||||||
treePath string,
|
case "100775":
|
||||||
) (*types.TreeNode, error) {
|
return types.TreeNodeTypeBlob, types.TreeNodeModeExec, nil
|
||||||
treePath = cleanTreePath(treePath)
|
case "160000":
|
||||||
|
return types.TreeNodeTypeCommit, types.TreeNodeModeCommit, nil
|
||||||
giteaRepo, err := gitea.OpenRepository(ctx, repoPath)
|
case "040000":
|
||||||
if err != nil {
|
return types.TreeNodeTypeTree, types.TreeNodeModeTree, nil
|
||||||
return nil, processGiteaErrorf(err, "failed to open repository")
|
default:
|
||||||
|
return types.TreeNodeTypeBlob, types.TreeNodeModeFile,
|
||||||
|
fmt.Errorf("unknown git tree node mode: '%s'", s)
|
||||||
}
|
}
|
||||||
defer giteaRepo.Close()
|
|
||||||
|
|
||||||
// Get the giteaCommit object for the ref
|
|
||||||
giteaCommit, err := giteaRepo.GetCommit(ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "error getting commit for ref '%s'", ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: handle ErrNotExist :)
|
|
||||||
giteaTreeEntry, err := giteaCommit.GetTreeEntryByPath(treePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "failed to get tree entry for commit '%s' at path '%s'",
|
|
||||||
giteaCommit.ID.String(), treePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeType, mode, err := mapGiteaNodeToTreeNodeModeAndType(giteaTreeEntry.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.TreeNode{
|
|
||||||
Mode: mode,
|
|
||||||
NodeType: nodeType,
|
|
||||||
Sha: giteaTreeEntry.ID.String(),
|
|
||||||
Name: giteaTreeEntry.Name(),
|
|
||||||
Path: treePath,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTreeNodes lists the child nodes of a tree reachable from ref via the specified path
|
func scanZeroSeparated(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
// and includes the latest commit for all nodes if requested.
|
if atEOF && len(data) == 0 {
|
||||||
// Note: ref can be Branch / Tag / CommitSHA.
|
return 0, nil, nil // Return nothing if at end of file and no data passed
|
||||||
func (a Adapter) ListTreeNodes(
|
}
|
||||||
|
if i := strings.IndexByte(string(data), 0); i >= 0 {
|
||||||
|
return i + 1, data[0:i], nil // Split at zero byte
|
||||||
|
}
|
||||||
|
if atEOF {
|
||||||
|
return len(data), data, nil // at the end of file return the data
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var regexpLsTreeLongColumns = regexp.MustCompile(`^(\d{6})\s+(\w+)\s+(\w+)\t(.+)$`)
|
||||||
|
|
||||||
|
func lsTree(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
repoPath string,
|
repoPath string,
|
||||||
ref string,
|
rev string,
|
||||||
treePath string,
|
treePath string,
|
||||||
) ([]types.TreeNode, error) {
|
) ([]types.TreeNode, error) {
|
||||||
|
if repoPath == "" {
|
||||||
|
return nil, ErrRepositoryPathEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"ls-tree", "-z", rev, treePath}
|
||||||
|
output, stderr, err := gitea.NewCommand(ctx, args...).RunStdString(&gitea.RunOpts{Dir: repoPath})
|
||||||
|
if strings.Contains(stderr, "fatal: Not a valid object name") {
|
||||||
|
return nil, errors.InvalidArgument("revision %q not found", rev)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to run git ls-tree: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if output == "" {
|
||||||
|
return nil, errors.Format(errors.StatusPathNotFound, "path %q not found", treePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
n := strings.Count(output, "\x00")
|
||||||
|
|
||||||
|
list := make([]types.TreeNode, 0, n)
|
||||||
|
scan := bufio.NewScanner(strings.NewReader(output))
|
||||||
|
scan.Split(scanZeroSeparated)
|
||||||
|
for scan.Scan() {
|
||||||
|
columns := regexpLsTreeLongColumns.FindStringSubmatch(scan.Text())
|
||||||
|
if columns == nil {
|
||||||
|
return nil, errors.New("unrecognized format of git directory listing")
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeType, nodeMode, err := parseTreeNodeMode(columns[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse git mode: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeSha := columns[3]
|
||||||
|
nodePath := columns[4]
|
||||||
|
nodeName := path.Base(nodePath)
|
||||||
|
|
||||||
|
list = append(list, types.TreeNode{
|
||||||
|
NodeType: nodeType,
|
||||||
|
Mode: nodeMode,
|
||||||
|
Sha: nodeSha,
|
||||||
|
Name: nodeName,
|
||||||
|
Path: nodePath,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lsFile returns all tree node entries in the requested directory.
|
||||||
|
func lsDirectory(
|
||||||
|
ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
|
rev string,
|
||||||
|
treePath string,
|
||||||
|
) ([]types.TreeNode, error) {
|
||||||
|
treePath = path.Clean(treePath)
|
||||||
|
if treePath == "" {
|
||||||
|
treePath = "."
|
||||||
|
} else {
|
||||||
|
treePath += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return lsTree(ctx, repoPath, rev, treePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lsFile returns one tree node entry.
|
||||||
|
func lsFile(
|
||||||
|
ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
|
rev string,
|
||||||
|
treePath string,
|
||||||
|
) (types.TreeNode, error) {
|
||||||
treePath = cleanTreePath(treePath)
|
treePath = cleanTreePath(treePath)
|
||||||
|
|
||||||
giteaRepo, err := gitea.OpenRepository(ctx, repoPath)
|
list, err := lsTree(ctx, repoPath, rev, treePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, processGiteaErrorf(err, "failed to open repository")
|
return types.TreeNode{}, fmt.Errorf("failed to ls file: %w", err)
|
||||||
}
|
}
|
||||||
defer giteaRepo.Close()
|
if len(list) != 1 {
|
||||||
|
return types.TreeNode{}, fmt.Errorf("ls file list contains more than one element, len=%d", len(list))
|
||||||
// Get the giteaCommit object for the ref
|
|
||||||
giteaCommit, err := giteaRepo.GetCommit(ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "error getting commit for ref '%s'", ref)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the giteaTree object for the ref
|
return list[0], nil
|
||||||
giteaTree, err := giteaCommit.SubTree(treePath)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "error getting tree for '%s'", treePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
giteaEntries, err := giteaTree.ListEntries()
|
// GetTreeNode returns the tree node at the given path as found for the provided reference.
|
||||||
if err != nil {
|
func (a Adapter) GetTreeNode(ctx context.Context, repoPath, rev, treePath string) (*types.TreeNode, error) {
|
||||||
return nil, processGiteaErrorf(err, "failed to list entries for tree '%s'", treePath)
|
// root path (empty path) is a special case
|
||||||
}
|
if treePath == "" {
|
||||||
|
if repoPath == "" {
|
||||||
|
return nil, ErrRepositoryPathEmpty
|
||||||
|
}
|
||||||
|
|
||||||
nodes := make([]types.TreeNode, len(giteaEntries))
|
args := []string{"show", "--no-patch", "--format=" + fmtTreeHash, rev}
|
||||||
for i := range giteaEntries {
|
treeSHA, stderr, err := gitea.NewCommand(ctx, args...).RunStdString(&gitea.RunOpts{Dir: repoPath})
|
||||||
giteaEntry := giteaEntries[i]
|
if strings.Contains(stderr, "ambiguous argument") {
|
||||||
|
return nil, errors.InvalidArgument("could not resolve git revision: %s", rev)
|
||||||
var nodeType types.TreeNodeType
|
}
|
||||||
var mode types.TreeNodeMode
|
|
||||||
nodeType, mode, err = mapGiteaNodeToTreeNodeModeAndType(giteaEntry.Mode())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to get root tree node: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// giteaNode.Name() returns the path of the node relative to the tree.
|
return &types.TreeNode{
|
||||||
relPath := giteaEntry.Name()
|
NodeType: types.TreeNodeTypeTree,
|
||||||
name := filepath.Base(relPath)
|
Mode: types.TreeNodeModeTree,
|
||||||
|
Sha: treeSHA,
|
||||||
nodes[i] = types.TreeNode{
|
Name: "",
|
||||||
NodeType: nodeType,
|
Path: "",
|
||||||
Mode: mode,
|
}, err
|
||||||
Sha: giteaEntry.ID.String(),
|
|
||||||
Name: name,
|
|
||||||
Path: filepath.Join(treePath, relPath),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodes, nil
|
treeNode, err := lsFile(ctx, repoPath, rev, treePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get tree node: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &treeNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTreeNodes lists the child nodes of a tree reachable from ref via the specified path.
|
||||||
|
func (a Adapter) ListTreeNodes(ctx context.Context, repoPath, rev, treePath string) ([]types.TreeNode, error) {
|
||||||
|
list, err := lsDirectory(ctx, repoPath, rev, treePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list tree nodes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Adapter) ReadTree(
|
func (a Adapter) ReadTree(
|
||||||
|
@ -27,15 +27,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var WireSet = wire.NewSet(
|
var WireSet = wire.NewSet(
|
||||||
ProvideGoGitRepoProvider,
|
|
||||||
ProvideLastCommitCache,
|
ProvideLastCommitCache,
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProvideGoGitRepoProvider() *GoGitRepoProvider {
|
|
||||||
const objectCacheSize = 16 << 20 // 16MiB
|
|
||||||
return NewGoGitRepoProvider(objectCacheSize, 15*time.Minute)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProvideLastCommitCache(
|
func ProvideLastCommitCache(
|
||||||
config types.Config,
|
config types.Config,
|
||||||
redisClient redis.UniversalClient,
|
redisClient redis.UniversalClient,
|
||||||
|
10
git/blob.go
10
git/blob.go
@ -17,8 +17,6 @@ package git
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GetBlobParams struct {
|
type GetBlobParams struct {
|
||||||
@ -34,7 +32,7 @@ type GetBlobOutput struct {
|
|||||||
// ContentSize is the total number of bytes returned by the Content Reader.
|
// ContentSize is the total number of bytes returned by the Content Reader.
|
||||||
ContentSize int64
|
ContentSize int64
|
||||||
// Content contains the (partial) content of the blob.
|
// Content contains the (partial) content of the blob.
|
||||||
Content io.Reader
|
Content io.ReadCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetBlob(ctx context.Context, params *GetBlobParams) (*GetBlobOutput, error) {
|
func (s *Service) GetBlob(ctx context.Context, params *GetBlobParams) (*GetBlobOutput, error) {
|
||||||
@ -49,12 +47,6 @@ func (s *Service) GetBlob(ctx context.Context, params *GetBlobParams) (*GetBlobO
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
dErr := reader.Content.Close()
|
|
||||||
if dErr != nil {
|
|
||||||
log.Ctx(ctx).Warn().Err(err).Msgf("failed to close blob content reader.")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return &GetBlobOutput{
|
return &GetBlobOutput{
|
||||||
SHA: reader.SHA,
|
SHA: reader.SHA,
|
||||||
|
@ -31,10 +31,9 @@ var WireSet = wire.NewSet(
|
|||||||
|
|
||||||
func ProvideGITAdapter(
|
func ProvideGITAdapter(
|
||||||
config types.Config,
|
config types.Config,
|
||||||
repoProvider *adapter.GoGitRepoProvider,
|
|
||||||
lastCommitCache cache.Cache[adapter.CommitEntryKey, *types.Commit],
|
lastCommitCache cache.Cache[adapter.CommitEntryKey, *types.Commit],
|
||||||
) (Adapter, error) {
|
) (Adapter, error) {
|
||||||
return adapter.New(config, repoProvider, lastCommitCache)
|
return adapter.New(config, lastCommitCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideService(config types.Config, adapter Adapter, storage storage.Store) (Interface, error) {
|
func ProvideService(config types.Config, adapter Adapter, storage storage.Store) (Interface, error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user