mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-10 13:31:01 -05:00
Refactor ls-tree and git path related problems (#35858)
Fix #35852, the root problem is that the "name" field is heavily abused (since #6816, and no way to get a clear fix) There are still a lot of legacy problems in old code. Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
@@ -41,8 +41,8 @@ func naturalSortAdvance(str string, pos int) (end int, isNumber bool) {
|
||||
return end, isNumber
|
||||
}
|
||||
|
||||
// NaturalSortLess compares two strings so that they could be sorted in natural order
|
||||
func NaturalSortLess(s1, s2 string) bool {
|
||||
// NaturalSortCompare compares two strings so that they could be sorted in natural order
|
||||
func NaturalSortCompare(s1, s2 string) int {
|
||||
// There is a bug in Golang's collate package: https://github.com/golang/go/issues/67997
|
||||
// text/collate: CompareString(collate.Numeric) returns wrong result for "0.0" vs "1.0" #67997
|
||||
// So we need to handle the number parts by ourselves
|
||||
@@ -55,16 +55,16 @@ func NaturalSortLess(s1, s2 string) bool {
|
||||
if isNum1 && isNum2 {
|
||||
if part1 != part2 {
|
||||
if len(part1) != len(part2) {
|
||||
return len(part1) < len(part2)
|
||||
return len(part1) - len(part2)
|
||||
}
|
||||
return part1 < part2
|
||||
return c.CompareString(part1, part2)
|
||||
}
|
||||
} else {
|
||||
if cmp := c.CompareString(part1, part2); cmp != 0 {
|
||||
return cmp < 0
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
pos1, pos2 = end1, end2
|
||||
}
|
||||
return len(s1) < len(s2)
|
||||
return len(s1) - len(s2)
|
||||
}
|
||||
|
||||
@@ -11,12 +11,10 @@ import (
|
||||
|
||||
func TestNaturalSortLess(t *testing.T) {
|
||||
testLess := func(s1, s2 string) {
|
||||
assert.True(t, NaturalSortLess(s1, s2), "s1<s2 should be true: s1=%q, s2=%q", s1, s2)
|
||||
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||
assert.Negative(t, NaturalSortCompare(s1, s2), "s1<s2 should be true: s1=%q, s2=%q", s1, s2)
|
||||
}
|
||||
testEqual := func(s1, s2 string) {
|
||||
assert.False(t, NaturalSortLess(s1, s2), "s1<s2 should be false: s1=%q, s2=%q", s1, s2)
|
||||
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||
assert.Zero(t, NaturalSortCompare(s1, s2), "s1<s2 should be false: s1=%q, s2=%q", s1, s2)
|
||||
}
|
||||
|
||||
testEqual("", "")
|
||||
|
||||
@@ -173,7 +173,6 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
|
||||
} else if entries, err = commit.Tree.ListEntries(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
entries.Sort()
|
||||
b.ResetTimer()
|
||||
b.Run(benchmark.name, func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
|
||||
@@ -7,6 +7,7 @@ package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -30,7 +31,11 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
|
||||
|
||||
remainingCommitID := commitID
|
||||
path := ""
|
||||
currentTree := notes.Tree.gogitTree
|
||||
currentTree, err := notes.Tree.gogitTreeObject()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get tree object for notes commit %q: %w", notes.ID.String(), err)
|
||||
}
|
||||
|
||||
log.Trace("Found tree with ID %q while searching for git note corresponding to the commit %q", currentTree.Entries[0].Name, commitID)
|
||||
var file *object.File
|
||||
for len(remainingCommitID) > 2 {
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build gogit
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||
"github.com/go-git/go-git/v5/plumbing/hash"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
// ParseTreeEntries parses the output of a `git ls-tree -l` command.
|
||||
func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
|
||||
return parseTreeEntries(data, nil)
|
||||
}
|
||||
|
||||
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
|
||||
entries := make([]*TreeEntry, 0, 10)
|
||||
for pos := 0; pos < len(data); {
|
||||
// expect line to be of the form "<mode> <type> <sha> <space-padded-size>\t<filename>"
|
||||
entry := new(TreeEntry)
|
||||
entry.gogitTreeEntry = &object.TreeEntry{}
|
||||
entry.ptree = ptree
|
||||
if pos+6 > len(data) {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
|
||||
}
|
||||
switch string(data[pos : pos+6]) {
|
||||
case "100644":
|
||||
entry.gogitTreeEntry.Mode = filemode.Regular
|
||||
pos += 12 // skip over "100644 blob "
|
||||
case "100755":
|
||||
entry.gogitTreeEntry.Mode = filemode.Executable
|
||||
pos += 12 // skip over "100755 blob "
|
||||
case "120000":
|
||||
entry.gogitTreeEntry.Mode = filemode.Symlink
|
||||
pos += 12 // skip over "120000 blob "
|
||||
case "160000":
|
||||
entry.gogitTreeEntry.Mode = filemode.Submodule
|
||||
pos += 14 // skip over "160000 object "
|
||||
case "040000":
|
||||
entry.gogitTreeEntry.Mode = filemode.Dir
|
||||
pos += 12 // skip over "040000 tree "
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6]))
|
||||
}
|
||||
|
||||
// in hex format, not byte format ....
|
||||
if pos+hash.Size*2 > len(data) {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
|
||||
}
|
||||
var err error
|
||||
entry.ID, err = NewIDFromString(string(data[pos : pos+hash.Size*2]))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid ls-tree output: %w", err)
|
||||
}
|
||||
entry.gogitTreeEntry.Hash = plumbing.Hash(entry.ID.RawValue())
|
||||
pos += 41 // skip over sha and trailing space
|
||||
|
||||
end := pos + bytes.IndexByte(data[pos:], '\t')
|
||||
if end < pos {
|
||||
return nil, fmt.Errorf("Invalid ls-tree -l output: %s", string(data))
|
||||
}
|
||||
entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(data[pos:end])), 10, 64)
|
||||
entry.sized = true
|
||||
|
||||
pos = end + 1
|
||||
|
||||
end = pos + bytes.IndexByte(data[pos:], '\n')
|
||||
if end < pos {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
|
||||
}
|
||||
|
||||
// In case entry name is surrounded by double quotes(it happens only in git-shell).
|
||||
if data[pos] == '"' {
|
||||
var err error
|
||||
entry.gogitTreeEntry.Name, err = strconv.Unquote(string(data[pos:end]))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %w", err)
|
||||
}
|
||||
} else {
|
||||
entry.gogitTreeEntry.Name = string(data[pos:end])
|
||||
}
|
||||
|
||||
pos = end + 1
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build gogit
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseTreeEntries(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Expected []*TreeEntry
|
||||
}{
|
||||
{
|
||||
Input: "",
|
||||
Expected: []*TreeEntry{},
|
||||
},
|
||||
{
|
||||
Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c 1022\texample/file2.txt\n",
|
||||
Expected: []*TreeEntry{
|
||||
{
|
||||
ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
|
||||
gogitTreeEntry: &object.TreeEntry{
|
||||
Hash: plumbing.Hash(MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()),
|
||||
Name: "example/file2.txt",
|
||||
Mode: filemode.Regular,
|
||||
},
|
||||
size: 1022,
|
||||
sized: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Input: "120000 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c 234131\t\"example/\\n.txt\"\n" +
|
||||
"040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8 -\texample\n",
|
||||
Expected: []*TreeEntry{
|
||||
{
|
||||
ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
|
||||
gogitTreeEntry: &object.TreeEntry{
|
||||
Hash: plumbing.Hash(MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()),
|
||||
Name: "example/\n.txt",
|
||||
Mode: filemode.Symlink,
|
||||
},
|
||||
size: 234131,
|
||||
sized: true,
|
||||
},
|
||||
{
|
||||
ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
|
||||
sized: true,
|
||||
gogitTreeEntry: &object.TreeEntry{
|
||||
Hash: plumbing.Hash(MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8").RawValue()),
|
||||
Name: "example",
|
||||
Mode: filemode.Dir,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
entries, err := ParseTreeEntries([]byte(testCase.Input))
|
||||
assert.NoError(t, err)
|
||||
if len(entries) > 1 {
|
||||
fmt.Println(testCase.Expected[0].ID)
|
||||
fmt.Println(entries[0].ID)
|
||||
}
|
||||
assert.EqualValues(t, testCase.Expected, entries)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !gogit
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !gogit
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
@@ -107,7 +107,7 @@ func (repo *Repository) getCommit(id ObjectID) (*Commit, error) {
|
||||
}
|
||||
|
||||
commit.Tree.ID = ParseGogitHash(tree.Hash)
|
||||
commit.Tree.gogitTree = tree
|
||||
commit.Tree.resolvedGogitTreeObject = tree
|
||||
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
|
||||
}
|
||||
|
||||
tree := NewTree(repo, id)
|
||||
tree.gogitTree = gogitTree
|
||||
tree.resolvedGogitTreeObject = gogitTree
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,21 @@ import (
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
)
|
||||
|
||||
type TreeCommon struct {
|
||||
ID ObjectID
|
||||
ResolvedID ObjectID
|
||||
|
||||
repo *Repository
|
||||
ptree *Tree // parent tree
|
||||
}
|
||||
|
||||
// NewTree create a new tree according the repository and tree id
|
||||
func NewTree(repo *Repository, id ObjectID) *Tree {
|
||||
return &Tree{
|
||||
TreeCommon: TreeCommon{
|
||||
ID: id,
|
||||
repo: repo,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
// GetTreeEntryByPath get the tree entries according the sub dir
|
||||
@@ -20,13 +18,9 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
|
||||
if len(relpath) == 0 {
|
||||
return &TreeEntry{
|
||||
ID: t.ID,
|
||||
// Type: ObjectTree,
|
||||
ptree: t,
|
||||
gogitTreeEntry: &object.TreeEntry{
|
||||
Name: "",
|
||||
Mode: filemode.Dir,
|
||||
Hash: plumbing.Hash(t.ID.RawValue()),
|
||||
},
|
||||
name: "",
|
||||
entryMode: EntryModeTree,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,60 @@ package git
|
||||
|
||||
import (
|
||||
"path"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// TreeEntry the leaf in the git tree
|
||||
type TreeEntry struct {
|
||||
ID ObjectID
|
||||
|
||||
name string
|
||||
ptree *Tree
|
||||
|
||||
entryMode EntryMode
|
||||
|
||||
size int64
|
||||
sized bool
|
||||
}
|
||||
|
||||
// Name returns the name of the entry (base name)
|
||||
func (te *TreeEntry) Name() string {
|
||||
return te.name
|
||||
}
|
||||
|
||||
// Mode returns the mode of the entry
|
||||
func (te *TreeEntry) Mode() EntryMode {
|
||||
return te.entryMode
|
||||
}
|
||||
|
||||
// IsSubModule if the entry is a submodule
|
||||
func (te *TreeEntry) IsSubModule() bool {
|
||||
return te.entryMode.IsSubModule()
|
||||
}
|
||||
|
||||
// IsDir if the entry is a sub dir
|
||||
func (te *TreeEntry) IsDir() bool {
|
||||
return te.entryMode.IsDir()
|
||||
}
|
||||
|
||||
// IsLink if the entry is a symlink
|
||||
func (te *TreeEntry) IsLink() bool {
|
||||
return te.entryMode.IsLink()
|
||||
}
|
||||
|
||||
// IsRegular if the entry is a regular file
|
||||
func (te *TreeEntry) IsRegular() bool {
|
||||
return te.entryMode.IsRegular()
|
||||
}
|
||||
|
||||
// IsExecutable if the entry is an executable file (not necessarily binary)
|
||||
func (te *TreeEntry) IsExecutable() bool {
|
||||
return te.entryMode.IsExecutable()
|
||||
}
|
||||
|
||||
// Type returns the type of the entry (commit, tree, blob)
|
||||
func (te *TreeEntry) Type() string {
|
||||
switch te.Mode() {
|
||||
@@ -109,49 +157,16 @@ func (te *TreeEntry) GetSubJumpablePathName() string {
|
||||
// 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})
|
||||
func (tes Entries) CustomSort(cmp func(s1, s2 string) int) {
|
||||
slices.SortFunc(tes, func(a, b *TreeEntry) int {
|
||||
s1Dir, s2Dir := a.IsDir() || a.IsSubModule(), b.IsDir() || b.IsSubModule()
|
||||
if s1Dir != s2Dir {
|
||||
if s1Dir {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return cmp(a.Name(), b.Name())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,25 +12,21 @@ import (
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
// TreeEntry the leaf in the git tree
|
||||
type TreeEntry struct {
|
||||
ID ObjectID
|
||||
|
||||
gogitTreeEntry *object.TreeEntry
|
||||
ptree *Tree
|
||||
|
||||
size int64
|
||||
sized bool
|
||||
// gogitFileModeToEntryMode converts go-git filemode to EntryMode
|
||||
func gogitFileModeToEntryMode(mode filemode.FileMode) EntryMode {
|
||||
return EntryMode(mode)
|
||||
}
|
||||
|
||||
// Name returns the name of the entry
|
||||
func (te *TreeEntry) Name() string {
|
||||
return te.gogitTreeEntry.Name
|
||||
func entryModeToGogitFileMode(mode EntryMode) filemode.FileMode {
|
||||
return filemode.FileMode(mode)
|
||||
}
|
||||
|
||||
// Mode returns the mode of the entry
|
||||
func (te *TreeEntry) Mode() EntryMode {
|
||||
return EntryMode(te.gogitTreeEntry.Mode)
|
||||
func (te *TreeEntry) toGogitTreeEntry() *object.TreeEntry {
|
||||
return &object.TreeEntry{
|
||||
Name: te.name,
|
||||
Mode: entryModeToGogitFileMode(te.entryMode),
|
||||
Hash: plumbing.Hash(te.ID.RawValue()),
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the size of the entry
|
||||
@@ -41,7 +37,11 @@ func (te *TreeEntry) Size() int64 {
|
||||
return te.size
|
||||
}
|
||||
|
||||
file, err := te.ptree.gogitTree.TreeEntryFile(te.gogitTreeEntry)
|
||||
ptreeGogitTree, err := te.ptree.gogitTreeObject()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
file, err := ptreeGogitTree.TreeEntryFile(te.toGogitTreeEntry())
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
@@ -51,40 +51,15 @@ func (te *TreeEntry) Size() int64 {
|
||||
return te.size
|
||||
}
|
||||
|
||||
// IsSubModule if the entry is a submodule
|
||||
func (te *TreeEntry) IsSubModule() bool {
|
||||
return te.gogitTreeEntry.Mode == filemode.Submodule
|
||||
}
|
||||
|
||||
// IsDir if the entry is a sub dir
|
||||
func (te *TreeEntry) IsDir() bool {
|
||||
return te.gogitTreeEntry.Mode == filemode.Dir
|
||||
}
|
||||
|
||||
// IsLink if the entry is a symlink
|
||||
func (te *TreeEntry) IsLink() bool {
|
||||
return te.gogitTreeEntry.Mode == filemode.Symlink
|
||||
}
|
||||
|
||||
// IsRegular if the entry is a regular file
|
||||
func (te *TreeEntry) IsRegular() bool {
|
||||
return te.gogitTreeEntry.Mode == filemode.Regular
|
||||
}
|
||||
|
||||
// IsExecutable if the entry is an executable file (not necessarily binary)
|
||||
func (te *TreeEntry) IsExecutable() bool {
|
||||
return te.gogitTreeEntry.Mode == filemode.Executable
|
||||
}
|
||||
|
||||
// Blob returns the blob object the entry
|
||||
func (te *TreeEntry) Blob() *Blob {
|
||||
encodedObj, err := te.ptree.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, te.gogitTreeEntry.Hash)
|
||||
encodedObj, err := te.ptree.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, te.toGogitTreeEntry().Hash)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Blob{
|
||||
ID: ParseGogitHash(te.gogitTreeEntry.Hash),
|
||||
ID: te.ID,
|
||||
gogitEncodedObj: encodedObj,
|
||||
name: te.Name(),
|
||||
}
|
||||
|
||||
27
modules/git/tree_entry_gogit_test.go
Normal file
27
modules/git/tree_entry_gogit_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build gogit
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEntryGogit(t *testing.T) {
|
||||
cases := map[EntryMode]filemode.FileMode{
|
||||
EntryModeBlob: filemode.Regular,
|
||||
EntryModeCommit: filemode.Submodule,
|
||||
EntryModeExec: filemode.Executable,
|
||||
EntryModeSymlink: filemode.Symlink,
|
||||
EntryModeTree: filemode.Dir,
|
||||
}
|
||||
for emode, fmode := range cases {
|
||||
assert.EqualValues(t, fmode, entryModeToGogitFileMode(emode))
|
||||
assert.EqualValues(t, emode, gogitFileModeToEntryMode(fmode))
|
||||
}
|
||||
}
|
||||
@@ -7,27 +7,6 @@ package git
|
||||
|
||||
import "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
|
||||
}
|
||||
|
||||
// Name returns the name of the entry (base name)
|
||||
func (te *TreeEntry) Name() string {
|
||||
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() {
|
||||
@@ -57,31 +36,6 @@ func (te *TreeEntry) Size() int64 {
|
||||
return te.size
|
||||
}
|
||||
|
||||
// IsSubModule if the entry is a submodule
|
||||
func (te *TreeEntry) IsSubModule() bool {
|
||||
return te.entryMode.IsSubModule()
|
||||
}
|
||||
|
||||
// IsDir if the entry is a sub dir
|
||||
func (te *TreeEntry) IsDir() bool {
|
||||
return te.entryMode.IsDir()
|
||||
}
|
||||
|
||||
// IsLink if the entry is a symlink
|
||||
func (te *TreeEntry) IsLink() bool {
|
||||
return te.entryMode.IsLink()
|
||||
}
|
||||
|
||||
// IsRegular if the entry is a regular file
|
||||
func (te *TreeEntry) IsRegular() bool {
|
||||
return te.entryMode.IsRegular()
|
||||
}
|
||||
|
||||
// IsExecutable if the entry is an executable file (not necessarily binary)
|
||||
func (te *TreeEntry) IsExecutable() bool {
|
||||
return te.entryMode.IsExecutable()
|
||||
}
|
||||
|
||||
// Blob returns the blob object the entry
|
||||
func (te *TreeEntry) Blob() *Blob {
|
||||
return &Blob{
|
||||
|
||||
@@ -1,55 +1,29 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build gogit
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func getTestEntries() Entries {
|
||||
return Entries{
|
||||
&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v1.0", Mode: filemode.Dir}},
|
||||
&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.0", Mode: filemode.Dir}},
|
||||
&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.1", Mode: filemode.Dir}},
|
||||
&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.12", Mode: filemode.Dir}},
|
||||
&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.2", Mode: filemode.Dir}},
|
||||
&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v12.0", Mode: filemode.Dir}},
|
||||
&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "abc", Mode: filemode.Regular}},
|
||||
&TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "bcd", Mode: filemode.Regular}},
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntriesSort(t *testing.T) {
|
||||
entries := getTestEntries()
|
||||
entries.Sort()
|
||||
assert.Equal(t, "v1.0", entries[0].Name())
|
||||
assert.Equal(t, "v12.0", entries[1].Name())
|
||||
assert.Equal(t, "v2.0", entries[2].Name())
|
||||
assert.Equal(t, "v2.1", entries[3].Name())
|
||||
assert.Equal(t, "v2.12", entries[4].Name())
|
||||
assert.Equal(t, "v2.2", entries[5].Name())
|
||||
assert.Equal(t, "abc", entries[6].Name())
|
||||
assert.Equal(t, "bcd", entries[7].Name())
|
||||
}
|
||||
|
||||
func TestEntriesCustomSort(t *testing.T) {
|
||||
entries := getTestEntries()
|
||||
entries.CustomSort(func(s1, s2 string) bool {
|
||||
return s1 > s2
|
||||
})
|
||||
assert.Equal(t, "v2.2", entries[0].Name())
|
||||
assert.Equal(t, "v2.12", entries[1].Name())
|
||||
assert.Equal(t, "v2.1", entries[2].Name())
|
||||
assert.Equal(t, "v2.0", entries[3].Name())
|
||||
assert.Equal(t, "v12.0", entries[4].Name())
|
||||
assert.Equal(t, "v1.0", entries[5].Name())
|
||||
assert.Equal(t, "bcd", entries[6].Name())
|
||||
assert.Equal(t, "abc", entries[7].Name())
|
||||
entries := Entries{
|
||||
&TreeEntry{name: "a-dir", entryMode: EntryModeTree},
|
||||
&TreeEntry{name: "a-submodule", entryMode: EntryModeCommit},
|
||||
&TreeEntry{name: "b-dir", entryMode: EntryModeTree},
|
||||
&TreeEntry{name: "b-submodule", entryMode: EntryModeCommit},
|
||||
&TreeEntry{name: "a-file", entryMode: EntryModeBlob},
|
||||
&TreeEntry{name: "b-file", entryMode: EntryModeBlob},
|
||||
}
|
||||
expected := slices.Clone(entries)
|
||||
rand.Shuffle(len(entries), func(i, j int) { entries[i], entries[j] = entries[j], entries[i] })
|
||||
assert.NotEqual(t, expected, entries)
|
||||
entries.CustomSort(strings.Compare)
|
||||
assert.Equal(t, expected, entries)
|
||||
}
|
||||
|
||||
@@ -15,41 +15,34 @@ import (
|
||||
|
||||
// Tree represents a flat directory listing.
|
||||
type Tree struct {
|
||||
ID ObjectID
|
||||
ResolvedID ObjectID
|
||||
repo *Repository
|
||||
TreeCommon
|
||||
|
||||
gogitTree *object.Tree
|
||||
|
||||
// parent tree
|
||||
ptree *Tree
|
||||
resolvedGogitTreeObject *object.Tree
|
||||
}
|
||||
|
||||
func (t *Tree) loadTreeObject() error {
|
||||
gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID.RawValue()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.gogitTree = gogitTree
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListEntries returns all entries of current tree.
|
||||
func (t *Tree) ListEntries() (Entries, error) {
|
||||
if t.gogitTree == nil {
|
||||
err := t.loadTreeObject()
|
||||
func (t *Tree) gogitTreeObject() (_ *object.Tree, err error) {
|
||||
if t.resolvedGogitTreeObject == nil {
|
||||
t.resolvedGogitTreeObject, err = t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID.RawValue()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return t.resolvedGogitTreeObject, nil
|
||||
}
|
||||
|
||||
entries := make([]*TreeEntry, len(t.gogitTree.Entries))
|
||||
for i, entry := range t.gogitTree.Entries {
|
||||
// ListEntries returns all entries of current tree.
|
||||
func (t *Tree) ListEntries() (Entries, error) {
|
||||
gogitTree, err := t.gogitTreeObject()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries := make([]*TreeEntry, len(gogitTree.Entries))
|
||||
for i, gogitTreeEntry := range gogitTree.Entries {
|
||||
entries[i] = &TreeEntry{
|
||||
ID: ParseGogitHash(entry.Hash),
|
||||
gogitTreeEntry: &t.gogitTree.Entries[i],
|
||||
ID: ParseGogitHash(gogitTreeEntry.Hash),
|
||||
ptree: t,
|
||||
name: gogitTreeEntry.Name,
|
||||
entryMode: gogitFileModeToEntryMode(gogitTreeEntry.Mode),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,37 +50,28 @@ func (t *Tree) ListEntries() (Entries, error) {
|
||||
}
|
||||
|
||||
// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees
|
||||
func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
|
||||
if t.gogitTree == nil {
|
||||
err := t.loadTreeObject()
|
||||
func (t *Tree) ListEntriesRecursiveWithSize() (entries Entries, _ error) {
|
||||
gogitTree, err := t.gogitTreeObject()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var entries []*TreeEntry
|
||||
seen := map[plumbing.Hash]bool{}
|
||||
walker := object.NewTreeWalker(t.gogitTree, true, seen)
|
||||
walker := object.NewTreeWalker(gogitTree, true, nil)
|
||||
for {
|
||||
_, entry, err := walker.Next()
|
||||
fullName, gogitTreeEntry, err := walker.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if seen[entry.Hash] {
|
||||
continue
|
||||
}
|
||||
|
||||
convertedEntry := &TreeEntry{
|
||||
ID: ParseGogitHash(entry.Hash),
|
||||
gogitTreeEntry: &entry,
|
||||
ptree: t,
|
||||
ID: ParseGogitHash(gogitTreeEntry.Hash),
|
||||
name: fullName, // FIXME: the "name" field is abused, here it is a full path
|
||||
ptree: t, // FIXME: this ptree is not right, fortunately it isn't really used
|
||||
entryMode: gogitFileModeToEntryMode(gogitTreeEntry.Mode),
|
||||
}
|
||||
entries = append(entries, convertedEntry)
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,18 +14,10 @@ import (
|
||||
|
||||
// Tree represents a flat directory listing.
|
||||
type Tree struct {
|
||||
ID ObjectID
|
||||
ResolvedID ObjectID
|
||||
repo *Repository
|
||||
|
||||
// parent tree
|
||||
ptree *Tree
|
||||
TreeCommon
|
||||
|
||||
entries Entries
|
||||
entriesParsed bool
|
||||
|
||||
entriesRecursive Entries
|
||||
entriesRecursiveParsed bool
|
||||
}
|
||||
|
||||
// ListEntries returns all entries of current tree.
|
||||
@@ -94,10 +86,6 @@ func (t *Tree) ListEntries() (Entries, error) {
|
||||
// listEntriesRecursive returns all entries of current tree recursively including all subtrees
|
||||
// extraArgs could be "-l" to get the size, which is slower
|
||||
func (t *Tree) listEntriesRecursive(extraArgs gitcmd.TrustedCmdArgs) (Entries, error) {
|
||||
if t.entriesRecursiveParsed {
|
||||
return t.entriesRecursive, nil
|
||||
}
|
||||
|
||||
stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-t", "-r").
|
||||
AddArguments(extraArgs...).
|
||||
AddDynamicArguments(t.ID.String()).
|
||||
@@ -107,13 +95,9 @@ func (t *Tree) listEntriesRecursive(extraArgs gitcmd.TrustedCmdArgs) (Entries, e
|
||||
return nil, runErr
|
||||
}
|
||||
|
||||
var err error
|
||||
t.entriesRecursive, err = parseTreeEntries(stdout, t)
|
||||
if err == nil {
|
||||
t.entriesRecursiveParsed = true
|
||||
}
|
||||
|
||||
return t.entriesRecursive, err
|
||||
// FIXME: the "name" field is abused, here it is a full path
|
||||
// FIXME: this ptree is not right, fortunately it isn't really used
|
||||
return parseTreeEntries(stdout, t)
|
||||
}
|
||||
|
||||
// ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size
|
||||
|
||||
@@ -415,6 +415,8 @@ func Diff(ctx *context.Context) {
|
||||
ctx.ServerError("PostProcessCommitMessage", err)
|
||||
return
|
||||
}
|
||||
} else if !git.IsErrNotExist(err) {
|
||||
log.Error("GetNote: %v", err)
|
||||
}
|
||||
|
||||
pr, _ := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, commitID)
|
||||
|
||||
@@ -33,7 +33,7 @@ func TreeList(ctx *context.Context) {
|
||||
ctx.ServerError("ListEntriesRecursiveFast", err)
|
||||
return
|
||||
}
|
||||
entries.CustomSort(base.NaturalSortLess)
|
||||
entries.CustomSort(base.NaturalSortCompare)
|
||||
|
||||
files := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
|
||||
@@ -307,7 +307,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
|
||||
ctx.ServerError("ListEntries", err)
|
||||
return nil
|
||||
}
|
||||
allEntries.CustomSort(base.NaturalSortLess)
|
||||
allEntries.CustomSort(base.NaturalSortCompare)
|
||||
|
||||
commitInfoCtx := gocontext.Context(ctx)
|
||||
if timeout > 0 {
|
||||
|
||||
@@ -67,7 +67,7 @@ func findReadmeFileInEntries(ctx *context.Context, parentDir string, entries []*
|
||||
for _, entry := range entries {
|
||||
if i, ok := util.IsReadmeFileExtension(entry.Name(), exts...); ok {
|
||||
fullPath := path.Join(parentDir, entry.Name())
|
||||
if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].Name(), entry.Blob().Name()) {
|
||||
if readmeFiles[i] == nil || base.NaturalSortCompare(readmeFiles[i].Name(), entry.Blob().Name()) < 0 {
|
||||
if entry.IsLink() {
|
||||
res, err := git.EntryFollowLinks(ctx.Repo.Commit, fullPath, entry)
|
||||
if err == nil && (res.TargetEntry.IsExecutable() || res.TargetEntry.IsRegular()) {
|
||||
|
||||
@@ -567,7 +567,7 @@ func WikiPages(ctx *context.Context) {
|
||||
ctx.ServerError("ListEntries", err)
|
||||
return
|
||||
}
|
||||
allEntries.CustomSort(base.NaturalSortLess)
|
||||
allEntries.CustomSort(base.NaturalSortCompare)
|
||||
|
||||
entries, _, err := allEntries.GetCommitsInfo(ctx, ctx.Repo.RepoLink, commit, treePath)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user