Skip to content

Commit 2a2b46c

Browse files
guillep2ktechknowlogick
authored andcommitted
Reference issues from pull requests and other issues (#8137)
* Update ref comment * Generate comment on simple ref * Make fmt + remove unneeded repo load * Add TODO comments * Add ref-check in issue creation; re-arrange template * Make unit tests pass; rearrange code * Make fmt * Filter out xref comment if user can't see the referencing issue * Add TODOs * Add cross reference * Rearrange code; add cross-repository references * Striketrhough obsolete references * Remove unnecesary TODO * Add "not supported" note * Support for edits and deletes, and issue title * Revert changes to go.mod * Fix fmt * Add support for xref from API * Add first integration test * Add integration tests * Correct formatting * Fix add comment test * Add migration * Remove outdated comments; fix typo * Some code refactoring and rearranging * Rename findCrossReferences to createCrossReferences * Delete xrefs when repository is deleted * Corrections as suggested by @lafriks * Prepare for merge * Fix log for errors
1 parent 8a0379d commit 2a2b46c

File tree

10 files changed

+610
-16
lines changed

10 files changed

+610
-16
lines changed

integrations/issue_test.go

+103-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package integrations
66

77
import (
8+
"fmt"
89
"net/http"
910
"path"
1011
"strconv"
@@ -136,7 +137,7 @@ func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content
136137
return issueURL
137138
}
138139

139-
func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) {
140+
func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) int64 {
140141

141142
req := NewRequest(t, "GET", issueURL)
142143
resp := session.MakeRequest(t, req, http.StatusOK)
@@ -161,6 +162,13 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content,
161162

162163
val := htmlDoc.doc.Find(".comment-list .comments .comment .render-content p").Eq(commentCount).Text()
163164
assert.Equal(t, content, val)
165+
166+
idAttr, has := htmlDoc.doc.Find(".comment-list .comments .comment").Eq(commentCount).Attr("id")
167+
idStr := idAttr[strings.LastIndexByte(idAttr, '-')+1:]
168+
assert.True(t, has)
169+
id, err := strconv.Atoi(idStr)
170+
assert.NoError(t, err)
171+
return int64(id)
164172
}
165173

