1
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-05-18 00:49:09 -04:00

feat: notify doers of a merge when automerging ()

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>
This commit is contained in:
kolaente 2022-11-03 16:49:00 +01:00 committed by GitHub
parent f17edfaf5a
commit 085f717529
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 87 additions and 27 deletions
models/activities
modules
options/locale
routers
api/v1/repo
web
services
tests/integration

@ -64,6 +64,7 @@ const (
ActionPublishRelease // 24 ActionPublishRelease // 24
ActionPullReviewDismissed // 25 ActionPullReviewDismissed // 25
ActionPullRequestReadyForReview // 26 ActionPullRequestReadyForReview // 26
ActionAutoMergePullRequest // 27
) )
// Action represents user operation type and other information to // Action represents user operation type and other information to
@ -550,7 +551,7 @@ func notifyWatchers(ctx context.Context, actions ...*Action) error {
if !permIssue[i] { if !permIssue[i] {
continue continue
} }
case ActionCreatePullRequest, ActionCommentPull, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest: case ActionCreatePullRequest, ActionCommentPull, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest, ActionAutoMergePullRequest:
if !permPR[i] { if !permPR[i] {
continue continue
} }

@ -283,6 +283,20 @@ func (*actionNotifier) NotifyMergePullRequest(pr *issues_model.PullRequest, doer
} }
} }
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) { func (*actionNotifier) NotifyPullRevieweDismiss(doer *user_model.User, review *issues_model.Review, comment *issues_model.Comment) {
reviewerName := review.Reviewer.Name reviewerName := review.Reviewer.Name
if len(review.OriginalAuthor) > 0 { if len(review.OriginalAuthor) > 0 {

@ -34,7 +34,8 @@ type Notifier interface {
NotifyIssueChangeLabels(doer *user_model.User, issue *issues_model.Issue, NotifyIssueChangeLabels(doer *user_model.User, issue *issues_model.Issue,
addedLabels, removedLabels []*issues_model.Label) addedLabels, removedLabels []*issues_model.Label)
NotifyNewPullRequest(pr *issues_model.PullRequest, mentions []*user_model.User) NotifyNewPullRequest(pr *issues_model.PullRequest, mentions []*user_model.User)
NotifyMergePullRequest(*issues_model.PullRequest, *user_model.User) NotifyMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User)
NotifyAutoMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User)
NotifyPullRequestSynchronized(doer *user_model.User, pr *issues_model.PullRequest) NotifyPullRequestSynchronized(doer *user_model.User, pr *issues_model.PullRequest)
NotifyPullRequestReview(pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) NotifyPullRequestReview(pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User)
NotifyPullRequestCodeComment(pr *issues_model.PullRequest, comment *issues_model.Comment, mentions []*user_model.User) NotifyPullRequestCodeComment(pr *issues_model.PullRequest, comment *issues_model.Comment, mentions []*user_model.User)

@ -54,6 +54,10 @@ func (*NullNotifier) NotifyPullRequestCodeComment(pr *issues_model.PullRequest,
func (*NullNotifier) NotifyMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User) { func (*NullNotifier) NotifyMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User) {
} }
// NotifyAutoMergePullRequest places a place holder function
func (*NullNotifier) NotifyAutoMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User) {
}
// NotifyPullRequestSynchronized places a place holder function // NotifyPullRequestSynchronized places a place holder function
func (*NullNotifier) NotifyPullRequestSynchronized(doer *user_model.User, pr *issues_model.PullRequest) { func (*NullNotifier) NotifyPullRequestSynchronized(doer *user_model.User, pr *issues_model.PullRequest) {
} }

@ -153,6 +153,16 @@ func (m *mailNotifier) NotifyMergePullRequest(pr *issues_model.PullRequest, doer
} }
} }
func (m *mailNotifier) NotifyAutoMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User) {
if err := pr.LoadIssue(); err != nil {
log.Error("pr.LoadIssue: %v", err)
return
}
if err := mailer.MailParticipants(pr.Issue, doer, activities_model.ActionAutoMergePullRequest, nil); err != nil {
log.Error("MailParticipants: %v", err)
}
}
func (m *mailNotifier) NotifyPullRequestPushCommits(doer *user_model.User, pr *issues_model.PullRequest, comment *issues_model.Comment) { func (m *mailNotifier) NotifyPullRequestPushCommits(doer *user_model.User, pr *issues_model.PullRequest, comment *issues_model.Comment) {
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("mailNotifier.NotifyPullRequestPushCommits Pull[%d] #%d in [%d]", pr.ID, pr.Index, pr.BaseRepoID)) ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("mailNotifier.NotifyPullRequestPushCommits Pull[%d] #%d in [%d]", pr.ID, pr.Index, pr.BaseRepoID))
defer finished() defer finished()

