prow/plugins/approve/approve.go
func handle(log *logrus.Entry, ghc githubClient, repo approvers.Repo, githubConfig config.GitHubOptions, opts *plugins.Approve, pr *state) error {
	fetchErr := func(context string, err error) error {
		return fmt.Errorf("failed to get %s for %s/%s#%d: %v", context, pr.org, pr.repo, pr.number, err)
	}

	changes, err := ghc.GetPullRequestChanges(pr.org, pr.repo, pr.number)
	if err != nil {
		return fetchErr("PR file changes", err)
	}
	var filenames []string
	for _, change := range changes {
		filenames = append(filenames, change.Filename)
	}
	issueLabels, err := ghc.GetIssueLabels(pr.org, pr.repo, pr.number)
	if err != nil {
		return fetchErr("issue labels", err)
	}
	hasApprovedLabel := false
	for _, label := range issueLabels {
		if label.Name == labels.Approved {
			hasApprovedLabel = true
			break
		}
	}
	botName, err := ghc.BotName()
	if err != nil {
		return fetchErr("bot name", err)
	}
	issueComments, err := ghc.ListIssueComments(pr.org, pr.repo, pr.number)
	if err != nil {
		return fetchErr("issue comments", err)
	}
	reviewComments, err := ghc.ListPullRequestComments(pr.org, pr.repo, pr.number)
	if err != nil {
		return fetchErr("review comments", err)
	}
	reviews, err := ghc.ListReviews(pr.org, pr.repo, pr.number)
	if err != nil {
		return fetchErr("reviews", err)
	}

	approversHandler := approvers.NewApprovers(
		approvers.NewOwners(
			log,
			filenames,
			repo,
			int64(pr.number),
		),
	)
	approversHandler.AssociatedIssue, err = findAssociatedIssue(pr.body, pr.org)
	if err != nil {
		log.WithError(err).Errorf("Failed to find associated issue from PR body: %v", err)
	}
	approversHandler.RequireIssue = opts.IssueRequired
	approversHandler.ManuallyApproved = humanAddedApproved(ghc, log, pr.org, pr.repo, pr.number, botName, hasApprovedLabel)

	// Author implicitly approves their own PR if config allows it
	if opts.HasSelfApproval() {
		approversHandler.AddAuthorSelfApprover(pr.author, pr.htmlURL+"#", false)
	} else {
		// Treat the author as an assignee, and suggest them if possible
		approversHandler.AddAssignees(pr.author)
	}

	commentsFromIssueComments := commentsFromIssueComments(issueComments)
	comments := append(commentsFromReviewComments(reviewComments), commentsFromIssueComments...)
	comments = append(comments, commentsFromReviews(reviews)...)
	sort.SliceStable(comments, func(i, j int) bool {
		return comments[i].CreatedAt.Before(comments[j].CreatedAt)
	})
	approveComments := filterComments(comments, approvalMatcher(botName, opts.LgtmActsAsApprove, opts.ConsiderReviewState()))
	addApprovers(&approversHandler, approveComments, pr.author, opts.ConsiderReviewState())

	for _, user := range pr.assignees {
		approversHandler.AddAssignees(user.Login)
	}

	notifications := filterComments(commentsFromIssueComments, notificationMatcher(botName))
	latestNotification := getLast(notifications)
	newMessage := updateNotification(githubConfig.LinkURL, pr.org, pr.repo, pr.branch, latestNotification, approversHandler)
	if newMessage != nil {
		for _, notif := range notifications {
			if err := ghc.DeleteComment(pr.org, pr.repo, notif.ID); err != nil {
				log.WithError(err).Errorf("Failed to delete comment from %s/%s#%d, ID: %d.", pr.org, pr.repo, pr.number, notif.ID)
			}
		}
		if err := ghc.CreateComment(pr.org, pr.repo, pr.number, *newMessage); err != nil {
			log.WithError(err).Errorf("Failed to create comment on %s/%s#%d: %q.", pr.org, pr.repo, pr.number, *newMessage)
		}
	}

	if !approversHandler.IsApproved() {
		if hasApprovedLabel {
			if err := ghc.RemoveLabel(pr.org, pr.repo, pr.number, labels.Approved); err != nil {
				log.WithError(err).Errorf("Failed to remove %q label from %s/%s#%d.", labels.Approved, pr.org, pr.repo, pr.number)
			}
		}
	} else if !hasApprovedLabel {
		if err := ghc.AddLabel(pr.org, pr.repo, pr.number, labels.Approved); err != nil {
			log.WithError(err).Errorf("Failed to add %q label to %s/%s#%d.", labels.Approved, pr.org, pr.repo, pr.number)
		}
	}
	return nil
}
func handle(log *logrus.Entry, ghc githubClient, repo approvers.Repo, githubConfig config.GitHubOptions, opts *plugins.Approve, pr *state) error {
	fetchErr := func(context string, err error) error {
		return fmt.Errorf("failed to get %s for %s/%s#%d: %v", context, pr.org, pr.repo, pr.number, err)
	}

	changes, err := ghc.GetPullRequestChanges(pr.org, pr.repo, pr.number)
	if err != nil {
		return fetchErr("PR file changes", err)
	}
	var filenames []string
	for _, change := range changes {
		filenames = append(filenames, change.Filename)
	}
	issueLabels, err := ghc.GetIssueLabels(pr.org, pr.repo, pr.number)
	if err != nil {
		return fetchErr("issue labels", err)
	}
	hasApprovedLabel := false
	for _, label := range issueLabels {
		if label.Name == labels.Approved {
			hasApprovedLabel = true
			break
		}
	}
	botName, err := ghc.BotName()
	if err != nil {
		return fetchErr("bot name", err)
	}
	issueComments, err := ghc.ListIssueComments(pr.org, pr.repo, pr.number)
	if err != nil {
		return fetchErr("issue comments", err)
	}
	reviewComments, err := ghc.ListPullRequestComments(pr.org, pr.repo, pr.number)
	if err != nil {
		return fetchErr("review comments", err)
	}
	reviews, err := ghc.ListReviews(pr.org, pr.repo, pr.number)
	if err != nil {
		return fetchErr("reviews", err)
	}

	approversHandler := approvers.NewApprovers(
		approvers.NewOwners(
			log,
			filenames,
			repo,
			int64(pr.number),
		),
	)
	approversHandler.AssociatedIssue, err = findAssociatedIssue(pr.body, pr.org)
	if err != nil {
		log.WithError(err).Errorf("Failed to find associated issue from PR body: %v", err)
	}
	approversHandler.RequireIssue = opts.IssueRequired
	approversHandler.ManuallyApproved = humanAddedApproved(ghc, log, pr.org, pr.repo, pr.number, botName, hasApprovedLabel)

	// Author implicitly approves their own PR if config allows it
	if opts.HasSelfApproval() {
		approversHandler.AddAuthorSelfApprover(pr.author, pr.htmlURL+"#", false)
	} else {
		// Treat the author as an assignee, and suggest them if possible
		approversHandler.AddAssignees(pr.author)
	}

	commentsFromIssueComments := commentsFromIssueComments(issueComments)
	comments := append(commentsFromReviewComments(reviewComments), commentsFromIssueComments...)
	comments = append(comments, commentsFromReviews(reviews)...)
	sort.SliceStable(comments, func(i, j int) bool {
		return comments[i].CreatedAt.Before(comments[j].CreatedAt)
	})
	approveComments := filterComments(comments, approvalMatcher(botName, opts.LgtmActsAsApprove, opts.ConsiderReviewState()))
	addApprovers(&approversHandler, approveComments, pr.author, opts.ConsiderReviewState())

	for _, user := range pr.assignees {
		approversHandler.AddAssignees(user.Login)
	}

	notifications := filterComments(commentsFromIssueComments, notificationMatcher(botName))
	latestNotification := getLast(notifications)
	newMessage := updateNotification(githubConfig.LinkURL, pr.org, pr.repo, pr.branch, latestNotification, approversHandler)
	if newMessage != nil {
		for _, notif := range notifications {
			if err := ghc.DeleteComment(pr.org, pr.repo, notif.ID); err != nil {
				log.WithError(err).Errorf("Failed to delete comment from %s/%s#%d, ID: %d.", pr.org, pr.repo, pr.number, notif.ID)
			}
		}
		if err := ghc.CreateComment(pr.org, pr.repo, pr.number, *newMessage); err != nil {
			log.WithError(err).Errorf("Failed to create comment on %s/%s#%d: %q.", pr.org, pr.repo, pr.number, *newMessage)
		}
	}

	if !approversHandler.IsApproved() {
		if hasApprovedLabel {
			if err := ghc.RemoveLabel(pr.org, pr.repo, pr.number, labels.Approved); err != nil {
				log.WithError(err).Errorf("Failed to remove %q label from %s/%s#%d.", labels.Approved, pr.org, pr.repo, pr.number)
			}
		}
	} else if !hasApprovedLabel {
		if err := ghc.AddLabel(pr.org, pr.repo, pr.number, labels.Approved); err != nil {
			log.WithError(err).Errorf("Failed to add %q label to %s/%s#%d.", labels.Approved, pr.org, pr.repo, pr.number)
		}
	}
	return nil
}
func handle ( log * logrus . Entry , ghc githubClient , repo approvers . Repo , githubConfig config . GitHubOptions , opts * plugins . Approve , pr * state ) error { fetchErr := func ( context string , err error ) error { return fmt . Errorf ( " " , context , pr . org , pr . repo , pr . number , err ) } changes , err := ghc . GetPullRequestChanges ( pr . org , pr . repo , pr . number ) if err != nil { return fetchErr ( " " , err ) } var filenames [ ] string for _ , change := range changes { filenames = append ( filenames , change . Filename ) } issueLabels , err := ghc . GetIssueLabels ( pr . org , pr . repo , pr . number ) if err != nil { return fetchErr ( " " , err ) } hasApprovedLabel := false for _ , label := range issueLabels { if label . Name == labels . Approved { hasApprovedLabel = true break } } botName , err := ghc . BotName ( ) if err != nil { return fetchErr ( " " , err ) } issueComments , err := ghc . ListIssueComments ( pr . org , pr . repo , pr . number ) if err != nil { return fetchErr ( " " , err ) } reviewComments , err := ghc . ListPullRequestComments ( pr . org , pr . repo , pr . number ) if err != nil { return fetchErr ( " " , err ) } reviews , err := ghc . ListReviews ( pr . org , pr . repo , pr . number ) if err != nil { return fetchErr ( " " , err ) } approversHandler := approvers . NewApprovers ( approvers . NewOwners ( log , filenames , repo , int64 ( pr . number ) , ) , ) approversHandler . AssociatedIssue , err = findAssociatedIssue ( pr . body , pr . org ) if err != nil { log . WithError ( err ) . Errorf ( " " , err ) } approversHandler . RequireIssue = opts . IssueRequired approversHandler . ManuallyApproved = humanAddedApproved ( ghc , log , pr . org , pr . repo , pr . number , botName , hasApprovedLabel ) // Author implicitly approves their own PR if config allows it if opts . HasSelfApproval ( ) { approversHandler . AddAuthorSelfApprover ( pr . author , pr . htmlURL + " " , false ) } else { // Treat the author as an assignee, and suggest them if possible approversHandler . AddAssignees ( pr . author ) } commentsFromIssueComments := commentsFromIssueComments ( issueComments ) comments := append ( commentsFromReviewComments ( reviewComments ) , commentsFromIssueComments ... ) comments = append ( comments , commentsFromReviews ( reviews ) ... ) sort . SliceStable ( comments , func ( i , j int ) bool { return comments [ i ] . CreatedAt . Before ( comments [ j ] . CreatedAt ) } ) approveComments := filterComments ( comments , approvalMatcher ( botName , opts . LgtmActsAsApprove , opts . ConsiderReviewState ( ) ) ) addApprovers ( & approversHandler , approveComments , pr . author , opts . ConsiderReviewState ( ) ) for _ , user := range pr . assignees { approversHandler . AddAssignees ( user . Login ) } notifications := filterComments ( commentsFromIssueComments , notificationMatcher ( botName ) ) latestNotification := getLast ( notifications ) newMessage := updateNotification ( githubConfig . LinkURL , pr . org , pr . repo , pr . branch , latestNotification , approversHandler ) if newMessage != nil { for _ , notif := range notifications { if err := ghc . DeleteComment ( pr . org , pr . repo , notif . ID ) ; err != nil { log . WithError ( err ) . Errorf ( " " , pr . org , pr . repo , pr . number , notif . ID ) } } if err := ghc . CreateComment ( pr . org , pr . repo , pr . number , * newMessage ) ; err != nil { log . WithError ( err ) . Errorf ( " " , pr . org , pr . repo , pr . number , * newMessage ) } } if ! approversHandler . IsApproved ( ) { if hasApprovedLabel { if err := ghc . RemoveLabel ( pr . org , pr . repo , pr . number , labels . Approved ) ; err != nil { log . WithError ( err ) . Errorf ( " " , labels . Approved , pr . org , pr . repo , pr . number ) } } } else if ! hasApprovedLabel { if err := ghc . AddLabel ( pr . org , pr . repo , pr . number , labels . Approved ) ; err != nil { log . WithError ( err ) . Errorf ( " " , labels . Approved , pr . org , pr . repo , pr . number ) } } return nil }
// handle is the workhorse the will actually make updates to the PR.
// The algorithm goes as:
// - Initially, we build an approverSet
//   - Go through all comments in order of creation.
//     - (Issue/PR comments, PR review comments, and PR review bodies are considered as comments)
//   - If anyone said "/approve", add them to approverSet.
//   - If anyone said "/lgtm" AND LgtmActsAsApprove is enabled, add them to approverSet.
//   - If anyone created an approved review AND ReviewActsAsApprove is enabled, add them to approverSet.
// - Then, for each file, we see if any approver of this file is in approverSet and keep track of files without approval
//   - An approver of a file is defined as:
//     - Someone listed as an "approver" in an OWNERS file in the files directory OR
//     - in one of the file's parent directories
// - Iff all files have been approved, the bot will add the "approved" label.
// - Iff a cancel command is found, that reviewer will be removed from the approverSet
// 	and the munger will remove the approved label if it has been applied
handle is the workhorse the will actually make updates to the PR . The algorithm goes as : - Initially we build an approverSet - Go through all comments in order of creation . - ( Issue / PR comments PR review comments and PR review bodies are considered as comments ) - If anyone said / approve add them to approverSet . - If anyone said / lgtm AND LgtmActsAsApprove is enabled add them to approverSet . - If anyone created an approved review AND ReviewActsAsApprove is enabled add them to approverSet . - Then for each file we see if any approver of this file is in approverSet and keep track of files without approval - An approver of a file is defined as : - Someone listed as an approver in an OWNERS file in the files directory OR - in one of the file s parent directories - Iff all files have been approved the bot will add the approved label . - Iff a cancel command is found that reviewer will be removed from the approverSet and the munger will remove the approved label if it has been applied