[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" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"slices"
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -19,6 +20,8 @@ const (
RepoCreatingPublic = "public" 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 // ItemsPerPage maximum items per page in forks, watchers and stars of a repo
const ItemsPerPage = 40 const ItemsPerPage = 40
@ -43,6 +46,7 @@ var (
DisabledRepoUnits []string DisabledRepoUnits []string
DefaultRepoUnits []string DefaultRepoUnits []string
DefaultForkRepoUnits []string DefaultForkRepoUnits []string
DownloadOrCloneMethods []string
PrefixArchiveFiles bool PrefixArchiveFiles bool
DisableMigrations bool DisableMigrations bool
DisableStars bool `ini:"DISABLE_STARS"` DisableStars bool `ini:"DISABLE_STARS"`
@ -161,6 +165,7 @@ var (
DisabledRepoUnits: []string{}, DisabledRepoUnits: []string{},
DefaultRepoUnits: []string{}, DefaultRepoUnits: []string{},
DefaultForkRepoUnits: []string{}, DefaultForkRepoUnits: []string{},
DownloadOrCloneMethods: []string{"download-zip", "download-targz", "download-bundle", "vscode-clone"},
PrefixArchiveFiles: true, PrefixArchiveFiles: true,
DisableMigrations: false, DisableMigrations: false,
DisableStars: false, DisableStars: false,
@ -361,4 +366,10 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
if err := loadRepoArchiveFrom(rootCfg); err != nil { if err := loadRepoArchiveFrom(rootCfg); err != nil {
log.Fatal("loadRepoArchiveFrom: %v", err) 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, "ShowMilestonesDashboardPage": setting.Service.ShowMilestonesDashboardPage,
"ShowFooterVersion": setting.Other.ShowFooterVersion, "ShowFooterVersion": setting.Other.ShowFooterVersion,
"DisableDownloadSourceArchives": setting.Repository.DisableDownloadSourceArchives, "DisableDownloadSourceArchives": setting.Repository.DisableDownloadSourceArchives,
"DownloadOrCloneMethods": setting.Repository.DownloadOrCloneMethods,
"EnableSwagger": setting.API.EnableSwagger, "EnableSwagger": setting.API.EnableSwagger,
"EnableOpenIDSignIn": setting.Service.EnableOpenIDSignIn, "EnableOpenIDSignIn": setting.Service.EnableOpenIDSignIn,

View file

@ -38,5 +38,8 @@
for (const el of document.getElementsByClassName('js-clone-url-vsc')) { for (const el of document.getElementsByClassName('js-clone-url-vsc')) {
el['href'] = 'vscode://vscode.git/clone?url=' + encodeURIComponent(link); 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> </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"}}"> <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"}} {{svg "octicon-kebab-horizontal"}}
<div class="menu"> <div class="menu">
{{if not $.DisableDownloadSourceArchives}} {{$citation := .CitationExist}}
<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> {{$originLink := .CloneButtonOriginLink}}
<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> {{range $.DownloadOrCloneMethods}}
<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 not $.DisableDownloadSourceArchives}}
{{if .CitiationExist}} {{if eq . "download-zip"}}
<a class="item" id="cite-repo-button">{{svg "octicon-cross-reference" 16 "gt-mr-3"}}{{ctx.Locale.Tr "repo.cite_this_repo"}}</a> <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>
{{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}}
{{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}}
{{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>
</div> </div>
</button> </button>
{{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}} {{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) 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) { func testViewRepo(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()