From b218c653e5409af996d6a267ad207782a5718c83 Mon Sep 17 00:00:00 2001 From: Rowan Bohde Date: Fri, 3 Jan 2025 13:47:06 -0600 Subject: [PATCH 01/14] add submodule diff links This adds links to submodules in diffs, similar to the existing link when viewing a repo at a specific commit. It does this by expanding diff parsing to recognize changes to submodules, and find the specific refs that are added, deleted or changed. The templates are updated to add either a link to the submodule at a commit, or the diff between two commits in the event that the submodule is updated. A slight refactor was done to simplify calling RefURL on the submodule. --- modules/git/commit_submodule_file.go | 6 +- options/locale/locale_en-US.ini | 5 + routers/web/repo/view.go | 1 - services/gitdiff/gitdiff.go | 31 ++++ services/gitdiff/submodule.go | 55 +++++++ services/gitdiff/submodule_test.go | 217 +++++++++++++++++++++++++++ templates/repo/diff/box.tmpl | 22 +++ templates/repo/view_list.tmpl | 2 +- 8 files changed, 335 insertions(+), 4 deletions(-) create mode 100644 services/gitdiff/submodule.go create mode 100644 services/gitdiff/submodule_test.go diff --git a/modules/git/commit_submodule_file.go b/modules/git/commit_submodule_file.go index bdec35f682987..fad4cd1765ae8 100644 --- a/modules/git/commit_submodule_file.go +++ b/modules/git/commit_submodule_file.go @@ -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._-]+):(.*)$`) @@ -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. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 6029d49ade0bc..fc22f8f7e4d88 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -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 %[2]s added at %[3]s +diff.submodule_deleted = Submodule %s deleted from %s +diff.submodule_updated = Submodule %s updated from %s to %s +diff.submodule_updated_link = Submodule %[2]s updated from %[3]s to %[5]s releases.desc = Track project versions and downloads. release.releases = Releases diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 14fc9038f3e6b..9fe2b58ebc66d 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -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 } diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 0b5a855d42fea..59bc2c4733039 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -372,6 +372,7 @@ type DiffFile struct { Language string Mode string OldMode string + SubmoduleInfo *SubmoduleInfo } // GetType returns type of diff file. @@ -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++ @@ -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 { @@ -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)) } diff --git a/services/gitdiff/submodule.go b/services/gitdiff/submodule.go new file mode 100644 index 0000000000000..6d919ffdce987 --- /dev/null +++ b/services/gitdiff/submodule.go @@ -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) +} diff --git a/services/gitdiff/submodule_test.go b/services/gitdiff/submodule_test.go new file mode 100644 index 0000000000000..dd62b7fdd20e9 --- /dev/null +++ b/services/gitdiff/submodule_test.go @@ -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) + } + }) + } +} diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 9733e5f980680..cb2cac0c5a962 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -195,6 +195,28 @@ {{ctx.Locale.Tr "repo.diff.bin_not_shown"}} {{end}} + {{else if and $file.IsSubmodule $file.SubmoduleInfo}} +
+ {{$subModule := $file.SubmoduleInfo}} + {{$refURL := $subModule.RefURL $.Repository.FullName}} + {{svg "octicon-file-submodule"}} + + {{if $file.IsDeleted}} + {{ctx.Locale.Tr "repo.diff.submodule_deleted" $file.Name (ShortSha $subModule.RefID)}} + {{else if $file.IsCreated}} + {{if $refURL}} + {{ctx.Locale.Tr "repo.diff.submodule_added_link" $refURL $file.Name (ShortSha $subModule.RefID) (PathEscape $subModule.RefID)}} + {{else}} + {{ctx.Locale.Tr "repo.diff.submodule_added" $file.Name (ShortSha $subModule.RefID)}} + {{end}} + {{else}} + {{if $refURL}} + {{ctx.Locale.Tr "repo.diff.submodule_updated_link" $refURL $file.Name (ShortSha $subModule.PreviousRefID) (PathEscape $subModule.PreviousRefID) (ShortSha $subModule.RefID) (PathEscape $subModule.RefID)}} + {{else}} + {{ctx.Locale.Tr "repo.diff.submodule_updated" $file.Name (ShortSha $subModule.PreviousRefID) (ShortSha $subModule.RefID)}} + {{end}} + {{end}} +
{{else}} {{if $.IsSplitStyle}} diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index c8e97d2617fd1..936fba77cea2f 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -17,7 +17,7 @@
{{if $entry.IsSubModule}} {{svg "octicon-file-submodule"}} - {{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}} {{/* FIXME: the usage of AppUrl seems incorrect, it would be fixed in the future, use AppSubUrl instead */}} + {{$refURL := $subModuleFile.RefURL $.Repository.FullName}} {{if $refURL}} {{$entry.Name}} @ {{ShortSha $subModuleFile.RefID}} {{else}} From 8d63859f5b7bf8b9dd6434b6c9b7b9f0eabf7003 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 4 Jan 2025 11:37:27 +0800 Subject: [PATCH 02/14] fix ui --- options/locale/locale_en-US.ini | 2 - services/gitdiff/gitdiff.go | 28 +++++------- templates/repo/diff/box.tmpl | 77 ++++++++++++++++++--------------- 3 files changed, 52 insertions(+), 55 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index fc22f8f7e4d88..1aca87b143f2f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2628,10 +2628,8 @@ 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 %[2]s added at %[3]s diff.submodule_deleted = Submodule %s deleted from %s diff.submodule_updated = Submodule %s updated from %s to %s -diff.submodule_updated_link = Submodule %[2]s updated from %[3]s to %[5]s releases.desc = Track project versions and downloads. release.releases = Releases diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 59bc2c4733039..3328df91b1296 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -360,7 +360,6 @@ type DiffFile struct { IsLFSFile bool IsRenamed bool IsAmbiguous bool - IsSubmodule bool Sections []*DiffSection IsIncomplete bool IsIncompleteLineTooLong bool @@ -372,7 +371,9 @@ type DiffFile struct { Language string Mode string OldMode string - SubmoduleInfo *SubmoduleInfo + + IsSubmodule bool // if IsSubmodule==true, then there must be a SubmoduleInfo + SubmoduleInfo *SubmoduleInfo } // GetType returns type of diff file. @@ -610,9 +611,8 @@ parsingLoop: if strings.HasPrefix(line, "new mode ") { curFile.Mode = prepareValue(line, "new mode ") } - if strings.HasSuffix(line, " 160000\n") { - curFile.IsSubmodule = true + curFile.IsSubmodule, curFile.SubmoduleInfo = true, &SubmoduleInfo{} } case strings.HasPrefix(line, "rename from "): curFile.IsRenamed = true @@ -647,17 +647,17 @@ parsingLoop: curFile.Mode = prepareValue(line, "new file mode ") } if strings.HasSuffix(line, " 160000\n") { - curFile.IsSubmodule = true + curFile.IsSubmodule, curFile.SubmoduleInfo = true, &SubmoduleInfo{} } case strings.HasPrefix(line, "deleted"): curFile.Type = DiffFileDel curFile.IsDeleted = true if strings.HasSuffix(line, " 160000\n") { - curFile.IsSubmodule = true + curFile.IsSubmodule, curFile.SubmoduleInfo = true, &SubmoduleInfo{} } case strings.HasPrefix(line, "index"): if strings.HasSuffix(line, " 160000\n") { - curFile.IsSubmodule = true + curFile.IsSubmodule, curFile.SubmoduleInfo = true, &SubmoduleInfo{} } case strings.HasPrefix(line, "similarity index 100%"): curFile.Type = DiffFileRename @@ -918,11 +918,7 @@ 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 curFile.SubmoduleInfo != nil { if ref, found := bytes.CutPrefix(lineBytes, []byte("+Subproject commit ")); found { curFile.SubmoduleInfo.NewRefID = string(bytes.TrimSpace(ref)) } @@ -950,11 +946,7 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact curSection.Lines = append(curSection.Lines, diffLine) // Parse submodule deletion - if curFile.IsSubmodule { - if curFile.SubmoduleInfo == nil { - curFile.SubmoduleInfo = &SubmoduleInfo{} - } - + if curFile.SubmoduleInfo != nil { if ref, found := bytes.CutPrefix(lineBytes, []byte("-Subproject commit ")); found { curFile.SubmoduleInfo.PreviousRefID = string(bytes.TrimSpace(ref)) } @@ -1219,7 +1211,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi } // Populate Submodule URLs - if diffFile.IsSubmodule && diffFile.SubmoduleInfo != nil { + if diffFile.SubmoduleInfo != nil { err := diffFile.SubmoduleInfo.PopulateURL(diffFile, beforeCommit, commit) if err != nil { return nil, err diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index cb2cac0c5a962..922081674ae2a 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -161,23 +161,25 @@ {{ctx.Locale.Tr "repo.pulls.has_viewed_file"}} {{end}} - -
- {{if not (or $file.IsIncomplete $file.IsBin $file.IsSubmodule)}} - - - {{end}} - {{if and (not $file.IsSubmodule) (not $.PageIsWiki)}} - {{if $file.IsDeleted}} - {{ctx.Locale.Tr "repo.diff.view_file"}} - {{else}} - {{ctx.Locale.Tr "repo.diff.view_file"}} - {{if and $.Repository.CanEnableEditor $.CanEditFile (not $file.IsLFSFile) (not $file.IsBin)}} - {{ctx.Locale.Tr "repo.editor.edit_this_file"}} + {{if not $file.IsSubmodule}} + +
+ {{if not (or $file.IsIncomplete $file.IsBin)}} + + + {{end}} + {{if not $.PageIsWiki}} + {{if $file.IsDeleted}} + {{ctx.Locale.Tr "repo.diff.view_file"}} + {{else}} + {{ctx.Locale.Tr "repo.diff.view_file"}} + {{if and $.Repository.CanEnableEditor $.CanEditFile (not $file.IsLFSFile) (not $file.IsBin)}} + {{ctx.Locale.Tr "repo.editor.edit_this_file"}} + {{end}} {{end}} {{end}} - {{end}} -
+
+ {{end}}
@@ -195,26 +197,31 @@ {{ctx.Locale.Tr "repo.diff.bin_not_shown"}} {{end}}
- {{else if and $file.IsSubmodule $file.SubmoduleInfo}} -
- {{$subModule := $file.SubmoduleInfo}} - {{$refURL := $subModule.RefURL $.Repository.FullName}} - {{svg "octicon-file-submodule"}} - - {{if $file.IsDeleted}} - {{ctx.Locale.Tr "repo.diff.submodule_deleted" $file.Name (ShortSha $subModule.RefID)}} - {{else if $file.IsCreated}} - {{if $refURL}} - {{ctx.Locale.Tr "repo.diff.submodule_added_link" $refURL $file.Name (ShortSha $subModule.RefID) (PathEscape $subModule.RefID)}} - {{else}} - {{ctx.Locale.Tr "repo.diff.submodule_added" $file.Name (ShortSha $subModule.RefID)}} - {{end}} - {{else}} - {{if $refURL}} - {{ctx.Locale.Tr "repo.diff.submodule_updated_link" $refURL $file.Name (ShortSha $subModule.PreviousRefID) (PathEscape $subModule.PreviousRefID) (ShortSha $subModule.RefID) (PathEscape $subModule.RefID)}} - {{else}} - {{ctx.Locale.Tr "repo.diff.submodule_updated" $file.Name (ShortSha $subModule.PreviousRefID) (ShortSha $subModule.RefID)}} - {{end}} + {{else if and $file.SubmoduleInfo}} +
{{svg "octicon-file-submodule"}} {{$submodule := $file.SubmoduleInfo -}} + {{- $refURL := $submodule.RefURL $.Repository.FullName -}} + {{- $submoduleName := $file.Name -}} + {{- $refShortID := ShortSha $submodule.RefID -}} + {{- if $file.IsDeleted -}} + {{- ctx.Locale.Tr "repo.diff.submodule_deleted" $submoduleName $refShortID -}} + {{- else if $file.IsCreated -}} + {{- $refAddedSubModule := $submoduleName -}} + {{- $refAddedAt := $refShortID -}} + {{- if $refURL -}} + {{- $refAddedSubModule = HTMLFormat `%s` $refURL $submoduleName -}} + {{- $refAddedAt = HTMLFormat `%s` $refURL (PathEscape $submodule.RefID) $refShortID -}} + {{- end -}} + {{- ctx.Locale.Tr "repo.diff.submodule_added" $refAddedSubModule $refAddedAt -}} + {{- else -}} + {{- $refUpdatedSubModule := $submoduleName -}} + {{- $refUpdatedFrom := ShortSha $submodule.PreviousRefID -}} + {{- $refUpdatedTo := $refShortID -}} + {{- if $refURL -}} + {{- $refUpdatedSubModule = HTMLFormat `%s` $refURL $submoduleName -}} + {{- $refUpdatedFrom = HTMLFormat `%s` $refURL (PathEscape $submodule.PreviousRefID) (ShortSha $submodule.PreviousRefID) -}} + {{- $refUpdatedTo = HTMLFormat `%s` $refURL (PathEscape $submodule.RefID) (ShortSha $submodule.RefID) -}} + {{- end -}} + {{- ctx.Locale.Tr "repo.diff.submodule_updated" $refUpdatedSubModule $refUpdatedFrom $refUpdatedTo -}} {{end}}
{{else}} From a3d1f262603f616952cedc73cb7554097a2b864e Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 5 Jan 2025 12:21:13 +0800 Subject: [PATCH 03/14] fix RefURL --- modules/git/commit_submodule_file.go | 9 ++++++--- modules/git/commit_submodule_file_test.go | 9 ++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/git/commit_submodule_file.go b/modules/git/commit_submodule_file.go index fad4cd1765ae8..7d84f91d14157 100644 --- a/modules/git/commit_submodule_file.go +++ b/modules/git/commit_submodule_file.go @@ -31,11 +31,15 @@ func NewCommitSubModuleFile(refURL, refID string) *CommitSubModuleFile { } } -func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string { +func getRefURL(refURL, repoFullName string) string { if refURL == "" { return "" } + // FIXME: use a more generic way to handle the domain and subpath + urlPrefix := setting.AppURL + sshDomain := setting.SSH.Domain + refURI := strings.TrimSuffix(refURL, ".git") prefixURL, _ := url.Parse(urlPrefix) @@ -102,9 +106,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(repoFullName string) string { - return getRefURL(sf.refURL, setting.AppURL, repoFullName, setting.SSH.Domain) + return getRefURL(sf.refURL, repoFullName) } // RefID returns reference ID. diff --git a/modules/git/commit_submodule_file_test.go b/modules/git/commit_submodule_file_test.go index 473b996b820ab..6ab30b26d153e 100644 --- a/modules/git/commit_submodule_file_test.go +++ b/modules/git/commit_submodule_file_test.go @@ -6,6 +6,9 @@ package git import ( "testing" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "github.com/stretchr/testify/assert" ) @@ -37,6 +40,10 @@ func TestCommitSubModuleFileGetRefURL(t *testing.T) { } for _, kase := range kases { - assert.EqualValues(t, kase.expect, getRefURL(kase.refURL, kase.prefixURL, kase.parentPath, kase.SSHDomain)) + t.Run(kase.refURL, func(t *testing.T) { + defer test.MockVariableValue(&setting.AppURL, kase.prefixURL)() + defer test.MockVariableValue(&setting.SSH.Domain, kase.SSHDomain)() + assert.EqualValues(t, kase.expect, getRefURL(kase.refURL, kase.parentPath)) + }) } } From edc96232d602c4f21bced6bcfcb024f24b63ce17 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 7 Jan 2025 08:33:04 +0800 Subject: [PATCH 04/14] refactor --- services/gitdiff/submodule.go | 66 ++++++++++++++++++------------ services/gitdiff/submodule_test.go | 4 ++ templates/repo/diff/box.tmpl | 24 ++--------- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/services/gitdiff/submodule.go b/services/gitdiff/submodule.go index 6d919ffdce987..75f8a1fd9e3ec 100644 --- a/services/gitdiff/submodule.go +++ b/services/gitdiff/submodule.go @@ -3,7 +3,14 @@ package gitdiff -import "code.gitea.io/gitea/modules/git" +import ( + "html/template" + + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/htmlutil" + "code.gitea.io/gitea/modules/log" +) type SubmoduleInfo struct { SubmoduleURL string @@ -12,44 +19,49 @@ type SubmoduleInfo struct { } 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()) + var submoduleCommit *git.Commit + switch { + case diffFile.IsDeleted: + submoduleCommit = leftCommit // If the submodule is removed, we need to check it at the left commit + default: + submoduleCommit = rightCommit // If the submodule path is added or updated, we check this at the right commit + } + if submoduleCommit != nil { + submodule, err := submoduleCommit.GetSubModule(diffFile.GetDiffFileName()) if err != nil { - return err + log.Error("Unable to PopulateURL for submodule %q: GetSubModule: %v", diffFile.GetDiffFileName(), err) + return nil // ignore the error, do not cause 500 errors for end users } - if submodule != nil { si.SubmoduleURL = submodule.URL } - - return nil } + 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 +func (si *SubmoduleInfo) NewRefIDLinkHTML() template.HTML { + refURL := si.refURL() + if si.PreviousRefID == "" { + return htmlutil.HTMLFormat(`%s`, refURL, si.NewRefID, base.ShortSha(si.NewRefID)) } + return htmlutil.HTMLFormat(`%s`, refURL, si.PreviousRefID, si.NewRefID, base.ShortSha(si.NewRefID)) +} - if submodule != nil { - si.SubmoduleURL = submodule.URL - } - return nil +func (si *SubmoduleInfo) PreviousRefIDLinkHTML() template.HTML { + refURL := si.refURL() + return htmlutil.HTMLFormat(`%s`, refURL, si.PreviousRefID, base.ShortSha(si.PreviousRefID)) } -func (si *SubmoduleInfo) RefID() string { - if si.NewRefID != "" { - return si.NewRefID - } - return si.PreviousRefID +func (si *SubmoduleInfo) SubmoduleRepoLinkHTML(name string) template.HTML { + refURL := si.refURL() + return htmlutil.HTMLFormat(`%s`, refURL, name) } // RefURL guesses and returns reference URL. -func (si *SubmoduleInfo) RefURL(repoFullName string) string { - return git.NewCommitSubModuleFile(si.SubmoduleURL, si.RefID()).RefURL(repoFullName) +func (si *SubmoduleInfo) refURL() string { + // FIXME: use unified way to handle domain and subpath + // FIXME: RefURL(repoFullName) is only used to provide relative path support for submodules + // it is not our use case because it won't work for git clone via http/ssh + // FIXME: the current RefURL is not right, it doesn't consider the subpath + return git.NewCommitSubModuleFile(si.SubmoduleURL, "").RefURL("") } diff --git a/services/gitdiff/submodule_test.go b/services/gitdiff/submodule_test.go index dd62b7fdd20e9..136060e4945a4 100644 --- a/services/gitdiff/submodule_test.go +++ b/services/gitdiff/submodule_test.go @@ -215,3 +215,7 @@ index 0000000..68972a9 }) } } + +func TestSubmoduleInfo(t *testing.T) { + // TODO: test NewRefIDLinkHTML PreviousRefIDLinkHTML SubmoduleRepoLinkHTML after we get the unifed "RefURL" function +} diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 922081674ae2a..fbe2bad9a414f 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -199,29 +199,13 @@
{{else if and $file.SubmoduleInfo}}
{{svg "octicon-file-submodule"}} {{$submodule := $file.SubmoduleInfo -}} - {{- $refURL := $submodule.RefURL $.Repository.FullName -}} - {{- $submoduleName := $file.Name -}} - {{- $refShortID := ShortSha $submodule.RefID -}} + {{- $submoduleName := ($submodule.SubmoduleRepoLinkHTML $file.Name) -}} {{- if $file.IsDeleted -}} - {{- ctx.Locale.Tr "repo.diff.submodule_deleted" $submoduleName $refShortID -}} + {{- ctx.Locale.Tr "repo.diff.submodule_deleted" $submoduleName $submodule.PreviousRefIDLinkHTML -}} {{- else if $file.IsCreated -}} - {{- $refAddedSubModule := $submoduleName -}} - {{- $refAddedAt := $refShortID -}} - {{- if $refURL -}} - {{- $refAddedSubModule = HTMLFormat `%s` $refURL $submoduleName -}} - {{- $refAddedAt = HTMLFormat `%s` $refURL (PathEscape $submodule.RefID) $refShortID -}} - {{- end -}} - {{- ctx.Locale.Tr "repo.diff.submodule_added" $refAddedSubModule $refAddedAt -}} + {{- ctx.Locale.Tr "repo.diff.submodule_added" $submoduleName $submodule.NewRefIDLinkHTML -}} {{- else -}} - {{- $refUpdatedSubModule := $submoduleName -}} - {{- $refUpdatedFrom := ShortSha $submodule.PreviousRefID -}} - {{- $refUpdatedTo := $refShortID -}} - {{- if $refURL -}} - {{- $refUpdatedSubModule = HTMLFormat `%s` $refURL $submoduleName -}} - {{- $refUpdatedFrom = HTMLFormat `%s` $refURL (PathEscape $submodule.PreviousRefID) (ShortSha $submodule.PreviousRefID) -}} - {{- $refUpdatedTo = HTMLFormat `%s` $refURL (PathEscape $submodule.RefID) (ShortSha $submodule.RefID) -}} - {{- end -}} - {{- ctx.Locale.Tr "repo.diff.submodule_updated" $refUpdatedSubModule $refUpdatedFrom $refUpdatedTo -}} + {{- ctx.Locale.Tr "repo.diff.submodule_updated" $submoduleName $submodule.PreviousRefIDLinkHTML $submodule.NewRefIDLinkHTML -}} {{end}}
{{else}} From 4ada9577e863768f86589927d0d71d9a8ecd9cfb Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 7 Jan 2025 08:40:31 +0800 Subject: [PATCH 05/14] fix --- services/gitdiff/submodule.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/services/gitdiff/submodule.go b/services/gitdiff/submodule.go index 75f8a1fd9e3ec..12460d0b49e8e 100644 --- a/services/gitdiff/submodule.go +++ b/services/gitdiff/submodule.go @@ -19,12 +19,9 @@ type SubmoduleInfo struct { } func (si *SubmoduleInfo) PopulateURL(diffFile *DiffFile, leftCommit, rightCommit *git.Commit) error { - var submoduleCommit *git.Commit - switch { - case diffFile.IsDeleted: - submoduleCommit = leftCommit // If the submodule is removed, we need to check it at the left commit - default: - submoduleCommit = rightCommit // If the submodule path is added or updated, we check this at the right commit + submoduleCommit := rightCommit // If the submodule is added or updated, check at the right commit + if diffFile.IsDeleted { + submoduleCommit = leftCommit // If the submodule is deleted, check at the left commit } if submoduleCommit != nil { submodule, err := submoduleCommit.GetSubModule(diffFile.GetDiffFileName()) @@ -42,7 +39,7 @@ func (si *SubmoduleInfo) PopulateURL(diffFile *DiffFile, leftCommit, rightCommit func (si *SubmoduleInfo) NewRefIDLinkHTML() template.HTML { refURL := si.refURL() if si.PreviousRefID == "" { - return htmlutil.HTMLFormat(`%s`, refURL, si.NewRefID, base.ShortSha(si.NewRefID)) + return htmlutil.HTMLFormat(`%s`, refURL, si.NewRefID, base.ShortSha(si.NewRefID)) } return htmlutil.HTMLFormat(`%s`, refURL, si.PreviousRefID, si.NewRefID, base.ShortSha(si.NewRefID)) } From ae53bc7df9ecb73cfc8d837cc72f82fdfd0be69c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 7 Jan 2025 09:15:28 +0800 Subject: [PATCH 06/14] fix translation --- options/locale/locale_en-US.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 1aca87b143f2f..5af370df2d9c6 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2627,9 +2627,9 @@ 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_deleted = Submodule %s deleted from %s -diff.submodule_updated = Submodule %s updated from %s to %s +diff.submodule_added = Submodule %[1]s added at %[2]s +diff.submodule_deleted = Submodule %[1]s deleted from %[2]s +diff.submodule_updated = Submodule %[1]s updated from %[2]s to %[3]s releases.desc = Track project versions and downloads. release.releases = Releases From 68a84889617f43f7a75069b3e42e662271123157 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 7 Jan 2025 18:08:27 +0800 Subject: [PATCH 07/14] refactor --- models/repo/repo.go | 54 +-------- models/repo/repo_test.go | 77 ------------- modules/git/commit_info.go | 2 +- modules/git/commit_info_gogit.go | 4 +- modules/git/commit_info_nogogit.go | 4 +- modules/git/commit_submodule.go | 4 + modules/git/commit_submodule_file.go | 132 ++++++---------------- modules/git/commit_submodule_file_test.go | 52 +++------ modules/git/url/url.go | 89 +++++++++++++++ modules/git/url/url_test.go | 100 ++++++++++++++++ options/locale/locale_en-US.ini | 2 +- services/gitdiff/gitdiff.go | 24 ++-- services/gitdiff/submodule.go | 42 ++++--- services/gitdiff/submodule_test.go | 16 +-- templates/repo/diff/box.tmpl | 12 +- templates/repo/view_list.tmpl | 10 +- 16 files changed, 299 insertions(+), 325 deletions(-) diff --git a/models/repo/repo.go b/models/repo/repo.go index 4432fef810211..fb8a6642f500b 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -784,60 +784,10 @@ func GetRepositoryByName(ctx context.Context, ownerID int64, name string) (*Repo return &repo, err } -func parseRepositoryURL(ctx context.Context, repoURL string) (ret struct { - OwnerName, RepoName, RemainingPath string -}, -) { - // possible urls for git: - // https://my.domain/sub-path//[.git] - // git+ssh://user@my.domain//[.git] - // ssh://user@my.domain//[.git] - // user@my.domain:/[.git] - - fillPathParts := func(s string) { - s = strings.TrimPrefix(s, "/") - fields := strings.SplitN(s, "/", 3) - if len(fields) >= 2 { - ret.OwnerName = fields[0] - ret.RepoName = strings.TrimSuffix(fields[1], ".git") - if len(fields) == 3 { - ret.RemainingPath = "/" + fields[2] - } - } - } - - parsed, err := giturl.ParseGitURL(repoURL) - if err != nil { - return ret - } - if parsed.URL.Scheme == "http" || parsed.URL.Scheme == "https" { - if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) { - return ret - } - fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL)) - } else if parsed.URL.Scheme == "ssh" || parsed.URL.Scheme == "git+ssh" { - domainSSH := setting.SSH.Domain - domainCur := httplib.GuessCurrentHostDomain(ctx) - urlDomain, _, _ := net.SplitHostPort(parsed.URL.Host) - urlDomain = util.IfZero(urlDomain, parsed.URL.Host) - if urlDomain == "" { - return ret - } - // check whether URL domain is the App domain - domainMatches := domainSSH == urlDomain - // check whether URL domain is current domain from context - domainMatches = domainMatches || (domainCur != "" && domainCur == urlDomain) - if domainMatches { - fillPathParts(parsed.URL.Path) - } - } - return ret -} - // GetRepositoryByURL returns the repository by given url func GetRepositoryByURL(ctx context.Context, repoURL string) (*Repository, error) { - ret := parseRepositoryURL(ctx, repoURL) - if ret.OwnerName == "" { + ret, err := giturl.ParseRepositoryURL(ctx, repoURL) + if err != nil || ret.OwnerName == "" { return nil, fmt.Errorf("unknown or malformed repository URL") } return GetRepositoryByOwnerAndName(ctx, ret.OwnerName, ret.RepoName) diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go index ffae642285d30..a9e2cdfb757e9 100644 --- a/models/repo/repo_test.go +++ b/models/repo/repo_test.go @@ -4,16 +4,12 @@ package repo import ( - "context" - "net/http" - "net/url" "testing" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" @@ -132,79 +128,6 @@ func TestMetas(t *testing.T) { assert.Equal(t, ",owners,team1,", metas["teams"]) } -func TestParseRepositoryURLPathSegments(t *testing.T) { - defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000")() - - ctxURL, _ := url.Parse("https://gitea") - ctxReq := &http.Request{URL: ctxURL, Header: http.Header{}} - ctxReq.Host = ctxURL.Host - ctxReq.Header.Add("X-Forwarded-Proto", ctxURL.Scheme) - ctx := context.WithValue(context.Background(), httplib.RequestContextKey, ctxReq) - cases := []struct { - input string - ownerName, repoName, remaining string - }{ - {input: "/user/repo"}, - - {input: "https://localhost:3000/user/repo", ownerName: "user", repoName: "repo"}, - {input: "https://external:3000/user/repo"}, - - {input: "https://localhost:3000/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"}, - - {input: "https://gitea/user/repo", ownerName: "user", repoName: "repo"}, - {input: "https://gitea:3333/user/repo"}, - - {input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"}, - {input: "ssh://external:2222/user/repo"}, - - {input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"}, - {input: "git+ssh://user@external/user/repo.git"}, - - {input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"}, - {input: "root@gitea:user/repo.git", ownerName: "user", repoName: "repo"}, - {input: "root@external:user/repo.git"}, - } - - for _, c := range cases { - t.Run(c.input, func(t *testing.T) { - ret := parseRepositoryURL(ctx, c.input) - assert.Equal(t, c.ownerName, ret.OwnerName) - assert.Equal(t, c.repoName, ret.RepoName) - assert.Equal(t, c.remaining, ret.RemainingPath) - }) - } - - t.Run("WithSubpath", func(t *testing.T) { - defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")() - defer test.MockVariableValue(&setting.AppSubURL, "/subpath")() - cases = []struct { - input string - ownerName, repoName, remaining string - }{ - {input: "https://localhost:3000/user/repo"}, - {input: "https://localhost:3000/subpath/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"}, - - {input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"}, - {input: "ssh://external:2222/user/repo"}, - - {input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"}, - {input: "git+ssh://user@external/user/repo.git"}, - - {input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"}, - {input: "root@external:user/repo.git"}, - } - - for _, c := range cases { - t.Run(c.input, func(t *testing.T) { - ret := parseRepositoryURL(ctx, c.input) - assert.Equal(t, c.ownerName, ret.OwnerName) - assert.Equal(t, c.repoName, ret.RepoName) - assert.Equal(t, c.remaining, ret.RemainingPath) - }) - } - }) -} - func TestGetRepositoryByURL(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index 545081275b7a7..c046acbb508c9 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -7,5 +7,5 @@ package git type CommitInfo struct { Entry *TreeEntry Commit *Commit - SubModuleFile *CommitSubModuleFile + SubmoduleFile *CommitSubmoduleFile } diff --git a/modules/git/commit_info_gogit.go b/modules/git/commit_info_gogit.go index 11b44f7c356a4..314c2df72848b 100644 --- a/modules/git/commit_info_gogit.go +++ b/modules/git/commit_info_gogit.go @@ -85,8 +85,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath } else if subModule != nil { subModuleURL = subModule.URL } - subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String()) - commitsInfo[i].SubModuleFile = subModuleFile + subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String()) + commitsInfo[i].SubmoduleFile = subModuleFile } } diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go index 20d586f0ff58c..ef2df0b133ade 100644 --- a/modules/git/commit_info_nogogit.go +++ b/modules/git/commit_info_nogogit.go @@ -79,8 +79,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath } else if subModule != nil { subModuleURL = subModule.URL } - subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String()) - commitsInfo[i].SubModuleFile = subModuleFile + subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String()) + commitsInfo[i].SubmoduleFile = subModuleFile } } diff --git a/modules/git/commit_submodule.go b/modules/git/commit_submodule.go index 6603061da29aa..031fd4e5d02ef 100644 --- a/modules/git/commit_submodule.go +++ b/modules/git/commit_submodule.go @@ -3,6 +3,10 @@ package git +type SubmoduleWebLink struct { + RepoWebLink, CommitWebLink string +} + // GetSubModules get all the submodules of current revision git tree func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) { if c.submoduleCache != nil { diff --git a/modules/git/commit_submodule_file.go b/modules/git/commit_submodule_file.go index 7d84f91d14157..f09ddb479a135 100644 --- a/modules/git/commit_submodule_file.go +++ b/modules/git/commit_submodule_file.go @@ -5,112 +5,46 @@ package git import ( - "fmt" - "net" - "net/url" - "path" - "regexp" - "strings" + "context" - "code.gitea.io/gitea/modules/setting" + giturl "code.gitea.io/gitea/modules/git/url" ) -var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`) - -// CommitSubModuleFile represents a file with submodule type. -type CommitSubModuleFile struct { - refURL string - refID string +// CommitSubmoduleFile represents a file with submodule type. +type CommitSubmoduleFile struct { + refURL string + parsedURL *giturl.RepositoryURL + parsed bool + refID string + repoLink string } -// NewCommitSubModuleFile create a new submodule file -func NewCommitSubModuleFile(refURL, refID string) *CommitSubModuleFile { - return &CommitSubModuleFile{ - refURL: refURL, - refID: refID, - } +// NewCommitSubmoduleFile create a new submodule file +func NewCommitSubmoduleFile(refURL, refID string) *CommitSubmoduleFile { + return &CommitSubmoduleFile{refURL: refURL, refID: refID} } -func getRefURL(refURL, repoFullName string) string { - if refURL == "" { - return "" - } - - // FIXME: use a more generic way to handle the domain and subpath - urlPrefix := setting.AppURL - sshDomain := setting.SSH.Domain - - refURI := strings.TrimSuffix(refURL, ".git") - - prefixURL, _ := url.Parse(urlPrefix) - urlPrefixHostname, _, err := net.SplitHostPort(prefixURL.Host) - if err != nil { - urlPrefixHostname = prefixURL.Host - } - - urlPrefix = strings.TrimSuffix(urlPrefix, "/") - - // FIXME: Need to consider branch - which will require changes in modules/git/commit.go:GetSubModules - // Relative url prefix check (according to git submodule documentation) - if strings.HasPrefix(refURI, "./") || strings.HasPrefix(refURI, "../") { - return urlPrefix + path.Clean(path.Join("/", repoFullName, refURI)) - } - - if !strings.Contains(refURI, "://") { - // scp style syntax which contains *no* port number after the : (and is not parsed by net/url) - // ex: git@try.gitea.io:go-gitea/gitea - match := scpSyntax.FindAllStringSubmatch(refURI, -1) - if len(match) > 0 { - m := match[0] - refHostname := m[2] - pth := m[3] - - if !strings.HasPrefix(pth, "/") { - pth = "/" + pth - } - - if urlPrefixHostname == refHostname || refHostname == sshDomain { - return urlPrefix + path.Clean(path.Join("/", pth)) - } - return "http://" + refHostname + pth - } - } - - ref, err := url.Parse(refURI) - if err != nil { - return "" - } - - refHostname, _, err := net.SplitHostPort(ref.Host) - if err != nil { - refHostname = ref.Host - } - - supportedSchemes := []string{"http", "https", "git", "ssh", "git+ssh"} - - for _, scheme := range supportedSchemes { - if ref.Scheme == scheme { - if ref.Scheme == "http" || ref.Scheme == "https" { - if len(ref.User.Username()) > 0 { - return ref.Scheme + "://" + fmt.Sprintf("%v", ref.User) + "@" + ref.Host + ref.Path - } - return ref.Scheme + "://" + ref.Host + ref.Path - } else if urlPrefixHostname == refHostname || refHostname == sshDomain { - return urlPrefix + path.Clean(path.Join("/", ref.Path)) - } - return "http://" + refHostname + ref.Path - } - } - - return "" -} - -// RefURL guesses and returns reference URL. -func (sf *CommitSubModuleFile) RefURL(repoFullName string) string { - return getRefURL(sf.refURL, repoFullName) +func (sf *CommitSubmoduleFile) RefID() string { + return sf.refID } -// RefID returns reference ID. -func (sf *CommitSubModuleFile) RefID() string { - return sf.refID +func (sf *CommitSubmoduleFile) SubmoduleWebLink(ctx context.Context, optCommitID ...string) *SubmoduleWebLink { + if !sf.parsed { + sf.parsed = true + parsedURL, err := giturl.ParseRepositoryURL(ctx, sf.refURL) + if err != nil { + return nil + } + sf.parsedURL = parsedURL + sf.repoLink = giturl.MakeRepositoryWebLink(sf.parsedURL) + } + var commitLink string + if len(optCommitID) == 2 { + commitLink = sf.repoLink + "/compare/" + optCommitID[0] + "..." + optCommitID[1] + } else if len(optCommitID) == 1 { + commitLink = sf.repoLink + "/commit/" + optCommitID[0] + } else { + commitLink = sf.repoLink + "/commit/" + sf.refID + } + return &SubmoduleWebLink{RepoWebLink: sf.repoLink, CommitWebLink: commitLink} } diff --git a/modules/git/commit_submodule_file_test.go b/modules/git/commit_submodule_file_test.go index 6ab30b26d153e..d73f6c801deae 100644 --- a/modules/git/commit_submodule_file_test.go +++ b/modules/git/commit_submodule_file_test.go @@ -1,49 +1,27 @@ -// Copyright 2018 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package git import ( + "context" "testing" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/test" - "github.com/stretchr/testify/assert" ) -func TestCommitSubModuleFileGetRefURL(t *testing.T) { - kases := []struct { - refURL string - prefixURL string - parentPath string - SSHDomain string - expect string - }{ - {"git://github.com/user1/repo1", "/", "user1/repo2", "", "http://github.com/user1/repo1"}, - {"https://localhost/user1/repo1.git", "/", "user1/repo2", "", "https://localhost/user1/repo1"}, - {"http://localhost/user1/repo1.git", "/", "owner/reponame", "", "http://localhost/user1/repo1"}, - {"git@github.com:user1/repo1.git", "/", "owner/reponame", "", "http://github.com/user1/repo1"}, - {"ssh://git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/zefie/lge_g6_kernel_scripts"}, - {"git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/2222/zefie/lge_g6_kernel_scripts"}, - {"git@try.gitea.io:go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"}, - {"ssh://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"}, - {"git://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"}, - {"ssh://git@127.0.0.1:9999/go-gitea/gitea", "https://127.0.0.1:3000/", "go-gitea/sdk", "", "https://127.0.0.1:3000/go-gitea/gitea"}, - {"https://gitea.com:3000/user1/repo1.git", "https://127.0.0.1:3000/", "user/repo2", "", "https://gitea.com:3000/user1/repo1"}, - {"https://example.gitea.com/gitea/user1/repo1.git", "https://example.gitea.com/gitea/", "", "user/repo2", "https://example.gitea.com/gitea/user1/repo1"}, - {"https://username:password@github.com/username/repository.git", "/", "username/repository2", "", "https://username:password@github.com/username/repository"}, - {"somethingbad", "https://127.0.0.1:3000/go-gitea/gitea", "/", "", ""}, - {"git@localhost:user/repo", "https://localhost/", "user2/repo1", "", "https://localhost/user/repo"}, - {"../path/to/repo.git/", "https://localhost/", "user/repo2", "", "https://localhost/user/path/to/repo.git"}, - {"ssh://git@ssh.gitea.io:2222/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "ssh.gitea.io", "https://try.gitea.io/go-gitea/gitea"}, - } +func TestCommitSubmoduleLink(t *testing.T) { + sf := NewCommitSubmoduleFile("git@github.com:user/repo.git", "aaaa") + + wl := sf.SubmoduleWebLink(context.Background()) + assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink) + assert.Equal(t, "https://github.com/user/repo/commit/aaaa", wl.CommitWebLink) + + wl = sf.SubmoduleWebLink(context.Background(), "1111") + assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink) + assert.Equal(t, "https://github.com/user/repo/commit/1111", wl.CommitWebLink) - for _, kase := range kases { - t.Run(kase.refURL, func(t *testing.T) { - defer test.MockVariableValue(&setting.AppURL, kase.prefixURL)() - defer test.MockVariableValue(&setting.SSH.Domain, kase.SSHDomain)() - assert.EqualValues(t, kase.expect, getRefURL(kase.refURL, kase.parentPath)) - }) - } + wl = sf.SubmoduleWebLink(context.Background(), "1111", "2222") + assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink) + assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink) } diff --git a/modules/git/url/url.go b/modules/git/url/url.go index 667e542199c8f..1c5e8377a6c77 100644 --- a/modules/git/url/url.go +++ b/modules/git/url/url.go @@ -4,9 +4,15 @@ package url import ( + "context" "fmt" + "net" stdurl "net/url" "strings" + + "code.gitea.io/gitea/modules/httplib" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) // ErrWrongURLFormat represents an error with wrong url format @@ -90,3 +96,86 @@ func ParseGitURL(remote string) (*GitURL, error) { extraMark: 2, }, nil } + +type RepositoryURL struct { + GitURL *GitURL + + // if the URL belongs to current Gitea instance, then the below fields have values + OwnerName string + RepoName string + RemainingPath string +} + +// ParseRepositoryURL tries to parse a Git URL and extract the owner/repository name if it belongs to current Gitea instance. +func ParseRepositoryURL(ctx context.Context, repoURL string) (*RepositoryURL, error) { + // possible urls for git: + // https://my.domain/sub-path//[.git] + // git+ssh://user@my.domain//[.git] + // ssh://user@my.domain//[.git] + // user@my.domain:/[.git] + parsed, err := ParseGitURL(repoURL) + if err != nil { + return nil, err + } + + ret := &RepositoryURL{} + ret.GitURL = parsed + + fillPathParts := func(s string) { + s = strings.TrimPrefix(s, "/") + fields := strings.SplitN(s, "/", 3) + if len(fields) >= 2 { + ret.OwnerName = fields[0] + ret.RepoName = strings.TrimSuffix(fields[1], ".git") + if len(fields) == 3 { + ret.RemainingPath = "/" + fields[2] + } + } + } + + if parsed.URL.Scheme == "http" || parsed.URL.Scheme == "https" { + if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) { + return ret, nil + } + fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL)) + } else if parsed.URL.Scheme == "ssh" || parsed.URL.Scheme == "git+ssh" { + domainSSH := setting.SSH.Domain + domainCur := httplib.GuessCurrentHostDomain(ctx) + urlDomain, _, _ := net.SplitHostPort(parsed.URL.Host) + urlDomain = util.IfZero(urlDomain, parsed.URL.Host) + if urlDomain == "" { + return ret, nil + } + // check whether URL domain is the App domain + domainMatches := domainSSH == urlDomain + // check whether URL domain is current domain from context + domainMatches = domainMatches || (domainCur != "" && domainCur == urlDomain) + if domainMatches { + fillPathParts(parsed.URL.Path) + } + } + return ret, nil +} + +// MakeRepositoryWebLink generates a web link (http/https) for a git repository (by guessing sometimes) +func MakeRepositoryWebLink(repoURL *RepositoryURL) string { + if repoURL.OwnerName != "" { + return setting.AppSubURL + "/" + repoURL.OwnerName + "/" + repoURL.RepoName + } + + // now, let's guess, for example: + // * git@github.com:owner/submodule.git + // * https://github.com/example/submodule1.git + if repoURL.GitURL.Scheme == "http" || repoURL.GitURL.Scheme == "https" { + return strings.TrimSuffix(repoURL.GitURL.String(), ".git") + } else if repoURL.GitURL.Scheme == "ssh" || repoURL.GitURL.Scheme == "git+ssh" { + hostname, _, _ := net.SplitHostPort(repoURL.GitURL.Host) + hostname = util.IfZero(hostname, repoURL.GitURL.Host) + urlPath := strings.TrimSuffix(repoURL.GitURL.Path, ".git") + urlPath = strings.TrimPrefix(urlPath, "/") + urlFull := fmt.Sprintf("https://%s/%s", hostname, urlPath) + urlFull = strings.TrimSuffix(urlFull, "/") + return urlFull + } + return "" +} diff --git a/modules/git/url/url_test.go b/modules/git/url/url_test.go index a23121a907ad9..93f58860293b7 100644 --- a/modules/git/url/url_test.go +++ b/modules/git/url/url_test.go @@ -4,9 +4,15 @@ package url import ( + "context" + "net/http" "net/url" "testing" + "code.gitea.io/gitea/modules/httplib" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "github.com/stretchr/testify/assert" ) @@ -164,3 +170,97 @@ func TestParseGitURLs(t *testing.T) { }) } } + +func TestParseRepositoryURL(t *testing.T) { + defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000")() + + ctxURL, _ := url.Parse("https://gitea") + ctxReq := &http.Request{URL: ctxURL, Header: http.Header{}} + ctxReq.Host = ctxURL.Host + ctxReq.Header.Add("X-Forwarded-Proto", ctxURL.Scheme) + ctx := context.WithValue(context.Background(), httplib.RequestContextKey, ctxReq) + cases := []struct { + input string + ownerName, repoName, remaining string + }{ + {input: "/user/repo"}, + + {input: "https://localhost:3000/user/repo", ownerName: "user", repoName: "repo"}, + {input: "https://external:3000/user/repo"}, + + {input: "https://localhost:3000/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"}, + + {input: "https://gitea/user/repo", ownerName: "user", repoName: "repo"}, + {input: "https://gitea:3333/user/repo"}, + + {input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"}, + {input: "ssh://external:2222/user/repo"}, + + {input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"}, + {input: "git+ssh://user@external/user/repo.git"}, + + {input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"}, + {input: "root@gitea:user/repo.git", ownerName: "user", repoName: "repo"}, + {input: "root@external:user/repo.git"}, + } + + for _, c := range cases { + t.Run(c.input, func(t *testing.T) { + ret, _ := ParseRepositoryURL(ctx, c.input) + assert.Equal(t, c.ownerName, ret.OwnerName) + assert.Equal(t, c.repoName, ret.RepoName) + assert.Equal(t, c.remaining, ret.RemainingPath) + }) + } + + t.Run("WithSubpath", func(t *testing.T) { + defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")() + defer test.MockVariableValue(&setting.AppSubURL, "/subpath")() + cases = []struct { + input string + ownerName, repoName, remaining string + }{ + {input: "https://localhost:3000/user/repo"}, + {input: "https://localhost:3000/subpath/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"}, + + {input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"}, + {input: "ssh://external:2222/user/repo"}, + + {input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"}, + {input: "git+ssh://user@external/user/repo.git"}, + + {input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"}, + {input: "root@external:user/repo.git"}, + } + + for _, c := range cases { + t.Run(c.input, func(t *testing.T) { + ret, _ := ParseRepositoryURL(ctx, c.input) + assert.Equal(t, c.ownerName, ret.OwnerName) + assert.Equal(t, c.repoName, ret.RepoName) + assert.Equal(t, c.remaining, ret.RemainingPath) + }) + } + }) +} + +func TestMakeRepositoryBaseLink(t *testing.T) { + defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")() + defer test.MockVariableValue(&setting.AppSubURL, "/subpath")() + + u, err := ParseRepositoryURL(context.Background(), "https://localhost:3000/subpath/user/repo.git") + assert.NoError(t, err) + assert.Equal(t, "/subpath/user/repo", MakeRepositoryWebLink(u)) + + u, err = ParseRepositoryURL(context.Background(), "https://github.com/owner/repo.git") + assert.NoError(t, err) + assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u)) + + u, err = ParseRepositoryURL(context.Background(), "git@github.com:owner/repo.git") + assert.NoError(t, err) + assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u)) + + u, err = ParseRepositoryURL(context.Background(), "git+ssh://other:123/owner/repo.git") + assert.NoError(t, err) + assert.Equal(t, "https://other/owner/repo", MakeRepositoryWebLink(u)) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c6ce8c20f258d..140e2efe57111 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2630,7 +2630,7 @@ diff.show_file_tree = Show file tree diff.hide_file_tree = Hide file tree diff.submodule_added = Submodule %[1]s added at %[2]s diff.submodule_deleted = Submodule %[1]s deleted from %[2]s -diff.submodule_updated = Submodule %[1]s updated from %[2]s to %[3]s +diff.submodule_updated = Submodule %[1]s updated: %[2]s releases.desc = Track project versions and downloads. release.releases = Releases diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 3328df91b1296..ce20bf2efc102 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -372,8 +372,8 @@ type DiffFile struct { Mode string OldMode string - IsSubmodule bool // if IsSubmodule==true, then there must be a SubmoduleInfo - SubmoduleInfo *SubmoduleInfo + IsSubmodule bool // if IsSubmodule==true, then there must be a SubmoduleDiffInfo + SubmoduleDiffInfo *SubmoduleDiffInfo } // GetType returns type of diff file. @@ -612,7 +612,7 @@ parsingLoop: curFile.Mode = prepareValue(line, "new mode ") } if strings.HasSuffix(line, " 160000\n") { - curFile.IsSubmodule, curFile.SubmoduleInfo = true, &SubmoduleInfo{} + curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{} } case strings.HasPrefix(line, "rename from "): curFile.IsRenamed = true @@ -647,17 +647,17 @@ parsingLoop: curFile.Mode = prepareValue(line, "new file mode ") } if strings.HasSuffix(line, " 160000\n") { - curFile.IsSubmodule, curFile.SubmoduleInfo = true, &SubmoduleInfo{} + curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{} } case strings.HasPrefix(line, "deleted"): curFile.Type = DiffFileDel curFile.IsDeleted = true if strings.HasSuffix(line, " 160000\n") { - curFile.IsSubmodule, curFile.SubmoduleInfo = true, &SubmoduleInfo{} + curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{} } case strings.HasPrefix(line, "index"): if strings.HasSuffix(line, " 160000\n") { - curFile.IsSubmodule, curFile.SubmoduleInfo = true, &SubmoduleInfo{} + curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{} } case strings.HasPrefix(line, "similarity index 100%"): curFile.Type = DiffFileRename @@ -918,9 +918,9 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact curSection.Lines = append(curSection.Lines, diffLine) // Parse submodule additions - if curFile.SubmoduleInfo != nil { + if curFile.SubmoduleDiffInfo != nil { if ref, found := bytes.CutPrefix(lineBytes, []byte("+Subproject commit ")); found { - curFile.SubmoduleInfo.NewRefID = string(bytes.TrimSpace(ref)) + curFile.SubmoduleDiffInfo.NewRefID = string(bytes.TrimSpace(ref)) } } case '-': @@ -946,9 +946,9 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact curSection.Lines = append(curSection.Lines, diffLine) // Parse submodule deletion - if curFile.SubmoduleInfo != nil { + if curFile.SubmoduleDiffInfo != nil { if ref, found := bytes.CutPrefix(lineBytes, []byte("-Subproject commit ")); found { - curFile.SubmoduleInfo.PreviousRefID = string(bytes.TrimSpace(ref)) + curFile.SubmoduleDiffInfo.PreviousRefID = string(bytes.TrimSpace(ref)) } } case ' ': @@ -1211,8 +1211,8 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi } // Populate Submodule URLs - if diffFile.SubmoduleInfo != nil { - err := diffFile.SubmoduleInfo.PopulateURL(diffFile, beforeCommit, commit) + if diffFile.SubmoduleDiffInfo != nil { + err := diffFile.SubmoduleDiffInfo.PopulateURL(diffFile, beforeCommit, commit) if err != nil { return nil, err } diff --git a/services/gitdiff/submodule.go b/services/gitdiff/submodule.go index 12460d0b49e8e..9e08eaa96001d 100644 --- a/services/gitdiff/submodule.go +++ b/services/gitdiff/submodule.go @@ -4,6 +4,7 @@ package gitdiff import ( + "context" "html/template" "code.gitea.io/gitea/modules/base" @@ -12,13 +13,15 @@ import ( "code.gitea.io/gitea/modules/log" ) -type SubmoduleInfo struct { - SubmoduleURL string +type SubmoduleDiffInfo struct { + SubmoduleName string + SubmoduleFile *git.CommitSubmoduleFile NewRefID string PreviousRefID string } -func (si *SubmoduleInfo) PopulateURL(diffFile *DiffFile, leftCommit, rightCommit *git.Commit) error { +func (si *SubmoduleDiffInfo) PopulateURL(diffFile *DiffFile, leftCommit, rightCommit *git.Commit) error { + si.SubmoduleName = diffFile.Name submoduleCommit := rightCommit // If the submodule is added or updated, check at the right commit if diffFile.IsDeleted { submoduleCommit = leftCommit // If the submodule is deleted, check at the left commit @@ -30,35 +33,28 @@ func (si *SubmoduleInfo) PopulateURL(diffFile *DiffFile, leftCommit, rightCommit return nil // ignore the error, do not cause 500 errors for end users } if submodule != nil { - si.SubmoduleURL = submodule.URL + si.SubmoduleFile = git.NewCommitSubmoduleFile(submodule.URL, submoduleCommit.ID.String()) } } return nil } -func (si *SubmoduleInfo) NewRefIDLinkHTML() template.HTML { - refURL := si.refURL() - if si.PreviousRefID == "" { - return htmlutil.HTMLFormat(`%s`, refURL, si.NewRefID, base.ShortSha(si.NewRefID)) - } - return htmlutil.HTMLFormat(`%s`, refURL, si.PreviousRefID, si.NewRefID, base.ShortSha(si.NewRefID)) +func (si *SubmoduleDiffInfo) PreviousRefIDLinkHTML(ctx context.Context) template.HTML { + webLink := si.SubmoduleFile.SubmoduleWebLink(ctx, si.PreviousRefID) + return htmlutil.HTMLFormat(`%s`, webLink.CommitWebLink, base.ShortSha(si.PreviousRefID)) } -func (si *SubmoduleInfo) PreviousRefIDLinkHTML() template.HTML { - refURL := si.refURL() - return htmlutil.HTMLFormat(`%s`, refURL, si.PreviousRefID, base.ShortSha(si.PreviousRefID)) +func (si *SubmoduleDiffInfo) NewRefIDLinkHTML(ctx context.Context) template.HTML { + webLink := si.SubmoduleFile.SubmoduleWebLink(ctx, si.NewRefID) + return htmlutil.HTMLFormat(`%s`, webLink.CommitWebLink, base.ShortSha(si.NewRefID)) } -func (si *SubmoduleInfo) SubmoduleRepoLinkHTML(name string) template.HTML { - refURL := si.refURL() - return htmlutil.HTMLFormat(`%s`, refURL, name) +func (si *SubmoduleDiffInfo) CompareRefIDLinkHTML(ctx context.Context) template.HTML { + webLink := si.SubmoduleFile.SubmoduleWebLink(ctx, si.PreviousRefID, si.NewRefID) + return htmlutil.HTMLFormat(`%s...%s`, webLink.CommitWebLink, base.ShortSha(si.PreviousRefID), base.ShortSha(si.NewRefID)) } -// RefURL guesses and returns reference URL. -func (si *SubmoduleInfo) refURL() string { - // FIXME: use unified way to handle domain and subpath - // FIXME: RefURL(repoFullName) is only used to provide relative path support for submodules - // it is not our use case because it won't work for git clone via http/ssh - // FIXME: the current RefURL is not right, it doesn't consider the subpath - return git.NewCommitSubModuleFile(si.SubmoduleURL, "").RefURL("") +func (si *SubmoduleDiffInfo) SubmoduleRepoLinkHTML(ctx context.Context) template.HTML { + webLink := si.SubmoduleFile.SubmoduleWebLink(ctx) + return htmlutil.HTMLFormat(`%s`, webLink.RepoWebLink, si.SubmoduleName) } diff --git a/services/gitdiff/submodule_test.go b/services/gitdiff/submodule_test.go index 136060e4945a4..33b7c7152e0d1 100644 --- a/services/gitdiff/submodule_test.go +++ b/services/gitdiff/submodule_test.go @@ -17,7 +17,7 @@ func TestParseSubmoduleInfo(t *testing.T) { type testcase struct { name string gitdiff string - infos map[int]SubmoduleInfo + infos map[int]SubmoduleDiffInfo } tests := []testcase{ @@ -40,7 +40,7 @@ index 0000000..68972a9 @@ -0,0 +1 @@ +Subproject commit 68972a994719ae5c74e28d8fa82fa27c23399bc8 `, - infos: map[int]SubmoduleInfo{ + infos: map[int]SubmoduleDiffInfo{ 1: {NewRefID: "68972a994719ae5c74e28d8fa82fa27c23399bc8"}, }, }, @@ -54,7 +54,7 @@ index 68972a9..c8ffe77 160000 -Subproject commit 68972a994719ae5c74e28d8fa82fa27c23399bc8 +Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d `, - infos: map[int]SubmoduleInfo{ + infos: map[int]SubmoduleDiffInfo{ 0: { PreviousRefID: "68972a994719ae5c74e28d8fa82fa27c23399bc8", NewRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d", @@ -96,7 +96,7 @@ index c8ffe77..0000000 @@ -1 +0,0 @@ -Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d `, - infos: map[int]SubmoduleInfo{ + infos: map[int]SubmoduleDiffInfo{ 1: { PreviousRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d", }, @@ -128,7 +128,7 @@ index 0000000..8eefa1f @@ -0,0 +1 @@ +Subproject commit 8eefa1f6dedf2488db2c9e12c916e8e51f673160 `, - infos: map[int]SubmoduleInfo{ + infos: map[int]SubmoduleDiffInfo{ 1: { PreviousRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d", }, @@ -162,7 +162,7 @@ index 0000000..33a9488 @@ -0,0 +1 @@ +example `, - infos: map[int]SubmoduleInfo{ + infos: map[int]SubmoduleDiffInfo{ 1: { PreviousRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d", }, @@ -193,7 +193,7 @@ index 0000000..68972a9 @@ -0,0 +1 @@ +Subproject commit 68972a994719ae5c74e28d8fa82fa27c23399bc8 `, - infos: map[int]SubmoduleInfo{ + infos: map[int]SubmoduleDiffInfo{ 2: { NewRefID: "68972a994719ae5c74e28d8fa82fa27c23399bc8", }, @@ -210,7 +210,7 @@ index 0000000..68972a9 for i, expected := range testcase.infos { actual := diff.Files[i] assert.NotNil(t, actual) - assert.Equal(t, expected, *actual.SubmoduleInfo) + assert.Equal(t, expected, *actual.SubmoduleDiffInfo) } }) } diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index fbe2bad9a414f..f9a4351f40942 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -197,15 +197,15 @@ {{ctx.Locale.Tr "repo.diff.bin_not_shown"}} {{end}} - {{else if and $file.SubmoduleInfo}} -
{{svg "octicon-file-submodule"}} {{$submodule := $file.SubmoduleInfo -}} - {{- $submoduleName := ($submodule.SubmoduleRepoLinkHTML $file.Name) -}} + {{else if $file.SubmoduleDiffInfo}} +
{{svg "octicon-file-submodule"}} {{$submoduleDiffInfo := $file.SubmoduleDiffInfo -}} + {{- $submoduleName := ($submoduleDiffInfo.SubmoduleRepoLinkHTML ctx) -}} {{- if $file.IsDeleted -}} - {{- ctx.Locale.Tr "repo.diff.submodule_deleted" $submoduleName $submodule.PreviousRefIDLinkHTML -}} + {{- ctx.Locale.Tr "repo.diff.submodule_deleted" $submoduleName ($submoduleDiffInfo.PreviousRefIDLinkHTML ctx) -}} {{- else if $file.IsCreated -}} - {{- ctx.Locale.Tr "repo.diff.submodule_added" $submoduleName $submodule.NewRefIDLinkHTML -}} + {{- ctx.Locale.Tr "repo.diff.submodule_added" $submoduleName ($submoduleDiffInfo.NewRefIDLinkHTML ctx) -}} {{- else -}} - {{- ctx.Locale.Tr "repo.diff.submodule_updated" $submoduleName $submodule.PreviousRefIDLinkHTML $submodule.NewRefIDLinkHTML -}} + {{- ctx.Locale.Tr "repo.diff.submodule_updated" $submoduleName ($submoduleDiffInfo.CompareRefIDLinkHTML ctx) -}} {{end}}
{{else}} diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index 936fba77cea2f..7540931010d1c 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -13,15 +13,15 @@
{{$entry := $item.Entry}} {{$commit := $item.Commit}} - {{$subModuleFile := $item.SubModuleFile}} + {{$submoduleFile := $item.SubmoduleFile}}
{{if $entry.IsSubModule}} {{svg "octicon-file-submodule"}} - {{$refURL := $subModuleFile.RefURL $.Repository.FullName}} - {{if $refURL}} - {{$entry.Name}} @ {{ShortSha $subModuleFile.RefID}} + {{$submoduleLink := $submoduleFile.SubmoduleWebLink ctx}} + {{if $submoduleLink}} + {{$entry.Name}} @ {{ShortSha $submoduleFile.RefID}} {{else}} - {{$entry.Name}} @ {{ShortSha $subModuleFile.RefID}} + {{$entry.Name}} @ {{ShortSha $submoduleFile.RefID}} {{end}} {{else}} {{if $entry.IsDir}} From a4d34ddcbbeecc673930faa7f5ffea58e8094118 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 7 Jan 2025 18:29:59 +0800 Subject: [PATCH 08/14] fine tune edge cases --- modules/git/commit_submodule_file.go | 6 +++- modules/git/commit_submodule_file_test.go | 3 ++ services/gitdiff/gitdiff.go | 5 +-- services/gitdiff/submodule.go | 39 +++++++++++++++-------- templates/repo/diff/box.tmpl | 2 +- 5 files changed, 36 insertions(+), 19 deletions(-) diff --git a/modules/git/commit_submodule_file.go b/modules/git/commit_submodule_file.go index f09ddb479a135..2ac744fbf61fd 100644 --- a/modules/git/commit_submodule_file.go +++ b/modules/git/commit_submodule_file.go @@ -25,10 +25,14 @@ func NewCommitSubmoduleFile(refURL, refID string) *CommitSubmoduleFile { } func (sf *CommitSubmoduleFile) RefID() string { - return sf.refID + return sf.refID // this function is only used in templates } +// SubmoduleWebLink tries to make some web links for a submodule, it also works on "nil" receiver func (sf *CommitSubmoduleFile) SubmoduleWebLink(ctx context.Context, optCommitID ...string) *SubmoduleWebLink { + if sf == nil { + return nil + } if !sf.parsed { sf.parsed = true parsedURL, err := giturl.ParseRepositoryURL(ctx, sf.refURL) diff --git a/modules/git/commit_submodule_file_test.go b/modules/git/commit_submodule_file_test.go index d73f6c801deae..4b5b7676126b8 100644 --- a/modules/git/commit_submodule_file_test.go +++ b/modules/git/commit_submodule_file_test.go @@ -24,4 +24,7 @@ func TestCommitSubmoduleLink(t *testing.T) { wl = sf.SubmoduleWebLink(context.Background(), "1111", "2222") assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink) assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink) + + wl = (*CommitSubmoduleFile)(nil).SubmoduleWebLink(context.Background()) + assert.Nil(t, wl) } diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index ce20bf2efc102..f42686bb7187d 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1212,10 +1212,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi // Populate Submodule URLs if diffFile.SubmoduleDiffInfo != nil { - err := diffFile.SubmoduleDiffInfo.PopulateURL(diffFile, beforeCommit, commit) - if err != nil { - return nil, err - } + diffFile.SubmoduleDiffInfo.PopulateURL(diffFile, beforeCommit, commit) } if !isVendored.Has() { diff --git a/services/gitdiff/submodule.go b/services/gitdiff/submodule.go index 9e08eaa96001d..5ee916927b2a5 100644 --- a/services/gitdiff/submodule.go +++ b/services/gitdiff/submodule.go @@ -15,46 +15,59 @@ import ( type SubmoduleDiffInfo struct { SubmoduleName string - SubmoduleFile *git.CommitSubmoduleFile + SubmoduleFile *git.CommitSubmoduleFile // it might be nil if the submodule is not found or unable to parse NewRefID string PreviousRefID string } -func (si *SubmoduleDiffInfo) PopulateURL(diffFile *DiffFile, leftCommit, rightCommit *git.Commit) error { +func (si *SubmoduleDiffInfo) PopulateURL(diffFile *DiffFile, leftCommit, rightCommit *git.Commit) { si.SubmoduleName = diffFile.Name submoduleCommit := rightCommit // If the submodule is added or updated, check at the right commit if diffFile.IsDeleted { submoduleCommit = leftCommit // If the submodule is deleted, check at the left commit } - if submoduleCommit != nil { - submodule, err := submoduleCommit.GetSubModule(diffFile.GetDiffFileName()) - if err != nil { - log.Error("Unable to PopulateURL for submodule %q: GetSubModule: %v", diffFile.GetDiffFileName(), err) - return nil // ignore the error, do not cause 500 errors for end users - } - if submodule != nil { - si.SubmoduleFile = git.NewCommitSubmoduleFile(submodule.URL, submoduleCommit.ID.String()) - } - } - return nil + if submoduleCommit == nil { + return + } + + submodule, err := submoduleCommit.GetSubModule(diffFile.GetDiffFileName()) + if err != nil { + log.Error("Unable to PopulateURL for submodule %q: GetSubModule: %v", diffFile.GetDiffFileName(), err) + return // ignore the error, do not cause 500 errors for end users + } + if submodule != nil { + si.SubmoduleFile = git.NewCommitSubmoduleFile(submodule.URL, submoduleCommit.ID.String()) + } } func (si *SubmoduleDiffInfo) PreviousRefIDLinkHTML(ctx context.Context) template.HTML { webLink := si.SubmoduleFile.SubmoduleWebLink(ctx, si.PreviousRefID) + if webLink == nil { + return htmlutil.HTMLFormat("%s", base.ShortSha(si.PreviousRefID)) + } return htmlutil.HTMLFormat(`%s`, webLink.CommitWebLink, base.ShortSha(si.PreviousRefID)) } func (si *SubmoduleDiffInfo) NewRefIDLinkHTML(ctx context.Context) template.HTML { webLink := si.SubmoduleFile.SubmoduleWebLink(ctx, si.NewRefID) + if webLink == nil { + return htmlutil.HTMLFormat("%s", base.ShortSha(si.NewRefID)) + } return htmlutil.HTMLFormat(`%s`, webLink.CommitWebLink, base.ShortSha(si.NewRefID)) } func (si *SubmoduleDiffInfo) CompareRefIDLinkHTML(ctx context.Context) template.HTML { webLink := si.SubmoduleFile.SubmoduleWebLink(ctx, si.PreviousRefID, si.NewRefID) + if webLink == nil { + return htmlutil.HTMLFormat("%s...%s", base.ShortSha(si.PreviousRefID), base.ShortSha(si.NewRefID)) + } return htmlutil.HTMLFormat(`%s...%s`, webLink.CommitWebLink, base.ShortSha(si.PreviousRefID), base.ShortSha(si.NewRefID)) } func (si *SubmoduleDiffInfo) SubmoduleRepoLinkHTML(ctx context.Context) template.HTML { webLink := si.SubmoduleFile.SubmoduleWebLink(ctx) + if webLink == nil { + return htmlutil.HTMLFormat("%s", si.SubmoduleName) + } return htmlutil.HTMLFormat(`%s`, webLink.RepoWebLink, si.SubmoduleName) } diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index f9a4351f40942..616360115cf90 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -199,7 +199,7 @@
{{else if $file.SubmoduleDiffInfo}}
{{svg "octicon-file-submodule"}} {{$submoduleDiffInfo := $file.SubmoduleDiffInfo -}} - {{- $submoduleName := ($submoduleDiffInfo.SubmoduleRepoLinkHTML ctx) -}} + {{- $submoduleName := $submoduleDiffInfo.SubmoduleRepoLinkHTML ctx -}} {{- if $file.IsDeleted -}} {{- ctx.Locale.Tr "repo.diff.submodule_deleted" $submoduleName ($submoduleDiffInfo.PreviousRefIDLinkHTML ctx) -}} {{- else if $file.IsCreated -}} From b06374d4cd3ddad5450562e038cd8be240467154 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 7 Jan 2025 18:49:31 +0800 Subject: [PATCH 09/14] add more tests --- services/gitdiff/submodule.go | 16 ++++------------ services/gitdiff/submodule_test.go | 17 ++++++++++++++++- templates/repo/diff/box.tmpl | 4 ++-- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/services/gitdiff/submodule.go b/services/gitdiff/submodule.go index 5ee916927b2a5..02ca666544c10 100644 --- a/services/gitdiff/submodule.go +++ b/services/gitdiff/submodule.go @@ -40,20 +40,12 @@ func (si *SubmoduleDiffInfo) PopulateURL(diffFile *DiffFile, leftCommit, rightCo } } -func (si *SubmoduleDiffInfo) PreviousRefIDLinkHTML(ctx context.Context) template.HTML { - webLink := si.SubmoduleFile.SubmoduleWebLink(ctx, si.PreviousRefID) +func (si *SubmoduleDiffInfo) CommitRefIDLinkHTML(ctx context.Context, commitID string) template.HTML { + webLink := si.SubmoduleFile.SubmoduleWebLink(ctx, commitID) if webLink == nil { - return htmlutil.HTMLFormat("%s", base.ShortSha(si.PreviousRefID)) + return htmlutil.HTMLFormat("%s", base.ShortSha(commitID)) } - return htmlutil.HTMLFormat(`%s`, webLink.CommitWebLink, base.ShortSha(si.PreviousRefID)) -} - -func (si *SubmoduleDiffInfo) NewRefIDLinkHTML(ctx context.Context) template.HTML { - webLink := si.SubmoduleFile.SubmoduleWebLink(ctx, si.NewRefID) - if webLink == nil { - return htmlutil.HTMLFormat("%s", base.ShortSha(si.NewRefID)) - } - return htmlutil.HTMLFormat(`%s`, webLink.CommitWebLink, base.ShortSha(si.NewRefID)) + return htmlutil.HTMLFormat(`%s`, webLink.CommitWebLink, base.ShortSha(commitID)) } func (si *SubmoduleDiffInfo) CompareRefIDLinkHTML(ctx context.Context) template.HTML { diff --git a/services/gitdiff/submodule_test.go b/services/gitdiff/submodule_test.go index 33b7c7152e0d1..89f32c0e0c849 100644 --- a/services/gitdiff/submodule_test.go +++ b/services/gitdiff/submodule_test.go @@ -4,10 +4,12 @@ package gitdiff import ( + "context" "strings" "testing" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" @@ -217,5 +219,18 @@ index 0000000..68972a9 } func TestSubmoduleInfo(t *testing.T) { - // TODO: test NewRefIDLinkHTML PreviousRefIDLinkHTML SubmoduleRepoLinkHTML after we get the unifed "RefURL" function + sdi := &SubmoduleDiffInfo{ + SubmoduleName: "name", + PreviousRefID: "aaaa", + NewRefID: "bbbb", + } + ctx := context.Background() + assert.EqualValues(t, "1111", sdi.CommitRefIDLinkHTML(ctx, "1111")) + assert.EqualValues(t, "aaaa...bbbb", sdi.CompareRefIDLinkHTML(ctx)) + assert.EqualValues(t, "name", sdi.SubmoduleRepoLinkHTML(ctx)) + + sdi.SubmoduleFile = git.NewCommitSubmoduleFile("https://github.com/owner/repo", "1234") + assert.EqualValues(t, `1111`, sdi.CommitRefIDLinkHTML(ctx, "1111")) + assert.EqualValues(t, `aaaa...bbbb`, sdi.CompareRefIDLinkHTML(ctx)) + assert.EqualValues(t, `name`, sdi.SubmoduleRepoLinkHTML(ctx)) } diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 616360115cf90..5eda427137501 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -201,9 +201,9 @@
{{svg "octicon-file-submodule"}} {{$submoduleDiffInfo := $file.SubmoduleDiffInfo -}} {{- $submoduleName := $submoduleDiffInfo.SubmoduleRepoLinkHTML ctx -}} {{- if $file.IsDeleted -}} - {{- ctx.Locale.Tr "repo.diff.submodule_deleted" $submoduleName ($submoduleDiffInfo.PreviousRefIDLinkHTML ctx) -}} + {{- ctx.Locale.Tr "repo.diff.submodule_deleted" $submoduleName ($submoduleDiffInfo.CommitRefIDLinkHTML ctx $submoduleDiffInfo.PreviousRefID) -}} {{- else if $file.IsCreated -}} - {{- ctx.Locale.Tr "repo.diff.submodule_added" $submoduleName ($submoduleDiffInfo.NewRefIDLinkHTML ctx) -}} + {{- ctx.Locale.Tr "repo.diff.submodule_added" $submoduleName ($submoduleDiffInfo.CommitRefIDLinkHTML ctx $submoduleDiffInfo.NewRefID) -}} {{- else -}} {{- ctx.Locale.Tr "repo.diff.submodule_updated" $submoduleName ($submoduleDiffInfo.CompareRefIDLinkHTML ctx) -}} {{end}} From 1a12acdd8891f930cefccea7bc0a465f2e3c6d3f Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 7 Jan 2025 19:02:46 +0800 Subject: [PATCH 10/14] fix test --- modules/git/url/url_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/git/url/url_test.go b/modules/git/url/url_test.go index 93f58860293b7..9c020adb4d96f 100644 --- a/modules/git/url/url_test.go +++ b/modules/git/url/url_test.go @@ -173,6 +173,7 @@ func TestParseGitURLs(t *testing.T) { func TestParseRepositoryURL(t *testing.T) { defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000")() + defer test.MockVariableValue(&setting.SSH.Domain, "try.gitea.io")() ctxURL, _ := url.Parse("https://gitea") ctxReq := &http.Request{URL: ctxURL, Header: http.Header{}} From ee74ae0b0239e8488b1e1353b3d995cdba6254d0 Mon Sep 17 00:00:00 2001 From: Rowan Bohde Date: Tue, 7 Jan 2025 15:20:36 -0600 Subject: [PATCH 11/14] use submodule icon in diff tree when rendering a submodule --- templates/repo/diff/box.tmpl | 2 +- web_src/js/components/DiffFileTreeItem.vue | 10 +++++++++- web_src/js/svg.ts | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 5eda427137501..ea01d96928c9e 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -58,7 +58,7 @@
{{end}}