Browse Source

new create webhook event

Unknwon 9 years ago
parent
commit
f509c59ac1

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

@@ -531,6 +531,12 @@ settings.content_type = Content Type
 settings.secret = Secret
 settings.event_desc = Upon which events should this webhook be triggered?
 settings.event_push_only = Just the <code>push</code> event.
+settings.event_send_everything = I need <strong>everything</strong>.
+settings.event_choose = Let me choose what I need.
+settings.event_create = Create
+settings.event_create_desc = Branch, or tag created
+settings.event_push = Push
+settings.event_push_desc = Git push to 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.

+ 106 - 119
models/action.go

@@ -16,6 +16,8 @@ import (
 
 	"github.com/go-xorm/xorm"
 
+	api "github.com/gogits/go-gogs-client"
+
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/git"
 	"github.com/gogits/gogs/modules/log"
@@ -290,20 +292,50 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
 }
 
 // CommitRepoAction adds new action for committing repository.
-func CommitRepoAction(userID, repoUserID int64, userName, actEmail string,
-	repoID int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits, oldCommitID string, newCommitID string) error {
+func CommitRepoAction(
+	userID, repoUserID int64,
+	userName, actEmail string,
+	repoID int64,
+	repoUserName, repoName string,
+	refFullName string,
+	commit *base.PushCommits,
+	oldCommitID string, newCommitID string) error {
+
+	u, err := GetUserByID(userID)
+	if err != nil {
+		return fmt.Errorf("GetUserByID: %v", err)
+	}
+
+	repo, err := GetRepositoryByName(repoUserID, repoName)
+	if err != nil {
+		return fmt.Errorf("GetRepositoryByName: %v", err)
+	} else if err = repo.GetOwner(); err != nil {
+		return fmt.Errorf("GetOwner: %v", err)
+	}
 
+	isNewBranch := false
 	opType := COMMIT_REPO
 	// Check it's tag push or branch.
 	if strings.HasPrefix(refFullName, "refs/tags/") {
 		opType = PUSH_TAG
 		commit = &base.PushCommits{}
-	}
+	} else {
+		// if not the first commit, set the compareUrl
+		if !strings.HasPrefix(oldCommitID, "0000000") {
+			commit.CompareUrl = fmt.Sprintf("%s/%s/compare/%s...%s", repoUserName, repoName, oldCommitID, newCommitID)
+		} else {
+			isNewBranch = true
+		}
 
-	repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
-	// if not the first commit, set the compareUrl
-	if !strings.HasPrefix(oldCommitID, "0000000") {
-		commit.CompareUrl = fmt.Sprintf("%s/%s/compare/%s...%s", repoUserName, repoName, oldCommitID, newCommitID)
+		// Change repository bare status and update last updated time.
+		repo.IsBare = false
+		if err = UpdateRepository(repo, false); err != nil {
+			return fmt.Errorf("UpdateRepository: %v", err)
+		}
+
+		if err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits); err != nil {
+			log.Debug("updateIssuesCommit: %v", err)
+		}
 	}
 
 	bs, err := json.Marshal(commit)
@@ -313,26 +345,6 @@ func CommitRepoAction(userID, repoUserID int64, userName, actEmail string,
 
 	refName := git.RefEndName(refFullName)
 
-	// Change repository bare status and update last updated time.
-	repo, err := GetRepositoryByName(repoUserID, repoName)
-	if err != nil {
-		return fmt.Errorf("GetRepositoryByName: %v", err)
-	}
-	repo.IsBare = false
-	if err = UpdateRepository(repo, false); err != nil {
-		return fmt.Errorf("UpdateRepository: %v", err)
-	}
-
-	u, err := GetUserByID(userID)
-	if err != nil {
-		return fmt.Errorf("GetUserByID: %v", err)
-	}
-
-	err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits)
-	if err != nil {
-		log.Debug("updateIssuesCommit: ", err)
-	}
-
 	if err = NotifyWatchers(&Action{
 		ActUserID:    u.Id,
 		ActUserName:  userName,
@@ -345,32 +357,24 @@ func CommitRepoAction(userID, repoUserID int64, userName, actEmail string,
 		RefName:      refName,
 		IsPrivate:    repo.IsPrivate,
 	}); err != nil {
-		return errors.New("NotifyWatchers: " + err.Error())
-
-	}
-
-	// New push event hook.
-	if err := repo.GetOwner(); err != nil {
-		return errors.New("GetOwner: " + err.Error())
-	}
+		return fmt.Errorf("NotifyWatchers: %v", err)
 
-	ws, err := GetActiveWebhooksByRepoId(repo.ID)
-	if err != nil {
-		return errors.New("GetActiveWebhooksByRepoId: " + err.Error())
 	}
 
-	// check if repo belongs to org and append additional webhooks
-	if repo.Owner.IsOrganization() {
-		// get hooks for org
-		orgws, err := GetActiveWebhooksByOrgId(repo.OwnerID)
-		if err != nil {
-			return errors.New("GetActiveWebhooksByOrgId: " + err.Error())
-		}
-		ws = append(ws, orgws...)
-	}
-
-	if len(ws) == 0 {
-		return nil
+	repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
+	payloadRepo := &api.PayloadRepo{
+		ID:          repo.ID,
+		Name:        repo.LowerName,
+		URL:         repoLink,
+		Description: repo.Description,
+		Website:     repo.Website,
+		Watchers:    repo.NumWatches,
+		Owner: &api.PayloadAuthor{
+			Name:     repo.Owner.DisplayName(),
+			Email:    repo.Owner.Email,
+			UserName: repo.Owner.Name,
+		},
+		Private: repo.IsPrivate,
 	}
 
 	pusher_email, pusher_name := "", ""
@@ -379,83 +383,66 @@ func CommitRepoAction(userID, repoUserID int64, userName, actEmail string,
 		pusher_email = pusher.Email
 		pusher_name = pusher.DisplayName()
 	}
+	payloadSender := &api.PayloadUser{
+		UserName:  pusher.Name,
+		ID:        pusher.Id,
+		AvatarUrl: setting.AppUrl + pusher.RelAvatarLink(),
+	}
 
-	commits := make([]*PayloadCommit, len(commit.Commits))
-	for i, cmt := range commit.Commits {
-		author_username := ""
-		author, err := GetUserByEmail(cmt.AuthorEmail)
-		if err == nil {
-			author_username = author.Name
+	switch opType {
+	case COMMIT_REPO: // Push
+		commits := make([]*api.PayloadCommit, len(commit.Commits))
+		for i, cmt := range commit.Commits {
+			author_username := ""
+			author, err := GetUserByEmail(cmt.AuthorEmail)
+			if err == nil {
+				author_username = author.Name
+			}
+			commits[i] = &api.PayloadCommit{
+				ID:      cmt.Sha1,
+				Message: cmt.Message,
+				URL:     fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
+				Author: &api.PayloadAuthor{
+					Name:     cmt.AuthorName,
+					Email:    cmt.AuthorEmail,
+					UserName: author_username,
+				},
+			}
 		}
-		commits[i] = &PayloadCommit{
-			Id:      cmt.Sha1,
-			Message: cmt.Message,
-			Url:     fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
-			Author: &PayloadAuthor{
-				Name:     cmt.AuthorName,
-				Email:    cmt.AuthorEmail,
-				UserName: author_username,
+		p := &api.PushPayload{
+			Ref:        refFullName,
+			Before:     oldCommitID,
+			After:      newCommitID,
+			CompareUrl: setting.AppUrl + commit.CompareUrl,
+			Commits:    commits,
+			Repo:       payloadRepo,
+			Pusher: &api.PayloadAuthor{
+				Name:     pusher_name,
+				Email:    pusher_email,
+				UserName: userName,
 			},
+			Sender: payloadSender,
 		}
-	}
-	p := &Payload{
-		Ref:     refFullName,
-		Commits: commits,
-		Repo: &PayloadRepo{
-			Id:          repo.ID,
-			Name:        repo.LowerName,
-			Url:         repoLink,
-			Description: repo.Description,
-			Website:     repo.Website,
-			Watchers:    repo.NumWatches,
-			Owner: &PayloadAuthor{
-				Name:     repo.Owner.DisplayName(),
-				Email:    repo.Owner.Email,
-				UserName: repo.Owner.Name,
-			},
-			Private: repo.IsPrivate,
-		},
-		Pusher: &PayloadAuthor{
-			Name:     pusher_name,
-			Email:    pusher_email,
-			UserName: userName,
-		},
-		Before:     oldCommitID,
-		After:      newCommitID,
-		CompareUrl: setting.AppUrl + commit.CompareUrl,
-	}
-
-	for _, w := range ws {
-		w.GetEvent()
-		if !w.HasPushEvent() {
-			continue
+		if err = PrepareWebhooks(repo, HOOK_EVENT_PUSH, p); err != nil {
+			return fmt.Errorf("PrepareWebhooks: %v", err)
 		}
 
-		var payload BasePayload
-		switch w.HookTaskType {
-		case SLACK:
-			s, err := GetSlackPayload(p, w.Meta)
-			if err != nil {
-				return errors.New("action.GetSlackPayload: " + err.Error())
-			}
-			payload = s
-		default:
-			payload = p
-			p.Secret = w.Secret
+		if isNewBranch {
+			return PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
+				Ref:     refName,
+				RefType: "branch",
+				Repo:    payloadRepo,
+				Sender:  payloadSender,
+			})
 		}
 
-		if err = CreateHookTask(&HookTask{
-			RepoID:      repo.ID,
-			HookID:      w.ID,
-			Type:        w.HookTaskType,
-			URL:         w.URL,
-			BasePayload: payload,
-			ContentType: w.ContentType,
-			EventType:   HOOK_EVENT_PUSH,
-			IsSSL:       w.IsSSL,
-		}); err != nil {
-			return fmt.Errorf("CreateHookTask: %v", err)
-		}
+	case PUSH_TAG: // Create
+		return PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
+			Ref:     refName,
+			RefType: "tag",
+			Repo:    payloadRepo,
+			Sender:  payloadSender,
+		})
 	}
 
 	return nil

+ 14 - 6
models/user.go

@@ -122,9 +122,8 @@ func (u *User) HomeLink() string {
 	return setting.AppSubUrl + "/" + u.Name
 }
 
-// AvatarLink returns user gravatar link.
-func (u *User) AvatarLink() string {
-	defaultImgUrl := setting.AppSubUrl + "/img/avatar_default.jpg"
+func (u *User) RelAvatarLink() string {
+	defaultImgUrl := "/img/avatar_default.jpg"
 	if u.Id == -1 {
 		return defaultImgUrl
 	}
@@ -135,7 +134,7 @@ func (u *User) AvatarLink() string {
 		if !com.IsExist(imgPath) {
 			return defaultImgUrl
 		}
-		return setting.AppSubUrl + "/avatars/" + com.ToStr(u.Id)
+		return "/avatars/" + com.ToStr(u.Id)
 	case setting.DisableGravatar, setting.OfflineMode:
 		if !com.IsExist(imgPath) {
 			img, err := avatar.RandomImage([]byte(u.Email))
@@ -161,13 +160,22 @@ func (u *User) AvatarLink() string {
 			log.Info("New random avatar created: %d", u.Id)
 		}
 
-		return setting.AppSubUrl + "/avatars/" + com.ToStr(u.Id)
+		return "/avatars/" + com.ToStr(u.Id)
 	case setting.Service.EnableCacheAvatar:
-		return setting.AppSubUrl + "/avatar/" + u.Avatar
+		return "/avatar/" + u.Avatar
 	}
 	return setting.GravatarSource + u.Avatar
 }
 
+// AvatarLink returns user gravatar link.
+func (u *User) AvatarLink() string {
+	link := u.RelAvatarLink()
+	if link[0] == '/' {
+		return setting.AppSubUrl + link
+	}
+	return link
+}
+
 // NewGitSig generates and returns the signature of given user.
 func (u *User) NewGitSig() *git.Signature {
 	return &git.Signature{

+ 98 - 66
models/webhook.go

@@ -15,6 +15,8 @@ import (
 
 	"github.com/go-xorm/xorm"
 
+	api "github.com/gogits/go-gogs-client"
+
 	"github.com/gogits/gogs/modules/httplib"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/setting"
@@ -54,9 +56,18 @@ func IsValidHookContentType(name string) bool {
 	return ok
 }
 
+type HookEvents struct {
+	Create bool `json:"create"`
+	Push   bool `json:"push"`
+}
+
 // HookEvent represents events that will delivery hook.
 type HookEvent struct {
-	PushOnly bool `json:"push_only"`
+	PushOnly       bool `json:"push_only"`
+	SendEverything bool `json:"send_everything"`
+	ChooseEvents   bool `json:"choose_events"`
+
+	HookEvents `json:"events"`
 }
 
 type HookStatus int
@@ -94,8 +105,8 @@ func (w *Webhook) GetEvent() {
 	}
 }
 
-func (w *Webhook) GetSlackHook() *Slack {
-	s := &Slack{}
+func (w *Webhook) GetSlackHook() *SlackMeta {
+	s := &SlackMeta{}
 	if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
 		log.Error(4, "webhook.GetSlackHook(%d): %v", w.ID, err)
 	}
@@ -114,12 +125,16 @@ func (w *Webhook) UpdateEvent() error {
 	return err
 }
 
+// HasCreateEvent returns true if hook enabled create event.
+func (w *Webhook) HasCreateEvent() bool {
+	return w.SendEverything ||
+		(w.ChooseEvents && w.HookEvents.Create)
+}
+
 // HasPushEvent returns true if hook enabled push event.
 func (w *Webhook) HasPushEvent() bool {
-	if w.PushOnly {
-		return true
-	}
-	return false
+	return w.PushOnly || w.SendEverything ||
+		(w.ChooseEvents && w.HookEvents.Push)
 }
 
 // CreateWebhook creates a new web hook.
@@ -140,9 +155,9 @@ func GetWebhookByID(id int64) (*Webhook, error) {
 	return w, nil
 }
 
-// GetActiveWebhooksByRepoId returns all active webhooks of repository.
-func GetActiveWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {
-	err = x.Where("repo_id=?", repoId).And("is_active=?", true).Find(&ws)
+// GetActiveWebhooksByRepoID returns all active webhooks of repository.
+func GetActiveWebhooksByRepoID(repoID int64) (ws []*Webhook, err error) {
+	err = x.Where("repo_id=?", repoID).And("is_active=?", true).Find(&ws)
 	return ws, err
 }
 
@@ -181,9 +196,9 @@ func GetWebhooksByOrgId(orgID int64) (ws []*Webhook, err error) {
 	return ws, err
 }
 
-// GetActiveWebhooksByOrgId returns all active webhooks for an organization.
-func GetActiveWebhooksByOrgId(orgId int64) (ws []*Webhook, err error) {
-	err = x.Where("org_id=?", orgId).And("is_active=?", true).Find(&ws)
+// GetActiveWebhooksByOrgID returns all active webhooks for an organization.
+func GetActiveWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
+	err = x.Where("org_id=?", orgID).And("is_active=?", true).Find(&ws)
 	return ws, err
 }
 
@@ -230,58 +245,10 @@ func IsValidHookTaskType(name string) bool {
 type HookEventType string
 
 const (
-	HOOK_EVENT_PUSH HookEventType = "push"
+	HOOK_EVENT_CREATE HookEventType = "create"
+	HOOK_EVENT_PUSH   HookEventType = "push"
 )
 
-// FIXME: just use go-gogs-client structs maybe?
-type PayloadAuthor struct {
-	Name     string `json:"name"`
-	Email    string `json:"email"`
-	UserName string `json:"username"`
-}
-
-type PayloadCommit struct {
-	Id      string         `json:"id"`
-	Message string         `json:"message"`
-	Url     string         `json:"url"`
-	Author  *PayloadAuthor `json:"author"`
-}
-
-type PayloadRepo struct {
-	Id          int64          `json:"id"`
-	Name        string         `json:"name"`
-	Url         string         `json:"url"`
-	Description string         `json:"description"`
-	Website     string         `json:"website"`
-	Watchers    int            `json:"watchers"`
-	Owner       *PayloadAuthor `json:"owner"`
-	Private     bool           `json:"private"`
-}
-
-type BasePayload interface {
-	GetJSONPayload() ([]byte, error)
-}
-
-// Payload represents a payload information of hook.
-type Payload struct {
-	Secret     string           `json:"secret"`
-	Ref        string           `json:"ref"`
-	Commits    []*PayloadCommit `json:"commits"`
-	Repo       *PayloadRepo     `json:"repository"`
-	Pusher     *PayloadAuthor   `json:"pusher"`
-	Before     string           `json:"before"`
-	After      string           `json:"after"`
-	CompareUrl string           `json:"compare_url"`
-}
-
-func (p Payload) GetJSONPayload() ([]byte, error) {
-	data, err := json.MarshalIndent(p, "", "  ")
-	if err != nil {
-		return []byte{}, err
-	}
-	return data, nil
-}
-
 // HookRequest represents hook task request information.
 type HookRequest struct {
 	Headers map[string]string `json:"headers"`
@@ -302,7 +269,7 @@ type HookTask struct {
 	UUID            string
 	Type            HookTaskType
 	URL             string
-	BasePayload     `xorm:"-"`
+	api.Payloader   `xorm:"-"`
 	PayloadContent  string `xorm:"TEXT"`
 	ContentType     HookContentType
 	EventType       HookEventType
@@ -367,13 +334,13 @@ func (t *HookTask) MarshalJSON(v interface{}) string {
 // HookTasks returns a list of hook tasks by given conditions.
 func HookTasks(hookID int64, page int) ([]*HookTask, error) {
 	tasks := make([]*HookTask, 0, setting.Webhook.PagingNum)
-	return tasks, x.Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).Desc("id").Find(&tasks)
+	return tasks, x.Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).Where("hook_id=?", hookID).Desc("id").Find(&tasks)
 }
 
 // CreateHookTask creates a new hook task,
 // it handles conversion from Payload to PayloadContent.
 func CreateHookTask(t *HookTask) error {
-	data, err := t.BasePayload.GetJSONPayload()
+	data, err := t.Payloader.JSONPayload()
 	if err != nil {
 		return err
 	}
@@ -389,6 +356,71 @@ func UpdateHookTask(t *HookTask) error {
 	return err
 }
 
+// PrepareWebhooks adds new webhooks to task queue for given payload.
+func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error {
+	if err := repo.GetOwner(); err != nil {
+		return fmt.Errorf("GetOwner: %v", err)
+	}
+
+	ws, err := GetActiveWebhooksByRepoID(repo.ID)
+	if err != nil {
+		return fmt.Errorf("GetActiveWebhooksByRepoID: %v", err)
+	}
+
+	// check if repo belongs to org and append additional webhooks
+	if repo.Owner.IsOrganization() {
+		// get hooks for org
+		orgws, err := GetActiveWebhooksByOrgID(repo.OwnerID)
+		if err != nil {
+			return fmt.Errorf("GetActiveWebhooksByOrgID: %v", err)
+		}
+		ws = append(ws, orgws...)
+	}
+
+	if len(ws) == 0 {
+		return nil
+	}
+
+	for _, w := range ws {
+		w.GetEvent()
+
+		switch event {
+		case HOOK_EVENT_CREATE:
+			if !w.HasCreateEvent() {
+				continue
+			}
+		case HOOK_EVENT_PUSH:
+			if !w.HasPushEvent() {
+				continue
+			}
+		}
+
+		switch w.HookTaskType {
+		case SLACK:
+			p, err = GetSlackPayload(p, event, w.Meta)
+			if err != nil {
+				return fmt.Errorf("GetSlackPayload: %v", err)
+			}
+		default:
+			p.SetSecret(w.Secret)
+		}
+
+		if err = CreateHookTask(&HookTask{
+			RepoID:      repo.ID,
+			HookID:      w.ID,
+			Type:        w.HookTaskType,
+			URL:         w.URL,
+			Payloader:   p,
+			ContentType: w.ContentType,
+			EventType:   HOOK_EVENT_PUSH,
+			IsSSL:       w.IsSSL,
+		}); err != nil {
+			return fmt.Errorf("CreateHookTask: %v", err)
+		}
+	}
+	return nil
+}
+
 type hookQueue struct {
 	// Make sure one repository only occur once in the queue.
 	lock    sync.Mutex

+ 64 - 36
models/webhook_slack.go

@@ -9,13 +9,18 @@ import (
 	"errors"
 	"fmt"
 	"strings"
+
+	api "github.com/gogits/go-gogs-client"
+
+	"github.com/gogits/gogs/modules/git"
+	"github.com/gogits/gogs/modules/setting"
 )
 
 const (
 	SLACK_COLOR string = "#dd4b39"
 )
 
-type Slack struct {
+type SlackMeta struct {
 	Channel string `json:"channel"`
 }
 
@@ -34,7 +39,9 @@ type SlackAttachment struct {
 	Text  string `json:"text"`
 }
 
-func (p SlackPayload) GetJSONPayload() ([]byte, error) {
+func (p *SlackPayload) SetSecret(_ string) {}
+
+func (p *SlackPayload) JSONPayload() ([]byte, error) {
 	data, err := json.Marshal(p)
 	if err != nil {
 		return []byte{}, err
@@ -42,27 +49,47 @@ func (p SlackPayload) GetJSONPayload() ([]byte, error) {
 	return data, nil
 }
 
-func GetSlackPayload(p *Payload, meta string) (*SlackPayload, error) {
-	slack := &Slack{}
-	slackPayload := &SlackPayload{}
-	if err := json.Unmarshal([]byte(meta), &slack); err != nil {
-		return slackPayload, errors.New("GetSlackPayload meta json:" + err.Error())
-	}
+// see: https://api.slack.com/docs/formatting
+func SlackTextFormatter(s string) string {
+	// take only first line of commit
+	first := strings.Split(s, "\n")[0]
+	// replace & < >
+	first = strings.Replace(first, "&", "&amp;", -1)
+	first = strings.Replace(first, "<", "&lt;", -1)
+	first = strings.Replace(first, ">", "&gt;", -1)
+	return first
+}
 
-	// TODO: handle different payload types: push, new branch, delete branch etc.
-	// when they are added to gogs. Only handles push now
-	return getSlackPushPayload(p, slack)
+func SlackLinkFormatter(url string, text string) string {
+	return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text))
 }
 
-func getSlackPushPayload(p *Payload, slack *Slack) (*SlackPayload, error) {
+func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayload, error) {
+	// created tag/branch
+	refName := git.RefEndName(p.Ref)
+
+	repoLink := SlackLinkFormatter(p.Repo.URL, p.Repo.Name)
+	refLink := SlackLinkFormatter(p.Repo.URL+"/src/"+refName, refName)
+	text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName)
+
+	return &SlackPayload{
+		Channel:  slack.Channel,
+		Text:     text,
+		Username: setting.AppName,
+		IconUrl:  setting.AppUrl + "/img/favicon.png",
+	}, nil
+}
+
+func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) {
 	// n new commits
-	refSplit := strings.Split(p.Ref, "/")
-	branchName := refSplit[len(refSplit)-1]
-	var commitString string
+	var (
+		branchName   = git.RefEndName(p.Ref)
+		commitString string
+	)
 
 	if len(p.Commits) == 1 {
 		commitString = "1 new commit"
-		if p.CompareUrl != "" {
+		if len(p.CompareUrl) > 0 {
 			commitString = SlackLinkFormatter(p.CompareUrl, commitString)
 		}
 	} else {
@@ -72,14 +99,14 @@ func getSlackPushPayload(p *Payload, slack *Slack) (*SlackPayload, error) {
 		}
 	}
 
-	repoLink := SlackLinkFormatter(p.Repo.Url, p.Repo.Name)
-	branchLink := SlackLinkFormatter(p.Repo.Url+"/src/"+branchName, branchName)
+	repoLink := SlackLinkFormatter(p.Repo.URL, p.Repo.Name)
+	branchLink := SlackLinkFormatter(p.Repo.URL+"/src/"+branchName, branchName)
 	text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.Name)
-	var attachmentText string
 
+	var attachmentText string
 	// for each commit, generate attachment text
 	for i, commit := range p.Commits {
-		attachmentText += fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.Url, commit.Id[:7]), SlackTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name))
+		attachmentText += fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.URL, commit.ID[:7]), SlackTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name))
 		// add linebreak to each commit but the last
 		if i < len(p.Commits)-1 {
 			attachmentText += "\n"
@@ -91,25 +118,26 @@ func getSlackPushPayload(p *Payload, slack *Slack) (*SlackPayload, error) {
 	return &SlackPayload{
 		Channel:     slack.Channel,
 		Text:        text,
-		Username:    "gogs",
-		IconUrl:     "https://raw.githubusercontent.com/gogits/gogs/master/public/img/favicon.png",
-		UnfurlLinks: 0,
-		LinkNames:   0,
+		Username:    setting.AppName,
+		IconUrl:     setting.AppUrl + "/img/favicon.png",
 		Attachments: slackAttachments,
 	}, nil
 }
 
-// see: https://api.slack.com/docs/formatting
-func SlackTextFormatter(s string) string {
-	// take only first line of commit
-	first := strings.Split(s, "\n")[0]
-	// replace & < >
-	first = strings.Replace(first, "&", "&amp;", -1)
-	first = strings.Replace(first, "<", "&lt;", -1)
-	first = strings.Replace(first, ">", "&gt;", -1)
-	return first
-}
+func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackPayload, error) {
+	s := new(SlackPayload)
 
-func SlackLinkFormatter(url string, text string) string {
-	return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text))
+	slack := &SlackMeta{}
+	if err := json.Unmarshal([]byte(meta), &slack); err != nil {
+		return s, errors.New("GetSlackPayload meta json:" + err.Error())
+	}
+
+	switch event {
+	case HOOK_EVENT_CREATE:
+		return getSlackCreatePayload(p.(*api.CreatePayload), slack)
+	case HOOK_EVENT_PUSH:
+		return getSlackPushPayload(p.(*api.PushPayload), slack)
+	}
+
+	return s, nil
 }

+ 16 - 2
modules/auth/repo_form.go

@@ -67,8 +67,22 @@ func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
 //        \/       \/    \/     \/     \/            \/
 
 type WebhookForm struct {
-	PushOnly bool
-	Active   bool
+	Events string
+	Create bool
+	Push   bool
+	Active bool
+}
+
+func (f WebhookForm) PushOnly() bool {
+	return f.Events == "push_only"
+}
+
+func (f WebhookForm) SendEverything() bool {
+	return f.Events == "send_everything"
+}
+
+func (f WebhookForm) ChooseEvents() bool {
+	return f.Events == "choose_events"
 }
 
 type NewWebhookForm struct {

File diff suppressed because it is too large
+ 0 - 0
modules/bindata/bindata.go


File diff suppressed because it is too large
+ 0 - 0
public/css/gogs.min.css


+ 18 - 0
public/js/gogs.js

@@ -367,6 +367,23 @@ function initRepository() {
     }
 };
 
+function initWebhook() {
+    if ($('.new.webhook').length == 0) {
+        return;
+    }
+
+    $('.events.checkbox input').change(function () {
+        if ($(this).is(':checked')) {
+            $('.events.fields').show();
+        }
+    });
+    $('.non-events.checkbox input').change(function () {
+        if ($(this).is(':checked')) {
+            $('.events.fields').hide();
+        }
+    });
+}
+
 $(document).ready(function () {
     csrf = $('meta[name=_csrf]').attr("content");
 
@@ -473,4 +490,5 @@ $(document).ready(function () {
     initCommentForm();
     initInstall();
     initRepository();
+    initWebhook();
 });

+ 8 - 0
public/less/_form.less

@@ -76,4 +76,12 @@
 			margin-left: 25px;
 		}
 	}
+}
+
+.new.webhook {
+	.events.fields {
+		.column {
+			padding-left: 40px;
+		}
+	}
 }

+ 2 - 2
routers/api/v1/repo_hooks.go

@@ -83,7 +83,7 @@ func CreateRepoHook(ctx *middleware.Context, form api.CreateHookOption) {
 			ctx.JSON(422, &base.ApiJsonErr{"missing config option: channel", base.DOC_URL})
 			return
 		}
-		meta, err := json.Marshal(&models.Slack{
+		meta, err := json.Marshal(&models.SlackMeta{
 			Channel: channel,
 		})
 		if err != nil {
@@ -141,7 +141,7 @@ func EditRepoHook(ctx *middleware.Context, form api.EditHookOption) {
 
 		if w.HookTaskType == models.SLACK {
 			if channel, ok := form.Config["channel"]; ok {
-				meta, err := json.Marshal(&models.Slack{
+				meta, err := json.Marshal(&models.SlackMeta{
 					Channel: channel,
 				})
 				if err != nil {

+ 25 - 21
routers/repo/setting.go

@@ -320,6 +320,18 @@ func WebhooksNew(ctx *middleware.Context) {
 	ctx.HTML(200, orCtx.NewTemplate)
 }
 
+func ParseHookEvent(form auth.WebhookForm) *models.HookEvent {
+	return &models.HookEvent{
+		PushOnly:       form.PushOnly(),
+		SendEverything: form.SendEverything(),
+		ChooseEvents:   form.ChooseEvents(),
+		HookEvents: models.HookEvents{
+			Create: form.Create,
+			Push:   form.Push,
+		},
+	}
+}
+
 func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) {
 	ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
 	ctx.Data["PageIsSettingsHooks"] = true
@@ -345,13 +357,11 @@ func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) {
 	}
 
 	w := &models.Webhook{
-		RepoID:      orCtx.RepoID,
-		URL:         form.PayloadURL,
-		ContentType: contentType,
-		Secret:      form.Secret,
-		HookEvent: &models.HookEvent{
-			PushOnly: form.PushOnly,
-		},
+		RepoID:       orCtx.RepoID,
+		URL:          form.PayloadURL,
+		ContentType:  contentType,
+		Secret:       form.Secret,
+		HookEvent:    ParseHookEvent(form.WebhookForm),
 		IsActive:     form.Active,
 		HookTaskType: models.GOGS,
 		OrgID:        orCtx.OrgID,
@@ -385,7 +395,7 @@ func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
 		return
 	}
 
-	meta, err := json.Marshal(&models.Slack{
+	meta, err := json.Marshal(&models.SlackMeta{
 		Channel: form.Channel,
 	})
 	if err != nil {
@@ -394,12 +404,10 @@ func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
 	}
 
 	w := &models.Webhook{
-		RepoID:      orCtx.RepoID,
-		URL:         form.PayloadURL,
-		ContentType: models.JSON,
-		HookEvent: &models.HookEvent{
-			PushOnly: form.PushOnly,
-		},
+		RepoID:       orCtx.RepoID,
+		URL:          form.PayloadURL,
+		ContentType:  models.JSON,
+		HookEvent:    ParseHookEvent(form.WebhookForm),
 		IsActive:     form.Active,
 		HookTaskType: models.SLACK,
 		Meta:         string(meta),
@@ -491,9 +499,7 @@ func WebHooksEditPost(ctx *middleware.Context, form auth.NewWebhookForm) {
 	w.URL = form.PayloadURL
 	w.ContentType = contentType
 	w.Secret = form.Secret
-	w.HookEvent = &models.HookEvent{
-		PushOnly: form.PushOnly,
-	}
+	w.HookEvent = ParseHookEvent(form.WebhookForm)
 	w.IsActive = form.Active
 	if err := w.UpdateEvent(); err != nil {
 		ctx.Handle(500, "UpdateEvent", err)
@@ -523,7 +529,7 @@ func SlackHooksEditPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
 		return
 	}
 
-	meta, err := json.Marshal(&models.Slack{
+	meta, err := json.Marshal(&models.SlackMeta{
 		Channel: form.Channel,
 	})
 	if err != nil {
@@ -533,9 +539,7 @@ func SlackHooksEditPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
 
 	w.URL = form.PayloadURL
 	w.Meta = string(meta)
-	w.HookEvent = &models.HookEvent{
-		PushOnly: form.PushOnly,
-	}
+	w.HookEvent = ParseHookEvent(form.WebhookForm)
 	w.IsActive = form.Active
 	if err := w.UpdateEvent(); err != nil {
 		ctx.Handle(500, "UpdateEvent", err)

+ 18 - 0
templates/base/footer.tmpl

@@ -26,4 +26,22 @@
 		</div>
 	</footer>
 </body>
+
+	<!-- Third-party libraries -->
+	{{if .RequireHighlightJS}}
+	<link rel="stylesheet" href="{{AppSubUrl}}/css/highlight-8.7/default.css">
+	<script src="{{AppSubUrl}}/js/libs/highlight-8.7.pack.js"></script>
+	{{end}}
+	{{if .RequireMinicolors}}
+	<link rel="stylesheet" href="{{AppSubUrl}}/css/jquery.minicolors-2.1.12.css">
+	<script src="{{AppSubUrl}}/js/libs/jquery.minicolors-2.1.12.min.js"></script>
+	{{end}}
+	{{if .RequireDatetimepicker}}
+	<link rel="stylesheet" href="{{AppSubUrl}}/css/jquery.datetimepicker-2.4.5.css">
+	<script src="{{AppSubUrl}}/js/libs/jquery.datetimepicker-2.4.5.js"></script>
+	{{end}}
+	{{if .RequireDropzone}}
+	<link rel="stylesheet" href="{{AppSubUrl}}/css/dropzone-4.0.1.css">
+	<script src="{{AppSubUrl}}/js/libs/dropzone-4.0.1.js"></script>
+	{{end}}
 </html>

+ 0 - 18
templates/base/head.tmpl

@@ -25,24 +25,6 @@
 	<script src="{{AppSubUrl}}/js/semantic-2.0.8.min.js"></script>
 	<script src="{{AppSubUrl}}/js/gogs.js?v={{AppVer}}"></script>
 
-	<!-- Third-party libraries -->
-	{{if .RequireHighlightJS}}
-	<link rel="stylesheet" href="{{AppSubUrl}}/css/highlight-8.7/default.css">
-	<script src="{{AppSubUrl}}/js/libs/highlight-8.7.pack.js"></script>
-	{{end}}
-	{{if .RequireMinicolors}}
-	<link rel="stylesheet" href="{{AppSubUrl}}/css/jquery.minicolors-2.1.12.css">
-	<script src="{{AppSubUrl}}/js/libs/jquery.minicolors-2.1.12.min.js"></script>
-	{{end}}
-	{{if .RequireDatetimepicker}}
-	<link rel="stylesheet" href="{{AppSubUrl}}/css/jquery.datetimepicker-2.4.5.css">
-	<script src="{{AppSubUrl}}/js/libs/jquery.datetimepicker-2.4.5.js"></script>
-	{{end}}
-	{{if .RequireDropzone}}
-	<link rel="stylesheet" href="{{AppSubUrl}}/css/dropzone-4.0.1.css">
-	<script src="{{AppSubUrl}}/js/libs/dropzone-4.0.1.js"></script>
-	{{end}}
-
 	<title>{{if .Title}}{{.Title}} - {{end}}{{AppName}}</title>
 </head>
 <body>

+ 1 - 1
templates/repo/settings/hook_history.tmpl

@@ -28,7 +28,7 @@
 					  	{{if .IsSucceed}}
 					  	<span class="ui green label">{{.ResponseInfo.Status}}</span>
 					  	{{else}}
-					  	<span class="ui red label">500</span>
+					  	<span class="ui red label">{{.ResponseInfo.Status}}</span>
 					  	{{end}}
 				  	{{else}}
 				  		<span class="ui label">N/A</span>

+ 45 - 7
templates/repo/settings/hook_settings.tmpl

@@ -1,14 +1,52 @@
 <div class="field">
   <h4>{{.i18n.Tr "repo.settings.event_desc"}}</h4>
-	<div class="grouped fields">
-		<div class="field">
-		  <div class="ui radio checkbox checked">
-		    <input class="hidden" name="push_only" type="radio" {{if or .PageIsSettingsHooksNew .Webhook.PushOnly}}checked{{end}}>
-		    <label>{{.i18n.Tr "repo.settings.event_push_only" | Str2html}}</label>
-		  </div>
-		</div>
+	<div class="grouped event type fields">
+    <div class="field">
+      <div class="ui radio non-events checkbox">
+        <input class="hidden" name="events" type="radio" value="push_only" {{if or .PageIsSettingsHooksNew .Webhook.PushOnly}}checked{{end}}>
+        <label>{{.i18n.Tr "repo.settings.event_push_only" | Str2html}}</label>
+      </div>
+    </div>
+    <div class="field">
+      <div class="ui radio non-events checkbox">
+        <input class="hidden" name="events" type="radio" value="send_everything" {{if .Webhook.SendEverything}}checked{{end}}>
+        <label>{{.i18n.Tr "repo.settings.event_send_everything" | Str2html}}</label>
+      </div>
+    </div>
+    <div class="field">
+      <div class="ui radio events checkbox">
+        <input class="hidden" name="events" type="radio" value="choose_events" {{if .Webhook.ChooseEvents}}checked{{end}}>
+        <label>{{.i18n.Tr "repo.settings.event_choose" | Str2html}}</label>
+      </div>
+    </div>
 	</div>
+
+  <div class="events fields ui grid" {{if not .Webhook.ChooseEvents}}style="display:none"{{end}}>
+    <!-- Create -->
+    <div class="seven wide column">
+      <div class="field">
+        <div class="ui checkbox">
+          <input class="hidden" name="create" type="checkbox" tabindex="0" {{if .Webhook.Create}}checked{{end}}>
+          <label>{{.i18n.Tr "repo.settings.event_create"}}</label>
+          <span class="help">{{.i18n.Tr "repo.settings.event_create_desc"}}</span>
+        </div>
+      </div>
+    </div>
+    <!-- Push -->
+    <div class="seven wide column">
+      <div class="field">
+        <div class="ui checkbox">
+          <input class="hidden" name="push" type="checkbox" tabindex="0" {{if .Webhook.Push}}checked{{end}}>
+          <label>{{.i18n.Tr "repo.settings.event_push"}}</label>
+            <span class="help">{{.i18n.Tr "repo.settings.event_push_desc"}}</span>
+        </div>
+      </div>
+    </div>
+  </div>
 </div>
+
+<div class="ui divider"></div>
+
 <div class="inline field">
   <div class="ui checkbox">
     <input class="hidden" name="active" type="checkbox" tabindex="0" {{if or .PageIsSettingsHooksNew .Webhook.IsActive}}checked{{end}}>

Some files were not shown because too many files changed in this diff