mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-10-31 12:41:17 +00:00
0ca13c5eae
When opening a repository, it will call `ensureValidRepository` and also `CatFileBatch`. But sometimes these will not be used until repository closed. So it's a waste of CPU to invoke 3 times git command for every open repository. This PR removed all of these from `OpenRepository` but only kept checking whether the folder exists. When a batch is necessary, the necessary functions will be invoked. --- Conflict resolution: Because of the removal of go-git in (#4941) `_nogogit.go` files were either renamed or merged into the 'common' file. Git does handle the renames correctly, but for those that were merged has to be manually copied pasted over. The patch looks the same, 201 additions 90 deletions as the original patch. (cherry picked from commit c03baab678ba5b2e9d974aea147e660417f5d3f7)
278 lines
6 KiB
Go
278 lines
6 KiB
Go
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package git
|
|
|
|
import (
|
|
"io"
|
|
"sort"
|
|
"strings"
|
|
|
|
"code.gitea.io/gitea/modules/log"
|
|
)
|
|
|
|
// TreeEntry the leaf in the git tree
|
|
type TreeEntry struct {
|
|
ID ObjectID
|
|
|
|
ptree *Tree
|
|
|
|
entryMode EntryMode
|
|
name string
|
|
|
|
size int64
|
|
sized bool
|
|
fullName string
|
|
}
|
|
|
|
// Name returns the name of the entry
|
|
func (te *TreeEntry) Name() string {
|
|
if te.fullName != "" {
|
|
return te.fullName
|
|
}
|
|
return te.name
|
|
}
|
|
|
|
// Mode returns the mode of the entry
|
|
func (te *TreeEntry) Mode() EntryMode {
|
|
return te.entryMode
|
|
}
|
|
|
|
// Size returns the size of the entry
|
|
func (te *TreeEntry) Size() int64 {
|
|
if te.IsDir() {
|
|
return 0
|
|
} else if te.sized {
|
|
return te.size
|
|
}
|
|
|
|
wr, rd, cancel, err := te.ptree.repo.CatFileBatchCheck(te.ptree.repo.Ctx)
|
|
if err != nil {
|
|
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
|
|
return 0
|
|
}
|
|
defer cancel()
|
|
_, err = wr.Write([]byte(te.ID.String() + "\n"))
|
|
if err != nil {
|
|
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
|
|
return 0
|
|
}
|
|
_, _, te.size, err = ReadBatchLine(rd)
|
|
if err != nil {
|
|
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
|
|
return 0
|
|
}
|
|
|
|
te.sized = true
|
|
return te.size
|
|
}
|
|
|
|
// IsSubModule if the entry is a sub module
|
|
func (te *TreeEntry) IsSubModule() bool {
|
|
return te.entryMode == EntryModeCommit
|
|
}
|
|
|
|
// IsDir if the entry is a sub dir
|
|
func (te *TreeEntry) IsDir() bool {
|
|
return te.entryMode == EntryModeTree
|
|
}
|
|
|
|
// IsLink if the entry is a symlink
|
|
func (te *TreeEntry) IsLink() bool {
|
|
return te.entryMode == EntryModeSymlink
|
|
}
|
|
|
|
// IsRegular if the entry is a regular file
|
|
func (te *TreeEntry) IsRegular() bool {
|
|
return te.entryMode == EntryModeBlob
|
|
}
|
|
|
|
// IsExecutable if the entry is an executable file (not necessarily binary)
|
|
func (te *TreeEntry) IsExecutable() bool {
|
|
return te.entryMode == EntryModeExec
|
|
}
|
|
|
|
// Blob returns the blob object the entry
|
|
func (te *TreeEntry) Blob() *Blob {
|
|
return &Blob{
|
|
ID: te.ID,
|
|
name: te.Name(),
|
|
size: te.size,
|
|
gotSize: te.sized,
|
|
repo: te.ptree.repo,
|
|
}
|
|
}
|
|
|
|
// Type returns the type of the entry (commit, tree, blob)
|
|
func (te *TreeEntry) Type() string {
|
|
switch te.Mode() {
|
|
case EntryModeCommit:
|
|
return "commit"
|
|
case EntryModeTree:
|
|
return "tree"
|
|
default:
|
|
return "blob"
|
|
}
|
|
}
|
|
|
|
// FollowLink returns the entry pointed to by a symlink
|
|
func (te *TreeEntry) FollowLink() (*TreeEntry, string, error) {
|
|
if !te.IsLink() {
|
|
return nil, "", ErrBadLink{te.Name(), "not a symlink"}
|
|
}
|
|
|
|
// read the link
|
|
r, err := te.Blob().DataAsync()
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
closed := false
|
|
defer func() {
|
|
if !closed {
|
|
_ = r.Close()
|
|
}
|
|
}()
|
|
buf := make([]byte, te.Size())
|
|
_, err = io.ReadFull(r, buf)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
_ = r.Close()
|
|
closed = true
|
|
|
|
lnk := string(buf)
|
|
t := te.ptree
|
|
|
|
// traverse up directories
|
|
for ; t != nil && strings.HasPrefix(lnk, "../"); lnk = lnk[3:] {
|
|
t = t.ptree
|
|
}
|
|
|
|
if t == nil {
|
|
return nil, "", ErrBadLink{te.Name(), "points outside of repo"}
|
|
}
|
|
|
|
target, err := t.GetTreeEntryByPath(lnk)
|
|
if err != nil {
|
|
if IsErrNotExist(err) {
|
|
return nil, "", ErrBadLink{te.Name(), "broken link"}
|
|
}
|
|
return nil, "", err
|
|
}
|
|
return target, lnk, nil
|
|
}
|
|
|
|
// FollowLinks returns the entry ultimately pointed to by a symlink
|
|
func (te *TreeEntry) FollowLinks() (*TreeEntry, string, error) {
|
|
if !te.IsLink() {
|
|
return nil, "", ErrBadLink{te.Name(), "not a symlink"}
|
|
}
|
|
entry := te
|
|
entryLink := ""
|
|
for i := 0; i < 999; i++ {
|
|
if entry.IsLink() {
|
|
next, link, err := entry.FollowLink()
|
|
entryLink = link
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
if next.ID == entry.ID {
|
|
return nil, "", ErrBadLink{
|
|
entry.Name(),
|
|
"recursive link",
|
|
}
|
|
}
|
|
entry = next
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
if entry.IsLink() {
|
|
return nil, "", ErrBadLink{
|
|
te.Name(),
|
|
"too many levels of symbolic links",
|
|
}
|
|
}
|
|
return entry, entryLink, nil
|
|
}
|
|
|
|
// returns the Tree pointed to by this TreeEntry, or nil if this is not a tree
|
|
func (te *TreeEntry) Tree() *Tree {
|
|
t, err := te.ptree.repo.getTree(te.ID)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
t.ptree = te.ptree
|
|
return t
|
|
}
|
|
|
|
// GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory )
|
|
func (te *TreeEntry) GetSubJumpablePathName() string {
|
|
if te.IsSubModule() || !te.IsDir() {
|
|
return ""
|
|
}
|
|
tree, err := te.ptree.SubTree(te.Name())
|
|
if err != nil {
|
|
return te.Name()
|
|
}
|
|
entries, _ := tree.ListEntries()
|
|
if len(entries) == 1 && entries[0].IsDir() {
|
|
name := entries[0].GetSubJumpablePathName()
|
|
if name != "" {
|
|
return te.Name() + "/" + name
|
|
}
|
|
}
|
|
return te.Name()
|
|
}
|
|
|
|
// Entries a list of entry
|
|
type Entries []*TreeEntry
|
|
|
|
type customSortableEntries struct {
|
|
Comparer func(s1, s2 string) bool
|
|
Entries
|
|
}
|
|
|
|
var sorter = []func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool{
|
|
func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
|
|
return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
|
|
},
|
|
func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
|
|
return cmp(t1.Name(), t2.Name())
|
|
},
|
|
}
|
|
|
|
func (ctes customSortableEntries) Len() int { return len(ctes.Entries) }
|
|
|
|
func (ctes customSortableEntries) Swap(i, j int) {
|
|
ctes.Entries[i], ctes.Entries[j] = ctes.Entries[j], ctes.Entries[i]
|
|
}
|
|
|
|
func (ctes customSortableEntries) Less(i, j int) bool {
|
|
t1, t2 := ctes.Entries[i], ctes.Entries[j]
|
|
var k int
|
|
for k = 0; k < len(sorter)-1; k++ {
|
|
s := sorter[k]
|
|
switch {
|
|
case s(t1, t2, ctes.Comparer):
|
|
return true
|
|
case s(t2, t1, ctes.Comparer):
|
|
return false
|
|
}
|
|
}
|
|
return sorter[k](t1, t2, ctes.Comparer)
|
|
}
|
|
|
|
// Sort sort the list of entry
|
|
func (tes Entries) Sort() {
|
|
sort.Sort(customSortableEntries{func(s1, s2 string) bool {
|
|
return s1 < s2
|
|
}, tes})
|
|
}
|
|
|
|
// CustomSort customizable string comparing sort entry list
|
|
func (tes Entries) CustomSort(cmp func(s1, s2 string) bool) {
|
|
sort.Sort(customSortableEntries{cmp, tes})
|
|
}
|