[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 2aadcf4946e48ee43800568fe705d00a062c42bf) (cherry picked from commit 42ac34fbf9105eed27ee687b305a85515270f0cc) (cherry picked from commit bd231b02450212aca6be775663c3d24ddf19f990) (cherry picked from commit 3d3366dbbee37621fc665e557a4a87bf08104375) (cherry picked from commit 0157fb9b88fd50832c07b06c11c8dba6e059a465) (cherry picked from commit bee88f6a8309c6f9aeba1522383d77f08e5a4d2d)
This commit is contained in:
parent
1bae2430c0
commit
1d8bca07f3
5 changed files with 117 additions and 7 deletions
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
{{$citation := .CitationExist}}
|
||||||
|
{{$originLink := .CloneButtonOriginLink}}
|
||||||
|
{{range $.DownloadOrCloneMethods}}
|
||||||
{{if not $.DisableDownloadSourceArchives}}
|
{{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>
|
<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>
|
<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>
|
<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>
|
<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}}
|
{{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>
|
</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 */}}
|
||||||
|
|
|
@ -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)()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue