[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), NewMigration("Add the `created` column to the `issue` table", forgejo_v1_22.AddCreatedToIssue),
// v12 -> v13 // v12 -> v13
NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount), 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. // 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 TargetBehind string `xorm:"-"` // to handle non-existing or empty target
Title string Title string
Sha1 string `xorm:"VARCHAR(64)"` Sha1 string `xorm:"VARCHAR(64)"`
HideArchiveLinks bool `xorm:"NOT NULL DEFAULT false"`
NumCommits int64 NumCommits int64
NumCommitsBehind int64 `xorm:"-"` NumCommitsBehind int64 `xorm:"-"`
Note string `xorm:"TEXT"` Note string `xorm:"TEXT"`

View file

@ -18,6 +18,7 @@ type Release struct {
HTMLURL string `json:"html_url"` HTMLURL string `json:"html_url"`
TarURL string `json:"tarball_url"` TarURL string `json:"tarball_url"`
ZipURL string `json:"zipball_url"` ZipURL string `json:"zipball_url"`
HideArchiveLinks bool `json:"hide_archive_links"`
UploadURL string `json:"upload_url"` UploadURL string `json:"upload_url"`
IsDraft bool `json:"draft"` IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"` IsPrerelease bool `json:"prerelease"`
@ -39,6 +40,7 @@ type CreateReleaseOption struct {
Note string `json:"body"` Note string `json:"body"`
IsDraft bool `json:"draft"` IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"` IsPrerelease bool `json:"prerelease"`
HideArchiveLinks bool `json:"hide_archive_links"`
} }
// EditReleaseOption options when editing a release // EditReleaseOption options when editing a release
@ -49,4 +51,5 @@ type EditReleaseOption struct {
Note string `json:"body"` Note string `json:"body"`
IsDraft *bool `json:"draft"` IsDraft *bool `json:"draft"`
IsPrerelease *bool `json:"prerelease"` 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_one = %s download
release.download_count_few = %s downloads release.download_count_few = %s downloads
release.add_tag_msg = Use the title and content of release as tag message. 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.add_tag = Create Tag Only
release.releases_for = Releases for %s release.releases_for = Releases for %s
release.tags_for = Tags for %s release.tags_for = Tags for %s

View file

@ -240,6 +240,7 @@ func CreateRelease(ctx *context.APIContext) {
Note: form.Note, Note: form.Note,
IsDraft: form.IsDraft, IsDraft: form.IsDraft,
IsPrerelease: form.IsPrerelease, IsPrerelease: form.IsPrerelease,
HideArchiveLinks: form.HideArchiveLinks,
IsTag: false, IsTag: false,
Repo: ctx.Repo.Repository, Repo: ctx.Repo.Repository,
} }
@ -261,6 +262,7 @@ func CreateRelease(ctx *context.APIContext) {
rel.Note = form.Note rel.Note = form.Note
rel.IsDraft = form.IsDraft rel.IsDraft = form.IsDraft
rel.IsPrerelease = form.IsPrerelease rel.IsPrerelease = form.IsPrerelease
rel.HideArchiveLinks = form.HideArchiveLinks
rel.PublisherID = ctx.Doer.ID rel.PublisherID = ctx.Doer.ID
rel.IsTag = false rel.IsTag = false
rel.Repo = ctx.Repo.Repository rel.Repo = ctx.Repo.Repository
@ -341,6 +343,9 @@ func EditRelease(ctx *context.APIContext) {
if form.IsPrerelease != nil { if form.IsPrerelease != nil {
rel.IsPrerelease = *form.IsPrerelease 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 { if err := release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, nil, nil, nil, false); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRelease", err) ctx.Error(http.StatusInternalServerError, "UpdateRelease", err)
return return

View file

@ -441,6 +441,20 @@ func NewRelease(ctx *context.Context) {
} }
ctx.Data["Tags"] = tags 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) ctx.HTML(http.StatusOK, tplReleaseNew)
} }
@ -535,6 +549,7 @@ func NewReleasePost(ctx *context.Context) {
Note: form.Content, Note: form.Content,
IsDraft: len(form.Draft) > 0, IsDraft: len(form.Draft) > 0,
IsPrerelease: form.Prerelease, IsPrerelease: form.Prerelease,
HideArchiveLinks: form.HideArchiveLinks,
IsTag: false, IsTag: false,
} }
@ -565,6 +580,7 @@ func NewReleasePost(ctx *context.Context) {
rel.IsDraft = len(form.Draft) > 0 rel.IsDraft = len(form.Draft) > 0
rel.IsPrerelease = form.Prerelease rel.IsPrerelease = form.Prerelease
rel.PublisherID = ctx.Doer.ID rel.PublisherID = ctx.Doer.ID
rel.HideArchiveLinks = form.HideArchiveLinks
rel.IsTag = false rel.IsTag = false
if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil, true); err != nil { 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["title"] = rel.Title
ctx.Data["content"] = rel.Note ctx.Data["content"] = rel.Note
ctx.Data["prerelease"] = rel.IsPrerelease ctx.Data["prerelease"] = rel.IsPrerelease
ctx.Data["hide_archive_links"] = rel.HideArchiveLinks
ctx.Data["IsDraft"] = rel.IsDraft ctx.Data["IsDraft"] = rel.IsDraft
rel.Repo = ctx.Repo.Repository rel.Repo = ctx.Repo.Repository
@ -648,6 +665,7 @@ func EditReleasePost(ctx *context.Context) {
ctx.Data["title"] = rel.Title ctx.Data["title"] = rel.Title
ctx.Data["content"] = rel.Note ctx.Data["content"] = rel.Note
ctx.Data["prerelease"] = rel.IsPrerelease ctx.Data["prerelease"] = rel.IsPrerelease
ctx.Data["hide_archive_links"] = rel.HideArchiveLinks
if ctx.HasError() { if ctx.HasError() {
ctx.HTML(http.StatusOK, tplReleaseNew) ctx.HTML(http.StatusOK, tplReleaseNew)
@ -673,6 +691,7 @@ func EditReleasePost(ctx *context.Context) {
rel.Note = form.Content rel.Note = form.Content
rel.IsDraft = len(form.Draft) > 0 rel.IsDraft = len(form.Draft) > 0
rel.IsPrerelease = form.Prerelease rel.IsPrerelease = form.Prerelease
rel.HideArchiveLinks = form.HideArchiveLinks
if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo,
rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments, false); err != nil { rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments, false); err != nil {
ctx.ServerError("UpdateRelease", err) 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(), HTMLURL: r.HTMLURL(),
TarURL: r.TarURL(), TarURL: r.TarURL(),
ZipURL: r.ZipURL(), ZipURL: r.ZipURL(),
HideArchiveLinks: r.HideArchiveLinks,
UploadURL: r.APIUploadURL(), UploadURL: r.APIUploadURL(),
IsDraft: r.IsDraft, IsDraft: r.IsDraft,
IsPrerelease: r.IsPrerelease, IsPrerelease: r.IsPrerelease,

View file

@ -567,6 +567,7 @@ type NewReleaseForm struct {
TagOnly string TagOnly string
Prerelease bool Prerelease bool
AddTagMsg bool AddTagMsg bool
HideArchiveLinks bool
Files []string Files []string
} }
@ -582,6 +583,7 @@ type EditReleaseForm struct {
Content string `form:"content"` Content string `form:"content"`
Draft string `form:"draft"` Draft string `form:"draft"`
Prerelease bool `form:"prerelease"` Prerelease bool `form:"prerelease"`
HideArchiveLinks bool
Files []string Files []string
} }

View file

@ -61,14 +61,16 @@
<div class="markup desc"> <div class="markup desc">
{{$release.RenderedNote}} {{$release.RenderedNote}}
</div> </div>
{{$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> <div class="divider"></div>
<details class="download" {{if eq $idx 0}}open{{end}}> <details class="download" {{if eq $idx 0}}open{{end}}>
<summary class="tw-my-4"> <summary class="tw-my-4">
{{ctx.Locale.Tr "repo.release.downloads"}} {{ctx.Locale.Tr "repo.release.downloads"}}
</summary> </summary>
<ul class="list"> <ul class="list">
{{$hasReleaseAttachment := gt (len $release.Attachments) 0}} {{if $hasArchiveLinks}}
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
<li> <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> <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"> <div class="tw-mr-1">
@ -101,6 +103,7 @@
{{end}} {{end}}
</ul> </ul>
</details> </details>
{{end}}
<div class="dot"></div> <div class="dot"></div>
</div> </div>
</li> </li>

View file

@ -98,6 +98,15 @@
</div> </div>
</div> </div>
<span class="help">{{ctx.Locale.Tr "repo.release.prerelease_helper"}}</span> <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="divider tw-mt-0"></div>
<div class="tw-flex tw-justify-end"> <div class="tw-flex tw-justify-end">
{{if .PageIsEditRelease}} {{if .PageIsEditRelease}}

View file

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

View file

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

View file

@ -9,15 +9,19 @@ import (
"testing" "testing"
"time" "time"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title string, preRelease, draft bool) { 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 := loginUser(t, "user2")
session.MakeRequest(t, req, http.StatusOK) 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)
}