2016-11-03 22:16:01 +00:00
|
|
|
// Copyright 2015 The Gogs Authors. All rights reserved.
|
2019-04-19 12:17:27 +00:00
|
|
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
2022-11-27 18:20:29 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2016-11-03 22:16:01 +00:00
|
|
|
|
|
|
|
package git
|
|
|
|
|
|
|
|
import (
|
2018-05-01 07:04:36 +00:00
|
|
|
"io"
|
2016-11-03 22:16:01 +00:00
|
|
|
"sort"
|
|
|
|
"strings"
|
2024-08-12 15:16:55 +00:00
|
|
|
|
|
|
|
"code.gitea.io/gitea/modules/log"
|
2016-11-03 22:16:01 +00:00
|
|
|
)
|
|
|
|
|
2024-08-12 15:16:55 +00:00
|
|
|
// 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 := te.ptree.repo.CatFileBatchCheck(te.ptree.repo.Ctx)
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-19 12:17:27 +00:00
|
|
|
// 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"
|
|
|
|
}
|
2018-11-28 07:00:25 +00:00
|
|
|
}
|
|
|
|
|
2018-05-01 07:04:36 +00:00
|
|
|
// FollowLink returns the entry pointed to by a symlink
|
2024-02-29 19:51:02 +00:00
|
|
|
func (te *TreeEntry) FollowLink() (*TreeEntry, string, error) {
|
2018-05-01 07:04:36 +00:00
|
|
|
if !te.IsLink() {
|
2024-02-29 19:51:02 +00:00
|
|
|
return nil, "", ErrBadLink{te.Name(), "not a symlink"}
|
2018-05-01 07:04:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// read the link
|
2019-04-19 12:17:27 +00:00
|
|
|
r, err := te.Blob().DataAsync()
|
2018-05-01 07:04:36 +00:00
|
|
|
if err != nil {
|
2024-02-29 19:51:02 +00:00
|
|
|
return nil, "", err
|
2018-05-01 07:04:36 +00:00
|
|
|
}
|
2021-05-10 01:27:03 +00:00
|
|
|
closed := false
|
|
|
|
defer func() {
|
|
|
|
if !closed {
|
|
|
|
_ = r.Close()
|
|
|
|
}
|
|
|
|
}()
|
2018-05-01 07:04:36 +00:00
|
|
|
buf := make([]byte, te.Size())
|
|
|
|
_, err = io.ReadFull(r, buf)
|
|
|
|
if err != nil {
|
2024-02-29 19:51:02 +00:00
|
|
|
return nil, "", err
|
2018-05-01 07:04:36 +00:00
|
|
|
}
|
2021-05-10 01:27:03 +00:00
|
|
|
_ = r.Close()
|
|
|
|
closed = true
|
2018-05-01 07:04:36 +00:00
|
|
|
|
|
|
|
lnk := string(buf)
|
|
|
|
t := te.ptree
|
|
|
|
|
|
|
|
// traverse up directories
|
|
|
|
for ; t != nil && strings.HasPrefix(lnk, "../"); lnk = lnk[3:] {
|
|
|
|
t = t.ptree
|
|
|
|
}
|
|
|
|
|
|
|
|
if t == nil {
|
2024-02-29 19:51:02 +00:00
|
|
|
return nil, "", ErrBadLink{te.Name(), "points outside of repo"}
|
2018-05-01 07:04:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
target, err := t.GetTreeEntryByPath(lnk)
|
|
|
|
if err != nil {
|
|
|
|
if IsErrNotExist(err) {
|
2024-02-29 19:51:02 +00:00
|
|
|
return nil, "", ErrBadLink{te.Name(), "broken link"}
|
2018-05-01 07:04:36 +00:00
|
|
|
}
|
2024-02-29 19:51:02 +00:00
|
|
|
return nil, "", err
|
2018-05-01 07:04:36 +00:00
|
|
|
}
|
2024-02-29 19:51:02 +00:00
|
|
|
return target, lnk, nil
|
2018-05-01 07:04:36 +00:00
|
|
|
}
|
|
|
|
|
2020-02-21 23:04:20 +00:00
|
|
|
// FollowLinks returns the entry ultimately pointed to by a symlink
|
2024-02-29 19:51:02 +00:00
|
|
|
func (te *TreeEntry) FollowLinks() (*TreeEntry, string, error) {
|
2020-02-21 23:04:20 +00:00
|
|
|
if !te.IsLink() {
|
2024-02-29 19:51:02 +00:00
|
|
|
return nil, "", ErrBadLink{te.Name(), "not a symlink"}
|
2020-02-21 23:04:20 +00:00
|
|
|
}
|
|
|
|
entry := te
|
2024-02-29 19:51:02 +00:00
|
|
|
entryLink := ""
|
2020-02-21 23:04:20 +00:00
|
|
|
for i := 0; i < 999; i++ {
|
|
|
|
if entry.IsLink() {
|
2024-02-29 19:51:02 +00:00
|
|
|
next, link, err := entry.FollowLink()
|
|
|
|
entryLink = link
|
2020-02-21 23:04:20 +00:00
|
|
|
if err != nil {
|
2024-02-29 19:51:02 +00:00
|
|
|
return nil, "", err
|
2020-02-21 23:04:20 +00:00
|
|
|
}
|
|
|
|
if next.ID == entry.ID {
|
2024-02-29 19:51:02 +00:00
|
|
|
return nil, "", ErrBadLink{
|
2020-02-21 23:04:20 +00:00
|
|
|
entry.Name(),
|
|
|
|
"recursive link",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
entry = next
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if entry.IsLink() {
|
2024-02-29 19:51:02 +00:00
|
|
|
return nil, "", ErrBadLink{
|
2020-02-21 23:04:20 +00:00
|
|
|
te.Name(),
|
|
|
|
"too many levels of symbolic links",
|
|
|
|
}
|
|
|
|
}
|
2024-02-29 19:51:02 +00:00
|
|
|
return entry, entryLink, nil
|
2020-02-21 23:04:20 +00:00
|
|
|
}
|
|
|
|
|
2023-02-14 21:23:04 +00:00
|
|
|
// returns the Tree pointed to by this TreeEntry, or nil if this is not a tree
|
2023-02-12 07:08:10 +00:00
|
|
|
func (te *TreeEntry) Tree() *Tree {
|
|
|
|
t, err := te.ptree.repo.getTree(te.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2023-02-14 21:23:04 +00:00
|
|
|
t.ptree = te.ptree
|
2023-02-12 07:08:10 +00:00
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2016-12-28 16:35:52 +00:00
|
|
|
// GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory )
|
|
|
|
func (te *TreeEntry) GetSubJumpablePathName() string {
|
|
|
|
if te.IsSubModule() || !te.IsDir() {
|
|
|
|
return ""
|
|
|
|
}
|
2019-04-19 12:17:27 +00:00
|
|
|
tree, err := te.ptree.SubTree(te.Name())
|
2016-12-28 16:35:52 +00:00
|
|
|
if err != nil {
|
2019-04-19 12:17:27 +00:00
|
|
|
return te.Name()
|
2016-12-28 16:35:52 +00:00
|
|
|
}
|
|
|
|
entries, _ := tree.ListEntries()
|
|
|
|
if len(entries) == 1 && entries[0].IsDir() {
|
|
|
|
name := entries[0].GetSubJumpablePathName()
|
|
|
|
if name != "" {
|
2019-04-19 12:17:27 +00:00
|
|
|
return te.Name() + "/" + name
|
2016-12-28 16:35:52 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-19 12:17:27 +00:00
|
|
|
return te.Name()
|
2016-12-28 16:35:52 +00:00
|
|
|
}
|
|
|
|
|
2016-12-22 09:30:52 +00:00
|
|
|
// Entries a list of entry
|
2016-11-03 22:16:01 +00:00
|
|
|
type Entries []*TreeEntry
|
|
|
|
|
2017-09-19 08:37:03 +00:00
|
|
|
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 {
|
2016-11-03 22:16:01 +00:00
|
|
|
return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
|
|
|
|
},
|
2017-09-19 08:37:03 +00:00
|
|
|
func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
|
2019-04-19 12:17:27 +00:00
|
|
|
return cmp(t1.Name(), t2.Name())
|
2016-11-03 22:16:01 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2017-09-19 08:37:03 +00:00
|
|
|
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]
|
2016-11-03 22:16:01 +00:00
|
|
|
var k int
|
|
|
|
for k = 0; k < len(sorter)-1; k++ {
|
2016-11-12 11:09:25 +00:00
|
|
|
s := sorter[k]
|
2016-11-03 22:16:01 +00:00
|
|
|
switch {
|
2017-09-19 08:37:03 +00:00
|
|
|
case s(t1, t2, ctes.Comparer):
|
2016-11-03 22:16:01 +00:00
|
|
|
return true
|
2017-09-19 08:37:03 +00:00
|
|
|
case s(t2, t1, ctes.Comparer):
|
2016-11-03 22:16:01 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2017-09-19 08:37:03 +00:00
|
|
|
return sorter[k](t1, t2, ctes.Comparer)
|
2016-11-03 22:16:01 +00:00
|
|
|
}
|
|
|
|
|
2016-12-22 09:30:52 +00:00
|
|
|
// Sort sort the list of entry
|
2016-11-03 22:16:01 +00:00
|
|
|
func (tes Entries) Sort() {
|
2017-09-19 08:37:03 +00:00
|
|
|
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})
|
2016-11-03 22:16:01 +00:00
|
|
|
}
|