[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 <forgejo@gergo.csillger.hu>
(cherry picked from commit 2aadcf4946)
(cherry picked from commit 42ac34fbf9)
(cherry picked from commit bd231b0245)
(cherry picked from commit 3d3366dbbe)
(cherry picked from commit 0157fb9b88)
(cherry picked from commit bee88f6a83)
This commit is contained in:
Gergely Nagy 2023-12-31 16:24:05 +01:00 committed by Earl Warren
parent 1bae2430c0
commit 1d8bca07f3
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
5 changed files with 117 additions and 7 deletions

View file

@ -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"`
@ -161,6 +165,7 @@ var (
DisabledRepoUnits: []string{},
DefaultRepoUnits: []string{},
DefaultForkRepoUnits: []string{},
DownloadOrCloneMethods: []string{"download-zip", "download-targz", "download-bundle", "vscode-clone"},
PrefixArchiveFiles: true,
DisableMigrations: false,
DisableStars: false,
@ -361,4 +366,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)
}
}
}

View file

@ -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,

View file

@ -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);
}
})();
</script>

View file

@ -131,15 +131,32 @@
<button id="more-btn" class="ui basic small compact jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
{{svg "octicon-kebab-horizontal"}}
<div class="menu">
{{$citation := .CitationExist}}
{{$originLink := .CloneButtonOriginLink}}
{{range $.DownloadOrCloneMethods}}
{{if not $.DisableDownloadSourceArchives}}
{{if eq . "download-zip"}}
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
{{end}}
{{if eq . "download-targz"}}
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
{{end}}
{{if eq . "download-bundle"}}
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.download_bundle"}}</a>
{{if .CitiationExist}}
{{end}}
{{if $citation}}
{{if eq . "cite"}}
<a class="item" id="cite-repo-button">{{svg "octicon-cross-reference" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.cite_this_repo"}}</a>
{{end}}
{{end}}
<a class="item js-clone-url-vsc" href="vscode://vscode.git/clone?url={{.CloneButtonOriginLink.HTTPS}}">{{svg "gitea-vscode" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.clone_in_vsc"}}</a>
{{end}}
{{if eq . "vscode-clone"}}
<a class="item js-clone-url-vsc" href="vscode://vscode.git/clone?url={{$originLink.HTTPS}}">{{svg "gitea-vscode" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.clone_in_vsc"}}</a>
{{end}}
{{if eq . "vscodium-clone"}}
<a class="item js-clone-url-vscodium" href="vscodium://vscode.git/clone?url={{$originLink.HTTPS}}">{{svg "gitea-vscode" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.clone_in_vscodium"}}</a>
{{end}}
{{end}}
</div>
</button>
{{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}}

View file

@ -43,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)()