Skip to content

Commit 3e0c740

Browse files
committed
Merge branch 'feature/enhanced-workflow-runs-api' of github.com:bdruth/gitea into feature/enhanced-workflow-runs-api
* 'feature/enhanced-workflow-runs-api' of github.com:bdruth/gitea: [skip ci] Updated translations via Crowdin Follow file symlinks in the UI to their target (go-gitea#28835) Fix issue filter (go-gitea#34914) Fix: RPM package download routing & missing package version count (go-gitea#34909) Add support for 3D/CAD file formats preview (go-gitea#34794) Add a `login`/`login-name`/`username` disambiguation to affected endpoint parameters and response/request models (go-gitea#34901) Improve tags list page (go-gitea#34898) [skip ci] Updated translations via Crowdin docs: fix typo in pull request merge warning message text (go-gitea#34899) Refactor container package (go-gitea#34877) [skip ci] Updated translations via Crowdin
2 parents 6b074cb + ac87911 commit 3e0c740

File tree

99 files changed

+1238
-960
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+1238
-960
lines changed

models/db/context.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@ func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error
178178
return txWithNoCheck(parentCtx, f)
179179
}
180180

181+
// WithTx2 is similar to WithTx, but it has two return values: result and error.
182+
func WithTx2[T any](parentCtx context.Context, f func(ctx context.Context) (T, error)) (ret T, errRet error) {
183+
errRet = WithTx(parentCtx, func(ctx context.Context) (errInner error) {
184+
ret, errInner = f(ctx)
185+
return errInner
186+
})
187+
return ret, errRet
188+
}
189+
181190
func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error) error {
182191
sess := xormEngine.NewSession()
183192
defer sess.Close()

modules/fileicon/entry.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ package fileicon
66
import "code.gitea.io/gitea/modules/git"
77

88
type EntryInfo struct {
9-
FullName string
9+
BaseName string
1010
EntryMode git.EntryMode
1111
SymlinkToMode git.EntryMode
1212
IsOpen bool
1313
}
1414

15-
func EntryInfoFromGitTreeEntry(gitEntry *git.TreeEntry) *EntryInfo {
16-
ret := &EntryInfo{FullName: gitEntry.Name(), EntryMode: gitEntry.Mode()}
15+
func EntryInfoFromGitTreeEntry(commit *git.Commit, fullPath string, gitEntry *git.TreeEntry) *EntryInfo {
16+
ret := &EntryInfo{BaseName: gitEntry.Name(), EntryMode: gitEntry.Mode()}
1717
if gitEntry.IsLink() {
18-
if te, err := gitEntry.FollowLink(); err == nil && te.IsDir() {
19-
ret.SymlinkToMode = te.Mode()
18+
if res, err := git.EntryFollowLink(commit, fullPath, gitEntry); err == nil && res.TargetEntry.IsDir() {
19+
ret.SymlinkToMode = res.TargetEntry.Mode()
2020
}
2121
}
2222
return ret

modules/fileicon/material.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package fileicon
55

66
import (
77
"html/template"
8-
"path"
98
"strings"
109
"sync"
1110

@@ -134,7 +133,7 @@ func (m *MaterialIconProvider) FindIconName(entry *EntryInfo) string {
134133
return "folder-git"
135134
}
136135

137-
fileNameLower := strings.ToLower(path.Base(entry.FullName))
136+
fileNameLower := strings.ToLower(entry.BaseName)
138137
if entry.EntryMode.IsDir() {
139138
if s, ok := m.rules.FolderNames[fileNameLower]; ok {
140139
return s

modules/fileicon/material_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ func TestMain(m *testing.M) {
2020
func TestFindIconName(t *testing.T) {
2121
unittest.PrepareTestEnv(t)
2222
p := fileicon.DefaultMaterialIconProvider()
23-
assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.php", EntryMode: git.EntryModeBlob}))
24-
assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.PHP", EntryMode: git.EntryModeBlob}))
25-
assert.Equal(t, "javascript", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.js", EntryMode: git.EntryModeBlob}))
26-
assert.Equal(t, "visualstudio", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.vba", EntryMode: git.EntryModeBlob}))
23+
assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{BaseName: "foo.php", EntryMode: git.EntryModeBlob}))
24+
assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{BaseName: "foo.PHP", EntryMode: git.EntryModeBlob}))
25+
assert.Equal(t, "javascript", p.FindIconName(&fileicon.EntryInfo{BaseName: "foo.js", EntryMode: git.EntryModeBlob}))
26+
assert.Equal(t, "visualstudio", p.FindIconName(&fileicon.EntryInfo{BaseName: "foo.vba", EntryMode: git.EntryModeBlob}))
2727
}

modules/git/blob.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,22 @@ func (b *Blob) Name() string {
2222
return b.name
2323
}
2424

25-
// GetBlobContent Gets the limited content of the blob as raw text
26-
func (b *Blob) GetBlobContent(limit int64) (string, error) {
25+
// GetBlobBytes Gets the limited content of the blob
26+
func (b *Blob) GetBlobBytes(limit int64) ([]byte, error) {
2727
if limit <= 0 {
28-
return "", nil
28+
return nil, nil
2929
}
3030
dataRc, err := b.DataAsync()
3131
if err != nil {
32-
return "", err
32+
return nil, err
3333
}
3434
defer dataRc.Close()
35-
buf, err := util.ReadWithLimit(dataRc, int(limit))
35+
return util.ReadWithLimit(dataRc, int(limit))
36+
}
37+
38+
// GetBlobContent Gets the limited content of the blob as raw text
39+
func (b *Blob) GetBlobContent(limit int64) (string, error) {
40+
buf, err := b.GetBlobBytes(limit)
3641
return string(buf), err
3742
}
3843

@@ -99,11 +104,9 @@ loop:
99104

100105
// GuessContentType guesses the content type of the blob.
101106
func (b *Blob) GuessContentType() (typesniffer.SniffedType, error) {
102-
r, err := b.DataAsync()
107+
buf, err := b.GetBlobBytes(typesniffer.SniffContentSize)
103108
if err != nil {
104109
return typesniffer.SniffedType{}, err
105110
}
106-
defer r.Close()
107-
108-
return typesniffer.DetectContentTypeFromReader(r)
111+
return typesniffer.DetectContentType(buf), nil
109112
}

modules/git/commit.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import (
2020

2121
// Commit represents a git commit.
2222
type Commit struct {
23-
Tree
23+
Tree // FIXME: bad design, this field can be nil if the commit is from "last commit cache"
24+
2425
ID ObjectID // The ID of this commit object
2526
Author *Signature
2627
Committer *Signature

modules/git/error.go

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,6 @@ func (err ErrNotExist) Unwrap() error {
3232
return util.ErrNotExist
3333
}
3434

35-
// ErrSymlinkUnresolved entry.FollowLink error
36-
type ErrSymlinkUnresolved struct {
37-
Name string
38-
Message string
39-
}
40-
41-
func (err ErrSymlinkUnresolved) Error() string {
42-
return fmt.Sprintf("%s: %s", err.Name, err.Message)
43-
}
44-
45-
// IsErrSymlinkUnresolved if some error is ErrSymlinkUnresolved
46-
func IsErrSymlinkUnresolved(err error) bool {
47-
_, ok := err.(ErrSymlinkUnresolved)
48-
return ok
49-
}
50-
5135
// ErrBranchNotExist represents a "BranchNotExist" kind of error.
5236
type ErrBranchNotExist struct {
5337
Name string

modules/git/tree_blob_nogogit.go

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
// GetTreeEntryByPath get the tree entries according the sub dir
14-
func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
14+
func (t *Tree) GetTreeEntryByPath(relpath string) (_ *TreeEntry, err error) {
1515
if len(relpath) == 0 {
1616
return &TreeEntry{
1717
ptree: t,
@@ -21,27 +21,25 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
2121
}, nil
2222
}
2323

24-
// FIXME: This should probably use git cat-file --batch to be a bit more efficient
2524
relpath = path.Clean(relpath)
2625
parts := strings.Split(relpath, "/")
27-
var err error
26+
2827
tree := t
29-
for i, name := range parts {
30-
if i == len(parts)-1 {
31-
entries, err := tree.ListEntries()
32-
if err != nil {
33-
return nil, err
34-
}
35-
for _, v := range entries {
36-
if v.Name() == name {
37-
return v, nil
38-
}
39-
}
40-
} else {
41-
tree, err = tree.SubTree(name)
42-
if err != nil {
43-
return nil, err
44-
}
28+
for _, name := range parts[:len(parts)-1] {
29+
tree, err = tree.SubTree(name)
30+
if err != nil {
31+
return nil, err
32+
}
33+
}
34+
35+
name := parts[len(parts)-1]
36+
entries, err := tree.ListEntries()
37+
if err != nil {
38+
return nil, err
39+
}
40+
for _, v := range entries {
41+
if v.Name() == name {
42+
return v, nil
4543
}
4644
}
4745
return nil, ErrNotExist{"", relpath}

modules/git/tree_entry.go

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
package git
66

77
import (
8-
"io"
8+
"path"
99
"sort"
1010
"strings"
1111

@@ -24,77 +24,57 @@ func (te *TreeEntry) Type() string {
2424
}
2525
}
2626

27-
// FollowLink returns the entry pointed to by a symlink
28-
func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
27+
type EntryFollowResult struct {
28+
SymlinkContent string
29+
TargetFullPath string
30+
TargetEntry *TreeEntry
31+
}
32+
33+
func EntryFollowLink(commit *Commit, fullPath string, te *TreeEntry) (*EntryFollowResult, error) {
2934
if !te.IsLink() {
30-
return nil, ErrSymlinkUnresolved{te.Name(), "not a symlink"}
35+
return nil, util.ErrorWrap(util.ErrUnprocessableContent, "%q is not a symlink", fullPath)
3136
}
3237

33-
// read the link
34-
r, err := te.Blob().DataAsync()
35-
if err != nil {
36-
return nil, err
38+
// git's filename max length is 4096, hopefully a link won't be longer than multiple of that
39+
const maxSymlinkSize = 20 * 4096
40+
if te.Blob().Size() > maxSymlinkSize {
41+
return nil, util.ErrorWrap(util.ErrUnprocessableContent, "%q content exceeds symlink limit", fullPath)
3742
}
38-
closed := false
39-
defer func() {
40-
if !closed {
41-
_ = r.Close()
42-
}
43-
}()
44-
buf := make([]byte, te.Size())
45-
_, err = io.ReadFull(r, buf)
43+
44+
link, err := te.Blob().GetBlobContent(maxSymlinkSize)
4645
if err != nil {
4746
return nil, err
4847
}
49-
_ = r.Close()
50-
closed = true
51-
52-
lnk := string(buf)
53-
t := te.ptree
54-
55-
// traverse up directories
56-
for ; t != nil && strings.HasPrefix(lnk, "../"); lnk = lnk[3:] {
57-
t = t.ptree
48+
if strings.HasPrefix(link, "/") {
49+
// It's said that absolute path will be stored as is in Git
50+
return &EntryFollowResult{SymlinkContent: link}, util.ErrorWrap(util.ErrUnprocessableContent, "%q is an absolute symlink", fullPath)
5851
}
5952

60-
if t == nil {
61-
return nil, ErrSymlinkUnresolved{te.Name(), "points outside of repo"}
62-
}
63-
64-
target, err := t.GetTreeEntryByPath(lnk)
53+
targetFullPath := path.Join(path.Dir(fullPath), link)
54+
targetEntry, err := commit.GetTreeEntryByPath(targetFullPath)
6555
if err != nil {
66-
if IsErrNotExist(err) {
67-
return nil, ErrSymlinkUnresolved{te.Name(), "broken link"}
68-
}
69-
return nil, err
56+
return &EntryFollowResult{SymlinkContent: link}, err
7057
}
71-
return target, nil
58+
return &EntryFollowResult{SymlinkContent: link, TargetFullPath: targetFullPath, TargetEntry: targetEntry}, nil
7259
}
7360

74-
// FollowLinks returns the entry ultimately pointed to by a symlink
75-
func (te *TreeEntry) FollowLinks(optLimit ...int) (*TreeEntry, error) {
76-
if !te.IsLink() {
77-
return nil, ErrSymlinkUnresolved{te.Name(), "not a symlink"}
78-
}
61+
func EntryFollowLinks(commit *Commit, firstFullPath string, firstTreeEntry *TreeEntry, optLimit ...int) (res *EntryFollowResult, err error) {
7962
limit := util.OptionalArg(optLimit, 10)
80-
entry := te
63+
treeEntry, fullPath := firstTreeEntry, firstFullPath
8164
for range limit {
82-
if !entry.IsLink() {
83-
break
84-
}
85-
next, err := entry.FollowLink()
65+
res, err = EntryFollowLink(commit, fullPath, treeEntry)
8666
if err != nil {
87-
return nil, err
67+
return res, err
8868
}
89-
if next.ID == entry.ID {
90-
return nil, ErrSymlinkUnresolved{entry.Name(), "recursive link"}
69+
treeEntry, fullPath = res.TargetEntry, res.TargetFullPath
70+
if !treeEntry.IsLink() {
71+
break
9172
}
92-
entry = next
9373
}
94-
if entry.IsLink() {
95-
return nil, ErrSymlinkUnresolved{te.Name(), "too many levels of symbolic links"}
74+
if treeEntry.IsLink() {
75+
return res, util.ErrorWrap(util.ErrUnprocessableContent, "%q has too many links", firstFullPath)
9676
}
97-
return entry, nil
77+
return res, nil
9878
}
9979

10080
// returns the Tree pointed to by this TreeEntry, or nil if this is not a tree

modules/git/tree_entry_common_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package git
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/modules/util"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestFollowLink(t *testing.T) {
16+
r, err := openRepositoryWithDefaultContext("tests/repos/repo1_bare")
17+
require.NoError(t, err)
18+
defer r.Close()
19+
20+
commit, err := r.GetCommit("37991dec2c8e592043f47155ce4808d4580f9123")
21+
require.NoError(t, err)
22+
23+
// get the symlink
24+
{
25+
lnkFullPath := "foo/bar/link_to_hello"
26+
lnk, err := commit.Tree.GetTreeEntryByPath("foo/bar/link_to_hello")
27+
require.NoError(t, err)
28+
assert.True(t, lnk.IsLink())
29+
30+
// should be able to dereference to target
31+
res, err := EntryFollowLink(commit, lnkFullPath, lnk)
32+
require.NoError(t, err)
33+
assert.Equal(t, "hello", res.TargetEntry.Name())
34+
assert.Equal(t, "foo/nar/hello", res.TargetFullPath)
35+
assert.False(t, res.TargetEntry.IsLink())
36+
assert.Equal(t, "b14df6442ea5a1b382985a6549b85d435376c351", res.TargetEntry.ID.String())
37+
}
38+
39+
{
40+
// should error when called on a normal file
41+
entry, err := commit.Tree.GetTreeEntryByPath("file1.txt")
42+
require.NoError(t, err)
43+
res, err := EntryFollowLink(commit, "file1.txt", entry)
44+
assert.ErrorIs(t, err, util.ErrUnprocessableContent)
45+
assert.Nil(t, res)
46+
}
47+
48+
{
49+
// should error for broken links
50+
entry, err := commit.Tree.GetTreeEntryByPath("foo/broken_link")
51+
require.NoError(t, err)
52+
assert.True(t, entry.IsLink())
53+
res, err := EntryFollowLink(commit, "foo/broken_link", entry)
54+
assert.ErrorIs(t, err, util.ErrNotExist)
55+
assert.Equal(t, "nar/broken_link", res.SymlinkContent)
56+
}
57+
58+
{
59+
// should error for external links
60+
entry, err := commit.Tree.GetTreeEntryByPath("foo/outside_repo")
61+
require.NoError(t, err)
62+
assert.True(t, entry.IsLink())
63+
res, err := EntryFollowLink(commit, "foo/outside_repo", entry)
64+
assert.ErrorIs(t, err, util.ErrNotExist)
65+
assert.Equal(t, "../../outside_repo", res.SymlinkContent)
66+
}
67+
68+
{
69+
// testing fix for short link bug
70+
entry, err := commit.Tree.GetTreeEntryByPath("foo/link_short")
71+
require.NoError(t, err)
72+
res, err := EntryFollowLink(commit, "foo/link_short", entry)
73+
assert.ErrorIs(t, err, util.ErrNotExist)
74+
assert.Equal(t, "a", res.SymlinkContent)
75+
}
76+
}

0 commit comments

Comments
 (0)