166174
func TestNewIssue(t *testing.T) {
@@ -184,3 +192,97 @@ func TestIssueCommentClose(t *testing.T) {
184192
val := htmlDoc.doc.Find(".comment-list .comments .comment .render-content p").First().Text()
185193
assert.Equal(t, "Description", val)
186194
}
195+
196+
func TestIssueCrossReference(t *testing.T) {
197+
prepareTestEnv(t)
198+
199+
// Issue that will be referenced
200+
_, issueBase := testIssueWithBean(t, "user2", 1, "Title", "Description")
201+
202+
// Ref from issue title
203+
issueRefURL, issueRef := testIssueWithBean(t, "user2", 1, fmt.Sprintf("Title ref #%d", issueBase.Index), "Description")
204+
models.AssertExistsAndLoadBean(t, &models.Comment{
205+
IssueID: issueBase.ID,
206+
RefRepoID: 1,
207+
RefIssueID: issueRef.ID,
208+
RefCommentID: 0,
209+
RefIsPull: false,
210+
RefAction: models.XRefActionNone})
211+
212+
// Edit title, neuter ref
213+
testIssueChangeInfo(t, "user2", issueRefURL, "title", "Title no ref")
214+
models.AssertExistsAndLoadBean(t, &models.Comment{
215+
IssueID: issueBase.ID,
216+
RefRepoID: 1,
217+
RefIssueID: issueRef.ID,
218+
RefCommentID: 0,
219+
RefIsPull: false,
220+
RefAction: models.XRefActionNeutered})
221+
222+
// Ref from issue content
223+
issueRefURL, issueRef = testIssueWithBean(t, "user2", 1, "TitleXRef", fmt.Sprintf("Description ref #%d", issueBase.Index))
224+
models.AssertExistsAndLoadBean(t, &models.Comment{
225+
IssueID: issueBase.ID,
226+
RefRepoID: 1,
227+
RefIssueID: issueRef.ID,
228+
RefCommentID: 0,
229+
RefIsPull: false,
230+
RefAction: models.XRefActionNone})
231+
232+
// Edit content, neuter ref
233+
testIssueChangeInfo(t, "user2", issueRefURL, "content", "Description no ref")
234+
models.AssertExistsAndLoadBean(t, &models.Comment{
235+
IssueID: issueBase.ID,
236+
RefRepoID: 1,
237+
RefIssueID: issueRef.ID,
238+
RefCommentID: 0,
239+
RefIsPull: false,
240+
RefAction: models.XRefActionNeutered})
241+
242+
// Ref from a comment
243+
session := loginUser(t, "user2")
244+
commentID := testIssueAddComment(t, session, issueRefURL, fmt.Sprintf("Adding ref from comment #%d", issueBase.Index), "")
245+
comment := &models.Comment{
246+
IssueID: issueBase.ID,
247+
RefRepoID: 1,
248+
RefIssueID: issueRef.ID,
249+
RefCommentID: commentID,
250+
RefIsPull: false,
251+
RefAction: models.XRefActionNone}
252+
models.AssertExistsAndLoadBean(t, comment)
253+
254+
// Ref from a different repository
255+
issueRefURL, issueRef = testIssueWithBean(t, "user12", 10, "TitleXRef", fmt.Sprintf("Description ref user2/repo1#%d", issueBase.Index))
256+
models.AssertExistsAndLoadBean(t, &models.Comment{
257+
IssueID: issueBase.ID,
258+
RefRepoID: 10,
259+
RefIssueID: issueRef.ID,
260+
RefCommentID: 0,
261+
RefIsPull: false,
262+
RefAction: models.XRefActionNone})
263+
}
264+
265+
func testIssueWithBean(t *testing.T, user string, repoID int64, title, content string) (string, *models.Issue) {
266+
session := loginUser(t, user)
267+
issueURL := testNewIssue(t, session, user, fmt.Sprintf("repo%d", repoID), title, content)
268+
indexStr := issueURL[strings.LastIndexByte(issueURL, '/')+1:]
269+
index, err := strconv.Atoi(indexStr)
270+
assert.NoError(t, err, "Invalid issue href: %s", issueURL)
271+
issue := &models.Issue{RepoID: repoID, Index: int64(index)}
272+
models.AssertExistsAndLoadBean(t, issue)
273+
return issueURL, issue
274+
}
275+
276+
func testIssueChangeInfo(t *testing.T, user, issueURL, info string, value string) {
277+
session := loginUser(t, user)
278+
279+
req := NewRequest(t, "GET", issueURL)
280+
resp := session.MakeRequest(t, req, http.StatusOK)
281+
htmlDoc := NewHTMLParser(t, resp.Body)
282+
283+
req = NewRequestWithValues(t, "POST", path.Join(issueURL, info), map[string]string{
284+
"_csrf": htmlDoc.GetCSRF(),
285+
info: value,
286+
})
287+
_ = session.MakeRequest(t, req, http.StatusOK)
288+
}

models/issue.go

+52-5
Original file line numberDiff line numberDiff line change
@@ -595,8 +595,9 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
595595
if err = sess.Commit(); err != nil {
596596
return fmt.Errorf("Commit: %v", err)
597597
}
598+
sess.Close()
598599

599-
if err = issue.loadPoster(x); err != nil {
600+
if err = issue.LoadPoster(); err != nil {
600601
return fmt.Errorf("loadPoster: %v", err)
601602
}
602603

@@ -870,9 +871,18 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
870871
return fmt.Errorf("createChangeTitleComment: %v", err)
871872
}
872873

874+
if err = issue.neuterCrossReferences(sess); err != nil {
875+
return err
876+
}
877+
878+
if err = issue.addCrossReferences(sess, doer); err != nil {
879+
return err
880+
}
881+
873882
if err = sess.Commit(); err != nil {
874883
return err
875884
}
885+
sess.Close()
876886

877887
mode, _ := AccessLevel(issue.Poster, issue.Repo)
878888
if issue.IsPull {
@@ -939,9 +949,26 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
939949
oldContent := issue.Content
940950
issue.Content = content
941951

942-
if err = UpdateIssueCols(issue, "content"); err != nil {
952+
sess := x.NewSession()
953+
defer sess.Close()
954+
if err = sess.Begin(); err != nil {
955+
return err
956+
}
957+
958+
if err = updateIssueCols(sess, issue, "content"); err != nil {
943959
return fmt.Errorf("UpdateIssueCols: %v", err)
944960
}
961+
if err = issue.neuterCrossReferences(sess); err != nil {
962+
return err
963+
}
964+
if err = issue.addCrossReferences(sess, doer); err != nil {
965+
return err
966+
}
967+
968+
if err = sess.Commit(); err != nil {
969+
return err
970+
}
971+
sess.Close()
945972

946973
mode, _ := AccessLevel(issue.Poster, issue.Repo)
947974
if issue.IsPull {
@@ -1171,8 +1198,10 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
11711198
}
11721199
}
11731200
}
1174-
1175-
return opts.Issue.loadAttributes(e)
1201+
if err = opts.Issue.loadAttributes(e); err != nil {
1202+
return err
1203+
}
1204+
return opts.Issue.addCrossReferences(e, doer)
11761205
}
11771206

