drone/gitrpc/internal/gitea/tree.go

153 lines
4.0 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gitea
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"path"
"path/filepath"
"strings"
"github.com/harness/gitness/gitrpc/internal/types"
gitea "code.gitea.io/gitea/modules/git"
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 {
return strings.Trim(path.Clean("/"+treePath), "/")
}
// GetTreeNode returns the tree node at the given path as found for the provided reference.
// Note: ref can be Branch / Tag / CommitSHA.
func (g Adapter) GetTreeNode(ctx context.Context,
repoPath string,
ref string,
treePath string,
) (*types.TreeNode, error) {
treePath = cleanTreePath(treePath)
_, refCommit, err := g.getGoGitCommit(ctx, repoPath, ref)
if err != nil {
return nil, err
}
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 errors.Is(err, gogitobject.ErrDirectoryNotFound) || errors.Is(err, gogitobject.ErrEntryNotFound) {
return nil, &types.PathNotFoundError{Path: treePath}
}
if err != nil {
return nil, fmt.Errorf("failed to find path entry %s: %w", treePath, err)
}
}
nodeType, mode, err := mapGogitNodeToTreeNodeModeAndType(treeEntry.Mode)
if err != nil {
return nil, err
}
return &types.TreeNode{
Mode: mode,
NodeType: nodeType,
Sha: treeEntry.Hash.String(),
Name: treeEntry.Name,
Path: treePath,
}, nil
}
// ListTreeNodes lists the child nodes of a tree reachable from ref via the specified path
// and includes the latest commit for all nodes if requested.
// Note: ref can be Branch / Tag / CommitSHA.
//
//nolint:gocognit // refactor if needed
func (g Adapter) ListTreeNodes(ctx context.Context,
repoPath string,
ref string,
treePath string,
) ([]types.TreeNode, error) {
treePath = cleanTreePath(treePath)
_, refCommit, err := g.getGoGitCommit(ctx, repoPath, ref)
if err != nil {
return nil, err
}
tree, err := refCommit.Tree()
if err != nil {
return nil, fmt.Errorf("failed to get tree for the commit: %w", err)
}
if len(treePath) > 0 {
tree, err = tree.Tree(treePath)
if errors.Is(err, gogitobject.ErrDirectoryNotFound) || errors.Is(err, gogitobject.ErrEntryNotFound) {
return nil, &types.PathNotFoundError{Path: treePath}
}
if err != nil {
return nil, fmt.Errorf("failed to find path entry %s: %w", treePath, err)
}
}
treeNodes := make([]types.TreeNode, len(tree.Entries))
for i, treeEntry := range tree.Entries {
nodeType, mode, err := mapGogitNodeToTreeNodeModeAndType(treeEntry.Mode)
if err != nil {
return nil, err
}
treeNodes[i] = types.TreeNode{
NodeType: nodeType,
Mode: mode,
Sha: treeEntry.Hash.String(),
Name: treeEntry.Name,
Path: filepath.Join(treePath, treeEntry.Name),
}
}
return treeNodes, nil
}
func (g Adapter) ReadTree(ctx context.Context, repoPath, ref string, w io.Writer, args ...string) error {
errbuf := bytes.Buffer{}
if err := gitea.NewCommand(ctx, append([]string{"read-tree", ref}, args...)...).
Run(&gitea.RunOpts{
Dir: repoPath,
Stdout: w,
Stderr: &errbuf,
}); err != nil {
return fmt.Errorf("unable to read %s in to the index: %w\n%s",
ref, err, errbuf.String())
}
return nil
}