]> source.dussan.org Git - gitea.git/commitdiff
Add new event commit status creation and webhook implementation (#27151)
authorLunny Xiao <xiaolunwen@gmail.com>
Thu, 7 Nov 2024 06:41:49 +0000 (22:41 -0800)
committerGitHub <noreply@github.com>
Thu, 7 Nov 2024 06:41:49 +0000 (06:41 +0000)
This PR introduces a new event which is similar as Github's. When a new
commit status submitted, the event will be trigged. That means, now we
can receive all feedback from CI/CD system in webhooks or other notify
systems.

ref:
https://docs.github.com/en/webhooks/webhook-events-and-payloads#status

Fix #20749

modules/repository/commits.go
modules/structs/hook.go
modules/webhook/type.go
services/actions/commit_status.go
services/automerge/notify.go
services/notify/notifier.go
services/notify/notify.go
services/notify/null.go
services/repository/commitstatus/commitstatus.go
services/webhook/notifier.go

index ede60429a13fb92be16b8c6d5b0ff52c1463ba9a..6e4b75d5caceaeb9aab70c22fcf545671b5f6987 100644 (file)
@@ -42,8 +42,8 @@ func NewPushCommits() *PushCommits {
        return &PushCommits{}
 }
 
-// toAPIPayloadCommit converts a single PushCommit to an api.PayloadCommit object.
-func (pc *PushCommits) toAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.User, repoPath, repoLink string, commit *PushCommit) (*api.PayloadCommit, error) {
+// ToAPIPayloadCommit converts a single PushCommit to an api.PayloadCommit object.
+func ToAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.User, repoPath, repoLink string, commit *PushCommit) (*api.PayloadCommit, error) {
        var err error
        authorUsername := ""
        author, ok := emailUsers[commit.AuthorEmail]
@@ -105,7 +105,7 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repoPath, repoLi
        emailUsers := make(map[string]*user_model.User)
 
        for i, commit := range pc.Commits {
-               apiCommit, err := pc.toAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, commit)
+               apiCommit, err := ToAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, commit)
                if err != nil {
                        return nil, nil, err
                }
@@ -117,7 +117,7 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repoPath, repoLi
        }
        if pc.HeadCommit != nil && headCommit == nil {
                var err error
-               headCommit, err = pc.toAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, pc.HeadCommit)
+               headCommit, err = ToAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, pc.HeadCommit)
                if err != nil {
                        return nil, nil, err
                }
index db8b20e7e5572c0cf42efd2bb05eb556b73267ee..ce5742e5c7ce95a7aea358a4e2d4b1c02bb07c3d 100644 (file)
@@ -262,13 +262,6 @@ func (p *ReleasePayload) JSONPayload() ([]byte, error) {
        return json.MarshalIndent(p, "", "  ")
 }
 
