mirror of
https://github.com/harness/drone.git
synced 2025-05-04 10:22:44 +08:00
dedicated api to get the last commit info
This commit is contained in:
parent
10ea3fa640
commit
4f1767d512
89
cache/redis_cache.go
vendored
Normal file
89
cache/redis_cache.go
vendored
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright 2022 Harness Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform Free Trial License
|
||||||
|
// that can be found in the LICENSE.md file for this repository.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Redis[K any, V any] struct {
|
||||||
|
client redis.UniversalClient
|
||||||
|
duration time.Duration
|
||||||
|
getter Getter[K, V]
|
||||||
|
keyEncoder func(K) string
|
||||||
|
codec Codec[V]
|
||||||
|
countHit int64
|
||||||
|
countMiss int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Encoder[V any] interface {
|
||||||
|
Encode(value V) string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Decoder[V any] interface {
|
||||||
|
Decode(encoded string) (V, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Codec[V any] interface {
|
||||||
|
Encoder[V]
|
||||||
|
Decoder[V]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRedis[K any, V any](
|
||||||
|
client redis.UniversalClient,
|
||||||
|
getter Getter[K, V],
|
||||||
|
keyEncoder func(K) string,
|
||||||
|
codec Codec[V],
|
||||||
|
duration time.Duration,
|
||||||
|
) *Redis[K, V] {
|
||||||
|
return &Redis[K, V]{
|
||||||
|
client: client,
|
||||||
|
duration: duration,
|
||||||
|
getter: getter,
|
||||||
|
keyEncoder: keyEncoder,
|
||||||
|
codec: codec,
|
||||||
|
countHit: 0,
|
||||||
|
countMiss: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats returns number of cache hits and misses and can be used to monitor the cache efficiency.
|
||||||
|
func (c *Redis[K, V]) Stats() (int64, int64) {
|
||||||
|
return c.countHit, c.countMiss
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the cache.Cache interface.
|
||||||
|
func (c *Redis[K, V]) Get(ctx context.Context, key K) (V, error) {
|
||||||
|
var nothing V
|
||||||
|
|
||||||
|
strKey := c.keyEncoder(key)
|
||||||
|
|
||||||
|
raw, err := c.client.Get(ctx, strKey).Result()
|
||||||
|
if err == nil {
|
||||||
|
c.countHit++
|
||||||
|
return c.codec.Decode(raw)
|
||||||
|
}
|
||||||
|
if err != redis.Nil {
|
||||||
|
return nothing, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.countMiss++
|
||||||
|
|
||||||
|
item, err := c.getter.Find(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return nothing, fmt.Errorf("cache: failed to find one: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.client.Set(ctx, strKey, c.codec.Encode(item), c.duration).Err()
|
||||||
|
if err != nil {
|
||||||
|
return nothing, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return item, nil
|
||||||
|
}
|
@ -147,7 +147,9 @@ 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 := server3.ProvideGITAdapter()
|
cacheCache := server3.ProvideGoGitRepoCache()
|
||||||
|
cache2 := server3.ProvideLastCommitCache(serverConfig, universalClient, cacheCache)
|
||||||
|
gitAdapter, err := server3.ProvideGITAdapter(cacheCache, cache2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ type Interface interface {
|
|||||||
DeleteBranch(ctx context.Context, params *DeleteBranchParams) error
|
DeleteBranch(ctx context.Context, params *DeleteBranchParams) error
|
||||||
ListBranches(ctx context.Context, params *ListBranchesParams) (*ListBranchesOutput, error)
|
ListBranches(ctx context.Context, params *ListBranchesParams) (*ListBranchesOutput, error)
|
||||||
GetRef(ctx context.Context, params GetRefParams) (GetRefResponse, error)
|
GetRef(ctx context.Context, params GetRefParams) (GetRefResponse, error)
|
||||||
|
PathsDetails(ctx context.Context, params PathsDetailsParams) (PathsDetailsOutput, error)
|
||||||
|
|
||||||
// UpdateRef creates, updates or deletes a git ref. If the OldValue is defined it must match the reference value
|
// UpdateRef creates, updates or deletes a git ref. If the OldValue is defined it must match the reference value
|
||||||
// prior to the call. To remove a ref use the zero ref as the NewValue. To require the creation of a new one and
|
// prior to the call. To remove a ref use the zero ref as the NewValue. To require the creation of a new one and
|
||||||
|
@ -7,14 +7,22 @@ package gitea
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/cache"
|
||||||
|
"github.com/harness/gitness/gitrpc/internal/types"
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
gitea "code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Adapter struct {
|
type Adapter struct {
|
||||||
|
repoCache cache.Cache[string, *RepoEntryValue]
|
||||||
|
lastCommitCache cache.Cache[CommitEntryKey, *types.Commit]
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() (Adapter, error) {
|
func New(
|
||||||
|
repoCache cache.Cache[string, *RepoEntryValue],
|
||||||
|
lastCommitCache cache.Cache[CommitEntryKey, *types.Commit],
|
||||||
|
) (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?
|
||||||
setting.Git.HomePath = "home"
|
setting.Git.HomePath = "home"
|
||||||
|
|
||||||
@ -23,5 +31,8 @@ func New() (Adapter, error) {
|
|||||||
return Adapter{}, err
|
return Adapter{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Adapter{}, nil
|
return Adapter{
|
||||||
|
repoCache: repoCache,
|
||||||
|
lastCommitCache: lastCommitCache,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
154
gitrpc/internal/gitea/last_commit_cache.go
Normal file
154
gitrpc/internal/gitea/last_commit_cache.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// Copyright 2022 Harness Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform Free Trial License
|
||||||
|
// that can be found in the LICENSE.md file for this repository.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/cache"
|
||||||
|
"github.com/harness/gitness/gitrpc/internal/types"
|
||||||
|
|
||||||
|
gitea "code.gitea.io/gitea/modules/git"
|
||||||
|
gogitplumbing "github.com/go-git/go-git/v5/plumbing"
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewInMemoryLastCommitCache(
|
||||||
|
cacheDuration time.Duration,
|
||||||
|
repoCache cache.Cache[string, *RepoEntryValue],
|
||||||
|
) cache.Cache[CommitEntryKey, *types.Commit] {
|
||||||
|
return cache.New[CommitEntryKey, *types.Commit](
|
||||||
|
commitEntryGetter{
|
||||||
|
repoCache: repoCache,
|
||||||
|
},
|
||||||
|
cacheDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRedisLastCommitCache(
|
||||||
|
redisClient redis.UniversalClient,
|
||||||
|
cacheDuration time.Duration,
|
||||||
|
repoCache cache.Cache[string, *RepoEntryValue],
|
||||||
|
) cache.Cache[CommitEntryKey, *types.Commit] {
|
||||||
|
return cache.NewRedis[CommitEntryKey, *types.Commit](
|
||||||
|
redisClient,
|
||||||
|
commitEntryGetter{
|
||||||
|
repoCache: repoCache,
|
||||||
|
},
|
||||||
|
func(key CommitEntryKey) string {
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(key))
|
||||||
|
return "gitrpc:last_commit:" + hex.EncodeToString(h.Sum(nil))
|
||||||
|
},
|
||||||
|
commitValueCodec{},
|
||||||
|
cacheDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommitEntryKey string
|
||||||
|
|
||||||
|
const commitEntryKeySeparator = "\x00"
|
||||||
|
|
||||||
|
func makeCommitEntryKey(repoPath, commitSHA, path string) CommitEntryKey {
|
||||||
|
return CommitEntryKey(repoPath + commitEntryKeySeparator + commitSHA + commitEntryKeySeparator + path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CommitEntryKey) Split() (repoPath, commitSHA, path string) {
|
||||||
|
parts := strings.Split(string(c), commitEntryKeySeparator)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repoPath = parts[0]
|
||||||
|
commitSHA = parts[1]
|
||||||
|
path = parts[2]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type commitValueCodec struct{}
|
||||||
|
|
||||||
|
func (c commitValueCodec) Encode(v *types.Commit) string {
|
||||||
|
buffer := &strings.Builder{}
|
||||||
|
_ = gob.NewEncoder(buffer).Encode(v)
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c commitValueCodec) Decode(s string) (*types.Commit, error) {
|
||||||
|
commit := &types.Commit{}
|
||||||
|
if err := gob.NewDecoder(strings.NewReader(s)).Decode(commit); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unpack commit entry value: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return commit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type commitEntryGetter struct {
|
||||||
|
repoCache cache.Cache[string, *RepoEntryValue]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find implements the cache.Getter interface.
|
||||||
|
func (c commitEntryGetter) Find(ctx context.Context, key CommitEntryKey) (*types.Commit, error) {
|
||||||
|
repoPath, rev, path := key.Split()
|
||||||
|
|
||||||
|
if path == "" {
|
||||||
|
path = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"log", "--max-count=1", "--format=%H", rev, "--", path}
|
||||||
|
commitSHA, _, runErr := gitea.NewCommand(ctx, args...).RunStdString(&gitea.RunOpts{Dir: repoPath})
|
||||||
|
if runErr != nil {
|
||||||
|
return nil, fmt.Errorf("failed to run git: %w", runErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
commitSHA = strings.TrimSpace(commitSHA)
|
||||||
|
|
||||||
|
if commitSHA == "" {
|
||||||
|
return nil, types.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := c.repoCache.Get(ctx, repoPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get repository %s from cache: %w", repoPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
commit, err := repo.Repo().CommitObject(gogitplumbing.NewHash(commitSHA))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load commit data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var title string
|
||||||
|
var message string
|
||||||
|
|
||||||
|
title = commit.Message
|
||||||
|
if idx := strings.IndexRune(commit.Message, '\n'); idx >= 0 {
|
||||||
|
title = commit.Message[:idx]
|
||||||
|
message = commit.Message[idx+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.Commit{
|
||||||
|
SHA: commitSHA,
|
||||||
|
Title: title,
|
||||||
|
Message: message,
|
||||||
|
Author: types.Signature{
|
||||||
|
Identity: types.Identity{
|
||||||
|
Name: commit.Author.Name,
|
||||||
|
Email: commit.Author.Email,
|
||||||
|
},
|
||||||
|
When: commit.Author.When,
|
||||||
|
},
|
||||||
|
Committer: types.Signature{
|
||||||
|
Identity: types.Identity{
|
||||||
|
Name: commit.Committer.Name,
|
||||||
|
Email: commit.Committer.Email,
|
||||||
|
},
|
||||||
|
When: commit.Committer.When,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/harness/gitness/gitrpc/internal/types"
|
"github.com/harness/gitness/gitrpc/internal/types"
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
gitea "code.gitea.io/gitea/modules/git"
|
||||||
|
gogitfilemode "github.com/go-git/go-git/v5/plumbing/filemode"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -132,21 +133,21 @@ func mapGiteaCommit(giteaCommit *gitea.Commit) (*types.Commit, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapGiteaNodeToTreeNodeModeAndType(giteaMode gitea.EntryMode) (types.TreeNodeType, types.TreeNodeMode, error) {
|
func mapGogitNodeToTreeNodeModeAndType(gogitMode gogitfilemode.FileMode) (types.TreeNodeType, types.TreeNodeMode, error) {
|
||||||
switch giteaMode {
|
switch gogitMode {
|
||||||
case gitea.EntryModeBlob:
|
case gogitfilemode.Regular, gogitfilemode.Deprecated:
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeFile, nil
|
return types.TreeNodeTypeBlob, types.TreeNodeModeFile, nil
|
||||||
case gitea.EntryModeSymlink:
|
case gogitfilemode.Symlink:
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeSymlink, nil
|
return types.TreeNodeTypeBlob, types.TreeNodeModeSymlink, nil
|
||||||
case gitea.EntryModeExec:
|
case gogitfilemode.Executable:
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeExec, nil
|
return types.TreeNodeTypeBlob, types.TreeNodeModeExec, nil
|
||||||
case gitea.EntryModeCommit:
|
case gogitfilemode.Submodule:
|
||||||
return types.TreeNodeTypeCommit, types.TreeNodeModeCommit, nil
|
return types.TreeNodeTypeCommit, types.TreeNodeModeCommit, nil
|
||||||
case gitea.EntryModeTree:
|
case gogitfilemode.Dir:
|
||||||
return types.TreeNodeTypeTree, types.TreeNodeModeTree, nil
|
return types.TreeNodeTypeTree, types.TreeNodeModeTree, nil
|
||||||
default:
|
default:
|
||||||
return types.TreeNodeTypeBlob, types.TreeNodeModeFile,
|
return types.TreeNodeTypeBlob, types.TreeNodeModeFile,
|
||||||
fmt.Errorf("received unknown tree node mode from gitea: '%s'", giteaMode.String())
|
fmt.Errorf("received unknown tree node mode from gogit: '%s'", gogitMode.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
82
gitrpc/internal/gitea/paths_details.go
Normal file
82
gitrpc/internal/gitea/paths_details.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2022 Harness Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform Free Trial License
|
||||||
|
// that can be found in the LICENSE.md file for this repository.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/gitrpc/internal/types"
|
||||||
|
|
||||||
|
gogitplumbing "github.com/go-git/go-git/v5/plumbing"
|
||||||
|
gogitfilemode "github.com/go-git/go-git/v5/plumbing/filemode"
|
||||||
|
gogitobject "github.com/go-git/go-git/v5/plumbing/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PathsDetails returns additional details about provided the paths.
|
||||||
|
func (g Adapter) PathsDetails(ctx context.Context,
|
||||||
|
repoPath string,
|
||||||
|
rev string,
|
||||||
|
paths []string,
|
||||||
|
) ([]types.PathDetails, error) {
|
||||||
|
repoEntry, err := g.repoCache.Get(ctx, repoPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open repository: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := repoEntry.Repo()
|
||||||
|
|
||||||
|
refSHA, err := repo.ResolveRevision(gogitplumbing.Revision(rev))
|
||||||
|
if errors.Is(err, gogitplumbing.ErrReferenceNotFound) {
|
||||||
|
return nil, types.ErrNotFound
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve revision %s: %w", rev, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
refCommit, err := repo.CommitObject(*refSHA)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load commit data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tree, err := refCommit.Tree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get tree for the commit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]types.PathDetails, len(paths))
|
||||||
|
|
||||||
|
for i, path := range paths {
|
||||||
|
results[i].Path = path
|
||||||
|
|
||||||
|
if len(path) > 0 {
|
||||||
|
entry, err := tree.FindEntry(path)
|
||||||
|
if errors.Is(err, gogitobject.ErrDirectoryNotFound) || errors.Is(err, gogitobject.ErrEntryNotFound) {
|
||||||
|
return nil, types.ErrPathNotFound
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't find path entry %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.Mode == gogitfilemode.Regular || entry.Mode == gogitfilemode.Executable {
|
||||||
|
blobObj, err := repo.Object(gogitplumbing.BlobObject, entry.Hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get blob object size for the path %s and hash %s: %w",
|
||||||
|
path, entry.Hash.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
results[i].Size = blobObj.(*gogitobject.Blob).Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commitEntry, err := g.lastCommitCache.Get(ctx, makeCommitEntryKey(repoPath, refSHA.String(), path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to find last commit for path %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
results[i].LastCommit = commitEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
35
gitrpc/internal/gitea/repo_cache.go
Normal file
35
gitrpc/internal/gitea/repo_cache.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2022 Harness Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform Free Trial License
|
||||||
|
// that can be found in the LICENSE.md file for this repository.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/cache"
|
||||||
|
|
||||||
|
gogit "github.com/go-git/go-git/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRepoCache() cache.Cache[string, *RepoEntryValue] {
|
||||||
|
return cache.New[string, *RepoEntryValue](repoGetter{}, 4*time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
type repoGetter struct{}
|
||||||
|
|
||||||
|
type RepoEntryValue gogit.Repository
|
||||||
|
|
||||||
|
func (repo *RepoEntryValue) Repo() *gogit.Repository {
|
||||||
|
return (*gogit.Repository)(repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r repoGetter) Find(_ context.Context, path string) (*RepoEntryValue, error) {
|
||||||
|
repo, err := gogit.PlainOpen(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*RepoEntryValue)(repo), nil
|
||||||
|
}
|
@ -7,6 +7,7 @@ package gitea
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
@ -16,6 +17,9 @@ import (
|
|||||||
"github.com/harness/gitness/gitrpc/internal/types"
|
"github.com/harness/gitness/gitrpc/internal/types"
|
||||||
|
|
||||||
gitea "code.gitea.io/gitea/modules/git"
|
gitea "code.gitea.io/gitea/modules/git"
|
||||||
|
gogitplumbing "github.com/go-git/go-git/v5/plumbing"
|
||||||
|
gogitfilemode "github.com/go-git/go-git/v5/plumbing/filemode"
|
||||||
|
gogitobject "github.com/go-git/go-git/v5/plumbing/object"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cleanTreePath(treePath string) string {
|
func cleanTreePath(treePath string) string {
|
||||||
@ -24,30 +28,53 @@ func cleanTreePath(treePath string) string {
|
|||||||
|
|
||||||
// GetTreeNode returns the tree node at the given path as found for the provided reference.
|
// GetTreeNode returns the tree node at the given path as found for the provided reference.
|
||||||
// Note: ref can be Branch / Tag / CommitSHA.
|
// Note: ref can be Branch / Tag / CommitSHA.
|
||||||
func (g Adapter) GetTreeNode(ctx context.Context, repoPath string,
|
func (g Adapter) GetTreeNode(ctx context.Context,
|
||||||
ref string, treePath string) (*types.TreeNode, error) {
|
repoPath string,
|
||||||
|
ref string,
|
||||||
|
treePath string,
|
||||||
|
) (*types.TreeNode, error) {
|
||||||
treePath = cleanTreePath(treePath)
|
treePath = cleanTreePath(treePath)
|
||||||
|
|
||||||
giteaRepo, err := gitea.OpenRepository(ctx, repoPath)
|
repoEntry, err := g.repoCache.Get(ctx, repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, processGiteaErrorf(err, "failed to open repository")
|
return nil, processGiteaErrorf(err, "failed to open repository")
|
||||||
}
|
}
|
||||||
defer giteaRepo.Close()
|
|
||||||
|
|
||||||
// Get the giteaCommit object for the ref
|
repo := repoEntry.Repo()
|
||||||
giteaCommit, err := giteaRepo.GetCommit(ref)
|
|
||||||
if err != nil {
|
refSHA, err := repo.ResolveRevision(gogitplumbing.Revision(ref))
|
||||||
return nil, processGiteaErrorf(err, "error getting commit for ref '%s'", ref)
|
if errors.Is(err, gogitplumbing.ErrReferenceNotFound) {
|
||||||
|
return nil, types.ErrNotFound
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve revision %s: %w", ref, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: handle ErrNotExist :)
|
refCommit, err := repo.CommitObject(*refSHA)
|
||||||
giteaTreeEntry, err := giteaCommit.GetTreeEntryByPath(treePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, processGiteaErrorf(err, "failed to get tree entry for commit '%s' at path '%s'",
|
return nil, fmt.Errorf("failed to load commit data: %w", err)
|
||||||
giteaCommit.ID.String(), treePath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeType, mode, err := mapGiteaNodeToTreeNodeModeAndType(giteaTreeEntry.Mode())
|
rootEntry := gogitobject.TreeEntry{
|
||||||
|
Name: "",
|
||||||
|
Mode: gogitfilemode.Dir,
|
||||||
|
Hash: refCommit.TreeHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
treeEntry := &rootEntry
|
||||||
|
|
||||||
|
if len(treePath) > 0 {
|
||||||
|
tree, err := refCommit.Tree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get tree for the commit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
treeEntry, err = tree.FindEntry(treePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't find path entry %s: %w", treePath, types.ErrPathNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeType, mode, err := mapGogitNodeToTreeNodeModeAndType(treeEntry.Mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -55,106 +82,74 @@ func (g Adapter) GetTreeNode(ctx context.Context, repoPath string,
|
|||||||
return &types.TreeNode{
|
return &types.TreeNode{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
NodeType: nodeType,
|
NodeType: nodeType,
|
||||||
Sha: giteaTreeEntry.ID.String(),
|
Sha: treeEntry.Hash.String(),
|
||||||
Name: giteaTreeEntry.Name(),
|
Name: treeEntry.Name,
|
||||||
Path: treePath,
|
Path: treePath,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTreeNodes lists the child nodes of a tree reachable from ref via the specified path
|
// 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.
|
// and includes the latest commit for all nodes if requested.
|
||||||
// IMPORTANT: recursive and includeLatestCommit can't be used together.
|
|
||||||
// Note: ref can be Branch / Tag / CommitSHA.
|
// Note: ref can be Branch / Tag / CommitSHA.
|
||||||
//
|
//
|
||||||
//nolint:gocognit // refactor if needed
|
//nolint:gocognit // refactor if needed
|
||||||
func (g Adapter) ListTreeNodes(ctx context.Context, repoPath string,
|
func (g Adapter) ListTreeNodes(ctx context.Context,
|
||||||
ref string, treePath string, recursive bool, includeLatestCommit bool) ([]types.TreeNodeWithCommit, error) {
|
repoPath string,
|
||||||
if recursive && includeLatestCommit {
|
ref string,
|
||||||
// To avoid potential performance catastrophe, block recursive with includeLatestCommit
|
treePath string,
|
||||||
// TODO: this should return bad error to caller if needed?
|
) ([]types.TreeNode, error) {
|
||||||
// TODO: should this be refactored in two methods?
|
|
||||||
return nil, fmt.Errorf("latest commit with recursive query is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
treePath = cleanTreePath(treePath)
|
treePath = cleanTreePath(treePath)
|
||||||
|
|
||||||
giteaRepo, err := gitea.OpenRepository(ctx, repoPath)
|
repoEntry, err := g.repoCache.Get(ctx, repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, processGiteaErrorf(err, "failed to open repository")
|
return nil, processGiteaErrorf(err, "failed to open repository")
|
||||||
}
|
}
|
||||||
defer giteaRepo.Close()
|
|
||||||
|
|
||||||
// Get the giteaCommit object for the ref
|
repo := repoEntry.Repo()
|
||||||
giteaCommit, err := giteaRepo.GetCommit(ref)
|
|
||||||
|
refSHA, err := repo.ResolveRevision(gogitplumbing.Revision(ref))
|
||||||
|
if errors.Is(err, gogitplumbing.ErrReferenceNotFound) {
|
||||||
|
return nil, types.ErrNotFound
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve revision %s: %w", ref, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
refCommit, err := repo.CommitObject(*refSHA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, processGiteaErrorf(err, "error getting commit for ref '%s'", ref)
|
return nil, fmt.Errorf("failed to load commit data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the giteaTree object for the ref
|
tree, err := refCommit.Tree()
|
||||||
giteaTree, err := giteaCommit.SubTree(treePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, processGiteaErrorf(err, "error getting tree for '%s'", treePath)
|
return nil, fmt.Errorf("failed to get tree for the commit: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var giteaEntries gitea.Entries
|
if len(treePath) > 0 {
|
||||||
if recursive {
|
tree, err = tree.Tree(treePath)
|
||||||
giteaEntries, err = giteaTree.ListEntriesRecursive()
|
if errors.Is(err, gogitobject.ErrDirectoryNotFound) || errors.Is(err, gogitobject.ErrEntryNotFound) {
|
||||||
} else {
|
return nil, types.ErrPathNotFound
|
||||||
giteaEntries, err = giteaTree.ListEntries()
|
} else if err != nil {
|
||||||
}
|
return nil, fmt.Errorf("can't find path entry %s: %w", treePath, err)
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "failed to list entries for tree '%s'", treePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
var latestCommits []gitea.CommitInfo
|
|
||||||
if includeLatestCommit {
|
|
||||||
// TODO: can be speed up with latestCommitCache (currently nil)
|
|
||||||
latestCommits, _, err = giteaEntries.GetCommitsInfo(ctx, giteaCommit, treePath, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGiteaErrorf(err, "failed to get latest commits for entries")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(latestCommits) != len(giteaEntries) {
|
|
||||||
return nil, fmt.Errorf("latest commit info doesn't match tree node info - count differs")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes := make([]types.TreeNodeWithCommit, len(giteaEntries))
|
treeNodes := make([]types.TreeNode, len(tree.Entries))
|
||||||
for i := range giteaEntries {
|
for i, treeEntry := range tree.Entries {
|
||||||
giteaEntry := giteaEntries[i]
|
nodeType, mode, err := mapGogitNodeToTreeNodeModeAndType(treeEntry.Mode)
|
||||||
|
|
||||||
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, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// giteaNode.Name() returns the path of the node relative to the tree.
|
treeNodes[i] = types.TreeNode{
|
||||||
relPath := giteaEntry.Name()
|
NodeType: nodeType,
|
||||||
name := filepath.Base(relPath)
|
Mode: mode,
|
||||||
|
Sha: treeEntry.Hash.String(),
|
||||||
var commit *types.Commit
|
Name: treeEntry.Name,
|
||||||
if includeLatestCommit {
|
Path: filepath.Join(treePath, treeEntry.Name),
|
||||||
commit, err = mapGiteaCommit(latestCommits[i].Commit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes[i] = types.TreeNodeWithCommit{
|
|
||||||
TreeNode: types.TreeNode{
|
|
||||||
NodeType: nodeType,
|
|
||||||
Mode: mode,
|
|
||||||
Sha: giteaEntry.ID.String(),
|
|
||||||
Name: name,
|
|
||||||
Path: filepath.Join(treePath, relPath),
|
|
||||||
},
|
|
||||||
Commit: commit,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodes, nil
|
return treeNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Adapter) ReadTree(ctx context.Context, repoPath, ref string, w io.Writer, args ...string) error {
|
func (g Adapter) ReadTree(ctx context.Context, repoPath, ref string, w io.Writer, args ...string) error {
|
||||||
|
@ -86,16 +86,6 @@ func (s RepositoryService) ListCommits(request *rpc.ListCommitsRequest,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s RepositoryService) getLatestCommit(ctx context.Context, repoPath string,
|
|
||||||
ref string, path string) (*rpc.Commit, error) {
|
|
||||||
gitCommit, err := s.adapter.GetLatestCommit(ctx, repoPath, ref, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, processGitErrorf(err, "failed to get latest commit")
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapGitCommit(gitCommit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s RepositoryService) GetCommitDivergences(ctx context.Context,
|
func (s RepositoryService) GetCommitDivergences(ctx context.Context,
|
||||||
request *rpc.GetCommitDivergencesRequest) (*rpc.GetCommitDivergencesResponse, error) {
|
request *rpc.GetCommitDivergencesRequest) (*rpc.GetCommitDivergencesResponse, error) {
|
||||||
base := request.GetBase()
|
base := request.GetBase()
|
||||||
|
@ -199,7 +199,8 @@ func processGitErrorf(err error, format string, args ...interface{}) error {
|
|||||||
switch {
|
switch {
|
||||||
case errors.Is(err, types.ErrNotFound),
|
case errors.Is(err, types.ErrNotFound),
|
||||||
errors.Is(err, types.ErrSHADoesNotMatch),
|
errors.Is(err, types.ErrSHADoesNotMatch),
|
||||||
errors.Is(err, types.ErrHunkNotFound):
|
errors.Is(err, types.ErrHunkNotFound),
|
||||||
|
errors.Is(err, types.ErrPathNotFound):
|
||||||
return ErrNotFoundf(format, args...)
|
return ErrNotFoundf(format, args...)
|
||||||
case errors.Is(err, types.ErrAlreadyExists):
|
case errors.Is(err, types.ErrAlreadyExists):
|
||||||
return ErrAlreadyExistsf(format, args...)
|
return ErrAlreadyExistsf(format, args...)
|
||||||
|
@ -25,8 +25,8 @@ type GitAdapter interface {
|
|||||||
Push(ctx context.Context, repoPath string, opts types.PushOptions) error
|
Push(ctx context.Context, repoPath string, opts types.PushOptions) error
|
||||||
ReadTree(ctx context.Context, repoPath, ref string, w io.Writer, args ...string) error
|
ReadTree(ctx context.Context, repoPath, ref string, w io.Writer, args ...string) error
|
||||||
GetTreeNode(ctx context.Context, repoPath string, ref string, treePath string) (*types.TreeNode, error)
|
GetTreeNode(ctx context.Context, repoPath string, ref string, treePath string) (*types.TreeNode, error)
|
||||||
ListTreeNodes(ctx context.Context, repoPath string, ref string, treePath string,
|
ListTreeNodes(ctx context.Context, repoPath string, ref string, treePath string) ([]types.TreeNode, error)
|
||||||
recursive bool, includeLatestCommit bool) ([]types.TreeNodeWithCommit, error)
|
PathsDetails(ctx context.Context, repoPath string, ref string, paths []string) ([]types.PathDetails, error)
|
||||||
GetSubmodule(ctx context.Context, repoPath string, ref string, treePath string) (*types.Submodule, error)
|
GetSubmodule(ctx context.Context, repoPath string, ref string, treePath string) (*types.Submodule, error)
|
||||||
GetBlob(ctx context.Context, repoPath string, sha string, sizeLimit int64) (*types.BlobReader, error)
|
GetBlob(ctx context.Context, repoPath string, sha string, sizeLimit int64) (*types.BlobReader, error)
|
||||||
WalkReferences(ctx context.Context, repoPath string, handler types.WalkReferencesHandler,
|
WalkReferences(ctx context.Context, repoPath string, handler types.WalkReferencesHandler,
|
||||||
|
@ -6,6 +6,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/harness/gitness/gitrpc/internal/types"
|
"github.com/harness/gitness/gitrpc/internal/types"
|
||||||
"github.com/harness/gitness/gitrpc/rpc"
|
"github.com/harness/gitness/gitrpc/rpc"
|
||||||
@ -15,8 +16,10 @@ import (
|
|||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s RepositoryService) ListTreeNodes(request *rpc.ListTreeNodesRequest,
|
func (s RepositoryService) ListTreeNodes(
|
||||||
stream rpc.RepositoryService_ListTreeNodesServer) error {
|
request *rpc.ListTreeNodesRequest,
|
||||||
|
stream rpc.RepositoryService_ListTreeNodesServer,
|
||||||
|
) error {
|
||||||
ctx := stream.Context()
|
ctx := stream.Context()
|
||||||
base := request.GetBase()
|
base := request.GetBase()
|
||||||
if base == nil {
|
if base == nil {
|
||||||
@ -26,7 +29,7 @@ func (s RepositoryService) ListTreeNodes(request *rpc.ListTreeNodesRequest,
|
|||||||
repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid())
|
repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid())
|
||||||
|
|
||||||
gitNodes, err := s.adapter.ListTreeNodes(ctx, repoPath,
|
gitNodes, err := s.adapter.ListTreeNodes(ctx, repoPath,
|
||||||
request.GetGitRef(), request.GetPath(), request.GetRecursive(), request.GetIncludeLatestCommit())
|
request.GetGitRef(), request.GetPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return processGitErrorf(err, "failed to list tree nodes")
|
return processGitErrorf(err, "failed to list tree nodes")
|
||||||
}
|
}
|
||||||
@ -34,14 +37,6 @@ func (s RepositoryService) ListTreeNodes(request *rpc.ListTreeNodesRequest,
|
|||||||
log.Ctx(ctx).Trace().Msgf("git adapter returned %d nodes", len(gitNodes))
|
log.Ctx(ctx).Trace().Msgf("git adapter returned %d nodes", len(gitNodes))
|
||||||
|
|
||||||
for _, gitNode := range gitNodes {
|
for _, gitNode := range gitNodes {
|
||||||
var commit *rpc.Commit
|
|
||||||
if request.GetIncludeLatestCommit() {
|
|
||||||
commit, err = mapGitCommit(gitNode.Commit)
|
|
||||||
if err != nil {
|
|
||||||
return status.Errorf(codes.Internal, "failed to map git commit: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = stream.Send(&rpc.ListTreeNodesResponse{
|
err = stream.Send(&rpc.ListTreeNodesResponse{
|
||||||
Node: &rpc.TreeNode{
|
Node: &rpc.TreeNode{
|
||||||
Type: mapGitNodeType(gitNode.NodeType),
|
Type: mapGitNodeType(gitNode.NodeType),
|
||||||
@ -50,7 +45,6 @@ func (s RepositoryService) ListTreeNodes(request *rpc.ListTreeNodesRequest,
|
|||||||
Name: gitNode.Name,
|
Name: gitNode.Name,
|
||||||
Path: gitNode.Path,
|
Path: gitNode.Path,
|
||||||
},
|
},
|
||||||
Commit: commit,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.Internal, "failed to send node: %v", err)
|
return status.Errorf(codes.Internal, "failed to send node: %v", err)
|
||||||
@ -61,15 +55,16 @@ func (s RepositoryService) ListTreeNodes(request *rpc.ListTreeNodesRequest,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s RepositoryService) GetTreeNode(ctx context.Context,
|
func (s RepositoryService) GetTreeNode(ctx context.Context,
|
||||||
request *rpc.GetTreeNodeRequest) (*rpc.GetTreeNodeResponse, error) {
|
request *rpc.GetTreeNodeRequest,
|
||||||
|
) (*rpc.GetTreeNodeResponse, error) {
|
||||||
base := request.GetBase()
|
base := request.GetBase()
|
||||||
if base == nil {
|
if base == nil {
|
||||||
return nil, types.ErrBaseCannotBeEmpty
|
return nil, types.ErrBaseCannotBeEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid())
|
repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid())
|
||||||
// TODO: do we need to validate request for nil?
|
|
||||||
gitNode, err := s.adapter.GetTreeNode(ctx, repoPath, request.GetGitRef(), request.GetPath())
|
gitNode, err := s.adapter.GetTreeNode(ctx, repoPath, request.GitRef, request.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, processGitErrorf(err, "no such path '%s' in '%s'", request.Path, request.GetGitRef())
|
return nil, processGitErrorf(err, "no such path '%s' in '%s'", request.Path, request.GetGitRef())
|
||||||
}
|
}
|
||||||
@ -84,15 +79,61 @@ func (s RepositoryService) GetTreeNode(ctx context.Context,
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: improve performance, could be done in lower layer?
|
|
||||||
if request.GetIncludeLatestCommit() {
|
if request.GetIncludeLatestCommit() {
|
||||||
var commit *rpc.Commit
|
pathDetails, err := s.adapter.PathsDetails(ctx, repoPath, request.GitRef, []string{request.Path})
|
||||||
commit, err = s.getLatestCommit(ctx, repoPath, request.GetGitRef(), request.GetPath())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res.Commit = commit
|
|
||||||
|
if len(pathDetails) != 1 {
|
||||||
|
return nil, fmt.Errorf("failed to get details for the path %s", request.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pathDetails[0].LastCommit != nil {
|
||||||
|
res.Commit, err = mapGitCommit(pathDetails[0].LastCommit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s RepositoryService) PathsDetails(ctx context.Context,
|
||||||
|
request *rpc.PathsDetailsRequest,
|
||||||
|
) (*rpc.PathsDetailsResponse, error) {
|
||||||
|
base := request.GetBase()
|
||||||
|
if base == nil {
|
||||||
|
return nil, types.ErrBaseCannotBeEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid())
|
||||||
|
|
||||||
|
pathsDetails, err := s.adapter.PathsDetails(ctx, repoPath, request.GetGitRef(), request.GetPaths())
|
||||||
|
if err != nil {
|
||||||
|
return nil, processGitErrorf(err, "failed to get path details in '%s'", request.GetGitRef())
|
||||||
|
}
|
||||||
|
|
||||||
|
details := make([]*rpc.PathDetails, len(pathsDetails))
|
||||||
|
for i, pathDetails := range pathsDetails {
|
||||||
|
var lastCommit *rpc.Commit
|
||||||
|
|
||||||
|
if pathDetails.LastCommit != nil {
|
||||||
|
lastCommit, err = mapGitCommit(pathDetails.LastCommit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to map commit: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
details[i] = &rpc.PathDetails{
|
||||||
|
Path: pathDetails.Path,
|
||||||
|
LastCommit: lastCommit,
|
||||||
|
Size: pathDetails.Size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &rpc.PathsDetailsResponse{
|
||||||
|
PathDetails: details,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -15,6 +15,7 @@ var (
|
|||||||
ErrAlreadyExists = errors.New("already exists")
|
ErrAlreadyExists = errors.New("already exists")
|
||||||
ErrInvalidArgument = errors.New("invalid argument")
|
ErrInvalidArgument = errors.New("invalid argument")
|
||||||
ErrNotFound = errors.New("not found")
|
ErrNotFound = errors.New("not found")
|
||||||
|
ErrPathNotFound = errors.New("path not found")
|
||||||
ErrInvalidPath = errors.New("path is invalid")
|
ErrInvalidPath = errors.New("path is invalid")
|
||||||
ErrUndefinedAction = errors.New("undefined action")
|
ErrUndefinedAction = errors.New("undefined action")
|
||||||
ErrActionNotAllowedOnEmptyRepo = errors.New("action not allowed on empty repository")
|
ErrActionNotAllowedOnEmptyRepo = errors.New("action not allowed on empty repository")
|
||||||
|
@ -320,3 +320,9 @@ type TempRepository struct {
|
|||||||
BaseSHA string
|
BaseSHA string
|
||||||
HeadSHA string
|
HeadSHA string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PathDetails struct {
|
||||||
|
Path string
|
||||||
|
LastCommit *Commit
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ service RepositoryService {
|
|||||||
rpc CreateRepository(stream CreateRepositoryRequest) returns (CreateRepositoryResponse);
|
rpc CreateRepository(stream CreateRepositoryRequest) returns (CreateRepositoryResponse);
|
||||||
rpc GetTreeNode(GetTreeNodeRequest) returns (GetTreeNodeResponse);
|
rpc GetTreeNode(GetTreeNodeRequest) returns (GetTreeNodeResponse);
|
||||||
rpc ListTreeNodes(ListTreeNodesRequest) returns (stream ListTreeNodesResponse);
|
rpc ListTreeNodes(ListTreeNodesRequest) returns (stream ListTreeNodesResponse);
|
||||||
|
rpc PathsDetails(PathsDetailsRequest) returns (PathsDetailsResponse);
|
||||||
rpc GetSubmodule(GetSubmoduleRequest) returns (GetSubmoduleResponse);
|
rpc GetSubmodule(GetSubmoduleRequest) returns (GetSubmoduleResponse);
|
||||||
rpc GetBlob(GetBlobRequest) returns (stream GetBlobResponse);
|
rpc GetBlob(GetBlobRequest) returns (stream GetBlobResponse);
|
||||||
rpc ListCommits(ListCommitsRequest) returns (stream ListCommitsResponse);
|
rpc ListCommits(ListCommitsRequest) returns (stream ListCommitsResponse);
|
||||||
@ -55,13 +56,10 @@ message ListTreeNodesRequest {
|
|||||||
ReadRequest base = 1;
|
ReadRequest base = 1;
|
||||||
string git_ref = 2;
|
string git_ref = 2;
|
||||||
string path = 3;
|
string path = 3;
|
||||||
bool include_latest_commit = 4;
|
|
||||||
bool recursive = 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListTreeNodesResponse {
|
message ListTreeNodesResponse {
|
||||||
TreeNode node = 1;
|
TreeNode node = 1;
|
||||||
Commit commit = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message TreeNode {
|
message TreeNode {
|
||||||
@ -86,6 +84,22 @@ enum TreeNodeMode {
|
|||||||
TreeNodeModeCommit = 4;
|
TreeNodeModeCommit = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message PathsDetailsRequest {
|
||||||
|
ReadRequest base = 1;
|
||||||
|
string git_ref = 2;
|
||||||
|
repeated string paths = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PathsDetailsResponse {
|
||||||
|
repeated PathDetails path_details = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PathDetails {
|
||||||
|
string path = 1;
|
||||||
|
Commit last_commit = 2;
|
||||||
|
int64 size = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message GetCommitRequest {
|
message GetCommitRequest {
|
||||||
ReadRequest base = 1;
|
ReadRequest base = 1;
|
||||||
string sha = 2;
|
string sha = 2;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -25,6 +25,7 @@ type RepositoryServiceClient interface {
|
|||||||
CreateRepository(ctx context.Context, opts ...grpc.CallOption) (RepositoryService_CreateRepositoryClient, error)
|
CreateRepository(ctx context.Context, opts ...grpc.CallOption) (RepositoryService_CreateRepositoryClient, error)
|
||||||
GetTreeNode(ctx context.Context, in *GetTreeNodeRequest, opts ...grpc.CallOption) (*GetTreeNodeResponse, error)
|
GetTreeNode(ctx context.Context, in *GetTreeNodeRequest, opts ...grpc.CallOption) (*GetTreeNodeResponse, error)
|
||||||
ListTreeNodes(ctx context.Context, in *ListTreeNodesRequest, opts ...grpc.CallOption) (RepositoryService_ListTreeNodesClient, error)
|
ListTreeNodes(ctx context.Context, in *ListTreeNodesRequest, opts ...grpc.CallOption) (RepositoryService_ListTreeNodesClient, error)
|
||||||
|
PathsDetails(ctx context.Context, in *PathsDetailsRequest, opts ...grpc.CallOption) (*PathsDetailsResponse, error)
|
||||||
GetSubmodule(ctx context.Context, in *GetSubmoduleRequest, opts ...grpc.CallOption) (*GetSubmoduleResponse, error)
|
GetSubmodule(ctx context.Context, in *GetSubmoduleRequest, opts ...grpc.CallOption) (*GetSubmoduleResponse, error)
|
||||||
GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (RepositoryService_GetBlobClient, error)
|
GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (RepositoryService_GetBlobClient, error)
|
||||||
ListCommits(ctx context.Context, in *ListCommitsRequest, opts ...grpc.CallOption) (RepositoryService_ListCommitsClient, error)
|
ListCommits(ctx context.Context, in *ListCommitsRequest, opts ...grpc.CallOption) (RepositoryService_ListCommitsClient, error)
|
||||||
@ -119,6 +120,15 @@ func (x *repositoryServiceListTreeNodesClient) Recv() (*ListTreeNodesResponse, e
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *repositoryServiceClient) PathsDetails(ctx context.Context, in *PathsDetailsRequest, opts ...grpc.CallOption) (*PathsDetailsResponse, error) {
|
||||||
|
out := new(PathsDetailsResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/rpc.RepositoryService/PathsDetails", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *repositoryServiceClient) GetSubmodule(ctx context.Context, in *GetSubmoduleRequest, opts ...grpc.CallOption) (*GetSubmoduleResponse, error) {
|
func (c *repositoryServiceClient) GetSubmodule(ctx context.Context, in *GetSubmoduleRequest, opts ...grpc.CallOption) (*GetSubmoduleResponse, error) {
|
||||||
out := new(GetSubmoduleResponse)
|
out := new(GetSubmoduleResponse)
|
||||||
err := c.cc.Invoke(ctx, "/rpc.RepositoryService/GetSubmodule", in, out, opts...)
|
err := c.cc.Invoke(ctx, "/rpc.RepositoryService/GetSubmodule", in, out, opts...)
|
||||||
@ -253,6 +263,7 @@ type RepositoryServiceServer interface {
|
|||||||
CreateRepository(RepositoryService_CreateRepositoryServer) error
|
CreateRepository(RepositoryService_CreateRepositoryServer) error
|
||||||
GetTreeNode(context.Context, *GetTreeNodeRequest) (*GetTreeNodeResponse, error)
|
GetTreeNode(context.Context, *GetTreeNodeRequest) (*GetTreeNodeResponse, error)
|
||||||
ListTreeNodes(*ListTreeNodesRequest, RepositoryService_ListTreeNodesServer) error
|
ListTreeNodes(*ListTreeNodesRequest, RepositoryService_ListTreeNodesServer) error
|
||||||
|
PathsDetails(context.Context, *PathsDetailsRequest) (*PathsDetailsResponse, error)
|
||||||
GetSubmodule(context.Context, *GetSubmoduleRequest) (*GetSubmoduleResponse, error)
|
GetSubmodule(context.Context, *GetSubmoduleRequest) (*GetSubmoduleResponse, error)
|
||||||
GetBlob(*GetBlobRequest, RepositoryService_GetBlobServer) error
|
GetBlob(*GetBlobRequest, RepositoryService_GetBlobServer) error
|
||||||
ListCommits(*ListCommitsRequest, RepositoryService_ListCommitsServer) error
|
ListCommits(*ListCommitsRequest, RepositoryService_ListCommitsServer) error
|
||||||
@ -278,6 +289,9 @@ func (UnimplementedRepositoryServiceServer) GetTreeNode(context.Context, *GetTre
|
|||||||
func (UnimplementedRepositoryServiceServer) ListTreeNodes(*ListTreeNodesRequest, RepositoryService_ListTreeNodesServer) error {
|
func (UnimplementedRepositoryServiceServer) ListTreeNodes(*ListTreeNodesRequest, RepositoryService_ListTreeNodesServer) error {
|
||||||
return status.Errorf(codes.Unimplemented, "method ListTreeNodes not implemented")
|
return status.Errorf(codes.Unimplemented, "method ListTreeNodes not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedRepositoryServiceServer) PathsDetails(context.Context, *PathsDetailsRequest) (*PathsDetailsResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method PathsDetails not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedRepositoryServiceServer) GetSubmodule(context.Context, *GetSubmoduleRequest) (*GetSubmoduleResponse, error) {
|
func (UnimplementedRepositoryServiceServer) GetSubmodule(context.Context, *GetSubmoduleRequest) (*GetSubmoduleResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetSubmodule not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetSubmodule not implemented")
|
||||||
}
|
}
|
||||||
@ -383,6 +397,24 @@ func (x *repositoryServiceListTreeNodesServer) Send(m *ListTreeNodesResponse) er
|
|||||||
return x.ServerStream.SendMsg(m)
|
return x.ServerStream.SendMsg(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _RepositoryService_PathsDetails_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(PathsDetailsRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(RepositoryServiceServer).PathsDetails(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/rpc.RepositoryService/PathsDetails",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(RepositoryServiceServer).PathsDetails(ctx, req.(*PathsDetailsRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _RepositoryService_GetSubmodule_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _RepositoryService_GetSubmodule_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(GetSubmoduleRequest)
|
in := new(GetSubmoduleRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -562,6 +594,10 @@ var RepositoryService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetTreeNode",
|
MethodName: "GetTreeNode",
|
||||||
Handler: _RepositoryService_GetTreeNode_Handler,
|
Handler: _RepositoryService_GetTreeNode_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "PathsDetails",
|
||||||
|
Handler: _RepositoryService_PathsDetails_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "GetSubmodule",
|
MethodName: "GetSubmodule",
|
||||||
Handler: _RepositoryService_GetSubmodule_Handler,
|
Handler: _RepositoryService_GetSubmodule_Handler,
|
||||||
|
@ -25,6 +25,16 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
MaxConnAge time.Duration `envconfig:"GITRPC_SERVER_MAX_CONN_AGE" default:"630720000s"`
|
MaxConnAge time.Duration `envconfig:"GITRPC_SERVER_MAX_CONN_AGE" default:"630720000s"`
|
||||||
MaxConnAgeGrace time.Duration `envconfig:"GITRPC_SERVER_MAX_CONN_AGE_GRACE" default:"630720000s"`
|
MaxConnAgeGrace time.Duration `envconfig:"GITRPC_SERVER_MAX_CONN_AGE_GRACE" default:"630720000s"`
|
||||||
|
|
||||||
|
// LastCommitCacheSeconds defines cache duration in seconds of last commit, default=12h
|
||||||
|
LastCommitCacheSeconds int `envconfig:"GITRPC_LAST_COMMIT_CACHE_SECONDS" default:"43200"`
|
||||||
|
|
||||||
|
Redis struct {
|
||||||
|
Endpoint string `envconfig:"GITRPC_REDIS_ENDPOINT" default:"localhost:6379"`
|
||||||
|
MaxRetries int `envconfig:"GITRPC_REDIS_MAX_RETRIES" default:"3"`
|
||||||
|
MinIdleConnections int `envconfig:"GITRPC_REDIS_MIN_IDLE_CONNECTIONS" default:"0"`
|
||||||
|
Password string `envconfig:"GITRPC_REDIS_PASSWORD"`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Validate() error {
|
func (c *Config) Validate() error {
|
||||||
|
@ -5,9 +5,14 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/cache"
|
||||||
"github.com/harness/gitness/gitrpc/internal/gitea"
|
"github.com/harness/gitness/gitrpc/internal/gitea"
|
||||||
"github.com/harness/gitness/gitrpc/internal/service"
|
"github.com/harness/gitness/gitrpc/internal/service"
|
||||||
|
"github.com/harness/gitness/gitrpc/internal/types"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,10 +21,33 @@ var WireSet = wire.NewSet(
|
|||||||
ProvideServer,
|
ProvideServer,
|
||||||
ProvideHTTPServer,
|
ProvideHTTPServer,
|
||||||
ProvideGITAdapter,
|
ProvideGITAdapter,
|
||||||
|
ProvideGoGitRepoCache,
|
||||||
|
ProvideLastCommitCache,
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProvideGITAdapter() (service.GitAdapter, error) {
|
func ProvideGoGitRepoCache() cache.Cache[string, *gitea.RepoEntryValue] {
|
||||||
return gitea.New()
|
return gitea.NewRepoCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProvideLastCommitCache(
|
||||||
|
config Config,
|
||||||
|
redisClient redis.UniversalClient,
|
||||||
|
repoCache cache.Cache[string, *gitea.RepoEntryValue],
|
||||||
|
) cache.Cache[gitea.CommitEntryKey, *types.Commit] {
|
||||||
|
cacheDuration := time.Duration(config.LastCommitCacheSeconds) * time.Second
|
||||||
|
|
||||||
|
if redisClient == nil {
|
||||||
|
return gitea.NewInMemoryLastCommitCache(cacheDuration, repoCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitea.NewRedisLastCommitCache(redisClient, cacheDuration, repoCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProvideGITAdapter(
|
||||||
|
repoCache cache.Cache[string, *gitea.RepoEntryValue],
|
||||||
|
lastCommitCache cache.Cache[gitea.CommitEntryKey, *types.Commit],
|
||||||
|
) (service.GitAdapter, error) {
|
||||||
|
return gitea.New(repoCache, lastCommitCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideServer(config Config, adapter service.GitAdapter) (*GRPCServer, error) {
|
func ProvideServer(config Config, adapter service.GitAdapter) (*GRPCServer, error) {
|
||||||
|
@ -51,16 +51,10 @@ type ListTreeNodeParams struct {
|
|||||||
GitREF string
|
GitREF string
|
||||||
Path string
|
Path string
|
||||||
IncludeLatestCommit bool
|
IncludeLatestCommit bool
|
||||||
Recursive bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListTreeNodeOutput struct {
|
type ListTreeNodeOutput struct {
|
||||||
Nodes []TreeNodeWithCommit
|
Nodes []TreeNode
|
||||||
}
|
|
||||||
|
|
||||||
type TreeNodeWithCommit struct {
|
|
||||||
TreeNode
|
|
||||||
Commit *Commit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetTreeNodeParams struct {
|
type GetTreeNodeParams struct {
|
||||||
@ -94,6 +88,7 @@ func (c *Client) GetTreeNode(ctx context.Context, params *GetTreeNodeParams) (*G
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to map rpc node: %w", err)
|
return nil, fmt.Errorf("failed to map rpc node: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var commit *Commit
|
var commit *Commit
|
||||||
if resp.GetCommit() != nil {
|
if resp.GetCommit() != nil {
|
||||||
commit, err = mapRPCCommit(resp.GetCommit())
|
commit, err = mapRPCCommit(resp.GetCommit())
|
||||||
@ -113,17 +108,15 @@ func (c *Client) ListTreeNodes(ctx context.Context, params *ListTreeNodeParams)
|
|||||||
return nil, ErrNoParamsProvided
|
return nil, ErrNoParamsProvided
|
||||||
}
|
}
|
||||||
stream, err := c.repoService.ListTreeNodes(ctx, &rpc.ListTreeNodesRequest{
|
stream, err := c.repoService.ListTreeNodes(ctx, &rpc.ListTreeNodesRequest{
|
||||||
Base: mapToRPCReadRequest(params.ReadParams),
|
Base: mapToRPCReadRequest(params.ReadParams),
|
||||||
GitRef: params.GitREF,
|
GitRef: params.GitREF,
|
||||||
Path: params.Path,
|
Path: params.Path,
|
||||||
IncludeLatestCommit: params.IncludeLatestCommit,
|
|
||||||
Recursive: params.Recursive,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to start stream for tree nodes: %w", err)
|
return nil, fmt.Errorf("failed to start stream for tree nodes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes := make([]TreeNodeWithCommit, 0, 16)
|
nodes := make([]TreeNode, 0, 16)
|
||||||
for {
|
for {
|
||||||
var next *rpc.ListTreeNodesResponse
|
var next *rpc.ListTreeNodesResponse
|
||||||
next, err = stream.Recv()
|
next, err = stream.Recv()
|
||||||
@ -140,21 +133,60 @@ func (c *Client) ListTreeNodes(ctx context.Context, params *ListTreeNodeParams)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to map rpc node: %w", err)
|
return nil, fmt.Errorf("failed to map rpc node: %w", err)
|
||||||
}
|
}
|
||||||
var commit *Commit
|
|
||||||
if next.GetCommit() != nil {
|
|
||||||
commit, err = mapRPCCommit(next.GetCommit())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to map rpc commit: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes = append(nodes, TreeNodeWithCommit{
|
nodes = append(nodes, node)
|
||||||
TreeNode: node,
|
|
||||||
Commit: commit,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ListTreeNodeOutput{
|
return &ListTreeNodeOutput{
|
||||||
Nodes: nodes,
|
Nodes: nodes,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PathsDetailsParams struct {
|
||||||
|
ReadParams
|
||||||
|
GitREF string
|
||||||
|
Paths []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathsDetailsOutput struct {
|
||||||
|
Details []PathDetails
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathDetails struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
LastCommit *Commit `json:"last_commit,omitempty"`
|
||||||
|
Size int64 `json:"size,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PathsDetails(ctx context.Context, params PathsDetailsParams) (PathsDetailsOutput, error) {
|
||||||
|
response, err := c.repoService.PathsDetails(ctx, &rpc.PathsDetailsRequest{
|
||||||
|
Base: mapToRPCReadRequest(params.ReadParams),
|
||||||
|
GitRef: params.GitREF,
|
||||||
|
Paths: params.Paths,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return PathsDetailsOutput{}, processRPCErrorf(err, "failed to get paths details")
|
||||||
|
}
|
||||||
|
|
||||||
|
details := make([]PathDetails, len(response.PathDetails))
|
||||||
|
for i, pathDetail := range response.PathDetails {
|
||||||
|
var lastCommit *Commit
|
||||||
|
|
||||||
|
if pathDetail.LastCommit != nil {
|
||||||
|
lastCommit, err = mapRPCCommit(pathDetail.LastCommit)
|
||||||
|
if err != nil {
|
||||||
|
return PathsDetailsOutput{}, fmt.Errorf("failed to map last commit: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
details[i] = PathDetails{
|
||||||
|
Path: pathDetail.Path,
|
||||||
|
Size: pathDetail.Size,
|
||||||
|
LastCommit: lastCommit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PathsDetailsOutput{
|
||||||
|
Details: details,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -80,13 +80,15 @@ type SubmoduleContent struct {
|
|||||||
|
|
||||||
func (c *SubmoduleContent) isContent() {}
|
func (c *SubmoduleContent) isContent() {}
|
||||||
|
|
||||||
/*
|
// GetContent finds the content of the repo at the given path.
|
||||||
* GetContent finds the content of the repo at the given path.
|
// If no gitRef is provided, the content is retrieved from the default branch.
|
||||||
* If no gitRef is provided, the content is retrieved from the default branch.
|
func (c *Controller) GetContent(ctx context.Context,
|
||||||
* If includeLatestCommit is enabled, the response contains information of the latest commit that changed the object.
|
session *auth.Session,
|
||||||
*/
|
repoRef string,
|
||||||
func (c *Controller) GetContent(ctx context.Context, session *auth.Session, repoRef string,
|
gitRef string,
|
||||||
gitRef string, repoPath string, includeLatestCommit bool) (*GetContentOutput, error) {
|
repoPath string,
|
||||||
|
includeLatestCommit bool,
|
||||||
|
) (*GetContentOutput, error) {
|
||||||
repo, err := c.repoStore.FindByRef(ctx, repoRef)
|
repo, err := c.repoStore.FindByRef(ctx, repoRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -114,7 +116,7 @@ func (c *Controller) GetContent(ctx context.Context, session *auth.Session, repo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := mapToContentInfo(&treeNodeOutput.Node, treeNodeOutput.Commit)
|
info, err := mapToContentInfo(treeNodeOutput.Node, treeNodeOutput.Commit, includeLatestCommit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -122,8 +124,7 @@ func (c *Controller) GetContent(ctx context.Context, session *auth.Session, repo
|
|||||||
var content Content
|
var content Content
|
||||||
switch info.Type {
|
switch info.Type {
|
||||||
case ContentTypeDir:
|
case ContentTypeDir:
|
||||||
// for getContent we don't want any recursiveness for dir content.
|
content, err = c.getDirContent(ctx, readParams, gitRef, repoPath, includeLatestCommit)
|
||||||
content, err = c.getDirContent(ctx, readParams, gitRef, repoPath, includeLatestCommit, false)
|
|
||||||
case ContentTypeFile:
|
case ContentTypeFile:
|
||||||
content, err = c.getFileContent(ctx, readParams, info.SHA)
|
content, err = c.getFileContent(ctx, readParams, info.SHA)
|
||||||
case ContentTypeSymlink:
|
case ContentTypeSymlink:
|
||||||
@ -139,13 +140,17 @@ func (c *Controller) GetContent(ctx context.Context, session *auth.Session, repo
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &GetContentOutput{
|
return &GetContentOutput{
|
||||||
ContentInfo: *info,
|
ContentInfo: info,
|
||||||
Content: content,
|
Content: content,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) getSubmoduleContent(ctx context.Context, readParams gitrpc.ReadParams, gitRef string,
|
func (c *Controller) getSubmoduleContent(ctx context.Context,
|
||||||
repoPath string, commitSHA string) (*SubmoduleContent, error) {
|
readParams gitrpc.ReadParams,
|
||||||
|
gitRef string,
|
||||||
|
repoPath string,
|
||||||
|
commitSHA string,
|
||||||
|
) (*SubmoduleContent, error) {
|
||||||
output, err := c.gitRPCClient.GetSubmodule(ctx, &gitrpc.GetSubmoduleParams{
|
output, err := c.gitRPCClient.GetSubmodule(ctx, &gitrpc.GetSubmoduleParams{
|
||||||
ReadParams: readParams,
|
ReadParams: readParams,
|
||||||
GitREF: gitRef,
|
GitREF: gitRef,
|
||||||
@ -163,8 +168,10 @@ func (c *Controller) getSubmoduleContent(ctx context.Context, readParams gitrpc.
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) getFileContent(ctx context.Context, readParams gitrpc.ReadParams,
|
func (c *Controller) getFileContent(ctx context.Context,
|
||||||
blobSHA string) (*FileContent, error) {
|
readParams gitrpc.ReadParams,
|
||||||
|
blobSHA string,
|
||||||
|
) (*FileContent, error) {
|
||||||
output, err := c.gitRPCClient.GetBlob(ctx, &gitrpc.GetBlobParams{
|
output, err := c.gitRPCClient.GetBlob(ctx, &gitrpc.GetBlobParams{
|
||||||
ReadParams: readParams,
|
ReadParams: readParams,
|
||||||
SHA: blobSHA,
|
SHA: blobSHA,
|
||||||
@ -187,8 +194,10 @@ func (c *Controller) getFileContent(ctx context.Context, readParams gitrpc.ReadP
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) getSymlinkContent(ctx context.Context, readParams gitrpc.ReadParams,
|
func (c *Controller) getSymlinkContent(ctx context.Context,
|
||||||
blobSHA string) (*SymlinkContent, error) {
|
readParams gitrpc.ReadParams,
|
||||||
|
blobSHA string,
|
||||||
|
) (*SymlinkContent, error) {
|
||||||
output, err := c.gitRPCClient.GetBlob(ctx, &gitrpc.GetBlobParams{
|
output, err := c.gitRPCClient.GetBlob(ctx, &gitrpc.GetBlobParams{
|
||||||
ReadParams: readParams,
|
ReadParams: readParams,
|
||||||
SHA: blobSHA,
|
SHA: blobSHA,
|
||||||
@ -211,14 +220,17 @@ func (c *Controller) getSymlinkContent(ctx context.Context, readParams gitrpc.Re
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) getDirContent(ctx context.Context, readParams gitrpc.ReadParams, gitRef string,
|
func (c *Controller) getDirContent(ctx context.Context,
|
||||||
repoPath string, includeLatestCommit bool, recursive bool) (*DirContent, error) {
|
readParams gitrpc.ReadParams,
|
||||||
|
gitRef string,
|
||||||
|
repoPath string,
|
||||||
|
includeLatestCommit bool,
|
||||||
|
) (*DirContent, error) {
|
||||||
output, err := c.gitRPCClient.ListTreeNodes(ctx, &gitrpc.ListTreeNodeParams{
|
output, err := c.gitRPCClient.ListTreeNodes(ctx, &gitrpc.ListTreeNodeParams{
|
||||||
ReadParams: readParams,
|
ReadParams: readParams,
|
||||||
GitREF: gitRef,
|
GitREF: gitRef,
|
||||||
Path: repoPath,
|
Path: repoPath,
|
||||||
IncludeLatestCommit: includeLatestCommit,
|
IncludeLatestCommit: includeLatestCommit,
|
||||||
Recursive: recursive,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: handle not found error
|
// TODO: handle not found error
|
||||||
@ -227,15 +239,11 @@ func (c *Controller) getDirContent(ctx context.Context, readParams gitrpc.ReadPa
|
|||||||
}
|
}
|
||||||
|
|
||||||
entries := make([]ContentInfo, len(output.Nodes))
|
entries := make([]ContentInfo, len(output.Nodes))
|
||||||
for i := range output.Nodes {
|
for i, node := range output.Nodes {
|
||||||
node := output.Nodes[i]
|
entries[i], err = mapToContentInfo(node, nil, false)
|
||||||
|
|
||||||
var entry *ContentInfo
|
|
||||||
entry, err = mapToContentInfo(&node.TreeNode, node.Commit)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
entries[i] = *entry
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &DirContent{
|
return &DirContent{
|
||||||
@ -243,17 +251,13 @@ func (c *Controller) getDirContent(ctx context.Context, readParams gitrpc.ReadPa
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapToContentInfo(node *gitrpc.TreeNode, commit *gitrpc.Commit) (*ContentInfo, error) {
|
func mapToContentInfo(node gitrpc.TreeNode, commit *gitrpc.Commit, includeLatestCommit bool) (ContentInfo, error) {
|
||||||
// node data is expected
|
|
||||||
if node == nil {
|
|
||||||
return nil, fmt.Errorf("node can't be nil")
|
|
||||||
}
|
|
||||||
typ, err := mapNodeModeToContentType(node.Mode)
|
typ, err := mapNodeModeToContentType(node.Mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ContentInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res := &ContentInfo{
|
res := ContentInfo{
|
||||||
Type: typ,
|
Type: typ,
|
||||||
SHA: node.SHA,
|
SHA: node.SHA,
|
||||||
Name: node.Name,
|
Name: node.Name,
|
||||||
@ -261,10 +265,10 @@ func mapToContentInfo(node *gitrpc.TreeNode, commit *gitrpc.Commit) (*ContentInf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse commit only if available
|
// parse commit only if available
|
||||||
if commit != nil {
|
if commit != nil && includeLatestCommit {
|
||||||
res.LatestCommit, err = controller.MapCommit(commit)
|
res.LatestCommit, err = controller.MapCommit(commit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ContentInfo{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
70
internal/api/controller/repo/content_paths_details.go
Normal file
70
internal/api/controller/repo/content_paths_details.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2022 Harness Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform Free Trial License
|
||||||
|
// that can be found in the LICENSE.md file for this repository.
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/gitrpc"
|
||||||
|
apiauth "github.com/harness/gitness/internal/api/auth"
|
||||||
|
"github.com/harness/gitness/internal/api/usererror"
|
||||||
|
"github.com/harness/gitness/internal/auth"
|
||||||
|
"github.com/harness/gitness/types/enum"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PathsDetailsInput struct {
|
||||||
|
Paths []string `json:"paths"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathsDetailsOutput struct {
|
||||||
|
Details []gitrpc.PathDetails `json:"details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathsDetails finds the additional info about the provided paths of the repo.
|
||||||
|
// If no gitRef is provided, the content is retrieved from the default branch.
|
||||||
|
func (c *Controller) PathsDetails(ctx context.Context,
|
||||||
|
session *auth.Session,
|
||||||
|
repoRef string,
|
||||||
|
gitRef string,
|
||||||
|
input PathsDetailsInput,
|
||||||
|
) (PathsDetailsOutput, error) {
|
||||||
|
repo, err := c.repoStore.FindByRef(ctx, repoRef)
|
||||||
|
if err != nil {
|
||||||
|
return PathsDetailsOutput{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, true); err != nil {
|
||||||
|
return PathsDetailsOutput{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(input.Paths) == 0 {
|
||||||
|
return PathsDetailsOutput{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(input.Paths) > 50 {
|
||||||
|
return PathsDetailsOutput{}, usererror.BadRequest("maximum number of elements in the Paths array is 25")
|
||||||
|
}
|
||||||
|
|
||||||
|
// set gitRef to default branch in case an empty reference was provided
|
||||||
|
if gitRef == "" {
|
||||||
|
gitRef = repo.DefaultBranch
|
||||||
|
}
|
||||||
|
|
||||||
|
// create read params once
|
||||||
|
readParams := CreateRPCReadParams(repo)
|
||||||
|
|
||||||
|
result, err := c.gitRPCClient.PathsDetails(ctx, gitrpc.PathsDetailsParams{
|
||||||
|
ReadParams: readParams,
|
||||||
|
GitREF: gitRef,
|
||||||
|
Paths: input.Paths,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return PathsDetailsOutput{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return PathsDetailsOutput{
|
||||||
|
Details: result.Details,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -12,9 +12,7 @@ import (
|
|||||||
"github.com/harness/gitness/internal/api/request"
|
"github.com/harness/gitness/internal/api/request"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
// HandleGetContent handles the get content HTTP API.
|
||||||
* Writes json-encoded content information to the http response body.
|
|
||||||
*/
|
|
||||||
func HandleGetContent(repoCtrl *repo.Controller) http.HandlerFunc {
|
func HandleGetContent(repoCtrl *repo.Controller) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
44
internal/api/handler/repo/content_paths_details.go
Normal file
44
internal/api/handler/repo/content_paths_details.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2022 Harness Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform Free Trial License
|
||||||
|
// that can be found in the LICENSE.md file for this repository.
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/internal/api/controller/repo"
|
||||||
|
"github.com/harness/gitness/internal/api/render"
|
||||||
|
"github.com/harness/gitness/internal/api/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlePathsDetails handles get file or directory details HTTP API.
|
||||||
|
func HandlePathsDetails(repoCtrl *repo.Controller) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
session, _ := request.AuthSessionFrom(ctx)
|
||||||
|
repoRef, err := request.GetRepoRefFromPath(r)
|
||||||
|
if err != nil {
|
||||||
|
render.TranslatedUserError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gitRef := request.GetGitRefFromQueryOrDefault(r, "")
|
||||||
|
|
||||||
|
var in repo.PathsDetailsInput
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&in)
|
||||||
|
if err != nil {
|
||||||
|
render.BadRequestf(w, "Invalid request body: %s.", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := repoCtrl.PathsDetails(ctx, session, repoRef, gitRef, in)
|
||||||
|
if err != nil {
|
||||||
|
render.TranslatedUserError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.JSON(w, http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
}
|
@ -57,6 +57,11 @@ type getContentRequest struct {
|
|||||||
Path string `path:"path"`
|
Path string `path:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pathsDetailsRequest struct {
|
||||||
|
repoRequest
|
||||||
|
repo.PathsDetailsInput
|
||||||
|
}
|
||||||
|
|
||||||
type getBlameRequest struct {
|
type getBlameRequest struct {
|
||||||
repoRequest
|
repoRequest
|
||||||
Path string `path:"path"`
|
Path string `path:"path"`
|
||||||
@ -481,6 +486,18 @@ func repoOperations(reflector *openapi3.Reflector) {
|
|||||||
_ = reflector.SetJSONResponse(&opGetContent, new(usererror.Error), http.StatusNotFound)
|
_ = reflector.SetJSONResponse(&opGetContent, new(usererror.Error), http.StatusNotFound)
|
||||||
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/content/{path}", opGetContent)
|
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/content/{path}", opGetContent)
|
||||||
|
|
||||||
|
opPathDetails := openapi3.Operation{}
|
||||||
|
opPathDetails.WithTags("repository")
|
||||||
|
opPathDetails.WithMapOfAnything(map[string]interface{}{"operationId": "pathDetails"})
|
||||||
|
opPathDetails.WithParameters(queryParameterGitRef)
|
||||||
|
_ = reflector.SetRequest(&opPathDetails, new(pathsDetailsRequest), http.MethodPost)
|
||||||
|
_ = reflector.SetJSONResponse(&opPathDetails, new(repo.PathsDetailsOutput), http.StatusOK)
|
||||||
|
_ = reflector.SetJSONResponse(&opPathDetails, new(usererror.Error), http.StatusInternalServerError)
|
||||||
|
_ = reflector.SetJSONResponse(&opPathDetails, new(usererror.Error), http.StatusUnauthorized)
|
||||||
|
_ = reflector.SetJSONResponse(&opPathDetails, new(usererror.Error), http.StatusForbidden)
|
||||||
|
_ = reflector.SetJSONResponse(&opPathDetails, new(usererror.Error), http.StatusNotFound)
|
||||||
|
_ = reflector.Spec.AddOperation(http.MethodPost, "/repos/{repo_ref}/path-details", opPathDetails)
|
||||||
|
|
||||||
opGetRaw := openapi3.Operation{}
|
opGetRaw := openapi3.Operation{}
|
||||||
opGetRaw.WithTags("repository")
|
opGetRaw.WithTags("repository")
|
||||||
opGetRaw.WithMapOfAnything(map[string]interface{}{"operationId": "getRaw"})
|
opGetRaw.WithMapOfAnything(map[string]interface{}{"operationId": "getRaw"})
|
||||||
|
@ -200,6 +200,8 @@ func setupRepos(r chi.Router,
|
|||||||
r.Get("/*", handlerrepo.HandleGetContent(repoCtrl))
|
r.Get("/*", handlerrepo.HandleGetContent(repoCtrl))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
r.Post("/path-details", handlerrepo.HandlePathsDetails(repoCtrl))
|
||||||
|
|
||||||
r.Route("/blame", func(r chi.Router) {
|
r.Route("/blame", func(r chi.Router) {
|
||||||
r.Get("/*", handlerrepo.HandleBlame(repoCtrl))
|
r.Get("/*", handlerrepo.HandleBlame(repoCtrl))
|
||||||
})
|
})
|
||||||
|
24
internal/store/cache/path.go
vendored
24
internal/store/cache/path.go
vendored
@ -13,38 +13,24 @@ import (
|
|||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// pathCacheEntry is used to return the proper transformed value as identifier when storing a path in cache.
|
|
||||||
type pathCacheEntry struct {
|
|
||||||
inner *types.Path
|
|
||||||
valueUnique string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *pathCacheEntry) Identifier() string {
|
|
||||||
return e.valueUnique
|
|
||||||
}
|
|
||||||
|
|
||||||
// pathCacheGetter is used to hook a PathStore as source of a pathCache.
|
// pathCacheGetter is used to hook a PathStore as source of a pathCache.
|
||||||
// IMPORTANT: It assumes that the pathCache already transformed the key.
|
// IMPORTANT: It assumes that the pathCache already transformed the key.
|
||||||
type pathCacheGetter struct {
|
type pathCacheGetter struct {
|
||||||
pathStore store.PathStore
|
pathStore store.PathStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *pathCacheGetter) Find(ctx context.Context, key string) (*pathCacheEntry, error) {
|
func (g *pathCacheGetter) Find(ctx context.Context, key string) (*types.Path, error) {
|
||||||
path, err := g.pathStore.FindValue(ctx, key)
|
path, err := g.pathStore.FindValue(ctx, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pathCacheEntry{
|
return path, nil
|
||||||
inner: path,
|
|
||||||
// key is already transformed - pathCache transforms the path before calling the inner cache.
|
|
||||||
valueUnique: key,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pathCache is a decorator of a Cache required to handle path transformations.
|
// pathCache is a decorator of a Cache required to handle path transformations.
|
||||||
type pathCache struct {
|
type pathCache struct {
|
||||||
inner cache.Cache[string, *pathCacheEntry]
|
inner cache.Cache[string, *types.Path]
|
||||||
pathTransformation store.PathTransformation
|
pathTransformation store.PathTransformation
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,12 +40,12 @@ func (c *pathCache) Get(ctx context.Context, key string) (*types.Path, error) {
|
|||||||
return nil, fmt.Errorf("failed to transform path: %w", err)
|
return nil, fmt.Errorf("failed to transform path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheEntry, err := c.inner.Get(ctx, uniqueKey)
|
path, err := c.inner.Get(ctx, uniqueKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return cacheEntry.inner, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *pathCache) Stats() (int64, int64) {
|
func (c *pathCache) Stats() (int64, int64) {
|
||||||
|
2
internal/store/cache/wire.go
vendored
2
internal/store/cache/wire.go
vendored
@ -29,7 +29,7 @@ func ProvidePrincipalInfoCache(getter store.PrincipalInfoView) store.PrincipalIn
|
|||||||
// ProvidePathCache provides a cache for storing routing paths and their types.Path objects.
|
// ProvidePathCache provides a cache for storing routing paths and their types.Path objects.
|
||||||
func ProvidePathCache(pathStore store.PathStore, pathTransformation store.PathTransformation) store.PathCache {
|
func ProvidePathCache(pathStore store.PathStore, pathTransformation store.PathTransformation) store.PathCache {
|
||||||
return &pathCache{
|
return &pathCache{
|
||||||
inner: cache.New[string, *pathCacheEntry](
|
inner: cache.New[string, *types.Path](
|
||||||
&pathCacheGetter{
|
&pathCacheGetter{
|
||||||
pathStore: pathStore,
|
pathStore: pathStore,
|
||||||
},
|
},
|
||||||
|
@ -57,7 +57,3 @@ type RepositoryGitInfo struct {
|
|||||||
ID int64
|
ID int64
|
||||||
GitUID string
|
GitUID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RepositoryGitInfo) Identifier() int64 {
|
|
||||||
return p.ID
|
|
||||||
}
|
|
||||||
|
@ -70,7 +70,7 @@ export function FolderContent({
|
|||||||
Cell: ({ row }: CellProps<OpenapiContentInfo>) => {
|
Cell: ({ row }: CellProps<OpenapiContentInfo>) => {
|
||||||
return (
|
return (
|
||||||
<Text lineClamp={1} color={Color.GREY_500} className={css.rowText}>
|
<Text lineClamp={1} color={Color.GREY_500} className={css.rowText}>
|
||||||
{formatDate(row.original.latest_commit?.author?.when as string)}
|
{!!row.original.latest_commit?.author?.when ? formatDate(row.original.latest_commit?.author?.when as string) : ""}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user