code.gitea.io/gitea@v1.21.7/services/webhook/notifier.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package webhook
     5  
     6  import (
     7  	"context"
     8  
     9  	issues_model "code.gitea.io/gitea/models/issues"
    10  	packages_model "code.gitea.io/gitea/models/packages"
    11  	"code.gitea.io/gitea/models/perm"
    12  	access_model "code.gitea.io/gitea/models/perm/access"
    13  	repo_model "code.gitea.io/gitea/models/repo"
    14  	user_model "code.gitea.io/gitea/models/user"
    15  	"code.gitea.io/gitea/modules/git"
    16  	"code.gitea.io/gitea/modules/log"
    17  	"code.gitea.io/gitea/modules/repository"
    18  	"code.gitea.io/gitea/modules/setting"
    19  	api "code.gitea.io/gitea/modules/structs"
    20  	webhook_module "code.gitea.io/gitea/modules/webhook"
    21  	"code.gitea.io/gitea/services/convert"
    22  	notify_service "code.gitea.io/gitea/services/notify"
    23  )
    24  
    25  func init() {
    26  	notify_service.RegisterNotifier(&webhookNotifier{})
    27  }
    28  
    29  type webhookNotifier struct {
    30  	notify_service.NullNotifier
    31  }
    32  
    33  var _ notify_service.Notifier = &webhookNotifier{}
    34  
    35  // NewNotifier create a new webhookNotifier notifier
    36  func NewNotifier() notify_service.Notifier {
    37  	return &webhookNotifier{}
    38  }
    39  
    40  func (m *webhookNotifier) IssueClearLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) {
    41  	if err := issue.LoadPoster(ctx); err != nil {
    42  		log.Error("LoadPoster: %v", err)
    43  		return
    44  	}
    45  
    46  	if err := issue.LoadRepo(ctx); err != nil {
    47  		log.Error("LoadRepo: %v", err)
    48  		return
    49  	}
    50  
    51  	permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
    52  	var err error
    53  	if issue.IsPull {
    54  		if err = issue.LoadPullRequest(ctx); err != nil {
    55  			log.Error("LoadPullRequest: %v", err)
    56  			return
    57  		}
    58  
    59  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestLabel, &api.PullRequestPayload{
    60  			Action:      api.HookIssueLabelCleared,
    61  			Index:       issue.Index,
    62  			PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
    63  			Repository:  convert.ToRepo(ctx, issue.Repo, permission),
    64  			Sender:      convert.ToUser(ctx, doer, nil),
    65  		})
    66  	} else {
    67  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueLabel, &api.IssuePayload{
    68  			Action:     api.HookIssueLabelCleared,
    69  			Index:      issue.Index,
    70  			Issue:      convert.ToAPIIssue(ctx, issue),
    71  			Repository: convert.ToRepo(ctx, issue.Repo, permission),
    72  			Sender:     convert.ToUser(ctx, doer, nil),
    73  		})
    74  	}
    75  	if err != nil {
    76  		log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
    77  	}
    78  }
    79  
    80  func (m *webhookNotifier) ForkRepository(ctx context.Context, doer *user_model.User, oldRepo, repo *repo_model.Repository) {
    81  	oldPermission, _ := access_model.GetUserRepoPermission(ctx, oldRepo, doer)
    82  	permission, _ := access_model.GetUserRepoPermission(ctx, repo, doer)
    83  
    84  	// forked webhook
    85  	if err := PrepareWebhooks(ctx, EventSource{Repository: oldRepo}, webhook_module.HookEventFork, &api.ForkPayload{
    86  		Forkee: convert.ToRepo(ctx, oldRepo, oldPermission),
    87  		Repo:   convert.ToRepo(ctx, repo, permission),
    88  		Sender: convert.ToUser(ctx, doer, nil),
    89  	}); err != nil {
    90  		log.Error("PrepareWebhooks [repo_id: %d]: %v", oldRepo.ID, err)
    91  	}
    92  
    93  	u := repo.MustOwner(ctx)
    94  
    95  	// Add to hook queue for created repo after session commit.
    96  	if u.IsOrganization() {
    97  		if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventRepository, &api.RepositoryPayload{
    98  			Action:       api.HookRepoCreated,
    99  			Repository:   convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
   100  			Organization: convert.ToUser(ctx, u, nil),
   101  			Sender:       convert.ToUser(ctx, doer, nil),
   102  		}); err != nil {
   103  			log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
   104  		}
   105  	}
   106  }
   107  
   108  func (m *webhookNotifier) CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository) {
   109  	// Add to hook queue for created repo after session commit.
   110  	if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventRepository, &api.RepositoryPayload{
   111  		Action:       api.HookRepoCreated,
   112  		Repository:   convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
   113  		Organization: convert.ToUser(ctx, u, nil),
   114  		Sender:       convert.ToUser(ctx, doer, nil),
   115  	}); err != nil {
   116  		log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
   117  	}
   118  }
   119  
   120  func (m *webhookNotifier) DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository) {
   121  	if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventRepository, &api.RepositoryPayload{
   122  		Action:       api.HookRepoDeleted,
   123  		Repository:   convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
   124  		Organization: convert.ToUser(ctx, repo.MustOwner(ctx), nil),
   125  		Sender:       convert.ToUser(ctx, doer, nil),
   126  	}); err != nil {
   127  		log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
   128  	}
   129  }
   130  
   131  func (m *webhookNotifier) MigrateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository) {
   132  	// Add to hook queue for created repo after session commit.
   133  	if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventRepository, &api.RepositoryPayload{
   134  		Action:       api.HookRepoCreated,
   135  		Repository:   convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
   136  		Organization: convert.ToUser(ctx, u, nil),
   137  		Sender:       convert.ToUser(ctx, doer, nil),
   138  	}); err != nil {
   139  		log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
   140  	}
   141  }
   142  
   143  func (m *webhookNotifier) IssueChangeAssignee(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, assignee *user_model.User, removed bool, comment *issues_model.Comment) {
   144  	if issue.IsPull {
   145  		permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
   146  
   147  		if err := issue.LoadPullRequest(ctx); err != nil {
   148  			log.Error("LoadPullRequest failed: %v", err)
   149  			return
   150  		}
   151  		apiPullRequest := &api.PullRequestPayload{
   152  			Index:       issue.Index,
   153  			PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
   154  			Repository:  convert.ToRepo(ctx, issue.Repo, permission),
   155  			Sender:      convert.ToUser(ctx, doer, nil),
   156  		}
   157  		if removed {
   158  			apiPullRequest.Action = api.HookIssueUnassigned
   159  		} else {
   160  			apiPullRequest.Action = api.HookIssueAssigned
   161  		}
   162  		// Assignee comment triggers a webhook
   163  		if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestAssign, apiPullRequest); err != nil {
   164  			log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
   165  			return
   166  		}
   167  	} else {
   168  		permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
   169  		apiIssue := &api.IssuePayload{
   170  			Index:      issue.Index,
   171  			Issue:      convert.ToAPIIssue(ctx, issue),
   172  			Repository: convert.ToRepo(ctx, issue.Repo, permission),
   173  			Sender:     convert.ToUser(ctx, doer, nil),
   174  		}
   175  		if removed {
   176  			apiIssue.Action = api.HookIssueUnassigned
   177  		} else {
   178  			apiIssue.Action = api.HookIssueAssigned
   179  		}
   180  		// Assignee comment triggers a webhook
   181  		if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueAssign, apiIssue); err != nil {
   182  			log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
   183  			return
   184  		}
   185  	}
   186  }
   187  
   188  func (m *webhookNotifier) IssueChangeTitle(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldTitle string) {
   189  	permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
   190  	var err error
   191  	if issue.IsPull {
   192  		if err = issue.LoadPullRequest(ctx); err != nil {
   193  			log.Error("LoadPullRequest failed: %v", err)
   194  			return
   195  		}
   196  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequest, &api.PullRequestPayload{
   197  			Action: api.HookIssueEdited,
   198  			Index:  issue.Index,
   199  			Changes: &api.ChangesPayload{
   200  				Title: &api.ChangesFromPayload{
   201  					From: oldTitle,
   202  				},
   203  			},
   204  			PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
   205  			Repository:  convert.ToRepo(ctx, issue.Repo, permission),
   206  			Sender:      convert.ToUser(ctx, doer, nil),
   207  		})
   208  	} else {
   209  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssues, &api.IssuePayload{
   210  			Action: api.HookIssueEdited,
   211  			Index:  issue.Index,
   212  			Changes: &api.ChangesPayload{
   213  				Title: &api.ChangesFromPayload{
   214  					From: oldTitle,
   215  				},
   216  			},
   217  			Issue:      convert.ToAPIIssue(ctx, issue),
   218  			Repository: convert.ToRepo(ctx, issue.Repo, permission),
   219  			Sender:     convert.ToUser(ctx, doer, nil),
   220  		})
   221  	}
   222  
   223  	if err != nil {
   224  		log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
   225  	}
   226  }
   227  
   228  func (m *webhookNotifier) IssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, actionComment *issues_model.Comment, isClosed bool) {
   229  	permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
   230  	var err error
   231  	if issue.IsPull {
   232  		if err = issue.LoadPullRequest(ctx); err != nil {
   233  			log.Error("LoadPullRequest: %v", err)
   234  			return
   235  		}
   236  		// Merge pull request calls issue.changeStatus so we need to handle separately.
   237  		apiPullRequest := &api.PullRequestPayload{
   238  			Index:       issue.Index,
   239  			PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
   240  			Repository:  convert.ToRepo(ctx, issue.Repo, permission),
   241  			Sender:      convert.ToUser(ctx, doer, nil),
   242  			CommitID:    commitID,
   243  		}
   244  		if isClosed {
   245  			apiPullRequest.Action = api.HookIssueClosed
   246  		} else {
   247  			apiPullRequest.Action = api.HookIssueReOpened
   248  		}
   249  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequest, apiPullRequest)
   250  	} else {
   251  		apiIssue := &api.IssuePayload{
   252  			Index:      issue.Index,
   253  			Issue:      convert.ToAPIIssue(ctx, issue),
   254  			Repository: convert.ToRepo(ctx, issue.Repo, permission),
   255  			Sender:     convert.ToUser(ctx, doer, nil),
   256  			CommitID:   commitID,
   257  		}
   258  		if isClosed {
   259  			apiIssue.Action = api.HookIssueClosed
   260  		} else {
   261  			apiIssue.Action = api.HookIssueReOpened
   262  		}
   263  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssues, apiIssue)
   264  	}
   265  	if err != nil {
   266  		log.Error("PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err)
   267  	}
   268  }
   269  
   270  func (m *webhookNotifier) NewIssue(ctx context.Context, issue *issues_model.Issue, mentions []*user_model.User) {
   271  	if err := issue.LoadRepo(ctx); err != nil {
   272  		log.Error("issue.LoadRepo: %v", err)
   273  		return
   274  	}
   275  	if err := issue.LoadPoster(ctx); err != nil {
   276  		log.Error("issue.LoadPoster: %v", err)
   277  		return
   278  	}
   279  
   280  	permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
   281  	if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssues, &api.IssuePayload{
   282  		Action:     api.HookIssueOpened,
   283  		Index:      issue.Index,
   284  		Issue:      convert.ToAPIIssue(ctx, issue),
   285  		Repository: convert.ToRepo(ctx, issue.Repo, permission),
   286  		Sender:     convert.ToUser(ctx, issue.Poster, nil),
   287  	}); err != nil {
   288  		log.Error("PrepareWebhooks: %v", err)
   289  	}
   290  }
   291  
   292  func (m *webhookNotifier) NewPullRequest(ctx context.Context, pull *issues_model.PullRequest, mentions []*user_model.User) {
   293  	if err := pull.LoadIssue(ctx); err != nil {
   294  		log.Error("pull.LoadIssue: %v", err)
   295  		return
   296  	}
   297  	if err := pull.Issue.LoadRepo(ctx); err != nil {
   298  		log.Error("pull.Issue.LoadRepo: %v", err)
   299  		return
   300  	}
   301  	if err := pull.Issue.LoadPoster(ctx); err != nil {
   302  		log.Error("pull.Issue.LoadPoster: %v", err)
   303  		return
   304  	}
   305  
   306  	permission, _ := access_model.GetUserRepoPermission(ctx, pull.Issue.Repo, pull.Issue.Poster)
   307  	if err := PrepareWebhooks(ctx, EventSource{Repository: pull.Issue.Repo}, webhook_module.HookEventPullRequest, &api.PullRequestPayload{
   308  		Action:      api.HookIssueOpened,
   309  		Index:       pull.Issue.Index,
   310  		PullRequest: convert.ToAPIPullRequest(ctx, pull, nil),
   311  		Repository:  convert.ToRepo(ctx, pull.Issue.Repo, permission),
   312  		Sender:      convert.ToUser(ctx, pull.Issue.Poster, nil),
   313  	}); err != nil {
   314  		log.Error("PrepareWebhooks: %v", err)
   315  	}
   316  }
   317  
   318  func (m *webhookNotifier) IssueChangeContent(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldContent string) {
   319  	if err := issue.LoadRepo(ctx); err != nil {
   320  		log.Error("LoadRepo: %v", err)
   321  		return
   322  	}
   323  
   324  	permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
   325  	var err error
   326  	if issue.IsPull {
   327  		if err := issue.LoadPullRequest(ctx); err != nil {
   328  			log.Error("LoadPullRequest: %v", err)
   329  			return
   330  		}
   331  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequest, &api.PullRequestPayload{
   332  			Action: api.HookIssueEdited,
   333  			Index:  issue.Index,
   334  			Changes: &api.ChangesPayload{
   335  				Body: &api.ChangesFromPayload{
   336  					From: oldContent,
   337  				},
   338  			},
   339  			PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
   340  			Repository:  convert.ToRepo(ctx, issue.Repo, permission),
   341  			Sender:      convert.ToUser(ctx, doer, nil),
   342  		})
   343  	} else {
   344  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssues, &api.IssuePayload{
   345  			Action: api.HookIssueEdited,
   346  			Index:  issue.Index,
   347  			Changes: &api.ChangesPayload{
   348  				Body: &api.ChangesFromPayload{
   349  					From: oldContent,
   350  				},
   351  			},
   352  			Issue:      convert.ToAPIIssue(ctx, issue),
   353  			Repository: convert.ToRepo(ctx, issue.Repo, permission),
   354  			Sender:     convert.ToUser(ctx, doer, nil),
   355  		})
   356  	}
   357  	if err != nil {
   358  		log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
   359  	}
   360  }
   361  
   362  func (m *webhookNotifier) UpdateComment(ctx context.Context, doer *user_model.User, c *issues_model.Comment, oldContent string) {
   363  	if err := c.LoadPoster(ctx); err != nil {
   364  		log.Error("LoadPoster: %v", err)
   365  		return
   366  	}
   367  	if err := c.LoadIssue(ctx); err != nil {
   368  		log.Error("LoadIssue: %v", err)
   369  		return
   370  	}
   371  
   372  	if err := c.Issue.LoadAttributes(ctx); err != nil {
   373  		log.Error("LoadAttributes: %v", err)
   374  		return
   375  	}
   376  
   377  	var eventType webhook_module.HookEventType
   378  	if c.Issue.IsPull {
   379  		eventType = webhook_module.HookEventPullRequestComment
   380  	} else {
   381  		eventType = webhook_module.HookEventIssueComment
   382  	}
   383  
   384  	permission, _ := access_model.GetUserRepoPermission(ctx, c.Issue.Repo, doer)
   385  	if err := PrepareWebhooks(ctx, EventSource{Repository: c.Issue.Repo}, eventType, &api.IssueCommentPayload{
   386  		Action:  api.HookIssueCommentEdited,
   387  		Issue:   convert.ToAPIIssue(ctx, c.Issue),
   388  		Comment: convert.ToAPIComment(ctx, c.Issue.Repo, c),
   389  		Changes: &api.ChangesPayload{
   390  			Body: &api.ChangesFromPayload{
   391  				From: oldContent,
   392  			},
   393  		},
   394  		Repository: convert.ToRepo(ctx, c.Issue.Repo, permission),
   395  		Sender:     convert.ToUser(ctx, doer, nil),
   396  		IsPull:     c.Issue.IsPull,
   397  	}); err != nil {
   398  		log.Error("PrepareWebhooks [comment_id: %d]: %v", c.ID, err)
   399  	}
   400  }
   401  
   402  func (m *webhookNotifier) CreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository,
   403  	issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User,
   404  ) {
   405  	var eventType webhook_module.HookEventType
   406  	if issue.IsPull {
   407  		eventType = webhook_module.HookEventPullRequestComment
   408  	} else {
   409  		eventType = webhook_module.HookEventIssueComment
   410  	}
   411  
   412  	permission, _ := access_model.GetUserRepoPermission(ctx, repo, doer)
   413  	if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, eventType, &api.IssueCommentPayload{
   414  		Action:     api.HookIssueCommentCreated,
   415  		Issue:      convert.ToAPIIssue(ctx, issue),
   416  		Comment:    convert.ToAPIComment(ctx, repo, comment),
   417  		Repository: convert.ToRepo(ctx, repo, permission),
   418  		Sender:     convert.ToUser(ctx, doer, nil),
   419  		IsPull:     issue.IsPull,
   420  	}); err != nil {
   421  		log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
   422  	}
   423  }
   424  
   425  func (m *webhookNotifier) DeleteComment(ctx context.Context, doer *user_model.User, comment *issues_model.Comment) {
   426  	var err error
   427  
   428  	if err = comment.LoadPoster(ctx); err != nil {
   429  		log.Error("LoadPoster: %v", err)
   430  		return
   431  	}
   432  	if err = comment.LoadIssue(ctx); err != nil {
   433  		log.Error("LoadIssue: %v", err)
   434  		return
   435  	}
   436  
   437  	if err = comment.Issue.LoadAttributes(ctx); err != nil {
   438  		log.Error("LoadAttributes: %v", err)
   439  		return
   440  	}
   441  
   442  	var eventType webhook_module.HookEventType
   443  	if comment.Issue.IsPull {
   444  		eventType = webhook_module.HookEventPullRequestComment
   445  	} else {
   446  		eventType = webhook_module.HookEventIssueComment
   447  	}
   448  
   449  	permission, _ := access_model.GetUserRepoPermission(ctx, comment.Issue.Repo, doer)
   450  	if err := PrepareWebhooks(ctx, EventSource{Repository: comment.Issue.Repo}, eventType, &api.IssueCommentPayload{
   451  		Action:     api.HookIssueCommentDeleted,
   452  		Issue:      convert.ToAPIIssue(ctx, comment.Issue),
   453  		Comment:    convert.ToAPIComment(ctx, comment.Issue.Repo, comment),
   454  		Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission),
   455  		Sender:     convert.ToUser(ctx, doer, nil),
   456  		IsPull:     comment.Issue.IsPull,
   457  	}); err != nil {
   458  		log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
   459  	}
   460  }
   461  
   462  func (m *webhookNotifier) NewWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) {
   463  	// Add to hook queue for created wiki page.
   464  	if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventWiki, &api.WikiPayload{
   465  		Action:     api.HookWikiCreated,
   466  		Repository: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
   467  		Sender:     convert.ToUser(ctx, doer, nil),
   468  		Page:       page,
   469  		Comment:    comment,
   470  	}); err != nil {
   471  		log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
   472  	}
   473  }
   474  
   475  func (m *webhookNotifier) EditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) {
   476  	// Add to hook queue for edit wiki page.
   477  	if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventWiki, &api.WikiPayload{
   478  		Action:     api.HookWikiEdited,
   479  		Repository: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
   480  		Sender:     convert.ToUser(ctx, doer, nil),
   481  		Page:       page,
   482  		Comment:    comment,
   483  	}); err != nil {
   484  		log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
   485  	}
   486  }
   487  
   488  func (m *webhookNotifier) DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page string) {
   489  	// Add to hook queue for edit wiki page.
   490  	if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventWiki, &api.WikiPayload{
   491  		Action:     api.HookWikiDeleted,
   492  		Repository: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
   493  		Sender:     convert.ToUser(ctx, doer, nil),
   494  		Page:       page,
   495  	}); err != nil {
   496  		log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
   497  	}
   498  }
   499  
   500  func (m *webhookNotifier) IssueChangeLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue,
   501  	addedLabels, removedLabels []*issues_model.Label,
   502  ) {
   503  	var err error
   504  
   505  	if err = issue.LoadRepo(ctx); err != nil {
   506  		log.Error("LoadRepo: %v", err)
   507  		return
   508  	}
   509  
   510  	if err = issue.LoadPoster(ctx); err != nil {
   511  		log.Error("LoadPoster: %v", err)
   512  		return
   513  	}
   514  
   515  	permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
   516  	if issue.IsPull {
   517  		if err = issue.LoadPullRequest(ctx); err != nil {
   518  			log.Error("loadPullRequest: %v", err)
   519  			return
   520  		}
   521  		if err = issue.PullRequest.LoadIssue(ctx); err != nil {
   522  			log.Error("LoadIssue: %v", err)
   523  			return
   524  		}
   525  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestLabel, &api.PullRequestPayload{
   526  			Action:      api.HookIssueLabelUpdated,
   527  			Index:       issue.Index,
   528  			PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
   529  			Repository:  convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
   530  			Sender:      convert.ToUser(ctx, doer, nil),
   531  		})
   532  	} else {
   533  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueLabel, &api.IssuePayload{
   534  			Action:     api.HookIssueLabelUpdated,
   535  			Index:      issue.Index,
   536  			Issue:      convert.ToAPIIssue(ctx, issue),
   537  			Repository: convert.ToRepo(ctx, issue.Repo, permission),
   538  			Sender:     convert.ToUser(ctx, doer, nil),
   539  		})
   540  	}
   541  	if err != nil {
   542  		log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
   543  	}
   544  }
   545  
   546  func (m *webhookNotifier) IssueChangeMilestone(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldMilestoneID int64) {
   547  	var hookAction api.HookIssueAction
   548  	var err error
   549  	if issue.MilestoneID > 0 {
   550  		hookAction = api.HookIssueMilestoned
   551  	} else {
   552  		hookAction = api.HookIssueDemilestoned
   553  	}
   554  
   555  	if err = issue.LoadAttributes(ctx); err != nil {
   556  		log.Error("issue.LoadAttributes failed: %v", err)
   557  		return
   558  	}
   559  
   560  	permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
   561  	if issue.IsPull {
   562  		err = issue.PullRequest.LoadIssue(ctx)
   563  		if err != nil {
   564  			log.Error("LoadIssue: %v", err)
   565  			return
   566  		}
   567  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestMilestone, &api.PullRequestPayload{
   568  			Action:      hookAction,
   569  			Index:       issue.Index,
   570  			PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
   571  			Repository:  convert.ToRepo(ctx, issue.Repo, permission),
   572  			Sender:      convert.ToUser(ctx, doer, nil),
   573  		})
   574  	} else {
   575  		err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueMilestone, &api.IssuePayload{
   576  			Action:     hookAction,
   577  			Index:      issue.Index,
   578  			Issue:      convert.ToAPIIssue(ctx, issue),
   579  			Repository: convert.ToRepo(ctx, issue.Repo, permission),
   580  			Sender:     convert.ToUser(ctx, doer, nil),
   581  		})
   582  	}
   583  	if err != nil {
   584  		log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
   585  	}
   586  }
   587  
   588  func (m *webhookNotifier) PushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
   589  	apiPusher := convert.ToUser(ctx, pusher, nil)
   590  	apiCommits, apiHeadCommit, err := commits.ToAPIPayloadCommits(ctx, repo.RepoPath(), repo.HTMLURL())
   591  	if err != nil {
   592  		log.Error("commits.ToAPIPayloadCommits failed: %v", err)
   593  		return
   594  	}
   595  
   596  	if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventPush, &api.PushPayload{
   597  		Ref:          opts.RefFullName.String(),
   598  		Before:       opts.OldCommitID,
   599  		After:        opts.NewCommitID,
   600  		CompareURL:   setting.AppURL + commits.CompareURL,
   601  		Commits:      apiCommits,
   602  		TotalCommits: commits.Len,
   603  		HeadCommit:   apiHeadCommit,
   604  		Repo:         convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
   605  		Pusher:       apiPusher,
   606  		Sender:       apiPusher,
   607  	}); err != nil {
   608  		log.Error("PrepareWebhooks: %v", err)
   609  	}
   610  }
   611  
   612  func (m *webhookNotifier) AutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
   613  	// just redirect to the MergePullRequest
   614  	m.MergePullRequest(ctx, doer, pr)
   615  }
   616  
   617  func (*webhookNotifier) MergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
   618  	// Reload pull request information.
   619  	if err := pr.LoadAttributes(ctx); err != nil {
   620  		log.Error("LoadAttributes: %v", err)
   621  		return
   622  	}
   623  
   624  	if err := pr.LoadIssue(ctx); err != nil {
   625  		log.Error("LoadIssue: %v", err)
   626  		return
   627  	}
   628  
   629  	if err := pr.Issue.LoadRepo(ctx); err != nil {
   630  		log.Error("pr.Issue.LoadRepo: %v", err)
   631  		return
   632  	}
   633  
   634  	permission, err := access_model.GetUserRepoPermission(ctx, pr.Issue.Repo, doer)
   635  	if err != nil {
   636  		log.Error("models.GetUserRepoPermission: %v", err)
   637  		return
   638  	}
   639  
   640  	// Merge pull request calls issue.changeStatus so we need to handle separately.
   641  	apiPullRequest := &api.PullRequestPayload{
   642  		Index:       pr.Issue.Index,
   643  		PullRequest: convert.ToAPIPullRequest(ctx, pr, nil),
   644  		Repository:  convert.ToRepo(ctx, pr.Issue.Repo, permission),
   645  		Sender:      convert.ToUser(ctx, doer, nil),
   646  		Action:      api.HookIssueClosed,
   647  	}
   648  
   649  	if err := PrepareWebhooks(ctx, EventSource{Repository: pr.Issue.Repo}, webhook_module.HookEventPullRequest, apiPullRequest); err != nil {
   650  		log.Error("PrepareWebhooks: %v", err)
   651  	}
   652  }
   653  
   654  func (m *webhookNotifier) PullRequestChangeTargetBranch(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest, oldBranch string) {
   655  	if err := pr.LoadIssue(ctx); err != nil {
   656  		log.Error("LoadIssue: %v", err)
   657  		return
   658  	}
   659  
   660  	issue := pr.Issue
   661  
   662  	mode, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
   663  	if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequest, &api.PullRequestPayload{
   664  		Action: api.HookIssueEdited,
   665  		Index:  issue.Index,
   666  		Changes: &api.ChangesPayload{
   667  			Ref: &api.ChangesFromPayload{
   668  				From: oldBranch,
   669  			},
   670  		},
   671  		PullRequest: convert.ToAPIPullRequest(ctx, pr, nil),
   672  		Repository:  convert.ToRepo(ctx, issue.Repo, mode),
   673  		Sender:      convert.ToUser(ctx, doer, nil),
   674  	}); err != nil {
   675  		log.Error("PrepareWebhooks [pr: %d]: %v", pr.ID, err)
   676  	}
   677  }
   678  
   679  func (m *webhookNotifier) PullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
   680  	var reviewHookType webhook_module.HookEventType
   681  
   682  	switch review.Type {
   683  	case issues_model.ReviewTypeApprove:
   684  		reviewHookType = webhook_module.HookEventPullRequestReviewApproved
   685  	case issues_model.ReviewTypeComment:
   686  		reviewHookType = webhook_module.HookEventPullRequestReviewComment
   687  	case issues_model.ReviewTypeReject:
   688  		reviewHookType = webhook_module.HookEventPullRequestReviewRejected
   689  	default:
   690  		// unsupported review webhook type here
   691  		log.Error("Unsupported review webhook type")
   692  		return
   693  	}
   694  
   695  	if err := pr.LoadIssue(ctx); err != nil {
   696  		log.Error("LoadIssue: %v", err)
   697  		return
   698  	}
   699  
   700  	permission, err := access_model.GetUserRepoPermission(ctx, review.Issue.Repo, review.Issue.Poster)
   701  	if err != nil {
   702  		log.Error("models.GetUserRepoPermission: %v", err)
   703  		return
   704  	}
   705  	if err := PrepareWebhooks(ctx, EventSource{Repository: review.Issue.Repo}, reviewHookType, &api.PullRequestPayload{
   706  		Action:      api.HookIssueReviewed,
   707  		Index:       review.Issue.Index,
   708  		PullRequest: convert.ToAPIPullRequest(ctx, pr, nil),
   709  		Repository:  convert.ToRepo(ctx, review.Issue.Repo, permission),
   710  		Sender:      convert.ToUser(ctx, review.Reviewer, nil),
   711  		Review: &api.ReviewPayload{
   712  			Type:    string(reviewHookType),
   713  			Content: review.Content,
   714  		},
   715  	}); err != nil {
   716  		log.Error("PrepareWebhooks: %v", err)
   717  	}
   718  }
   719  
   720  func (m *webhookNotifier) PullRequestReviewRequest(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, reviewer *user_model.User, isRequest bool, comment *issues_model.Comment) {
   721  	if !issue.IsPull {
   722  		log.Warn("PullRequestReviewRequest: issue is not a pull request: %v", issue.ID)
   723  		return
   724  	}
   725  	permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
   726  	if err := issue.LoadPullRequest(ctx); err != nil {
   727  		log.Error("LoadPullRequest failed: %v", err)
   728  		return
   729  	}
   730  	apiPullRequest := &api.PullRequestPayload{
   731  		Index:             issue.Index,
   732  		PullRequest:       convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
   733  		RequestedReviewer: convert.ToUser(ctx, reviewer, nil),
   734  		Repository:        convert.ToRepo(ctx, issue.Repo, permission),
   735  		Sender:            convert.ToUser(ctx, doer, nil),
   736  	}
   737  	if isRequest {
   738  		apiPullRequest.Action = api.HookIssueReviewRequested
   739  	} else {
   740  		apiPullRequest.Action = api.HookIssueReviewRequestRemoved
   741  	}
   742  	if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestReviewRequest, apiPullRequest); err != nil {
   743  		log.Error("PrepareWebhooks [review_requested: %v]: %v", isRequest, err)
   744  		return
   745  	}
   746  }
   747  
   748  func (m *webhookNotifier) CreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
   749  	apiPusher := convert.ToUser(ctx, pusher, nil)
   750  	apiRepo := convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeNone})
   751  	refName := refFullName.ShortName()
   752  
   753  	if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventCreate, &api.CreatePayload{
   754  		Ref:     refName, // FIXME: should it be a full ref name?
   755  		Sha:     refID,
   756  		RefType: refFullName.RefType(),
   757  		Repo:    apiRepo,
   758  		Sender:  apiPusher,
   759  	}); err != nil {
   760  		log.Error("PrepareWebhooks: %v", err)
   761  	}
   762  }
   763  
   764  func (m *webhookNotifier) PullRequestSynchronized(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
   765  	if err := pr.LoadIssue(ctx); err != nil {
   766  		log.Error("LoadIssue: %v", err)
   767  		return
   768  	}
   769  	if err := pr.Issue.LoadAttributes(ctx); err != nil {
   770  		log.Error("LoadAttributes: %v", err)
   771  		return
   772  	}
   773  
   774  	if err := PrepareWebhooks(ctx, EventSource{Repository: pr.Issue.Repo}, webhook_module.HookEventPullRequestSync, &api.PullRequestPayload{
   775  		Action:      api.HookIssueSynchronized,
   776  		Index:       pr.Issue.Index,
   777  		PullRequest: convert.ToAPIPullRequest(ctx, pr, nil),
   778  		Repository:  convert.ToRepo(ctx, pr.Issue.Repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
   779  		Sender:      convert.ToUser(ctx, doer, nil),
   780  	}); err != nil {
   781  		log.Error("PrepareWebhooks [pull_id: %v]: %v", pr.ID, err)
   782  	}
   783  }
   784  
   785  func (m *webhookNotifier) DeleteRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName) {
   786  	apiPusher := convert.ToUser(ctx, pusher, nil)
   787  	apiRepo := convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner})
   788  	refName := refFullName.ShortName()
   789  
   790  	if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventDelete, &api.DeletePayload{
   791  		Ref:        refName, // FIXME: should it be a full ref name?
   792  		RefType:    refFullName.RefType(),
   793  		PusherType: api.PusherTypeUser,
   794  		Repo:       apiRepo,
   795  		Sender:     apiPusher,
   796  	}); err != nil {
   797  		log.Error("PrepareWebhooks.(delete %s): %v", refFullName.RefType(), err)
   798  	}
   799  }
   800  
   801  func sendReleaseHook(ctx context.Context, doer *user_model.User, rel *repo_model.Release, action api.HookReleaseAction) {
   802  	if err := rel.LoadAttributes(ctx); err != nil {
   803  		log.Error("LoadAttributes: %v", err)
   804  		return
   805  	}
   806  
   807  	permission, _ := access_model.GetUserRepoPermission(ctx, rel.Repo, doer)
   808  	if err := PrepareWebhooks(ctx, EventSource{Repository: rel.Repo}, webhook_module.HookEventRelease, &api.ReleasePayload{
   809  		Action:     action,
   810  		Release:    convert.ToAPIRelease(ctx, rel.Repo, rel),
   811  		Repository: convert.ToRepo(ctx, rel.Repo, permission),
   812  		Sender:     convert.ToUser(ctx, doer, nil),
   813  	}); err != nil {
   814  		log.Error("PrepareWebhooks: %v", err)
   815  	}
   816  }
   817  
   818  func (m *webhookNotifier) NewRelease(ctx context.Context, rel *repo_model.Release) {
   819  	sendReleaseHook(ctx, rel.Publisher, rel, api.HookReleasePublished)
   820  }
   821  
   822  func (m *webhookNotifier) UpdateRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) {
   823  	sendReleaseHook(ctx, doer, rel, api.HookReleaseUpdated)
   824  }
   825  
   826  func (m *webhookNotifier) DeleteRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) {
   827  	sendReleaseHook(ctx, doer, rel, api.HookReleaseDeleted)
   828  }
   829  
   830  func (m *webhookNotifier) SyncPushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
   831  	apiPusher := convert.ToUser(ctx, pusher, nil)
   832  	apiCommits, apiHeadCommit, err := commits.ToAPIPayloadCommits(ctx, repo.RepoPath(), repo.HTMLURL())
   833  	if err != nil {
   834  		log.Error("commits.ToAPIPayloadCommits failed: %v", err)
   835  		return
   836  	}
   837  
   838  	if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventPush, &api.PushPayload{
   839  		Ref:          opts.RefFullName.String(),
   840  		Before:       opts.OldCommitID,
   841  		After:        opts.NewCommitID,
   842  		CompareURL:   setting.AppURL + commits.CompareURL,
   843  		Commits:      apiCommits,
   844  		TotalCommits: commits.Len,
   845  		HeadCommit:   apiHeadCommit,
   846  		Repo:         convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
   847  		Pusher:       apiPusher,
   848  		Sender:       apiPusher,
   849  	}); err != nil {
   850  		log.Error("PrepareWebhooks: %v", err)
   851  	}
   852  }
   853  
   854  func (m *webhookNotifier) SyncCreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
   855  	m.CreateRef(ctx, pusher, repo, refFullName, refID)
   856  }
   857  
   858  func (m *webhookNotifier) SyncDeleteRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName) {
   859  	m.DeleteRef(ctx, pusher, repo, refFullName)
   860  }
   861  
   862  func (m *webhookNotifier) PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
   863  	notifyPackage(ctx, doer, pd, api.HookPackageCreated)
   864  }
   865  
   866  func (m *webhookNotifier) PackageDelete(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
   867  	notifyPackage(ctx, doer, pd, api.HookPackageDeleted)
   868  }
   869  
   870  func notifyPackage(ctx context.Context, sender *user_model.User, pd *packages_model.PackageDescriptor, action api.HookPackageAction) {
   871  	source := EventSource{
   872  		Repository: pd.Repository,
   873  		Owner:      pd.Owner,
   874  	}
   875  
   876  	apiPackage, err := convert.ToPackage(ctx, pd, sender)
   877  	if err != nil {
   878  		log.Error("Error converting package: %v", err)
   879  		return
   880  	}
   881  
   882  	if err := PrepareWebhooks(ctx, source, webhook_module.HookEventPackage, &api.PackagePayload{
   883  		Action:  action,
   884  		Package: apiPackage,
   885  		Sender:  convert.ToUser(ctx, sender, nil),
   886  	}); err != nil {
   887  		log.Error("PrepareWebhooks: %v", err)
   888  	}
   889  }