From 42ac34fbf9105eed27ee687b305a85515270f0cc Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Sun, 31 Dec 2023 16:24:05 +0100 Subject: [PATCH] [GITEA] Configurable clone methods Adds `[repository].DOWNLOAD_OR_CLONE_METHODS` (defaulting to "download-zip,download-targz,download-bundle,vscode-clone"), which lets an instance administrator override the additional clone methods displayed on the repository home view. This is purely display-only, the clone methods not listed here are still available, unless disabled elsewhere. They're just not displayed. Fixes #710. Signed-off-by: Gergely Nagy (cherry picked from commit 2aadcf4946e48ee43800568fe705d00a062c42bf) --- modules/setting/repository.go | 11 +++++ modules/web/middleware/data.go | 1 + options/locale/locale_en-US.ini | 1 + templates/repo/clone_script.tmpl | 3 ++ templates/repo/home.tmpl | 31 ++++++++++--- tests/integration/repo_test.go | 79 ++++++++++++++++++++++++++++++++ 6 files changed, 119 insertions(+), 7 deletions(-) diff --git a/modules/setting/repository.go b/modules/setting/repository.go index d5a521b973..8add0a353b 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -7,6 +7,7 @@ import ( "os/exec" "path" "path/filepath" + "slices" "strings" "code.gitea.io/gitea/modules/log" @@ -19,6 +20,8 @@ const ( RepoCreatingPublic = "public" ) +var RecognisedRepositoryDownloadOrCloneMethods = []string{"download-zip", "download-targz", "download-bundle", "vscode-clone", "vscodium-clone", "cite"} + // ItemsPerPage maximum items per page in forks, watchers and stars of a repo const ItemsPerPage = 40 @@ -43,6 +46,7 @@ var ( DisabledRepoUnits []string DefaultRepoUnits []string DefaultForkRepoUnits []string + DownloadOrCloneMethods []string PrefixArchiveFiles bool DisableMigrations bool DisableStars bool `ini:"DISABLE_STARS"` @@ -160,6 +164,7 @@ var ( DisabledRepoUnits: []string{}, DefaultRepoUnits: []string{}, DefaultForkRepoUnits: []string{}, + DownloadOrCloneMethods: []string{"download-zip", "download-targz", "download-bundle", "vscode-clone"}, PrefixArchiveFiles: true, DisableMigrations: false, DisableStars: false, @@ -358,4 +363,10 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { if err := loadRepoArchiveFrom(rootCfg); err != nil { log.Fatal("loadRepoArchiveFrom: %v", err) } + + for _, method := range Repository.DownloadOrCloneMethods { + if !slices.Contains(RecognisedRepositoryDownloadOrCloneMethods, method) { + log.Error("Unrecognised repository download or clone method: %s", method) + } + } } diff --git a/modules/web/middleware/data.go b/modules/web/middleware/data.go index 08d83f94be..c1d1af8528 100644 --- a/modules/web/middleware/data.go +++ b/modules/web/middleware/data.go @@ -53,6 +53,7 @@ func CommonTemplateContextData() ContextData { "ShowMilestonesDashboardPage": setting.Service.ShowMilestonesDashboardPage, "ShowFooterVersion": setting.Other.ShowFooterVersion, "DisableDownloadSourceArchives": setting.Repository.DisableDownloadSourceArchives, + "DownloadOrCloneMethods": setting.Repository.DownloadOrCloneMethods, "EnableSwagger": setting.API.EnableSwagger, "EnableOpenIDSignIn": setting.Service.EnableOpenIDSignIn, diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 40e1a3ecbe..34380cceb5 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -961,6 +961,7 @@ all_branches = All branches fork_no_valid_owners = This repository can not be forked because there are no valid owners. use_template = Use this template clone_in_vsc = Clone in VS Code +clone_in_vscodium = Clone in VS Codium download_zip = Download ZIP download_tar = Download TAR.GZ download_bundle = Download BUNDLE diff --git a/templates/repo/clone_script.tmpl b/templates/repo/clone_script.tmpl index 0797b400d8..9ff826bc93 100644 --- a/templates/repo/clone_script.tmpl +++ b/templates/repo/clone_script.tmpl @@ -38,5 +38,8 @@ for (const el of document.getElementsByClassName('js-clone-url-vsc')) { el['href'] = 'vscode://vscode.git/clone?url=' + encodeURIComponent(link); } + for (const el of document.getElementsByClassName('js-clone-url-vscodium')) { + el['href'] = 'vscodium://vscode.git/clone?url=' + encodeURIComponent(link); + } })(); diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index d91dc4394e..eb6d96f28e 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -131,15 +131,32 @@ {{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}} diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index 3393f147ea..f0056dbdad 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -12,6 +12,7 @@ import ( "time" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/PuerkitoBio/goquery" @@ -42,6 +43,84 @@ func TestViewRepo(t *testing.T) { session.MakeRequest(t, req, http.StatusNotFound) } +func TestViewRepoCloneMethods(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + getCloneMethods := func() []string { + req := NewRequest(t, "GET", "/user2/repo1") + resp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + cloneMoreMethodsHTML := htmlDoc.doc.Find("#more-btn div a") + + var methods []string + cloneMoreMethodsHTML.Each(func(i int, s *goquery.Selection) { + a, _ := s.Attr("href") + methods = append(methods, a) + }) + + return methods + } + + testCloneMethods := func(expected []string) { + methods := getCloneMethods() + + assert.Len(t, methods, len(expected)) + for i, expectedMethod := range expected { + assert.Contains(t, methods[i], expectedMethod) + } + } + + t.Run("Defaults", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + testCloneMethods([]string{"/master.zip", "/master.tar.gz", "/master.bundle", "vscode://"}) + }) + + t.Run("Customized methods", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.Repository.DownloadOrCloneMethods, []string{"vscodium-clone", "download-targz"})() + + testCloneMethods([]string{"vscodium://", "/master.tar.gz"}) + }) + + t.Run("Individual methods", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + singleMethodTest := func(method, expectedURLPart string) { + t.Run(method, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.Repository.DownloadOrCloneMethods, []string{method})() + + testCloneMethods([]string{expectedURLPart}) + }) + } + + cases := map[string]string{ + "download-zip": "/master.zip", + "download-targz": "/master.tar.gz", + "download-bundle": "/master.bundle", + "vscode-clone": "vscode://", + "vscodium-clone": "vscodium://", + } + for method, expectedURLPart := range cases { + singleMethodTest(method, expectedURLPart) + } + }) + + t.Run("All methods", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.Repository.DownloadOrCloneMethods, setting.RecognisedRepositoryDownloadOrCloneMethods)() + + methods := getCloneMethods() + // We compare against + // len(setting.RecognisedRepositoryDownloadOrCloneMethods) - 1, because + // the test environment does not currently set things up for the cite + // method to display. + assert.GreaterOrEqual(t, len(methods), len(setting.RecognisedRepositoryDownloadOrCloneMethods)-1) + }) +} + func testViewRepo(t *testing.T) { defer tests.PrepareTestEnv(t)()