085f717529
I found myself wondering whether a PR I scheduled for automerge was actually merged. It was, but I didn't receive a mail notification for it - that makes sense considering I am the doer and usually don't want to receive such notifications. But ideally I want to receive a notification when a PR was merged because I scheduled it for automerge. This PR implements exactly that. The implementation works, but I wonder if there's a way to avoid passing the "This PR was automerged" state down so much. I tried solving this via the database (checking if there's an automerge scheduled for this PR when sending the notification) but that did not work reliably, probably because sending the notification happens async and the entry might have already been deleted. My implementation might be the most straightforward but maybe not the most elegant. Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
457 lines
14 KiB
Go
457 lines
14 KiB
Go
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package action
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
|
|
activities_model "code.gitea.io/gitea/models/activities"
|
|
"code.gitea.io/gitea/models/db"
|
|
issues_model "code.gitea.io/gitea/models/issues"
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/graceful"
|
|
"code.gitea.io/gitea/modules/json"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/notification/base"
|
|
"code.gitea.io/gitea/modules/process"
|
|
"code.gitea.io/gitea/modules/repository"
|
|
"code.gitea.io/gitea/modules/util"
|
|
)
|
|
|
|
type actionNotifier struct {
|
|
base.NullNotifier
|
|
}
|
|
|
|
var _ base.Notifier = &actionNotifier{}
|
|
|
|
// NewNotifier create a new actionNotifier notifier
|
|
func NewNotifier() base.Notifier {
|
|
return &actionNotifier{}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifyNewIssue(issue *issues_model.Issue, mentions []*user_model.User) {
|
|
if err := issue.LoadPoster(); err != nil {
|
|
log.Error("issue.LoadPoster: %v", err)
|
|
return
|
|
}
|
|
if err := issue.LoadRepo(db.DefaultContext); err != nil {
|
|
log.Error("issue.LoadRepo: %v", err)
|
|
return
|
|
}
|
|
repo := issue.Repo
|
|
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: issue.Poster.ID,
|
|
ActUser: issue.Poster,
|
|
OpType: activities_model.ActionCreateIssue,
|
|
Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title),
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
IsPrivate: repo.IsPrivate,
|
|
}); err != nil {
|
|
log.Error("NotifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
// NotifyIssueChangeStatus notifies close or reopen issue to notifiers
|
|
func (a *actionNotifier) NotifyIssueChangeStatus(doer *user_model.User, issue *issues_model.Issue, actionComment *issues_model.Comment, closeOrReopen bool) {
|
|
// Compose comment action, could be plain comment, close or reopen issue/pull request.
|
|
// This object will be used to notify watchers in the end of function.
|
|
act := &activities_model.Action{
|
|
ActUserID: doer.ID,
|
|
ActUser: doer,
|
|
Content: fmt.Sprintf("%d|%s", issue.Index, ""),
|
|
RepoID: issue.Repo.ID,
|
|
Repo: issue.Repo,
|
|
Comment: actionComment,
|
|
CommentID: actionComment.ID,
|
|
IsPrivate: issue.Repo.IsPrivate,
|
|
}
|
|
// Check comment type.
|
|
if closeOrReopen {
|
|
act.OpType = activities_model.ActionCloseIssue
|
|
if issue.IsPull {
|
|
act.OpType = activities_model.ActionClosePullRequest
|
|
}
|
|
} else {
|
|
act.OpType = activities_model.ActionReopenIssue
|
|
if issue.IsPull {
|
|
act.OpType = activities_model.ActionReopenPullRequest
|
|
}
|
|
}
|
|
|
|
// Notify watchers for whatever action comes in, ignore if no action type.
|
|
if err := activities_model.NotifyWatchers(act); err != nil {
|
|
log.Error("NotifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
// NotifyCreateIssueComment notifies comment on an issue to notifiers
|
|
func (a *actionNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
|
issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User,
|
|
) {
|
|
act := &activities_model.Action{
|
|
ActUserID: doer.ID,
|
|
ActUser: doer,
|
|
RepoID: issue.Repo.ID,
|
|
Repo: issue.Repo,
|
|
Comment: comment,
|
|
CommentID: comment.ID,
|
|
IsPrivate: issue.Repo.IsPrivate,
|
|
}
|
|
|
|
truncatedContent, truncatedRight := util.SplitStringAtByteN(comment.Content, 200)
|
|
if truncatedRight != "" {
|
|
// in case the content is in a Latin family language, we remove the last broken word.
|
|
lastSpaceIdx := strings.LastIndex(truncatedContent, " ")
|
|
if lastSpaceIdx != -1 && (len(truncatedContent)-lastSpaceIdx < 15) {
|
|
truncatedContent = truncatedContent[:lastSpaceIdx] + "…"
|
|
}
|
|
}
|
|
act.Content = fmt.Sprintf("%d|%s", issue.Index, truncatedContent)
|
|
|
|
if issue.IsPull {
|
|
act.OpType = activities_model.ActionCommentPull
|
|
} else {
|
|
act.OpType = activities_model.ActionCommentIssue
|
|
}
|
|
|
|
// Notify watchers for whatever action comes in, ignore if no action type.
|
|
if err := activities_model.NotifyWatchers(act); err != nil {
|
|
log.Error("NotifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifyNewPullRequest(pull *issues_model.PullRequest, mentions []*user_model.User) {
|
|
if err := pull.LoadIssue(); err != nil {
|
|
log.Error("pull.LoadIssue: %v", err)
|
|
return
|
|
}
|
|
if err := pull.Issue.LoadRepo(db.DefaultContext); err != nil {
|
|
log.Error("pull.Issue.LoadRepo: %v", err)
|
|
return
|
|
}
|
|
if err := pull.Issue.LoadPoster(); err != nil {
|
|
log.Error("pull.Issue.LoadPoster: %v", err)
|
|
return
|
|
}
|
|
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: pull.Issue.Poster.ID,
|
|
ActUser: pull.Issue.Poster,
|
|
OpType: activities_model.ActionCreatePullRequest,
|
|
Content: fmt.Sprintf("%d|%s", pull.Issue.Index, pull.Issue.Title),
|
|
RepoID: pull.Issue.Repo.ID,
|
|
Repo: pull.Issue.Repo,
|
|
IsPrivate: pull.Issue.Repo.IsPrivate,
|
|
}); err != nil {
|
|
log.Error("NotifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifyRenameRepository(doer *user_model.User, repo *repo_model.Repository, oldRepoName string) {
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: doer.ID,
|
|
ActUser: doer,
|
|
OpType: activities_model.ActionRenameRepo,
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
IsPrivate: repo.IsPrivate,
|
|
Content: oldRepoName,
|
|
}); err != nil {
|
|
log.Error("NotifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifyTransferRepository(doer *user_model.User, repo *repo_model.Repository, oldOwnerName string) {
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: doer.ID,
|
|
ActUser: doer,
|
|
OpType: activities_model.ActionTransferRepo,
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
IsPrivate: repo.IsPrivate,
|
|
Content: path.Join(oldOwnerName, repo.Name),
|
|
}); err != nil {
|
|
log.Error("NotifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifyCreateRepository(doer, u *user_model.User, repo *repo_model.Repository) {
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: doer.ID,
|
|
ActUser: doer,
|
|
OpType: activities_model.ActionCreateRepo,
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
IsPrivate: repo.IsPrivate,
|
|
}); err != nil {
|
|
log.Error("notify watchers '%d/%d': %v", doer.ID, repo.ID, err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifyForkRepository(doer *user_model.User, oldRepo, repo *repo_model.Repository) {
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: doer.ID,
|
|
ActUser: doer,
|
|
OpType: activities_model.ActionCreateRepo,
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
IsPrivate: repo.IsPrivate,
|
|
}); err != nil {
|
|
log.Error("notify watchers '%d/%d': %v", doer.ID, repo.ID, err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifyPullRequestReview(pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
|
|
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("actionNotifier.NotifyPullRequestReview Pull[%d] #%d in [%d]", pr.ID, pr.Index, pr.BaseRepoID))
|
|
defer finished()
|
|
|
|
if err := review.LoadReviewer(); err != nil {
|
|
log.Error("LoadReviewer '%d/%d': %v", review.ID, review.ReviewerID, err)
|
|
return
|
|
}
|
|
if err := review.LoadCodeComments(ctx); err != nil {
|
|
log.Error("LoadCodeComments '%d/%d': %v", review.Reviewer.ID, review.ID, err)
|
|
return
|
|
}
|
|
|
|
actions := make([]*activities_model.Action, 0, 10)
|
|
for _, lines := range review.CodeComments {
|
|
for _, comments := range lines {
|
|
for _, comm := range comments {
|
|
actions = append(actions, &activities_model.Action{
|
|
ActUserID: review.Reviewer.ID,
|
|
ActUser: review.Reviewer,
|
|
Content: fmt.Sprintf("%d|%s", review.Issue.Index, strings.Split(comm.Content, "\n")[0]),
|
|
OpType: activities_model.ActionCommentPull,
|
|
RepoID: review.Issue.RepoID,
|
|
Repo: review.Issue.Repo,
|
|
IsPrivate: review.Issue.Repo.IsPrivate,
|
|
Comment: comm,
|
|
CommentID: comm.ID,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if review.Type != issues_model.ReviewTypeComment || strings.TrimSpace(comment.Content) != "" {
|
|
action := &activities_model.Action{
|
|
ActUserID: review.Reviewer.ID,
|
|
ActUser: review.Reviewer,
|
|
Content: fmt.Sprintf("%d|%s", review.Issue.Index, strings.Split(comment.Content, "\n")[0]),
|
|
RepoID: review.Issue.RepoID,
|
|
Repo: review.Issue.Repo,
|
|
IsPrivate: review.Issue.Repo.IsPrivate,
|
|
Comment: comment,
|
|
CommentID: comment.ID,
|
|
}
|
|
|
|
switch review.Type {
|
|
case issues_model.ReviewTypeApprove:
|
|
action.OpType = activities_model.ActionApprovePullRequest
|
|
case issues_model.ReviewTypeReject:
|
|
action.OpType = activities_model.ActionRejectPullRequest
|
|
default:
|
|
action.OpType = activities_model.ActionCommentPull
|
|
}
|
|
|
|
actions = append(actions, action)
|
|
}
|
|
|
|
if err := activities_model.NotifyWatchersActions(actions); err != nil {
|
|
log.Error("notify watchers '%d/%d': %v", review.Reviewer.ID, review.Issue.RepoID, err)
|
|
}
|
|
}
|
|
|
|
func (*actionNotifier) NotifyMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User) {
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: doer.ID,
|
|
ActUser: doer,
|
|
OpType: activities_model.ActionMergePullRequest,
|
|
Content: fmt.Sprintf("%d|%s", pr.Issue.Index, pr.Issue.Title),
|
|
RepoID: pr.Issue.Repo.ID,
|
|
Repo: pr.Issue.Repo,
|
|
IsPrivate: pr.Issue.Repo.IsPrivate,
|
|
}); err != nil {
|
|
log.Error("NotifyWatchers [%d]: %v", pr.ID, err)
|
|
}
|
|
}
|
|
|
|
func (*actionNotifier) NotifyAutoMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User) {
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: doer.ID,
|
|
ActUser: doer,
|
|
OpType: activities_model.ActionAutoMergePullRequest,
|
|
Content: fmt.Sprintf("%d|%s", pr.Issue.Index, pr.Issue.Title),
|
|
RepoID: pr.Issue.Repo.ID,
|
|
Repo: pr.Issue.Repo,
|
|
IsPrivate: pr.Issue.Repo.IsPrivate,
|
|
}); err != nil {
|
|
log.Error("NotifyWatchers [%d]: %v", pr.ID, err)
|
|
}
|
|
}
|
|
|
|
func (*actionNotifier) NotifyPullRevieweDismiss(doer *user_model.User, review *issues_model.Review, comment *issues_model.Comment) {
|
|
reviewerName := review.Reviewer.Name
|
|
if len(review.OriginalAuthor) > 0 {
|
|
reviewerName = review.OriginalAuthor
|
|
}
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: doer.ID,
|
|
ActUser: doer,
|
|
OpType: activities_model.ActionPullReviewDismissed,
|
|
Content: fmt.Sprintf("%d|%s|%s", review.Issue.Index, reviewerName, comment.Content),
|
|
RepoID: review.Issue.Repo.ID,
|
|
Repo: review.Issue.Repo,
|
|
IsPrivate: review.Issue.Repo.IsPrivate,
|
|
CommentID: comment.ID,
|
|
Comment: comment,
|
|
}); err != nil {
|
|
log.Error("NotifyWatchers [%d]: %v", review.Issue.ID, err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifyPushCommits(pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
|
|
data, err := json.Marshal(commits)
|
|
if err != nil {
|
|
log.Error("Marshal: %v", err)
|
|
return
|
|
}
|
|
|
|
opType := activities_model.ActionCommitRepo
|
|
|
|
// Check it's tag push or branch.
|
|
if opts.IsTag() {
|
|
opType = activities_model.ActionPushTag
|
|
if opts.IsDelRef() {
|
|
opType = activities_model.ActionDeleteTag
|
|
}
|
|
} else if opts.IsDelRef() {
|
|
opType = activities_model.ActionDeleteBranch
|
|
}
|
|
|
|
if err = activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: pusher.ID,
|
|
ActUser: pusher,
|
|
OpType: opType,
|
|
Content: string(data),
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
RefName: opts.RefFullName,
|
|
IsPrivate: repo.IsPrivate,
|
|
}); err != nil {
|
|
log.Error("notifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifyCreateRef(doer *user_model.User, repo *repo_model.Repository, refType, refFullName, refID string) {
|
|
opType := activities_model.ActionCommitRepo
|
|
if refType == "tag" {
|
|
// has sent same action in `NotifyPushCommits`, so skip it.
|
|
return
|
|
}
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: doer.ID,
|
|
ActUser: doer,
|
|
OpType: opType,
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
IsPrivate: repo.IsPrivate,
|
|
RefName: refFullName,
|
|
}); err != nil {
|
|
log.Error("notifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifyDeleteRef(doer *user_model.User, repo *repo_model.Repository, refType, refFullName string) {
|
|
opType := activities_model.ActionDeleteBranch
|
|
if refType == "tag" {
|
|
// has sent same action in `NotifyPushCommits`, so skip it.
|
|
return
|
|
}
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: doer.ID,
|
|
ActUser: doer,
|
|
OpType: opType,
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
IsPrivate: repo.IsPrivate,
|
|
RefName: refFullName,
|
|
}); err != nil {
|
|
log.Error("notifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifySyncPushCommits(pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
|
|
data, err := json.Marshal(commits)
|
|
if err != nil {
|
|
log.Error("json.Marshal: %v", err)
|
|
return
|
|
}
|
|
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: repo.OwnerID,
|
|
ActUser: repo.MustOwner(),
|
|
OpType: activities_model.ActionMirrorSyncPush,
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
IsPrivate: repo.IsPrivate,
|
|
RefName: opts.RefFullName,
|
|
Content: string(data),
|
|
}); err != nil {
|
|
log.Error("notifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifySyncCreateRef(doer *user_model.User, repo *repo_model.Repository, refType, refFullName, refID string) {
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: repo.OwnerID,
|
|
ActUser: repo.MustOwner(),
|
|
OpType: activities_model.ActionMirrorSyncCreate,
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
IsPrivate: repo.IsPrivate,
|
|
RefName: refFullName,
|
|
}); err != nil {
|
|
log.Error("notifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifySyncDeleteRef(doer *user_model.User, repo *repo_model.Repository, refType, refFullName string) {
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: repo.OwnerID,
|
|
ActUser: repo.MustOwner(),
|
|
OpType: activities_model.ActionMirrorSyncDelete,
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
IsPrivate: repo.IsPrivate,
|
|
RefName: refFullName,
|
|
}); err != nil {
|
|
log.Error("notifyWatchers: %v", err)
|
|
}
|
|
}
|
|
|
|
func (a *actionNotifier) NotifyNewRelease(rel *repo_model.Release) {
|
|
if err := rel.LoadAttributes(); err != nil {
|
|
log.Error("NotifyNewRelease: %v", err)
|
|
return
|
|
}
|
|
if err := activities_model.NotifyWatchers(&activities_model.Action{
|
|
ActUserID: rel.PublisherID,
|
|
ActUser: rel.Publisher,
|
|
OpType: activities_model.ActionPublishRelease,
|
|
RepoID: rel.RepoID,
|
|
Repo: rel.Repo,
|
|
IsPrivate: rel.Repo.IsPrivate,
|
|
Content: rel.Title,
|
|
RefName: rel.TagName,
|
|
}); err != nil {
|
|
log.Error("notifyWatchers: %v", err)
|
|
}
|
|
}
|