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