mirror of
https://github.com/harness/drone.git
synced 2025-05-19 02:20:03 +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)
|
||||
if errors.Is(codeowners.ErrNotFound, err) {
|
||||
if errors.Is(err, codeowners.ErrNotFound) {
|
||||
return types.CodeOwnerEvaluation{}, usererror.ErrNotFound
|
||||
}
|
||||
if codeowners.IsTooLargeError(err) {
|
||||
|
@ -26,6 +26,8 @@ import (
|
||||
"github.com/harness/gitness/git"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -186,6 +188,12 @@ func (c *Controller) getFileContent(ctx context.Context,
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read blob content: %w", err)
|
||||
|
@ -32,7 +32,7 @@ func (c *Controller) Raw(ctx context.Context,
|
||||
repoRef string,
|
||||
gitRef string,
|
||||
repoPath string,
|
||||
) (io.Reader, int64, error) {
|
||||
) (io.ReadCloser, int64, error) {
|
||||
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
@ -21,6 +21,8 @@ import (
|
||||
"github.com/harness/gitness/app/api/controller/repo"
|
||||
"github.com/harness/gitness/app/api/render"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// HandleRaw returns the raw content of a file.
|
||||
@ -45,6 +47,12 @@ func HandleRaw(repoCtrl *repo.Controller) http.HandlerFunc {
|
||||
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))
|
||||
|
||||
render.Reader(ctx, w, http.StatusOK, dataReader)
|
||||
|
@ -21,6 +21,8 @@ import (
|
||||
|
||||
"github.com/harness/gitness/git"
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
@ -63,6 +65,12 @@ func (f *service) Get(
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
typesConfig := server.ProvideGitConfig(config)
|
||||
goGitRepoProvider := adapter.ProvideGoGitRepoProvider()
|
||||
universalClient, err := server.ProvideRedis(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -136,7 +135,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gitAdapter, err := git.ProvideGITAdapter(typesConfig, goGitRepoProvider, cacheCache)
|
||||
gitAdapter, err := git.ProvideGITAdapter(typesConfig, cacheCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -26,13 +26,11 @@ import (
|
||||
|
||||
type Adapter struct {
|
||||
traceGit bool
|
||||
repoProvider *GoGitRepoProvider
|
||||
lastCommitCache cache.Cache[CommitEntryKey, *types.Commit]
|
||||
}
|
||||
|
||||
func New(
|
||||
config types.Config,
|
||||
repoProvider *GoGitRepoProvider,
|
||||
lastCommitCache cache.Cache[CommitEntryKey, *types.Commit],
|
||||
) (Adapter, error) {
|
||||
// TODO: should be subdir of gitRoot? What is it being used for?
|
||||
@ -45,7 +43,6 @@ func New(
|
||||
|
||||
return Adapter{
|
||||
traceGit: config.Trace,
|
||||
repoProvider: repoProvider,
|
||||
lastCommitCache: lastCommitCache,
|
||||
}, nil
|
||||
}
|
||||
|
@ -16,12 +16,13 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/harness/gitness/errors"
|
||||
"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.
|
||||
@ -31,58 +32,60 @@ func (a Adapter) GetBlob(
|
||||
sha string,
|
||||
sizeLimit int64,
|
||||
) (*types.BlobReader, error) {
|
||||
if repoPath == "" {
|
||||
return nil, ErrRepositoryPathEmpty
|
||||
}
|
||||
repo, err := a.repoProvider.Get(ctx, repoPath)
|
||||
stdIn, stdOut, cancel := git.CatFileBatch(ctx, repoPath)
|
||||
|
||||
_, err := stdIn.Write([]byte(sha + "\n"))
|
||||
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 errors.Is(err, gogitplumbing.ErrObjectNotFound) {
|
||||
return nil, errors.NotFound("blob sha %s not found", sha)
|
||||
}
|
||||
return nil, errors.Internal("failed to get blob object for sha '%s'", sha, err)
|
||||
cancel()
|
||||
return nil, processGiteaErrorf(err, "failed to read cat-file batch line")
|
||||
}
|
||||
|
||||
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
|
||||
if sizeLimit > 0 && contentSize > sizeLimit {
|
||||
if sizeLimit > 0 && sizeLimit < contentSize {
|
||||
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{
|
||||
SHA: sha,
|
||||
Size: objectSize,
|
||||
ContentSize: contentSize,
|
||||
Content: LimitReadCloser(reader, contentSize),
|
||||
Content: newLimitReaderCloser(stdOut, contentSize, cancel),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func LimitReadCloser(r io.ReadCloser, n int64) io.ReadCloser {
|
||||
return limitReadCloser{
|
||||
r: io.LimitReader(r, n),
|
||||
c: r,
|
||||
func newLimitReaderCloser(reader io.Reader, limit int64, stop func()) limitReaderCloser {
|
||||
return limitReaderCloser{
|
||||
reader: io.LimitReader(reader, limit),
|
||||
stop: stop,
|
||||
}
|
||||
}
|
||||
|
||||
// limitReadCloser implements io.ReadCloser interface.
|
||||
type limitReadCloser struct {
|
||||
r io.Reader
|
||||
c io.Closer
|
||||
type limitReaderCloser struct {
|
||||
reader io.Reader
|
||||
stop func()
|
||||
}
|
||||
|
||||
func (l limitReadCloser) Read(p []byte) (n int, err error) {
|
||||
return l.r.Read(p)
|
||||
func (l limitReaderCloser) Read(p []byte) (n int, err error) {
|
||||
return l.reader.Read(p)
|
||||
}
|
||||
|
||||
func (l limitReadCloser) Close() error {
|
||||
return l.c.Close()
|
||||
func (l limitReaderCloser) Close() error {
|
||||
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
|
||||
}
|
||||
|
||||
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(
|
||||
giteaSignature *gitea.Signature,
|
||||
) (types.Signature, error) {
|
||||
|
@ -16,93 +16,86 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
|
||||
"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(
|
||||
ctx context.Context,
|
||||
repoPath string,
|
||||
ref string,
|
||||
dirPath string,
|
||||
rev string,
|
||||
treePath string,
|
||||
pattern string,
|
||||
maxSize int,
|
||||
) ([]types.FileContent, error) {
|
||||
if repoPath == "" {
|
||||
return nil, ErrRepositoryPathEmpty
|
||||
}
|
||||
_, refCommit, err := a.getGoGitCommit(ctx, repoPath, ref)
|
||||
nodes, err := lsDirectory(ctx, repoPath, rev, treePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to list files in match files: %w", err)
|
||||
}
|
||||
|
||||
tree, err := refCommit.Tree()
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
catFileWriter, catFileReader, catFileStop := gitea.CatFileBatch(ctx, repoPath)
|
||||
defer catFileStop()
|
||||
|
||||
var files []types.FileContent
|
||||
for i := range tree.Entries {
|
||||
fileEntry := tree.Entries[i]
|
||||
ok, err := path.Match(pattern, fileEntry.Name)
|
||||
for i := range nodes {
|
||||
if nodes[i].NodeType != types.TreeNodeTypeBlob {
|
||||
continue
|
||||
}
|
||||
|
||||
fileName := nodes[i].Name
|
||||
ok, err := path.Match(pattern, fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to match file name against pattern: %w", err)
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
name := fileEntry.Name
|
||||
|
||||
f, err := tree.TreeEntryFile(&fileEntry)
|
||||
_, err = catFileWriter.Write([]byte(nodes[i].Sha + "\n"))
|
||||
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 {
|
||||
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) {
|
||||
defer func() {
|
||||
_ = r.Close()
|
||||
}()
|
||||
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 size > int64(maxSize) {
|
||||
_, err = io.Copy(io.Discard, reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to discard a large file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(content) == maxSize {
|
||||
// skip truncated files
|
||||
data, err := io.ReadAll(reader)
|
||||
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
|
||||
}
|
||||
|
||||
files = append(files, types.FileContent{
|
||||
Path: filePath,
|
||||
Content: content,
|
||||
Path: nodes[i].Path,
|
||||
Content: data,
|
||||
})
|
||||
}
|
||||
|
||||
_ = catFileWriter.Close()
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
@ -44,10 +44,8 @@ var (
|
||||
|
||||
func setupGit(t *testing.T) adapter.Adapter {
|
||||
t.Helper()
|
||||
gogitProvider := adapter.ProvideGoGitRepoProvider()
|
||||
git, err := adapter.New(
|
||||
types.Config{Trace: true},
|
||||
gogitProvider,
|
||||
adapter.NewInMemoryLastCommitCache(5*time.Minute),
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -15,14 +15,16 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/harness/gitness/errors"
|
||||
"github.com/harness/gitness/git/types"
|
||||
|
||||
gitea "code.gitea.io/gitea/modules/git"
|
||||
@ -32,108 +34,173 @@ func cleanTreePath(treePath string) string {
|
||||
return strings.Trim(path.Clean("/"+treePath), "/")
|
||||
}
|
||||
|
||||
// GetTreeNode returns the tree node at the given path as found for the provided reference.
|
||||
// Note: ref can be Branch / Tag / CommitSHA.
|
||||
func (a Adapter) GetTreeNode(
|
||||
ctx context.Context,
|
||||
repoPath string,
|
||||
ref string,
|
||||
treePath string,
|
||||
) (*types.TreeNode, error) {
|
||||
treePath = cleanTreePath(treePath)
|
||||
|
||||
giteaRepo, err := gitea.OpenRepository(ctx, repoPath)
|
||||
if err != nil {
|
||||
return nil, processGiteaErrorf(err, "failed to open repository")
|
||||
func parseTreeNodeMode(s string) (types.TreeNodeType, types.TreeNodeMode, error) {
|
||||
switch s {
|
||||
case "100644":
|
||||
return types.TreeNodeTypeBlob, types.TreeNodeModeFile, nil
|
||||
case "120000":
|
||||
return types.TreeNodeTypeBlob, types.TreeNodeModeSymlink, nil
|
||||
case "100775":
|
||||
return types.TreeNodeTypeBlob, types.TreeNodeModeExec, nil
|
||||
case "160000":
|
||||
return types.TreeNodeTypeCommit, types.TreeNodeModeCommit, nil
|
||||
case "040000":
|
||||
return types.TreeNodeTypeTree, types.TreeNodeModeTree, nil
|
||||
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
|
||||
// and includes the latest commit for all nodes if requested.
|
||||
// Note: ref can be Branch / Tag / CommitSHA.
|
||||
func (a Adapter) ListTreeNodes(
|
||||
func scanZeroSeparated(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
return 0, nil, nil // Return nothing if at end of file and no data passed
|
||||
}
|
||||
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,
|
||||
repoPath string,
|
||||
ref string,
|
||||
rev string,
|
||||
treePath string,
|
||||
) ([]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)
|
||||
|
||||
giteaRepo, err := gitea.OpenRepository(ctx, repoPath)
|
||||
list, err := lsTree(ctx, repoPath, rev, treePath)
|
||||
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()
|
||||
|
||||
// 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)
|
||||
if len(list) != 1 {
|
||||
return types.TreeNode{}, fmt.Errorf("ls file list contains more than one element, len=%d", len(list))
|
||||
}
|
||||
|
||||
// Get the giteaTree object for the ref
|
||||
giteaTree, err := giteaCommit.SubTree(treePath)
|
||||
if err != nil {
|
||||
return nil, processGiteaErrorf(err, "error getting tree for '%s'", treePath)
|
||||
}
|
||||
return list[0], nil
|
||||
}
|
||||
|
||||
giteaEntries, err := giteaTree.ListEntries()
|
||||
if err != nil {
|
||||
return nil, processGiteaErrorf(err, "failed to list entries for tree '%s'", treePath)
|
||||
}
|
||||
// GetTreeNode returns the tree node at the given path as found for the provided reference.
|
||||
func (a Adapter) GetTreeNode(ctx context.Context, repoPath, rev, treePath string) (*types.TreeNode, error) {
|
||||
// root path (empty path) is a special case
|
||||
if treePath == "" {
|
||||
if repoPath == "" {
|
||||
return nil, ErrRepositoryPathEmpty
|
||||
}
|
||||
|
||||
nodes := make([]types.TreeNode, len(giteaEntries))
|
||||
for i := range giteaEntries {
|
||||
giteaEntry := giteaEntries[i]
|
||||
|
||||
var nodeType types.TreeNodeType
|
||||
var mode types.TreeNodeMode
|
||||
nodeType, mode, err = mapGiteaNodeToTreeNodeModeAndType(giteaEntry.Mode())
|
||||
args := []string{"show", "--no-patch", "--format=" + fmtTreeHash, rev}
|
||||
treeSHA, stderr, err := gitea.NewCommand(ctx, args...).RunStdString(&gitea.RunOpts{Dir: repoPath})
|
||||
if strings.Contains(stderr, "ambiguous argument") {
|
||||
return nil, errors.InvalidArgument("could not resolve git revision: %s", rev)
|
||||
}
|
||||
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.
|
||||
relPath := giteaEntry.Name()
|
||||
name := filepath.Base(relPath)
|
||||
|
||||
nodes[i] = types.TreeNode{
|
||||
NodeType: nodeType,
|
||||
Mode: mode,
|
||||
Sha: giteaEntry.ID.String(),
|
||||
Name: name,
|
||||
Path: filepath.Join(treePath, relPath),
|
||||
}
|
||||
return &types.TreeNode{
|
||||
NodeType: types.TreeNodeTypeTree,
|
||||
Mode: types.TreeNodeModeTree,
|
||||
Sha: treeSHA,
|
||||
Name: "",
|
||||
Path: "",
|
||||
}, err
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -27,15 +27,9 @@ import (
|
||||
)
|
||||
|
||||
var WireSet = wire.NewSet(
|
||||
ProvideGoGitRepoProvider,
|
||||
ProvideLastCommitCache,
|
||||
)
|
||||
|
||||
func ProvideGoGitRepoProvider() *GoGitRepoProvider {
|
||||
const objectCacheSize = 16 << 20 // 16MiB
|
||||
return NewGoGitRepoProvider(objectCacheSize, 15*time.Minute)
|
||||
}
|
||||
|
||||
func ProvideLastCommitCache(
|
||||
config types.Config,
|
||||
redisClient redis.UniversalClient,
|
||||
|
10
git/blob.go
10
git/blob.go
@ -17,8 +17,6 @@ package git
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type GetBlobParams struct {
|
||||
@ -34,7 +32,7 @@ type GetBlobOutput struct {
|
||||
// ContentSize is the total number of bytes returned by the Content Reader.
|
||||
ContentSize int64
|
||||
// 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) {
|
||||
@ -49,12 +47,6 @@ func (s *Service) GetBlob(ctx context.Context, params *GetBlobParams) (*GetBlobO
|
||||
if err != nil {
|
||||
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{
|
||||
SHA: reader.SHA,
|
||||
|
@ -31,10 +31,9 @@ var WireSet = wire.NewSet(
|
||||
|
||||
func ProvideGITAdapter(
|
||||
config types.Config,
|
||||
repoProvider *adapter.GoGitRepoProvider,
|
||||
lastCommitCache cache.Cache[adapter.CommitEntryKey, *types.Commit],
|
||||
) (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) {
|
||||
|
Loading…
Reference in New Issue
Block a user