mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
156 lines
5.1 KiB
Go
156 lines
5.1 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package files
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
repo_model "github.com/hanzoai/git/models/repo"
|
|
"github.com/hanzoai/git/modules/git"
|
|
"github.com/hanzoai/git/modules/setting"
|
|
api "github.com/hanzoai/git/modules/structs"
|
|
"github.com/hanzoai/git/modules/util"
|
|
"github.com/hanzoai/git/routers/api/v1/utils"
|
|
)
|
|
|
|
func GetContentsListFromTreePaths(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, treePaths []string) (files []*api.ContentsResponse) {
|
|
var size int64
|
|
for _, treePath := range treePaths {
|
|
// ok if fails, then will be nil
|
|
fileContents, _ := GetFileContents(ctx, repo, gitRepo, refCommit, GetContentsOrListOptions{
|
|
TreePath: treePath,
|
|
IncludeSingleFileContent: true,
|
|
IncludeCommitMetadata: true,
|
|
})
|
|
if fileContents != nil && fileContents.Content != nil && *fileContents.Content != "" {
|
|
// if content isn't empty (e.g., due to the single blob being too large), add file size to response size
|
|
size += int64(len(*fileContents.Content))
|
|
}
|
|
if size > setting.API.DefaultMaxResponseSize {
|
|
break // stop if max response size would be exceeded
|
|
}
|
|
files = append(files, fileContents)
|
|
if len(files) == setting.API.DefaultPagingNum {
|
|
break // stop if paging num reached
|
|
}
|
|
}
|
|
return files
|
|
}
|
|
|
|
func GetFilesResponseFromCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, treeNames []string) (*api.FilesResponse, error) {
|
|
files := GetContentsListFromTreePaths(ctx, repo, gitRepo, refCommit, treeNames)
|
|
fileCommitResponse, _ := GetFileCommitResponse(repo, gitRepo, refCommit.Commit) // ok if fails, then will be nil
|
|
verification := GetPayloadCommitVerification(ctx, refCommit.Commit)
|
|
filesResponse := &api.FilesResponse{
|
|
Files: files,
|
|
Commit: fileCommitResponse,
|
|
Verification: verification,
|
|
}
|
|
return filesResponse, nil
|
|
}
|
|
|
|
// constructs a FileResponse with the file at the index from FilesResponse
|
|
func GetFileResponseFromFilesResponse(filesResponse *api.FilesResponse, index int) *api.FileResponse {
|
|
content := &api.ContentsResponse{}
|
|
if len(filesResponse.Files) > index {
|
|
content = filesResponse.Files[index]
|
|
}
|
|
fileResponse := &api.FileResponse{
|
|
Content: content,
|
|
Commit: filesResponse.Commit,
|
|
Verification: filesResponse.Verification,
|
|
}
|
|
return fileResponse
|
|
}
|
|
|
|
// GetFileCommitResponse Constructs a FileCommitResponse from a Commit object
|
|
func GetFileCommitResponse(repo *repo_model.Repository, gitRepo *git.Repository, commit *git.Commit) (*api.FileCommitResponse, error) {
|
|
if repo == nil {
|
|
return nil, errors.New("repo cannot be nil")
|
|
}
|
|
if commit == nil {
|
|
return nil, errors.New("commit cannot be nil")
|
|
}
|
|
commitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()))
|
|
commitTreeURL, _ := url.Parse(repo.APIURL() + "/git/trees/" + url.PathEscape(commit.TreeID.String()))
|
|
parents := make([]*api.CommitMeta, commit.ParentCount())
|
|
for i := 0; i < commit.ParentCount(); i++ {
|
|
if parent, err := commit.Parent(gitRepo, i); err == nil && parent != nil {
|
|
parentCommitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(parent.ID.String()))
|
|
parents[i] = &api.CommitMeta{
|
|
SHA: parent.ID.String(),
|
|
URL: parentCommitURL.String(),
|
|
}
|
|
}
|
|
}
|
|
commitHTMLURL, _ := url.Parse(repo.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()))
|
|
fileCommit := &api.FileCommitResponse{
|
|
CommitMeta: api.CommitMeta{
|
|
SHA: commit.ID.String(),
|
|
URL: commitURL.String(),
|
|
},
|
|
HTMLURL: commitHTMLURL.String(),
|
|
Author: &api.CommitUser{
|
|
Identity: api.Identity{
|
|
Name: commit.Author.Name,
|
|
Email: commit.Author.Email,
|
|
},
|
|
Date: commit.Author.When.UTC().Format(time.RFC3339),
|
|
},
|
|
Committer: &api.CommitUser{
|
|
Identity: api.Identity{
|
|
Name: commit.Committer.Name,
|
|
Email: commit.Committer.Email,
|
|
},
|
|
Date: commit.Committer.When.UTC().Format(time.RFC3339),
|
|
},
|
|
Message: commit.MessageUTF8(),
|
|
Tree: &api.CommitMeta{
|
|
URL: commitTreeURL.String(),
|
|
SHA: commit.TreeID.String(),
|
|
},
|
|
Parents: parents,
|
|
}
|
|
return fileCommit, nil
|
|
}
|
|
|
|
// ErrFilenameInvalid represents a "FilenameInvalid" kind of error.
|
|
type ErrFilenameInvalid struct {
|
|
Path string
|
|
}
|
|
|
|
// IsErrFilenameInvalid checks if an error is an ErrFilenameInvalid.
|
|
func IsErrFilenameInvalid(err error) bool {
|
|
_, ok := err.(ErrFilenameInvalid)
|
|
return ok
|
|
}
|
|
|
|
func (err ErrFilenameInvalid) Error() string {
|
|
return fmt.Sprintf("path contains a malformed path component [path: %s]", err.Path)
|
|
}
|
|
|
|
func (err ErrFilenameInvalid) Unwrap() error {
|
|
return util.ErrInvalidArgument
|
|
}
|
|
|
|
// CleanGitTreePath cleans a tree path for git, it returns an empty string the path is invalid (e.g.: contains ".git" part)
|
|
func CleanGitTreePath(name string) string {
|
|
name = util.PathJoinRel(name)
|
|
// Git disallows any filenames to have a .git directory in them.
|
|
for part := range strings.SplitSeq(name, "/") {
|
|
if strings.EqualFold(part, ".git") {
|
|
return ""
|
|
}
|
|
}
|
|
if name == "." {
|
|
name = ""
|
|
}
|
|
return name
|
|
}
|