code.gitea.io/gitea@v1.21.7/routers/web/repo/release.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2018 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package repo
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"strings"
    12  
    13  	"code.gitea.io/gitea/models"
    14  	"code.gitea.io/gitea/models/db"
    15  	repo_model "code.gitea.io/gitea/models/repo"
    16  	"code.gitea.io/gitea/models/unit"
    17  	user_model "code.gitea.io/gitea/models/user"
    18  	"code.gitea.io/gitea/modules/base"
    19  	"code.gitea.io/gitea/modules/context"
    20  	"code.gitea.io/gitea/modules/git"
    21  	"code.gitea.io/gitea/modules/log"
    22  	"code.gitea.io/gitea/modules/markup"
    23  	"code.gitea.io/gitea/modules/markup/markdown"
    24  	"code.gitea.io/gitea/modules/setting"
    25  	"code.gitea.io/gitea/modules/upload"
    26  	"code.gitea.io/gitea/modules/util"
    27  	"code.gitea.io/gitea/modules/web"
    28  	"code.gitea.io/gitea/routers/web/feed"
    29  	"code.gitea.io/gitea/services/forms"
    30  	releaseservice "code.gitea.io/gitea/services/release"
    31  )
    32  
    33  const (
    34  	tplReleasesList base.TplName = "repo/release/list"
    35  	tplReleaseNew   base.TplName = "repo/release/new"
    36  	tplTagsList     base.TplName = "repo/tag/list"
    37  )
    38  
    39  // calReleaseNumCommitsBehind calculates given release has how many commits behind release target.
    40  func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *repo_model.Release, countCache map[string]int64) error {
    41  	target := release.Target
    42  	if target == "" {
    43  		target = repoCtx.Repository.DefaultBranch
    44  	}
    45  	// Get count if not cached
    46  	if _, ok := countCache[target]; !ok {
    47  		commit, err := repoCtx.GitRepo.GetBranchCommit(target)
    48  		if err != nil {
    49  			var errNotExist git.ErrNotExist
    50  			if target == repoCtx.Repository.DefaultBranch || !errors.As(err, &errNotExist) {
    51  				return fmt.Errorf("GetBranchCommit: %w", err)
    52  			}
    53  			// fallback to default branch
    54  			target = repoCtx.Repository.DefaultBranch
    55  			commit, err = repoCtx.GitRepo.GetBranchCommit(target)
    56  			if err != nil {
    57  				return fmt.Errorf("GetBranchCommit(DefaultBranch): %w", err)
    58  			}
    59  		}
    60  		countCache[target], err = commit.CommitsCount()
    61  		if err != nil {
    62  			return fmt.Errorf("CommitsCount: %w", err)
    63  		}
    64  	}
    65  	release.NumCommitsBehind = countCache[target] - release.NumCommits
    66  	release.TargetBehind = target
    67  	return nil
    68  }
    69  
    70  // Releases render releases list page
    71  func Releases(ctx *context.Context) {
    72  	ctx.Data["PageIsReleaseList"] = true
    73  	ctx.Data["Title"] = ctx.Tr("repo.release.releases")
    74  	releasesOrTags(ctx, false)
    75  }
    76  
    77  // TagsList render tags list page
    78  func TagsList(ctx *context.Context) {
    79  	ctx.Data["PageIsTagList"] = true
    80  	ctx.Data["Title"] = ctx.Tr("repo.release.tags")
    81  	releasesOrTags(ctx, true)
    82  }
    83  
    84  func releasesOrTags(ctx *context.Context, isTagList bool) {
    85  	ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
    86  	ctx.Data["IsViewBranch"] = false
    87  	ctx.Data["IsViewTag"] = true
    88  	// Disable the showCreateNewBranch form in the dropdown on this page.
    89  	ctx.Data["CanCreateBranch"] = false
    90  	ctx.Data["HideBranchesInDropdown"] = true
    91  
    92  	listOptions := db.ListOptions{
    93  		Page:     ctx.FormInt("page"),
    94  		PageSize: ctx.FormInt("limit"),
    95  	}
    96  	if listOptions.PageSize == 0 {
    97  		listOptions.PageSize = setting.Repository.Release.DefaultPagingNum
    98  	}
    99  	if listOptions.PageSize > setting.API.MaxResponseItems {
   100  		listOptions.PageSize = setting.API.MaxResponseItems
   101  	}
   102  
   103  	// TODO(20073) tags are used for compare feature which needs all tags
   104  	// filtering is done on the client-side atm
   105  	tagListStart, tagListEnd := 0, 0
   106  	if isTagList {
   107  		tagListStart, tagListEnd = listOptions.GetStartEnd()
   108  	}
   109  
   110  	tags, err := ctx.Repo.GitRepo.GetTags(tagListStart, tagListEnd)
   111  	if err != nil {
   112  		ctx.ServerError("GetTags", err)
   113  		return
   114  	}
   115  	ctx.Data["Tags"] = tags
   116  
   117  	writeAccess := ctx.Repo.CanWrite(unit.TypeReleases)
   118  	ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
   119  
   120  	opts := repo_model.FindReleasesOptions{
   121  		ListOptions: listOptions,
   122  	}
   123  	if isTagList {
   124  		// for the tags list page, show all releases with real tags (having real commit-id),
   125  		// the drafts should also be included because a real tag might be used as a draft.
   126  		opts.IncludeDrafts = true
   127  		opts.IncludeTags = true
   128  		opts.HasSha1 = util.OptionalBoolTrue
   129  	} else {
   130  		// only show draft releases for users who can write, read-only users shouldn't see draft releases.
   131  		opts.IncludeDrafts = writeAccess
   132  	}
   133  
   134  	releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts)
   135  	if err != nil {
   136  		ctx.ServerError("GetReleasesByRepoID", err)
   137  		return
   138  	}
   139  
   140  	count, err := repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, opts)
   141  	if err != nil {
   142  		ctx.ServerError("GetReleaseCountByRepoID", err)
   143  		return
   144  	}
   145  
   146  	for _, release := range releases {
   147  		release.Repo = ctx.Repo.Repository
   148  	}
   149  
   150  	if err = repo_model.GetReleaseAttachments(ctx, releases...); err != nil {
   151  		ctx.ServerError("GetReleaseAttachments", err)
   152  		return
   153  	}
   154  
   155  	// Temporary cache commits count of used branches to speed up.
   156  	countCache := make(map[string]int64)
   157  	cacheUsers := make(map[int64]*user_model.User)
   158  	if ctx.Doer != nil {
   159  		cacheUsers[ctx.Doer.ID] = ctx.Doer
   160  	}
   161  	var ok bool
   162  
   163  	for _, r := range releases {
   164  		if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok {
   165  			r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
   166  			if err != nil {
   167  				if user_model.IsErrUserNotExist(err) {
   168  					r.Publisher = user_model.NewGhostUser()
   169  				} else {
   170  					ctx.ServerError("GetUserByID", err)
   171  					return
   172  				}
   173  			}
   174  			cacheUsers[r.PublisherID] = r.Publisher
   175  		}
   176  
   177  		r.Note, err = markdown.RenderString(&markup.RenderContext{
   178  			Links: markup.Links{
   179  				Base: ctx.Repo.RepoLink,
   180  			},
   181  			Metas:   ctx.Repo.Repository.ComposeMetas(),
   182  			GitRepo: ctx.Repo.GitRepo,
   183  			Ctx:     ctx,
   184  		}, r.Note)
   185  		if err != nil {
   186  			ctx.ServerError("RenderString", err)
   187  			return
   188  		}
   189  
   190  		if r.IsDraft {
   191  			continue
   192  		}
   193  
   194  		if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil {
   195  			ctx.ServerError("calReleaseNumCommitsBehind", err)
   196  			return
   197  		}
   198  	}
   199  
   200  	ctx.Data["Releases"] = releases
   201  
   202  	pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
   203  	pager.SetDefaultParams(ctx)
   204  	ctx.Data["Page"] = pager
   205  
   206  	if isTagList {
   207  		ctx.Data["PageIsViewCode"] = !ctx.Repo.Repository.UnitEnabled(ctx, unit.TypeReleases)
   208  		ctx.HTML(http.StatusOK, tplTagsList)
   209  	} else {
   210  		ctx.HTML(http.StatusOK, tplReleasesList)
   211  	}
   212  }
   213  
   214  // ReleasesFeedRSS get feeds for releases in RSS format
   215  func ReleasesFeedRSS(ctx *context.Context) {
   216  	releasesOrTagsFeed(ctx, true, "rss")
   217  }
   218  
   219  // TagsListFeedRSS get feeds for tags in RSS format
   220  func TagsListFeedRSS(ctx *context.Context) {
   221  	releasesOrTagsFeed(ctx, false, "rss")
   222  }
   223  
   224  // ReleasesFeedAtom get feeds for releases in Atom format
   225  func ReleasesFeedAtom(ctx *context.Context) {
   226  	releasesOrTagsFeed(ctx, true, "atom")
   227  }
   228  
   229  // TagsListFeedAtom get feeds for tags in RSS format
   230  func TagsListFeedAtom(ctx *context.Context) {
   231  	releasesOrTagsFeed(ctx, false, "atom")
   232  }
   233  
   234  func releasesOrTagsFeed(ctx *context.Context, isReleasesOnly bool, formatType string) {
   235  	feed.ShowReleaseFeed(ctx, ctx.Repo.Repository, isReleasesOnly, formatType)
   236  }
   237  
   238  // SingleRelease renders a single release's page
   239  func SingleRelease(ctx *context.Context) {
   240  	ctx.Data["PageIsReleaseList"] = true
   241  	ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
   242  
   243  	writeAccess := ctx.Repo.CanWrite(unit.TypeReleases)
   244  	ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
   245  
   246  	release, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, ctx.Params("*"))
   247  	if err != nil {
   248  		if repo_model.IsErrReleaseNotExist(err) {
   249  			ctx.NotFound("GetRelease", err)
   250  			return
   251  		}
   252  		ctx.ServerError("GetReleasesByRepoID", err)
   253  		return
   254  	}
   255  	ctx.Data["PageIsSingleTag"] = release.IsTag
   256  	if release.IsTag {
   257  		ctx.Data["Title"] = release.TagName
   258  	} else {
   259  		ctx.Data["Title"] = release.Title
   260  	}
   261  
   262  	release.Repo = ctx.Repo.Repository
   263  
   264  	err = repo_model.GetReleaseAttachments(ctx, release)
   265  	if err != nil {
   266  		ctx.ServerError("GetReleaseAttachments", err)
   267  		return
   268  	}
   269  
   270  	release.Publisher, err = user_model.GetUserByID(ctx, release.PublisherID)
   271  	if err != nil {
   272  		if user_model.IsErrUserNotExist(err) {
   273  			release.Publisher = user_model.NewGhostUser()
   274  		} else {
   275  			ctx.ServerError("GetUserByID", err)
   276  			return
   277  		}
   278  	}
   279  	if !release.IsDraft {
   280  		if err := calReleaseNumCommitsBehind(ctx.Repo, release, make(map[string]int64)); err != nil {
   281  			ctx.ServerError("calReleaseNumCommitsBehind", err)
   282  			return
   283  		}
   284  	}
   285  	release.Note, err = markdown.RenderString(&markup.RenderContext{
   286  		Links: markup.Links{
   287  			Base: ctx.Repo.RepoLink,
   288  		},
   289  		Metas:   ctx.Repo.Repository.ComposeMetas(),
   290  		GitRepo: ctx.Repo.GitRepo,
   291  		Ctx:     ctx,
   292  	}, release.Note)
   293  	if err != nil {
   294  		ctx.ServerError("RenderString", err)
   295  		return
   296  	}
   297  
   298  	ctx.Data["Releases"] = []*repo_model.Release{release}
   299  	ctx.HTML(http.StatusOK, tplReleasesList)
   300  }
   301  
   302  // LatestRelease redirects to the latest release
   303  func LatestRelease(ctx *context.Context) {
   304  	release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID)
   305  	if err != nil {
   306  		if repo_model.IsErrReleaseNotExist(err) {
   307  			ctx.NotFound("LatestRelease", err)
   308  			return
   309  		}
   310  		ctx.ServerError("GetLatestReleaseByRepoID", err)
   311  		return
   312  	}
   313  
   314  	if err := release.LoadAttributes(ctx); err != nil {
   315  		ctx.ServerError("LoadAttributes", err)
   316  		return
   317  	}
   318  
   319  	ctx.Redirect(release.Link())
   320  }
   321  
   322  // NewRelease render creating or edit release page
   323  func NewRelease(ctx *context.Context) {
   324  	ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
   325  	ctx.Data["PageIsReleaseList"] = true
   326  	ctx.Data["tag_target"] = ctx.Repo.Repository.DefaultBranch
   327  	if tagName := ctx.FormString("tag"); len(tagName) > 0 {
   328  		rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
   329  		if err != nil && !repo_model.IsErrReleaseNotExist(err) {
   330  			ctx.ServerError("GetRelease", err)
   331  			return
   332  		}
   333  
   334  		if rel != nil {
   335  			rel.Repo = ctx.Repo.Repository
   336  			if err := rel.LoadAttributes(ctx); err != nil {
   337  				ctx.ServerError("LoadAttributes", err)
   338  				return
   339  			}
   340  
   341  			ctx.Data["tag_name"] = rel.TagName
   342  			if rel.Target != "" {
   343  				ctx.Data["tag_target"] = rel.Target
   344  			}
   345  			ctx.Data["title"] = rel.Title
   346  			ctx.Data["content"] = rel.Note
   347  			ctx.Data["attachments"] = rel.Attachments
   348  		}
   349  	}
   350  	ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
   351  	assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
   352  	if err != nil {
   353  		ctx.ServerError("GetRepoAssignees", err)
   354  		return
   355  	}
   356  	ctx.Data["Assignees"] = MakeSelfOnTop(ctx.Doer, assigneeUsers)
   357  
   358  	upload.AddUploadContext(ctx, "release")
   359  
   360  	// For New Release page
   361  	PrepareBranchList(ctx)
   362  	if ctx.Written() {
   363  		return
   364  	}
   365  
   366  	tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
   367  	if err != nil {
   368  		ctx.ServerError("GetTagNamesByRepoID", err)
   369  		return
   370  	}
   371  	ctx.Data["Tags"] = tags
   372  
   373  	ctx.HTML(http.StatusOK, tplReleaseNew)
   374  }
   375  
   376  // NewReleasePost response for creating a release
   377  func NewReleasePost(ctx *context.Context) {
   378  	form := web.GetForm(ctx).(*forms.NewReleaseForm)
   379  	ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
   380  	ctx.Data["PageIsReleaseList"] = true
   381  
   382  	tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
   383  	if err != nil {
   384  		ctx.ServerError("GetTagNamesByRepoID", err)
   385  		return
   386  	}
   387  	ctx.Data["Tags"] = tags
   388  
   389  	if ctx.HasError() {
   390  		ctx.HTML(http.StatusOK, tplReleaseNew)
   391  		return
   392  	}
   393  
   394  	if !ctx.Repo.GitRepo.IsBranchExist(form.Target) {
   395  		ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form)
   396  		return
   397  	}
   398  
   399  	// Title of release cannot be empty
   400  	if len(form.TagOnly) == 0 && len(form.Title) == 0 {
   401  		ctx.RenderWithErr(ctx.Tr("repo.release.title_empty"), tplReleaseNew, &form)
   402  		return
   403  	}
   404  
   405  	var attachmentUUIDs []string
   406  	if setting.Attachment.Enabled {
   407  		attachmentUUIDs = form.Files
   408  	}
   409  
   410  	rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName)
   411  	if err != nil {
   412  		if !repo_model.IsErrReleaseNotExist(err) {
   413  			ctx.ServerError("GetRelease", err)
   414  			return
   415  		}
   416  
   417  		msg := ""
   418  		if len(form.Title) > 0 && form.AddTagMsg {
   419  			msg = form.Title + "\n\n" + form.Content
   420  		}
   421  
   422  		if len(form.TagOnly) > 0 {
   423  			if err = releaseservice.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, form.Target, form.TagName, msg); err != nil {
   424  				if models.IsErrTagAlreadyExists(err) {
   425  					e := err.(models.ErrTagAlreadyExists)
   426  					ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName))
   427  					ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
   428  					return
   429  				}
   430  
   431  				if models.IsErrInvalidTagName(err) {
   432  					ctx.Flash.Error(ctx.Tr("repo.release.tag_name_invalid"))
   433  					ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
   434  					return
   435  				}
   436  
   437  				if models.IsErrProtectedTagName(err) {
   438  					ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
   439  					ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
   440  					return
   441  				}
   442  
   443  				ctx.ServerError("releaseservice.CreateNewTag", err)
   444  				return
   445  			}
   446  
   447  			ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.TagName))
   448  			ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.TagName))
   449  			return
   450  		}
   451  
   452  		rel = &repo_model.Release{
   453  			RepoID:       ctx.Repo.Repository.ID,
   454  			Repo:         ctx.Repo.Repository,
   455  			PublisherID:  ctx.Doer.ID,
   456  			Publisher:    ctx.Doer,
   457  			Title:        form.Title,
   458  			TagName:      form.TagName,
   459  			Target:       form.Target,
   460  			Note:         form.Content,
   461  			IsDraft:      len(form.Draft) > 0,
   462  			IsPrerelease: form.Prerelease,
   463  			IsTag:        false,
   464  		}
   465  
   466  		if err = releaseservice.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs, msg); err != nil {
   467  			ctx.Data["Err_TagName"] = true
   468  			switch {
   469  			case repo_model.IsErrReleaseAlreadyExist(err):
   470  				ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
   471  			case models.IsErrInvalidTagName(err):
   472  				ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form)
   473  			case models.IsErrProtectedTagName(err):
   474  				ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form)
   475  			default:
   476  				ctx.ServerError("CreateRelease", err)
   477  			}
   478  			return
   479  		}
   480  	} else {
   481  		if !rel.IsTag {
   482  			ctx.Data["Err_TagName"] = true
   483  			ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
   484  			return
   485  		}
   486  
   487  		rel.Title = form.Title
   488  		rel.Note = form.Content
   489  		rel.Target = form.Target
   490  		rel.IsDraft = len(form.Draft) > 0
   491  		rel.IsPrerelease = form.Prerelease
   492  		rel.PublisherID = ctx.Doer.ID
   493  		rel.IsTag = false
   494  
   495  		if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil); err != nil {
   496  			ctx.Data["Err_TagName"] = true
   497  			ctx.ServerError("UpdateRelease", err)
   498  			return
   499  		}
   500  	}
   501  	log.Trace("Release created: %s/%s:%s", ctx.Doer.LowerName, ctx.Repo.Repository.Name, form.TagName)
   502  
   503  	ctx.Redirect(ctx.Repo.RepoLink + "/releases")
   504  }
   505  
   506  // EditRelease render release edit page
   507  func EditRelease(ctx *context.Context) {
   508  	ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
   509  	ctx.Data["PageIsReleaseList"] = true
   510  	ctx.Data["PageIsEditRelease"] = true
   511  	ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
   512  	upload.AddUploadContext(ctx, "release")
   513  
   514  	tagName := ctx.Params("*")
   515  	rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
   516  	if err != nil {
   517  		if repo_model.IsErrReleaseNotExist(err) {
   518  			ctx.NotFound("GetRelease", err)
   519  		} else {
   520  			ctx.ServerError("GetRelease", err)
   521  		}
   522  		return
   523  	}
   524  	ctx.Data["ID"] = rel.ID
   525  	ctx.Data["tag_name"] = rel.TagName
   526  	ctx.Data["tag_target"] = rel.Target
   527  	ctx.Data["title"] = rel.Title
   528  	ctx.Data["content"] = rel.Note
   529  	ctx.Data["prerelease"] = rel.IsPrerelease
   530  	ctx.Data["IsDraft"] = rel.IsDraft
   531  
   532  	rel.Repo = ctx.Repo.Repository
   533  	if err := rel.LoadAttributes(ctx); err != nil {
   534  		ctx.ServerError("LoadAttributes", err)
   535  		return
   536  	}
   537  	ctx.Data["attachments"] = rel.Attachments
   538  
   539  	// Get assignees.
   540  	assigneeUsers, err := repo_model.GetRepoAssignees(ctx, rel.Repo)
   541  	if err != nil {
   542  		ctx.ServerError("GetRepoAssignees", err)
   543  		return
   544  	}
   545  	ctx.Data["Assignees"] = MakeSelfOnTop(ctx.Doer, assigneeUsers)
   546  
   547  	ctx.HTML(http.StatusOK, tplReleaseNew)
   548  }
   549  
   550  // EditReleasePost response for edit release
   551  func EditReleasePost(ctx *context.Context) {
   552  	form := web.GetForm(ctx).(*forms.EditReleaseForm)
   553  	ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
   554  	ctx.Data["PageIsReleaseList"] = true
   555  	ctx.Data["PageIsEditRelease"] = true
   556  
   557  	tagName := ctx.Params("*")
   558  	rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
   559  	if err != nil {
   560  		if repo_model.IsErrReleaseNotExist(err) {
   561  			ctx.NotFound("GetRelease", err)
   562  		} else {
   563  			ctx.ServerError("GetRelease", err)
   564  		}
   565  		return
   566  	}
   567  	if rel.IsTag {
   568  		ctx.NotFound("GetRelease", err)
   569  		return
   570  	}
   571  	ctx.Data["tag_name"] = rel.TagName
   572  	ctx.Data["tag_target"] = rel.Target
   573  	ctx.Data["title"] = rel.Title
   574  	ctx.Data["content"] = rel.Note
   575  	ctx.Data["prerelease"] = rel.IsPrerelease
   576  
   577  	if ctx.HasError() {
   578  		ctx.HTML(http.StatusOK, tplReleaseNew)
   579  		return
   580  	}
   581  
   582  	const delPrefix = "attachment-del-"
   583  	const editPrefix = "attachment-edit-"
   584  	var addAttachmentUUIDs, delAttachmentUUIDs []string
   585  	editAttachments := make(map[string]string) // uuid -> new name
   586  	if setting.Attachment.Enabled {
   587  		addAttachmentUUIDs = form.Files
   588  		for k, v := range ctx.Req.Form {
   589  			if strings.HasPrefix(k, delPrefix) && v[0] == "true" {
   590  				delAttachmentUUIDs = append(delAttachmentUUIDs, k[len(delPrefix):])
   591  			} else if strings.HasPrefix(k, editPrefix) {
   592  				editAttachments[k[len(editPrefix):]] = v[0]
   593  			}
   594  		}
   595  	}
   596  
   597  	rel.Title = form.Title
   598  	rel.Note = form.Content
   599  	rel.IsDraft = len(form.Draft) > 0
   600  	rel.IsPrerelease = form.Prerelease
   601  	if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo,
   602  		rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments); err != nil {
   603  		ctx.ServerError("UpdateRelease", err)
   604  		return
   605  	}
   606  	ctx.Redirect(ctx.Repo.RepoLink + "/releases")
   607  }
   608  
   609  // DeleteRelease deletes a release
   610  func DeleteRelease(ctx *context.Context) {
   611  	deleteReleaseOrTag(ctx, false)
   612  }
   613  
   614  // DeleteTag deletes a tag
   615  func DeleteTag(ctx *context.Context) {
   616  	deleteReleaseOrTag(ctx, true)
   617  }
   618  
   619  func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
   620  	redirect := func() {
   621  		if isDelTag {
   622  			ctx.JSONRedirect(ctx.Repo.RepoLink + "/tags")
   623  			return
   624  		}
   625  
   626  		ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
   627  	}
   628  
   629  	rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id"))
   630  	if err != nil {
   631  		if repo_model.IsErrReleaseNotExist(err) {
   632  			ctx.NotFound("GetReleaseForRepoByID", err)
   633  		} else {
   634  			ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
   635  			redirect()
   636  		}
   637  		return
   638  	}
   639  
   640  	if err := releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, isDelTag); err != nil {
   641  		if models.IsErrProtectedTagName(err) {
   642  			ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
   643  		} else {
   644  			ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
   645  		}
   646  	} else {
   647  		if isDelTag {
   648  			ctx.Flash.Success(ctx.Tr("repo.release.deletion_tag_success"))
   649  		} else {
   650  			ctx.Flash.Success(ctx.Tr("repo.release.deletion_success"))
   651  		}
   652  	}
   653  
   654  	redirect()
   655  }