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