diff --git a/modules/base/natural_sort.go b/modules/base/natural_sort.go index acb9002276..d1ee7b04ec 100644 --- a/modules/base/natural_sort.go +++ b/modules/base/natural_sort.go @@ -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) } diff --git a/modules/base/natural_sort_test.go b/modules/base/natural_sort_test.go index b001bc4ac9..451aba6618 100644 --- a/modules/base/natural_sort_test.go +++ b/modules/base/natural_sort_test.go @@ -11,12 +11,10 @@ import ( func TestNaturalSortLess(t *testing.T) { testLess := func(s1, s2 string) { - assert.True(t, NaturalSortLess(s1, s2), "s1 2 { diff --git a/modules/git/parse_gogit.go b/modules/git/parse_gogit.go deleted file mode 100644 index 74d258de8e..0000000000 --- a/modules/git/parse_gogit.go +++ /dev/null @@ -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 " \t" - 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 -} diff --git a/modules/git/parse_gogit_test.go b/modules/git/parse_gogit_test.go deleted file mode 100644 index 3e171d7e56..0000000000 --- a/modules/git/parse_gogit_test.go +++ /dev/null @@ -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) - } -} diff --git a/modules/git/parse_nogogit.go b/modules/git/parse_treeentry.go similarity index 99% rename from modules/git/parse_nogogit.go rename to modules/git/parse_treeentry.go index 78a0162889..e14d9f17b5 100644 --- a/modules/git/parse_nogogit.go +++ b/modules/git/parse_treeentry.go @@ -1,8 +1,6 @@ // Copyright 2018 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !gogit - package git import ( diff --git a/modules/git/parse_nogogit_test.go b/modules/git/parse_treeentry_test.go similarity index 99% rename from modules/git/parse_nogogit_test.go rename to modules/git/parse_treeentry_test.go index 6594c84269..4223cbb3d7 100644 --- a/modules/git/parse_nogogit_test.go +++ b/modules/git/parse_treeentry_test.go @@ -1,8 +1,6 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !gogit - package git import ( diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go index 896d656039..c84aabde1a 100644 --- a/modules/git/repo_commit_gogit.go +++ b/modules/git/repo_commit_gogit.go @@ -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 } diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go index e15663a32a..89d34e87da 100644 --- a/modules/git/repo_tree_gogit.go +++ b/modules/git/repo_tree_gogit.go @@ -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 } diff --git a/modules/git/tree.go b/modules/git/tree.go index 9c73aec735..c1898b20cb 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -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{ - ID: id, - repo: repo, + TreeCommon: TreeCommon{ + ID: id, + repo: repo, + }, } } diff --git a/modules/git/tree_blob_gogit.go b/modules/git/tree_blob_gogit.go index f29e8f8b9e..2c0ff0e1b0 100644 --- a/modules/git/tree_blob_gogit.go +++ b/modules/git/tree_blob_gogit.go @@ -11,22 +11,16 @@ 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 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()), - }, + ID: t.ID, + ptree: t, + name: "", + entryMode: EntryModeTree, }, nil } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 5099d8ee79..e7e4ea2d5b 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -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()) + }) } diff --git a/modules/git/tree_entry_gogit.go b/modules/git/tree_entry_gogit.go index e6845f1c77..27877a2e28 100644 --- a/modules/git/tree_entry_gogit.go +++ b/modules/git/tree_entry_gogit.go @@ -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(), } diff --git a/modules/git/tree_entry_gogit_test.go b/modules/git/tree_entry_gogit_test.go new file mode 100644 index 0000000000..ed14b45e9e --- /dev/null +++ b/modules/git/tree_entry_gogit_test.go @@ -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)) + } +} diff --git a/modules/git/tree_entry_nogogit.go b/modules/git/tree_entry_nogogit.go index 8fad96cdf8..fd2f3c567f 100644 --- a/modules/git/tree_entry_nogogit.go +++ b/modules/git/tree_entry_nogogit.go @@ -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{ diff --git a/modules/git/tree_entry_test.go b/modules/git/tree_entry_test.go index 9ca82675e0..b28abfb545 100644 --- a/modules/git/tree_entry_test.go +++ b/modules/git/tree_entry_test.go @@ -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) } diff --git a/modules/git/tree_gogit.go b/modules/git/tree_gogit.go index 272b018ffd..fec6e2704e 100644 --- a/modules/git/tree_gogit.go +++ b/modules/git/tree_gogit.go @@ -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], - ptree: t, + 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() - if err != nil { - return nil, err - } +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 } diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go index 956a5938f0..d0ddb1d041 100644 --- a/modules/git/tree_nogogit.go +++ b/modules/git/tree_nogogit.go @@ -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 diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 0383e4ca9e..6bb9a8ae77 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -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) diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go index 340b2bc091..8a3ed0a1c9 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/treelist.go @@ -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 { diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 1d05a3aa51..79357bfd76 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -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 { diff --git a/routers/web/repo/view_readme.go b/routers/web/repo/view_readme.go index edf38b7892..f1fa5732f0 100644 --- a/routers/web/repo/view_readme.go +++ b/routers/web/repo/view_readme.go @@ -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()) { diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 289db11a4f..e7c34ba1d6 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -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 {