Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions modules/git/commit_submodule_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"path"
"regexp"
"strings"

"code.gitea.io/gitea/modules/setting"
)

var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
Expand Down Expand Up @@ -101,8 +103,8 @@ func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string {

// RefURL guesses and returns reference URL.
// FIXME: template passes AppURL as urlPrefix, it needs to figure out the correct approach (no hard-coded AppURL anymore)
func (sf *CommitSubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string {
return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain)
func (sf *CommitSubModuleFile) RefURL(repoFullName string) string {
return getRefURL(sf.refURL, setting.AppURL, repoFullName, setting.SSH.Domain)
}

// RefID returns reference ID.
Expand Down
5 changes: 5 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2627,6 +2627,11 @@ diff.image.overlay = Overlay
diff.has_escaped = This line has hidden Unicode characters
diff.show_file_tree = Show file tree
diff.hide_file_tree = Hide file tree
diff.submodule_added = Submodule %s added at %s
diff.submodule_added_link = Submodule <a href="%[1]s">%[2]s</a> added at <a href="%[1]s/commit/%[4]s">%[3]s</a>
diff.submodule_deleted = Submodule %s deleted from %s
diff.submodule_updated = Submodule %s updated from %s to %s
diff.submodule_updated_link = Submodule <a href="%[1]s">%[2]s</a> updated <a href="%[1]s/compare/%[4]s...%[6]s">from %[3]s to %[5]s</a>

releases.desc = Track project versions and downloads.
release.releases = Releases
Expand Down
1 change: 0 additions & 1 deletion routers/web/repo/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,6 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
}

ctx.Data["TreeLink"] = treeLink
ctx.Data["SSHDomain"] = setting.SSH.Domain

return allEntries
}
Expand Down
31 changes: 31 additions & 0 deletions services/gitdiff/gitdiff.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ type DiffFile struct {
Language string
Mode string
OldMode string
SubmoduleInfo *SubmoduleInfo
}

// GetType returns type of diff file.
Expand Down Expand Up @@ -915,6 +916,17 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact
}
}
curSection.Lines = append(curSection.Lines, diffLine)

// Parse submodule additions
if curFile.IsSubmodule {
if curFile.SubmoduleInfo == nil {
curFile.SubmoduleInfo = &SubmoduleInfo{}
}

if ref, found := bytes.CutPrefix(lineBytes, []byte("+Subproject commit ")); found {
curFile.SubmoduleInfo.NewRefID = string(bytes.TrimSpace(ref))
}
}
case '-':
curFileLinesCount++
curFile.Deletion++
Expand All @@ -936,6 +948,17 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact
lastLeftIdx = len(curSection.Lines)
}
curSection.Lines = append(curSection.Lines, diffLine)

// Parse submodule deletion
if curFile.IsSubmodule {
if curFile.SubmoduleInfo == nil {
curFile.SubmoduleInfo = &SubmoduleInfo{}
}

if ref, found := bytes.CutPrefix(lineBytes, []byte("-Subproject commit ")); found {
curFile.SubmoduleInfo.PreviousRefID = string(bytes.TrimSpace(ref))
}
}
case ' ':
curFileLinesCount++
if maxLines > -1 && curFileLinesCount >= maxLines {
Expand Down Expand Up @@ -1195,6 +1218,14 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
}
}

// Populate Submodule URLs
if diffFile.IsSubmodule && diffFile.SubmoduleInfo != nil {
err := diffFile.SubmoduleInfo.PopulateURL(diffFile, beforeCommit, commit)
if err != nil {
return nil, err
}
}

if !isVendored.Has() {
isVendored = optional.Some(analyze.IsVendor(diffFile.Name))
}
Expand Down
55 changes: 55 additions & 0 deletions services/gitdiff/submodule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package gitdiff

import "code.gitea.io/gitea/modules/git"

type SubmoduleInfo struct {
SubmoduleURL string
NewRefID string
PreviousRefID string
}

func (si *SubmoduleInfo) PopulateURL(diffFile *DiffFile, leftCommit, rightCommit *git.Commit) error {
// If the submodule is removed, we need to check it at the left commit
if diffFile.IsDeleted {
if leftCommit == nil {
return nil
}

submodule, err := leftCommit.GetSubModule(diffFile.GetDiffFileName())
if err != nil {
return err
}

if submodule != nil {
si.SubmoduleURL = submodule.URL
}

return nil
}

// Even if the submodule path is updated, we check this at the right commit
submodule, err := rightCommit.GetSubModule(diffFile.Name)
if err != nil {
return err
}

if submodule != nil {
si.SubmoduleURL = submodule.URL
}
return nil
}

func (si *SubmoduleInfo) RefID() string {
if si.NewRefID != "" {
return si.NewRefID
}
return si.PreviousRefID
}

// RefURL guesses and returns reference URL.
func (si *SubmoduleInfo) RefURL(repoFullName string) string {
return git.NewCommitSubModuleFile(si.SubmoduleURL, si.RefID()).RefURL(repoFullName)
}
217 changes: 217 additions & 0 deletions services/gitdiff/submodule_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package gitdiff

