enhance: allow auto-closing PRs from PRs (#39393)

On GitHub, one can close PRs via `Fixes: #123` references which was not
possible on Gitea before, but now is. Verified fully that behaviour
matches GH and ensured no regressions for external trackers.
This commit is contained in:
silverwind authored and GitHub committed 2026-09-25 11:35:13 +00:00
1 parent 455f30a7b6
commit 6d59229331
5 files changed
+42 -8

No files matched your search

+8 -2
View File
@@ -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
}
+15 -2
View File
@@ -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) {