浏览代码

webhook: add Release event (#2387)

Unknwon 7 年之前
父节点
当前提交
b615d670b3

+ 2 - 0
conf/locale/locale_en-US.ini

@@ -776,6 +776,8 @@ settings.event_issue_comment = Issue Comment
 settings.event_issue_comment_desc = Issue comment created, edited, or deleted.
 settings.event_pull_request = Pull Request
 settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, demilestoned, or synchronized.
+settings.event_release = Release
+settings.event_release_desc = Release published in a repository.
 settings.active = Active
 settings.active_helper = Details regarding the event which triggered the hook will be delivered as well.
 settings.add_hook_success = New webhook has been added.

+ 2 - 1
models/issue.go

@@ -12,9 +12,10 @@ import (
 
 	"github.com/Unknwon/com"
 	"github.com/go-xorm/xorm"
-	api "github.com/gogits/go-gogs-client"
 	log "gopkg.in/clog.v1"
 
+	api "github.com/gogits/go-gogs-client"
+
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/setting"
 )

+ 66 - 7
models/release.go

@@ -11,8 +11,10 @@ import (
 	"time"
 
 	"github.com/go-xorm/xorm"
+	log "gopkg.in/clog.v1"
 
 	"github.com/gogits/git-module"
+	api "github.com/gogits/go-gogs-client"
 
 	"github.com/gogits/gogs/modules/process"
 )
@@ -21,6 +23,7 @@ import (
 type Release struct {
 	ID               int64 `xorm:"pk autoincr"`
 	RepoID           int64
+	Repo             *Repository `xorm:"-"`
 	PublisherID      int64
 	Publisher        *User `xorm:"-"`
 	TagName          string
@@ -52,6 +55,13 @@ func (r *Release) AfterSet(colName string, _ xorm.Cell) {
 }
 
 func (r *Release) loadAttributes(e Engine) (err error) {
+	if r.Repo == nil {
+		r.Repo, err = getRepositoryByID(e, r.RepoID)
+		if err != nil {
+			return fmt.Errorf("getRepositoryByID [repo_id: %d]: %v", r.RepoID, err)
+		}
+	}
+
 	if r.Publisher == nil {
 		r.Publisher, err = getUserByID(e, r.PublisherID)
 		if err != nil {
@@ -59,7 +69,7 @@ func (r *Release) loadAttributes(e Engine) (err error) {
 				r.PublisherID = -1
 				r.Publisher = NewGhostUser()
 			} else {
-				return fmt.Errorf("getUserByID.(Publisher) [%d]: %v", r.PublisherID, err)
+				return fmt.Errorf("getUserByID.(Publisher) [publisher_id: %d]: %v", r.PublisherID, err)
 			}
 		}
 	}
@@ -71,6 +81,22 @@ func (r *Release) LoadAttributes() error {
 	return r.loadAttributes(x)
 }
 
+// This method assumes some fields assigned with values:
+// Required - Publisher
+func (r *Release) APIFormat() *api.Release {
+	return &api.Release{
+		ID:              r.ID,
+		TagName:         r.TagName,
+		TargetCommitish: r.Target,
+		Name:            r.Title,
+		Body:            r.Note,
+		Draft:           r.IsDraft,
+		Prerelease:      r.IsPrerelease,
+		Author:          r.Publisher.APIFormat(),
+		Created:         r.Created,
+	}
+}
+
 // IsReleaseExist returns true if release with given tag name already exists.
 func IsReleaseExist(repoID int64, tagName string) (bool, error) {
 	if len(tagName) == 0 {
@@ -113,6 +139,17 @@ func createTag(gitRepo *git.Repository, r *Release) error {
 	return nil
 }
 
+func (r *Release) preparePublishWebhooks() {
+	if err := PrepareWebhooks(r.Repo, HOOK_EVENT_RELEASE, &api.ReleasePayload{
+		Action:     api.HOOK_RELEASE_PUBLISHED,
+		Release:    r.APIFormat(),
+		Repository: r.Repo.APIFormat(nil),
+		Sender:     r.Publisher.APIFormat(),
+	}); err != nil {
+		log.Error(2, "PrepareWebhooks: %v", err)
+	}
+}
+
 // CreateRelease creates a new release of repository.
 func CreateRelease(gitRepo *git.Repository, r *Release) error {
 	isExist, err := IsReleaseExist(r.RepoID, r.TagName)
@@ -126,8 +163,20 @@ func CreateRelease(gitRepo *git.Repository, r *Release) error {
 		return err
 	}
 	r.LowerTagName = strings.ToLower(r.TagName)
-	_, err = x.InsertOne(r)
-	return err
+	if _, err = x.Insert(r); err != nil {
+		return fmt.Errorf("Insert: %v", err)
+	}
+
+	// Only send webhook when actually published, skip drafts
+	if r.IsDraft {
+		return nil
+	}
+	r, err = GetReleaseByID(r.ID)
+	if err != nil {
+		return fmt.Errorf("GetReleaseByID: %v", err)
+	}
+	r.preparePublishWebhooks()
+	return nil
 }
 
 // GetRelease returns release by given ID.
@@ -205,12 +254,22 @@ func SortReleases(rels []*Release) {
 }
 
 // UpdateRelease updates information of a release.
-func UpdateRelease(gitRepo *git.Repository, rel *Release) (err error) {
-	if err = createTag(gitRepo, rel); err != nil {
+func UpdateRelease(doer *User, gitRepo *git.Repository, r *Release, isPublish bool) (err error) {
+	if err = createTag(gitRepo, r); err != nil {
+		return fmt.Errorf("createTag: %v", err)
+	}
+
+	r.PublisherID = doer.ID
+	if _, err = x.Id(r.ID).AllCols().Update(r); err != nil {
 		return err
 	}
-	_, err = x.Id(rel.ID).AllCols().Update(rel)
-	return err
+
+	if !isPublish {
+		return nil
+	}
+	r.Publisher = doer
+	r.preparePublishWebhooks()
+	return nil
 }
 
 // DeleteReleaseOfRepoByID deletes a release and corresponding Git tag by given ID.

+ 9 - 0
models/webhook.go

@@ -69,6 +69,7 @@ type HookEvents struct {
 	Issues       bool `json:"issues"`
 	IssueComment bool `json:"issue_comment"`
 	PullRequest  bool `json:"pull_request"`
+	Release      bool `json:"release"`
 }
 
 // HookEvent represents events that will delivery hook.
@@ -196,6 +197,12 @@ func (w *Webhook) HasPullRequestEvent() bool {
 		(w.ChooseEvents && w.HookEvents.PullRequest)
 }
 
+// HasReleaseEvent returns true if hook enabled release event.
+func (w *Webhook) HasReleaseEvent() bool {
+	return w.SendEverything ||
+		(w.ChooseEvents && w.HookEvents.Release)
+}
+
 type eventChecker struct {
 	checker func() bool
 	typ     HookEventType
@@ -211,6 +218,7 @@ func (w *Webhook) EventsArray() []string {
 		{w.HasIssuesEvent, HOOK_EVENT_ISSUES},
 		{w.HasIssueCommentEvent, HOOK_EVENT_ISSUE_COMMENT},
 		{w.HasPullRequestEvent, HOOK_EVENT_PULL_REQUEST},
+		{w.HasReleaseEvent, HOOK_EVENT_RELEASE},
 	}
 	for _, c := range eventCheckers {
 		if c.checker() {
@@ -381,6 +389,7 @@ const (
 	HOOK_EVENT_ISSUES        HookEventType = "issues"
 	HOOK_EVENT_ISSUE_COMMENT HookEventType = "issue_comment"
 	HOOK_EVENT_PULL_REQUEST  HookEventType = "pull_request"
+	HOOK_EVENT_RELEASE       HookEventType = "release"
 )
 
 // HookRequest represents hook task request information.

+ 18 - 0
models/webhook_discord.go

@@ -353,6 +353,22 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (
 	}, nil
 }
 
+func getDiscordReleasePayload(p *api.ReleasePayload) (*DiscordPayload, error) {
+	repoLink := DiscordLinkFormatter(p.Repository.HTMLURL, p.Repository.Name)
+	refLink := DiscordLinkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName)
+	content := fmt.Sprintf("Published new release %s of %s", refLink, repoLink)
+	return &DiscordPayload{
+		Embeds: []*DiscordEmbedObject{{
+			Description: content,
+			URL:         setting.AppUrl + p.Sender.UserName,
+			Author: &DiscordEmbedAuthorObject{
+				Name:    p.Sender.UserName,
+				IconURL: p.Sender.AvatarUrl,
+			},
+		}},
+	}, nil
+}
+
 func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (payload *DiscordPayload, err error) {
 	slack := &SlackMeta{}
 	if err := json.Unmarshal([]byte(meta), &slack); err != nil {
@@ -374,6 +390,8 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (paylo
 		payload, err = getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
 	case HOOK_EVENT_PULL_REQUEST:
 		payload, err = getDiscordPullRequestPayload(p.(*api.PullRequestPayload), slack)
+	case HOOK_EVENT_RELEASE:
+		payload, err = getDiscordReleasePayload(p.(*api.ReleasePayload))
 	}
 	if err != nil {
 		return nil, fmt.Errorf("event '%s': %v", event, err)

+ 11 - 0
models/webhook_slack.go

@@ -277,6 +277,15 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S
 	}, nil
 }
 
+func getSlackReleasePayload(p *api.ReleasePayload) (*SlackPayload, error) {
+	repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.Name)
+	refLink := SlackLinkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName)
+	text := fmt.Sprintf("[%s] new release %s published by %s", repoLink, refLink, p.Sender.UserName)
+	return &SlackPayload{
+		Text: text,
+	}, nil
+}
+
 func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (payload *SlackPayload, err error) {
 	slack := &SlackMeta{}
 	if err := json.Unmarshal([]byte(meta), &slack); err != nil {
@@ -298,6 +307,8 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (payload
 		payload, err = getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
 	case HOOK_EVENT_PULL_REQUEST:
 		payload, err = getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
+	case HOOK_EVENT_RELEASE:
+		payload, err = getSlackReleasePayload(p.(*api.ReleasePayload))
 	}
 	if err != nil {
 		return nil, fmt.Errorf("event '%s': %v", event, err)

文件差异内容过多而无法显示
+ 3 - 3
modules/bindata/bindata.go


+ 1 - 0
modules/form/repo.go

@@ -141,6 +141,7 @@ type Webhook struct {
 	Issues       bool
 	IssueComment bool
 	PullRequest  bool
+	Release      bool
 	Active       bool
 }
 

+ 15 - 19
public/css/gogs.css

@@ -1227,30 +1227,30 @@ footer .ui.language .menu {
   right: 0!important;
   left: auto!important;
 }
-.repository.branches .ui.list {
+.repository.branches:not(.settings) .ui.list {
   padding: 0;
 }
-.repository.branches .ui.list > .item {
+.repository.branches:not(.settings) .ui.list > .item {
   margin: 0;
   line-height: 31px;
 }
-.repository.branches .ui.list > .item:not(:last-child) {
+.repository.branches:not(.settings) .ui.list > .item:not(:last-child) {
   border-bottom: 1px solid #DDD;
 }
-.repository.branches .ui.list > .item .column {
+.repository.branches:not(.settings) .ui.list > .item .column {
   padding: 5px 15px;
 }
-.repository.branches .ui.list > .item .column .octicon {
+.repository.branches:not(.settings) .ui.list > .item .column .octicon {
   vertical-align: text-bottom;
 }
-.repository.branches .ui.list > .item .column code {
+.repository.branches:not(.settings) .ui.list > .item .column code {
   padding: 4px 0;
   font-size: 12px;
 }
-.repository.branches .ui.list > .item .column .ui.text:not(i) {
+.repository.branches:not(.settings) .ui.list > .item .column .ui.text:not(i) {
   font-size: 12px;
 }
-.repository.branches .ui.list > .item .column .ui.button {
+.repository.branches:not(.settings) .ui.list > .item .column .ui.button {
   font-size: 12px;
   padding: 8px 10px;
 }
@@ -2338,28 +2338,28 @@ footer .ui.language .menu {
   margin-left: 5px;
   margin-top: -3px;
 }
-.repository.settings.branches .protected-branches .selection.dropdown {
+.repository.settings.settings.branches .protected-branches .selection.dropdown {
   width: 300px;
 }
-.repository.settings.branches .protected-branches .item {
+.repository.settings.settings.branches .protected-branches .item {
   border: 1px solid #eaeaea;
   padding: 10px 15px;
 }
-.repository.settings.branches .protected-branches .item:not(:last-child) {
+.repository.settings.settings.branches .protected-branches .item:not(:last-child) {
   border-bottom: 0;
 }
-.repository.settings.branches .branch-protection .help {
+.repository.settings.settings.branches .branch-protection .help {
   margin-left: 26px;
   padding-top: 0;
 }
-.repository.settings.branches .branch-protection .fields {
+.repository.settings.settings.branches .branch-protection .fields {
   margin-left: 20px;
   display: block;
 }
-.repository.settings.branches .branch-protection .whitelist {
+.repository.settings.settings.branches .branch-protection .whitelist {
   margin-left: 26px;
 }
-.repository.settings.branches .branch-protection .whitelist .dropdown img {
+.repository.settings.settings.branches .branch-protection .whitelist .dropdown img {
   display: inline-block;
 }
 .repository.settings.webhooks .types .menu .item {
@@ -3035,10 +3035,6 @@ footer .ui.language .menu {
 .admin .table.segment:not(.select) td:first-of-type {
   padding-left: 15px !important;
 }
-.admin .ui.header,
-.admin .ui.segment {
-  box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15);
-}
 .admin.user .email {
   max-width: 200px;
 }

+ 0 - 4
public/less/_admin.less

@@ -29,10 +29,6 @@
 			}
 		}
 	}
-	.ui.header,
-	.ui.segment {
-		box-shadow: 0 1px 2px 0 rgba(34,36,38,.15);
-	}
 
 	&.user {
 		.email {

+ 2 - 2
public/less/_repository.less

@@ -150,7 +150,7 @@
 		}
 	}
 
-	&.branches {
+	&.branches:not(.settings) {
 		.ui.list {
 			padding: 0;
 			>.item {
@@ -1351,7 +1351,7 @@
 			}
 		}
 
-		&.branches {
+		&.settings.branches {
 			.protected-branches {
 				.selection.dropdown {
 					width: 300px;

+ 2 - 2
routers/repo/release.go

@@ -203,7 +203,6 @@ func NewReleasePost(ctx *context.Context, f form.NewRelease) {
 		IsPrerelease: f.Prerelease,
 		CreatedUnix:  tagCreatedUnix,
 	}
-
 	if err = models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil {
 		ctx.Data["Err_TagName"] = true
 		switch {
@@ -274,11 +273,12 @@ func EditReleasePost(ctx *context.Context, f form.EditRelease) {
 		return
 	}
 
+	isPublish := rel.IsDraft && len(f.Draft) == 0
 	rel.Title = f.Title
 	rel.Note = f.Content
 	rel.IsDraft = len(f.Draft) > 0
 	rel.IsPrerelease = f.Prerelease
-	if err = models.UpdateRelease(ctx.Repo.GitRepo, rel); err != nil {
+	if err = models.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, isPublish); err != nil {
 		ctx.Handle(500, "UpdateRelease", err)
 		return
 	}

+ 1 - 0
routers/repo/webhook.go

@@ -116,6 +116,7 @@ func ParseHookEvent(f form.Webhook) *models.HookEvent {
 			Issues:       f.Issues,
 			IssueComment: f.IssueComment,
 			PullRequest:  f.PullRequest,
+			Release:      f.Release,
 		},
 	}
 }

+ 10 - 0
templates/repo/settings/webhook_settings.tmpl

@@ -92,6 +92,16 @@
 				</div>
 			</div>
 		</div>
+		<!-- Release -->
+		<div class="seven wide column">
+			<div class="field">
+				<div class="ui checkbox">
+					<input class="hidden" name="release" type="checkbox" tabindex="0" {{if .Webhook.Release}}checked{{end}}>
+					<label>{{.i18n.Tr "repo.settings.event_release"}}</label>
+					<span class="help">{{.i18n.Tr "repo.settings.event_release_desc"}}</span>
+				</div>
+			</div>
+		</div>
 	</div>
 </div>
 

+ 1 - 1
vendor/github.com/gogits/go-gogs-client/gogs.go

@@ -14,7 +14,7 @@ import (
 )
 
 func Version() string {
-	return "0.12.9"
+	return "0.12.10"
 }
 
 // Client represents a Gogs API client.

+ 22 - 0
vendor/github.com/gogits/go-gogs-client/release.go

@@ -0,0 +1,22 @@
+// Copyright 2017 The Gogs 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 gogs
+
+import (
+	"time"
+)
+
+// Release represents a release API object.
+type Release struct {
+	ID              int64     `json:"id"`
+	TagName         string    `json:"tag_name"`
+	TargetCommitish string    `json:"target_commitish"`
+	Name            string    `json:"name"`
+	Body            string    `json:"body"`
+	Draft           bool      `json:"draft"`
+	Prerelease      bool      `json:"prerelease"`
+	Author          *User     `json:"author"`
+	Created         time.Time `json:"created_at"`
+}

+ 27 - 0
vendor/github.com/gogits/go-gogs-client/repo_hook.go

@@ -256,6 +256,7 @@ type ChangesPayload struct {
 	Body  *ChangesFromPayload `json:"body,omitempty"`
 }
 
+// IssuesPayload represents a payload information of issues event.
 type IssuesPayload struct {
 	Action     HookIssueAction `json:"action"`
 	Index      int64           `json:"number"`
@@ -277,6 +278,7 @@ const (
 	HOOK_ISSUE_COMMENT_DELETED HookIssueCommentAction = "deleted"
 )
 
+// IssueCommentPayload represents a payload information of issue comment event.
 type IssueCommentPayload struct {
 	Action     HookIssueCommentAction `json:"action"`
 	Issue      *Issue                 `json:"issue"`
@@ -310,3 +312,28 @@ type PullRequestPayload struct {
 func (p *PullRequestPayload) JSONPayload() ([]byte, error) {
 	return json.MarshalIndent(p, "", "  ")
 }
+
+// __________       .__
+// \______   \ ____ |  |   ____ _____    ______ ____
+//  |       _// __ \|  | _/ __ \\__  \  /  ___// __ \
+//  |    |   \  ___/|  |_\  ___/ / __ \_\___ \\  ___/
+//  |____|_  /\___  >____/\___  >____  /____  >\___  >
+//         \/     \/          \/     \/     \/     \/
+
+type HookReleaseAction string
+
+const (
+	HOOK_RELEASE_PUBLISHED HookReleaseAction = "published"
+)
+
+// ReleasePayload represents a payload information of release event.
+type ReleasePayload struct {
+	Action     HookReleaseAction `json:"action"`
+	Release    *Release          `json:"release"`
+	Repository *Repository       `json:"repository"`
+	Sender     *User             `json:"sender"`
+}
+
+func (p *ReleasePayload) JSONPayload() ([]byte, error) {
+	return json.MarshalIndent(p, "", "  ")
+}

+ 3 - 3
vendor/vendor.json

@@ -165,10 +165,10 @@
 			"revisionTime": "2017-03-10T19:06:55Z"
 		},
 		{
-			"checksumSHA1": "Rvj0LCHGhFQyIM7MzBPt1iRP89c=",
+			"checksumSHA1": "1p1/OSDPORWbSBCD791BbGh2vVc=",
 			"path": "github.com/gogits/go-gogs-client",
-			"revision": "8e438478f71b840fcd0b3e810684ea4f6bf476bb",
-			"revisionTime": "2017-03-09T09:10:09Z"
+			"revision": "08824b5ad7408bc38f2b9287c94be2f059c9966a",
+			"revisionTime": "2017-03-11T23:40:19Z"
 		},
 		{
 			"checksumSHA1": "p4yoFWgDiTfpu1JYgh26t6+VDTk=",

部分文件因为文件数量过多而无法显示