import (
"strings"
"testing"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/setting"

"github.com/stretchr/testify/assert"
)

func TestParseSubmoduleInfo(t *testing.T) {
type testcase struct {
name string
gitdiff string
infos map[int]SubmoduleInfo
}

tests := []testcase{
{
name: "added",
gitdiff: `diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..4ac13c1
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "gitea-mirror"]
+ path = gitea-mirror
+ url = https://gitea.com/gitea/gitea-mirror
diff --git a/gitea-mirror b/gitea-mirror
new file mode 160000
index 0000000..68972a9
--- /dev/null
+++ b/gitea-mirror
@@ -0,0 +1 @@
+Subproject commit 68972a994719ae5c74e28d8fa82fa27c23399bc8
`,
infos: map[int]SubmoduleInfo{
1: {NewRefID: "68972a994719ae5c74e28d8fa82fa27c23399bc8"},
},
},
{
name: "updated",
gitdiff: `diff --git a/gitea-mirror b/gitea-mirror
index 68972a9..c8ffe77 160000
--- a/gitea-mirror
+++ b/gitea-mirror
@@ -1 +1 @@
-Subproject commit 68972a994719ae5c74e28d8fa82fa27c23399bc8
+Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d
`,
infos: map[int]SubmoduleInfo{
0: {
PreviousRefID: "68972a994719ae5c74e28d8fa82fa27c23399bc8",
NewRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d",
},
},
},
{
name: "rename",
gitdiff: `diff --git a/.gitmodules b/.gitmodules
index 4ac13c1..0510edd 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
[submodule "gitea-mirror"]
- path = gitea-mirror
+ path = gitea
url = https://gitea.com/gitea/gitea-mirror
diff --git a/gitea-mirror b/gitea
similarity index 100%
rename from gitea-mirror
rename to gitea
`,
},
{
name: "deleted",
gitdiff: `diff --git a/.gitmodules b/.gitmodules
index 0510edd..e69de29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +0,0 @@
-[submodule "gitea-mirror"]
- path = gitea
- url = https://gitea.com/gitea/gitea-mirror
diff --git a/gitea b/gitea
deleted file mode 160000
index c8ffe77..0000000
--- a/gitea
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d
`,
infos: map[int]SubmoduleInfo{
1: {
PreviousRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d",
},
},
},
{
name: "moved and updated",
gitdiff: `diff --git a/.gitmodules b/.gitmodules
index 0510edd..bced3d8 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
[submodule "gitea-mirror"]
- path = gitea
+ path = gitea-1.22
url = https://gitea.com/gitea/gitea-mirror
diff --git a/gitea b/gitea
deleted file mode 160000
index c8ffe77..0000000
--- a/gitea
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d
diff --git a/gitea-1.22 b/gitea-1.22
new file mode 160000
index 0000000..8eefa1f
--- /dev/null
+++ b/gitea-1.22
@@ -0,0 +1 @@
+Subproject commit 8eefa1f6dedf2488db2c9e12c916e8e51f673160
`,
infos: map[int]SubmoduleInfo{
1: {
PreviousRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d",
},
2: {
NewRefID: "8eefa1f6dedf2488db2c9e12c916e8e51f673160",
},
},
},
{
name: "converted to file",
gitdiff: `diff --git a/.gitmodules b/.gitmodules
index 0510edd..e69de29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +0,0 @@
-[submodule "gitea-mirror"]
- path = gitea
- url = https://gitea.com/gitea/gitea-mirror
diff --git a/gitea b/gitea
deleted file mode 160000
index c8ffe77..0000000
--- a/gitea
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d
diff --git a/gitea b/gitea
new file mode 100644
index 0000000..33a9488
--- /dev/null
+++ b/gitea
@@ -0,0 +1 @@
+example
`,
infos: map[int]SubmoduleInfo{
1: {
PreviousRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d",
},
},
},
{
name: "converted to submodule",
gitdiff: `diff --git a/.gitmodules b/.gitmodules
index e69de29..14ee267 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "gitea"]
+ path = gitea
+ url = https://gitea.com/gitea/gitea-mirror
diff --git a/gitea b/gitea
deleted file mode 100644
index 33a9488..0000000
--- a/gitea
+++ /dev/null
@@ -1 +0,0 @@
-example
diff --git a/gitea b/gitea
new file mode 160000
index 0000000..68972a9
--- /dev/null
+++ b/gitea
@@ -0,0 +1 @@
+Subproject commit 68972a994719ae5c74e28d8fa82fa27c23399bc8
`,
infos: map[int]SubmoduleInfo{
2: {
NewRefID: "68972a994719ae5c74e28d8fa82fa27c23399bc8",
},
},
},
}

for _, testcase := range tests {
testcase := testcase
t.Run(testcase.name, func(t *testing.T) {
diff, err := ParsePatch(db.DefaultContext, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(testcase.gitdiff), "")
assert.NoError(t, err)

for i, expected := range testcase.infos {
actual := diff.Files[i]
assert.NotNil(t, actual)
assert.Equal(t, expected, *actual.SubmoduleInfo)
}
})
}
}
Loading
Loading