@ -98,6 +98,13 @@ func NotifyMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User)
} }
} }
// NotifyAutoMergePullRequest notifies merge pull request to notifiers
func NotifyAutoMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User) {
for _, notifier := range notifiers {
notifier.NotifyAutoMergePullRequest(pr, doer)
}
}
// NotifyNewPullRequest notifies new pull request to notifiers // NotifyNewPullRequest notifies new pull request to notifiers
func NotifyNewPullRequest(pr *issues_model.PullRequest, mentions []*user_model.User) { func NotifyNewPullRequest(pr *issues_model.PullRequest, mentions []*user_model.User) {
for _, notifier := range notifiers { for _, notifier := range notifiers {

@ -119,6 +119,10 @@ func (ns *notificationService) NotifyMergePullRequest(pr *issues_model.PullReque
}) })
} }
func (ns *notificationService) NotifyAutoMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User) {
ns.NotifyMergePullRequest(pr, doer)
}
func (ns *notificationService) NotifyNewPullRequest(pr *issues_model.PullRequest, mentions []*user_model.User) { func (ns *notificationService) NotifyNewPullRequest(pr *issues_model.PullRequest, mentions []*user_model.User) {
if err := pr.LoadIssue(); err != nil { if err := pr.LoadIssue(); err != nil {
log.Error("Unable to load issue: %d for pr: %d: Error: %v", pr.IssueID, pr.ID, err) log.Error("Unable to load issue: %d for pr: %d: Error: %v", pr.IssueID, pr.ID, err)

@ -632,6 +632,11 @@ func (m *webhookNotifier) NotifyPushCommits(pusher *user_model.User, repo *repo_
} }
} }
func (m *webhookNotifier) NotifyAutoMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User) {
// just redirect to the NotifyMergePullRequest
m.NotifyMergePullRequest(pr, doer)
}
func (*webhookNotifier) NotifyMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User) { func (*webhookNotifier) NotifyMergePullRequest(pr *issues_model.PullRequest, doer *user_model.User) {
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("webhook.NotifyMergePullRequest Pull[%d] #%d in [%d]", pr.ID, pr.Index, pr.BaseRepoID)) ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("webhook.NotifyMergePullRequest Pull[%d] #%d in [%d]", pr.ID, pr.Index, pr.BaseRepoID))
defer finished() defer finished()