11781207
// NewIssue creates new issue with labels for repository.
@@ -1199,6 +1228,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in
11991228
if err = sess.Commit(); err != nil {
12001229
return fmt.Errorf("Commit: %v", err)
12011230
}
1231+
sess.Close()
12021232

12031233
if err = NotifyWatchers(&Action{
12041234
ActUserID: issue.Poster.ID,
@@ -1808,7 +1838,24 @@ func updateIssue(e Engine, issue *Issue) error {
18081838

18091839
// UpdateIssue updates all fields of given issue.
18101840
func UpdateIssue(issue *Issue) error {
1811-
return updateIssue(x, issue)
1841+
sess := x.NewSession()
1842+
defer sess.Close()
1843+
if err := sess.Begin(); err != nil {
1844+
return err
1845+
}
1846+
if err := updateIssue(sess, issue); err != nil {
1847+
return err
1848+
}
1849+
if err := issue.neuterCrossReferences(sess); err != nil {
1850+
return err
1851+
}
1852+
if err := issue.loadPoster(sess); err != nil {
1853+
return err
1854+
}
1855+
if err := issue.addCrossReferences(sess, issue.Poster); err != nil {
1856+
return err
1857+
}
1858+
return sess.Commit()
18121859
}
18131860

18141861
// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.

models/issue_comment.go

+55-6
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,30 @@ type Comment struct {
142142
Review *Review `xorm:"-"`
143143
ReviewID int64 `xorm:"index"`
144144
Invalidated bool
145+
146+
// Reference an issue or pull from another comment, issue or PR
147+
// All information is about the origin of the reference
148+
RefRepoID int64 `xorm:"index"` // Repo where the referencing
149+
RefIssueID int64 `xorm:"index"`
150+
RefCommentID int64 `xorm:"index"` // 0 if origin is Issue title or content (or PR's)
151+
RefAction XRefAction `xorm:"SMALLINT"` // What hapens if RefIssueID resolves
152+
RefIsPull bool
153+
154+
RefRepo *Repository `xorm:"-"`
155+
RefIssue *Issue `xorm:"-"`
156+
RefComment *Comment `xorm:"-"`
145157
}
146158

147159
// LoadIssue loads issue from database
148160
func (c *Comment) LoadIssue() (err error) {
161+
return c.loadIssue(x)
162+
}
163+
164+
func (c *Comment) loadIssue(e Engine) (err error) {
149165
if c.Issue != nil {
150166
return nil
151167
}
152-
c.Issue, err = GetIssueByID(c.IssueID)
168+
c.Issue, err = getIssueByID(e, c.IssueID)
153169
return
154170
}
155171

@@ -527,6 +543,11 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
527543
TreePath: opts.TreePath,
528544
ReviewID: opts.ReviewID,
529545
Patch: opts.Patch,
546+
RefRepoID: opts.RefRepoID,
547+
RefIssueID: opts.RefIssueID,
548+
RefCommentID: opts.RefCommentID,
549+
RefAction: opts.RefAction,
550+
RefIsPull: opts.RefIsPull,
530551
}
531552
if _, err = e.Insert(comment); err != nil {
532553
return nil, err
@@ -540,6 +561,10 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
540561
return nil, err
541562
}
542563

564+
if err = comment.addCrossReferences(e, opts.Doer); err != nil {
565+
return nil, err
566+
}
567+
543568
return comment, nil
544569
}
545570

@@ -794,6 +819,11 @@ type CreateCommentOptions struct {
794819
ReviewID int64
795820
Content string
796821
Attachments []string // UUIDs of attachments
822+
RefRepoID int64
823+
RefIssueID int64
824+
RefCommentID int64
825+
RefAction XRefAction
826+
RefIsPull bool
797827
}
798828

799829
// CreateComment creates comment of issue or commit.
@@ -934,21 +964,33 @@ func FindComments(opts FindCommentsOptions) ([]*Comment, error) {
934964

935965
// UpdateComment updates information of comment.
936966
func UpdateComment(doer *User, c *Comment, oldContent string) error {
937-
if _, err := x.ID(c.ID).AllCols().Update(c); err != nil {
967+
sess := x.NewSession()
968+
defer sess.Close()
969+
if err := sess.Begin(); err != nil {
938970
return err
939971
}
940972

941-
if err := c.LoadPoster(); err != nil {
973+
if _, err := sess.ID(c.ID).AllCols().Update(c); err != nil {
942974
return err
943975
}
944-
if err := c.LoadIssue(); err != nil {
976+
if err := c.loadIssue(sess); err != nil {
945977
return err
946978
}
979+
if err := c.neuterCrossReferences(sess); err != nil {
980+
return err
981+
}
982+
if err := c.addCrossReferences(sess, doer); err != nil {
983+
return err
984+
}
985+
if err := sess.Commit(); err != nil {
986+
return fmt.Errorf("Commit: %v", err)
987+
}
988+
sess.Close()
947989

948-
if err := c.Issue.LoadAttributes(); err != nil {
990+
if err := c.LoadPoster(); err != nil {
949991
return err
950992
}
951-
if err := c.loadPoster(x); err != nil {
993+
if err := c.Issue.LoadAttributes(); err != nil {
952994
return err
953995
}
954996

@@ -996,6 +1038,10 @@ func DeleteComment(doer *User, comment *Comment) error {
9961038
return err
9971039
}
9981040

1041+
if err := comment.neuterCrossReferences(sess); err != nil {
1042+
return err
1043+
}
1044+
9991045
if err := sess.Commit(); err != nil {
10001046
return err
10011047
}
@@ -1014,6 +1060,9 @@ func DeleteComment(doer *User, comment *Comment) error {
10141060
if err := comment.loadPoster(x); err != nil {
10151061
return err
10161062
}
1063+
if err := comment.neuterCrossReferences(x); err != nil {
1064+
return err
1065+
}
10171066

10181067
mode, _ := AccessLevel(doer, comment.Issue.Repo)
10191068

0 commit comments

Comments
 (0)