aboutsummaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
Diffstat (limited to 'models')
-rw-r--r--models/action.go19
-rw-r--r--models/issue.go362
-rw-r--r--models/issue_comment.go2
-rw-r--r--models/issue_label.go14
-rw-r--r--models/issue_mail.go2
-rw-r--r--models/pull.go282
-rw-r--r--models/repo.go54
-rw-r--r--models/user.go19
-rw-r--r--models/webhook.go28
-rw-r--r--models/webhook_slack.go90
10 files changed, 689 insertions, 183 deletions
diff --git a/models/action.go b/models/action.go
index 5bfa5b75b2..584543d9c1 100644
--- a/models/action.go
+++ b/models/action.go
@@ -169,7 +169,7 @@ func (a *Action) GetIssueTitle() string {
log.Error(4, "GetIssueByIndex: %v", err)
return "500 when get issue"
}
- return issue.Name
+ return issue.Title
}
func (a *Action) GetIssueContent() string {
@@ -513,11 +513,11 @@ func CommitRepoAction(
payloadRepo := repo.ComposePayload()
- pusher_email, pusher_name := "", ""
+ var pusherEmail, pusherName string
pusher, err := GetUserByName(userName)
if err == nil {
- pusher_email = pusher.Email
- pusher_name = pusher.DisplayName()
+ pusherEmail = pusher.Email
+ pusherName = pusher.DisplayName()
}
payloadSender := &api.PayloadUser{
UserName: pusher.Name,
@@ -527,7 +527,7 @@ func CommitRepoAction(
switch opType {
case ACTION_COMMIT_REPO: // Push
- p := &api.PushPayload{
+ if err = PrepareWebhooks(repo, HOOK_EVENT_PUSH, &api.PushPayload{
Ref: refFullName,
Before: oldCommitID,
After: newCommitID,
@@ -535,13 +535,12 @@ func CommitRepoAction(
Commits: commit.ToApiPayloadCommits(repo.FullLink()),
Repo: payloadRepo,
Pusher: &api.PayloadAuthor{
- Name: pusher_name,
- Email: pusher_email,
+ Name: pusherName,
+ Email: pusherEmail,
UserName: userName,
},
Sender: payloadSender,
- }
- if err = PrepareWebhooks(repo, HOOK_EVENT_PUSH, p); err != nil {
+ }); err != nil {
return fmt.Errorf("PrepareWebhooks: %v", err)
}
@@ -603,7 +602,7 @@ func mergePullRequestAction(e Engine, actUser *User, repo *Repository, pull *Iss
ActUserName: actUser.Name,
ActEmail: actUser.Email,
OpType: ACTION_MERGE_PULL_REQUEST,
- Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
+ Content: fmt.Sprintf("%d|%s", pull.Index, pull.Title),
RepoID: repo.ID,
RepoUserName: repo.Owner.Name,
RepoName: repo.Name,
diff --git a/models/issue.go b/models/issue.go
index 3d7e862c3b..9d9d3db0a3 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -16,6 +16,7 @@ import (
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
+ api "github.com/gogits/go-gogs-client"
gouuid "github.com/satori/go.uuid"
"github.com/gogits/gogs/modules/base"
@@ -31,10 +32,10 @@ var (
// Issue represents an issue or pull request of repository.
type Issue struct {
- ID int64 `xorm:"pk autoincr"`
- RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
- Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
- Name string
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
+ Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
+ Title string `xorm:"name"`
Repo *Repository `xorm:"-"`
PosterID int64
Poster *User `xorm:"-"`
@@ -73,19 +74,6 @@ func (i *Issue) BeforeUpdate() {
i.DeadlineUnix = i.Deadline.Unix()
}
-func (issue *Issue) loadAttributes(e Engine) (err error) {
- issue.Repo, err = getRepositoryByID(e, issue.RepoID)
- if err != nil {
- return fmt.Errorf("getRepositoryByID: %v", err)
- }
-
- return nil
-}
-
-func (issue *Issue) LoadAttributes() error {
- return issue.loadAttributes(x)
-}
-
func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
var err error
switch colName {
@@ -110,7 +98,7 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
if err != nil {
if IsErrUserNotExist(err) {
i.PosterID = -1
- i.Poster = NewFakeUser()
+ i.Poster = NewGhostUser()
} else {
log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
}
@@ -146,17 +134,80 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
}
}
-// HashTag returns unique hash tag for issue.
-func (i *Issue) HashTag() string {
- return "issue-" + com.ToStr(i.ID)
+func (issue *Issue) loadAttributes(e Engine) (err error) {
+ if issue.Repo == nil {
+ issue.Repo, err = getRepositoryByID(e, issue.RepoID)
+ if err != nil {
+ return fmt.Errorf("getRepositoryByID [%d]: %v", issue.RepoID, err)
+ }
+ }
+
+ if issue.IsPull && issue.PullRequest == nil {
+ // It is possible pull request is not yet created.
+ issue.PullRequest, err = getPullRequestByIssueID(e, issue.ID)
+ if err != nil && !IsErrPullRequestNotExist(err) {
+ return fmt.Errorf("getPullRequestByIssueID [%d]: %v", issue.ID, err)
+ }
+ }
+
+ return nil
+}
+
+func (issue *Issue) LoadAttributes() error {
+ return issue.loadAttributes(x)
}
// State returns string representation of issue status.
-func (i *Issue) State() string {
+func (i *Issue) State() api.StateType {
if i.IsClosed {
- return "closed"
+ return api.STATE_CLOSED
}
- return "open"
+ return api.STATE_OPEN
+}
+
+// This method assumes some fields assigned with values:
+// Required - Poster, Labels,
+// Optional - Milestone, Assignee, PullRequest
+func (issue *Issue) APIFormat() *api.Issue {
+ apiLabels := make([]*api.Label, len(issue.Labels))
+ for i := range issue.Labels {
+ apiLabels[i] = issue.Labels[i].APIFormat()
+ }
+
+ apiIssue := &api.Issue{
+ ID: issue.ID,
+ Index: issue.Index,
+ State: issue.State(),
+ Title: issue.Title,
+ Body: issue.Content,
+ User: issue.Poster.APIFormat(),
+ Labels: apiLabels,
+ Comments: issue.NumComments,
+ Created: issue.Created,
+ Updated: issue.Updated,
+ }
+
+ if issue.Milestone != nil {
+ apiIssue.Milestone = issue.Milestone.APIFormat()
+ }
+ if issue.Assignee != nil {
+ apiIssue.Assignee = issue.Assignee.APIFormat()
+ }
+ if issue.IsPull {
+ apiIssue.PullRequest = &api.PullRequestMeta{
+ HasMerged: issue.PullRequest.HasMerged,
+ }
+ if issue.PullRequest.HasMerged {
+ apiIssue.PullRequest.Merged = &issue.PullRequest.Merged
+ }
+ }
+
+ return apiIssue
+}
+
+// HashTag returns unique hash tag for issue.
+func (i *Issue) HashTag() string {
+ return "issue-" + com.ToStr(i.ID)
}
func (issue *Issue) FullLink() string {
@@ -183,23 +234,37 @@ func (i *Issue) HasLabel(labelID int64) bool {
return i.hasLabel(x, labelID)
}
+func (issue *Issue) sendLabelUpdatedWebhook(doer *User) {
+ var err error
+ if issue.IsPull {
+ issue.PullRequest.Issue = issue
+ err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{
+ Action: api.HOOK_ISSUE_LABEL_UPDATED,
+ Index: issue.Index,
+ PullRequest: issue.PullRequest.APIFormat(),
+ Repository: issue.Repo.APIFormat(nil),
+ Sender: doer.APIFormat(),
+ })
+ }
+ if err != nil {
+ log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
+ } else {
+ go HookQueue.Add(issue.RepoID)
+ }
+}
+
func (i *Issue) addLabel(e *xorm.Session, label *Label) error {
return newIssueLabel(e, i, label)
}
// AddLabel adds a new label to the issue.
-func (i *Issue) AddLabel(label *Label) (err error) {
- sess := x.NewSession()
- defer sessionRelease(sess)
- if err = sess.Begin(); err != nil {
+func (issue *Issue) AddLabel(doer *User, label *Label) error {
+ if err := NewIssueLabel(issue, label); err != nil {
return err
}
- if err = i.addLabel(sess, label); err != nil {
- return err
- }
-
- return sess.Commit()
+ issue.sendLabelUpdatedWebhook(doer)
+ return nil
}
func (issue *Issue) addLabels(e *xorm.Session, labels []*Label) error {
@@ -207,8 +272,13 @@ func (issue *Issue) addLabels(e *xorm.Session, labels []*Label) error {
}
// AddLabels adds a list of new labels to the issue.
-func (issue *Issue) AddLabels(labels []*Label) error {
- return NewIssueLabels(issue, labels)
+func (issue *Issue) AddLabels(doer *User, labels []*Label) error {
+ if err := NewIssueLabels(issue, labels); err != nil {
+ return err
+ }
+
+ issue.sendLabelUpdatedWebhook(doer)
+ return nil
}
func (issue *Issue) getLabels(e Engine) (err error) {
@@ -228,8 +298,13 @@ func (issue *Issue) removeLabel(e *xorm.Session, label *Label) error {
}
// RemoveLabel removes a label from issue by given ID.
-func (issue *Issue) RemoveLabel(label *Label) (err error) {
- return DeleteIssueLabel(issue, label)
+func (issue *Issue) RemoveLabel(doer *User, label *Label) error {
+ if err := DeleteIssueLabel(issue, label); err != nil {
+ return err
+ }
+
+ issue.sendLabelUpdatedWebhook(doer)
+ return nil
}
func (issue *Issue) clearLabels(e *xorm.Session) (err error) {
@@ -246,7 +321,7 @@ func (issue *Issue) clearLabels(e *xorm.Session) (err error) {
return nil
}
-func (issue *Issue) ClearLabels() (err error) {
+func (issue *Issue) ClearLabels(doer *User) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
@@ -257,7 +332,27 @@ func (issue *Issue) ClearLabels() (err error) {
return err
}
- return sess.Commit()
+ if err = sess.Commit(); err != nil {
+ return fmt.Errorf("Commit: %v", err)
+ }
+
+ if issue.IsPull {
+ issue.PullRequest.Issue = issue
+ err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{
+ Action: api.HOOK_ISSUE_LABEL_CLEARED,
+ Index: issue.Index,
+ PullRequest: issue.PullRequest.APIFormat(),
+ Repository: issue.Repo.APIFormat(nil),
+ Sender: doer.APIFormat(),
+ })
+ }
+ if err != nil {
+ log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
+ } else {
+ go HookQueue.Add(issue.RepoID)
+ }
+
+ return nil
}
// ReplaceLabels removes all current labels and add new labels to the issue.
@@ -294,6 +389,16 @@ func (i *Issue) ReadBy(uid int64) error {
return UpdateIssueUserByRead(uid, i.ID)
}
+func updateIssueCols(e Engine, issue *Issue, cols ...string) error {
+ _, err := e.Id(issue.ID).Cols(cols...).Update(issue)
+ return err
+}
+
+// UpdateIssueCols only updates values of specific columns for given issue.
+func UpdateIssueCols(issue *Issue, cols ...string) error {
+ return updateIssueCols(x, issue, cols...)
+}
+
func (i *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, isClosed bool) (err error) {
// Nothing should be performed if current status is same as target status
if i.IsClosed == isClosed {
@@ -336,32 +441,149 @@ func (i *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, isCl
}
// ChangeStatus changes issue status to open or closed.
-func (i *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (err error) {
+func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
- if err = i.changeStatus(sess, doer, repo, isClosed); err != nil {
+ if err = issue.changeStatus(sess, doer, repo, isClosed); err != nil {
return err
}
- return sess.Commit()
+ if err = sess.Commit(); err != nil {
+ return fmt.Errorf("Commit: %v", err)
+ }
+
+ if issue.IsPull {
+ // Merge pull request calls issue.changeStatus so we need to handle separately.
+ issue.PullRequest.Issue = issue
+ apiPullRequest := &api.PullRequestPayload{
+ Index: issue.Index,
+ PullRequest: issue.PullRequest.APIFormat(),
+ Repository: repo.APIFormat(nil),
+ Sender: doer.APIFormat(),
+ }
+ if isClosed {
+ apiPullRequest.Action = api.HOOK_ISSUE_CLOSED
+ } else {
+ apiPullRequest.Action = api.HOOK_ISSUE_REOPENED
+ }
+ err = PrepareWebhooks(repo, HOOK_EVENT_PULL_REQUEST, apiPullRequest)
+ }
+ if err != nil {
+ log.Error(4, "PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err)
+ } else {
+ go HookQueue.Add(repo.ID)
+ }
+
+ return nil
+}
+
+func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
+ oldTitle := issue.Title
+ issue.Title = title
+ if err = UpdateIssueCols(issue, "name"); err != nil {
+ return fmt.Errorf("UpdateIssueCols: %v", err)
+ }
+
+ if issue.IsPull {
+ issue.PullRequest.Issue = issue
+ err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{
+ Action: api.HOOK_ISSUE_EDITED,
+ Index: issue.Index,
+ Changes: &api.ChangesPayload{
+ Title: &api.ChangesFromPayload{
+ From: oldTitle,
+ },
+ },
+ PullRequest: issue.PullRequest.APIFormat(),
+ Repository: issue.Repo.APIFormat(nil),
+ Sender: doer.APIFormat(),
+ })
+ }
+ if err != nil {
+ log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
+ } else {
+ go HookQueue.Add(issue.RepoID)
+ }
+
+ return nil
}
-func (i *Issue) GetPullRequest() (err error) {
- if i.PullRequest != nil {
+func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
+ oldContent := issue.Content
+ issue.Content = content
+ if err = UpdateIssueCols(issue, "content"); err != nil {
+ return fmt.Errorf("UpdateIssueCols: %v", err)
+ }
+
+ if issue.IsPull {
+ issue.PullRequest.Issue = issue
+ err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{
+ Action: api.HOOK_ISSUE_EDITED,
+ Index: issue.Index,
+ Changes: &api.ChangesPayload{
+ Body: &api.ChangesFromPayload{
+ From: oldContent,
+ },
+ },
+ PullRequest: issue.PullRequest.APIFormat(),
+ Repository: issue.Repo.APIFormat(nil),
+ Sender: doer.APIFormat(),
+ })
+ }
+ if err != nil {
+ log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
+ } else {
+ go HookQueue.Add(issue.RepoID)
+ }
+
+ return nil
+}
+
+func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
+ issue.AssigneeID = assigneeID
+ if err = UpdateIssueUserByAssignee(issue); err != nil {
+ return fmt.Errorf("UpdateIssueUserByAssignee: %v", err)
+ }
+
+ issue.Assignee, err = GetUserByID(issue.AssigneeID)
+ if err != nil && !IsErrUserNotExist(err) {
+ log.Error(4, "GetUserByID [assignee_id: %v]: %v", issue.AssigneeID, err)
return nil
}
- i.PullRequest, err = GetPullRequestByIssueID(i.ID)
- return err
+ // Error not nil here means user does not exist, which is remove assignee.
+ isRemoveAssignee := err != nil
+ if issue.IsPull {
+ issue.PullRequest.Issue = issue
+ apiPullRequest := &api.PullRequestPayload{
+ Index: issue.Index,
+ PullRequest: issue.PullRequest.APIFormat(),
+ Repository: issue.Repo.APIFormat(nil),
+ Sender: doer.APIFormat(),
+ }
+ if isRemoveAssignee {
+ apiPullRequest.Action = api.HOOK_ISSUE_UNASSIGNED
+ } else {
+ apiPullRequest.Action = api.HOOK_ISSUE_ASSIGNED
+ }
+ err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, apiPullRequest)
+ }
+ if err != nil {
+ log.Error(4, "PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, isRemoveAssignee, err)
+ } else {
+ go HookQueue.Add(issue.RepoID)
+ }
+
+ return nil
}
// It's caller's responsibility to create action.
func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string, isPull bool) (err error) {
- issue.Name = strings.TrimSpace(issue.Name)
+ issue.Title = strings.TrimSpace(issue.Title)
issue.Index = repo.NextIssueIndex()
if issue.AssigneeID > 0 {
@@ -462,7 +684,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
ActUserName: issue.Poster.Name,
ActEmail: issue.Poster.Email,
OpType: ACTION_CREATE_ISSUE,
- Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
+ Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title),
RepoID: repo.ID,
RepoUserName: repo.Owner.Name,
RepoName: repo.Name,
@@ -518,10 +740,9 @@ func GetIssueByIndex(repoID, index int64) (*Issue, error) {
return issue, issue.LoadAttributes()
}
-// GetIssueByID returns an issue by given ID.
-func GetIssueByID(id int64) (*Issue, error) {
+func getIssueByID(e Engine, id int64) (*Issue, error) {
issue := new(Issue)
- has, err := x.Id(id).Get(issue)
+ has, err := e.Id(id).Get(issue)
if err != nil {
return nil, err
} else if !has {
@@ -530,6 +751,11 @@ func GetIssueByID(id int64) (*Issue, error) {
return issue, issue.LoadAttributes()
}
+// GetIssueByID returns an issue by given ID.
+func GetIssueByID(id int64) (*Issue, error) {
+ return getIssueByID(x, id)
+}
+
type IssuesOptions struct {
UserID int64
AssigneeID int64
@@ -970,12 +1196,6 @@ func UpdateIssue(issue *Issue) error {
return updateIssue(x, issue)
}
-// updateIssueCols only updates values of specific columns for given issue.
-func updateIssueCols(e Engine, issue *Issue, cols ...string) error {
- _, err := e.Id(issue.ID).Cols(cols...).Update(issue)
- return err
-}
-
func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error {
_, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID)
return err
@@ -987,13 +1207,13 @@ func UpdateIssueUsersByStatus(issueID int64, isClosed bool) error {
}
func updateIssueUserByAssignee(e *xorm.Session, issue *Issue) (err error) {
- if _, err = e.Exec("UPDATE `issue_user` SET is_assigned=? WHERE issue_id=?", false, issue.ID); err != nil {
+ if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?", false, issue.ID); err != nil {
return err
}
// Assignee ID equals to 0 means clear assignee.
if issue.AssigneeID > 0 {
- if _, err = e.Exec("UPDATE `issue_user` SET is_assigned=? WHERE uid=? AND issue_id=?", true, issue.AssigneeID, issue.ID); err != nil {
+ if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?", true, issue.AssigneeID, issue.ID); err != nil {
return err
}
}
@@ -1112,11 +1332,29 @@ func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
}
// State returns string representation of milestone status.
-func (m *Milestone) State() string {
+func (m *Milestone) State() api.StateType {
if m.IsClosed {
- return "closed"
+ return api.STATE_CLOSED
+ }
+ return api.STATE_OPEN
+}
+
+func (m *Milestone) APIFormat() *api.Milestone {
+ apiMilestone := &api.Milestone{
+ ID: m.ID,
+ State: m.State(),
+ Title: m.Name,
+ Description: m.Content,
+ OpenIssues: m.NumOpenIssues,
+ ClosedIssues: m.NumClosedIssues,
+ }
+ if m.IsClosed {
+ apiMilestone.Closed = &m.ClosedDate
+ }
+ if m.Deadline.Year() < 9999 {
+ apiMilestone.Deadline = &m.Deadline
}
- return "open"
+ return apiMilestone
}
// NewMilestone creates new milestone of repository.
diff --git a/models/issue_comment.go b/models/issue_comment.go
index e9857b4216..9194060938 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -86,7 +86,7 @@ func (c *Comment) AfterSet(colName string, _ xorm.Cell) {
if err != nil {
if IsErrUserNotExist(err) {
c.PosterID = -1
- c.Poster = NewFakeUser()
+ c.Poster = NewGhostUser()
} else {
log.Error(3, "GetUserByID[%d]: %v", c.ID, err)
}
diff --git a/models/issue_label.go b/models/issue_label.go
index ed9b616011..a3015b02cd 100644
--- a/models/issue_label.go
+++ b/models/issue_label.go
@@ -12,6 +12,8 @@ import (
"github.com/go-xorm/xorm"
+ api "github.com/gogits/go-gogs-client"
+
"github.com/gogits/gogs/modules/base"
)
@@ -27,9 +29,17 @@ type Label struct {
IsChecked bool `xorm:"-"`
}
+func (label *Label) APIFormat() *api.Label {
+ return &api.Label{
+ ID: label.ID,
+ Name: label.Name,
+ Color: label.Color,
+ }
+}
+
// CalOpenIssues calculates the open issues of label.
-func (m *Label) CalOpenIssues() {
- m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
+func (label *Label) CalOpenIssues() {
+ label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
}
// ForegroundColor calculates the text color for labels based
diff --git a/models/issue_mail.go b/models/issue_mail.go
index 5441a45460..02548bc3ed 100644
--- a/models/issue_mail.go
+++ b/models/issue_mail.go
@@ -15,7 +15,7 @@ import (
)
func (issue *Issue) MailSubject() string {
- return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Name, issue.Index)
+ return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Title, issue.Index)
}
// mailIssueCommentToParticipants can be used for both new issue creation and comment.
diff --git a/models/pull.go b/models/pull.go
index 1710bff6b7..4a2a376704 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -68,7 +68,7 @@ func (pr *PullRequest) BeforeUpdate() {
pr.MergedUnix = pr.Merged.Unix()
}
-// Note: don't try to get Pull because will end up recursive querying.
+// Note: don't try to get Issue because will end up recursive querying.
func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "merged_unix":
@@ -80,6 +80,62 @@ func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
}
}
+// Note: don't try to get Issue because will end up recursive querying.
+func (pr *PullRequest) loadAttributes(e Engine) (err error) {
+ if pr.HasMerged && pr.Merger == nil {
+ pr.Merger, err = getUserByID(e, pr.MergerID)
+ if IsErrUserNotExist(err) {
+ pr.MergerID = -1
+ pr.Merger = NewGhostUser()
+ } else if err != nil {
+ return fmt.Errorf("getUserByID [%d]: %v", pr.MergerID, err)
+ }
+ }
+
+ return nil
+}
+
+func (pr *PullRequest) LoadAttributes() error {
+ return pr.loadAttributes(x)
+}
+
+func (pr *PullRequest) LoadIssue() (err error) {
+ pr.Issue, err = GetIssueByID(pr.IssueID)
+ return err
+}
+
+// This method assumes following fields have been assigned with valid values:
+// Required - Issue
+// Optional - Merger
+func (pr *PullRequest) APIFormat() *api.PullRequest {
+ apiIssue := pr.Issue.APIFormat()
+ apiPullRequest := &api.PullRequest{
+ ID: pr.ID,
+ Index: pr.Index,
+ State: apiIssue.State,
+ Title: apiIssue.Title,
+ Body: apiIssue.Body,
+ User: apiIssue.User,
+ Labels: apiIssue.Labels,
+ Milestone: apiIssue.Milestone,
+ Assignee: apiIssue.Assignee,
+ Comments: apiIssue.Comments,
+ HasMerged: pr.HasMerged,
+ }
+
+ if pr.Status != PULL_REQUEST_STATUS_CHECKING {
+ mergeable := pr.Status != PULL_REQUEST_STATUS_CONFLICT
+ apiPullRequest.Mergeable = &mergeable
+ }
+ if pr.HasMerged {
+ apiPullRequest.Merged = &pr.Merged
+ apiPullRequest.MergedCommitID = &pr.MergedCommitID
+ apiPullRequest.MergedBy = pr.Merger.APIFormat()
+ }
+
+ return apiPullRequest
+}
+
func (pr *PullRequest) getHeadRepo(e Engine) (err error) {
pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID)
if err != nil && !IsErrRepoNotExist(err) {
@@ -88,7 +144,7 @@ func (pr *PullRequest) getHeadRepo(e Engine) (err error) {
return nil
}
-func (pr *PullRequest) GetHeadRepo() (err error) {
+func (pr *PullRequest) GetHeadRepo() error {
return pr.getHeadRepo(x)
}
@@ -104,21 +160,6 @@ func (pr *PullRequest) GetBaseRepo() (err error) {
return nil
}
-func (pr *PullRequest) GetMerger() (err error) {
- if !pr.HasMerged || pr.Merger != nil {
- return nil
- }
-
- pr.Merger, err = GetUserByID(pr.MergerID)
- if IsErrUserNotExist(err) {
- pr.MergerID = -1
- pr.Merger = NewFakeUser()
- } else if err != nil {
- return fmt.Errorf("GetUserByID: %v", err)
- }
- return nil
-}
-
// IsChecking returns true if this pull request is still checking conflict.
func (pr *PullRequest) IsChecking() bool {
return pr.Status == PULL_REQUEST_STATUS_CHECKING
@@ -130,6 +171,7 @@ func (pr *PullRequest) CanAutoMerge() bool {
}
// Merge merges pull request to base repository.
+// FIXME: add repoWorkingPull make sure two merges does not happen at same time.
func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
if err = pr.GetHeadRepo(); err != nil {
return fmt.Errorf("GetHeadRepo: %v", err)
@@ -137,6 +179,11 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
return fmt.Errorf("GetBaseRepo: %v", err)
}
+ defer func() {
+ go HookQueue.Add(pr.BaseRepo.ID)
+ go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false)
+ }()
+
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
@@ -152,21 +199,6 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
if err != nil {
return fmt.Errorf("OpenRepository: %v", err)
}
- pr.MergedCommitID, err = headGitRepo.GetBranchCommitID(pr.HeadBranch)
- if err != nil {
- return fmt.Errorf("GetBranchCommitID: %v", err)
- }
-
- if err = mergePullRequestAction(sess, doer, pr.Issue.Repo, pr.Issue); err != nil {
- return fmt.Errorf("mergePullRequestAction: %v", err)
- }
-
- pr.HasMerged = true
- pr.Merged = time.Now()
- pr.MergerID = doer.ID
- if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
- return fmt.Errorf("update pull request: %v", err)
- }
// Clone base repo.
tmpBasePath := path.Join(setting.AppDataPath, "tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
@@ -222,15 +254,59 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
return fmt.Errorf("git push: %s", stderr)
}
+ pr.MergedCommitID, err = headGitRepo.GetBranchCommitID(pr.HeadBranch)
+ if err != nil {
+ return fmt.Errorf("GetBranchCommit: %v", err)
+ }
+
+ pr.HasMerged = true
+ pr.Merged = time.Now()
+ pr.MergerID = doer.ID
+ if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
+ return fmt.Errorf("update pull request: %v", err)
+ }
+
if err = sess.Commit(); err != nil {
return fmt.Errorf("Commit: %v", err)
}
- // Compose commit repository action
+ if err = MergePullRequestAction(doer, pr.Issue.Repo, pr.Issue); err != nil {
+ log.Error(4, "MergePullRequestAction [%d]: %v", pr.ID, err)
+ }
+
+ // Reload pull request information.
+ if err = pr.LoadAttributes(); err != nil {
+ log.Error(4, "LoadAttributes: %v", err)
+ return nil
+ }
+ if err = PrepareWebhooks(pr.Issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{
+ Action: api.HOOK_ISSUE_CLOSED,
+ Index: pr.Index,
+ PullRequest: pr.APIFormat(),
+ Repository: pr.Issue.Repo.APIFormat(nil),
+ Sender: doer.APIFormat(),
+ }); err != nil {
+ log.Error(4, "PrepareWebhooks: %v", err)
+ return nil
+ }
+
l, err := headGitRepo.CommitsBetweenIDs(pr.MergedCommitID, pr.MergeBase)
if err != nil {
- return fmt.Errorf("CommitsBetween: %v", err)
+ log.Error(4, "CommitsBetweenIDs: %v", err)
+ return nil
+ }
+
+ // TODO: when squash commits, no need to append merge commit.
+ // It is possible that head branch is not fully sync with base branch for merge commits,
+ // so we need to get latest head commit and append merge commit manully
+ // to avoid strange diff commits produced.
+ mergeCommit, err := baseGitRepo.GetBranchCommit(pr.BaseBranch)
+ if err != nil {
+ log.Error(4, "GetBranchCommit: %v", err)
+ return nil
}
+ l.PushFront(mergeCommit)
+
p := &api.PushPayload{
Ref: "refs/heads/" + pr.BaseBranch,
Before: pr.MergeBase,
@@ -252,8 +328,6 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
if err = PrepareWebhooks(pr.BaseRepo, HOOK_EVENT_PUSH, p); err != nil {
return fmt.Errorf("PrepareWebhooks: %v", err)
}
- go HookQueue.Add(pr.BaseRepo.ID)
- go AddTestPullRequestTask(pr.BaseRepo.ID, pr.BaseBranch)
return nil
}
@@ -331,19 +405,6 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
return fmt.Errorf("newIssue: %v", err)
}
- // Notify watchers.
- act := &Action{
- ActUserID: pull.Poster.ID,
- ActUserName: pull.Poster.Name,
- ActEmail: pull.Poster.Email,
- OpType: ACTION_CREATE_PULL_REQUEST,
- Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
- RepoID: repo.ID,
- RepoUserName: repo.Owner.Name,
- RepoName: repo.Name,
- IsPrivate: repo.IsPrivate,
- }
-
pr.Index = pull.Index
if err = repo.SavePatch(pr.Index, patch); err != nil {
return fmt.Errorf("SavePatch: %v", err)
@@ -353,6 +414,7 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
if err = pr.testPatch(); err != nil {
return fmt.Errorf("testPatch: %v", err)
}
+ // No conflict appears after test means mergeable.
if pr.Status == PULL_REQUEST_STATUS_CHECKING {
pr.Status = PULL_REQUEST_STATUS_MERGEABLE
}
@@ -366,12 +428,35 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
return fmt.Errorf("Commit: %v", err)
}
- if err = NotifyWatchers(act); err != nil {
+ if err = NotifyWatchers(&Action{
+ ActUserID: pull.Poster.ID,
+ ActUserName: pull.Poster.Name,
+ ActEmail: pull.Poster.Email,
+ OpType: ACTION_CREATE_PULL_REQUEST,
+ Content: fmt.Sprintf("%d|%s", pull.Index, pull.Title),
+ RepoID: repo.ID,
+ RepoUserName: repo.Owner.Name,
+ RepoName: repo.Name,
+ IsPrivate: repo.IsPrivate,
+ }); err != nil {
log.Error(4, "NotifyWatchers: %v", err)
} else if err = pull.MailParticipants(); err != nil {
log.Error(4, "MailParticipants: %v", err)
}
+ pr.Issue = pull
+ pull.PullRequest = pr
+ if err = PrepareWebhooks(repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{
+ Action: api.HOOK_ISSUE_OPENED,
+ Index: pull.Index,
+ PullRequest: pr.APIFormat(),
+ Repository: repo.APIFormat(nil),
+ Sender: pull.Poster.APIFormat(),
+ }); err != nil {
+ log.Error(4, "PrepareWebhooks: %v", err)
+ }
+ go HookQueue.Add(repo.ID)
+
return nil
}
@@ -395,9 +480,9 @@ func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch
// by given head information (repo and branch).
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
prs := make([]*PullRequest, 0, 2)
- return prs, x.Where("head_repo_id=? AND head_branch=? AND has_merged=? AND issue.is_closed=?",
+ return prs, x.Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ?",
repoID, branch, false, false).
- Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
+ Join("INNER", "issue", "issue.id = pull_request.issue_id").Find(&prs)
}
// GetUnmergedPullRequestsByBaseInfo returnss all pull requests that are open and has not been merged
@@ -409,30 +494,38 @@ func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequ
Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
}
-// GetPullRequestByID returns a pull request by given ID.
-func GetPullRequestByID(id int64) (*PullRequest, error) {
+func getPullRequestByID(e Engine, id int64) (*PullRequest, error) {
pr := new(PullRequest)
- has, err := x.Id(id).Get(pr)
+ has, err := e.Id(id).Get(pr)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{id, 0, 0, 0, "", ""}
}
- return pr, nil
+ return pr, pr.loadAttributes(e)
}
-// GetPullRequestByIssueID returns pull request by given issue ID.
-func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) {
+// GetPullRequestByID returns a pull request by given ID.
+func GetPullRequestByID(id int64) (*PullRequest, error) {
+ return getPullRequestByID(x, id)
+}
+
+func getPullRequestByIssueID(e Engine, issueID int64) (*PullRequest, error) {
pr := &PullRequest{
IssueID: issueID,
}
- has, err := x.Get(pr)
+ has, err := e.Get(pr)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""}
}
- return pr, nil
+ return pr, pr.loadAttributes(e)
+}
+
+// GetPullRequestByIssueID returns pull request by given issue ID.
+func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) {
+ return getPullRequestByIssueID(x, issueID)
}
// Update updates all fields of pull request.
@@ -536,6 +629,37 @@ func (pr *PullRequest) AddToTaskQueue() {
})
}
+type PullRequestList []*PullRequest
+
+func (prs PullRequestList) loadAttributes(e Engine) error {
+ if len(prs) == 0 {
+ return nil
+ }
+
+ // Load issues.
+ issueIDs := make([]int64, 0, len(prs))
+ for i := range prs {
+ issueIDs = append(issueIDs, prs[i].IssueID)
+ }
+ issues := make([]*Issue, 0, len(issueIDs))
+ if err := e.Where("id > 0").In("id", issueIDs).Find(&issues); err != nil {
+ return fmt.Errorf("find issues: %v", err)
+ }
+
+ set := make(map[int64]*Issue)
+ for i := range issues {
+ set[issues[i].ID] = issues[i]
+ }
+ for i := range prs {
+ prs[i].Issue = set[prs[i].IssueID]
+ }
+ return nil
+}
+
+func (prs PullRequestList) LoadAttributes() error {
+ return prs.loadAttributes(x)
+}
+
func addHeadRepoTasks(prs []*PullRequest) {
for _, pr := range prs {
log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
@@ -553,19 +677,47 @@ func addHeadRepoTasks(prs []*PullRequest) {
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
// and generate new patch for testing as needed.
-func AddTestPullRequestTask(repoID int64, branch string) {
- log.Trace("AddTestPullRequestTask[head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
+func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool) {
+ log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
prs, err := GetUnmergedPullRequestsByHeadInfo(repoID, branch)
if err != nil {
- log.Error(4, "Find pull requests[head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
+ log.Error(4, "Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
return
}
+
+ if isSync {
+ if err = PullRequestList(prs).LoadAttributes(); err != nil {
+ log.Error(4, "PullRequestList.LoadAttributes: %v", err)
+ }
+
+ if err == nil {
+ for _, pr := range prs {
+ pr.Issue.PullRequest = pr
+ if err = pr.Issue.LoadAttributes(); err != nil {
+ log.Error(4, "LoadAttributes: %v", err)
+ continue
+ }
+ if err = PrepareWebhooks(pr.Issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{
+ Action: api.HOOK_ISSUE_SYNCHRONIZED,
+ Index: pr.Issue.Index,
+ PullRequest: pr.Issue.PullRequest.APIFormat(),
+ Repository: pr.Issue.Repo.APIFormat(nil),
+ Sender: doer.APIFormat(),
+ }); err != nil {
+ log.Error(4, "PrepareWebhooks [pull_id: %v]: %v", pr.ID, err)
+ continue
+ }
+ go HookQueue.Add(pr.Issue.Repo.ID)
+ }
+ }
+ }
+
addHeadRepoTasks(prs)
- log.Trace("AddTestPullRequestTask[base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
+ log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
prs, err = GetUnmergedPullRequestsByBaseInfo(repoID, branch)
if err != nil {
- log.Error(4, "Find pull requests[base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
+ log.Error(4, "Find pull requests [base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
return
}
for _, pr := range prs {
diff --git a/models/repo.go b/models/repo.go
index afa8d23b2b..ef8cd34375 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -216,6 +216,48 @@ func (repo *Repository) AfterSet(colName string, _ xorm.Cell) {
}
}
+// MustOwner always returns a valid *User object to avoid
+// conceptually impossible error handling.
+// It creates a fake object that contains error deftail
+// when error occurs.
+func (repo *Repository) MustOwner() *User {
+ return repo.mustOwner(x)
+}
+
+func (repo *Repository) FullName() string {
+ return repo.MustOwner().Name + "/" + repo.Name
+}
+
+func (repo *Repository) FullLink() string {
+ return setting.AppUrl + repo.FullName()
+}
+
+// Arguments that are allowed to be nil: permission
+func (repo *Repository) APIFormat(permission *api.Permission) *api.Repository {
+ cloneLink := repo.CloneLink()
+ return &api.Repository{
+ ID: repo.ID,
+ Owner: repo.Owner.APIFormat(),
+ Name: repo.Name,
+ FullName: repo.FullName(),
+ Description: repo.Description,
+ Private: repo.IsPrivate,
+ Fork: repo.IsFork,
+ HTMLURL: repo.FullLink(),
+ SSHURL: cloneLink.SSH,
+ CloneURL: cloneLink.HTTPS,
+ Website: repo.Website,
+ Stars: repo.NumStars,
+ Forks: repo.NumForks,
+ Watchers: repo.NumWatches,
+ OpenIssues: repo.NumOpenIssues,
+ DefaultBranch: repo.DefaultBranch,
+ Created: repo.Created,
+ Updated: repo.Updated,
+ Permissions: permission,
+ }
+}
+
func (repo *Repository) getOwner(e Engine) (err error) {
if repo.Owner != nil {
return nil
@@ -240,14 +282,6 @@ func (repo *Repository) mustOwner(e Engine) *User {
return repo.Owner
}
-// MustOwner always returns a valid *User object to avoid
-// conceptually impossible error handling.
-// It creates a fake object that contains error deftail
-// when error occurs.
-func (repo *Repository) MustOwner() *User {
- return repo.mustOwner(x)
-}
-
// ComposeMetas composes a map of metas for rendering external issue tracker URL.
func (repo *Repository) ComposeMetas() map[string]string {
if !repo.EnableExternalTracker {
@@ -357,10 +391,6 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin
return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID)
}
-func (repo *Repository) FullLink() string {
- return setting.AppUrl + repo.MustOwner().Name + "/" + repo.Name
-}
-
func (repo *Repository) HasAccess(u *User) bool {
has, _ := HasAccess(u, repo, ACCESS_MODE_READ)
return has
diff --git a/models/user.go b/models/user.go
index d08b1cc8fa..7ec5e12b2f 100644
--- a/models/user.go
+++ b/models/user.go
@@ -25,6 +25,7 @@ import (
"github.com/nfnt/resize"
"github.com/gogits/git-module"
+ api "github.com/gogits/go-gogs-client"
"github.com/gogits/gogs/modules/avatar"
"github.com/gogits/gogs/modules/base"
@@ -130,6 +131,16 @@ func (u *User) AfterSet(colName string, _ xorm.Cell) {
}
}
+func (u *User) APIFormat() *api.User {
+ return &api.User{
+ ID: u.ID,
+ UserName: u.Name,
+ FullName: u.FullName,
+ Email: u.Email,
+ AvatarUrl: u.AvatarLink(),
+ }
+}
+
// returns true if user login type is LOGIN_PLAIN.
func (u *User) IsLocal() bool {
return u.LoginType <= LOGIN_PLAIN
@@ -468,12 +479,12 @@ func GetUserSalt() string {
return base.GetRandomString(10)
}
-// NewFakeUser creates and returns a fake user for someone has deleted his/her account.
-func NewFakeUser() *User {
+// NewGhostUser creates and returns a fake user for someone has deleted his/her account.
+func NewGhostUser() *User {
return &User{
ID: -1,
- Name: "Someone",
- LowerName: "someone",
+ Name: "Ghost",
+ LowerName: "ghost",
}
}
diff --git a/models/webhook.go b/models/webhook.go
index 79d60da3a2..ad4ac189bb 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -58,8 +58,9 @@ func IsValidHookContentType(name string) bool {
}
type HookEvents struct {
- Create bool `json:"create"`
- Push bool `json:"push"`
+ Create bool `json:"create"`
+ Push bool `json:"push"`
+ PullRequest bool `json:"pull_request"`
}
// HookEvent represents events that will delivery hook.
@@ -157,6 +158,12 @@ func (w *Webhook) HasPushEvent() bool {
(w.ChooseEvents && w.HookEvents.Push)
}
+// HasPullRequestEvent returns true if hook enabled pull request event.
+func (w *Webhook) HasPullRequestEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.PullRequest)
+}
+
func (w *Webhook) EventsArray() []string {
events := make([]string, 0, 2)
if w.HasCreateEvent() {
@@ -309,8 +316,9 @@ func IsValidHookTaskType(name string) bool {
type HookEventType string
const (
- HOOK_EVENT_CREATE HookEventType = "create"
- HOOK_EVENT_PUSH HookEventType = "push"
+ HOOK_EVENT_CREATE HookEventType = "create"
+ HOOK_EVENT_PUSH HookEventType = "push"
+ HOOK_EVENT_PULL_REQUEST HookEventType = "pull_request"
)
// HookRequest represents hook task request information.
@@ -422,17 +430,13 @@ func UpdateHookTask(t *HookTask) error {
// 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() {
+ if repo.MustOwner().IsOrganization() {
// get hooks for org
orgws, err := GetActiveWebhooksByOrgID(repo.OwnerID)
if err != nil {
@@ -456,6 +460,10 @@ func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) err
if !w.HasPushEvent() {
continue
}
+ case HOOK_EVENT_PULL_REQUEST:
+ if !w.HasPullRequestEvent() {
+ continue
+ }
}
// Use separate objects so modifcations won't be made on payload on non-Gogs type hooks.
@@ -477,7 +485,7 @@ func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) err
URL: w.URL,
Payloader: payloader,
ContentType: w.ContentType,
- EventType: HOOK_EVENT_PUSH,
+ EventType: event,
IsSSL: w.IsSSL,
}); err != nil {
return fmt.Errorf("CreateHookTask: %v", err)
diff --git a/models/webhook_slack.go b/models/webhook_slack.go
index e41bbbc79f..5578c816a5 100644
--- a/models/webhook_slack.go
+++ b/models/webhook_slack.go
@@ -12,6 +12,8 @@ import (
"github.com/gogits/git-module"
api "github.com/gogits/go-gogs-client"
+
+ "github.com/gogits/gogs/modules/setting"
)
type SlackMeta struct {
@@ -34,6 +36,7 @@ type SlackPayload struct {
type SlackAttachment struct {
Fallback string `json:"fallback"`
Color string `json:"color"`
+ Title string `json:"title"`
Text string `json:"text"`
}
@@ -49,13 +52,20 @@ func (p *SlackPayload) JSONPayload() ([]byte, 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
+ s = strings.Replace(s, "&", "&amp;", -1)
+ s = strings.Replace(s, "<", "&lt;", -1)
+ s = strings.Replace(s, ">", "&gt;", -1)
+ return s
+}
+
+func SlackShortTextFormatter(s string) string {
+ s = strings.Split(s, "\n")[0]
+ // replace & < >
+ s = strings.Replace(s, "&", "&amp;", -1)
+ s = strings.Replace(s, "<", "&lt;", -1)
+ s = strings.Replace(s, ">", "&gt;", -1)
+ return s
}
func SlackLinkFormatter(url string, text string) string {
@@ -104,24 +114,70 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
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]), SlackShortTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name))
// add linebreak to each commit but the last
if i < len(p.Commits)-1 {
attachmentText += "\n"
}
}
- slackAttachments := []SlackAttachment{{
- Color: slack.Color,
- Text: attachmentText,
- }}
+ return &SlackPayload{
+ Channel: slack.Channel,
+ Text: text,
+ Username: slack.Username,
+ IconURL: slack.IconURL,
+ Attachments: []SlackAttachment{{
+ Color: slack.Color,
+ Text: attachmentText,
+ }},
+ }, nil
+}
+
+func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*SlackPayload, error) {
+ senderLink := SlackLinkFormatter(setting.AppUrl+p.Sender.UserName, p.Sender.UserName)
+ titleLink := SlackLinkFormatter(fmt.Sprintf("%s/%d", setting.AppUrl+p.Repository.FullName+"/pulls", p.Index),
+ fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
+ var text, title, attachmentText string
+ switch p.Action {
+ case api.HOOK_ISSUE_OPENED:
+ text = fmt.Sprintf("[%s] Pull request submitted by %s", p.Repository.FullName, senderLink)
+ title = titleLink
+ attachmentText = SlackTextFormatter(p.PullRequest.Body)
+ case api.HOOK_ISSUE_CLOSED:
+ if p.PullRequest.HasMerged {
+ text = fmt.Sprintf("[%s] Pull request merged: %s by %s", p.Repository.FullName, titleLink, senderLink)
+ } else {
+ text = fmt.Sprintf("[%s] Pull request closed: %s by %s", p.Repository.FullName, titleLink, senderLink)
+ }
+ case api.HOOK_ISSUE_REOPENED:
+ text = fmt.Sprintf("[%s] Pull request re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink)
+ case api.HOOK_ISSUE_EDITED:
+ text = fmt.Sprintf("[%s] Pull request edited: %s by %s", p.Repository.FullName, titleLink, senderLink)
+ attachmentText = SlackTextFormatter(p.PullRequest.Body)
+ case api.HOOK_ISSUE_ASSIGNED:
+ text = fmt.Sprintf("[%s] Pull request assigned to %s: %s by %s", p.Repository.FullName,
+ SlackLinkFormatter(setting.AppUrl+p.PullRequest.Assignee.UserName, p.PullRequest.Assignee.UserName),
+ titleLink, senderLink)
+ case api.HOOK_ISSUE_UNASSIGNED:
+ text = fmt.Sprintf("[%s] Pull request unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)
+ case api.HOOK_ISSUE_LABEL_UPDATED:
+ text = fmt.Sprintf("[%s] Pull request labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink)
+ case api.HOOK_ISSUE_LABEL_CLEARED:
+ text = fmt.Sprintf("[%s] Pull request labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink)
+ case api.HOOK_ISSUE_SYNCHRONIZED:
+ text = fmt.Sprintf("[%s] Pull request synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink)
+ }
return &SlackPayload{
- Channel: slack.Channel,
- Text: text,
- Username: slack.Username,
- IconURL: slack.IconURL,
- Attachments: slackAttachments,
+ Channel: slack.Channel,
+ Text: text,
+ Username: slack.Username,
+ IconURL: slack.IconURL,
+ Attachments: []SlackAttachment{{
+ Color: slack.Color,
+ Title: title,
+ Text: attachmentText,
+ }},
}, nil
}
@@ -138,6 +194,8 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackP
return getSlackCreatePayload(p.(*api.CreatePayload), slack)
case HOOK_EVENT_PUSH:
return getSlackPushPayload(p.(*api.PushPayload), slack)
+ case HOOK_EVENT_PULL_REQUEST:
+ return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
}
return s, nil