loading up the forgejo repo on tangled to test page performance
at forgejo 277 lines 6.2 kB view raw
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}