gitea源码

tree.go 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package files
  4. import (
  5. "context"
  6. "fmt"
  7. "html/template"
  8. "net/url"
  9. "path"
  10. "sort"
  11. "strings"
  12. repo_model "code.gitea.io/gitea/models/repo"
  13. "code.gitea.io/gitea/modules/fileicon"
  14. "code.gitea.io/gitea/modules/git"
  15. "code.gitea.io/gitea/modules/log"
  16. "code.gitea.io/gitea/modules/setting"
  17. api "code.gitea.io/gitea/modules/structs"
  18. "code.gitea.io/gitea/modules/util"
  19. )
  20. // ErrSHANotFound represents a "SHADoesNotMatch" kind of error.
  21. type ErrSHANotFound struct {
  22. SHA string
  23. }
  24. // IsErrSHANotFound checks if an error is a ErrSHANotFound.
  25. func IsErrSHANotFound(err error) bool {
  26. _, ok := err.(ErrSHANotFound)
  27. return ok
  28. }
  29. func (err ErrSHANotFound) Error() string {
  30. return fmt.Sprintf("sha not found [%s]", err.SHA)
  31. }
  32. func (err ErrSHANotFound) Unwrap() error {
  33. return util.ErrNotExist
  34. }
  35. // GetTreeBySHA get the GitTreeResponse of a repository using a sha hash.
  36. func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, sha string, page, perPage int, recursive bool) (*api.GitTreeResponse, error) {
  37. gitTree, err := gitRepo.GetTree(sha)
  38. if err != nil || gitTree == nil {
  39. return nil, ErrSHANotFound{ // TODO: this error has never been catch outside of this function
  40. SHA: sha,
  41. }
  42. }
  43. tree := new(api.GitTreeResponse)
  44. tree.SHA = gitTree.ResolvedID.String()
  45. tree.URL = repo.APIURL() + "/git/trees/" + url.PathEscape(tree.SHA)
  46. var entries git.Entries
  47. if recursive {
  48. entries, err = gitTree.ListEntriesRecursiveWithSize()
  49. } else {
  50. entries, err = gitTree.ListEntries()
  51. }
  52. if err != nil {
  53. return nil, err
  54. }
  55. apiURL := repo.APIURL()
  56. apiURLLen := len(apiURL)
  57. objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
  58. hashLen := objectFormat.FullLength()
  59. const gitBlobsPath = "/git/blobs/"
  60. blobURL := make([]byte, apiURLLen+hashLen+len(gitBlobsPath))
  61. copy(blobURL, apiURL)
  62. copy(blobURL[apiURLLen:], []byte(gitBlobsPath))
  63. const gitTreePath = "/git/trees/"
  64. treeURL := make([]byte, apiURLLen+hashLen+len(gitTreePath))
  65. copy(treeURL, apiURL)
  66. copy(treeURL[apiURLLen:], []byte(gitTreePath))
  67. // copyPos is at the start of the hash
  68. copyPos := len(treeURL) - hashLen
  69. if perPage <= 0 || perPage > setting.API.DefaultGitTreesPerPage {
  70. perPage = setting.API.DefaultGitTreesPerPage
  71. }
  72. if page <= 0 {
  73. page = 1
  74. }
  75. tree.Page = page
  76. tree.TotalCount = len(entries)
  77. rangeStart := perPage * (page - 1)
  78. if rangeStart >= len(entries) {
  79. return tree, nil
  80. }
  81. rangeEnd := min(rangeStart+perPage, len(entries))
  82. tree.Truncated = rangeEnd < len(entries)
  83. tree.Entries = make([]api.GitEntry, rangeEnd-rangeStart)
  84. for e := rangeStart; e < rangeEnd; e++ {
  85. i := e - rangeStart
  86. tree.Entries[i].Path = entries[e].Name()
  87. tree.Entries[i].Mode = fmt.Sprintf("%06o", entries[e].Mode())
  88. tree.Entries[i].Type = entries[e].Type()
  89. tree.Entries[i].Size = entries[e].Size()
  90. tree.Entries[i].SHA = entries[e].ID.String()
  91. if entries[e].IsDir() {
  92. copy(treeURL[copyPos:], entries[e].ID.String())
  93. tree.Entries[i].URL = string(treeURL)
  94. } else if entries[e].IsSubModule() {
  95. // In Github Rest API Version=2022-11-28, if a tree entry is a submodule,
  96. // its url will be returned as an empty string.
  97. // So the URL will be set to "" here.
  98. tree.Entries[i].URL = ""
  99. } else {
  100. copy(blobURL[copyPos:], entries[e].ID.String())
  101. tree.Entries[i].URL = string(blobURL)
  102. }
  103. }
  104. return tree, nil
  105. }
  106. func entryModeString(entryMode git.EntryMode) string {
  107. switch entryMode {
  108. case git.EntryModeBlob:
  109. return "blob"
  110. case git.EntryModeExec:
  111. return "exec"
  112. case git.EntryModeSymlink:
  113. return "symlink"
  114. case git.EntryModeCommit:
  115. return "commit" // submodule
  116. case git.EntryModeTree:
  117. return "tree"
  118. }
  119. return "unknown"
  120. }
  121. type TreeViewNode struct {
  122. EntryName string `json:"entryName"`
  123. EntryMode string `json:"entryMode"`
  124. EntryIcon template.HTML `json:"entryIcon"`
  125. EntryIconOpen template.HTML `json:"entryIconOpen,omitempty"`
  126. SymLinkedToMode string `json:"symLinkedToMode,omitempty"` // TODO: for the EntryMode="symlink"
  127. FullPath string `json:"fullPath"`
  128. SubmoduleURL string `json:"submoduleUrl,omitempty"`
  129. Children []*TreeViewNode `json:"children,omitempty"`
  130. }
  131. func (node *TreeViewNode) sortLevel() int {
  132. return util.Iif(node.EntryMode == "tree" || node.EntryMode == "commit", 0, 1)
  133. }
  134. func newTreeViewNodeFromEntry(ctx context.Context, repoLink string, renderedIconPool *fileicon.RenderedIconPool, commit *git.Commit, parentDir string, entry *git.TreeEntry) *TreeViewNode {
  135. node := &TreeViewNode{
  136. EntryName: entry.Name(),
  137. EntryMode: entryModeString(entry.Mode()),
  138. FullPath: path.Join(parentDir, entry.Name()),
  139. }
  140. entryInfo := fileicon.EntryInfoFromGitTreeEntry(commit, node.FullPath, entry)
  141. node.EntryIcon = fileicon.RenderEntryIconHTML(renderedIconPool, entryInfo)
  142. if entryInfo.EntryMode.IsDir() {
  143. entryInfo.IsOpen = true
  144. node.EntryIconOpen = fileicon.RenderEntryIconHTML(renderedIconPool, entryInfo)
  145. }
  146. if node.EntryMode == "commit" {
  147. if subModule, err := commit.GetSubModule(node.FullPath); err != nil {
  148. log.Error("GetSubModule: %v", err)
  149. } else if subModule != nil {
  150. submoduleFile := git.NewCommitSubmoduleFile(repoLink, node.FullPath, subModule.URL, entry.ID.String())
  151. webLink := submoduleFile.SubmoduleWebLinkTree(ctx)
  152. if webLink != nil {
  153. node.SubmoduleURL = webLink.CommitWebLink
  154. }
  155. }
  156. }
  157. return node
  158. }
  159. // sortTreeViewNodes list directory first and with alpha sequence
  160. func sortTreeViewNodes(nodes []*TreeViewNode) {
  161. sort.Slice(nodes, func(i, j int) bool {
  162. a, b := nodes[i].sortLevel(), nodes[j].sortLevel()
  163. if a != b {
  164. return a < b
  165. }
  166. return nodes[i].EntryName < nodes[j].EntryName
  167. })
  168. }
  169. func listTreeNodes(ctx context.Context, repoLink string, renderedIconPool *fileicon.RenderedIconPool, commit *git.Commit, tree *git.Tree, treePath, subPath string) ([]*TreeViewNode, error) {
  170. entries, err := tree.ListEntries()
  171. if err != nil {
  172. return nil, err
  173. }
  174. subPathDirName, subPathRemaining, _ := strings.Cut(subPath, "/")
  175. nodes := make([]*TreeViewNode, 0, len(entries))
  176. for _, entry := range entries {
  177. node := newTreeViewNodeFromEntry(ctx, repoLink, renderedIconPool, commit, treePath, entry)
  178. nodes = append(nodes, node)
  179. if entry.IsDir() && subPathDirName == entry.Name() {
  180. subTreePath := treePath + "/" + node.EntryName
  181. if subTreePath[0] == '/' {
  182. subTreePath = subTreePath[1:]
  183. }
  184. subNodes, err := listTreeNodes(ctx, repoLink, renderedIconPool, commit, entry.Tree(), subTreePath, subPathRemaining)
  185. if err != nil {
  186. log.Error("listTreeNodes: %v", err)
  187. } else {
  188. node.Children = subNodes
  189. }
  190. }
  191. }
  192. sortTreeViewNodes(nodes)
  193. return nodes, nil
  194. }
  195. func GetTreeViewNodes(ctx context.Context, repoLink string, renderedIconPool *fileicon.RenderedIconPool, commit *git.Commit, treePath, subPath string) ([]*TreeViewNode, error) {
  196. entry, err := commit.GetTreeEntryByPath(treePath)
  197. if err != nil {
  198. return nil, err
  199. }
  200. return listTreeNodes(ctx, repoLink, renderedIconPool, commit, entry.Tree(), treePath, subPath)
  201. }