code.gitea.io/gitea@v1.21.7/services/repository/files/content.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package files 5 6 import ( 7 "context" 8 "fmt" 9 "net/url" 10 "path" 11 "strings" 12 13 "code.gitea.io/gitea/models" 14 repo_model "code.gitea.io/gitea/models/repo" 15 "code.gitea.io/gitea/modules/git" 16 "code.gitea.io/gitea/modules/setting" 17 api "code.gitea.io/gitea/modules/structs" 18 "code.gitea.io/gitea/modules/util" 19 ) 20 21 // ContentType repo content type 22 type ContentType string 23 24 // The string representations of different content types 25 const ( 26 // ContentTypeRegular regular content type (file) 27 ContentTypeRegular ContentType = "file" 28 // ContentTypeDir dir content type (dir) 29 ContentTypeDir ContentType = "dir" 30 // ContentLink link content type (symlink) 31 ContentTypeLink ContentType = "symlink" 32 // ContentTag submodule content type (submodule) 33 ContentTypeSubmodule ContentType = "submodule" 34 ) 35 36 // String gets the string of ContentType 37 func (ct *ContentType) String() string { 38 return string(*ct) 39 } 40 41 // GetContentsOrList gets the meta data of a file's contents (*ContentsResponse) if treePath not a tree 42 // directory, otherwise a listing of file contents ([]*ContentsResponse). Ref can be a branch, commit or tag 43 func GetContentsOrList(ctx context.Context, repo *repo_model.Repository, treePath, ref string) (any, error) { 44 if repo.IsEmpty { 45 return make([]any, 0), nil 46 } 47 if ref == "" { 48 ref = repo.DefaultBranch 49 } 50 origRef := ref 51 52 // Check that the path given in opts.treePath is valid (not a git path) 53 cleanTreePath := CleanUploadFileName(treePath) 54 if cleanTreePath == "" && treePath != "" { 55 return nil, models.ErrFilenameInvalid{ 56 Path: treePath, 57 } 58 } 59 treePath = cleanTreePath 60 61 gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.RepoPath()) 62 if err != nil { 63 return nil, err 64 } 65 defer closer.Close() 66 67 // Get the commit object for the ref 68 commit, err := gitRepo.GetCommit(ref) 69 if err != nil { 70 return nil, err 71 } 72 73 entry, err := commit.GetTreeEntryByPath(treePath) 74 if err != nil { 75 return nil, err 76 } 77 78 if entry.Type() != "tree" { 79 return GetContents(ctx, repo, treePath, origRef, false) 80 } 81 82 // We are in a directory, so we return a list of FileContentResponse objects 83 var fileList []*api.ContentsResponse 84 85 gitTree, err := commit.SubTree(treePath) 86 if err != nil { 87 return nil, err 88 } 89 entries, err := gitTree.ListEntries() 90 if err != nil { 91 return nil, err 92 } 93 for _, e := range entries { 94 subTreePath := path.Join(treePath, e.Name()) 95 fileContentResponse, err := GetContents(ctx, repo, subTreePath, origRef, true) 96 if err != nil { 97 return nil, err 98 } 99 fileList = append(fileList, fileContentResponse) 100 } 101 return fileList, nil 102 } 103 104 // GetObjectTypeFromTreeEntry check what content is behind it 105 func GetObjectTypeFromTreeEntry(entry *git.TreeEntry) ContentType { 106 switch { 107 case entry.IsDir(): 108 return ContentTypeDir 109 case entry.IsSubModule(): 110 return ContentTypeSubmodule 111 case entry.IsExecutable(), entry.IsRegular(): 112 return ContentTypeRegular 113 case entry.IsLink(): 114 return ContentTypeLink 115 default: 116 return "" 117 } 118 } 119 120 // GetContents gets the meta data on a file's contents. Ref can be a branch, commit or tag 121 func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref string, forList bool) (*api.ContentsResponse, error) { 122 if ref == "" { 123 ref = repo.DefaultBranch 124 } 125 origRef := ref 126 127 // Check that the path given in opts.treePath is valid (not a git path) 128 cleanTreePath := CleanUploadFileName(treePath) 129 if cleanTreePath == "" && treePath != "" { 130 return nil, models.ErrFilenameInvalid{ 131 Path: treePath, 132 } 133 } 134 treePath = cleanTreePath 135 136 gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.RepoPath()) 137 if err != nil { 138 return nil, err 139 } 140 defer closer.Close() 141 142 // Get the commit object for the ref 143 commit, err := gitRepo.GetCommit(ref) 144 if err != nil { 145 return nil, err 146 } 147 commitID := commit.ID.String() 148 if len(ref) >= 4 && strings.HasPrefix(commitID, ref) { 149 ref = commit.ID.String() 150 } 151 152 entry, err := commit.GetTreeEntryByPath(treePath) 153 if err != nil { 154 return nil, err 155 } 156 157 refType := gitRepo.GetRefType(ref) 158 if refType == "invalid" { 159 return nil, fmt.Errorf("no commit found for the ref [ref: %s]", ref) 160 } 161 162 selfURL, err := url.Parse(repo.APIURL() + "/contents/" + util.PathEscapeSegments(treePath) + "?ref=" + url.QueryEscape(origRef)) 163 if err != nil { 164 return nil, err 165 } 166 selfURLString := selfURL.String() 167 168 err = gitRepo.AddLastCommitCache(repo.GetCommitsCountCacheKey(ref, refType != git.ObjectCommit), repo.FullName(), commitID) 169 if err != nil { 170 return nil, err 171 } 172 173 lastCommit, err := commit.GetCommitByPath(treePath) 174 if err != nil { 175 return nil, err 176 } 177 178 // All content types have these fields in populated 179 contentsResponse := &api.ContentsResponse{ 180 Name: entry.Name(), 181 Path: treePath, 182 SHA: entry.ID.String(), 183 LastCommitSHA: lastCommit.ID.String(), 184 Size: entry.Size(), 185 URL: &selfURLString, 186 Links: &api.FileLinksResponse{ 187 Self: &selfURLString, 188 }, 189 } 190 191 // Now populate the rest of the ContentsResponse based on entry type 192 if entry.IsRegular() || entry.IsExecutable() { 193 contentsResponse.Type = string(ContentTypeRegular) 194 if blobResponse, err := GetBlobBySHA(ctx, repo, gitRepo, entry.ID.String()); err != nil { 195 return nil, err 196 } else if !forList { 197 // We don't show the content if we are getting a list of FileContentResponses 198 contentsResponse.Encoding = &blobResponse.Encoding 199 contentsResponse.Content = &blobResponse.Content 200 } 201 } else if entry.IsDir() { 202 contentsResponse.Type = string(ContentTypeDir) 203 } else if entry.IsLink() { 204 contentsResponse.Type = string(ContentTypeLink) 205 // The target of a symlink file is the content of the file 206 targetFromContent, err := entry.Blob().GetBlobContent(1024) 207 if err != nil { 208 return nil, err 209 } 210 contentsResponse.Target = &targetFromContent 211 } else if entry.IsSubModule() { 212 contentsResponse.Type = string(ContentTypeSubmodule) 213 submodule, err := commit.GetSubModule(treePath) 214 if err != nil { 215 return nil, err 216 } 217 if submodule != nil && submodule.URL != "" { 218 contentsResponse.SubmoduleGitURL = &submodule.URL 219 } 220 } 221 // Handle links 222 if entry.IsRegular() || entry.IsLink() { 223 downloadURL, err := url.Parse(repo.HTMLURL() + "/raw/" + url.PathEscape(string(refType)) + "/" + util.PathEscapeSegments(ref) + "/" + util.PathEscapeSegments(treePath)) 224 if err != nil { 225 return nil, err 226 } 227 downloadURLString := downloadURL.String() 228 contentsResponse.DownloadURL = &downloadURLString 229 } 230 if !entry.IsSubModule() { 231 htmlURL, err := url.Parse(repo.HTMLURL() + "/src/" + url.PathEscape(string(refType)) + "/" + util.PathEscapeSegments(ref) + "/" + util.PathEscapeSegments(treePath)) 232 if err != nil { 233 return nil, err 234 } 235 htmlURLString := htmlURL.String() 236 contentsResponse.HTMLURL = &htmlURLString 237 contentsResponse.Links.HTMLURL = &htmlURLString 238 239 gitURL, err := url.Parse(repo.APIURL() + "/git/blobs/" + url.PathEscape(entry.ID.String())) 240 if err != nil { 241 return nil, err 242 } 243 gitURLString := gitURL.String() 244 contentsResponse.GitURL = &gitURLString 245 contentsResponse.Links.GitURL = &gitURLString 246 } 247 248 return contentsResponse, nil 249 } 250 251 // GetBlobBySHA get the GitBlobResponse of a repository using a sha hash. 252 func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, sha string) (*api.GitBlobResponse, error) { 253 gitBlob, err := gitRepo.GetBlob(sha) 254 if err != nil { 255 return nil, err 256 } 257 content := "" 258 if gitBlob.Size() <= setting.API.DefaultMaxBlobSize { 259 content, err = gitBlob.GetBlobContentBase64() 260 if err != nil { 261 return nil, err 262 } 263 } 264 return &api.GitBlobResponse{ 265 SHA: gitBlob.ID.String(), 266 URL: repo.APIURL() + "/git/blobs/" + url.PathEscape(gitBlob.ID.String()), 267 Size: gitBlob.Size(), 268 Encoding: "base64", 269 Content: content, 270 }, nil 271 }