forked from NYANDEV/forgejo
d67eac487b
- The code and tests are already there to allow releases to be created on commits. - This patch modifies the web code to take into account that an commitID could've been passed as target. - Added unit test. - Resolves https://codeberg.org/forgejo/forgejo/issues/1196 (cherry picked from commit 90863e0ab51d1b243f67de266bbeeb7a9031c525) (cherry picked from commit c805aa23b5c6c9a8ab79e2e66786a4ef798e827a) (cherry picked from commit cf45567ca60b2a9411694c8e9b649fd77c64bdae) (cherry picked from commit 672a2b91e5612f438bd7951d173f42c223629fd1) (cherry picked from commit 82c930152cd693f8451e9553504365c724e1fced) (cherry picked from commit 95ac2508b3e8dd9fc2b0168600d989dbce0744ec) (cherry picked from commit b13a81ab98a02e30d1b508bb89cdd67a05eae782) (cherry picked from commit 9f463a7c1fa74ce17ab6ff8df49e2bcea3c1bc89) (cherry picked from commit 758ce84dc58e0c689e0fcc34386c7a8ed50f3df9) Conflicts: tests/integration/release_test.go https://codeberg.org/forgejo/forgejo/pulls/1550 (cherry picked from commit edf0531aeead2f68bbb283e437494ace33a8d3b8) (cherry picked from commit 44b29f3a1df81c072737b139cad34435313f086c) (cherry picked from commit b851b674195ecf3020aba55c5f46704fa3405289) (cherry picked from commit 37b408f5aac53bf72cd530722c774d7ace8356e1) (cherry picked from commit e81dbedb88a8601cf5a071176ecdbf29a0018cc1) (cherry picked from commitd5fa6be6ec
) (cherry picked from commitb8c4be2529
) (cherry picked from commit f23ce2843c59e442f63a240862d0d2e009a6eff2) (cherry picked from commit 8b7bcabae27bc5f66c72c44693e1d051231d2a79) (cherry picked from commit 2d6e52dda9b7f5fd29d7700f9a7835627aeada90) (cherry picked from commit 42e4f3ffdd211d3bb45e505a0cf632172bcbf6b2) (cherry picked from commit 39a1f689d8cb7a741cb10c35d4748fb54ecec34a) (cherry picked from commit 553d4872f883b8ac5cd6e9e585c599201b06067a) (cherry picked from commit df3743372576e708b03fe253eac0f37901a524be)
663 lines
19 KiB
Go
663 lines
19 KiB
Go
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package repo
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"code.gitea.io/gitea/models"
|
|
"code.gitea.io/gitea/models/db"
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
"code.gitea.io/gitea/models/unit"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/base"
|
|
"code.gitea.io/gitea/modules/context"
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/markup"
|
|
"code.gitea.io/gitea/modules/markup/markdown"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/upload"
|
|
"code.gitea.io/gitea/modules/util"
|
|
"code.gitea.io/gitea/modules/web"
|
|
"code.gitea.io/gitea/routers/web/feed"
|
|
"code.gitea.io/gitea/services/forms"
|
|
releaseservice "code.gitea.io/gitea/services/release"
|
|
)
|
|
|
|
const (
|
|
tplReleasesList base.TplName = "repo/release/list"
|
|
tplReleaseNew base.TplName = "repo/release/new"
|
|
tplTagsList base.TplName = "repo/tag/list"
|
|
)
|
|
|
|
// calReleaseNumCommitsBehind calculates given release has how many commits behind release target.
|
|
func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *repo_model.Release, countCache map[string]int64) error {
|
|
target := release.Target
|
|
if target == "" {
|
|
target = repoCtx.Repository.DefaultBranch
|
|
}
|
|
// Get count if not cached
|
|
if _, ok := countCache[target]; !ok {
|
|
commit, err := repoCtx.GitRepo.GetBranchCommit(target)
|
|
if err != nil {
|
|
var errNotExist git.ErrNotExist
|
|
if target == repoCtx.Repository.DefaultBranch || !errors.As(err, &errNotExist) {
|
|
return fmt.Errorf("GetBranchCommit: %w", err)
|
|
}
|
|
// fallback to default branch
|
|
target = repoCtx.Repository.DefaultBranch
|
|
commit, err = repoCtx.GitRepo.GetBranchCommit(target)
|
|
if err != nil {
|
|
return fmt.Errorf("GetBranchCommit(DefaultBranch): %w", err)
|
|
}
|
|
}
|
|
countCache[target], err = commit.CommitsCount()
|
|
if err != nil {
|
|
return fmt.Errorf("CommitsCount: %w", err)
|
|
}
|
|
}
|
|
release.NumCommitsBehind = countCache[target] - release.NumCommits
|
|
release.TargetBehind = target
|
|
return nil
|
|
}
|
|
|
|
// Releases render releases list page
|
|
func Releases(ctx *context.Context) {
|
|
ctx.Data["PageIsReleaseList"] = true
|
|
ctx.Data["Title"] = ctx.Tr("repo.release.releases")
|
|
ctx.Data["IsViewBranch"] = false
|
|
ctx.Data["IsViewTag"] = true
|
|
// Disable the showCreateNewBranch form in the dropdown on this page.
|
|
ctx.Data["CanCreateBranch"] = false
|
|
ctx.Data["HideBranchesInDropdown"] = true
|
|
|
|
listOptions := db.ListOptions{
|
|
Page: ctx.FormInt("page"),
|
|
PageSize: ctx.FormInt("limit"),
|
|
}
|
|
if listOptions.PageSize == 0 {
|
|
listOptions.PageSize = setting.Repository.Release.DefaultPagingNum
|
|
}
|
|
if listOptions.PageSize > setting.API.MaxResponseItems {
|
|
listOptions.PageSize = setting.API.MaxResponseItems
|
|
}
|
|
|
|
writeAccess := ctx.Repo.CanWrite(unit.TypeReleases)
|
|
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
|
|
|
|
opts := repo_model.FindReleasesOptions{
|
|
ListOptions: listOptions,
|
|
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
|
|
IncludeDrafts: writeAccess,
|
|
RepoID: ctx.Repo.Repository.ID,
|
|
}
|
|
|
|
releases, err := db.Find[repo_model.Release](ctx, opts)
|
|
if err != nil {
|
|
ctx.ServerError("GetReleasesByRepoID", err)
|
|
return
|
|
}
|
|
|
|
for _, release := range releases {
|
|
release.Repo = ctx.Repo.Repository
|
|
}
|
|
|
|
if err = repo_model.GetReleaseAttachments(ctx, releases...); err != nil {
|
|
ctx.ServerError("GetReleaseAttachments", err)
|
|
return
|
|
}
|
|
|
|
// Temporary cache commits count of used branches to speed up.
|
|
countCache := make(map[string]int64)
|
|
cacheUsers := make(map[int64]*user_model.User)
|
|
if ctx.Doer != nil {
|
|
cacheUsers[ctx.Doer.ID] = ctx.Doer
|
|
}
|
|
var ok bool
|
|
|
|
for _, r := range releases {
|
|
if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok {
|
|
r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
|
|
if err != nil {
|
|
if user_model.IsErrUserNotExist(err) {
|
|
r.Publisher = user_model.NewGhostUser()
|
|
} else {
|
|
ctx.ServerError("GetUserByID", err)
|
|
return
|
|
}
|
|
}
|
|
cacheUsers[r.PublisherID] = r.Publisher
|
|
}
|
|
|
|
r.Note, err = markdown.RenderString(&markup.RenderContext{
|
|
Links: markup.Links{
|
|
Base: ctx.Repo.RepoLink,
|
|
},
|
|
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
|
GitRepo: ctx.Repo.GitRepo,
|
|
Ctx: ctx,
|
|
}, r.Note)
|
|
if err != nil {
|
|
ctx.ServerError("RenderString", err)
|
|
return
|
|
}
|
|
|
|
if r.IsDraft {
|
|
continue
|
|
}
|
|
|
|
if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil {
|
|
ctx.ServerError("calReleaseNumCommitsBehind", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.Data["Releases"] = releases
|
|
|
|
numReleases := ctx.Data["NumReleases"].(int64)
|
|
pager := context.NewPagination(int(numReleases), opts.PageSize, opts.Page, 5)
|
|
pager.SetDefaultParams(ctx)
|
|
ctx.Data["Page"] = pager
|
|
|
|
ctx.HTML(http.StatusOK, tplReleasesList)
|
|
}
|
|
|
|
// TagsList render tags list page
|
|
func TagsList(ctx *context.Context) {
|
|
ctx.Data["PageIsTagList"] = true
|
|
ctx.Data["Title"] = ctx.Tr("repo.release.tags")
|
|
ctx.Data["IsViewBranch"] = false
|
|
ctx.Data["IsViewTag"] = true
|
|
// Disable the showCreateNewBranch form in the dropdown on this page.
|
|
ctx.Data["CanCreateBranch"] = false
|
|
ctx.Data["HideBranchesInDropdown"] = true
|
|
ctx.Data["CanCreateRelease"] = ctx.Repo.CanWrite(unit.TypeReleases) && !ctx.Repo.Repository.IsArchived
|
|
|
|
listOptions := db.ListOptions{
|
|
Page: ctx.FormInt("page"),
|
|
PageSize: ctx.FormInt("limit"),
|
|
}
|
|
if listOptions.PageSize == 0 {
|
|
listOptions.PageSize = setting.Repository.Release.DefaultPagingNum
|
|
}
|
|
if listOptions.PageSize > setting.API.MaxResponseItems {
|
|
listOptions.PageSize = setting.API.MaxResponseItems
|
|
}
|
|
|
|
opts := repo_model.FindReleasesOptions{
|
|
ListOptions: listOptions,
|
|
// for the tags list page, show all releases with real tags (having real commit-id),
|
|
// the drafts should also be included because a real tag might be used as a draft.
|
|
IncludeDrafts: true,
|
|
IncludeTags: true,
|
|
HasSha1: util.OptionalBoolTrue,
|
|
RepoID: ctx.Repo.Repository.ID,
|
|
}
|
|
|
|
releases, err := db.Find[repo_model.Release](ctx, opts)
|
|
if err != nil {
|
|
ctx.ServerError("GetReleasesByRepoID", err)
|
|
return
|
|
}
|
|
|
|
ctx.Data["Releases"] = releases
|
|
|
|
numTags := ctx.Data["NumTags"].(int64)
|
|
pager := context.NewPagination(int(numTags), opts.PageSize, opts.Page, 5)
|
|
pager.SetDefaultParams(ctx)
|
|
ctx.Data["Page"] = pager
|
|
|
|
ctx.Data["PageIsViewCode"] = !ctx.Repo.Repository.UnitEnabled(ctx, unit.TypeReleases)
|
|
ctx.HTML(http.StatusOK, tplTagsList)
|
|
}
|
|
|
|
// ReleasesFeedRSS get feeds for releases in RSS format
|
|
func ReleasesFeedRSS(ctx *context.Context) {
|
|
releasesOrTagsFeed(ctx, true, "rss")
|
|
}
|
|
|
|
// TagsListFeedRSS get feeds for tags in RSS format
|
|
func TagsListFeedRSS(ctx *context.Context) {
|
|
releasesOrTagsFeed(ctx, false, "rss")
|
|
}
|
|
|
|
// ReleasesFeedAtom get feeds for releases in Atom format
|
|
func ReleasesFeedAtom(ctx *context.Context) {
|
|
releasesOrTagsFeed(ctx, true, "atom")
|
|
}
|
|
|
|
// TagsListFeedAtom get feeds for tags in RSS format
|
|
func TagsListFeedAtom(ctx *context.Context) {
|
|
releasesOrTagsFeed(ctx, false, "atom")
|
|
}
|
|
|
|
func releasesOrTagsFeed(ctx *context.Context, isReleasesOnly bool, formatType string) {
|
|
feed.ShowReleaseFeed(ctx, ctx.Repo.Repository, isReleasesOnly, formatType)
|
|
}
|
|
|
|
// SingleRelease renders a single release's page
|
|
func SingleRelease(ctx *context.Context) {
|
|
ctx.Data["PageIsReleaseList"] = true
|
|
ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
|
|
|
|
writeAccess := ctx.Repo.CanWrite(unit.TypeReleases)
|
|
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
|
|
|
|
release, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, ctx.Params("*"))
|
|
if err != nil {
|
|
if repo_model.IsErrReleaseNotExist(err) {
|
|
ctx.NotFound("GetRelease", err)
|
|
return
|
|
}
|
|
ctx.ServerError("GetReleasesByRepoID", err)
|
|
return
|
|
}
|
|
ctx.Data["PageIsSingleTag"] = release.IsTag
|
|
if release.IsTag {
|
|
ctx.Data["Title"] = release.TagName
|
|
} else {
|
|
ctx.Data["Title"] = release.Title
|
|
}
|
|
|
|
release.Repo = ctx.Repo.Repository
|
|
|
|
err = repo_model.GetReleaseAttachments(ctx, release)
|
|
if err != nil {
|
|
ctx.ServerError("GetReleaseAttachments", err)
|
|
return
|
|
}
|
|
|
|
release.Publisher, err = user_model.GetUserByID(ctx, release.PublisherID)
|
|
if err != nil {
|
|
if user_model.IsErrUserNotExist(err) {
|
|
release.Publisher = user_model.NewGhostUser()
|
|
} else {
|
|
ctx.ServerError("GetUserByID", err)
|
|
return
|
|
}
|
|
}
|
|
if !release.IsDraft {
|
|
if err := calReleaseNumCommitsBehind(ctx.Repo, release, make(map[string]int64)); err != nil {
|
|
ctx.ServerError("calReleaseNumCommitsBehind", err)
|
|
return
|
|
}
|
|
}
|
|
release.Note, err = markdown.RenderString(&markup.RenderContext{
|
|
Links: markup.Links{
|
|
Base: ctx.Repo.RepoLink,
|
|
},
|
|
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
|
GitRepo: ctx.Repo.GitRepo,
|
|
Ctx: ctx,
|
|
}, release.Note)
|
|
if err != nil {
|
|
ctx.ServerError("RenderString", err)
|
|
return
|
|
}
|
|
|
|
ctx.Data["Releases"] = []*repo_model.Release{release}
|
|
ctx.HTML(http.StatusOK, tplReleasesList)
|
|
}
|
|
|
|
// LatestRelease redirects to the latest release
|
|
func LatestRelease(ctx *context.Context) {
|
|
release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID)
|
|
if err != nil {
|
|
if repo_model.IsErrReleaseNotExist(err) {
|
|
ctx.NotFound("LatestRelease", err)
|
|
return
|
|
}
|
|
ctx.ServerError("GetLatestReleaseByRepoID", err)
|
|
return
|
|
}
|
|
|
|
if err := release.LoadAttributes(ctx); err != nil {
|
|
ctx.ServerError("LoadAttributes", err)
|
|
return
|
|
}
|
|
|
|
ctx.Redirect(release.Link())
|
|
}
|
|
|
|
// NewRelease render creating or edit release page
|
|
func NewRelease(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
|
|
ctx.Data["PageIsReleaseList"] = true
|
|
ctx.Data["tag_target"] = ctx.Repo.Repository.DefaultBranch
|
|
if tagName := ctx.FormString("tag"); len(tagName) > 0 {
|
|
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
|
|
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
|
|
ctx.ServerError("GetRelease", err)
|
|
return
|
|
}
|
|
|
|
if rel != nil {
|
|
rel.Repo = ctx.Repo.Repository
|
|
if err := rel.LoadAttributes(ctx); err != nil {
|
|
ctx.ServerError("LoadAttributes", err)
|
|
return
|
|
}
|
|
|
|
ctx.Data["tag_name"] = rel.TagName
|
|
if rel.Target != "" {
|
|
ctx.Data["tag_target"] = rel.Target
|
|
}
|
|
ctx.Data["title"] = rel.Title
|
|
ctx.Data["content"] = rel.Note
|
|
ctx.Data["attachments"] = rel.Attachments
|
|
}
|
|
}
|
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
|
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
|
|
if err != nil {
|
|
ctx.ServerError("GetRepoAssignees", err)
|
|
return
|
|
}
|
|
ctx.Data["Assignees"] = MakeSelfOnTop(ctx.Doer, assigneeUsers)
|
|
|
|
upload.AddUploadContext(ctx, "release")
|
|
|
|
// For New Release page
|
|
PrepareBranchList(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
|
|
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
|
|
if err != nil {
|
|
ctx.ServerError("GetTagNamesByRepoID", err)
|
|
return
|
|
}
|
|
ctx.Data["Tags"] = tags
|
|
|
|
ctx.HTML(http.StatusOK, tplReleaseNew)
|
|
}
|
|
|
|
// NewReleasePost response for creating a release
|
|
func NewReleasePost(ctx *context.Context) {
|
|
form := web.GetForm(ctx).(*forms.NewReleaseForm)
|
|
ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
|
|
ctx.Data["PageIsReleaseList"] = true
|
|
|
|
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
|
|
if err != nil {
|
|
ctx.ServerError("GetTagNamesByRepoID", err)
|
|
return
|
|
}
|
|
ctx.Data["Tags"] = tags
|
|
|
|
if ctx.HasError() {
|
|
ctx.HTML(http.StatusOK, tplReleaseNew)
|
|
return
|
|
}
|
|
|
|
// form.Target can be a branch name or a full commitID.
|
|
if !ctx.Repo.GitRepo.IsBranchExist(form.Target) &&
|
|
len(form.Target) == git.SHAFullLength && !ctx.Repo.GitRepo.IsCommitExist(form.Target) {
|
|
ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form)
|
|
return
|
|
}
|
|
|
|
// Title of release cannot be empty
|
|
if len(form.TagOnly) == 0 && len(form.Title) == 0 {
|
|
ctx.RenderWithErr(ctx.Tr("repo.release.title_empty"), tplReleaseNew, &form)
|
|
return
|
|
}
|
|
|
|
var attachmentUUIDs []string
|
|
if setting.Attachment.Enabled {
|
|
attachmentUUIDs = form.Files
|
|
}
|
|
|
|
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName)
|
|
if err != nil {
|
|
if !repo_model.IsErrReleaseNotExist(err) {
|
|
ctx.ServerError("GetRelease", err)
|
|
return
|
|
}
|
|
|
|
msg := ""
|
|
if len(form.Title) > 0 && form.AddTagMsg {
|
|
msg = form.Title + "\n\n" + form.Content
|
|
}
|
|
|
|
if len(form.TagOnly) > 0 {
|
|
if err = releaseservice.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, form.Target, form.TagName, msg); err != nil {
|
|
if models.IsErrTagAlreadyExists(err) {
|
|
e := err.(models.ErrTagAlreadyExists)
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
|
|
return
|
|
}
|
|
|
|
if models.IsErrInvalidTagName(err) {
|
|
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_invalid"))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
|
|
return
|
|
}
|
|
|
|
if models.IsErrProtectedTagName(err) {
|
|
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
|
|
return
|
|
}
|
|
|
|
ctx.ServerError("releaseservice.CreateNewTag", err)
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.TagName))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.TagName))
|
|
return
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
if err = releaseservice.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs, msg); err != nil {
|
|
ctx.Data["Err_TagName"] = true
|
|
switch {
|
|
case repo_model.IsErrReleaseAlreadyExist(err):
|
|
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
|
|
case models.IsErrInvalidTagName(err):
|
|
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form)
|
|
case models.IsErrProtectedTagName(err):
|
|
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form)
|
|
default:
|
|
ctx.ServerError("CreateRelease", err)
|
|
}
|
|
return
|
|
}
|
|
} else {
|
|
if !rel.IsTag {
|
|
ctx.Data["Err_TagName"] = true
|
|
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
|
|
return
|
|
}
|
|
|
|
rel.Title = form.Title
|
|
rel.Note = form.Content
|
|
rel.Target = form.Target
|
|
rel.IsDraft = len(form.Draft) > 0
|
|
rel.IsPrerelease = form.Prerelease
|
|
rel.PublisherID = ctx.Doer.ID
|
|
rel.IsTag = false
|
|
|
|
if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil); err != nil {
|
|
ctx.Data["Err_TagName"] = true
|
|
ctx.ServerError("UpdateRelease", err)
|
|
return
|
|
}
|
|
}
|
|
log.Trace("Release created: %s/%s:%s", ctx.Doer.LowerName, ctx.Repo.Repository.Name, form.TagName)
|
|
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
|
|
}
|
|
|
|
// EditRelease render release edit page
|
|
func EditRelease(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
|
|
ctx.Data["PageIsReleaseList"] = true
|
|
ctx.Data["PageIsEditRelease"] = true
|
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
|
upload.AddUploadContext(ctx, "release")
|
|
|
|
tagName := ctx.Params("*")
|
|
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
|
|
if err != nil {
|
|
if repo_model.IsErrReleaseNotExist(err) {
|
|
ctx.NotFound("GetRelease", err)
|
|
} else {
|
|
ctx.ServerError("GetRelease", err)
|
|
}
|
|
return
|
|
}
|
|
ctx.Data["ID"] = rel.ID
|
|
ctx.Data["tag_name"] = rel.TagName
|
|
ctx.Data["tag_target"] = rel.Target
|
|
ctx.Data["title"] = rel.Title
|
|
ctx.Data["content"] = rel.Note
|
|
ctx.Data["prerelease"] = rel.IsPrerelease
|
|
ctx.Data["IsDraft"] = rel.IsDraft
|
|
|
|
rel.Repo = ctx.Repo.Repository
|
|
if err := rel.LoadAttributes(ctx); err != nil {
|
|
ctx.ServerError("LoadAttributes", err)
|
|
return
|
|
}
|
|
ctx.Data["attachments"] = rel.Attachments
|
|
|
|
// Get assignees.
|
|
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, rel.Repo)
|
|
if err != nil {
|
|
ctx.ServerError("GetRepoAssignees", err)
|
|
return
|
|
}
|
|
ctx.Data["Assignees"] = MakeSelfOnTop(ctx.Doer, assigneeUsers)
|
|
|
|
ctx.HTML(http.StatusOK, tplReleaseNew)
|
|
}
|
|
|
|
// EditReleasePost response for edit release
|
|
func EditReleasePost(ctx *context.Context) {
|
|
form := web.GetForm(ctx).(*forms.EditReleaseForm)
|
|
ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
|
|
ctx.Data["PageIsReleaseList"] = true
|
|
ctx.Data["PageIsEditRelease"] = true
|
|
|
|
tagName := ctx.Params("*")
|
|
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
|
|
if err != nil {
|
|
if repo_model.IsErrReleaseNotExist(err) {
|
|
ctx.NotFound("GetRelease", err)
|
|
} else {
|
|
ctx.ServerError("GetRelease", err)
|
|
}
|
|
return
|
|
}
|
|
if rel.IsTag {
|
|
ctx.NotFound("GetRelease", err)
|
|
return
|
|
}
|
|
ctx.Data["tag_name"] = rel.TagName
|
|
ctx.Data["tag_target"] = rel.Target
|
|
ctx.Data["title"] = rel.Title
|
|
ctx.Data["content"] = rel.Note
|
|
ctx.Data["prerelease"] = rel.IsPrerelease
|
|
|
|
if ctx.HasError() {
|
|
ctx.HTML(http.StatusOK, tplReleaseNew)
|
|
return
|
|
}
|
|
|
|
const delPrefix = "attachment-del-"
|
|
const editPrefix = "attachment-edit-"
|
|
var addAttachmentUUIDs, delAttachmentUUIDs []string
|
|
editAttachments := make(map[string]string) // uuid -> new name
|
|
if setting.Attachment.Enabled {
|
|
addAttachmentUUIDs = form.Files
|
|
for k, v := range ctx.Req.Form {
|
|
if strings.HasPrefix(k, delPrefix) && v[0] == "true" {
|
|
delAttachmentUUIDs = append(delAttachmentUUIDs, k[len(delPrefix):])
|
|
} else if strings.HasPrefix(k, editPrefix) {
|
|
editAttachments[k[len(editPrefix):]] = v[0]
|
|
}
|
|
}
|
|
}
|
|
|
|
rel.Title = form.Title
|
|
rel.Note = form.Content
|
|
rel.IsDraft = len(form.Draft) > 0
|
|
rel.IsPrerelease = form.Prerelease
|
|
if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo,
|
|
rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments); err != nil {
|
|
ctx.ServerError("UpdateRelease", err)
|
|
return
|
|
}
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
|
|
}
|
|
|
|
// DeleteRelease deletes a release
|
|
func DeleteRelease(ctx *context.Context) {
|
|
deleteReleaseOrTag(ctx, false)
|
|
}
|
|
|
|
// DeleteTag deletes a tag
|
|
func DeleteTag(ctx *context.Context) {
|
|
deleteReleaseOrTag(ctx, true)
|
|
}
|
|
|
|
func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
|
|
redirect := func() {
|
|
if isDelTag {
|
|
ctx.JSONRedirect(ctx.Repo.RepoLink + "/tags")
|
|
return
|
|
}
|
|
|
|
ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
|
|
}
|
|
|
|
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id"))
|
|
if err != nil {
|
|
if repo_model.IsErrReleaseNotExist(err) {
|
|
ctx.NotFound("GetReleaseForRepoByID", err)
|
|
} else {
|
|
ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
|
|
redirect()
|
|
}
|
|
return
|
|
}
|
|
|
|
if err := releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, isDelTag); err != nil {
|
|
if models.IsErrProtectedTagName(err) {
|
|
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
|
|
} else {
|
|
ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
|
|
}
|
|
} else {
|
|
if isDelTag {
|
|
ctx.Flash.Success(ctx.Tr("repo.release.deletion_tag_success"))
|
|
} else {
|
|
ctx.Flash.Success(ctx.Tr("repo.release.deletion_success"))
|
|
}
|
|
}
|
|
|
|
redirect()
|
|
}
|