[FEAT]Add Option to hide Release Archive links (#3139)

This adds a new options to releases to hide the links to the automatically generated archives. This is useful, when the automatically generated Archives are broken e.g. because of Submodules.

![grafik](/attachments/5686edf6-f318-4175-8459-89c33973b181)
![grafik](/attachments/74a8bf92-2abb-47a0-876d-d41024770d0b)

Note:
This juts hides the Archives from the UI. Users can still download 5the Archive if they know t correct URL.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3139
Reviewed-by: Otto <otto@codeberg.org>
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: JakobDev <jakobdev@gmx.de>
Co-committed-by: JakobDev <jakobdev@gmx.de>
This commit is contained in:
JakobDev 2024-04-24 15:15:55 +00:00 committed by Earl Warren
parent 6bcaf4f875
commit 1bce2dc5c5
14 changed files with 220 additions and 106 deletions

View file

@ -62,6 +62,8 @@ var migrations = []*Migration{
NewMigration("Add the `created` column to the `issue` table", forgejo_v1_22.AddCreatedToIssue),
// v12 -> v13
NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount),
// v13 -> v14
NewMigration("Add `hide_archive_links` column to `release` table", AddHideArchiveLinksToRelease),
}
// GetCurrentDBVersion returns the current Forgejo database version.

View file

