Skip to content

Commit ecd463c

Browse files
kemzebwxiaoguang
andauthored
Validate that the tag doesn't exist when creating a tag via the web (#33241)
Found while investigating #33210. This line no longer makes sense because the form field "TagName" is required, so this would mean that this code path would never be covered. Because it isn't covered, we end up going down the "update release" logic where we eventually set `Release.IsTag` to false (meaning it will now be treated as a release instead of a tag). This snapshot rewrites the condition to ensure that we aren't trying to create a tag that already exists. --------- Co-authored-by: wxiaoguang <[email protected]>
1 parent 58ac17c commit ecd463c

File tree

5 files changed

+238
-167
lines changed

5 files changed

+238
-167
lines changed

routers/web/repo/release.go

Lines changed: 115 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"code.gitea.io/gitea/models/unit"
1818
user_model "code.gitea.io/gitea/models/user"
1919
"code.gitea.io/gitea/modules/git"
20-
"code.gitea.io/gitea/modules/log"
2120
"code.gitea.io/gitea/modules/markup/markdown"
2221
"code.gitea.io/gitea/modules/optional"
2322
"code.gitea.io/gitea/modules/setting"
@@ -330,12 +329,42 @@ func LatestRelease(ctx *context.Context) {
330329
ctx.Redirect(release.Link())
331330
}
332331

