Skip to content

Commit a13e13d

Browse files
committed
fix
1 parent 0b8580f commit a13e13d

File tree

25 files changed

+268
-309
lines changed

25 files changed

+268
-309
lines changed

modules/fileicon/basic.go

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,17 @@ import (
1010
"code.gitea.io/gitea/modules/svg"
1111
)
1212

13-
type FileIcon struct {
14-
Name string
15-
Entry git.TreeEntry
16-
EntryMode git.EntryMode
17-
}
18-
19-
func BasicThemeFolderIconName(isOpen bool) string {
20-
if isOpen {
21-
return "octicon-file-directory-open-fill"
22-
}
23-
return "octicon-file-directory-fill"
24-
}
25-
26-
func BasicThemeFolderIcon(isOpen bool) template.HTML {
27-
return svg.RenderHTML(BasicThemeFolderIconName(isOpen))
28-
}
29-
30-
func BasicThemeIcon(file *FileIcon) template.HTML {
13+
func BasicThemeIcon(entry *git.TreeEntry) template.HTML {
3114
svgName := "octicon-file"
3215
switch {
33-
case file.EntryMode.IsLink():
16+
case entry.IsLink():
3417
svgName = "octicon-file-symlink-file"
35-
if te, err := file.Entry.FollowLink(); err == nil && te.IsDir() {
18+
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
3619
svgName = "octicon-file-directory-symlink"
3720
}
38-
case file.EntryMode.IsDir():
39-
svgName = BasicThemeFolderIconName(false)
40-
case file.EntryMode.IsSubModule():
21+
case entry.IsDir():
22+
svgName = "octicon-file-directory-fill"
23+
case entry.IsSubModule():
4124
svgName = "octicon-file-submodule"
4225
}
4326
return svg.RenderHTML(svgName)

modules/fileicon/material.go

Lines changed: 25 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import (
99
"strings"
1010
"sync"
1111

12+
"code.gitea.io/gitea/modules/git"
1213
"code.gitea.io/gitea/modules/json"
1314
"code.gitea.io/gitea/modules/log"
1415
"code.gitea.io/gitea/modules/options"
15-
"code.gitea.io/gitea/modules/reqctx"
1616
"code.gitea.io/gitea/modules/svg"
1717
)
1818

@@ -61,70 +61,49 @@ func (m *MaterialIconProvider) loadData() {
6161
log.Debug("Loaded material icon rules and SVG images")
6262
}
6363

64-
func (m *MaterialIconProvider) renderFileIconSVG(ctx reqctx.RequestContext, name, svg, extraClass string) template.HTML {
65-
data := ctx.GetData()
66-
renderedSVGs, _ := data["_RenderedSVGs"].(map[string]bool)
67-
if renderedSVGs == nil {
68-
renderedSVGs = make(map[string]bool)
69-
data["_RenderedSVGs"] = renderedSVGs
70-
}
64+
func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg, extraClass string) template.HTML {
7165
// This part is a bit hacky, but it works really well. It should be safe to do so because all SVG icons are generated by us.
7266
// Will try to refactor this in the future.
7367
if !strings.HasPrefix(svg, "<svg") {
7468
panic("Invalid SVG icon")
7569
}
7670
svgID := "svg-mfi-" + name
7771
svgCommonAttrs := `class="svg git-entry-icon ` + extraClass + `" width="16" height="16" aria-hidden="true"`
78-
posOuterBefore := strings.IndexByte(svg, '>')
79-
if renderedSVGs[svgID] && posOuterBefore != -1 {
80-
return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
72+
if p.IconSVGs[svgID] == "" {
73+
p.IconSVGs[svgID] = template.HTML(`<svg id="` + svgID + `" ` + svgCommonAttrs + svg[4:])
8174
}
82-
svg = `<svg id="` + svgID + `" ` + svgCommonAttrs + svg[4:]
83-
renderedSVGs[svgID] = true
84-
return template.HTML(svg)
75+
return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
8576
}
8677

87-
func (m *MaterialIconProvider) FolderIcon(ctx reqctx.RequestContext, isOpen bool) template.HTML {
88-
iconName := "folder"
89-
if isOpen {
90-
iconName = "folder-open"
91-
}
92-
return m.renderIconByName(ctx, iconName, BasicThemeFolderIconName(isOpen))
93-
}
94-
95-
func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, file *FileIcon) template.HTML {
78+
func (m *MaterialIconProvider) FileIcon(p *RenderedIconPool, entry *git.TreeEntry) template.HTML {
9679
if m.rules == nil {
97-
return BasicThemeIcon(file)
80+
return BasicThemeIcon(entry)
9881
}
9982

100-
if file.EntryMode.IsLink() {
101-
if te, err := file.Entry.FollowLink(); err == nil && te.IsDir() {
83+
if entry.IsLink() {
84+
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
10285
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
10386
return svg.RenderHTML("material-folder-symlink", 16, "octicon-file-directory-symlink")
10487
}
10588
return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them
10689
}
10790

108-
name := m.findIconNameByGit(file)
109-
110-
extraClass := "octicon-file"
111-
switch {
112-
case file.EntryMode.IsDir():
113-
extraClass = BasicThemeFolderIconName(false)
114-
case file.EntryMode.IsSubModule():
115-
extraClass = "octicon-file-submodule"
116-
}
117-
118-
return m.renderIconByName(ctx, name, extraClass)
119-
}
120-
121-
func (m *MaterialIconProvider) renderIconByName(ctx reqctx.RequestContext, name, extraClass string) template.HTML {
122-
if iconSVG, ok := m.svgs[name]; ok && iconSVG != "" {
91+
name := m.findIconNameByGit(entry)
92+
// the material icon pack's "folder" icon doesn't look good, so use our built-in one
93+
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
94+
if iconSVG, ok := m.svgs[name]; ok && name != "folder" && iconSVG != "" {
12395
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
124-
return m.renderFileIconSVG(ctx, name, iconSVG, extraClass)
96+
extraClass := "octicon-file"
97+
switch {
98+
case entry.IsDir():
99+
extraClass = "octicon-file-directory-fill"
100+
case entry.IsSubModule():
101+
extraClass = "octicon-file-submodule"
102+
}
103+
return m.renderFileIconSVG(p, name, iconSVG, extraClass)
125104
}
126105
// TODO: use an interface or wrapper for git.Entry to make the code testable.
127-
return svg.RenderHTML("octicon-file")
106+
return BasicThemeIcon(entry)
128107
}
129108

130109
func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
@@ -168,9 +147,9 @@ func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
168147
return "file"
169148
}
170149

171-
func (m *MaterialIconProvider) findIconNameByGit(file *FileIcon) string {
172-
if file.EntryMode.IsSubModule() {
150+
func (m *MaterialIconProvider) findIconNameByGit(entry *git.TreeEntry) string {
151+
if entry.IsSubModule() {
173152
return "folder-git"
174153
}
175-
return m.FindIconName(file.Name, file.EntryMode.IsDir())
154+
return m.FindIconName(entry.Name(), entry.IsDir())
176155
}

modules/fileicon/render.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package fileicon
5+
6+
import (
7+
"html/template"
8+
"strings"
9+
10+
"code.gitea.io/gitea/modules/git"
11+
"code.gitea.io/gitea/modules/setting"
12+
)
13+
14+
type RenderedIconPool struct {
15+
IconSVGs map[string]template.HTML
16+
}
17+
18+
func NewRenderedIconPool() *RenderedIconPool {
19+
return &RenderedIconPool{
20+
IconSVGs: make(map[string]template.HTML),
21+
}
22+
}
23+
24+
func (p *RenderedIconPool) RenderToHTML() template.HTML {
25+
if len(p.IconSVGs) == 0 {
26+
return ""
27+
}
28+
sb := &strings.Builder{}
29+
sb.WriteString(`<div class=tw-hidden>`)
30+
for _, icon := range p.IconSVGs {
31+
sb.WriteString(string(icon))
32+
}
33+
sb.WriteString(`</div>`)
34+
return template.HTML(sb.String())
35+
}
36+
37+
func RenderEntryIcon(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML {
38+
if setting.UI.FileIconTheme == "material" {
39+
return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry)
40+
}
41+
return BasicThemeIcon(entry)
42+
}
43+
44+
func RenderEntryIconOpen(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML {
45+
// TODO: add "open icon" support
46+
if setting.UI.FileIconTheme == "material" {
47+
return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry)
48+
}
49+
return BasicThemeIcon(entry)
50+
}

modules/git/error.go

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

35-
// ErrBadLink entry.FollowLink error
36-
type ErrBadLink struct {
35+
// ErrSymlinkUnresolved entry.FollowLink error
36+
type ErrSymlinkUnresolved struct {
3737
Name string
3838
Message string
3939
}
4040

41-
func (err ErrBadLink) Error() string {
41+
func (err ErrSymlinkUnresolved) Error() string {
4242
return fmt.Sprintf("%s: %s", err.Name, err.Message)
4343
}
4444

45-
// IsErrBadLink if some error is ErrBadLink
46-
func IsErrBadLink(err error) bool {
47-
_, ok := err.(ErrBadLink)
45+
// IsErrSymlinkUnresolved if some error is ErrSymlinkUnresolved
46+
func IsErrSymlinkUnresolved(err error) bool {
47+
_, ok := err.(ErrSymlinkUnresolved)
4848
return ok
4949
}
5050

modules/git/tree_entry.go

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"io"
99
"sort"
1010
"strings"
11+
12+
"code.gitea.io/gitea/modules/util"
1113
)
1214

1315
// Type returns the type of the entry (commit, tree, blob)
@@ -25,7 +27,7 @@ func (te *TreeEntry) Type() string {
2527
// FollowLink returns the entry pointed to by a symlink
2628
func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
2729
if !te.IsLink() {
28-
return nil, ErrBadLink{te.Name(), "not a symlink"}
30+
return nil, ErrSymlinkUnresolved{te.Name(), "not a symlink"}
2931
}
3032

3133
// read the link
@@ -56,47 +58,41 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
5658
}
5759

5860
if t == nil {
59-
return nil, ErrBadLink{te.Name(), "points outside of repo"}
61+
return nil, ErrSymlinkUnresolved{te.Name(), "points outside of repo"}
6062
}
6163

6264
target, err := t.GetTreeEntryByPath(lnk)
6365
if err != nil {
6466
if IsErrNotExist(err) {
65-
return nil, ErrBadLink{te.Name(), "broken link"}
67+
return nil, ErrSymlinkUnresolved{te.Name(), "broken link"}
6668
}
6769
return nil, err
6870
}
6971
return target, nil
7072
}
7173

7274
// FollowLinks returns the entry ultimately pointed to by a symlink
73-
func (te *TreeEntry) FollowLinks() (*TreeEntry, error) {
75+
func (te *TreeEntry) FollowLinks(optLimit ...int) (*TreeEntry, error) {
7476
if !te.IsLink() {
75-
return nil, ErrBadLink{te.Name(), "not a symlink"}
77+
return nil, ErrSymlinkUnresolved{te.Name(), "not a symlink"}
7678
}
79+
limit := util.OptionalArg(optLimit, 10)
7780
entry := te
78-
for i := 0; i < 999; i++ {
79-
if entry.IsLink() {
80-
next, err := entry.FollowLink()
81-
if err != nil {
82-
return nil, err
83-
}
84-
if next.ID == entry.ID {
85-
return nil, ErrBadLink{
86-
entry.Name(),
87-
"recursive link",
88-
}
89-
}
90-
entry = next
91-
} else {
81+
for i := 0; i < limit; i++ {
82+
if !entry.IsLink() {
9283
break
9384
}
85+
next, err := entry.FollowLink()
86+
if err != nil {
87+
return nil, err
88+
}
89+
if next.ID == entry.ID {
90+
return nil, ErrSymlinkUnresolved{entry.Name(), "recursive link"}
91+
}
92+
entry = next
9493
}
9594
if entry.IsLink() {
96-
return nil, ErrBadLink{
97-
te.Name(),
98-
"too many levels of symbolic links",
99-
}
95+
return nil, ErrSymlinkUnresolved{te.Name(), "too many levels of symbolic links"}
10096
}
10197
return entry, nil
10298
}

modules/git/tree_entry_mode.go

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,49 +17,19 @@ const (
1717
// EntryModeNoEntry is possible if the file was added or removed in a commit. In the case of
1818
// added the base commit will not have the file in its tree so a mode of 0o000000 is used.
1919
EntryModeNoEntry EntryMode = 0o000000
20-
// EntryModeBlob
21-
EntryModeBlob EntryMode = 0o100644
22-
// EntryModeExec
23-
EntryModeExec EntryMode = 0o100755
24-
// EntryModeSymlink
20+
21+
EntryModeBlob EntryMode = 0o100644
22+
EntryModeExec EntryMode = 0o100755
2523
EntryModeSymlink EntryMode = 0o120000
26-
// EntryModeCommit
27-
EntryModeCommit EntryMode = 0o160000
28-
// EntryModeTree
29-
EntryModeTree EntryMode = 0o040000
24+
EntryModeCommit EntryMode = 0o160000
25+
EntryModeTree EntryMode = 0o040000
3026
)
3127

3228
// String converts an EntryMode to a string
3329
func (e EntryMode) String() string {
3430
return strconv.FormatInt(int64(e), 8)
3531
}
3632

37-
func (e EntryMode) IsSubModule() bool {
38-
return e == EntryModeCommit
39-
}
40-
41-
func (e EntryMode) IsDir() bool {
42-
return e == EntryModeTree
43-
}
44-
45-
func (e EntryMode) IsLink() bool {
46-
return e == EntryModeSymlink
47-
}
48-
49-
func (e EntryMode) IsRegular() bool {
50-
return e == EntryModeBlob
51-
}
52-
53-
func (e EntryMode) IsExecutable() bool {
54-
return e == EntryModeExec
55-
}
56-
57-
// ToEntryMode converts a string to an EntryMode
58-
func ToEntryMode(value string) EntryMode {
59-
v, _ := strconv.ParseInt(value, 8, 32)
60-
return EntryMode(v)
61-
}
62-
6333
func ParseEntryMode(mode string) (EntryMode, error) {
6434
switch mode {
6535
case "000000":

0 commit comments

Comments
 (0)