diff options
Diffstat (limited to 'models')
-rw-r--r-- | models/action.go | 225 | ||||
-rw-r--r-- | models/user.go | 20 | ||||
-rw-r--r-- | models/webhook.go | 164 | ||||
-rw-r--r-- | models/webhook_slack.go | 100 |
4 files changed, 282 insertions, 227 deletions
diff --git a/models/action.go b/models/action.go index cffcbaac96..57ab926880 100644 --- a/models/action.go +++ b/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 diff --git a/models/user.go b/models/user.go index c548d6d2e3..c7e42dce0b 100644 --- a/models/user.go +++ b/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{ diff --git a/models/webhook.go b/models/webhook.go index debe46e5ec..b2fecb8d3c 100644 --- a/models/webhook.go +++ b/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 diff --git a/models/webhook_slack.go b/models/webhook_slack.go index 0b1b579ff0..9ae125873f 100644 --- a/models/webhook_slack.go +++ b/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, "&", "&", -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 } |