333-
// NewRelease render creating or edit release page
334-
func NewRelease(ctx *context.Context) {
332+
func newReleaseCommon(ctx *context.Context) {
335333
ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
336334
ctx.Data["PageIsReleaseList"] = true
335+
336+
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
337+
if err != nil {
338+
ctx.ServerError("GetTagNamesByRepoID", err)
339+
return
340+
}
341+
ctx.Data["Tags"] = tags
342+
343+
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
344+
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
345+
if err != nil {
346+
ctx.ServerError("GetRepoAssignees", err)
347+
return
348+
}
349+
ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers)
350+
351+
upload.AddUploadContext(ctx, "release")
352+
353+
PrepareBranchList(ctx) // for New Release page
354+
}
355+
356+
// NewRelease render creating or edit release page
357+
func NewRelease(ctx *context.Context) {
358+
newReleaseCommon(ctx)
359+
if ctx.Written() {
360+
return
361+
}
362+
363+
ctx.Data["ShowCreateTagOnlyButton"] = true
364+
365+
// pre-fill the form with the tag name, target branch and the existing release (if exists)
337366
ctx.Data["tag_target"] = ctx.Repo.Repository.DefaultBranch
338-
if tagName := ctx.FormString("tag"); len(tagName) > 0 {
367+
if tagName := ctx.FormString("tag"); tagName != "" {
339368
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
340369
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
341370
ctx.ServerError("GetRelease", err)
@@ -344,59 +373,51 @@ func NewRelease(ctx *context.Context) {
344373

345374
if rel != nil {
346375
rel.Repo = ctx.Repo.Repository
347-
if err := rel.LoadAttributes(ctx); err != nil {
376+
if err = rel.LoadAttributes(ctx); err != nil {
348377
ctx.ServerError("LoadAttributes", err)
349378
return
350379
}
351380

381+
ctx.Data["ShowCreateTagOnlyButton"] = false
352382
ctx.Data["tag_name"] = rel.TagName
353-
if rel.Target != "" {
354-
ctx.Data["tag_target"] = rel.Target
355-
}
383+
ctx.Data["tag_target"] = rel.Target
356384
ctx.Data["title"] = rel.Title
357385
ctx.Data["content"] = rel.Note
358386
ctx.Data["attachments"] = rel.Attachments
359387
}
360388
}
361-
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
362-
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
363-
if err != nil {
364-
ctx.ServerError("GetRepoAssignees", err)
365-
return
366-
}
367-
ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers)
368-
369-
upload.AddUploadContext(ctx, "release")
370-
371-
// For New Release page
372-
PrepareBranchList(ctx)
373-
if ctx.Written() {
374-
return
375-
}
376-
377-
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
378-
if err != nil {
379-
ctx.ServerError("GetTagNamesByRepoID", err)
380-
return
381-
}
382-
ctx.Data["Tags"] = tags
383389

384390
ctx.HTML(http.StatusOK, tplReleaseNew)
385391
}
386392

387393
// NewReleasePost response for creating a release
388394
func NewReleasePost(ctx *context.Context) {
395+
newReleaseCommon(ctx)
396+
if ctx.Written() {
397+
return
398+
}
399+
389400
form := web.GetForm(ctx).(*forms.NewReleaseForm)
390-
ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
391-
ctx.Data["PageIsReleaseList"] = true
392401

393-
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
394-
if err != nil {
395-
ctx.ServerError("GetTagNamesByRepoID", err)
402+
// first, check whether the release exists, and prepare "ShowCreateTagOnlyButton"
403+
// the logic should be done before the form error check to make the tmpl has correct variables
404+
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName)
405+
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
406+
ctx.ServerError("GetRelease", err)
396407
return
397408
}
398-
ctx.Data["Tags"] = tags
399409

410+
// We should still show the "tag only" button if the user clicks it, no matter the release exists or not.
411+
// Because if error occurs, end users need to have the chance to edit the name and submit the form with "tag-only" again.
412+
// It is still not completely right, because there could still be cases like this:
413+
// * user visit "new release" page, see the "tag only" button
414+
// * input something, click other buttons but not "tag only"
415+
// * error occurs, the "new release" page is rendered again, but the "tag only" button is gone
416+
// Such cases are not able to be handled by current code, it needs frontend code to toggle the "tag-only" button if the input changes.
417+
// Or another choice is "always show the tag-only button" if error occurs.
418+
ctx.Data["ShowCreateTagOnlyButton"] = form.TagOnly || rel == nil
419+
420+
// do some form checks
400421
if ctx.HasError() {
401422
ctx.HTML(http.StatusOK, tplReleaseNew)
402423
return
@@ -407,59 +428,49 @@ func NewReleasePost(ctx *context.Context) {
407428
return
408429
}
409430

410-
// Title of release cannot be empty
411-
if len(form.TagOnly) == 0 && len(form.Title) == 0 {
431+
if !form.TagOnly && form.Title == "" {
432+
// if not "tag only", then the title of the release cannot be empty
412433
ctx.RenderWithErr(ctx.Tr("repo.release.title_empty"), tplReleaseNew, &form)
413434
return
414435
}
415436

416-
var attachmentUUIDs []string
417-
if setting.Attachment.Enabled {
418-
attachmentUUIDs = form.Files
419-
}
420-
421-
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName)
422-
if err != nil {
423-
if !repo_model.IsErrReleaseNotExist(err) {
424-
ctx.ServerError("GetRelease", err)
425-
return
426-
}
427-
428-
msg := ""
429-
if len(form.Title) > 0 && form.AddTagMsg {
430-
msg = form.Title + "\n\n" + form.Content
437+
handleTagReleaseError := func(err error) {
438+
ctx.Data["Err_TagName"] = true
439+
switch {
440+
case release_service.IsErrTagAlreadyExists(err):
441+
ctx.RenderWithErr(ctx.Tr("repo.branch.tag_collision", form.TagName), tplReleaseNew, &form)
442+
case repo_model.IsErrReleaseAlreadyExist(err):
443+
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
444+
case release_service.IsErrInvalidTagName(err):
445+
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form)
446+
case release_service.IsErrProtectedTagName(err):
447+
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form)
448+
default:
449+
ctx.ServerError("handleTagReleaseError", err)
431450
}
451+
}
432452

433-
if len(form.TagOnly) > 0 {
434-
if err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, form.Target, form.TagName, msg); err != nil {
435-
if release_service.IsErrTagAlreadyExists(err) {
436-
e := err.(release_service.ErrTagAlreadyExists)
437-
ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName))
438-
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
439-
return
440-
}
441-
442-
if release_service.IsErrInvalidTagName(err) {
443-
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_invalid"))
444-
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
445-
return
446-
}
447-
448-
if release_service.IsErrProtectedTagName(err) {
449-
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
450-
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
451-
return
452-
}
453-
454-
ctx.ServerError("release_service.CreateNewTag", err)
455-
return
456-
}
453+
// prepare the git message for creating a new tag
454+
newTagMsg := ""
455+
if form.Title != "" && form.AddTagMsg {
456+
newTagMsg = form.Title + "\n\n" + form.Content
457+
}
457458

458-
ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.TagName))
459-
ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.TagName))
459+
// no release, and tag only
460+
if rel == nil && form.TagOnly {
461+
if err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, form.Target, form.TagName, newTagMsg); err != nil {
462+
handleTagReleaseError(err)
460463
return
461464
}
465+
ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.TagName))
466+
ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.TagName))
467+
return
468+
}
469+
470+
attachmentUUIDs := util.Iif(setting.Attachment.Enabled, form.Files, nil)
462471

