From c3f5ea3b1fd7db51fdfe0d18a4ce6ef78554499a Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 31 Jul 2025 09:34:51 +0800 Subject: [PATCH] Fix repo file list partial reloading for submodules (#35183) Fix the TODO and add more tests --- modules/git/commit_info.go | 9 +++---- modules/git/commit_info_gogit.go | 2 +- modules/git/commit_info_nogogit.go | 2 +- modules/git/commit_info_test.go | 4 +-- routers/web/repo/view_home.go | 42 +++++++++++++++++------------- routers/web/repo/view_home_test.go | 13 ++++++--- 6 files changed, 40 insertions(+), 32 deletions(-) diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index b44e9fa51d..4f76a28f31 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -3,8 +3,6 @@ package git -import "path" - // CommitInfo describes the first commit with the provided entry type CommitInfo struct { Entry *TreeEntry @@ -12,15 +10,14 @@ type CommitInfo struct { SubmoduleFile *CommitSubmoduleFile } -func getCommitInfoSubmoduleFile(repoLink string, entry *TreeEntry, commit *Commit, treePathDir string) (*CommitSubmoduleFile, error) { - fullPath := path.Join(treePathDir, entry.Name()) +func GetCommitInfoSubmoduleFile(repoLink, fullPath string, commit *Commit, refCommitID ObjectID) (*CommitSubmoduleFile, error) { submodule, err := commit.GetSubModule(fullPath) if err != nil { return nil, err } if submodule == nil { // unable to find submodule from ".gitmodules" file - return NewCommitSubmoduleFile(repoLink, fullPath, "", entry.ID.String()), nil + return NewCommitSubmoduleFile(repoLink, fullPath, "", refCommitID.String()), nil } - return NewCommitSubmoduleFile(repoLink, fullPath, submodule.URL, entry.ID.String()), nil + return NewCommitSubmoduleFile(repoLink, fullPath, submodule.URL, refCommitID.String()), nil } diff --git a/modules/git/commit_info_gogit.go b/modules/git/commit_info_gogit.go index 7e03e634a0..73227347bc 100644 --- a/modules/git/commit_info_gogit.go +++ b/modules/git/commit_info_gogit.go @@ -73,7 +73,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit * // If the entry is a submodule, add a submodule file for this if entry.IsSubModule() { - commitsInfo[i].SubmoduleFile, err = getCommitInfoSubmoduleFile(repoLink, entry, commit, treePath) + commitsInfo[i].SubmoduleFile, err = GetCommitInfoSubmoduleFile(repoLink, path.Join(treePath, entry.Name()), commit, entry.ID) if err != nil { return nil, nil, err } diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go index 161edb7e96..ed775332a9 100644 --- a/modules/git/commit_info_nogogit.go +++ b/modules/git/commit_info_nogogit.go @@ -64,7 +64,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit * // If the entry is a submodule, add a submodule file for this if entry.IsSubModule() { - commitsInfo[i].SubmoduleFile, err = getCommitInfoSubmoduleFile(repoLink, entry, commit, treePath) + commitsInfo[i].SubmoduleFile, err = GetCommitInfoSubmoduleFile(repoLink, path.Join(treePath, entry.Name()), commit, entry.ID) if err != nil { return nil, nil, err } diff --git a/modules/git/commit_info_test.go b/modules/git/commit_info_test.go index 5f2eb5e129..078b6815d2 100644 --- a/modules/git/commit_info_test.go +++ b/modules/git/commit_info_test.go @@ -125,9 +125,9 @@ func TestEntries_GetCommitsInfo(t *testing.T) { t.Run("NonExistingSubmoduleAsNil", func(t *testing.T) { commit, err := bareRepo1.GetCommit("HEAD") require.NoError(t, err) - tree, err := commit.GetTreeEntryByPath("file1.txt") + treeEntry, err := commit.GetTreeEntryByPath("file1.txt") require.NoError(t, err) - cisf, err := getCommitInfoSubmoduleFile("/any/repo-link", tree, commit, "") + cisf, err := GetCommitInfoSubmoduleFile("/any/repo-link", "file1.txt", commit, treeEntry.ID) require.NoError(t, err) assert.Equal(t, &CommitSubmoduleFile{ repoLink: "/any/repo-link", diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index fd6e746381..f475e93f60 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -19,8 +19,8 @@ import ( unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" - giturl "code.gitea.io/gitea/modules/git/url" "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" @@ -258,35 +258,41 @@ func handleRepoEmptyOrBroken(ctx *context.Context) { ctx.Redirect(link) } -func handleRepoViewSubmodule(ctx *context.Context, submodule *git.SubModule) { - // TODO: it needs to use git.NewCommitSubmoduleFile and SubmoduleWebLinkTree to correctly handle relative paths - submoduleRepoURL, err := giturl.ParseRepositoryURL(ctx, submodule.URL) - if err != nil { - HandleGitError(ctx, "handleRepoViewSubmodule: ParseRepositoryURL", err) +func isViewHomeOnlyContent(ctx *context.Context) bool { + return ctx.FormBool("only_content") +} + +func handleRepoViewSubmodule(ctx *context.Context, commitSubmoduleFile *git.CommitSubmoduleFile) { + submoduleWebLink := commitSubmoduleFile.SubmoduleWebLinkTree(ctx) + if submoduleWebLink == nil { + ctx.Data["NotFoundPrompt"] = ctx.Repo.TreePath + ctx.NotFound(nil) return } - submoduleURL := giturl.MakeRepositoryWebLink(submoduleRepoURL) - if httplib.IsCurrentGiteaSiteURL(ctx, submoduleURL) { - ctx.RedirectToCurrentSite(submoduleURL) - } else { + + redirectLink := submoduleWebLink.CommitWebLink + if isViewHomeOnlyContent(ctx) { + ctx.Resp.Header().Set("Content-Type", "text/html; charset=utf-8") + _, _ = ctx.Resp.Write([]byte(htmlutil.HTMLFormat(`%s`, redirectLink, redirectLink))) + } else if !httplib.IsCurrentGiteaSiteURL(ctx, redirectLink) { // don't auto-redirect to external URL, to avoid open redirect or phishing - ctx.Data["NotFoundPrompt"] = submoduleURL + ctx.Data["NotFoundPrompt"] = redirectLink ctx.NotFound(nil) + } else { + ctx.Redirect(submoduleWebLink.CommitWebLink) } } func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) { return func(ctx *context.Context) { if entry.IsSubModule() { - submodule, err := ctx.Repo.Commit.GetSubModule(entry.Name()) + commitSubmoduleFile, err := git.GetCommitInfoSubmoduleFile(ctx.Repo.RepoLink, ctx.Repo.TreePath, ctx.Repo.Commit, entry.ID) if err != nil { - HandleGitError(ctx, "prepareToRenderDirOrFile: GetSubModule", err) + HandleGitError(ctx, "prepareToRenderDirOrFile: GetCommitInfoSubmoduleFile", err) return } - handleRepoViewSubmodule(ctx, submodule) - return - } - if entry.IsDir() { + handleRepoViewSubmodule(ctx, commitSubmoduleFile) + } else if entry.IsDir() { prepareToRenderDirectory(ctx) } else { prepareFileView(ctx, entry) @@ -441,7 +447,7 @@ func Home(ctx *context.Context) { } } - if ctx.FormBool("only_content") { + if isViewHomeOnlyContent(ctx) { ctx.HTML(http.StatusOK, tplRepoViewContent) } else if len(treeNames) != 0 { ctx.HTML(http.StatusOK, tplRepoView) diff --git a/routers/web/repo/view_home_test.go b/routers/web/repo/view_home_test.go index 6264dba71c..dd74ae560b 100644 --- a/routers/web/repo/view_home_test.go +++ b/routers/web/repo/view_home_test.go @@ -9,7 +9,6 @@ import ( "code.gitea.io/gitea/models/unittest" git_module "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/contexttest" "github.com/stretchr/testify/assert" @@ -19,14 +18,20 @@ func TestViewHomeSubmoduleRedirect(t *testing.T) { unittest.PrepareTestEnv(t) ctx, _ := contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule") - submodule := &git_module.SubModule{Path: "test-submodule", URL: setting.AppURL + "user2/repo-other.git"} + submodule := git_module.NewCommitSubmoduleFile("/user2/repo1", "test-submodule", "../repo-other", "any-ref-id") handleRepoViewSubmodule(ctx, submodule) assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) - assert.Equal(t, "/user2/repo-other", ctx.Resp.Header().Get("Location")) + assert.Equal(t, "/user2/repo-other/tree/any-ref-id", ctx.Resp.Header().Get("Location")) ctx, _ = contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule") - submodule = &git_module.SubModule{Path: "test-submodule", URL: "https://other/user2/repo-other.git"} + submodule = git_module.NewCommitSubmoduleFile("/user2/repo1", "test-submodule", "https://other/user2/repo-other.git", "any-ref-id") handleRepoViewSubmodule(ctx, submodule) // do not auto-redirect for external URLs, to avoid open redirect or phishing assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus()) + + ctx, respWriter := contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule?only_content=true") + submodule = git_module.NewCommitSubmoduleFile("/user2/repo1", "test-submodule", "../repo-other", "any-ref-id") + handleRepoViewSubmodule(ctx, submodule) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, `/user2/repo-other/tree/any-ref-id`, respWriter.Body.String()) }