You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

200 lines
4.3 KiB

  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package git
  5. import (
  6. "io"
  7. "sort"
  8. "strconv"
  9. "strings"
  10. )
  11. // EntryMode the type of the object in the git tree
  12. type EntryMode int
  13. // There are only a few file modes in Git. They look like unix file modes, but they can only be
  14. // one of these.
  15. const (
  16. // EntryModeBlob
  17. EntryModeBlob EntryMode = 0100644
  18. // EntryModeExec
  19. EntryModeExec EntryMode = 0100755
  20. // EntryModeSymlink
  21. EntryModeSymlink EntryMode = 0120000
  22. // EntryModeCommit
  23. EntryModeCommit EntryMode = 0160000
  24. // EntryModeTree
  25. EntryModeTree EntryMode = 0040000
  26. )
  27. // TreeEntry the leaf in the git tree
  28. type TreeEntry struct {
  29. ID SHA1
  30. Type ObjectType
  31. mode EntryMode
  32. name string
  33. ptree *Tree
  34. commited bool
  35. size int64
  36. sized bool
  37. }
  38. // Name returns the name of the entry
  39. func (te *TreeEntry) Name() string {
  40. return te.name
  41. }
  42. // Size returns the size of the entry
  43. func (te *TreeEntry) Size() int64 {
  44. if te.IsDir() {
  45. return 0
  46. } else if te.sized {
  47. return te.size
  48. }
  49. stdout, err := NewCommand("cat-file", "-s", te.ID.String()).RunInDir(te.ptree.repo.Path)
  50. if err != nil {
  51. return 0
  52. }
  53. te.sized = true
  54. te.size, _ = strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
  55. return te.size
  56. }
  57. // IsSubModule if the entry is a sub module
  58. func (te *TreeEntry) IsSubModule() bool {
  59. return te.mode == EntryModeCommit
  60. }
  61. // IsDir if the entry is a sub dir
  62. func (te *TreeEntry) IsDir() bool {
  63. return te.mode == EntryModeTree
  64. }
  65. // IsLink if the entry is a symlink
  66. func (te *TreeEntry) IsLink() bool {
  67. return te.mode == EntryModeSymlink
  68. }
  69. // Blob retrun the blob object the entry
  70. func (te *TreeEntry) Blob() *Blob {
  71. return &Blob{
  72. repo: te.ptree.repo,
  73. TreeEntry: te,
  74. }
  75. }
  76. // FollowLink returns the entry pointed to by a symlink
  77. func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
  78. if !te.IsLink() {
  79. return nil, ErrBadLink{te.Name(), "not a symlink"}
  80. }
  81. // read the link
  82. r, err := te.Blob().Data()
  83. if err != nil {
  84. return nil, err
  85. }
  86. buf := make([]byte, te.Size())
  87. _, err = io.ReadFull(r, buf)
  88. if err != nil {
  89. return nil, err
  90. }
  91. lnk := string(buf)
  92. t := te.ptree
  93. // traverse up directories
  94. for ; t != nil && strings.HasPrefix(lnk, "../"); lnk = lnk[3:] {
  95. t = t.ptree
  96. }
  97. if t == nil {
  98. return nil, ErrBadLink{te.Name(), "points outside of repo"}
  99. }
  100. target, err := t.GetTreeEntryByPath(lnk)
  101. if err != nil {
  102. if IsErrNotExist(err) {
  103. return nil, ErrBadLink{te.Name(), "broken link"}
  104. }
  105. return nil, err
  106. }
  107. return target, nil
  108. }
  109. // GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory )
  110. func (te *TreeEntry) GetSubJumpablePathName() string {
  111. if te.IsSubModule() || !te.IsDir() {
  112. return ""
  113. }
  114. tree, err := te.ptree.SubTree(te.name)
  115. if err != nil {
  116. return te.name
  117. }
  118. entries, _ := tree.ListEntries()
  119. if len(entries) == 1 && entries[0].IsDir() {
  120. name := entries[0].GetSubJumpablePathName()
  121. if name != "" {
  122. return te.name + "/" + name
  123. }
  124. }
  125. return te.name
  126. }
  127. // Entries a list of entry
  128. type Entries []*TreeEntry
  129. type customSortableEntries struct {
  130. Comparer func(s1, s2 string) bool
  131. Entries
  132. }
  133. var sorter = []func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool{
  134. func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
  135. return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
  136. },
  137. func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
  138. return cmp(t1.name, t2.name)
  139. },
  140. }
  141. func (ctes customSortableEntries) Len() int { return len(ctes.Entries) }
  142. func (ctes customSortableEntries) Swap(i, j int) {
  143. ctes.Entries[i], ctes.Entries[j] = ctes.Entries[j], ctes.Entries[i]
  144. }
  145. func (ctes customSortableEntries) Less(i, j int) bool {
  146. t1, t2 := ctes.Entries[i], ctes.Entries[j]
  147. var k int
  148. for k = 0; k < len(sorter)-1; k++ {
  149. s := sorter[k]
  150. switch {
  151. case s(t1, t2, ctes.Comparer):
  152. return true
  153. case s(t2, t1, ctes.Comparer):
  154. return false
  155. }
  156. }
  157. return sorter[k](t1, t2, ctes.Comparer)
  158. }
  159. // Sort sort the list of entry
  160. func (tes Entries) Sort() {
  161. sort.Sort(customSortableEntries{func(s1, s2 string) bool {
  162. return s1 < s2
  163. }, tes})
  164. }
  165. // CustomSort customizable string comparing sort entry list
  166. func (tes Entries) CustomSort(cmp func(s1, s2 string) bool) {
  167. sort.Sort(customSortableEntries{cmp, tes})
  168. }