2019-04-17 10:06:35 -06:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2019-04-17 10:06:35 -06:00
2021-11-24 15:56:24 +08:00
package files
2019-04-17 10:06:35 -06:00
import (
2022-01-19 23:26:57 +00:00
"context"
2019-06-29 16:51:10 -04:00
"fmt"
2019-04-17 10:06:35 -06:00
"net/url"
2019-06-29 16:51:10 -04:00
"path"
"strings"
2019-04-17 10:06:35 -06:00
"code.gitea.io/gitea/models"
2021-12-10 09:27:50 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2019-04-17 10:06:35 -06:00
"code.gitea.io/gitea/modules/git"
2021-11-24 15:56:24 +08:00
"code.gitea.io/gitea/modules/setting"
2019-05-11 18:21:34 +08:00
api "code.gitea.io/gitea/modules/structs"
2023-02-12 09:31:14 +08:00
"code.gitea.io/gitea/modules/util"
2019-04-17 10:06:35 -06:00
)
2019-06-29 16:51:10 -04:00
// ContentType repo content type
type ContentType string
// The string representations of different content types
const (
// ContentTypeRegular regular content type (file)
ContentTypeRegular ContentType = "file"
// ContentTypeDir dir content type (dir)
ContentTypeDir ContentType = "dir"
// ContentLink link content type (symlink)
ContentTypeLink ContentType = "symlink"
// ContentTag submodule content type (submodule)
ContentTypeSubmodule ContentType = "submodule"
)
// String gets the string of ContentType
func ( ct * ContentType ) String ( ) string {
return string ( * ct )
}
// GetContentsOrList gets the meta data of a file's contents (*ContentsResponse) if treePath not a tree
// directory, otherwise a listing of file contents ([]*ContentsResponse). Ref can be a branch, commit or tag
2022-01-19 23:26:57 +00:00
func GetContentsOrList ( ctx context . Context , repo * repo_model . Repository , treePath , ref string ) ( interface { } , error ) {
2019-10-19 17:38:49 +02:00
if repo . IsEmpty {
return make ( [ ] interface { } , 0 ) , nil
}
2019-04-17 10:06:35 -06:00
if ref == "" {
ref = repo . DefaultBranch
}
2019-06-29 16:51:10 -04:00
origRef := ref
2019-04-17 10:06:35 -06:00
// Check that the path given in opts.treePath is valid (not a git path)
2019-06-29 16:51:10 -04:00
cleanTreePath := CleanUploadFileName ( treePath )
if cleanTreePath == "" && treePath != "" {
2019-04-17 10:06:35 -06:00
return nil , models . ErrFilenameInvalid {
Path : treePath ,
}
}
2019-06-29 16:51:10 -04:00
treePath = cleanTreePath
2019-04-17 10:06:35 -06:00
2022-01-19 23:26:57 +00:00
gitRepo , closer , err := git . RepositoryFromContextOrOpen ( ctx , repo . RepoPath ( ) )
2019-04-17 10:06:35 -06:00
if err != nil {
return nil , err
}
2022-01-19 23:26:57 +00:00
defer closer . Close ( )
2019-04-17 10:06:35 -06:00
// Get the commit object for the ref
commit , err := gitRepo . GetCommit ( ref )
if err != nil {
return nil , err
}
entry , err := commit . GetTreeEntryByPath ( treePath )
if err != nil {
return nil , err
}
2019-06-29 16:51:10 -04:00
if entry . Type ( ) != "tree" {
2022-01-19 23:26:57 +00:00
return GetContents ( ctx , repo , treePath , origRef , false )
2019-06-29 16:51:10 -04:00
}
// We are in a directory, so we return a list of FileContentResponse objects
var fileList [ ] * api . ContentsResponse
gitTree , err := commit . SubTree ( treePath )
if err != nil {
return nil , err
}
entries , err := gitTree . ListEntries ( )
if err != nil {
return nil , err
}
for _ , e := range entries {
subTreePath := path . Join ( treePath , e . Name ( ) )
2022-01-19 23:26:57 +00:00
fileContentResponse , err := GetContents ( ctx , repo , subTreePath , origRef , true )
2019-06-29 16:51:10 -04:00
if err != nil {
return nil , err
}
fileList = append ( fileList , fileContentResponse )
}
return fileList , nil
}
2022-07-21 21:18:41 +02:00
// GetObjectTypeFromTreeEntry check what content is behind it
func GetObjectTypeFromTreeEntry ( entry * git . TreeEntry ) ContentType {
switch {
case entry . IsDir ( ) :
return ContentTypeDir
case entry . IsSubModule ( ) :
return ContentTypeSubmodule
case entry . IsExecutable ( ) , entry . IsRegular ( ) :
return ContentTypeRegular
case entry . IsLink ( ) :
return ContentTypeLink
default :
return ""
}
}
2019-06-29 16:51:10 -04:00
// GetContents gets the meta data on a file's contents. Ref can be a branch, commit or tag
2022-01-19 23:26:57 +00:00
func GetContents ( ctx context . Context , repo * repo_model . Repository , treePath , ref string , forList bool ) ( * api . ContentsResponse , error ) {
2019-06-29 16:51:10 -04:00
if ref == "" {
ref = repo . DefaultBranch
2019-04-17 10:06:35 -06:00
}
2019-06-29 16:51:10 -04:00
origRef := ref
2019-04-17 10:06:35 -06:00
2019-06-29 16:51:10 -04:00
// Check that the path given in opts.treePath is valid (not a git path)
cleanTreePath := CleanUploadFileName ( treePath )
if cleanTreePath == "" && treePath != "" {
return nil , models . ErrFilenameInvalid {
Path : treePath ,
}
}
treePath = cleanTreePath
2022-01-19 23:26:57 +00:00
gitRepo , closer , err := git . RepositoryFromContextOrOpen ( ctx , repo . RepoPath ( ) )
2019-06-29 16:51:10 -04:00
if err != nil {
return nil , err
}
2022-01-19 23:26:57 +00:00
defer closer . Close ( )
2019-04-17 10:06:35 -06:00
2019-06-29 16:51:10 -04:00
// Get the commit object for the ref
commit , err := gitRepo . GetCommit ( ref )
if err != nil {
return nil , err
}
commitID := commit . ID . String ( )
if len ( ref ) >= 4 && strings . HasPrefix ( commitID , ref ) {
ref = commit . ID . String ( )
}
entry , err := commit . GetTreeEntryByPath ( treePath )
if err != nil {
return nil , err
}
refType := gitRepo . GetRefType ( ref )
if refType == "invalid" {
return nil , fmt . Errorf ( "no commit found for the ref [ref: %s]" , ref )
}
2023-02-12 09:31:14 +08:00
selfURL , err := url . Parse ( repo . APIURL ( ) + "/contents/" + util . PathEscapeSegments ( treePath ) + "?ref=" + url . QueryEscape ( origRef ) )
2019-06-29 16:51:10 -04:00
if err != nil {
return nil , err
}
selfURLString := selfURL . String ( )
2022-07-30 10:09:04 +02:00
err = gitRepo . AddLastCommitCache ( repo . GetCommitsCountCacheKey ( ref , refType != git . ObjectCommit ) , repo . FullName ( ) , commitID )
if err != nil {
return nil , err
}
lastCommit , err := commit . GetCommitByPath ( treePath )
if err != nil {
return nil , err
}
2019-06-29 16:51:10 -04:00
// All content types have these fields in populated
contentsResponse := & api . ContentsResponse {
2022-07-30 10:09:04 +02:00
Name : entry . Name ( ) ,
Path : treePath ,
SHA : entry . ID . String ( ) ,
LastCommitSHA : lastCommit . ID . String ( ) ,
Size : entry . Size ( ) ,
URL : & selfURLString ,
2019-04-17 10:06:35 -06:00
Links : & api . FileLinksResponse {
2019-06-29 16:51:10 -04:00
Self : & selfURLString ,
2019-04-17 10:06:35 -06:00
} ,
}
2019-06-29 16:51:10 -04:00
// Now populate the rest of the ContentsResponse based on entry type
2020-04-24 18:20:22 +02:00
if entry . IsRegular ( ) || entry . IsExecutable ( ) {
2019-06-29 16:51:10 -04:00
contentsResponse . Type = string ( ContentTypeRegular )
2022-04-21 17:17:57 +02:00
if blobResponse , err := GetBlobBySHA ( ctx , repo , gitRepo , entry . ID . String ( ) ) ; err != nil {
2019-06-29 16:51:10 -04:00
return nil , err
} else if ! forList {
// We don't show the content if we are getting a list of FileContentResponses
contentsResponse . Encoding = & blobResponse . Encoding
contentsResponse . Content = & blobResponse . Content
}
} else if entry . IsDir ( ) {
contentsResponse . Type = string ( ContentTypeDir )
} else if entry . IsLink ( ) {
contentsResponse . Type = string ( ContentTypeLink )
// The target of a symlink file is the content of the file
targetFromContent , err := entry . Blob ( ) . GetBlobContent ( )
if err != nil {
return nil , err
}
contentsResponse . Target = & targetFromContent
} else if entry . IsSubModule ( ) {
contentsResponse . Type = string ( ContentTypeSubmodule )
submodule , err := commit . GetSubModule ( treePath )
if err != nil {
return nil , err
}
2023-03-21 06:26:01 +08:00
if submodule != nil && submodule . URL != "" {
contentsResponse . SubmoduleGitURL = & submodule . URL
}
2019-06-29 16:51:10 -04:00
}
// Handle links
if entry . IsRegular ( ) || entry . IsLink ( ) {
2023-02-12 09:31:14 +08:00
downloadURL , err := url . Parse ( repo . HTMLURL ( ) + "/raw/" + url . PathEscape ( string ( refType ) ) + "/" + util . PathEscapeSegments ( ref ) + "/" + util . PathEscapeSegments ( treePath ) )
2019-06-29 16:51:10 -04:00
if err != nil {
return nil , err
}
downloadURLString := downloadURL . String ( )
contentsResponse . DownloadURL = & downloadURLString
}
if ! entry . IsSubModule ( ) {
2023-02-12 09:31:14 +08:00
htmlURL , err := url . Parse ( repo . HTMLURL ( ) + "/src/" + url . PathEscape ( string ( refType ) ) + "/" + util . PathEscapeSegments ( ref ) + "/" + util . PathEscapeSegments ( treePath ) )
2019-06-29 16:51:10 -04:00
if err != nil {
return nil , err
}
htmlURLString := htmlURL . String ( )
contentsResponse . HTMLURL = & htmlURLString
contentsResponse . Links . HTMLURL = & htmlURLString
2023-02-12 09:31:14 +08:00
gitURL , err := url . Parse ( repo . APIURL ( ) + "/git/blobs/" + url . PathEscape ( entry . ID . String ( ) ) )
2019-06-29 16:51:10 -04:00
if err != nil {
return nil , err
}
gitURLString := gitURL . String ( )
contentsResponse . GitURL = & gitURLString
contentsResponse . Links . GitURL = & gitURLString
}
return contentsResponse , nil
2019-04-17 10:06:35 -06:00
}
2021-11-24 15:56:24 +08:00
// GetBlobBySHA get the GitBlobResponse of a repository using a sha hash.
2022-04-21 17:17:57 +02:00
func GetBlobBySHA ( ctx context . Context , repo * repo_model . Repository , gitRepo * git . Repository , sha string ) ( * api . GitBlobResponse , error ) {
2021-11-24 15:56:24 +08:00
gitBlob , err := gitRepo . GetBlob ( sha )
if err != nil {
return nil , err
}
content := ""
if gitBlob . Size ( ) <= setting . API . DefaultMaxBlobSize {
content , err = gitBlob . GetBlobContentBase64 ( )
if err != nil {
return nil , err
}
}
return & api . GitBlobResponse {
SHA : gitBlob . ID . String ( ) ,
URL : repo . APIURL ( ) + "/git/blobs/" + url . PathEscape ( gitBlob . ID . String ( ) ) ,
Size : gitBlob . Size ( ) ,
Encoding : "base64" ,
Content : content ,
} , nil
}