@ -905,7 +905,7 @@ func ActionIcon(opType activities_model.ActionType) string {
return "git-pull-request" return "git-pull-request"
case activities_model.ActionCommentIssue, activities_model.ActionCommentPull: case activities_model.ActionCommentIssue, activities_model.ActionCommentPull:
return "comment-discussion" return "comment-discussion"
case activities_model.ActionMergePullRequest: case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest:
return "git-merge" return "git-merge"
case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest: case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest:
return "issue-closed" return "issue-closed"

@ -3003,6 +3003,7 @@ reopen_pull_request = `reopened pull request <a href="%[1]s">%[3]s#%[2]s</a>`
comment_issue = `commented on issue <a href="%[1]s">%[3]s#%[2]s</a>` comment_issue = `commented on issue <a href="%[1]s">%[3]s#%[2]s</a>`
comment_pull = `commented on pull request <a href="%[1]s">%[3]s#%[2]s</a>` comment_pull = `commented on pull request <a href="%[1]s">%[3]s#%[2]s</a>`
merge_pull_request = `merged pull request <a href="%[1]s">%[3]s#%[2]s</a>` merge_pull_request = `merged pull request <a href="%[1]s">%[3]s#%[2]s</a>`
auto_merge_pull_request = `automatically merged pull request <a href="%[1]s">%[3]s#%[2]s</a>`
transfer_repo = transferred repository <code>%s</code> to <a href="%s">%s</a> transfer_repo = transferred repository <code>%s</code> to <a href="%s">%s</a>
push_tag = pushed tag <a href="%[2]s">%[3]s</a> to <a href="%[1]s">%[4]s</a> push_tag = pushed tag <a href="%[2]s">%[3]s</a> to <a href="%[1]s">%[4]s</a>
delete_tag = deleted tag %[2]s from <a href="%[1]s">%[3]s</a> delete_tag = deleted tag %[2]s from <a href="%[1]s">%[3]s</a>

@ -839,7 +839,7 @@ func MergePullRequest(ctx *context.APIContext) {
} }
} }
if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message); err != nil { if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
if models.IsErrInvalidMergeStyle(err) { if models.IsErrInvalidMergeStyle(err) {
ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do))) ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))
} else if models.IsErrMergeConflicts(err) { } else if models.IsErrMergeConflicts(err) {

@ -115,6 +115,12 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
link.Href = pullLink link.Href = pullLink
} }
title += ctx.TrHTMLEscapeArgs("action.merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath())
case activities_model.ActionAutoMergePullRequest:
pullLink := toPullLink(act)
if link.Href == "#" {
link.Href = pullLink
}
title += ctx.TrHTMLEscapeArgs("action.auto_merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath())
case activities_model.ActionCloseIssue: case activities_model.ActionCloseIssue:
issueLink := toIssueLink(act) issueLink := toIssueLink(act)
if link.Href == "#" { if link.Href == "#" {
@ -221,7 +227,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
if len(comment) != 0 { if len(comment) != 0 {
desc += "\n\n" + renderMarkdown(ctx, act, comment) desc += "\n\n" + renderMarkdown(ctx, act, comment)
} }
case activities_model.ActionMergePullRequest: case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest:
desc = act.GetIssueInfos()[1] desc = act.GetIssueInfos()[1]
case activities_model.ActionCloseIssue, activities_model.ActionReopenIssue, activities_model.ActionClosePullRequest, activities_model.ActionReopenPullRequest: case activities_model.ActionCloseIssue, activities_model.ActionReopenIssue, activities_model.ActionClosePullRequest, activities_model.ActionReopenPullRequest:
desc = act.GetIssueTitle() desc = act.GetIssueTitle()

@ -1002,7 +1002,7 @@ func MergePullRequest(ctx *context.Context) {
} }
} }
if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message); err != nil { if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
if models.IsErrInvalidMergeStyle(err) { if models.IsErrInvalidMergeStyle(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
ctx.Redirect(issue.Link()) ctx.Redirect(issue.Link())

@ -257,7 +257,7 @@ func handlePull(pullID int64, sha string) {
defer baseGitRepo.Close() defer baseGitRepo.Close()
} }
if err := pull_service.Merge(ctx, pr, doer, baseGitRepo, scheduledPRM.MergeStyle, "", scheduledPRM.Message); err != nil { if err := pull_service.Merge(ctx, pr, doer, baseGitRepo, scheduledPRM.MergeStyle, "", scheduledPRM.Message, true); err != nil {
log.Error("pull_service.Merge: %v", err) log.Error("pull_service.Merge: %v", err)
return return
} }

@ -340,7 +340,7 @@ func createReference(issue *issues_model.Issue, comment *issues_model.Comment, a
extra = fmt.Sprintf("/close/%d", time.Now().UnixNano()/1e6) extra = fmt.Sprintf("/close/%d", time.Now().UnixNano()/1e6)
case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest: case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest:
extra = fmt.Sprintf("/reopen/%d", time.Now().UnixNano()/1e6) extra = fmt.Sprintf("/reopen/%d", time.Now().UnixNano()/1e6)
case activities_model.ActionMergePullRequest: case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest:
extra = fmt.Sprintf("/merge/%d", time.Now().UnixNano()/1e6) extra = fmt.Sprintf("/merge/%d", time.Now().UnixNano()/1e6)
case activities_model.ActionPullRequestReadyForReview: case activities_model.ActionPullRequestReadyForReview:
extra = fmt.Sprintf("/ready/%d", time.Now().UnixNano()/1e6) extra = fmt.Sprintf("/ready/%d", time.Now().UnixNano()/1e6)
@ -451,7 +451,7 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act
name = "close" name = "close"
case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest: case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest:
name = "reopen" name = "reopen"
case activities_model.ActionMergePullRequest: case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest:
name = "merge" name = "merge"
case activities_model.ActionPullReviewDismissed: case activities_model.ActionPullReviewDismissed:
name = "review_dismissed" name = "review_dismissed"

@ -25,11 +25,12 @@ func fallbackMailSubject(issue *issues_model.Issue) string {
type mailCommentContext struct { type mailCommentContext struct {
context.Context context.Context
Issue *issues_model.Issue Issue *issues_model.Issue
Doer *user_model.User Doer *user_model.User
ActionType activities_model.ActionType ActionType activities_model.ActionType
Content string Content string
Comment *issues_model.Comment Comment *issues_model.Comment
ForceDoerNotification bool
} }
const ( const (
@ -93,7 +94,7 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo
visited := make(container.Set[int64], len(unfiltered)+len(mentions)+1) visited := make(container.Set[int64], len(unfiltered)+len(mentions)+1)
// Avoid mailing the doer // Avoid mailing the doer
if ctx.Doer.EmailNotificationsPreference != user_model.EmailNotificationsAndYourOwn { if ctx.Doer.EmailNotificationsPreference != user_model.EmailNotificationsAndYourOwn && !ctx.ForceDoerNotification {
visited.Add(ctx.Doer.ID) visited.Add(ctx.Doer.ID)
} }
@ -181,17 +182,19 @@ func MailParticipants(issue *issues_model.Issue, doer *user_model.User, opType a
content := issue.Content content := issue.Content
if opType == activities_model.ActionCloseIssue || opType == activities_model.ActionClosePullRequest || if opType == activities_model.ActionCloseIssue || opType == activities_model.ActionClosePullRequest ||
opType == activities_model.ActionReopenIssue || opType == activities_model.ActionReopenPullRequest || opType == activities_model.ActionReopenIssue || opType == activities_model.ActionReopenPullRequest ||
opType == activities_model.ActionMergePullRequest { opType == activities_model.ActionMergePullRequest || opType == activities_model.ActionAutoMergePullRequest {
content = "" content = ""
} }
forceDoerNotification := opType == activities_model.ActionAutoMergePullRequest
if err := mailIssueCommentToParticipants( if err := mailIssueCommentToParticipants(
&mailCommentContext{ &mailCommentContext{
Context: context.TODO(), // TODO: use a correct context Context: context.TODO(), // TODO: use a correct context
Issue: issue, Issue: issue,
Doer: doer, Doer: doer,
ActionType: opType, ActionType: opType,
Content: content, Content: content,
Comment: nil, Comment: nil,
ForceDoerNotification: forceDoerNotification,
}, mentions); err != nil { }, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err) log.Error("mailIssueCommentToParticipants: %v", err)
} }

@ -133,7 +133,7 @@ func GetDefaultMergeMessage(baseGitRepo *git.Repository, pr *issues_model.PullRe
// Merge merges pull request to base repository. // Merge merges pull request to base repository.
// Caller should check PR is ready to be merged (review and status checks) // Caller should check PR is ready to be merged (review and status checks)
func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string) error { func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, wasAutoMerged bool) error {
if err := pr.LoadHeadRepo(); err != nil { if err := pr.LoadHeadRepo(); err != nil {
log.Error("LoadHeadRepo: %v", err) log.Error("LoadHeadRepo: %v", err)
return fmt.Errorf("LoadHeadRepo: %w", err) return fmt.Errorf("LoadHeadRepo: %w", err)
@ -193,7 +193,11 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
log.Error("GetOwner for issue repo [%d]: %v", pr.ID, err) log.Error("GetOwner for issue repo [%d]: %v", pr.ID, err)
} }
notification.NotifyMergePullRequest(pr, doer) if wasAutoMerged {
notification.NotifyAutoMergePullRequest(pr, doer)
} else {
notification.NotifyMergePullRequest(pr, doer)
}
// Reset cached commit count // Reset cached commit count
cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true)) cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true))

@ -245,11 +245,11 @@ func TestCantMergeConflict(t *testing.T) {
gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name))
assert.NoError(t, err) assert.NoError(t, err)
err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "CONFLICT") err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "CONFLICT", false)
assert.Error(t, err, "Merge should return an error due to conflict") assert.Error(t, err, "Merge should return an error due to conflict")
assert.True(t, models.IsErrMergeConflicts(err), "Merge error is not a conflict error") assert.True(t, models.IsErrMergeConflicts(err), "Merge error is not a conflict error")
err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleRebase, "", "CONFLICT") err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleRebase, "", "CONFLICT", false)
assert.Error(t, err, "Merge should return an error due to conflict") assert.Error(t, err, "Merge should return an error due to conflict")
assert.True(t, models.IsErrRebaseConflicts(err), "Merge error is not a conflict error") assert.True(t, models.IsErrRebaseConflicts(err), "Merge error is not a conflict error")
gitRepo.Close() gitRepo.Close()
@ -344,7 +344,7 @@ func TestCantMergeUnrelated(t *testing.T) {
BaseBranch: "base", BaseBranch: "base",
}) })
err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "UNRELATED") err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "UNRELATED", false)
assert.Error(t, err, "Merge should return an error due to unrelated") assert.Error(t, err, "Merge should return an error due to unrelated")
assert.True(t, models.IsErrMergeUnrelatedHistories(err), "Merge error is not a unrelated histories error") assert.True(t, models.IsErrMergeUnrelatedHistories(err), "Merge error is not a unrelated histories error")
gitRepo.Close() gitRepo.Close()