Skip to content

Commit 7fa1483

Browse files
authored
Merge branch 'main' into patch-1
2 parents e1cdef8 + 7bd2ce7 commit 7fa1483

File tree

22 files changed

+196
-111
lines changed

22 files changed

+196
-111
lines changed

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module code.gitea.io/gitea
22

3-
go 1.24
3+
go 1.24.2
44

55
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
66
// But some CAs use negative serial number, just relax the check. related:
@@ -325,6 +325,8 @@ replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-tra
325325
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
326326
replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2
327327

328+
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078
329+
328330
exclude github.com/gofrs/uuid v3.2.0+incompatible
329331

330332
exclude github.com/gofrs/uuid v4.0.0+incompatible

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
1414
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
1515
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
1616
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
17-
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
18-
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
1917
gitea.com/gitea/act v0.261.4 h1:Tf9eLlvsYFtKcpuxlMvf9yT3g4Hshb2Beqw6C1STuH8=
2018
gitea.com/gitea/act v0.261.4/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
2119
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
2220
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
21+
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
22+
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
2323
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
2424
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
2525
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=

modules/fileicon/basic.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,26 @@ package fileicon
66
import (
77
"html/template"
88

9-
"code.gitea.io/gitea/modules/git"
109
"code.gitea.io/gitea/modules/svg"
10+
"code.gitea.io/gitea/modules/util"
1111
)
1212

13-
func BasicThemeIcon(entry *git.TreeEntry) template.HTML {
13+
func BasicEntryIconName(entry *EntryInfo) string {
1414
svgName := "octicon-file"
1515
switch {
16-
case entry.IsLink():
16+
case entry.EntryMode.IsLink():
1717
svgName = "octicon-file-symlink-file"
18-
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
18+
if entry.SymlinkToMode.IsDir() {
1919
svgName = "octicon-file-directory-symlink"
2020
}
21-
case entry.IsDir():
22-
svgName = "octicon-file-directory-fill"
23-
case entry.IsSubModule():
21+
case entry.EntryMode.IsDir():
22+
svgName = util.Iif(entry.IsOpen, "octicon-file-directory-open-fill", "octicon-file-directory-fill")
23+
case entry.EntryMode.IsSubModule():
2424
svgName = "octicon-file-submodule"
2525
}
26-
return svg.RenderHTML(svgName)
26+
return svgName
27+
}
28+
29+
func BasicEntryIconHTML(entry *EntryInfo) template.HTML {
30+
return svg.RenderHTML(BasicEntryIconName(entry))
2731
}

modules/fileicon/entry.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package fileicon
5+
6+
import "code.gitea.io/gitea/modules/git"
7+
8+
type EntryInfo struct {
9+
FullName string
10+
EntryMode git.EntryMode
11+
SymlinkToMode git.EntryMode
12+
IsOpen bool
13+
}
14+
15+
func EntryInfoFromGitTreeEntry(gitEntry *git.TreeEntry) *EntryInfo {
16+
ret := &EntryInfo{FullName: gitEntry.Name(), EntryMode: gitEntry.Mode()}
17+
if gitEntry.IsLink() {
18+
if te, err := gitEntry.FollowLink(); err == nil && te.IsDir() {
19+
ret.SymlinkToMode = te.Mode()
20+
}
21+
}
22+
return ret
23+
}
24+
25+
func EntryInfoFolder() *EntryInfo {
26+
return &EntryInfo{EntryMode: git.EntryModeTree}
27+
}
28+
29+
func EntryInfoFolderOpen() *EntryInfo {
30+
return &EntryInfo{EntryMode: git.EntryModeTree, IsOpen: true}
31+
}

modules/fileicon/material.go

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import (
99
"strings"
1010
"sync"
1111

12-
"code.gitea.io/gitea/modules/git"
1312
"code.gitea.io/gitea/modules/json"
1413
"code.gitea.io/gitea/modules/log"
1514
"code.gitea.io/gitea/modules/options"
15+
"code.gitea.io/gitea/modules/setting"
1616
"code.gitea.io/gitea/modules/svg"
17+
"code.gitea.io/gitea/modules/util"
1718
)
1819

1920
type materialIconRulesData struct {
@@ -69,41 +70,51 @@ func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg,
6970
}
7071
svgID := "svg-mfi-" + name
7172
svgCommonAttrs := `class="svg git-entry-icon ` + extraClass + `" width="16" height="16" aria-hidden="true"`
73+
svgHTML := template.HTML(`<svg id="` + svgID + `" ` + svgCommonAttrs + svg[4:])
74+
if p == nil {
75+
return svgHTML
76+
}
7277
if p.IconSVGs[svgID] == "" {
73-
p.IconSVGs[svgID] = template.HTML(`<svg id="` + svgID + `" ` + svgCommonAttrs + svg[4:])
78+
p.IconSVGs[svgID] = svgHTML
7479
}
7580
return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
7681
}
7782