@ -0,0 +1,15 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgejo_migrations //nolint:revive
import "xorm.io/xorm"
func AddHideArchiveLinksToRelease(x *xorm.Engine) error {
type Release struct {
ID int64 `xorm:"pk autoincr"`
HideArchiveLinks bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync(&Release{})
}

View file

@ -78,6 +78,7 @@ type Release struct {
TargetBehind string `xorm:"-"` // to handle non-existing or empty target
Title string
Sha1 string `xorm:"VARCHAR(64)"`
HideArchiveLinks bool `xorm:"NOT NULL DEFAULT false"`
NumCommits int64
NumCommitsBehind int64 `xorm:"-"`
Note string `xorm:"TEXT"`

View file

@ -9,18 +9,19 @@ import (
// Release represents a repository release
type Release struct {
ID int64 `json:"id"`
TagName string `json:"tag_name"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
TarURL string `json:"tarball_url"`
ZipURL string `json:"zipball_url"`
UploadURL string `json:"upload_url"`
IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"`
ID int64 `json:"id"`
TagName string `json:"tag_name"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
TarURL string `json:"tarball_url"`
ZipURL string `json:"zipball_url"`
HideArchiveLinks bool `json:"hide_archive_links"`
UploadURL string `json:"upload_url"`
IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"`
// swagger:strfmt date-time
CreatedAt time.Time `json:"created_at"`
// swagger:strfmt date-time
@ -33,20 +34,22 @@ type Release struct {
// CreateReleaseOption options when creating a release
type CreateReleaseOption struct {
// required: true
TagName string `json:"tag_name" binding:"Required"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"`
TagName string `json:"tag_name" binding:"Required"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"`
HideArchiveLinks bool `json:"hide_archive_links"`
}
// EditReleaseOption options when editing a release
type EditReleaseOption struct {
TagName string `json:"tag_name"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
IsDraft *bool `json:"draft"`
IsPrerelease *bool `json:"prerelease"`
TagName string `json:"tag_name"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
IsDraft *bool `json:"draft"`
IsPrerelease *bool `json:"prerelease"`
HideArchiveLinks *bool `json:"hide_archive_links"`
}

View file

@ -2662,6 +2662,8 @@ release.downloads = Downloads
release.download_count_one = %s download
release.download_count_few = %s downloads
release.add_tag_msg = Use the title and content of release as tag message.
release.hide_archive_links = Hide automatically generated archives
release.hide_archive_links_helper = Hide automatically generated source code archives for this release. For example, if you are uploading your own.
release.add_tag = Create Tag Only
release.releases_for = Releases for %s
release.tags_for = Tags for %s

View file

@ -231,17 +231,18 @@ func CreateRelease(ctx *context.APIContext) {
form.Target = ctx.Repo.Repository.DefaultBranch
}
rel = &repo_model.Release{
RepoID: ctx.Repo.Repository.ID,
PublisherID: ctx.Doer.ID,
Publisher: ctx.Doer,
TagName: form.TagName,
Target: form.Target,
Title: form.Title,
Note: form.Note,
IsDraft: form.IsDraft,
IsPrerelease: form.IsPrerelease,
IsTag: false,
Repo: ctx.Repo.Repository,
RepoID: ctx.Repo.Repository.ID,
PublisherID: ctx.Doer.ID,
Publisher: ctx.Doer,
TagName: form.TagName,
Target: form.Target,
Title: form.Title,
Note: form.Note,
IsDraft: form.IsDraft,
IsPrerelease: form.IsPrerelease,
HideArchiveLinks: form.HideArchiveLinks,
IsTag: false,
Repo: ctx.Repo.Repository,
}
if err := release_service.CreateRelease(ctx.Repo.GitRepo, rel, nil, ""); err != nil {
if repo_model.IsErrReleaseAlreadyExist(err) {
@ -261,6 +262,7 @@ func CreateRelease(ctx *context.APIContext) {
rel.Note = form.Note
rel.IsDraft = form.IsDraft
rel.IsPrerelease = form.IsPrerelease
rel.HideArchiveLinks = form.HideArchiveLinks
rel.PublisherID = ctx.Doer.ID
rel.IsTag = false
rel.Repo = ctx.Repo.Repository
@ -341,6 +343,9 @@ func EditRelease(ctx *context.APIContext) {
if form.IsPrerelease != nil {
rel.IsPrerelease = *form.IsPrerelease
}
if form.HideArchiveLinks != nil {
rel.HideArchiveLinks = *form.HideArchiveLinks
}
if err := release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, nil, nil, nil, false); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRelease", err)
return

View file

@ -441,6 +441,20 @@ func NewRelease(ctx *context.Context) {
}
ctx.Data["Tags"] = tags
// We set the value of the hide_archive_link textbox depending on the latest release
latestRelease, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
ctx.Data["hide_archive_links"] = false
} else {
ctx.ServerError("GetLatestReleaseByRepoID", err)
return
}
}
if latestRelease != nil {
ctx.Data["hide_archive_links"] = latestRelease.HideArchiveLinks
}
ctx.HTML(http.StatusOK, tplReleaseNew)
}
@ -525,17 +539,18 @@ func NewReleasePost(ctx *context.Context) {
}
rel = &repo_model.Release{
RepoID: ctx.Repo.Repository.ID,
Repo: ctx.Repo.Repository,
PublisherID: ctx.Doer.ID,
Publisher: ctx.Doer,
Title: form.Title,
TagName: form.TagName,
Target: form.Target,
Note: form.Content,
IsDraft: len(form.Draft) > 0,
IsPrerelease: form.Prerelease,
IsTag: false,
RepoID: ctx.Repo.Repository.ID,
Repo: ctx.Repo.Repository,
PublisherID: ctx.Doer.ID,
Publisher: ctx.Doer,
Title: form.Title,
TagName: form.TagName,
Target: form.Target,
Note: form.Content,
IsDraft: len(form.Draft) > 0,
IsPrerelease: form.Prerelease,
HideArchiveLinks: form.HideArchiveLinks,
IsTag: false,
}
if err = releaseservice.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs, msg); err != nil {
@ -565,6 +580,7 @@ func NewReleasePost(ctx *context.Context) {
rel.IsDraft = len(form.Draft) > 0
rel.IsPrerelease = form.Prerelease
rel.PublisherID = ctx.Doer.ID
rel.HideArchiveLinks = form.HideArchiveLinks
rel.IsTag = false
if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil, true); err != nil {
@ -602,6 +618,7 @@ func EditRelease(ctx *context.Context) {
ctx.Data["title"] = rel.Title
ctx.Data["content"] = rel.Note
ctx.Data["prerelease"] = rel.IsPrerelease
ctx.Data["hide_archive_links"] = rel.HideArchiveLinks
ctx.Data["IsDraft"] = rel.IsDraft
rel.Repo = ctx.Repo.Repository
@ -648,6 +665,7 @@ func EditReleasePost(ctx *context.Context) {
ctx.Data["title"] = rel.Title
ctx.Data["content"] = rel.Note
ctx.Data["prerelease"] = rel.IsPrerelease
ctx.Data["hide_archive_links"] = rel.HideArchiveLinks
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplReleaseNew)
@ -673,6 +691,7 @@ func EditReleasePost(ctx *context.Context) {
rel.Note = form.Content
rel.IsDraft = len(form.Draft) > 0
rel.IsPrerelease = form.Prerelease
rel.HideArchiveLinks = form.HideArchiveLinks
if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo,
rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments, false); err != nil {
ctx.ServerError("UpdateRelease", err)

View file

@ -22,6 +22,7 @@ func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_mode
HTMLURL: r.HTMLURL(),
TarURL: r.TarURL(),
ZipURL: r.ZipURL(),
HideArchiveLinks: r.HideArchiveLinks,
UploadURL: r.APIUploadURL(),
IsDraft: r.IsDraft,
IsPrerelease: r.IsPrerelease,

View file

@ -559,15 +559,16 @@ type UpdateAllowEditsForm struct {
// NewReleaseForm form for creating release
type NewReleaseForm struct {
TagName string `binding:"Required;GitRefName;MaxSize(255)"`
Target string `form:"tag_target" binding:"Required;MaxSize(255)"`
Title string `binding:"MaxSize(255)"`
Content string
Draft string
TagOnly string
Prerelease bool
AddTagMsg bool
Files []string
TagName string `binding:"Required;GitRefName;MaxSize(255)"`
Target string `form:"tag_target" binding:"Required;MaxSize(255)"`
Title string `binding:"MaxSize(255)"`
Content string
Draft string
TagOnly string
Prerelease bool
AddTagMsg bool
HideArchiveLinks bool
Files []string
}
// Validate validates the fields
@ -578,11 +579,12 @@ func (f *NewReleaseForm) Validate(req *http.Request, errs binding.Errors) bindin
// EditReleaseForm form for changing release
type EditReleaseForm struct {
Title string `form:"title" binding:"Required;MaxSize(255)"`
Content string `form:"content"`
Draft string `form:"draft"`
Prerelease bool `form:"prerelease"`
Files []string
Title string `form:"title" binding:"Required;MaxSize(255)"`
Content string `form:"content"`
Draft string `form:"draft"`
Prerelease bool `form:"prerelease"`
HideArchiveLinks bool
Files []string
}
// Validate validates the fields

View file

@ -61,46 +61,49 @@
<div class="markup desc">
{{$release.RenderedNote}}
</div>
<div class="divider"></div>
<details class="download" {{if eq $idx 0}}open{{end}}>
<summary class="tw-my-4">
{{ctx.Locale.Tr "repo.release.downloads"}}
</summary>
<ul class="list">
{{$hasReleaseAttachment := gt (len $release.Attachments) 0}}
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
<li>
<a class="archive-link tw-flex-1" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a>
<div class="tw-mr-1">
<span class="text grey">{{ctx.Locale.TrN .Release.ArchiveDownloadCount.Zip "repo.release.download_count_one" "repo.release.download_count_few" (ctx.Locale.PrettyNumber .Release.ArchiveDownloadCount.Zip)}}</span>
</div>
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.system_generated"}}">
{{svg "octicon-info"}}
</span>
</li>
<li class="{{if $hasReleaseAttachment}}start-gap{{end}}">
<a class="archive-link tw-flex-1" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a>
<div class="tw-mr-1">
<span class="text grey">{{ctx.Locale.TrN .Release.ArchiveDownloadCount.TarGz "repo.release.download_count_one" "repo.release.download_count_few" (ctx.Locale.PrettyNumber .Release.ArchiveDownloadCount.TarGz)}}</span>
</div>
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.system_generated"}}">
{{svg "octicon-info"}}
</span>
</li>
{{if $hasReleaseAttachment}}<hr>{{end}}
{{end}}
{{range $release.Attachments}}
<li>
<a target="_blank" rel="nofollow" href="{{.DownloadURL}}" download>
<strong>{{svg "octicon-package" 16 "tw-mr-1"}}{{.Name}}</strong>
</a>
<div>
<span class="text grey">{{ctx.Locale.TrN .DownloadCount "repo.release.download_count_one" "repo.release.download_count_few" (ctx.Locale.PrettyNumber .DownloadCount)}} · {{.Size | ctx.Locale.TrSize}}</span>
</div>
</li>
{{end}}
</ul>
</details>
{{$hasReleaseAttachment := gt (len $release.Attachments) 0}}
{{$hasArchiveLinks := and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) (not $release.HideArchiveLinks) ($.Permission.CanRead $.UnitTypeCode)}}
{{if or $hasArchiveLinks $hasReleaseAttachment}}
<div class="divider"></div>
<details class="download" {{if eq $idx 0}}open{{end}}>
<summary class="tw-my-4">
{{ctx.Locale.Tr "repo.release.downloads"}}
</summary>
<ul class="list">
{{if $hasArchiveLinks}}
<li>
<a class="archive-link tw-flex-1" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a>
<div class="tw-mr-1">
<span class="text grey">{{ctx.Locale.TrN .Release.ArchiveDownloadCount.Zip "repo.release.download_count_one" "repo.release.download_count_few" (ctx.Locale.PrettyNumber .Release.ArchiveDownloadCount.Zip)}}</span>
</div>
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.system_generated"}}">
{{svg "octicon-info"}}
</span>
</li>
<li class="{{if $hasReleaseAttachment}}start-gap{{end}}">
<a class="archive-link tw-flex-1" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a>
<div class="tw-mr-1">
<span class="text grey">{{ctx.Locale.TrN .Release.ArchiveDownloadCount.TarGz "repo.release.download_count_one" "repo.release.download_count_few" (ctx.Locale.PrettyNumber .Release.ArchiveDownloadCount.TarGz)}}</span>
</div>
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.system_generated"}}">
{{svg "octicon-info"}}
</span>
</li>
{{if $hasReleaseAttachment}}<hr>{{end}}
{{end}}
{{range $release.Attachments}}
<li>
<a target="_blank" rel="nofollow" href="{{.DownloadURL}}" download>
<strong>{{svg "octicon-package" 16 "tw-mr-1"}}{{.Name}}</strong>
</a>
<div>
<span class="text grey">{{ctx.Locale.TrN .DownloadCount "repo.release.download_count_one" "repo.release.download_count_few" (ctx.Locale.PrettyNumber .DownloadCount)}} · {{.Size | ctx.Locale.TrSize}}</span>
</div>
</li>
{{end}}
</ul>
</details>
{{end}}
<div class="dot"></div>
</div>
</li>

View file

@ -98,6 +98,15 @@
</div>
</div>
<span class="help">{{ctx.Locale.Tr "repo.release.prerelease_helper"}}</span>
{{if not .DisableDownloadSourceArchives}}
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="hide_archive_links" {{if .hide_archive_links}}checked{{end}}>
<label><strong>{{ctx.Locale.Tr "repo.release.hide_archive_links"}}</strong></label>
</div>
</div>
<span class="help">{{ctx.Locale.Tr "repo.release.hide_archive_links_helper"}}</span>
{{end}}
<div class="divider tw-mt-0"></div>
<div class="tw-flex tw-justify-end">
{{if .PageIsEditRelease}}

View file

@ -19890,6 +19890,10 @@
"type": "boolean",
"x-go-name": "IsDraft"
},
"hide_archive_links": {
"type": "boolean",
"x-go-name": "HideArchiveLinks"
},
"name": {
"type": "string",
"x-go-name": "Title"
@ -20795,6 +20799,10 @@
"type": "boolean",
"x-go-name": "IsDraft"
},
"hide_archive_links": {
"type": "boolean",
"x-go-name": "HideArchiveLinks"
},
"name": {
"type": "string",
"x-go-name": "Title"
@ -23562,6 +23570,10 @@
"type": "boolean",
"x-go-name": "IsDraft"
},
"hide_archive_links": {
"type": "boolean",
"x-go-name": "HideArchiveLinks"
},
"html_url": {
"type": "string",
"x-go-name": "HTMLURL"

View file

@ -133,14 +133,18 @@ func TestAPICreateAndUpdateRelease(t *testing.T) {
assert.Equal(t, newRelease.TagName, release.TagName)
assert.Equal(t, newRelease.Title, release.Title)
assert.Equal(t, newRelease.Note, release.Note)
assert.False(t, newRelease.HideArchiveLinks)
hideArchiveLinks := true
req = NewRequestWithJSON(t, "PATCH", urlStr, &api.EditReleaseOption{
TagName: release.TagName,
Title: release.Title,
Note: "updated",
IsDraft: &release.IsDraft,
IsPrerelease: &release.IsPrerelease,
Target: release.Target,
TagName: release.TagName,
Title: release.Title,
Note: "updated",
IsDraft: &release.IsDraft,
IsPrerelease: &release.IsPrerelease,
Target: release.Target,
HideArchiveLinks: &hideArchiveLinks,
}).AddTokenAuth(token)
resp = MakeRequest(t, req, http.StatusOK)
@ -152,6 +156,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) {
}
unittest.AssertExistsAndLoadBean(t, rel)
assert.EqualValues(t, rel.Note, newRelease.Note)
assert.True(t, newRelease.HideArchiveLinks)
}
func TestAPICreateReleaseToDefaultBranch(t *testing.T) {

View file

@ -9,15 +9,19 @@ import (
"testing"
"time"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/tests"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title string, preRelease, draft bool) {
@ -307,3 +311,34 @@ func TestDownloadReleaseAttachment(t *testing.T) {
session := loginUser(t, "user2")
session.MakeRequest(t, req, http.StatusOK)
}
func TestReleaseHideArchiveLinksUI(t *testing.T) {
defer tests.PrepareTestEnv(t)()
release := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{TagName: "v2.0"})
require.NoError(t, release.LoadAttributes(db.DefaultContext))
session := loginUser(t, release.Repo.OwnerName)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
zipURL := fmt.Sprintf("%s/archive/%s.zip", release.Repo.Link(), release.TagName)
tarGzURL := fmt.Sprintf("%s/archive/%s.tar.gz", release.Repo.Link(), release.TagName)
resp := session.MakeRequest(t, NewRequest(t, "GET", release.HTMLURL()), http.StatusOK)
body := resp.Body.String()
assert.Contains(t, body, zipURL)
assert.Contains(t, body, tarGzURL)
hideArchiveLinks := true
req := NewRequestWithJSON(t, "PATCH", release.APIURL(), &api.EditReleaseOption{
HideArchiveLinks: &hideArchiveLinks,
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusOK)
resp = session.MakeRequest(t, NewRequest(t, "GET", release.HTMLURL()), http.StatusOK)
body = resp.Body.String()
assert.NotContains(t, body, zipURL)
assert.NotContains(t, body, tarGzURL)
}