From 4959bf1c097917576472f9f66afa0fb615500c07 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Sun, 15 Sep 2019 23:28:25 +0800
Subject: [PATCH] Move create release from models to a standalone package
 (#7539)

* move create release from models to a standalone package

* fix lint

* fix comment year

* fix lint

* fix lint

* fix package import name

* fix vendor

* fix go mod

* some refactors

* fix vendor

* use go1.12 make vendor

* fix vendor
---
 models/release.go                            | 126 ++----------------
 models/repo_mirror.go                        |   6 +
 routers/api/v1/repo/release.go               |   7 +-
 routers/repo/release.go                      |   7 +-
 services/release/release.go                  | 132 +++++++++++++++++++
 {models => services/release}/release_test.go |  60 +++++----
 6 files changed, 191 insertions(+), 147 deletions(-)
 create mode 100644 services/release/release.go
 rename {models => services/release}/release_test.go (62%)

diff --git a/models/release.go b/models/release.go
index e35a55741a..e3386d074f 100644
--- a/models/release.go
+++ b/models/release.go
@@ -112,43 +112,20 @@ func IsReleaseExist(repoID int64, tagName string) (bool, error) {
 	return x.Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)})
 }
 
-func createTag(gitRepo *git.Repository, rel *Release) error {
-	// Only actual create when publish.
-	if !rel.IsDraft {
-		if !gitRepo.IsTagExist(rel.TagName) {
-			commit, err := gitRepo.GetCommit(rel.Target)
-			if err != nil {
-				return fmt.Errorf("GetCommit: %v", err)
-			}
-
-			// Trim '--' prefix to prevent command line argument vulnerability.
-			rel.TagName = strings.TrimPrefix(rel.TagName, "--")
-			if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil {
-				if strings.Contains(err.Error(), "is not a valid tag name") {
-					return ErrInvalidTagName{rel.TagName}
-				}
-				return err
-			}
-			rel.LowerTagName = strings.ToLower(rel.TagName)
-		}
-		commit, err := gitRepo.GetTagCommit(rel.TagName)
-		if err != nil {
-			return fmt.Errorf("GetTagCommit: %v", err)
-		}
-
-		rel.Sha1 = commit.ID.String()
-		rel.CreatedUnix = timeutil.TimeStamp(commit.Author.When.Unix())
-		rel.NumCommits, err = commit.CommitsCount()
-		if err != nil {
-			return fmt.Errorf("CommitsCount: %v", err)
-		}
-	} else {
-		rel.CreatedUnix = timeutil.TimeStampNow()
-	}
-	return nil
+// InsertRelease inserts a release
+func InsertRelease(rel *Release) error {
+	_, err := x.Insert(rel)
+	return err
 }
 
-func addReleaseAttachments(releaseID int64, attachmentUUIDs []string) (err error) {
+// UpdateRelease updates all columns of a release
+func UpdateRelease(rel *Release) error {
+	_, err := x.ID(rel.ID).AllCols().Update(rel)
+	return err
+}
+
+// AddReleaseAttachments adds a release attachments
+func AddReleaseAttachments(releaseID int64, attachmentUUIDs []string) (err error) {
 	// Check attachments
 	var attachments = make([]*Attachment, 0)
 	for _, uuid := range attachmentUUIDs {
@@ -173,51 +150,6 @@ func addReleaseAttachments(releaseID int64, attachmentUUIDs []string) (err error
 	return
 }
 
-// CreateRelease creates a new release of repository.
-func CreateRelease(gitRepo *git.Repository, rel *Release, attachmentUUIDs []string) error {
-	isExist, err := IsReleaseExist(rel.RepoID, rel.TagName)
-	if err != nil {
-		return err
-	} else if isExist {
-		return ErrReleaseAlreadyExist{rel.TagName}
-	}
-
-	if err = createTag(gitRepo, rel); err != nil {
-		return err
-	}
-	rel.LowerTagName = strings.ToLower(rel.TagName)
-
-	_, err = x.InsertOne(rel)
-	if err != nil {
-		return err
-	}
-
-	err = addReleaseAttachments(rel.ID, attachmentUUIDs)
-	if err != nil {
-		return err
-	}
-
-	if !rel.IsDraft {
-		if err := rel.LoadAttributes(); err != nil {
-			log.Error("LoadAttributes: %v", err)
-		} else {
-			mode, _ := AccessLevel(rel.Publisher, rel.Repo)
-			if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{
-				Action:     api.HookReleasePublished,
-				Release:    rel.APIFormat(),
-				Repository: rel.Repo.APIFormat(mode),
-				Sender:     rel.Publisher.APIFormat(),
-			}); err != nil {
-				log.Error("PrepareWebhooks: %v", err)
-			} else {
-				go HookQueue.Add(rel.Repo.ID)
-			}
-		}
-	}
-
-	return nil
-}
-
 // GetRelease returns release by given ID.
 func GetRelease(repoID int64, tagName string) (*Release, error) {
 	isExist, err := IsReleaseExist(repoID, tagName)
@@ -385,40 +317,6 @@ func SortReleases(rels []*Release) {
 	sort.Sort(sorter)
 }
 
-// UpdateRelease updates information of a release.
-func UpdateRelease(doer *User, gitRepo *git.Repository, rel *Release, attachmentUUIDs []string) (err error) {
-	if err = createTag(gitRepo, rel); err != nil {
-		return err
-	}
-	rel.LowerTagName = strings.ToLower(rel.TagName)
-
-	_, err = x.ID(rel.ID).AllCols().Update(rel)
-	if err != nil {
-		return err
-	}
-
-	err = rel.loadAttributes(x)
-	if err != nil {
-		return err
-	}
-
-	err = addReleaseAttachments(rel.ID, attachmentUUIDs)
-
-	mode, _ := AccessLevel(doer, rel.Repo)
-	if err1 := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{
-		Action:     api.HookReleaseUpdated,
-		Release:    rel.APIFormat(),
-		Repository: rel.Repo.APIFormat(mode),
-		Sender:     doer.APIFormat(),
-	}); err1 != nil {
-		log.Error("PrepareWebhooks: %v", err)
-	} else {
-		go HookQueue.Add(rel.Repo.ID)
-	}
-
-	return err
-}
-
 // DeleteReleaseByID deletes a release and corresponding Git tag by given ID.
 func DeleteReleaseByID(id int64, doer *User, delTag bool) error {
 	rel, err := GetReleaseByID(id)
diff --git a/models/repo_mirror.go b/models/repo_mirror.go
index 9801c05ae4..e1c1aefc39 100644
--- a/models/repo_mirror.go
+++ b/models/repo_mirror.go
@@ -318,6 +318,12 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
 	return parseRemoteUpdateOutput(output), true
 }
 
+// RunMirrorSync will invoke Mirror's runSync
+func RunMirrorSync(mirror *Mirror) bool {
+	_, ok := mirror.runSync()
+	return ok
+}
+
 func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) {
 	m := &Mirror{RepoID: repoID}
 	has, err := e.Get(m)
diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go
index 9296f22e80..da32b955fb 100644
--- a/routers/api/v1/repo/release.go
+++ b/routers/api/v1/repo/release.go
@@ -9,6 +9,7 @@ import (
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
+	releaseservice "code.gitea.io/gitea/services/release"
 )
 
 // GetRelease get a single release of a repository
@@ -168,7 +169,7 @@ func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) {
 			IsTag:        false,
 			Repo:         ctx.Repo.Repository,
 		}
-		if err := models.CreateRelease(ctx.Repo.GitRepo, rel, nil); err != nil {
+		if err := releaseservice.CreateRelease(ctx.Repo.GitRepo, rel, nil); err != nil {
 			if models.IsErrReleaseAlreadyExist(err) {
 				ctx.Status(409)
 			} else {
@@ -191,7 +192,7 @@ func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) {
 		rel.Repo = ctx.Repo.Repository
 		rel.Publisher = ctx.User
 
-		if err = models.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, nil); err != nil {
+		if err = releaseservice.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, nil); err != nil {
 			ctx.ServerError("UpdateRelease", err)
 			return
 		}
@@ -262,7 +263,7 @@ func EditRelease(ctx *context.APIContext, form api.EditReleaseOption) {
 	if form.IsPrerelease != nil {
 		rel.IsPrerelease = *form.IsPrerelease
 	}
-	if err := models.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, nil); err != nil {
+	if err := releaseservice.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, nil); err != nil {
 		ctx.Error(500, "UpdateRelease", err)
 		return
 	}
diff --git a/routers/repo/release.go b/routers/repo/release.go
index fdef533f10..95423a92b2 100644
--- a/routers/repo/release.go
+++ b/routers/repo/release.go
@@ -15,6 +15,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/setting"
+	releaseservice "code.gitea.io/gitea/services/release"
 )
 
 const (
@@ -175,7 +176,7 @@ func NewReleasePost(ctx *context.Context, form auth.NewReleaseForm) {
 			IsTag:        false,
 		}
 
-		if err = models.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil {
+		if err = releaseservice.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil {
 			ctx.Data["Err_TagName"] = true
 			switch {
 			case models.IsErrReleaseAlreadyExist(err):
@@ -202,7 +203,7 @@ func NewReleasePost(ctx *context.Context, form auth.NewReleaseForm) {
 		rel.PublisherID = ctx.User.ID
 		rel.IsTag = false
 
-		if err = models.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil {
+		if err = releaseservice.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil {
 			ctx.Data["Err_TagName"] = true
 			ctx.ServerError("UpdateRelease", err)
 			return
@@ -281,7 +282,7 @@ func EditReleasePost(ctx *context.Context, form auth.EditReleaseForm) {
 	rel.Note = form.Content
 	rel.IsDraft = len(form.Draft) > 0
 	rel.IsPrerelease = form.Prerelease
-	if err = models.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil {
+	if err = releaseservice.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil {
 		ctx.ServerError("UpdateRelease", err)
 		return
 	}
diff --git a/services/release/release.go b/services/release/release.go
new file mode 100644
index 0000000000..4451633798
--- /dev/null
+++ b/services/release/release.go
@@ -0,0 +1,132 @@
+// Copyright 2019 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 release
+
+import (
+	"fmt"
+	"strings"
+
+	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/log"
+	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/timeutil"
+)
+
+func createTag(gitRepo *git.Repository, rel *models.Release) error {
+	// Only actual create when publish.
+	if !rel.IsDraft {
+		if !gitRepo.IsTagExist(rel.TagName) {
+			commit, err := gitRepo.GetCommit(rel.Target)
+			if err != nil {
+				return fmt.Errorf("GetCommit: %v", err)
+			}
+
+			// Trim '--' prefix to prevent command line argument vulnerability.
+			rel.TagName = strings.TrimPrefix(rel.TagName, "--")
+			if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil {
+				if strings.Contains(err.Error(), "is not a valid tag name") {
+					return models.ErrInvalidTagName{
+						TagName: rel.TagName,
+					}
+				}
+				return err
+			}
+			rel.LowerTagName = strings.ToLower(rel.TagName)
+		}
+		commit, err := gitRepo.GetTagCommit(rel.TagName)
+		if err != nil {
+			return fmt.Errorf("GetTagCommit: %v", err)
+		}
+
+		rel.Sha1 = commit.ID.String()
+		rel.CreatedUnix = timeutil.TimeStamp(commit.Author.When.Unix())
+		rel.NumCommits, err = commit.CommitsCount()
+		if err != nil {
+			return fmt.Errorf("CommitsCount: %v", err)
+		}
+	} else {
+		rel.CreatedUnix = timeutil.TimeStampNow()
+	}
+	return nil
+}
+
+// CreateRelease creates a new release of repository.
+func CreateRelease(gitRepo *git.Repository, rel *models.Release, attachmentUUIDs []string) error {
+	isExist, err := models.IsReleaseExist(rel.RepoID, rel.TagName)
+	if err != nil {
+		return err
+	} else if isExist {
+		return models.ErrReleaseAlreadyExist{
+			TagName: rel.TagName,
+		}
+	}
+
+	if err = createTag(gitRepo, rel); err != nil {
+		return err
+	}
+
+	rel.LowerTagName = strings.ToLower(rel.TagName)
+	if err = models.InsertRelease(rel); err != nil {
+		return err
+	}
+
+	if err = models.AddReleaseAttachments(rel.ID, attachmentUUIDs); err != nil {
+		return err
+	}
+
+	if !rel.IsDraft {
+		if err := rel.LoadAttributes(); err != nil {
+			log.Error("LoadAttributes: %v", err)
+		} else {
+			mode, _ := models.AccessLevel(rel.Publisher, rel.Repo)
+			if err := models.PrepareWebhooks(rel.Repo, models.HookEventRelease, &api.ReleasePayload{
+				Action:     api.HookReleasePublished,
+				Release:    rel.APIFormat(),
+				Repository: rel.Repo.APIFormat(mode),
+				Sender:     rel.Publisher.APIFormat(),
+			}); err != nil {
+				log.Error("PrepareWebhooks: %v", err)
+			} else {
+				go models.HookQueue.Add(rel.Repo.ID)
+			}
+		}
+	}
+
+	return nil
+}
+
+// UpdateRelease updates information of a release.
+func UpdateRelease(doer *models.User, gitRepo *git.Repository, rel *models.Release, attachmentUUIDs []string) (err error) {
+	if err = createTag(gitRepo, rel); err != nil {
+		return err
+	}
+	rel.LowerTagName = strings.ToLower(rel.TagName)
+
+	if err = models.UpdateRelease(rel); err != nil {
+		return err
+	}
+
+	if err = rel.LoadAttributes(); err != nil {
+		return err
+	}
+
+	err = models.AddReleaseAttachments(rel.ID, attachmentUUIDs)
+
+	// even if attachments added failed, hooks will be still triggered
+	mode, _ := models.AccessLevel(doer, rel.Repo)
+	if err1 := models.PrepareWebhooks(rel.Repo, models.HookEventRelease, &api.ReleasePayload{
+		Action:     api.HookReleaseUpdated,
+		Release:    rel.APIFormat(),
+		Repository: rel.Repo.APIFormat(mode),
+		Sender:     doer.APIFormat(),
+	}); err1 != nil {
+		log.Error("PrepareWebhooks: %v", err)
+	} else {
+		go models.HookQueue.Add(rel.Repo.ID)
+	}
+
+	return err
+}
diff --git a/models/release_test.go b/services/release/release_test.go
similarity index 62%
rename from models/release_test.go
rename to services/release/release_test.go
index 6da3cd7628..91dd0a73b5 100644
--- a/models/release_test.go
+++ b/services/release/release_test.go
@@ -2,27 +2,33 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package release
 
 import (
+	"path/filepath"
 	"testing"
 
+	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/git"
 
 	"github.com/stretchr/testify/assert"
 )
 
-func TestRelease_Create(t *testing.T) {
-	assert.NoError(t, PrepareTestDatabase())
+func TestMain(m *testing.M) {
+	models.MainTest(m, filepath.Join("..", ".."))
+}
 
-	user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
-	repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
-	repoPath := RepoPath(user.Name, repo.Name)
+func TestRelease_Create(t *testing.T) {
+	assert.NoError(t, models.PrepareTestDatabase())
+
+	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
+	repoPath := models.RepoPath(user.Name, repo.Name)
 
 	gitRepo, err := git.OpenRepository(repoPath)
 	assert.NoError(t, err)
 
-	assert.NoError(t, CreateRelease(gitRepo, &Release{
+	assert.NoError(t, CreateRelease(gitRepo, &models.Release{
 		RepoID:       repo.ID,
 		PublisherID:  user.ID,
 		TagName:      "v0.1",
@@ -34,7 +40,7 @@ func TestRelease_Create(t *testing.T) {
 		IsTag:        false,
 	}, nil))
 
-	assert.NoError(t, CreateRelease(gitRepo, &Release{
+	assert.NoError(t, CreateRelease(gitRepo, &models.Release{
 		RepoID:       repo.ID,
 		PublisherID:  user.ID,
 		TagName:      "v0.1.1",
@@ -46,7 +52,7 @@ func TestRelease_Create(t *testing.T) {
 		IsTag:        false,
 	}, nil))
 
-	assert.NoError(t, CreateRelease(gitRepo, &Release{
+	assert.NoError(t, CreateRelease(gitRepo, &models.Release{
 		RepoID:       repo.ID,
 		PublisherID:  user.ID,
 		TagName:      "v0.1.2",
@@ -58,7 +64,7 @@ func TestRelease_Create(t *testing.T) {
 		IsTag:        false,
 	}, nil))
 
-	assert.NoError(t, CreateRelease(gitRepo, &Release{
+	assert.NoError(t, CreateRelease(gitRepo, &models.Release{
 		RepoID:       repo.ID,
 		PublisherID:  user.ID,
 		TagName:      "v0.1.3",
@@ -70,7 +76,7 @@ func TestRelease_Create(t *testing.T) {
 		IsTag:        false,
 	}, nil))
 
-	assert.NoError(t, CreateRelease(gitRepo, &Release{
+	assert.NoError(t, CreateRelease(gitRepo, &models.Release{
 		RepoID:       repo.ID,
 		PublisherID:  user.ID,
 		TagName:      "v0.1.4",
@@ -82,7 +88,7 @@ func TestRelease_Create(t *testing.T) {
 		IsTag:        false,
 	}, nil))
 
-	assert.NoError(t, CreateRelease(gitRepo, &Release{
+	assert.NoError(t, CreateRelease(gitRepo, &models.Release{
 		RepoID:       repo.ID,
 		PublisherID:  user.ID,
 		TagName:      "v0.1.5",
@@ -96,12 +102,12 @@ func TestRelease_Create(t *testing.T) {
 }
 
 func TestRelease_MirrorDelete(t *testing.T) {
-	assert.NoError(t, PrepareTestDatabase())
+	assert.NoError(t, models.PrepareTestDatabase())
 
-	user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
-	repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
-	repoPath := RepoPath(user.Name, repo.Name)
-	migrationOptions := MigrateRepoOptions{
+	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
+	repoPath := models.RepoPath(user.Name, repo.Name)
+	migrationOptions := models.MigrateRepoOptions{
 		Name:                 "test_mirror",
 		Description:          "Test mirror",
 		IsPrivate:            false,
@@ -110,17 +116,17 @@ func TestRelease_MirrorDelete(t *testing.T) {
 		Wiki:                 true,
 		SyncReleasesWithTags: true,
 	}
-	mirror, err := MigrateRepository(user, user, migrationOptions)
+	mirror, err := models.MigrateRepository(user, user, migrationOptions)
 	assert.NoError(t, err)
 
 	gitRepo, err := git.OpenRepository(repoPath)
 	assert.NoError(t, err)
 
-	findOptions := FindReleasesOptions{IncludeDrafts: true, IncludeTags: true}
-	initCount, err := GetReleaseCountByRepoID(mirror.ID, findOptions)
+	findOptions := models.FindReleasesOptions{IncludeDrafts: true, IncludeTags: true}
+	initCount, err := models.GetReleaseCountByRepoID(mirror.ID, findOptions)
 	assert.NoError(t, err)
 
-	assert.NoError(t, CreateRelease(gitRepo, &Release{
+	assert.NoError(t, CreateRelease(gitRepo, &models.Release{
 		RepoID:       repo.ID,
 		PublisherID:  user.ID,
 		TagName:      "v0.2",
@@ -135,21 +141,21 @@ func TestRelease_MirrorDelete(t *testing.T) {
 	err = mirror.GetMirror()
 	assert.NoError(t, err)
 
-	_, ok := mirror.Mirror.runSync()
+	ok := models.RunMirrorSync(mirror.Mirror)
 	assert.True(t, ok)
 
-	count, err := GetReleaseCountByRepoID(mirror.ID, findOptions)
+	count, err := models.GetReleaseCountByRepoID(mirror.ID, findOptions)
 	assert.NoError(t, err)
 	assert.EqualValues(t, initCount+1, count)
 
-	release, err := GetRelease(repo.ID, "v0.2")
+	release, err := models.GetRelease(repo.ID, "v0.2")
 	assert.NoError(t, err)
-	assert.NoError(t, DeleteReleaseByID(release.ID, user, true))
+	assert.NoError(t, models.DeleteReleaseByID(release.ID, user, true))
 
-	_, ok = mirror.Mirror.runSync()
+	ok = models.RunMirrorSync(mirror.Mirror)
 	assert.True(t, ok)
 
-	count, err = GetReleaseCountByRepoID(mirror.ID, findOptions)
+	count, err = models.GetReleaseCountByRepoID(mirror.ID, findOptions)
 	assert.NoError(t, err)
 	assert.EqualValues(t, initCount, count)
 }