Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

content.go 7.7KB

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