| @@ -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. | |||
| @@ -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 | |||
| @@ -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{ | |||
| @@ -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 | |||
| @@ -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, "&", "&", -1) | |||
| first = strings.Replace(first, "<", "<", -1) | |||
| first = strings.Replace(first, ">", ">", -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, "&", "&", -1) | |||
| first = strings.Replace(first, "<", "<", -1) | |||
| first = strings.Replace(first, ">", ">", -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 | |||
| } | |||
| @@ -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 { | |||
| @@ -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(); | |||
| }); | |||
| @@ -76,4 +76,12 @@ | |||
| margin-left: 25px; | |||
| } | |||
| } | |||
| } | |||
| .new.webhook { | |||
| .events.fields { | |||
| .column { | |||
| padding-left: 40px; | |||
| } | |||
| } | |||
| } | |||
| @@ -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 { | |||
| @@ -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) | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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}}> | |||