-// __________             .__
-// \______   \__ __  _____|  |__
-//  |     ___/  |  \/  ___/  |  \
-//  |    |   |  |  /\___ \|   Y  \
-//  |____|   |____//____  >___|  /
-//                      \/     \/
-
 // PushPayload represents a payload information of push event.
 type PushPayload struct {
        Ref          string           `json:"ref"`
@@ -509,3 +502,26 @@ type WorkflowDispatchPayload struct {
 func (p *WorkflowDispatchPayload) JSONPayload() ([]byte, error) {
        return json.MarshalIndent(p, "", "  ")
 }
+
+// CommitStatusPayload represents a payload information of commit status event.
+type CommitStatusPayload struct {
+       // TODO: add Branches per https://docs.github.com/en/webhooks/webhook-events-and-payloads#status
+       Commit  *PayloadCommit `json:"commit"`
+       Context string         `json:"context"`
+       // swagger:strfmt date-time
+       CreatedAt   time.Time   `json:"created_at"`
+       Description string      `json:"description"`
+       ID          int64       `json:"id"`
+       Repo        *Repository `json:"repository"`
+       Sender      *User       `json:"sender"`
+       SHA         string      `json:"sha"`
+       State       string      `json:"state"`
+       TargetURL   string      `json:"target_url"`
+       // swagger:strfmt date-time
+       UpdatedAt *time.Time `json:"updated_at"`
+}
+
+// JSONPayload implements Payload
+func (p *CommitStatusPayload) JSONPayload() ([]byte, error) {
+       return json.MarshalIndent(p, "", "  ")
+}
index 0013691c02f47539dfc87c7dbc726f6443190f09..fbec8892722b7fb0e6848cdf48b82bd4f910e417 100644 (file)
@@ -32,6 +32,7 @@ const (
        HookEventRelease                   HookEventType = "release"
        HookEventPackage                   HookEventType = "package"
        HookEventSchedule                  HookEventType = "schedule"
+       HookEventStatus                    HookEventType = "status"
 )
 
 // Event returns the HookEventType as an event string
index 8d86ec4dfada9ccbee6f37b18e6bf60c935dbb88..7f52c9d31b5906dc7b3b8befbf90156c0a8101e4 100644 (file)
@@ -128,18 +128,16 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
        if err != nil {
                return fmt.Errorf("HashTypeInterfaceFromHashString: %w", err)
        }
-       if err := commitstatus_service.CreateCommitStatus(ctx, repo, creator, commitID.String(), &git_model.CommitStatus{
+       status := git_model.CommitStatus{
                SHA:         sha,
                TargetURL:   fmt.Sprintf("%s/jobs/%d", run.Link(), index),
                Description: description,
                Context:     ctxname,
                CreatorID:   creator.ID,
                State:       state,
-       }); err != nil {
-               return fmt.Errorf("NewCommitStatus: %w", err)
        }
 
-       return nil
+       return commitstatus_service.CreateCommitStatus(ctx, repo, creator, commitID.String(), &status)
 }
 
 func toCommitStatus(status actions_model.Status) api.CommitStatusState {
index cb078214f63b776064df3ff9c621fc480ce97cff..b6bbca333b41d92643cbdbd18143ab63d1721b6f 100644 (file)
@@ -6,9 +6,12 @@ package automerge
 import (
        "context"
 
+       git_model "code.gitea.io/gitea/models/git"
        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/log"
+       "code.gitea.io/gitea/modules/repository"
        notify_service "code.gitea.io/gitea/services/notify"
 )
 
@@ -44,3 +47,11 @@ func (n *automergeNotifier) PullReviewDismiss(ctx context.Context, doer *user_mo
        // as reviews could have blocked a pending automerge let's recheck
        StartPRCheckAndAutoMerge(ctx, review.Issue.PullRequest)
 }
+
+func (n *automergeNotifier) CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) {
+       if status.State.IsSuccess() {
+               if err := StartPRCheckAndAutoMergeBySHA(ctx, commit.Sha1, repo); err != nil {
+                       log.Error("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, sender.ID, commit.Sha1, err)
+               }
+       }
+}
index ed053a812a6fdd51f5be29b8a353528dde828f96..29bbb5702b3bdcd3f9625ec966754f4915242683 100644 (file)
@@ -6,6 +6,7 @@ package notify
 import (
        "context"
 
+       git_model "code.gitea.io/gitea/models/git"
        issues_model "code.gitea.io/gitea/models/issues"
        packages_model "code.gitea.io/gitea/models/packages"
        repo_model "code.gitea.io/gitea/models/repo"
@@ -74,4 +75,6 @@ type Notifier interface {
        PackageDelete(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor)
 
        ChangeDefaultBranch(ctx context.Context, repo *repo_model.Repository)
+
+       CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus)
 }
index 0c8262ef7a5d3da59c5f5fb7b3688aaa8abc700d..3b5f24340bb86e94dc76cc917e09a203d53b1ab0 100644 (file)
@@ -6,6 +6,7 @@ package notify
 import (
        "context"
 
+       git_model "code.gitea.io/gitea/models/git"
        issues_model "code.gitea.io/gitea/models/issues"
        packages_model "code.gitea.io/gitea/models/packages"
        repo_model "code.gitea.io/gitea/models/repo"
@@ -367,3 +368,9 @@ func ChangeDefaultBranch(ctx context.Context, repo *repo_model.Repository) {
                notifier.ChangeDefaultBranch(ctx, repo)
        }
 }
+
+func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) {
+       for _, notifier := range notifiers {
+               notifier.CreateCommitStatus(ctx, repo, commit, sender, status)
+       }
+}
index dddd421bef92655f5568ef54167341e8501dd108..7354efd70157c4d2afaa5ac8052308c2aa54d26c 100644 (file)
@@ -6,6 +6,7 @@ package notify
 import (
        "context"
 
+       git_model "code.gitea.io/gitea/models/git"
        issues_model "code.gitea.io/gitea/models/issues"
        packages_model "code.gitea.io/gitea/models/packages"
        repo_model "code.gitea.io/gitea/models/repo"
@@ -208,3 +209,6 @@ func (*NullNotifier) PackageDelete(ctx context.Context, doer *user_model.User, p
 // ChangeDefaultBranch places a place holder function
 func (*NullNotifier) ChangeDefaultBranch(ctx context.Context, repo *repo_model.Repository) {
 }
+
+func (*NullNotifier) CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) {
+}
index adc59abed8f47182635375093696ba8132e3b155..f369a303e6ce4924f6d900561650b6528e8c9a78 100644 (file)
@@ -18,8 +18,9 @@ import (
        "code.gitea.io/gitea/modules/gitrepo"
        "code.gitea.io/gitea/modules/json"
        "code.gitea.io/gitea/modules/log"
+       repo_module "code.gitea.io/gitea/modules/repository"
        api "code.gitea.io/gitea/modules/structs"
-       "code.gitea.io/gitea/services/automerge"
+       "code.gitea.io/gitea/services/notify"
 )
 
 func getCacheKey(repoID int64, brancheName string) string {
@@ -103,6 +104,8 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
                return err
        }
 
+       notify.CreateCommitStatus(ctx, repo, repo_module.CommitToPushCommit(commit), creator, status)
+
        defaultBranchCommit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
        if err != nil {
                return fmt.Errorf("GetBranchCommit[%s]: %w", repo.DefaultBranch, err)
@@ -114,12 +117,6 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
                }
        }
 
-       if status.State.IsSuccess() {
-               if err := automerge.StartPRCheckAndAutoMergeBySHA(ctx, sha, repo); err != nil {
-                       return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
-               }
-       }
-
        return nil
 }
 
index 38fad7f5e8eed7fe692921231947e0ecb685ea36..cc263947e9b90e3403a0910c3769700cc553beac 100644 (file)
@@ -6,6 +6,7 @@ package webhook
 import (
        "context"
 
+       git_model "code.gitea.io/gitea/models/git"
        issues_model "code.gitea.io/gitea/models/issues"
        packages_model "code.gitea.io/gitea/models/packages"
        "code.gitea.io/gitea/models/perm"
@@ -861,6 +862,36 @@ func (m *webhookNotifier) SyncPushCommits(ctx context.Context, pusher *user_mode
        }
 }
 
+func (m *webhookNotifier) CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) {
+       apiSender := convert.ToUser(ctx, sender, nil)
+       apiCommit, err := repository.ToAPIPayloadCommit(ctx, map[string]*user_model.User{}, repo.RepoPath(), repo.HTMLURL(), commit)
+       if err != nil {
+               log.Error("commits.ToAPIPayloadCommits failed: %v", err)
+               return
+       }
+
+       payload := api.CommitStatusPayload{
+               Context:     status.Context,
+               CreatedAt:   status.CreatedUnix.AsTime().UTC(),
+               Description: status.Description,
+               ID:          status.ID,
+               SHA:         commit.Sha1,
+               State:       status.State.String(),
+               TargetURL:   status.TargetURL,
+
+               Commit: apiCommit,
+               Repo:   convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
+               Sender: apiSender,
+       }
+       if !status.UpdatedUnix.IsZero() {
+               t := status.UpdatedUnix.AsTime().UTC()
+               payload.UpdatedAt = &t
+       }
+       if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventStatus, &payload); err != nil {
+               log.Error("PrepareWebhooks: %v", err)
+       }
+}
+
 func (m *webhookNotifier) SyncCreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
        m.CreateRef(ctx, pusher, repo, refFullName, refID)
 }