diff --git a/models/issues/comment.go b/models/issues/comment.go index 69d43f16d1..de0d2fd0f4 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -277,6 +277,15 @@ func NewProjectWorkflowDoer(title string, workflowID int64, workflowEvent projec } } +// IsProjectWorkflowDoer reports whether the doer is the virtual project workflow actor. +func IsProjectWorkflowDoer(doer *user_model.User) bool { + if doer == nil { + return false + } + _, ok := doer.ExtDoerData.(*projectWorkflowDoer) + return ok +} + // Comment represents a comment in commit and issue page. type Comment struct { ID int64 `xorm:"pk autoincr"` diff --git a/models/issues/comment_meta_test.go b/models/issues/comment_meta_test.go index 59e139382d..9f2351f895 100644 --- a/models/issues/comment_meta_test.go +++ b/models/issues/comment_meta_test.go @@ -67,4 +67,7 @@ func TestBuildCreateCommentMetaData(t *testing.T) { } meta = buildCreateCommentMetaData(&CreateCommentOptions{Doer: nilMetaDoer}) assert.Nil(t, meta, "value-type projectWorkflowDoer must not match *projectWorkflowDoer type assertion") + assert.True(t, IsProjectWorkflowDoer(workflowDoer)) + assert.False(t, IsProjectWorkflowDoer(nilMetaDoer)) + assert.False(t, IsProjectWorkflowDoer(&user_model.User{ID: 1})) } diff --git a/services/projects/workflow_notifier.go b/services/projects/workflow_notifier.go index cb1f623ca7..77486ef208 100644 --- a/services/projects/workflow_notifier.go +++ b/services/projects/workflow_notifier.go @@ -73,7 +73,7 @@ func (m *workflowNotifier) NewPullRequest(ctx context.Context, pr *issues_model. func (m *workflowNotifier) IssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, actionComment *issues_model.Comment, isClosed bool) { // Skip state changes triggered by workflow actions to prevent cascade loops // (same guard as feed/notifier.go). - if doer.ExtDoerData != nil { + if issues_model.IsProjectWorkflowDoer(doer) { return } if err := issue.LoadRepo(ctx); err != nil { @@ -106,6 +106,9 @@ func (m *workflowNotifier) IssueChangeStatus(ctx context.Context, doer *user_mod } func (*workflowNotifier) IssueChangeProjects(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldProjectColumnMap map[int64]int64, newProjects []*project_model.Project) { + if issues_model.IsProjectWorkflowDoer(doer) { + return + } addedProjects := make(map[int64]*project_model.Project) for _, newProject := range newProjects { // Use key presence check; column ID 0 is technically valid. @@ -166,7 +169,7 @@ func (*workflowNotifier) IssueChangeProjects(ctx context.Context, doer *user_mod func (*workflowNotifier) IssueChangeProjectColumn(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldColumnID, newColumnID int64) { // Skip column moves triggered by workflow actions to prevent cascade loops. - if doer.ExtDoerData != nil { + if issues_model.IsProjectWorkflowDoer(doer) { return } if err := issue.LoadRepo(ctx); err != nil { @@ -219,6 +222,9 @@ func (*workflowNotifier) IssueChangeProjectColumn(ctx context.Context, doer *use } func (*workflowNotifier) MergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) { + if issues_model.IsProjectWorkflowDoer(doer) { + return + } if err := pr.LoadIssue(ctx); err != nil { log.Error("MergePullRequest: LoadIssue: %v", err) return @@ -255,6 +261,9 @@ func (*workflowNotifier) MergePullRequest(ctx context.Context, doer *user_model. } func (m *workflowNotifier) AutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) { + if issues_model.IsProjectWorkflowDoer(doer) { + return + } m.MergePullRequest(ctx, doer, pr) }