78-
func (m *MaterialIconProvider) FileIcon(p *RenderedIconPool, entry *git.TreeEntry) template.HTML {
83+
func (m *MaterialIconProvider) EntryIconHTML(p *RenderedIconPool, entry *EntryInfo) template.HTML {
7984
if m.rules == nil {
80-
return BasicThemeIcon(entry)
85+
return BasicEntryIconHTML(entry)
8186
}
8287

83-
if entry.IsLink() {
84-
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
88+
if entry.EntryMode.IsLink() {
89+
if entry.SymlinkToMode.IsDir() {
8590
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
8691
return svg.RenderHTML("material-folder-symlink", 16, "octicon-file-directory-symlink")
8792
}
8893
return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them
8994
}
9095

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 != "" {
95-
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
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"
96+
name := m.FindIconName(entry)
97+
iconSVG := m.svgs[name]
98+
if iconSVG == "" {
99+
name = "file"
100+
if entry.EntryMode.IsDir() {
101+
name = util.Iif(entry.IsOpen, "folder-open", "folder")
102+
}
103+
iconSVG = m.svgs[name]
104+
if iconSVG == "" {
105+
setting.PanicInDevOrTesting("missing file icon for %s", name)
102106
}
103-
return m.renderFileIconSVG(p, name, iconSVG, extraClass)
104107
}
105-
// TODO: use an interface or wrapper for git.Entry to make the code testable.
106-
return BasicThemeIcon(entry)
108+
109+
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
110+
extraClass := "octicon-file"
111+
switch {
112+
case entry.EntryMode.IsDir():
113+
extraClass = BasicEntryIconName(entry)
114+
case entry.EntryMode.IsSubModule():
115+
extraClass = "octicon-file-submodule"
116+
}
117+
return m.renderFileIconSVG(p, name, iconSVG, extraClass)
107118
}
108119

109120
func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
@@ -118,13 +129,17 @@ func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
118129
return ""
119130
}
120131

121-
func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
122-
fileNameLower := strings.ToLower(path.Base(name))
123-
if isDir {
132+
func (m *MaterialIconProvider) FindIconName(entry *EntryInfo) string {
133+
if entry.EntryMode.IsSubModule() {
134+
return "folder-git"
135+
}
136+
137+
fileNameLower := strings.ToLower(path.Base(entry.FullName))
138+
if entry.EntryMode.IsDir() {
124139
if s, ok := m.rules.FolderNames[fileNameLower]; ok {
125140
return s
126141
}
127-
return "folder"
142+
return util.Iif(entry.IsOpen, "folder-open", "folder")
128143
}
129144

130145
if s, ok := m.rules.FileNames[fileNameLower]; ok {
@@ -146,10 +161,3 @@ func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
146161

147162
return "file"
148163
}
149-
150-
func (m *MaterialIconProvider) findIconNameByGit(entry *git.TreeEntry) string {
151-
if entry.IsSubModule() {
152-
return "folder-git"
153-
}
154-
return m.FindIconName(entry.Name(), entry.IsDir())
155-
}

modules/fileicon/material_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"code.gitea.io/gitea/models/unittest"
1010
"code.gitea.io/gitea/modules/fileicon"
11+
"code.gitea.io/gitea/modules/git"
1112

1213
"github.com/stretchr/testify/assert"
1314
)
@@ -19,8 +20,8 @@ func TestMain(m *testing.M) {
1920
func TestFindIconName(t *testing.T) {
2021
unittest.PrepareTestEnv(t)
2122
p := fileicon.DefaultMaterialIconProvider()
22-
assert.Equal(t, "php", p.FindIconName("foo.php", false))
23-
assert.Equal(t, "php", p.FindIconName("foo.PHP", false))
24-
assert.Equal(t, "javascript", p.FindIconName("foo.js", false))
25-
assert.Equal(t, "visualstudio", p.FindIconName("foo.vba", false))
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}))
2627
}

modules/fileicon/render.go

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"html/template"
88
"strings"
99

10-
"code.gitea.io/gitea/modules/git"
1110
"code.gitea.io/gitea/modules/setting"
1211
)
1312

