loading up the forgejo repo on tangled to test page performance
1// Copyright 2015 The Gogs Authors. All rights reserved.
2// Copyright 2019 The Gitea Authors. All rights reserved.
3// SPDX-License-Identifier: MIT
4
5package git
6
7import (
8 "io"
9 "sort"
10 "strings"
11
12 "forgejo.org/modules/log"
13)
14
15// TreeEntry the leaf in the git tree
16type TreeEntry struct {
17 ID ObjectID
18
19 ptree *Tree
20
21 entryMode EntryMode
22 name string
23
24 size int64
25 sized bool
26 fullName string
27}
28
29// Name returns the name of the entry
30func (te *TreeEntry) Name() string {
31 if te.fullName != "" {
32 return te.fullName
33 }
34 return te.name
35}
36
37// Mode returns the mode of the entry
38func (te *TreeEntry) Mode() EntryMode {
39 return te.entryMode
40}
41
42// Size returns the size of the entry
43func (te *TreeEntry) Size() int64 {
44 if te.IsDir() {
45 return 0
46 } else if te.sized {
47 return te.size
48 }
49
50 wr, rd, cancel, err := te.ptree.repo.CatFileBatchCheck(te.ptree.repo.Ctx)
51 if err != nil {
52 log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
53 return 0
54 }
55 defer cancel()
56 _, err = wr.Write([]byte(te.ID.String() + "\n"))
57 if err != nil {
58 log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
59 return 0
60 }
61 _, _, te.size, err = ReadBatchLine(rd)
62 if err != nil {
63 log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
64 return 0
65 }
66
67 te.sized = true
68 return te.size
69}
70
71// IsSubModule if the entry is a sub module
72func (te *TreeEntry) IsSubModule() bool {
73 return te.entryMode == EntryModeCommit
74}
75
76// IsDir if the entry is a sub dir
77func (te *TreeEntry) IsDir() bool {
78 return te.entryMode == EntryModeTree
79}
80
81// IsLink if the entry is a symlink
82func (te *TreeEntry) IsLink() bool {
83 return te.entryMode == EntryModeSymlink
84}
85
86// IsRegular if the entry is a regular file
87func (te *TreeEntry) IsRegular() bool {
88 return te.entryMode == EntryModeBlob
89}
90
91// IsExecutable if the entry is an executable file (not necessarily binary)
92func (te *TreeEntry) IsExecutable() bool {
93 return te.entryMode == EntryModeExec
94}
95
96// Blob returns the blob object the entry
97func (te *TreeEntry) Blob() *Blob {
98 return &Blob{
99 ID: te.ID,
100 name: te.Name(),
101 size: te.size,
102 gotSize: te.sized,
103 repo: te.ptree.repo,
104 }
105}
106
107// Type returns the type of the entry (commit, tree, blob)
108func (te *TreeEntry) Type() string {
109 switch te.Mode() {
110 case EntryModeCommit:
111 return "commit"
112 case EntryModeTree:
113 return "tree"
114 default:
115 return "blob"
116 }
117}
118
119// FollowLink returns the entry pointed to by a symlink
120func (te *TreeEntry) FollowLink() (*TreeEntry, string, error) {
121 if !te.IsLink() {
122 return nil, "", ErrBadLink{te.Name(), "not a symlink"}
123 }
124
125 // read the link
126 r, err := te.Blob().DataAsync()
127 if err != nil {
128 return nil, "", err
129 }
130 closed := false
131 defer func() {
132 if !closed {
133 _ = r.Close()
134 }
135 }()
136 buf := make([]byte, te.Size())
137 _, err = io.ReadFull(r, buf)
138 if err != nil {
139 return nil, "", err
140 }
141 _ = r.Close()
142 closed = true
143
144 lnk := string(buf)
145 t := te.ptree
146
147 // traverse up directories
148 for ; t != nil && strings.HasPrefix(lnk, "../"); lnk = lnk[3:] {
149 t = t.ptree
150 }
151
152 if t == nil {
153 return nil, "", ErrBadLink{te.Name(), "points outside of repo"}
154 }
155
156 target, err := t.GetTreeEntryByPath(lnk)
157 if err != nil {
158 if IsErrNotExist(err) {
159 return nil, "", ErrBadLink{te.Name(), "broken link"}
160 }
161 return nil, "", err
162 }
163 return target, lnk, nil
164}
165
166// FollowLinks returns the entry ultimately pointed to by a symlink
167func (te *TreeEntry) FollowLinks() (*TreeEntry, string, error) {
168 if !te.IsLink() {
169 return nil, "", ErrBadLink{te.Name(), "not a symlink"}
170 }
171 entry := te
172 entryLink := ""
173 for i := 0; i < 999; i++ {
174 if entry.IsLink() {
175 next, link, err := entry.FollowLink()
176 entryLink = link
177 if err != nil {
178 return nil, "", err
179 }
180 if next.ID == entry.ID {
181 return nil, "", ErrBadLink{
182 entry.Name(),
183 "recursive link",
184 }
185 }
186 entry = next
187 } else {
188 break
189 }
190 }
191 if entry.IsLink() {
192 return nil, "", ErrBadLink{
193 te.Name(),
194 "too many levels of symbolic links",
195 }
196 }
197 return entry, entryLink, nil
198}
199
200// returns the Tree pointed to by this TreeEntry, or nil if this is not a tree
201func (te *TreeEntry) Tree() *Tree {
202 t, err := te.ptree.repo.getTree(te.ID)
203 if err != nil {
204 return nil
205 }
206 t.ptree = te.ptree
207 return t
208}
209
210// GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory )
211func (te *TreeEntry) GetSubJumpablePathName() string {
212 if te.IsSubModule() || !te.IsDir() {
213 return ""
214 }
215 tree, err := te.ptree.SubTree(te.Name())
216 if err != nil {
217 return te.Name()
218 }
219 entries, _ := tree.ListEntries()
220 if len(entries) == 1 && entries[0].IsDir() {
221 name := entries[0].GetSubJumpablePathName()
222 if name != "" {
223 return te.Name() + "/" + name
224 }
225 }
226 return te.Name()
227}
228
229// Entries a list of entry
230type Entries []*TreeEntry
231
232type customSortableEntries struct {
233 Comparer func(s1, s2 string) bool
234 Entries
235}
236
237var sorter = []func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool{
238 func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
239 return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
240 },
241 func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
242 return cmp(t1.Name(), t2.Name())
243 },
244}
245
246func (ctes customSortableEntries) Len() int { return len(ctes.Entries) }
247
248func (ctes customSortableEntries) Swap(i, j int) {
249 ctes.Entries[i], ctes.Entries[j] = ctes.Entries[j], ctes.Entries[i]
250}
251
252func (ctes customSortableEntries) Less(i, j int) bool {
253 t1, t2 := ctes.Entries[i], ctes.Entries[j]
254 var k int
255 for k = 0; k < len(sorter)-1; k++ {
256 s := sorter[k]
257 switch {
258 case s(t1, t2, ctes.Comparer):
259 return true
260 case s(t2, t1, ctes.Comparer):
261 return false
262 }
263 }
264 return sorter[k](t1, t2, ctes.Comparer)
265}
266
267// Sort sort the list of entry
268func (tes Entries) Sort() {
269 sort.Sort(customSortableEntries{func(s1, s2 string) bool {
270 return s1 < s2
271 }, tes})
272}
273
274// CustomSort customizable string comparing sort entry list
275func (tes Entries) CustomSort(cmp func(s1, s2 string) bool) {
276 sort.Sort(customSortableEntries{cmp, tes})
277}