[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 commit2aadcf4946
) (cherry picked from commit42ac34fbf9
) (cherry picked from commitbd231b0245
) (cherry picked from commit3d3366dbbe
) (cherry picked from commit0157fb9b88
) (cherry picked from commitbee88f6a83
)
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