From 1bce2dc5c576b4818b407efcb04e90019bbe8af6 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Wed, 24 Apr 2024 15:15:55 +0000 Subject: [PATCH] [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 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: JakobDev Co-committed-by: JakobDev --- models/forgejo_migrations/migrate.go | 2 + models/forgejo_migrations/v13.go | 15 +++++ models/repo/release.go | 1 + modules/structs/release.go | 51 ++++++++-------- options/locale/locale_en-US.ini | 2 + routers/api/v1/repo/release.go | 27 +++++---- routers/web/repo/release.go | 41 +++++++++---- services/convert/release.go | 1 + services/forms/repo_form.go | 30 +++++----- templates/repo/release/list.tmpl | 83 +++++++++++++------------- templates/repo/release/new.tmpl | 9 +++ templates/swagger/v1_json.tmpl | 12 ++++ tests/integration/api_releases_test.go | 17 ++++-- tests/integration/release_test.go | 35 +++++++++++ 14 files changed, 220 insertions(+), 106 deletions(-) create mode 100644 models/forgejo_migrations/v13.go diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go index 697efb5b5..b9e0e384c 100644 --- a/models/forgejo_migrations/migrate.go +++ b/models/forgejo_migrations/migrate.go @@ -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. diff --git a/models/forgejo_migrations/v13.go b/models/forgejo_migrations/v13.go new file mode 100644 index 000000000..614f68249 --- /dev/null +++ b/models/forgejo_migrations/v13.go @@ -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{}) +} diff --git a/models/repo/release.go b/models/repo/release.go index 3168bdaae..075e28717 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -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"` diff --git a/modules/structs/release.go b/modules/structs/release.go index 02c68af18..d8da924f5 100644 --- a/modules/structs/release.go +++ b/modules/structs/release.go @@ -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"` } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 2c6d831c8..d7680f354 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -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 diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index c6495c3de..057282b21 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -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 diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index bb1644b89..3927e3d2d 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -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) diff --git a/services/convert/release.go b/services/convert/release.go index fb8bd4567..8c0f61b56 100644 --- a/services/convert/release.go +++ b/services/convert/release.go @@ -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, diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index e0540852a..e4fcf8e0c 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -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 diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index 2d38cacfc..4755da091 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -61,46 +61,49 @@
{{$release.RenderedNote}}
-
-
- - {{ctx.Locale.Tr "repo.release.downloads"}} - - -
+ {{$hasReleaseAttachment := gt (len $release.Attachments) 0}} + {{$hasArchiveLinks := and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) (not $release.HideArchiveLinks) ($.Permission.CanRead $.UnitTypeCode)}} + {{if or $hasArchiveLinks $hasReleaseAttachment}} +
+
+ + {{ctx.Locale.Tr "repo.release.downloads"}} + + +
+ {{end}}
diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl index 81104a70d..e1881b1d9 100644 --- a/templates/repo/release/new.tmpl +++ b/templates/repo/release/new.tmpl @@ -98,6 +98,15 @@ {{ctx.Locale.Tr "repo.release.prerelease_helper"}} + {{if not .DisableDownloadSourceArchives}} +
+
+ + +
+
+ {{ctx.Locale.Tr "repo.release.hide_archive_links_helper"}} + {{end}}
{{if .PageIsEditRelease}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index c68a90a4c..c49c6208e 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -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" diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index 5dc51044e..0a392d0a1 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -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) { diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go index acc8cb68c..ad3c7bf32 100644 --- a/tests/integration/release_test.go +++ b/tests/integration/release_test.go @@ -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) +}