472+
// no existing release, create a new release
473+
if rel == nil {
463474
rel = &repo_model.Release{
464475
RepoID: ctx.Repo.Repository.ID,
465476
Repo: ctx.Repo.Repository,
@@ -469,48 +480,39 @@ func NewReleasePost(ctx *context.Context) {
469480
TagName: form.TagName,
470481
Target: form.Target,
471482
Note: form.Content,
472-
IsDraft: len(form.Draft) > 0,
483+
IsDraft: form.Draft,
473484
IsPrerelease: form.Prerelease,
474485
IsTag: false,
475486
}
476-
477-
if err = release_service.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs, msg); err != nil {
478-
ctx.Data["Err_TagName"] = true
479-
switch {
480-
case repo_model.IsErrReleaseAlreadyExist(err):
481-
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
482-
case release_service.IsErrInvalidTagName(err):
483-
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form)
484-
case release_service.IsErrProtectedTagName(err):
485-
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form)
486-
default:
487-
ctx.ServerError("CreateRelease", err)
488-
}
489-
return
490-
}
491-
} else {
492-
if !rel.IsTag {
493-
ctx.Data["Err_TagName"] = true
494-
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
487+
if err = release_service.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs, newTagMsg); err != nil {
488+
handleTagReleaseError(err)
495489
return
496490
}
491+
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
492+
return
493+
}
497494

498-
rel.Title = form.Title
499-
rel.Note = form.Content
500-
rel.Target = form.Target
501-
rel.IsDraft = len(form.Draft) > 0
502-
rel.IsPrerelease = form.Prerelease
503-
rel.PublisherID = ctx.Doer.ID
504-
rel.IsTag = false
505-
506-
if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil); err != nil {
507-
ctx.Data["Err_TagName"] = true
508-
ctx.ServerError("UpdateRelease", err)
509-
return
510-
}
495+
// tag exists, try to convert it to a real release
496+
// old logic: if the release is not a tag (it is a real release), do not update it on the "new release" page
497+
// add new logic: if tag-only, do not convert the tag to a release
498+
if form.TagOnly || !rel.IsTag {
499+
ctx.Data["Err_TagName"] = true
500+
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
501+
return
511502
}
512-
log.Trace("Release created: %s/%s:%s", ctx.Doer.LowerName, ctx.Repo.Repository.Name, form.TagName)
513503

504+
// convert a tag to a real release (set is_tag=false)
505+
rel.Title = form.Title
506+
rel.Note = form.Content
507+
rel.Target = form.Target
508+
rel.IsDraft = form.Draft
509+
rel.IsPrerelease = form.Prerelease
510+
rel.PublisherID = ctx.Doer.ID
511+
rel.IsTag = false
512+
if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil); err != nil {
513+
handleTagReleaseError(err)
514+
return
515+
}
514516
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
515517
}
516518

0 commit comments

Comments
 (0)