diff --git a/models/issues/issue_xref.go b/models/issues/issue_xref.go index 74f97f1ad51..09865bda0d0 100644 --- a/models/issues/issue_xref.go +++ b/models/issues/issue_xref.go @@ -10,6 +10,7 @@ import ( "gitea.dev/models/db" access_model "gitea.dev/models/perm/access" repo_model "gitea.dev/models/repo" + "gitea.dev/models/unit" user_model "gitea.dev/models/user" "gitea.dev/modules/log" "gitea.dev/modules/references" @@ -202,8 +203,13 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe return nil, references.XRefActionNone, err } - // Close/reopen actions can only be set from pull requests to issues - if refIssue.IsPull || !issue.IsPull { + // Close/reopen actions can only be set from pull requests, reopen only to issues + if !issue.IsPull || (refIssue.IsPull && refAction == references.XRefActionReopens) { + refAction = references.XRefActionNone + } + + // With an external tracker, pull requests are referenced as "!N" + if refAction != references.XRefActionNone && !ref.IsPull && refIssue.IsPull && refIssue.Repo.UnitEnabled(stdCtx, unit.TypeExternalTracker) { refAction = references.XRefActionNone } diff --git a/models/issues/issue_xref_test.go b/models/issues/issue_xref_test.go index 4ecc6547852..6c66e11af0a 100644 --- a/models/issues/issue_xref_test.go +++ b/models/issues/issue_xref_test.go @@ -10,6 +10,7 @@ import ( "gitea.dev/models/db" issues_model "gitea.dev/models/issues" repo_model "gitea.dev/models/repo" + "gitea.dev/models/unit" "gitea.dev/models/unittest" user_model "gitea.dev/models/user" "gitea.dev/modules/references" @@ -24,22 +25,26 @@ func TestXRef_AddCrossReferences(t *testing.T) { itarget := testCreateIssue(t, 1, 2, "title1", "content1", false) // PR to close issue #1 - content := fmt.Sprintf("content2, closes #%d", itarget.Index) + content := fmt.Sprintf("content2, closes #%d, fixes #2", itarget.Index) pr := testCreateIssue(t, 1, 2, "title2", content, true) ref := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: pr.ID, RefCommentID: 0}) assert.Equal(t, issues_model.CommentTypePullRef, ref.Type) assert.Equal(t, pr.RepoID, ref.RefRepoID) assert.True(t, ref.RefIsPull) assert.Equal(t, references.XRefActionCloses, ref.RefAction) + ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: 2, RefIssueID: pr.ID, RefCommentID: 0}) + assert.Equal(t, references.XRefActionCloses, ref.RefAction) // Comment on PR to reopen issue #1 - content = fmt.Sprintf("content2, reopens #%d", itarget.Index) + content = fmt.Sprintf("content2, reopens #%d, reopens #2", itarget.Index) c := testCreateComment(t, 2, pr.ID, content) ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: pr.ID, RefCommentID: c.ID}) assert.Equal(t, issues_model.CommentTypeCommentRef, ref.Type) assert.Equal(t, pr.RepoID, ref.RefRepoID) assert.True(t, ref.RefIsPull) assert.Equal(t, references.XRefActionReopens, ref.RefAction) + ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: 2, RefIssueID: pr.ID, RefCommentID: c.ID}) + assert.Equal(t, references.XRefActionNone, ref.RefAction) // Issue mentioning issue #1 content = fmt.Sprintf("content3, mentions #%d", itarget.Index) @@ -66,6 +71,14 @@ func TestXRef_AddCrossReferences(t *testing.T) { content = fmt.Sprintf("content6, mentions org3/repo3#%d", itarget.Index) i = testCreateIssue(t, 4, 5, "title6", content, false) unittest.AssertNotExistsBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}) + + assert.NoError(t, db.DeleteBeans(t.Context(), &repo_model.RepoUnit{RepoID: 1, Type: unit.TypeIssues})) + assert.NoError(t, db.Insert(t.Context(), &repo_model.RepoUnit{RepoID: 1, Type: unit.TypeExternalTracker, Config: &repo_model.ExternalTrackerConfig{}})) + c = testCreateComment(t, 2, pr.ID, "fixes #2, fixes !3") + ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: 2, RefIssueID: pr.ID, RefCommentID: c.ID}) + assert.Equal(t, references.XRefActionNone, ref.RefAction) + ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: 3, RefIssueID: pr.ID, RefCommentID: c.ID}) + assert.Equal(t, references.XRefActionCloses, ref.RefAction) } func TestXRef_NeuterCrossReferences(t *testing.T) { diff --git a/modules/references/references.go b/modules/references/references.go index 0d470d479ae..db8f9d606dd 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -85,6 +85,7 @@ type IssueReference struct { Index int64 Owner string Name string + IsPull bool Action XRefAction TimeLog string } @@ -123,6 +124,7 @@ func rawToIssueReferenceList(reflist []*rawReference) []IssueReference { Index: r.index, Owner: r.owner, Name: r.name, + IsPull: r.isPull, Action: r.action, TimeLog: r.timeLog, } diff --git a/services/issue/commit.go b/services/issue/commit.go index 0fb070d0c17..1847979f7ce 100644 --- a/services/issue/commit.go +++ b/services/issue/commit.go @@ -18,6 +18,7 @@ import ( issues_model "gitea.dev/models/issues" access_model "gitea.dev/models/perm/access" repo_model "gitea.dev/models/repo" + "gitea.dev/models/unit" user_model "gitea.dev/models/user" "gitea.dev/modules/container" "gitea.dev/modules/git" @@ -188,8 +189,8 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m return err } - // Only issues can be closed/reopened this way, and user needs the correct permissions - if refIssue.IsPull || !canclose { + // Only issues can be reopened this way, and user needs the correct permissions + if !canclose || (refIssue.IsPull && ref.Action == references.XRefActionReopens) { continue } @@ -198,6 +199,11 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m continue } + // With an external tracker, pull requests are referenced as "!N" + if !ref.IsPull && refIssue.IsPull && refRepo.UnitEnabled(ctx, unit.TypeExternalTracker) { + continue + } + if !repo.CloseIssuesViaCommitInAnyBranch { // If the issue was specified to be in a particular branch, don't allow commits in other branches to close it if refIssue.Ref != "" { diff --git a/services/issue/commit_test.go b/services/issue/commit_test.go index 61e4b00cb21..3c7d753c886 100644 --- a/services/issue/commit_test.go +++ b/services/issue/commit_test.go @@ -42,7 +42,7 @@ func TestUpdateIssuesCommit(t *testing.T) { CommitterName: "User Two", AuthorEmail: "user2@example.com", AuthorName: "User Two", - Message: "close #2", + Message: "close #2, reopen #2", }, } @@ -56,7 +56,7 @@ func TestUpdateIssuesCommit(t *testing.T) { PosterID: user.ID, IssueID: 1, } - issueBean := &issues_model.Issue{RepoID: repo.ID, Index: 4} + issueBean := &issues_model.Issue{RepoID: repo.ID, Index: 2} unittest.AssertNotExistsBean(t, commentBean) unittest.AssertNotExistsBean(t, &issues_model.Issue{RepoID: repo.ID, Index: 2}, "is_closed=1") @@ -117,6 +117,13 @@ func TestUpdateIssuesCommit(t *testing.T) { unittest.AssertExistsAndLoadBean(t, commentBean) unittest.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") unittest.CheckConsistencyFor(t, &activities_model.Action{}) + + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 48}) + admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + assert.NoError(t, UpdateIssuesCommit(t.Context(), admin, repo, []*repository.PushCommit{{Sha1: "abcdef4", Message: "close #1"}}, repo.DefaultBranch)) + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID, Index: 1}, "is_closed=0") + assert.NoError(t, UpdateIssuesCommit(t.Context(), admin, repo, []*repository.PushCommit{{Sha1: "abcdef5", Message: "close !1"}}, repo.DefaultBranch)) + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID, Index: 1}, "is_closed=1") } func TestUpdateIssuesCommit_Colon(t *testing.T) {