@@ -34,19 +33,9 @@ func (p *RenderedIconPool) RenderToHTML() template.HTML {
3433
return template.HTML(sb.String())
3534
}
3635

37-
// TODO: use an interface or struct to replace "*git.TreeEntry", to decouple the fileicon module from git module
38-
39-
func RenderEntryIcon(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML {
40-
if setting.UI.FileIconTheme == "material" {
41-
return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry)
42-
}
43-
return BasicThemeIcon(entry)
44-
}
45-
46-
func RenderEntryIconOpen(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML {
47-
// TODO: add "open icon" support
36+
func RenderEntryIconHTML(renderedIconPool *RenderedIconPool, entry *EntryInfo) template.HTML {
4837
if setting.UI.FileIconTheme == "material" {
49-
return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry)
38+
return DefaultMaterialIconProvider().EntryIconHTML(renderedIconPool, entry)
5039
}
51-
return BasicThemeIcon(entry)
40+
return BasicEntryIconHTML(entry)
5241
}

modules/git/tree_entry_mode.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,31 @@ func (e EntryMode) String() string {
3030
return strconv.FormatInt(int64(e), 8)
3131
}
3232

33+
// IsSubModule if the entry is a sub module
34+
func (e EntryMode) IsSubModule() bool {
35+
return e == EntryModeCommit
36+
}
37+
38+
// IsDir if the entry is a sub dir
39+
func (e EntryMode) IsDir() bool {
40+
return e == EntryModeTree
41+
}
42+
43+
// IsLink if the entry is a symlink
44+
func (e EntryMode) IsLink() bool {
45+
return e == EntryModeSymlink
46+
}
47+
48+
// IsRegular if the entry is a regular file
49+
func (e EntryMode) IsRegular() bool {
50+
return e == EntryModeBlob
51+
}
52+
53+
// IsExecutable if the entry is an executable file (not necessarily binary)
54+
func (e EntryMode) IsExecutable() bool {
55+
return e == EntryModeExec
56+
}
57+
3358
func ParseEntryMode(mode string) (EntryMode, error) {
3459
switch mode {
3560
case "000000":

modules/git/tree_entry_nogogit.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,27 +59,27 @@ func (te *TreeEntry) Size() int64 {
5959

6060
// IsSubModule if the entry is a sub module
6161
func (te *TreeEntry) IsSubModule() bool {
62-
return te.entryMode == EntryModeCommit
62+
return te.entryMode.IsSubModule()
6363
}
6464

6565
// IsDir if the entry is a sub dir
6666
func (te *TreeEntry) IsDir() bool {
67-
return te.entryMode == EntryModeTree
67+
return te.entryMode.IsDir()
6868
}
6969

7070
// IsLink if the entry is a symlink
7171
func (te *TreeEntry) IsLink() bool {
72-
return te.entryMode == EntryModeSymlink
72+
return te.entryMode.IsLink()
7373
}
7474

7575
// IsRegular if the entry is a regular file
7676
func (te *TreeEntry) IsRegular() bool {
77-
return te.entryMode == EntryModeBlob
77+
return te.entryMode.IsRegular()
7878
}
7979

8080
// IsExecutable if the entry is an executable file (not necessarily binary)
8181
func (te *TreeEntry) IsExecutable() bool {
82-
return te.entryMode == EntryModeExec
82+
return te.entryMode.IsExecutable()
8383
}
8484

8585
// Blob returns the blob object the entry

routers/web/repo/commit.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
user_model "code.gitea.io/gitea/models/user"
2222
"code.gitea.io/gitea/modules/base"
2323
"code.gitea.io/gitea/modules/charset"
24+
"code.gitea.io/gitea/modules/fileicon"
2425
"code.gitea.io/gitea/modules/git"
2526
"code.gitea.io/gitea/modules/gitrepo"
2627
"code.gitea.io/gitea/modules/log"
@@ -369,7 +370,11 @@ func Diff(ctx *context.Context) {
369370
return
370371
}
371372

372-
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, nil)
373+
renderedIconPool := fileicon.NewRenderedIconPool()
374+
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(renderedIconPool, diffTree, nil)
375+
ctx.PageData["FolderIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
376+
ctx.PageData["FolderOpenIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolderOpen())
377+
ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
373378
}
374379

375380
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)

0 commit comments

Comments
 (0)