forked from Shiloh/githaven
e918636283
* show author for releases created outside Gitea UI. Also show the number of commits behind the default branch for tags created outside the UI don't show the tag date again for tags pushed to the repo. Since it is already on the sidebar and looks like duplication * add migration for already existing tags * update as per review * fix build * add space * fix import statments * Update models/migrations/v113.go Co-Authored-By: zeripath <art27@cantab.net> * Update models/migrations/v114.go Co-authored-by: 6543 <6543@obermui.de> * Update services/release/release.go Co-authored-by: 6543 <6543@obermui.de> * impruve * remove dependency on models package * Close the gitrepos in a defer to ensure that they are closed. * gofmt Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
367 lines
10 KiB
Go
367 lines
10 KiB
Go
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package repo
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"code.gitea.io/gitea/models"
|
|
"code.gitea.io/gitea/modules/auth"
|
|
"code.gitea.io/gitea/modules/base"
|
|
"code.gitea.io/gitea/modules/context"
|
|
"code.gitea.io/gitea/modules/convert"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/markup/markdown"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/upload"
|
|
releaseservice "code.gitea.io/gitea/services/release"
|
|
)
|
|
|
|
const (
|
|
tplReleases base.TplName = "repo/release/list"
|
|
tplReleaseNew base.TplName = "repo/release/new"
|
|
)
|
|
|
|
// calReleaseNumCommitsBehind calculates given release has how many commits behind release target.
|
|
func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *models.Release, countCache map[string]int64) error {
|
|
// Fast return if release target is same as default branch.
|
|
if repoCtx.BranchName == release.Target {
|
|
release.NumCommitsBehind = repoCtx.CommitsCount - release.NumCommits
|
|
return nil
|
|
}
|
|
|
|
// Get count if not exists
|
|
if _, ok := countCache[release.Target]; !ok {
|
|
if repoCtx.GitRepo.IsBranchExist(release.Target) {
|
|
commit, err := repoCtx.GitRepo.GetBranchCommit(release.Target)
|
|
if err != nil {
|
|
return fmt.Errorf("GetBranchCommit: %v", err)
|
|
}
|
|
countCache[release.Target], err = commit.CommitsCount()
|
|
if err != nil {
|
|
return fmt.Errorf("CommitsCount: %v", err)
|
|
}
|
|
} else {
|
|
// Use NumCommits of the newest release on that target
|
|
countCache[release.Target] = release.NumCommits
|
|
}
|
|
}
|
|
release.NumCommitsBehind = countCache[release.Target] - release.NumCommits
|
|
return nil
|
|
}
|
|
|
|
// Releases render releases list page
|
|
func Releases(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.release.releases")
|
|
ctx.Data["PageIsReleaseList"] = true
|
|
ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
|
|
|
|
writeAccess := ctx.Repo.CanWrite(models.UnitTypeReleases)
|
|
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
|
|
|
|
opts := models.FindReleasesOptions{
|
|
ListOptions: models.ListOptions{
|
|
Page: ctx.QueryInt("page"),
|
|
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
|
|
},
|
|
IncludeDrafts: writeAccess,
|
|
IncludeTags: true,
|
|
}
|
|
|
|
releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, opts)
|
|
if err != nil {
|
|
ctx.ServerError("GetReleasesByRepoID", err)
|
|
return
|
|
}
|
|
|
|
count, err := models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, opts)
|
|
if err != nil {
|
|
ctx.ServerError("GetReleaseCountByRepoID", err)
|
|
return
|
|
}
|
|
|
|
err = models.GetReleaseAttachments(releases...)
|
|
if 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]*models.User)
|
|
if ctx.User != nil {
|
|
cacheUsers[ctx.User.ID] = ctx.User
|
|
}
|
|
var ok bool
|
|
|
|
for _, r := range releases {
|
|
if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok {
|
|
r.Publisher, err = models.GetUserByID(r.PublisherID)
|
|
if err != nil {
|
|
if models.IsErrUserNotExist(err) {
|
|
r.Publisher = models.NewGhostUser()
|
|
} else {
|
|
ctx.ServerError("GetUserByID", err)
|
|
return
|
|
}
|
|
}
|
|
cacheUsers[r.PublisherID] = r.Publisher
|
|
}
|
|
if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil {
|
|
ctx.ServerError("calReleaseNumCommitsBehind", err)
|
|
return
|
|
}
|
|
r.Note = markdown.RenderString(r.Note, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())
|
|
}
|
|
|
|
ctx.Data["Releases"] = releases
|
|
|
|
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
|
|
pager.SetDefaultParams(ctx)
|
|
ctx.Data["Page"] = pager
|
|
|
|
ctx.HTML(200, tplReleases)
|
|
}
|
|
|
|
// SingleRelease renders a single release's page
|
|
func SingleRelease(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.release.releases")
|
|
ctx.Data["PageIsReleaseList"] = true
|
|
|
|
writeAccess := ctx.Repo.CanWrite(models.UnitTypeReleases)
|
|
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
|
|
|
|
release, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("*"))
|
|
if err != nil {
|
|
if models.IsErrReleaseNotExist(err) {
|
|
ctx.NotFound("GetRelease", err)
|
|
return
|
|
}
|
|
ctx.ServerError("GetReleasesByRepoID", err)
|
|
return
|
|
}
|
|
|
|
err = models.GetReleaseAttachments(release)
|
|
if err != nil {
|
|
ctx.ServerError("GetReleaseAttachments", err)
|
|
return
|
|
}
|
|
|
|
release.Publisher, err = models.GetUserByID(release.PublisherID)
|
|
if err != nil {
|
|
if models.IsErrUserNotExist(err) {
|
|
release.Publisher = models.NewGhostUser()
|
|
} else {
|
|
ctx.ServerError("GetUserByID", err)
|
|
return
|
|
}
|
|
}
|
|
if err := calReleaseNumCommitsBehind(ctx.Repo, release, make(map[string]int64)); err != nil {
|
|
ctx.ServerError("calReleaseNumCommitsBehind", err)
|
|
return
|
|
}
|
|
release.Note = markdown.RenderString(release.Note, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())
|
|
|
|
ctx.Data["Releases"] = []*models.Release{release}
|
|
ctx.HTML(200, tplReleases)
|
|
}
|
|
|
|
// LatestRelease redirects to the latest release
|
|
func LatestRelease(ctx *context.Context) {
|
|
release, err := models.GetLatestReleaseByRepoID(ctx.Repo.Repository.ID)
|
|
if err != nil {
|
|
if models.IsErrReleaseNotExist(err) {
|
|
ctx.NotFound("LatestRelease", err)
|
|
return
|
|
}
|
|
ctx.ServerError("GetLatestReleaseByRepoID", err)
|
|
return
|
|
}
|
|
|
|
if err := release.LoadAttributes(); err != nil {
|
|
ctx.ServerError("LoadAttributes", err)
|
|
return
|
|
}
|
|
|
|
ctx.Redirect(release.HTMLURL())
|
|
}
|
|
|
|
// NewRelease render creating 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
|
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
|
upload.AddUploadContext(ctx, "release")
|
|
ctx.HTML(200, tplReleaseNew)
|
|
}
|
|
|
|
// NewReleasePost response for creating a release
|
|
func NewReleasePost(ctx *context.Context, form auth.NewReleaseForm) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
|
|
ctx.Data["PageIsReleaseList"] = true
|
|
|
|
if ctx.HasError() {
|
|
ctx.HTML(200, tplReleaseNew)
|
|
return
|
|
}
|
|
|
|
if !ctx.Repo.GitRepo.IsBranchExist(form.Target) {
|
|
ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form)
|
|
return
|
|
}
|
|
|
|
var attachmentUUIDs []string
|
|
if setting.Attachment.Enabled {
|
|
attachmentUUIDs = form.Files
|
|
}
|
|
|
|
rel, err := models.GetRelease(ctx.Repo.Repository.ID, form.TagName)
|
|
if err != nil {
|
|
if !models.IsErrReleaseNotExist(err) {
|
|
ctx.ServerError("GetRelease", err)
|
|
return
|
|
}
|
|
|
|
rel = &models.Release{
|
|
RepoID: ctx.Repo.Repository.ID,
|
|
PublisherID: ctx.User.ID,
|
|
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); err != nil {
|
|
ctx.Data["Err_TagName"] = true
|
|
switch {
|
|
case models.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)
|
|
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.User.ID
|
|
rel.IsTag = false
|
|
|
|
if err = releaseservice.UpdateReleaseOrCreatReleaseFromTag(ctx.User, ctx.Repo.GitRepo, rel, attachmentUUIDs, true); err != nil {
|
|
ctx.Data["Err_TagName"] = true
|
|
ctx.ServerError("UpdateReleaseOrCreatReleaseFromTag", err)
|
|
return
|
|
}
|
|
}
|
|
log.Trace("Release created: %s/%s:%s", ctx.User.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 := models.GetRelease(ctx.Repo.Repository.ID, tagName)
|
|
if err != nil {
|
|
if models.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
|
|
|
|
ctx.HTML(200, tplReleaseNew)
|
|
}
|
|
|
|
// EditReleasePost response for edit release
|
|
func EditReleasePost(ctx *context.Context, form auth.EditReleaseForm) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
|
|
ctx.Data["PageIsReleaseList"] = true
|
|
ctx.Data["PageIsEditRelease"] = true
|
|
|
|
tagName := ctx.Params("*")
|
|
rel, err := models.GetRelease(ctx.Repo.Repository.ID, tagName)
|
|
if err != nil {
|
|
if models.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(200, tplReleaseNew)
|
|
return
|
|
}
|
|
|
|
var attachmentUUIDs []string
|
|
if setting.Attachment.Enabled {
|
|
attachmentUUIDs = form.Files
|
|
}
|
|
|
|
rel.Title = form.Title
|
|
rel.Note = form.Content
|
|
rel.IsDraft = len(form.Draft) > 0
|
|
rel.IsPrerelease = form.Prerelease
|
|
if err = releaseservice.UpdateReleaseOrCreatReleaseFromTag(ctx.User, ctx.Repo.GitRepo, rel, attachmentUUIDs, false); err != nil {
|
|
ctx.ServerError("UpdateRelease", err)
|
|
return
|
|
}
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
|
|
}
|
|
|
|
// DeleteRelease delete a release
|
|
func DeleteRelease(ctx *context.Context) {
|
|
if err := releaseservice.DeleteReleaseByID(ctx.QueryInt64("id"), ctx.User, true); err != nil {
|
|
ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
|
|
} else {
|
|
ctx.Flash.Success(ctx.Tr("repo.release.deletion_success"))
|
|
}
|
|
|
|
ctx.JSON(200, map[string]interface{}{
|
|
"redirect": ctx.Repo.RepoLink + "/releases",
|
|
})
|
|
}
|