remove gogit (#887)

This commit is contained in:
Marko Gacesa 2023-12-11 10:15:15 +00:00 committed by Harness
parent 4d550a78bd
commit a7f11126fa
17 changed files with 265 additions and 337 deletions

View File

@ -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) {

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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
}

View File

@ -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) {

View File

@ -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
} }

View File

@ -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 {

View File

@ -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(

View File

@ -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,

View File

@ -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,

View File

@ -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) {