From 78c3778df8e307c05413acd2c7b102843c7d5bb6 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Mon, 9 Sep 2019 19:24:44 -0300 Subject: [PATCH 01/30] Update ref comment --- models/issue_comment.go | 45 +++++++++++++++++++ options/locale/locale_en-US.ini | 2 + .../repo/issue/view_content/comments.tmpl | 14 ++++++ 3 files changed, 61 insertions(+) diff --git a/models/issue_comment.go b/models/issue_comment.go index 2a9e8596cb202..6ee520b8af565 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -142,6 +142,12 @@ type Comment struct { Review *Review `xorm:"-"` ReviewID int64 `xorm:"index"` Invalidated bool + + // Reference issue and pull from comment + RefIssueID int64 `xorm:"index"` // GAP: el issue desde el que se creó esta referencia (dueño del RefCommentID) + RefCommentID int64 `xorm:"index"` + RefComment *Comment `xorm:"-"` + RefIssue *Issue `xorm:"-"` } // LoadIssue loads issue from database @@ -281,6 +287,45 @@ func (c *Comment) EventTag() string { return "event-" + com.ToStr(c.ID) } +// LoadRefComment loads comment that created this reference from database +func (c *Comment) LoadRefComment() (err error) { + if c.RefComment != nil { + return nil + } + c.RefComment, err = GetCommentByID(c.RefCommentID) + return +} + +// LoadRefIssue loads comment that created this reference from database +func (c *Comment) LoadRefIssue() (err error) { + if c.RefIssue != nil { + return nil + } + c.RefIssue, err = GetIssueByID(c.RefIssueID) + return +} + +// RefCommentHTMLURL returns the HTML URL for the comment that created this reference +func (c *Comment) RefCommentHTMLURL() string { + if err := c.LoadRefComment(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefComment(%d): %v", c.RefCommentID, err) + return "" + } + // GAP: No sería necesario referenciar exactamente al comentario + // si seguimos el ejemplo de GitHub + return c.RefComment.HTMLURL() +} + +// RefIssueName returns the name of the issue where this reference was created +func (c *Comment) RefIssueName() string { + if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) + return "" + } + // GAP: TODO: check this name (cross-rep references) + return "#" + com.ToStr(c.RefIssue.Index) +} + // LoadLabel if comment.Type is CommentTypeLabel, then load Label func (c *Comment) LoadLabel() error { var label Label diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index a72263d088c49..bb6231b991cd0 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -835,6 +835,8 @@ issues.create_comment = Comment issues.closed_at = `closed %[2]s` issues.reopened_at = `reopened %[2]s` issues.commit_ref_at = `referenced this issue from a commit %[2]s` +issues.ref_issue_at = `referenced this issue from %[2]s %[3]s` +issues.ref_pull_at = `referenced this pull from %[2]s %[3]s` issues.poster = Poster issues.collaborator = Collaborator issues.owner = Owner diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 70de314c918dc..f3897d925d584 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -96,6 +96,20 @@ {{.Poster.GetDisplayName}} {{$.i18n.Tr "repo.issues.closed_at" .EventTag $createdStr | Safe}} + {{else if or (eq .Type 3) (eq .Type 5) (eq .Type 6)}} +
+ + + + + {{.Poster.GetDisplayName}} + {{if .Issue.IsPull}} + {{$.i18n.Tr "repo.issues.ref_pull_at" .RefCommentHTMLURL .RefIssueName $createdStr | Safe}} + {{else}} + {{$.i18n.Tr "repo.issues.ref_issue_at" .RefCommentHTMLURL .RefIssueName $createdStr | Safe}} + {{end}} + +
{{else if eq .Type 4}}
From b4d5dd339c303bcd3020fdfed9d1301dfa2139e8 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Tue, 10 Sep 2019 00:15:49 -0300 Subject: [PATCH 02/30] Generate comment on simple ref --- models/issue.go | 61 +++++++++++++++++++ models/issue_comment.go | 48 +++++++++++++-- options/locale/locale_en-US.ini | 4 +- .../repo/issue/view_content/comments.tmpl | 9 ++- 4 files changed, 113 insertions(+), 9 deletions(-) diff --git a/models/issue.go b/models/issue.go index 511bfa31c411e..ed71502c4ce2d 100644 --- a/models/issue.go +++ b/models/issue.go @@ -9,6 +9,7 @@ import ( "path" "regexp" "sort" + "strconv" "strings" "code.gitea.io/gitea/modules/base" @@ -69,6 +70,12 @@ type Issue struct { var ( issueTasksPat *regexp.Regexp issueTasksDonePat *regexp.Regexp + + // issueNumericPattern matches string that references to a numeric issue, e.g. #1287 + issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(#[0-9]+)(?:\s|$|\)|\]|:|\.(\s|$))`) + // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository + // e.g. gogits/gogs#12345 + crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+#[0-9]+)(?:\s|$|\)|\]|\.(\s|$))`) ) const issueTasksRegexpStr = `(^\s*[-*]\s\[[\sx]\]\s.)|(\n\s*[-*]\s\[[\sx]\]\s.)` @@ -1863,3 +1870,57 @@ func (issue *Issue) updateClosedNum(e Engine) (err error) { } return } + +// ParseReferencesOptions represents a comment or issue that might make references to other issues +type ParseReferencesOptions struct { + Type CommentType + Doer *User + OrigIssue *Issue + OrigComment *Comment +} + +func (issue *Issue) parseCommentReferences(e *xorm.Session, refopts *ParseReferencesOptions, content string) error { + // Issues in the same repository + // FIXME: Should we support IssueNameStyleAlphanumeric? + log.Info("GAP: parseCommentReferences() for `%s`", content) + matches := issueNumericPattern.FindAllStringSubmatch(content, -1) + for _, match := range matches { + if i, err := strconv.ParseInt(match[1][1:], 10, 64); err == nil { + if err = issue.checkCommentReference(e, refopts, issue.Repo, i); err != nil { + return err + } + } + } + return nil +} + +func (issue *Issue) checkCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, repo *Repository, index int64) error { + log.Info("GAP: checkCommentReference() repo_id: %d, index #d", repo.ID, index) + refIssue := &Issue{RepoID: repo.ID, Index: index} + if has, _ := e.Get(refIssue); !has { + log.Info("GAP: checkCommentReference(): not found :(") + return nil + } + log.Info("GAP: checkCommentReference(): found! :D") + addCommentReference(e, refopts, refIssue) + return nil +} + +func addCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, referred *Issue) error { + if err := referred.loadRepo(e); err != nil { + return err + } + var refCommentID int64 + if refopts.OrigComment != nil { + refCommentID = refopts.OrigComment.ID + } + _, err := createComment(e, &CreateCommentOptions{ + Type: refopts.Type, + Doer: refopts.Doer, + Repo: referred.Repo, + Issue: referred, + RefIssueID: refopts.OrigIssue.ID, + RefCommentID: refCommentID, + }) + return err +} diff --git a/models/issue_comment.go b/models/issue_comment.go index 6ee520b8af565..0898fbd23d446 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -144,10 +144,10 @@ type Comment struct { Invalidated bool // Reference issue and pull from comment - RefIssueID int64 `xorm:"index"` // GAP: el issue desde el que se creó esta referencia (dueño del RefCommentID) + RefIssueID int64 `xorm:"index"` RefCommentID int64 `xorm:"index"` - RefComment *Comment `xorm:"-"` RefIssue *Issue `xorm:"-"` + RefComment *Comment `xorm:"-"` } // LoadIssue loads issue from database @@ -302,6 +302,9 @@ func (c *Comment) LoadRefIssue() (err error) { return nil } c.RefIssue, err = GetIssueByID(c.RefIssueID) + if err == nil { + err = c.RefIssue.loadRepo(x) + } return } @@ -316,13 +319,31 @@ func (c *Comment) RefCommentHTMLURL() string { return c.RefComment.HTMLURL() } -// RefIssueName returns the name of the issue where this reference was created -func (c *Comment) RefIssueName() string { +// RefIssueHTMLURL returns the HTML URL of the issue where this reference was created +func (c *Comment) RefIssueHTMLURL() string { + if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) + return "" + } + return c.RefIssue.HTMLURL() +} + +// RefIssueTitle returns the title of the issue where this reference was created +func (c *Comment) RefIssueTitle() string { if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) return "" } - // GAP: TODO: check this name (cross-rep references) + return c.RefIssue.Title +} + +// RefIssueIdent returns the user friendly identity (e.g. "#1234") of the issue where this reference was created +func (c *Comment) RefIssueIdent() string { + if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) + return "" + } + // FIXME: check this name for cross-repository references (#7901 if it gets merged) return "#" + com.ToStr(c.RefIssue.Index) } @@ -572,6 +593,8 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err TreePath: opts.TreePath, ReviewID: opts.ReviewID, Patch: opts.Patch, + RefIssueID: opts.RefIssueID, + RefCommentID: opts.RefCommentID, } if _, err = e.Insert(comment); err != nil { return nil, err @@ -585,6 +608,19 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err return nil, err } + // Check references to other issues/pulls + if opts.Type == CommentTypeCode || opts.Type == CommentTypeComment { + refopts := &ParseReferencesOptions { + Type: CommentTypeCommentRef, + Doer: opts.Doer, + OrigIssue: comment.Issue, + OrigComment: comment, + } + if err = comment.Issue.parseCommentReferences(e, refopts, opts.Content); err != nil { + return nil, err + } + } + return comment, nil } @@ -839,6 +875,8 @@ type CreateCommentOptions struct { ReviewID int64 Content string Attachments []string // UUIDs of attachments + RefIssueID int64 + RefCommentID int64 } // CreateComment creates comment of issue or commit. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index bb6231b991cd0..c7f46e81a03fe 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -835,8 +835,8 @@ issues.create_comment = Comment issues.closed_at = `closed %[2]s` issues.reopened_at = `reopened %[2]s` issues.commit_ref_at = `referenced this issue from a commit %[2]s` -issues.ref_issue_at = `referenced this issue from %[2]s %[3]s` -issues.ref_pull_at = `referenced this pull from %[2]s %[3]s` +issues.ref_issue_at = `referenced this issue %[1]s` +issues.ref_pull_at = `referenced this pull request %[1]s` issues.poster = Poster issues.collaborator = Collaborator issues.owner = Owner diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index f3897d925d584..84615af6976bd 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -104,11 +104,16 @@ {{.Poster.GetDisplayName}} {{if .Issue.IsPull}} - {{$.i18n.Tr "repo.issues.ref_pull_at" .RefCommentHTMLURL .RefIssueName $createdStr | Safe}} + {{$.i18n.Tr "repo.issues.ref_pull_at" $createdStr | Safe}} {{else}} - {{$.i18n.Tr "repo.issues.ref_issue_at" .RefCommentHTMLURL .RefIssueName $createdStr | Safe}} + {{$.i18n.Tr "repo.issues.ref_issue_at" $createdStr | Safe}} {{end}} + +
{{else if eq .Type 4}}
From 235694a24cf4e2147ca38dfbcc1946e22622bc89 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Tue, 10 Sep 2019 00:34:31 -0300 Subject: [PATCH 03/30] Make fmt + remove unneeded repo load --- models/issue.go | 33 ++++++++++++++++----------------- models/issue_comment.go | 23 ++++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/models/issue.go b/models/issue.go index ed71502c4ce2d..5b8ad39198b7b 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1873,20 +1873,22 @@ func (issue *Issue) updateClosedNum(e Engine) (err error) { // ParseReferencesOptions represents a comment or issue that might make references to other issues type ParseReferencesOptions struct { - Type CommentType - Doer *User - OrigIssue *Issue - OrigComment *Comment + Type CommentType + Doer *User + OrigIssue *Issue + OrigComment *Comment } func (issue *Issue) parseCommentReferences(e *xorm.Session, refopts *ParseReferencesOptions, content string) error { // Issues in the same repository // FIXME: Should we support IssueNameStyleAlphanumeric? - log.Info("GAP: parseCommentReferences() for `%s`", content) matches := issueNumericPattern.FindAllStringSubmatch(content, -1) for _, match := range matches { if i, err := strconv.ParseInt(match[1][1:], 10, 64); err == nil { - if err = issue.checkCommentReference(e, refopts, issue.Repo, i); err != nil { + if err = issue.loadRepo(e); err != nil { + return err + } + if err = issue.checkCommentReference(e, refopts, issue.RepoID, i); err != nil { return err } } @@ -1894,14 +1896,11 @@ func (issue *Issue) parseCommentReferences(e *xorm.Session, refopts *ParseRefere return nil } -func (issue *Issue) checkCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, repo *Repository, index int64) error { - log.Info("GAP: checkCommentReference() repo_id: %d, index #d", repo.ID, index) - refIssue := &Issue{RepoID: repo.ID, Index: index} +func (issue *Issue) checkCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, repoID int64, index int64) error { + refIssue := &Issue{RepoID: repoID, Index: index} if has, _ := e.Get(refIssue); !has { - log.Info("GAP: checkCommentReference(): not found :(") return nil } - log.Info("GAP: checkCommentReference(): found! :D") addCommentReference(e, refopts, refIssue) return nil } @@ -1915,12 +1914,12 @@ func addCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, refer refCommentID = refopts.OrigComment.ID } _, err := createComment(e, &CreateCommentOptions{ - Type: refopts.Type, - Doer: refopts.Doer, - Repo: referred.Repo, - Issue: referred, - RefIssueID: refopts.OrigIssue.ID, - RefCommentID: refCommentID, + Type: refopts.Type, + Doer: refopts.Doer, + Repo: referred.Repo, + Issue: referred, + RefIssueID: refopts.OrigIssue.ID, + RefCommentID: refCommentID, }) return err } diff --git a/models/issue_comment.go b/models/issue_comment.go index 0898fbd23d446..edcc39a8af82b 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -144,10 +144,10 @@ type Comment struct { Invalidated bool // Reference issue and pull from comment - RefIssueID int64 `xorm:"index"` - RefCommentID int64 `xorm:"index"` - RefIssue *Issue `xorm:"-"` - RefComment *Comment `xorm:"-"` + RefIssueID int64 `xorm:"index"` + RefCommentID int64 `xorm:"index"` + RefIssue *Issue `xorm:"-"` + RefComment *Comment `xorm:"-"` } // LoadIssue loads issue from database @@ -314,8 +314,6 @@ func (c *Comment) RefCommentHTMLURL() string { log.Error("LoadRefComment(%d): %v", c.RefCommentID, err) return "" } - // GAP: No sería necesario referenciar exactamente al comentario - // si seguimos el ejemplo de GitHub return c.RefComment.HTMLURL() } @@ -610,11 +608,14 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err // Check references to other issues/pulls if opts.Type == CommentTypeCode || opts.Type == CommentTypeComment { - refopts := &ParseReferencesOptions { - Type: CommentTypeCommentRef, - Doer: opts.Doer, - OrigIssue: comment.Issue, - OrigComment: comment, + if err := comment.LoadIssue(); err != nil { + return nil, err + } + refopts := &ParseReferencesOptions{ + Type: CommentTypeCommentRef, + Doer: opts.Doer, + OrigIssue: comment.Issue, + OrigComment: comment, } if err = comment.Issue.parseCommentReferences(e, refopts, opts.Content); err != nil { return nil, err From 64e4b4231b5c24075e7ec530e5b7c4b6179ac564 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Tue, 10 Sep 2019 00:42:20 -0300 Subject: [PATCH 04/30] Add TODO comments --- models/issue.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/models/issue.go b/models/issue.go index 5b8ad39198b7b..cd786e3ac90a9 100644 --- a/models/issue.go +++ b/models/issue.go @@ -75,7 +75,7 @@ var ( issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(#[0-9]+)(?:\s|$|\)|\]|:|\.(\s|$))`) // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository // e.g. gogits/gogs#12345 - crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+#[0-9]+)(?:\s|$|\)|\]|\.(\s|$))`) + // crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+#[0-9]+)(?:\s|$|\)|\]|\.(\s|$))`) ) const issueTasksRegexpStr = `(^\s*[-*]\s\[[\sx]\]\s.)|(\n\s*[-*]\s\[[\sx]\]\s.)` @@ -1880,6 +1880,7 @@ type ParseReferencesOptions struct { } func (issue *Issue) parseCommentReferences(e *xorm.Session, refopts *ParseReferencesOptions, content string) error { + // TODO: Check for multiple references to the same Issue // Issues in the same repository // FIXME: Should we support IssueNameStyleAlphanumeric? matches := issueNumericPattern.FindAllStringSubmatch(content, -1) @@ -1893,10 +1894,15 @@ func (issue *Issue) parseCommentReferences(e *xorm.Session, refopts *ParseRefere } } } + // Issues in other repositories + // GAP: TODO: use crossReferenceIssueNumericPattern to parse references to other repos; take units into account (?) return nil } func (issue *Issue) checkCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, repoID int64, index int64) error { + if refopts.OrigIssue.RepoID == repoID && refopts.OrigIssue.Index == index { + return nil + } refIssue := &Issue{RepoID: repoID, Index: index} if has, _ := e.Get(refIssue); !has { return nil From 7edef316f1caa0c1688aae08f6cc9fcec16d5d14 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Tue, 10 Sep 2019 01:13:06 -0300 Subject: [PATCH 05/30] Add ref-check in issue creation; re-arrange template --- models/issue.go | 11 +++++++++-- templates/repo/issue/view_content/comments.tmpl | 3 +-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/models/issue.go b/models/issue.go index cd786e3ac90a9..fc54efe66356a 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1178,8 +1178,15 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) { } } } - - return opts.Issue.loadAttributes(e) + if err = opts.Issue.loadAttributes(e); err != nil { + return err + } + refopts := &ParseReferencesOptions{ + Type: CommentTypeIssueRef, + Doer: doer, + OrigIssue: opts.Issue, + } + return opts.Issue.parseCommentReferences(e, refopts, opts.Issue.Content) } // NewIssue creates new issue with labels for repository. diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 84615af6976bd..a6d6490f8de53 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -98,7 +98,7 @@
{{else if or (eq .Type 3) (eq .Type 5) (eq .Type 6)}}
- + @@ -111,7 +111,6 @@
From 11eb10c4c8408ddcc9431ff2a1e6006c0d2670b5 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Tue, 10 Sep 2019 23:52:01 -0300 Subject: [PATCH 06/30] Make unit tests pass; rearrange code --- models/issue.go | 66 ------------------- models/issue_comment.go | 19 ++++-- models/issue_xref.go | 143 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 72 deletions(-) create mode 100644 models/issue_xref.go diff --git a/models/issue.go b/models/issue.go index fc54efe66356a..f4bdaa7e1d8d4 100644 --- a/models/issue.go +++ b/models/issue.go @@ -9,7 +9,6 @@ import ( "path" "regexp" "sort" - "strconv" "strings" "code.gitea.io/gitea/modules/base" @@ -70,12 +69,6 @@ type Issue struct { var ( issueTasksPat *regexp.Regexp issueTasksDonePat *regexp.Regexp - - // issueNumericPattern matches string that references to a numeric issue, e.g. #1287 - issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(#[0-9]+)(?:\s|$|\)|\]|:|\.(\s|$))`) - // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository - // e.g. gogits/gogs#12345 - // crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+#[0-9]+)(?:\s|$|\)|\]|\.(\s|$))`) ) const issueTasksRegexpStr = `(^\s*[-*]\s\[[\sx]\]\s.)|(\n\s*[-*]\s\[[\sx]\]\s.)` @@ -1877,62 +1870,3 @@ func (issue *Issue) updateClosedNum(e Engine) (err error) { } return } - -// ParseReferencesOptions represents a comment or issue that might make references to other issues -type ParseReferencesOptions struct { - Type CommentType - Doer *User - OrigIssue *Issue - OrigComment *Comment -} - -func (issue *Issue) parseCommentReferences(e *xorm.Session, refopts *ParseReferencesOptions, content string) error { - // TODO: Check for multiple references to the same Issue - // Issues in the same repository - // FIXME: Should we support IssueNameStyleAlphanumeric? - matches := issueNumericPattern.FindAllStringSubmatch(content, -1) - for _, match := range matches { - if i, err := strconv.ParseInt(match[1][1:], 10, 64); err == nil { - if err = issue.loadRepo(e); err != nil { - return err - } - if err = issue.checkCommentReference(e, refopts, issue.RepoID, i); err != nil { - return err - } - } - } - // Issues in other repositories - // GAP: TODO: use crossReferenceIssueNumericPattern to parse references to other repos; take units into account (?) - return nil -} - -func (issue *Issue) checkCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, repoID int64, index int64) error { - if refopts.OrigIssue.RepoID == repoID && refopts.OrigIssue.Index == index { - return nil - } - refIssue := &Issue{RepoID: repoID, Index: index} - if has, _ := e.Get(refIssue); !has { - return nil - } - addCommentReference(e, refopts, refIssue) - return nil -} - -func addCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, referred *Issue) error { - if err := referred.loadRepo(e); err != nil { - return err - } - var refCommentID int64 - if refopts.OrigComment != nil { - refCommentID = refopts.OrigComment.ID - } - _, err := createComment(e, &CreateCommentOptions{ - Type: refopts.Type, - Doer: refopts.Doer, - Repo: referred.Repo, - Issue: referred, - RefIssueID: refopts.OrigIssue.ID, - RefCommentID: refCommentID, - }) - return err -} diff --git a/models/issue_comment.go b/models/issue_comment.go index edcc39a8af82b..c5132070ca1ea 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -144,18 +144,23 @@ type Comment struct { Invalidated bool // Reference issue and pull from comment - RefIssueID int64 `xorm:"index"` - RefCommentID int64 `xorm:"index"` - RefIssue *Issue `xorm:"-"` - RefComment *Comment `xorm:"-"` + RefIssueID int64 `xorm:"index"` + RefCommentID int64 `xorm:"index"` + RefAction XRefAction `xorm:"SMALLINT"` + RefIssue *Issue `xorm:"-"` + RefComment *Comment `xorm:"-"` } // LoadIssue loads issue from database func (c *Comment) LoadIssue() (err error) { + return c.loadIssue(x) +} + +func (c *Comment) loadIssue(e Engine) (err error) { if c.Issue != nil { return nil } - c.Issue, err = GetIssueByID(c.IssueID) + c.Issue, err = getIssueByID(e, c.IssueID) return } @@ -593,6 +598,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err Patch: opts.Patch, RefIssueID: opts.RefIssueID, RefCommentID: opts.RefCommentID, + RefAction: opts.RefAction, } if _, err = e.Insert(comment); err != nil { return nil, err @@ -608,7 +614,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err // Check references to other issues/pulls if opts.Type == CommentTypeCode || opts.Type == CommentTypeComment { - if err := comment.LoadIssue(); err != nil { + if err := comment.loadIssue(e); err != nil { return nil, err } refopts := &ParseReferencesOptions{ @@ -878,6 +884,7 @@ type CreateCommentOptions struct { Attachments []string // UUIDs of attachments RefIssueID int64 RefCommentID int64 + RefAction XRefAction } // CreateComment creates comment of issue or commit. diff --git a/models/issue_xref.go b/models/issue_xref.go new file mode 100644 index 0000000000000..310e07cfbb764 --- /dev/null +++ b/models/issue_xref.go @@ -0,0 +1,143 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + "regexp" + "strconv" + + "github.com/go-xorm/xorm" +) + +var ( + // TODO: Unify all regexp treatment of cross references in one place + + // issueNumericPattern matches string that references to a numeric issue, e.g. #1287 + issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(#[0-9]+)(?:\s|$|\)|\]|:|\.(\s|$))`) + // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository + // e.g. gogits/gogs#12345 + // crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+#[0-9]+)(?:\s|$|\)|\]|\.(\s|$))`) +) + +// XRefAction represents the kind of effect a cross reference has once is resolved +type XRefAction int64 + +const ( + // XRefActionNone means the cross-reference is a mention (commit, etc.) + XRefActionNone XRefAction = iota + // XRefActionCloses means the cross-reference should close an issue if it is resolved + XRefActionCloses // Not implemented yet + // XRefActionReopens means the cross-reference should reopen an issue if it is resolved + XRefActionReopens // Not implemented yet +) + +type crossReference struct { + Issue *Issue + Action XRefAction +} + +// ParseReferencesOptions represents a comment or issue that might make references to other issues +type ParseReferencesOptions struct { + Type CommentType + Doer *User + OrigIssue *Issue + OrigComment *Comment +} + +func (issue *Issue) parseCommentReferences(e *xorm.Session, refopts *ParseReferencesOptions, content string) error { + xreflist, err := issue.getCrossReferences(e, refopts, content) + if err != nil { + return err + } + for _, xref := range xreflist { + if err = addCommentReference(e, refopts, xref); err != nil { + return err + } + } + return nil +} + +func (issue *Issue) getCrossReferences(e *xorm.Session, refopts *ParseReferencesOptions, content string) ([]*crossReference, error) { + xreflist := make([]*crossReference,0,5) + var xref *crossReference + + // Issues in the same repository + // FIXME: Should we support IssueNameStyleAlphanumeric? + matches := issueNumericPattern.FindAllStringSubmatch(content, -1) + for _, match := range matches { + if i, err := strconv.ParseInt(match[1][1:], 10, 64); err == nil { + if err = refopts.OrigIssue.loadRepo(e); err != nil { + return nil, err + } + if xref, err = issue.checkCommentReference(e, refopts, issue.Repo, i); err != nil { + return nil, err + } + if xref != nil { + xreflist = issue.updateCrossReferenceList(xreflist, xref) + } + } + } + + // Issues in other repositories + // GAP: TODO: use crossReferenceIssueNumericPattern to parse references to other repos + + return xreflist, nil +} + +func (issue *Issue) updateCrossReferenceList(list []*crossReference, xref *crossReference) []*crossReference { + if xref.Issue.ID == issue.ID { + return list + } + for i, r := range list { + if r.Issue.ID == xref.Issue.ID { + if xref.Action != XRefActionNone { + list[i].Action = xref.Action + } + return list + } + } + return append(list, xref) +} + +func (issue *Issue) checkCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, repo *Repository, index int64) (*crossReference, error) { + refIssue := &Issue{RepoID: repo.ID, Index: index} + if has, _ := e.Get(refIssue); !has { + return nil, nil + } + if err := refIssue.loadRepo(e); err != nil { + return nil, err + } + // Check user permissions + if refIssue.Repo.ID != refopts.OrigIssue.Repo.ID { + perm, err := getUserRepoPermission(e, refIssue.Repo, refopts.Doer) + if err != nil { + return nil, err + } + if !perm.CanReadIssuesOrPulls(refIssue.IsPull) { + return nil, nil + } + } + return &crossReference { + Issue: refIssue, + Action: XRefActionNone, + }, nil +} + +func addCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, xref *crossReference) error { + var refCommentID int64 + if refopts.OrigComment != nil { + refCommentID = refopts.OrigComment.ID + } + _, err := createComment(e, &CreateCommentOptions{ + Type: refopts.Type, + Doer: refopts.Doer, + Repo: xref.Issue.Repo, + Issue: xref.Issue, + RefIssueID: refopts.OrigIssue.ID, + RefCommentID: refCommentID, + RefAction: xref.Action, + }) + return err +} From e4c21f63ad5292eae10271cf503a6aba6666644c Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Tue, 10 Sep 2019 23:54:02 -0300 Subject: [PATCH 07/30] Make fmt --- models/issue_comment.go | 10 +++++----- models/issue_xref.go | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/models/issue_comment.go b/models/issue_comment.go index c5132070ca1ea..72139587f9e6f 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -144,11 +144,11 @@ type Comment struct { Invalidated bool // Reference issue and pull from comment - RefIssueID int64 `xorm:"index"` - RefCommentID int64 `xorm:"index"` - RefAction XRefAction `xorm:"SMALLINT"` - RefIssue *Issue `xorm:"-"` - RefComment *Comment `xorm:"-"` + RefIssueID int64 `xorm:"index"` + RefCommentID int64 `xorm:"index"` + RefAction XRefAction `xorm:"SMALLINT"` + RefIssue *Issue `xorm:"-"` + RefComment *Comment `xorm:"-"` } // LoadIssue loads issue from database diff --git a/models/issue_xref.go b/models/issue_xref.go index 310e07cfbb764..9340d0fb6f367 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -26,16 +26,16 @@ type XRefAction int64 const ( // XRefActionNone means the cross-reference is a mention (commit, etc.) - XRefActionNone XRefAction = iota + XRefActionNone XRefAction = iota // XRefActionCloses means the cross-reference should close an issue if it is resolved - XRefActionCloses // Not implemented yet + XRefActionCloses // Not implemented yet // XRefActionReopens means the cross-reference should reopen an issue if it is resolved - XRefActionReopens // Not implemented yet + XRefActionReopens // Not implemented yet ) type crossReference struct { - Issue *Issue - Action XRefAction + Issue *Issue + Action XRefAction } // ParseReferencesOptions represents a comment or issue that might make references to other issues @@ -60,7 +60,7 @@ func (issue *Issue) parseCommentReferences(e *xorm.Session, refopts *ParseRefere } func (issue *Issue) getCrossReferences(e *xorm.Session, refopts *ParseReferencesOptions, content string) ([]*crossReference, error) { - xreflist := make([]*crossReference,0,5) + xreflist := make([]*crossReference, 0, 5) var xref *crossReference // Issues in the same repository @@ -108,7 +108,7 @@ func (issue *Issue) checkCommentReference(e *xorm.Session, refopts *ParseReferen } if err := refIssue.loadRepo(e); err != nil { return nil, err - } + } // Check user permissions if refIssue.Repo.ID != refopts.OrigIssue.Repo.ID { perm, err := getUserRepoPermission(e, refIssue.Repo, refopts.Doer) @@ -119,8 +119,8 @@ func (issue *Issue) checkCommentReference(e *xorm.Session, refopts *ParseReferen return nil, nil } } - return &crossReference { - Issue: refIssue, + return &crossReference{ + Issue: refIssue, Action: XRefActionNone, }, nil } From 9f85400e6825fc33662b4ef4c3fc02cf8c23b49b Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Wed, 11 Sep 2019 00:54:18 -0300 Subject: [PATCH 08/30] Filter out xref comment if user can't see the referencing issue --- models/issue_comment.go | 15 +++++++++++++-- models/issue_xref.go | 2 ++ routers/repo/issue.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/models/issue_comment.go b/models/issue_comment.go index 72139587f9e6f..aedfb106bd365 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -144,11 +144,13 @@ type Comment struct { Invalidated bool // Reference issue and pull from comment + RefRepoID int64 `xorm:"index"` RefIssueID int64 `xorm:"index"` RefCommentID int64 `xorm:"index"` RefAction XRefAction `xorm:"SMALLINT"` - RefIssue *Issue `xorm:"-"` - RefComment *Comment `xorm:"-"` + RefIsPull bool + RefIssue *Issue `xorm:"-"` + RefComment *Comment `xorm:"-"` } // LoadIssue loads issue from database @@ -596,9 +598,11 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err TreePath: opts.TreePath, ReviewID: opts.ReviewID, Patch: opts.Patch, + RefRepoID: opts.RefRepoID, RefIssueID: opts.RefIssueID, RefCommentID: opts.RefCommentID, RefAction: opts.RefAction, + RefIsPull: opts.RefIsPull, } if _, err = e.Insert(comment); err != nil { return nil, err @@ -882,9 +886,11 @@ type CreateCommentOptions struct { ReviewID int64 Content string Attachments []string // UUIDs of attachments + RefRepoID int64 RefIssueID int64 RefCommentID int64 RefAction XRefAction + RefIsPull bool } // CreateComment creates comment of issue or commit. @@ -1203,3 +1209,8 @@ func fetchCodeCommentsByReview(e Engine, issue *Issue, currentUser *User, review func FetchCodeComments(issue *Issue, currentUser *User) (CodeComments, error) { return fetchCodeComments(x, issue, currentUser) } + +// CommentTypeIsRef returns true if CommentType is a reference from another issue +func CommentTypeIsRef(t CommentType) bool { + return t == CommentTypeCommentRef || t == CommentTypePullRef || t == CommentTypeIssueRef +} diff --git a/models/issue_xref.go b/models/issue_xref.go index 9340d0fb6f367..e554704f77b9a 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -135,9 +135,11 @@ func addCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, xref Doer: refopts.Doer, Repo: xref.Issue.Repo, Issue: xref.Issue, + RefRepoID: refopts.OrigIssue.RepoID, RefIssueID: refopts.OrigIssue.ID, RefCommentID: refCommentID, RefAction: xref.Action, + RefIsPull: xref.Issue.IsPull, }) return err } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index dc09a650fed7f..7fe8873e46c27 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -648,6 +648,12 @@ func ViewIssue(ctx *context.Context) { return } + err = filterXRefComments(ctx, issue) + if err != nil { + ctx.ServerError("GetIssueByIndex", err) + return + } + ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title) var iw *models.IssueWatch @@ -1571,3 +1577,26 @@ func addParticipant(poster *models.User, participants []*models.User) []*models. } return append(participants, poster) } + +func filterXRefComments(ctx *context.Context, issue *models.Issue) error { + // Remove comments that the user has no permissions to see + for i := 0; i < len(issue.Comments); { + c := issue.Comments[i] + if models.CommentTypeIsRef(c.Type) && c.RefRepoID != issue.RepoID && c.RefRepoID != 0 { + repo, err := models.GetRepositoryByID(c.RefRepoID) + if err != nil { + return err + } + perm, err := models.GetUserRepoPermission(repo, ctx.User) + if err != nil { + return err + } + if !perm.CanReadIssuesOrPulls(c.RefIsPull) { + issue.Comments = append(issue.Comments[:i], issue.Comments[i+1:]...) + continue + } + } + i++ + } + return nil +} From dd8cbe248195ec238c895e6bb35c5f59f8a2b68d Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Wed, 11 Sep 2019 01:14:52 -0300 Subject: [PATCH 09/30] Add TODOs --- models/issue.go | 3 +++ models/issue_comment.go | 2 ++ routers/api/v1/repo/issue_comment.go | 1 + 3 files changed, 6 insertions(+) diff --git a/models/issue.go b/models/issue.go index f4bdaa7e1d8d4..03bf03c38b7ee 100644 --- a/models/issue.go +++ b/models/issue.go @@ -943,6 +943,8 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) { return fmt.Errorf("UpdateIssueCols: %v", err) } + // GAP: TODO: remove/add cross references + mode, _ := AccessLevel(issue.Poster, issue.Repo) if issue.IsPull { issue.PullRequest.Issue = issue @@ -1783,6 +1785,7 @@ func SearchIssueIDsByKeyword(kw string, repoID int64, limit, start int) (int64, } func updateIssue(e Engine, issue *Issue) error { + // GAP: TODO: remove/add cross references _, err := e.ID(issue.ID).AllCols().Update(issue) if err != nil { return err diff --git a/models/issue_comment.go b/models/issue_comment.go index aedfb106bd365..7bbf9295056bd 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -1049,6 +1049,8 @@ func UpdateComment(doer *User, c *Comment, oldContent string) error { return err } + // GAP: TODO: remove/add cross references + mode, _ := AccessLevel(doer, c.Issue.Repo) if err := PrepareWebhooks(c.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentEdited, diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index 18fa1d3eb2a07..66727a6d44698 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -67,6 +67,7 @@ func ListIssueComments(ctx *context.APIContext) { ctx.Error(500, "FindComments", err) return } + // GAP: TODO: filter out cross-references if err := models.CommentList(comments).LoadPosters(); err != nil { ctx.Error(500, "LoadPosters", err) From ecba703ca1c6ea53ca103049ea27fb6194d19187 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Wed, 11 Sep 2019 19:53:16 -0300 Subject: [PATCH 10/30] Add cross reference --- models/issue_xref.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/models/issue_xref.go b/models/issue_xref.go index e554704f77b9a..1ea54b800cd7d 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -15,10 +15,10 @@ var ( // TODO: Unify all regexp treatment of cross references in one place // issueNumericPattern matches string that references to a numeric issue, e.g. #1287 - issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(#[0-9]+)(?:\s|$|\)|\]|:|\.(\s|$))`) + issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(?:#)([0-9]+)(?:\s|$|\)|\]|:|\.(\s|$))`) // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository // e.g. gogits/gogs#12345 - // crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+#[0-9]+)(?:\s|$|\)|\]|\.(\s|$))`) + crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+)#([0-9]+)(?:\s|$|\)|\]|\.(\s|$))`) ) // XRefAction represents the kind of effect a cross reference has once is resolved @@ -67,11 +67,11 @@ func (issue *Issue) getCrossReferences(e *xorm.Session, refopts *ParseReferences // FIXME: Should we support IssueNameStyleAlphanumeric? matches := issueNumericPattern.FindAllStringSubmatch(content, -1) for _, match := range matches { - if i, err := strconv.ParseInt(match[1][1:], 10, 64); err == nil { + if index, err := strconv.ParseInt(match[1], 10, 64); err == nil { if err = refopts.OrigIssue.loadRepo(e); err != nil { return nil, err } - if xref, err = issue.checkCommentReference(e, refopts, issue.Repo, i); err != nil { + if xref, err = issue.checkCommentReference(e, refopts, issue.Repo, index); err != nil { return nil, err } if xref != nil { @@ -81,7 +81,27 @@ func (issue *Issue) getCrossReferences(e *xorm.Session, refopts *ParseReferences } // Issues in other repositories - // GAP: TODO: use crossReferenceIssueNumericPattern to parse references to other repos + matches = crossReferenceIssueNumericPattern.FindAllStringSubmatch(content, -1) + for _, match := range matches { + if index, err := strconv.ParseInt(match[3], 10, 64); err == nil { + repo, err := GetRepositoryByOwnerAndName(match[1], match[2]) + if err != nil { + if IsErrRepoNotExist(err) { + continue + } + return nil, err + } + if err = refopts.OrigIssue.loadRepo(e); err != nil { + return nil, err + } + if xref, err = issue.checkCommentReference(e, refopts, repo, index); err != nil { + return nil, err + } + if xref != nil { + xreflist = issue.updateCrossReferenceList(xreflist, xref) + } + } + } return xreflist, nil } From cc85b0ffd451ccb39f625305382009edec12ede0 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 01:39:14 -0300 Subject: [PATCH 11/30] Rearrange code; add cross-repository references --- models/issue.go | 7 +- models/issue_comment.go | 38 ++++----- models/issue_xref.go | 85 ++++++++++++++----- options/locale/locale_en-US.ini | 2 + routers/repo/issue.go | 6 +- .../repo/issue/view_content/comments.tmpl | 14 ++- 6 files changed, 100 insertions(+), 52 deletions(-) diff --git a/models/issue.go b/models/issue.go index 03bf03c38b7ee..992ea39f7c248 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1176,12 +1176,7 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) { if err = opts.Issue.loadAttributes(e); err != nil { return err } - refopts := &ParseReferencesOptions{ - Type: CommentTypeIssueRef, - Doer: doer, - OrigIssue: opts.Issue, - } - return opts.Issue.parseCommentReferences(e, refopts, opts.Issue.Content) + return opts.Issue.addIssueReferences(e, doer) } // NewIssue creates new issue with labels for repository. diff --git a/models/issue_comment.go b/models/issue_comment.go index 7bbf9295056bd..37c091af5b9c6 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -149,8 +149,9 @@ type Comment struct { RefCommentID int64 `xorm:"index"` RefAction XRefAction `xorm:"SMALLINT"` RefIsPull bool - RefIssue *Issue `xorm:"-"` - RefComment *Comment `xorm:"-"` + RefIssue *Issue `xorm:"-"` + RefRepo *Repository `xorm:"-"` + RefComment *Comment `xorm:"-"` } // LoadIssue loads issue from database @@ -616,20 +617,8 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err return nil, err } - // Check references to other issues/pulls - if opts.Type == CommentTypeCode || opts.Type == CommentTypeComment { - if err := comment.loadIssue(e); err != nil { - return nil, err - } - refopts := &ParseReferencesOptions{ - Type: CommentTypeCommentRef, - Doer: opts.Doer, - OrigIssue: comment.Issue, - OrigComment: comment, - } - if err = comment.Issue.parseCommentReferences(e, refopts, opts.Content); err != nil { - return nil, err - } + if err = comment.addCommentReferences(e, opts.Doer); err != nil { + return nil, err } return comment, nil @@ -1031,7 +1020,9 @@ func FindComments(opts FindCommentsOptions) ([]*Comment, error) { // UpdateComment updates information of comment. func UpdateComment(doer *User, c *Comment, oldContent string) error { - if _, err := x.ID(c.ID).AllCols().Update(c); err != nil { + sess := x.NewSession() + defer sess.Close() + if _, err := sess.ID(c.ID).AllCols().Update(c); err != nil { return err } @@ -1045,11 +1036,15 @@ func UpdateComment(doer *User, c *Comment, oldContent string) error { if err := c.Issue.LoadAttributes(); err != nil { return err } - if err := c.loadPoster(x); err != nil { + if err := c.loadPoster(sess); err != nil { + return err + } + if err := c.neuterReferencingComments(sess); err != nil { + return err + } + if err := c.addCommentReferences(sess, doer); err != nil { return err } - - // GAP: TODO: remove/add cross references mode, _ := AccessLevel(doer, c.Issue.Repo) if err := PrepareWebhooks(c.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{ @@ -1113,6 +1108,9 @@ func DeleteComment(doer *User, comment *Comment) error { if err := comment.loadPoster(x); err != nil { return err } + if err := comment.neuterReferencingComments(x); err != nil { + return err + } mode, _ := AccessLevel(doer, comment.Issue.Repo) diff --git a/models/issue_xref.go b/models/issue_xref.go index 1ea54b800cd7d..e918702076b86 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -31,6 +31,8 @@ const ( XRefActionCloses // Not implemented yet // XRefActionReopens means the cross-reference should reopen an issue if it is resolved XRefActionReopens // Not implemented yet + // XRefActionNeutered means the cross-reference will no longer affect the source + XRefActionNeutered ) type crossReference struct { @@ -38,28 +40,28 @@ type crossReference struct { Action XRefAction } -// ParseReferencesOptions represents a comment or issue that might make references to other issues -type ParseReferencesOptions struct { +// crossReferencesContext is context to pass along findCrossReference functions +type crossReferencesContext struct { Type CommentType Doer *User OrigIssue *Issue OrigComment *Comment } -func (issue *Issue) parseCommentReferences(e *xorm.Session, refopts *ParseReferencesOptions, content string) error { - xreflist, err := issue.getCrossReferences(e, refopts, content) +func (issue *Issue) findCrossReferences(e *xorm.Session, ctx *crossReferencesContext, content string) error { + xreflist, err := ctx.OrigIssue.getCrossReferences(e, ctx, content) if err != nil { return err } for _, xref := range xreflist { - if err = addCommentReference(e, refopts, xref); err != nil { + if err = newCrossReference(e, ctx, xref); err != nil { return err } } return nil } -func (issue *Issue) getCrossReferences(e *xorm.Session, refopts *ParseReferencesOptions, content string) ([]*crossReference, error) { +func (issue *Issue) getCrossReferences(e Engine, ctx *crossReferencesContext, content string) ([]*crossReference, error) { xreflist := make([]*crossReference, 0, 5) var xref *crossReference @@ -68,14 +70,14 @@ func (issue *Issue) getCrossReferences(e *xorm.Session, refopts *ParseReferences matches := issueNumericPattern.FindAllStringSubmatch(content, -1) for _, match := range matches { if index, err := strconv.ParseInt(match[1], 10, 64); err == nil { - if err = refopts.OrigIssue.loadRepo(e); err != nil { + if err = ctx.OrigIssue.loadRepo(e); err != nil { return nil, err } - if xref, err = issue.checkCommentReference(e, refopts, issue.Repo, index); err != nil { + if xref, err = ctx.OrigIssue.isValidCommentReference(e, ctx, issue.Repo, index); err != nil { return nil, err } if xref != nil { - xreflist = issue.updateCrossReferenceList(xreflist, xref) + xreflist = ctx.OrigIssue.updateCrossReferenceList(xreflist, xref) } } } @@ -91,10 +93,10 @@ func (issue *Issue) getCrossReferences(e *xorm.Session, refopts *ParseReferences } return nil, err } - if err = refopts.OrigIssue.loadRepo(e); err != nil { + if err = ctx.OrigIssue.loadRepo(e); err != nil { return nil, err } - if xref, err = issue.checkCommentReference(e, refopts, repo, index); err != nil { + if xref, err = issue.isValidCommentReference(e, ctx, repo, index); err != nil { return nil, err } if xref != nil { @@ -121,7 +123,7 @@ func (issue *Issue) updateCrossReferenceList(list []*crossReference, xref *cross return append(list, xref) } -func (issue *Issue) checkCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, repo *Repository, index int64) (*crossReference, error) { +func (issue *Issue) isValidCommentReference(e Engine, ctx *crossReferencesContext, repo *Repository, index int64) (*crossReference, error) { refIssue := &Issue{RepoID: repo.ID, Index: index} if has, _ := e.Get(refIssue); !has { return nil, nil @@ -130,8 +132,8 @@ func (issue *Issue) checkCommentReference(e *xorm.Session, refopts *ParseReferen return nil, err } // Check user permissions - if refIssue.Repo.ID != refopts.OrigIssue.Repo.ID { - perm, err := getUserRepoPermission(e, refIssue.Repo, refopts.Doer) + if refIssue.Repo.ID != ctx.OrigIssue.Repo.ID { + perm, err := getUserRepoPermission(e, refIssue.Repo, ctx.Doer) if err != nil { return nil, err } @@ -145,21 +147,62 @@ func (issue *Issue) checkCommentReference(e *xorm.Session, refopts *ParseReferen }, nil } -func addCommentReference(e *xorm.Session, refopts *ParseReferencesOptions, xref *crossReference) error { +func newCrossReference(e *xorm.Session, ctx *crossReferencesContext, xref *crossReference) error { var refCommentID int64 - if refopts.OrigComment != nil { - refCommentID = refopts.OrigComment.ID + if ctx.OrigComment != nil { + refCommentID = ctx.OrigComment.ID } _, err := createComment(e, &CreateCommentOptions{ - Type: refopts.Type, - Doer: refopts.Doer, + Type: ctx.Type, + Doer: ctx.Doer, Repo: xref.Issue.Repo, Issue: xref.Issue, - RefRepoID: refopts.OrigIssue.RepoID, - RefIssueID: refopts.OrigIssue.ID, + RefRepoID: ctx.OrigIssue.RepoID, + RefIssueID: ctx.OrigIssue.ID, RefCommentID: refCommentID, RefAction: xref.Action, RefIsPull: xref.Issue.IsPull, }) return err } + +func (issue *Issue) addIssueReferences(e *xorm.Session, doer *User) error { + ctx := &crossReferencesContext{ + Type: CommentTypeIssueRef, + Doer: doer, + OrigIssue: issue, + } + return issue.findCrossReferences(e, ctx, issue.Content) +} + +func (comment *Comment) addCommentReferences(e *xorm.Session, doer *User) error { + if comment.Type != CommentTypeCode && comment.Type != CommentTypeComment { + return nil + } + if err := comment.loadIssue(e); err != nil { + return err + } + ctx := &crossReferencesContext{ + Type: CommentTypeCommentRef, + Doer: doer, + OrigIssue: comment.Issue, + OrigComment: comment, + } + return comment.Issue.findCrossReferences(e, ctx, comment.Content) +} + +func (comment *Comment) neuterReferencingComments(e Engine) error { + active := make([]*Comment, 0, 10) + err := e.Where("`ref_comment_id` = ?", comment.ID). + And("`ref_action` IN (?, ?)", XRefActionCloses, XRefActionReopens). + Find(&active) + if err != nil || len(active) == 0 { + return err + } + ids := make([]int64, len(active)) + for i, c := range active { + ids[i] = c.ID + } + _, err = e.In("id", ids).Cols("`ref_action`").Update(&Comment{RefAction: XRefActionNeutered}) + return err +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 947cc53b531d4..bef9d49ae2274 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -838,6 +838,8 @@ issues.reopened_at = `reopened %[2]s` issues.commit_ref_at = `referenced this issue from a commit %[2]s` issues.ref_issue_at = `referenced this issue %[1]s` issues.ref_pull_at = `referenced this pull request %[1]s` +issues.ref_issue_ext_at = `referenced this issue from %[1]s %[2]s` +issues.ref_pull_ext_at = `referenced this pull request from %[1]s %[2]s` issues.poster = Poster issues.collaborator = Collaborator issues.owner = Owner diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 7fe8873e46c27..28a512834010d 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -1583,11 +1583,13 @@ func filterXRefComments(ctx *context.Context, issue *models.Issue) error { for i := 0; i < len(issue.Comments); { c := issue.Comments[i] if models.CommentTypeIsRef(c.Type) && c.RefRepoID != issue.RepoID && c.RefRepoID != 0 { - repo, err := models.GetRepositoryByID(c.RefRepoID) + var err error + // Set RefRepo for description in template + c.RefRepo, err = models.GetRepositoryByID(c.RefRepoID) if err != nil { return err } - perm, err := models.GetUserRepoPermission(repo, ctx.User) + perm, err := models.GetUserRepoPermission(c.RefRepo, ctx.User) if err != nil { return err } diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index a6d6490f8de53..564d9d8bcae4b 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -103,10 +103,18 @@ {{.Poster.GetDisplayName}} - {{if .Issue.IsPull}} - {{$.i18n.Tr "repo.issues.ref_pull_at" $createdStr | Safe}} + {{if (eq .RefRepoID .Issue.RepoID)}} + {{if .Issue.IsPull}} + {{$.i18n.Tr "repo.issues.ref_pull_at" $createdStr | Safe}} + {{else}} + {{$.i18n.Tr "repo.issues.ref_issue_at" $createdStr | Safe}} + {{end}} {{else}} - {{$.i18n.Tr "repo.issues.ref_issue_at" $createdStr | Safe}} + {{if .Issue.IsPull}} + {{$.i18n.Tr "repo.issues.ref_pull_ext_at" .RefRepo.FullName $createdStr | Safe}} + {{else}} + {{$.i18n.Tr "repo.issues.ref_issue_ext_at" .RefRepo.FullName $createdStr | Safe}} + {{end}} {{end}} From 145e1a9672ae04f4f0ce50c5d2f5452919a27eb2 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 13:14:41 -0300 Subject: [PATCH 12/30] Striketrhough obsolete references --- templates/repo/issue/view_content/comments.tmpl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 564d9d8bcae4b..a5f25954c7f97 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -102,6 +102,7 @@ + {{if eq .RefAction 3}}{{end}} {{.Poster.GetDisplayName}} {{if (eq .RefRepoID .Issue.RepoID)}} {{if .Issue.IsPull}} @@ -117,6 +118,7 @@ {{end}} {{end}} + {{if eq .RefAction 3}}{{end}}
{{.RefIssueTitle | Str2html}} {{.RefIssueIdent | Str2html}} From 3fd20ebb54f21ae9eb500ae3c9dade6c4e4f5e32 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 13:17:57 -0300 Subject: [PATCH 13/30] Remove unnecesary TODO --- routers/api/v1/repo/issue_comment.go | 1 - 1 file changed, 1 deletion(-) diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index 66727a6d44698..18fa1d3eb2a07 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -67,7 +67,6 @@ func ListIssueComments(ctx *context.APIContext) { ctx.Error(500, "FindComments", err) return } - // GAP: TODO: filter out cross-references if err := models.CommentList(comments).LoadPosters(); err != nil { ctx.Error(500, "LoadPosters", err) From ba36bc6acd39807f0d7f180da633a23cfb6e5f99 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 13:24:13 -0300 Subject: [PATCH 14/30] Add "not supported" note --- routers/api/v1/repo/issue.go | 1 + routers/api/v1/repo/pull.go | 1 + 2 files changed, 2 insertions(+) diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 402da82f07817..8cd48eea8946e 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -351,6 +351,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { } } + // Note: adding cross references from API is not supported ATM if err = models.UpdateIssue(issue); err != nil { ctx.Error(500, "UpdateIssue", err) return diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 5ac542ec207b2..b116add5eeb65 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -420,6 +420,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { } } + // Note: adding cross references from API is not supported ATM if err = models.UpdateIssue(issue); err != nil { ctx.Error(500, "UpdateIssue", err) return From 0bf921653fd146f77121ad025287cf64ba8fb356 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 13:44:41 -0300 Subject: [PATCH 15/30] Support for edits and deletes, and issue title --- go.mod | 2 ++ models/issue.go | 33 ++++++++++++++++--- models/issue_comment.go | 36 +++++++++++++-------- models/issue_xref.go | 71 ++++++++++++++++++++++++----------------- 4 files changed, 96 insertions(+), 46 deletions(-) diff --git a/go.mod b/go.mod index d72269abdd664..8651099eaa9a6 100644 --- a/go.mod +++ b/go.mod @@ -75,6 +75,8 @@ require ( github.com/mattn/go-sqlite3 v1.11.0 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae // indirect github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 diff --git a/models/issue.go b/models/issue.go index 992ea39f7c248..5a0b0f68b9db2 100644 --- a/models/issue.go +++ b/models/issue.go @@ -595,8 +595,9 @@ func (issue *Issue) ClearLabels(doer *User) (err error) { if err = sess.Commit(); err != nil { return fmt.Errorf("Commit: %v", err) } + sess.Close() - if err = issue.loadPoster(x); err != nil { + if err = issue.LoadPoster(); err != nil { return fmt.Errorf("loadPoster: %v", err) } @@ -870,9 +871,18 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { return fmt.Errorf("createChangeTitleComment: %v", err) } + if err = issue.neuterReferencingComments(sess); err != nil { + return err + } + + if err = issue.addIssueReferences(sess, doer); err != nil { + return err + } + if err = sess.Commit(); err != nil { return err } + sess.Close() mode, _ := AccessLevel(issue.Poster, issue.Repo) if issue.IsPull { @@ -939,11 +949,26 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) { oldContent := issue.Content issue.Content = content - if err = UpdateIssueCols(issue, "content"); err != nil { + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + if err = updateIssueCols(sess, issue, "content"); err != nil { return fmt.Errorf("UpdateIssueCols: %v", err) } + if err = issue.neuterReferencingComments(sess); err != nil { + return err + } + if err = issue.addIssueReferences(sess, doer); err != nil { + return err + } - // GAP: TODO: remove/add cross references + if err = sess.Commit(); err != nil { + return err + } + sess.Close() mode, _ := AccessLevel(issue.Poster, issue.Repo) if issue.IsPull { @@ -1203,6 +1228,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in if err = sess.Commit(); err != nil { return fmt.Errorf("Commit: %v", err) } + sess.Close() if err = NotifyWatchers(&Action{ ActUserID: issue.Poster.ID, @@ -1780,7 +1806,6 @@ func SearchIssueIDsByKeyword(kw string, repoID int64, limit, start int) (int64, } func updateIssue(e Engine, issue *Issue) error { - // GAP: TODO: remove/add cross references _, err := e.ID(issue.ID).AllCols().Update(issue) if err != nil { return err diff --git a/models/issue_comment.go b/models/issue_comment.go index 37c091af5b9c6..0353aed0a189c 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -143,14 +143,16 @@ type Comment struct { ReviewID int64 `xorm:"index"` Invalidated bool - // Reference issue and pull from comment - RefRepoID int64 `xorm:"index"` + // Reference an issue or pull from another comment, issue or PR + // All information is about the origin of the reference + RefRepoID int64 `xorm:"index"` // Repo where the referencing RefIssueID int64 `xorm:"index"` - RefCommentID int64 `xorm:"index"` - RefAction XRefAction `xorm:"SMALLINT"` + RefCommentID int64 `xorm:"index"` // 0 if origin is Issue title or content (or PR's) + RefAction XRefAction `xorm:"SMALLINT"` // What hapens if RefIssueID resolves RefIsPull bool - RefIssue *Issue `xorm:"-"` + RefRepo *Repository `xorm:"-"` + RefIssue *Issue `xorm:"-"` RefComment *Comment `xorm:"-"` } @@ -1022,27 +1024,31 @@ func FindComments(opts FindCommentsOptions) ([]*Comment, error) { func UpdateComment(doer *User, c *Comment, oldContent string) error { sess := x.NewSession() defer sess.Close() - if _, err := sess.ID(c.ID).AllCols().Update(c); err != nil { + if err := sess.Begin(); err != nil { return err } - if err := c.LoadPoster(); err != nil { + if _, err := sess.ID(c.ID).AllCols().Update(c); err != nil { return err } - if err := c.LoadIssue(); err != nil { + if err := c.loadIssue(sess); err != nil { return err } - - if err := c.Issue.LoadAttributes(); err != nil { + if err := c.neuterReferencingComments(sess); err != nil { return err } - if err := c.loadPoster(sess); err != nil { + if err := c.addCommentReferences(sess, doer); err != nil { return err } - if err := c.neuterReferencingComments(sess); err != nil { + if err := sess.Commit(); err != nil { + return fmt.Errorf("Commit: %v", err) + } + sess.Close() + + if err := c.LoadPoster(); err != nil { return err } - if err := c.addCommentReferences(sess, doer); err != nil { + if err := c.Issue.LoadAttributes(); err != nil { return err } @@ -1090,6 +1096,10 @@ func DeleteComment(doer *User, comment *Comment) error { return err } + if err := comment.neuterReferencingComments(sess); err != nil { + return err + } + if err := sess.Commit(); err != nil { return err } diff --git a/models/issue_xref.go b/models/issue_xref.go index e918702076b86..2f4b855bfd5ec 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -26,13 +26,13 @@ type XRefAction int64 const ( // XRefActionNone means the cross-reference is a mention (commit, etc.) - XRefActionNone XRefAction = iota + XRefActionNone XRefAction = iota // 0 // XRefActionCloses means the cross-reference should close an issue if it is resolved - XRefActionCloses // Not implemented yet + XRefActionCloses // 1 - not implemented yet // XRefActionReopens means the cross-reference should reopen an issue if it is resolved - XRefActionReopens // Not implemented yet + XRefActionReopens // 2 - Not implemented yet // XRefActionNeutered means the cross-reference will no longer affect the source - XRefActionNeutered + XRefActionNeutered // 3 ) type crossReference struct { @@ -48,6 +48,31 @@ type crossReferencesContext struct { OrigComment *Comment } +func (issue *Issue) addIssueReferences(e *xorm.Session, doer *User) error { + ctx := &crossReferencesContext{ + Type: CommentTypeIssueRef, + Doer: doer, + OrigIssue: issue, + } + return issue.findCrossReferences(e, ctx, issue.Title + "\n" + issue.Content) +} + +func (comment *Comment) addCommentReferences(e *xorm.Session, doer *User) error { + if comment.Type != CommentTypeCode && comment.Type != CommentTypeComment { + return nil + } + if err := comment.loadIssue(e); err != nil { + return err + } + ctx := &crossReferencesContext{ + Type: CommentTypeCommentRef, + Doer: doer, + OrigIssue: comment.Issue, + OrigComment: comment, + } + return comment.Issue.findCrossReferences(e, ctx, comment.Content) +} + func (issue *Issue) findCrossReferences(e *xorm.Session, ctx *crossReferencesContext, content string) error { xreflist, err := ctx.OrigIssue.getCrossReferences(e, ctx, content) if err != nil { @@ -166,36 +191,24 @@ func newCrossReference(e *xorm.Session, ctx *crossReferencesContext, xref *cross return err } -func (issue *Issue) addIssueReferences(e *xorm.Session, doer *User) error { - ctx := &crossReferencesContext{ - Type: CommentTypeIssueRef, - Doer: doer, - OrigIssue: issue, - } - return issue.findCrossReferences(e, ctx, issue.Content) +func (issue *Issue) neuterReferencingComments(e Engine) error { + return neuterReferencingComments(e, issue.ID, 0) } -func (comment *Comment) addCommentReferences(e *xorm.Session, doer *User) error { - if comment.Type != CommentTypeCode && comment.Type != CommentTypeComment { - return nil - } - if err := comment.loadIssue(e); err != nil { - return err - } - ctx := &crossReferencesContext{ - Type: CommentTypeCommentRef, - Doer: doer, - OrigIssue: comment.Issue, - OrigComment: comment, - } - return comment.Issue.findCrossReferences(e, ctx, comment.Content) +func (comment *Comment) neuterReferencingComments(e Engine) error { + return neuterReferencingComments(e, 0, comment.ID) } -func (comment *Comment) neuterReferencingComments(e Engine) error { +func neuterReferencingComments(e Engine, issueID int64, commentID int64) error { active := make([]*Comment, 0, 10) - err := e.Where("`ref_comment_id` = ?", comment.ID). - And("`ref_action` IN (?, ?)", XRefActionCloses, XRefActionReopens). - Find(&active) + sess := e.Where("`ref_action` IN (?, ?, ?)", XRefActionNone, XRefActionCloses, XRefActionReopens) + if issueID != 0 { + sess = sess.And("`ref_issue_id` = ?", issueID) + } + if commentID != 0 { + sess = sess.And("`ref_comment_id` = ?", commentID) + } + err := sess.Find(&active) if err != nil || len(active) == 0 { return err } From fefd40500a67e25cfbbd8903beaac6c35939e9ad Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 13:48:02 -0300 Subject: [PATCH 16/30] Revert changes to go.mod --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index 8651099eaa9a6..d72269abdd664 100644 --- a/go.mod +++ b/go.mod @@ -75,8 +75,6 @@ require ( github.com/mattn/go-sqlite3 v1.11.0 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae // indirect github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 From 7f4aedf3d58e0e55597c4c4460ccb801e46d824e Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 13:50:24 -0300 Subject: [PATCH 17/30] Fix fmt --- models/issue.go | 2 +- models/issue_comment.go | 12 ++++++------ models/issue_xref.go | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/models/issue.go b/models/issue.go index 5a0b0f68b9db2..c3e479e7d9b55 100644 --- a/models/issue.go +++ b/models/issue.go @@ -874,7 +874,7 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { if err = issue.neuterReferencingComments(sess); err != nil { return err } - + if err = issue.addIssueReferences(sess, doer); err != nil { return err } diff --git a/models/issue_comment.go b/models/issue_comment.go index 0353aed0a189c..35397e5e2746d 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -145,15 +145,15 @@ type Comment struct { // Reference an issue or pull from another comment, issue or PR // All information is about the origin of the reference - RefRepoID int64 `xorm:"index"` // Repo where the referencing + RefRepoID int64 `xorm:"index"` // Repo where the referencing RefIssueID int64 `xorm:"index"` - RefCommentID int64 `xorm:"index"` // 0 if origin is Issue title or content (or PR's) - RefAction XRefAction `xorm:"SMALLINT"` // What hapens if RefIssueID resolves + RefCommentID int64 `xorm:"index"` // 0 if origin is Issue title or content (or PR's) + RefAction XRefAction `xorm:"SMALLINT"` // What hapens if RefIssueID resolves RefIsPull bool - RefRepo *Repository `xorm:"-"` - RefIssue *Issue `xorm:"-"` - RefComment *Comment `xorm:"-"` + RefRepo *Repository `xorm:"-"` + RefIssue *Issue `xorm:"-"` + RefComment *Comment `xorm:"-"` } // LoadIssue loads issue from database diff --git a/models/issue_xref.go b/models/issue_xref.go index 2f4b855bfd5ec..c00317d888fc5 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -26,13 +26,13 @@ type XRefAction int64 const ( // XRefActionNone means the cross-reference is a mention (commit, etc.) - XRefActionNone XRefAction = iota // 0 + XRefActionNone XRefAction = iota // 0 // XRefActionCloses means the cross-reference should close an issue if it is resolved - XRefActionCloses // 1 - not implemented yet + XRefActionCloses // 1 - not implemented yet // XRefActionReopens means the cross-reference should reopen an issue if it is resolved - XRefActionReopens // 2 - Not implemented yet + XRefActionReopens // 2 - Not implemented yet // XRefActionNeutered means the cross-reference will no longer affect the source - XRefActionNeutered // 3 + XRefActionNeutered // 3 ) type crossReference struct { @@ -54,7 +54,7 @@ func (issue *Issue) addIssueReferences(e *xorm.Session, doer *User) error { Doer: doer, OrigIssue: issue, } - return issue.findCrossReferences(e, ctx, issue.Title + "\n" + issue.Content) + return issue.findCrossReferences(e, ctx, issue.Title+"\n"+issue.Content) } func (comment *Comment) addCommentReferences(e *xorm.Session, doer *User) error { From 4f24ee9cf229ed7e621508ed35740674481f1a15 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 14:49:45 -0300 Subject: [PATCH 18/30] Add support for xref from API --- models/issue.go | 19 ++++++++++++++++++- routers/api/v1/repo/issue.go | 1 - routers/api/v1/repo/issue_comment.go | 2 ++ routers/api/v1/repo/pull.go | 1 - 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/models/issue.go b/models/issue.go index c3e479e7d9b55..73b299af14545 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1815,7 +1815,24 @@ func updateIssue(e Engine, issue *Issue) error { // UpdateIssue updates all fields of given issue. func UpdateIssue(issue *Issue) error { - return updateIssue(x, issue) + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + if err := updateIssue(sess, issue); err != nil { + return err + } + if err := issue.neuterReferencingComments(sess); err != nil { + return err + } + if err := issue.loadPoster(sess); err != nil { + return err + } + if err := issue.addIssueReferences(sess, issue.Poster); err != nil { + return err + } + return sess.Commit() } // UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it. diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 8cd48eea8946e..402da82f07817 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -351,7 +351,6 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { } } - // Note: adding cross references from API is not supported ATM if err = models.UpdateIssue(issue); err != nil { ctx.Error(500, "UpdateIssue", err) return diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index 18fa1d3eb2a07..9616642fe7b87 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -189,6 +189,7 @@ func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOpti return } + // Note: adding cross references from API is not supported ATM comment, err := models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Body, nil) if err != nil { ctx.Error(500, "CreateIssueComment", err) @@ -299,6 +300,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) oldContent := comment.Content comment.Content = form.Body + // Note: adding cross references from API is not supported ATM if err := models.UpdateComment(ctx.User, comment, oldContent); err != nil { ctx.Error(500, "UpdateComment", err) return diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index b116add5eeb65..5ac542ec207b2 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -420,7 +420,6 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { } } - // Note: adding cross references from API is not supported ATM if err = models.UpdateIssue(issue); err != nil { ctx.Error(500, "UpdateIssue", err) return From 2c838664fa44267fbbe10fdd479ebe1253f431ec Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 15:33:52 -0300 Subject: [PATCH 19/30] Add first integration test --- integrations/issue_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/integrations/issue_test.go b/integrations/issue_test.go index 10084000db30f..57c316affcde0 100644 --- a/integrations/issue_test.go +++ b/integrations/issue_test.go @@ -184,3 +184,28 @@ func TestIssueCommentClose(t *testing.T) { val := htmlDoc.doc.Find(".comment-list .comments .comment .render-content p").First().Text() assert.Equal(t, "Description", val) } + +func TestCrossReferences(t *testing.T) { + prepareTestEnv(t) + session := loginUser(t, "user2") + + repoID := int64(1) + issueBaseURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description") + indexBaseStr := issueBaseURL[strings.LastIndexByte(issueBaseURL, '/')+1:] + indexBase, err := strconv.Atoi(indexBaseStr) + assert.NoError(t, err, "Invalid issue href: %s", issueBaseURL) + + issueBase := &models.Issue{RepoID: repoID, Index: int64(indexBase)} + models.AssertExistsAndLoadBean(t, issueBase) + + issueRefURL := testNewIssue(t, session, "user2", "repo1", "TitleXRef", "Description ref #" + indexBaseStr) + indexRefStr := issueRefURL[strings.LastIndexByte(issueRefURL, '/')+1:] + indexRef, err := strconv.Atoi(indexRefStr) + assert.NoError(t, err, "Invalid issue href: %s", issueRefURL) + + issueRef := &models.Issue{RepoID: repoID, Index: int64(indexRef)} + models.AssertExistsAndLoadBean(t, issueRef) + + models.AssertExistsAndLoadBean(t, &models.Comment{IssueID: issueBase.ID, RefIssueID: issueRef.ID, RefAction: models.XRefActionNone}) + +} \ No newline at end of file From c4fcdd6781783e16f40a31e744bcbedfb1b56fe3 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 18:15:02 -0300 Subject: [PATCH 20/30] Add integration tests --- integrations/issue_test.go | 115 +++++++++++++++++++++++++++++++------ models/issue_xref.go | 4 +- models/repo.go | 6 +- 3 files changed, 103 insertions(+), 22 deletions(-) diff --git a/integrations/issue_test.go b/integrations/issue_test.go index 57c316affcde0..94da9fe735fb2 100644 --- a/integrations/issue_test.go +++ b/integrations/issue_test.go @@ -5,6 +5,7 @@ package integrations import ( + "fmt" "net/http" "path" "strconv" @@ -136,7 +137,7 @@ func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content return issueURL } -func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) { +func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) int64 { req := NewRequest(t, "GET", issueURL) resp := session.MakeRequest(t, req, http.StatusOK) @@ -161,6 +162,13 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, val := htmlDoc.doc.Find(".comment-list .comments .comment .render-content p").Eq(commentCount).Text() assert.Equal(t, content, val) + + idStr, has := htmlDoc.doc.Find(".comment-list .comments .comment").Eq(commentCount).Attr("id") + assert.True(t, has) + id, err := strconv.Atoi(idStr) + assert.NoError(t, err) + + return int64(id) } func TestNewIssue(t *testing.T) { @@ -185,27 +193,96 @@ func TestIssueCommentClose(t *testing.T) { assert.Equal(t, "Description", val) } -func TestCrossReferences(t *testing.T) { +func TestIssueCrossReference(t *testing.T) { prepareTestEnv(t) - session := loginUser(t, "user2") - - repoID := int64(1) - issueBaseURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description") - indexBaseStr := issueBaseURL[strings.LastIndexByte(issueBaseURL, '/')+1:] - indexBase, err := strconv.Atoi(indexBaseStr) - assert.NoError(t, err, "Invalid issue href: %s", issueBaseURL) - issueBase := &models.Issue{RepoID: repoID, Index: int64(indexBase)} - models.AssertExistsAndLoadBean(t, issueBase) + // Issue that will be referenced + _, issueBase := testIssueWithBean(t, "user2", 1, "Title", "Description") + + // Ref from issue title + issueRefURL, issueRef := testIssueWithBean(t, "user2", 1, fmt.Sprintf("Title ref #%d", issueBase.Index), "Description") + models.AssertExistsAndLoadBean(t, &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: models.XRefActionNone}) + + // Edit title, neuter ref + testIssueChangeInfo(t, "user2", issueRefURL, "title", "Title no ref") + models.AssertExistsAndLoadBean(t, &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: models.XRefActionNeutered}) + + // Ref from issue content + issueRefURL, issueRef = testIssueWithBean(t, "user2", 1, "TitleXRef", fmt.Sprintf("Description ref #%d", issueBase.Index)) + models.AssertExistsAndLoadBean(t, &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: models.XRefActionNone}) + + // Edit content, neuter ref + testIssueChangeInfo(t, "user2", issueRefURL, "content", "Description no ref") + models.AssertExistsAndLoadBean(t, &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: models.XRefActionNeutered}) + + // Ref from a comment + session := loginUser(t, "user2") + commentID := testIssueAddComment(t, session, issueRefURL, fmt.Sprintf("Adding ref from comment #%d", issueBase.Index), "") + comment := &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: commentID, + RefIsPull: false, + RefAction: models.XRefActionNone} + models.AssertExistsAndLoadBean(t, comment) + + // Ref from a different repository + issueRefURL, issueRef = testIssueWithBean(t, "user12", 10, "TitleXRef", fmt.Sprintf("Description ref user2/repo1#%d", issueBase.Index)) + models.AssertExistsAndLoadBean(t, &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 10, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: models.XRefActionNone}) +} - issueRefURL := testNewIssue(t, session, "user2", "repo1", "TitleXRef", "Description ref #" + indexBaseStr) - indexRefStr := issueRefURL[strings.LastIndexByte(issueRefURL, '/')+1:] - indexRef, err := strconv.Atoi(indexRefStr) - assert.NoError(t, err, "Invalid issue href: %s", issueRefURL) +func testIssueWithBean(t *testing.T, user string, repoID int64, title, content string) (string, *models.Issue) { + session := loginUser(t, user) + issueURL := testNewIssue(t, session, user, fmt.Sprintf("repo%d", repoID), title, content) + indexStr := issueURL[strings.LastIndexByte(issueURL, '/')+1:] + index, err := strconv.Atoi(indexStr) + assert.NoError(t, err, "Invalid issue href: %s", issueURL) + issue := &models.Issue{RepoID: repoID, Index: int64(index)} + models.AssertExistsAndLoadBean(t, issue) + return issueURL, issue +} - issueRef := &models.Issue{RepoID: repoID, Index: int64(indexRef)} - models.AssertExistsAndLoadBean(t, issueRef) +func testIssueChangeInfo(t *testing.T, user, issueURL, info string, value string) { + session := loginUser(t, user) - models.AssertExistsAndLoadBean(t, &models.Comment{IssueID: issueBase.ID, RefIssueID: issueRef.ID, RefAction: models.XRefActionNone}) + req := NewRequest(t, "GET", issueURL) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) -} \ No newline at end of file + req = NewRequestWithValues(t, "POST", path.Join(issueURL,info), map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + info: value, + }) + _ = session.MakeRequest(t, req, http.StatusOK) +} diff --git a/models/issue_xref.go b/models/issue_xref.go index c00317d888fc5..509e6fa5147d4 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -86,7 +86,7 @@ func (issue *Issue) findCrossReferences(e *xorm.Session, ctx *crossReferencesCon return nil } -func (issue *Issue) getCrossReferences(e Engine, ctx *crossReferencesContext, content string) ([]*crossReference, error) { +func (issue *Issue) getCrossReferences(e *xorm.Session, ctx *crossReferencesContext, content string) ([]*crossReference, error) { xreflist := make([]*crossReference, 0, 5) var xref *crossReference @@ -111,7 +111,7 @@ func (issue *Issue) getCrossReferences(e Engine, ctx *crossReferencesContext, co matches = crossReferenceIssueNumericPattern.FindAllStringSubmatch(content, -1) for _, match := range matches { if index, err := strconv.ParseInt(match[3], 10, 64); err == nil { - repo, err := GetRepositoryByOwnerAndName(match[1], match[2]) + repo, err := getRepositoryByOwnerAndName(e, match[1], match[2]) if err != nil { if IsErrRepoNotExist(err) { continue diff --git a/models/repo.go b/models/repo.go index 3e593ce4ed4f2..5574659535f7f 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1956,8 +1956,12 @@ func DeleteRepository(doer *User, uid, repoID int64) error { // GetRepositoryByOwnerAndName returns the repository by given ownername and reponame. func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) { + return getRepositoryByOwnerAndName(x, ownerName, repoName) +} + +func getRepositoryByOwnerAndName(e Engine, ownerName, repoName string) (*Repository, error) { var repo Repository - has, err := x.Select("repository.*"). + has, err := e.Table("repository").Select("repository.*"). Join("INNER", "`user`", "`user`.id = repository.owner_id"). Where("repository.lower_name = ?", strings.ToLower(repoName)). And("`user`.lower_name = ?", strings.ToLower(ownerName)). From 31304f560aea9a91fb86afecad09ff0ab26fd16c Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 18:16:45 -0300 Subject: [PATCH 21/30] Correct formatting --- integrations/issue_test.go | 68 +++++++++++++++++++------------------- models/issue.go | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/integrations/issue_test.go b/integrations/issue_test.go index 94da9fe735fb2..4a3794d19c49e 100644 --- a/integrations/issue_test.go +++ b/integrations/issue_test.go @@ -202,64 +202,64 @@ func TestIssueCrossReference(t *testing.T) { // Ref from issue title issueRefURL, issueRef := testIssueWithBean(t, "user2", 1, fmt.Sprintf("Title ref #%d", issueBase.Index), "Description") models.AssertExistsAndLoadBean(t, &models.Comment{ - IssueID: issueBase.ID, - RefRepoID: 1, - RefIssueID: issueRef.ID, + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, RefCommentID: 0, - RefIsPull: false, - RefAction: models.XRefActionNone}) + RefIsPull: false, + RefAction: models.XRefActionNone}) // Edit title, neuter ref testIssueChangeInfo(t, "user2", issueRefURL, "title", "Title no ref") models.AssertExistsAndLoadBean(t, &models.Comment{ - IssueID: issueBase.ID, - RefRepoID: 1, - RefIssueID: issueRef.ID, + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, RefCommentID: 0, - RefIsPull: false, - RefAction: models.XRefActionNeutered}) + RefIsPull: false, + RefAction: models.XRefActionNeutered}) // Ref from issue content issueRefURL, issueRef = testIssueWithBean(t, "user2", 1, "TitleXRef", fmt.Sprintf("Description ref #%d", issueBase.Index)) models.AssertExistsAndLoadBean(t, &models.Comment{ - IssueID: issueBase.ID, - RefRepoID: 1, - RefIssueID: issueRef.ID, + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, RefCommentID: 0, - RefIsPull: false, - RefAction: models.XRefActionNone}) + RefIsPull: false, + RefAction: models.XRefActionNone}) // Edit content, neuter ref testIssueChangeInfo(t, "user2", issueRefURL, "content", "Description no ref") models.AssertExistsAndLoadBean(t, &models.Comment{ - IssueID: issueBase.ID, - RefRepoID: 1, - RefIssueID: issueRef.ID, + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, RefCommentID: 0, - RefIsPull: false, - RefAction: models.XRefActionNeutered}) + RefIsPull: false, + RefAction: models.XRefActionNeutered}) // Ref from a comment session := loginUser(t, "user2") commentID := testIssueAddComment(t, session, issueRefURL, fmt.Sprintf("Adding ref from comment #%d", issueBase.Index), "") comment := &models.Comment{ - IssueID: issueBase.ID, - RefRepoID: 1, - RefIssueID: issueRef.ID, + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, RefCommentID: commentID, - RefIsPull: false, - RefAction: models.XRefActionNone} + RefIsPull: false, + RefAction: models.XRefActionNone} models.AssertExistsAndLoadBean(t, comment) - + // Ref from a different repository issueRefURL, issueRef = testIssueWithBean(t, "user12", 10, "TitleXRef", fmt.Sprintf("Description ref user2/repo1#%d", issueBase.Index)) models.AssertExistsAndLoadBean(t, &models.Comment{ - IssueID: issueBase.ID, - RefRepoID: 10, - RefIssueID: issueRef.ID, + IssueID: issueBase.ID, + RefRepoID: 10, + RefIssueID: issueRef.ID, RefCommentID: 0, - RefIsPull: false, - RefAction: models.XRefActionNone}) + RefIsPull: false, + RefAction: models.XRefActionNone}) } func testIssueWithBean(t *testing.T, user string, repoID int64, title, content string) (string, *models.Issue) { @@ -280,9 +280,9 @@ func testIssueChangeInfo(t *testing.T, user, issueURL, info string, value string resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - req = NewRequestWithValues(t, "POST", path.Join(issueURL,info), map[string]string{ - "_csrf": htmlDoc.GetCSRF(), - info: value, + req = NewRequestWithValues(t, "POST", path.Join(issueURL, info), map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + info: value, }) _ = session.MakeRequest(t, req, http.StatusOK) } diff --git a/models/issue.go b/models/issue.go index 73b299af14545..7196dee50e335 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1826,7 +1826,7 @@ func UpdateIssue(issue *Issue) error { if err := issue.neuterReferencingComments(sess); err != nil { return err } - if err := issue.loadPoster(sess); err != nil { + if err := issue.loadPoster(sess); err != nil { return err } if err := issue.addIssueReferences(sess, issue.Poster); err != nil { From 8b2b0d64c80f0dc85a32988a377ce14e7b54d534 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 18:41:27 -0300 Subject: [PATCH 22/30] Fix add comment test --- integrations/issue_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrations/issue_test.go b/integrations/issue_test.go index 4a3794d19c49e..0b153607ee645 100644 --- a/integrations/issue_test.go +++ b/integrations/issue_test.go @@ -163,11 +163,11 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, val := htmlDoc.doc.Find(".comment-list .comments .comment .render-content p").Eq(commentCount).Text() assert.Equal(t, content, val) - idStr, has := htmlDoc.doc.Find(".comment-list .comments .comment").Eq(commentCount).Attr("id") + idAttr, has := htmlDoc.doc.Find(".comment-list .comments .comment").Eq(commentCount).Attr("id") + idStr := idAttr[strings.LastIndexByte(idAttr, '-')+1:] assert.True(t, has) id, err := strconv.Atoi(idStr) assert.NoError(t, err) - return int64(id) } From fcc6bf0684f60161570b6a1d896b767583e9739f Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 19:48:31 -0300 Subject: [PATCH 23/30] Add migration --- models/migrations/migrations.go | 2 ++ models/migrations/v94.go | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 models/migrations/v94.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 15e021c05a796..108782a7ca530 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -242,6 +242,8 @@ var migrations = []Migration{ NewMigration("remove orphaned repository index statuses", removeLingeringIndexStatus), // v93 -> v94 NewMigration("add email notification enabled preference to user", addEmailNotificationEnabledToUser), + // v94 -> v95 + NewMigration("add table columns for cross referencing issues", addCrossReferenceCOlumns), } // Migrate database to current version diff --git a/models/migrations/v94.go b/models/migrations/v94.go new file mode 100644 index 0000000000000..4bc861e2103aa --- /dev/null +++ b/models/migrations/v94.go @@ -0,0 +1,20 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import "github.com/go-xorm/xorm" + +func addCrossReferenceCOlumns(x *xorm.Engine) error { + // Comment see models/comment.go + type Comment struct { + RefRepoID int64 `xorm:"index"` + RefIssueID int64 `xorm:"index"` + RefCommentID int64 `xorm:"index"` + RefAction int64 `xorm:"SMALLINT"` + RefIsPull bool + } + + return x.Sync2(new(Comment)) +} From 610291c15830415915e5068dced70ca063a99902 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 12 Sep 2019 20:39:49 -0300 Subject: [PATCH 24/30] Remove outdated comments; fix typo --- models/migrations/migrations.go | 2 +- models/migrations/v94.go | 2 +- routers/api/v1/repo/issue_comment.go | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 108782a7ca530..c7f748e8092ba 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -243,7 +243,7 @@ var migrations = []Migration{ // v93 -> v94 NewMigration("add email notification enabled preference to user", addEmailNotificationEnabledToUser), // v94 -> v95 - NewMigration("add table columns for cross referencing issues", addCrossReferenceCOlumns), + NewMigration("add table columns for cross referencing issues", addCrossReferenceColumns), } // Migrate database to current version diff --git a/models/migrations/v94.go b/models/migrations/v94.go index 4bc861e2103aa..f6e4e41c4886f 100644 --- a/models/migrations/v94.go +++ b/models/migrations/v94.go @@ -6,7 +6,7 @@ package migrations import "github.com/go-xorm/xorm" -func addCrossReferenceCOlumns(x *xorm.Engine) error { +func addCrossReferenceColumns(x *xorm.Engine) error { // Comment see models/comment.go type Comment struct { RefRepoID int64 `xorm:"index"` diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index 9616642fe7b87..18fa1d3eb2a07 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -189,7 +189,6 @@ func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOpti return } - // Note: adding cross references from API is not supported ATM comment, err := models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Body, nil) if err != nil { ctx.Error(500, "CreateIssueComment", err) @@ -300,7 +299,6 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) oldContent := comment.Content comment.Content = form.Body - // Note: adding cross references from API is not supported ATM if err := models.UpdateComment(ctx.User, comment, oldContent); err != nil { ctx.Error(500, "UpdateComment", err) return From 5312037a270fe5acca93a5a4c28c6563f4d4a667 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Sat, 14 Sep 2019 15:03:52 -0300 Subject: [PATCH 25/30] Some code refactoring and rearranging --- models/issue.go | 14 +-- models/issue_comment.go | 73 ++-------------- models/issue_xref.go | 188 +++++++++++++++++++++++++++++----------- 3 files changed, 150 insertions(+), 125 deletions(-) diff --git a/models/issue.go b/models/issue.go index 7196dee50e335..28c53e5cd0ccc 100644 --- a/models/issue.go +++ b/models/issue.go @@ -871,11 +871,11 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { return fmt.Errorf("createChangeTitleComment: %v", err) } - if err = issue.neuterReferencingComments(sess); err != nil { + if err = issue.neuterCrossReferences(sess); err != nil { return err } - if err = issue.addIssueReferences(sess, doer); err != nil { + if err = issue.addCrossReferences(sess, doer); err != nil { return err } @@ -958,10 +958,10 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) { if err = updateIssueCols(sess, issue, "content"); err != nil { return fmt.Errorf("UpdateIssueCols: %v", err) } - if err = issue.neuterReferencingComments(sess); err != nil { + if err = issue.neuterCrossReferences(sess); err != nil { return err } - if err = issue.addIssueReferences(sess, doer); err != nil { + if err = issue.addCrossReferences(sess, doer); err != nil { return err } @@ -1201,7 +1201,7 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) { if err = opts.Issue.loadAttributes(e); err != nil { return err } - return opts.Issue.addIssueReferences(e, doer) + return opts.Issue.addCrossReferences(e, doer) } // NewIssue creates new issue with labels for repository. @@ -1823,13 +1823,13 @@ func UpdateIssue(issue *Issue) error { if err := updateIssue(sess, issue); err != nil { return err } - if err := issue.neuterReferencingComments(sess); err != nil { + if err := issue.neuterCrossReferences(sess); err != nil { return err } if err := issue.loadPoster(sess); err != nil { return err } - if err := issue.addIssueReferences(sess, issue.Poster); err != nil { + if err := issue.addCrossReferences(sess, issue.Poster); err != nil { return err } return sess.Commit() diff --git a/models/issue_comment.go b/models/issue_comment.go index 35397e5e2746d..0bb313c30bbf9 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -297,64 +297,6 @@ func (c *Comment) EventTag() string { return "event-" + com.ToStr(c.ID) } -// LoadRefComment loads comment that created this reference from database -func (c *Comment) LoadRefComment() (err error) { - if c.RefComment != nil { - return nil - } - c.RefComment, err = GetCommentByID(c.RefCommentID) - return -} - -// LoadRefIssue loads comment that created this reference from database -func (c *Comment) LoadRefIssue() (err error) { - if c.RefIssue != nil { - return nil - } - c.RefIssue, err = GetIssueByID(c.RefIssueID) - if err == nil { - err = c.RefIssue.loadRepo(x) - } - return -} - -// RefCommentHTMLURL returns the HTML URL for the comment that created this reference -func (c *Comment) RefCommentHTMLURL() string { - if err := c.LoadRefComment(); err != nil { // Silently dropping errors :unamused: - log.Error("LoadRefComment(%d): %v", c.RefCommentID, err) - return "" - } - return c.RefComment.HTMLURL() -} - -// RefIssueHTMLURL returns the HTML URL of the issue where this reference was created -func (c *Comment) RefIssueHTMLURL() string { - if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: - log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) - return "" - } - return c.RefIssue.HTMLURL() -} - -// RefIssueTitle returns the title of the issue where this reference was created -func (c *Comment) RefIssueTitle() string { - if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: - log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) - return "" - } - return c.RefIssue.Title -} - -// RefIssueIdent returns the user friendly identity (e.g. "#1234") of the issue where this reference was created -func (c *Comment) RefIssueIdent() string { - if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: - log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) - return "" - } - // FIXME: check this name for cross-repository references (#7901 if it gets merged) - return "#" + com.ToStr(c.RefIssue.Index) -} - // LoadLabel if comment.Type is CommentTypeLabel, then load Label func (c *Comment) LoadLabel() error { var label Label @@ -619,7 +561,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err return nil, err } - if err = comment.addCommentReferences(e, opts.Doer); err != nil { + if err = comment.addCrossReferences(e, opts.Doer); err != nil { return nil, err } @@ -1034,10 +976,10 @@ func UpdateComment(doer *User, c *Comment, oldContent string) error { if err := c.loadIssue(sess); err != nil { return err } - if err := c.neuterReferencingComments(sess); err != nil { + if err := c.neuterCrossReferences(sess); err != nil { return err } - if err := c.addCommentReferences(sess, doer); err != nil { + if err := c.addCrossReferences(sess, doer); err != nil { return err } if err := sess.Commit(); err != nil { @@ -1096,7 +1038,7 @@ func DeleteComment(doer *User, comment *Comment) error { return err } - if err := comment.neuterReferencingComments(sess); err != nil { + if err := comment.neuterCrossReferences(sess); err != nil { return err } @@ -1118,7 +1060,7 @@ func DeleteComment(doer *User, comment *Comment) error { if err := comment.loadPoster(x); err != nil { return err } - if err := comment.neuterReferencingComments(x); err != nil { + if err := comment.neuterCrossReferences(x); err != nil { return err } @@ -1219,8 +1161,3 @@ func fetchCodeCommentsByReview(e Engine, issue *Issue, currentUser *User, review func FetchCodeComments(issue *Issue, currentUser *User) (CodeComments, error) { return fetchCodeComments(x, issue, currentUser) } - -// CommentTypeIsRef returns true if CommentType is a reference from another issue -func CommentTypeIsRef(t CommentType) bool { - return t == CommentTypeCommentRef || t == CommentTypePullRef || t == CommentTypeIssueRef -} diff --git a/models/issue_xref.go b/models/issue_xref.go index 509e6fa5147d4..ea0a68ec90ace 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -8,7 +8,10 @@ import ( "regexp" "strconv" + "code.gitea.io/gitea/modules/log" + "github.com/go-xorm/xorm" + "github.com/unknwon/com" ) var ( @@ -48,29 +51,67 @@ type crossReferencesContext struct { OrigComment *Comment } -func (issue *Issue) addIssueReferences(e *xorm.Session, doer *User) error { - ctx := &crossReferencesContext{ - Type: CommentTypeIssueRef, - Doer: doer, - OrigIssue: issue, +func newCrossReference(e *xorm.Session, ctx *crossReferencesContext, xref *crossReference) error { + var refCommentID int64 + if ctx.OrigComment != nil { + refCommentID = ctx.OrigComment.ID } - return issue.findCrossReferences(e, ctx, issue.Title+"\n"+issue.Content) + _, err := createComment(e, &CreateCommentOptions{ + Type: ctx.Type, + Doer: ctx.Doer, + Repo: xref.Issue.Repo, + Issue: xref.Issue, + RefRepoID: ctx.OrigIssue.RepoID, + RefIssueID: ctx.OrigIssue.ID, + RefCommentID: refCommentID, + RefAction: xref.Action, + RefIsPull: xref.Issue.IsPull, + }) + return err } -func (comment *Comment) addCommentReferences(e *xorm.Session, doer *User) error { - if comment.Type != CommentTypeCode && comment.Type != CommentTypeComment { - return nil +func neuterCrossReferences(e Engine, issueID int64, commentID int64) error { + active := make([]*Comment, 0, 10) + sess := e.Where("`ref_action` IN (?, ?, ?)", XRefActionNone, XRefActionCloses, XRefActionReopens) + if issueID != 0 { + sess = sess.And("`ref_issue_id` = ?", issueID) } - if err := comment.loadIssue(e); err != nil { + if commentID != 0 { + sess = sess.And("`ref_comment_id` = ?", commentID) + } + err := sess.Find(&active) + if err != nil || len(active) == 0 { return err } + ids := make([]int64, len(active)) + for i, c := range active { + ids[i] = c.ID + } + _, err = e.In("id", ids).Cols("`ref_action`").Update(&Comment{RefAction: XRefActionNeutered}) + return err +} + +// .___ +// | | ______ ________ __ ____ +// | |/ ___// ___/ | \_/ __ \ +// | |\___ \ \___ \| | /\ ___/ +// |___/____ >____ >____/ \___ > +// \/ \/ \/ +// + +func (issue *Issue) addCrossReferences(e *xorm.Session, doer *User) error { + var commentType CommentType + if issue.IsPull { + commentType = CommentTypePullRef + } else { + commentType = CommentTypeIssueRef + } ctx := &crossReferencesContext{ - Type: CommentTypeCommentRef, - Doer: doer, - OrigIssue: comment.Issue, - OrigComment: comment, + Type: commentType, + Doer: doer, + OrigIssue: issue, } - return comment.Issue.findCrossReferences(e, ctx, comment.Content) + return issue.findCrossReferences(e, ctx, issue.Title+"\n"+issue.Content) } func (issue *Issue) findCrossReferences(e *xorm.Session, ctx *crossReferencesContext, content string) error { @@ -172,50 +213,97 @@ func (issue *Issue) isValidCommentReference(e Engine, ctx *crossReferencesContex }, nil } -func newCrossReference(e *xorm.Session, ctx *crossReferencesContext, xref *crossReference) error { - var refCommentID int64 - if ctx.OrigComment != nil { - refCommentID = ctx.OrigComment.ID +func (issue *Issue) neuterCrossReferences(e Engine) error { + return neuterCrossReferences(e, issue.ID, 0) +} + +// _________ __ +// \_ ___ \ ____ _____ _____ ____ _____/ |_ +// / \ \/ / _ \ / \ / \_/ __ \ / \ __\ +// \ \___( <_> ) Y Y \ Y Y \ ___/| | \ | +// \______ /\____/|__|_| /__|_| /\___ >___| /__| +// \/ \/ \/ \/ \/ +// + +func (comment *Comment) addCrossReferences(e *xorm.Session, doer *User) error { + if comment.Type != CommentTypeCode && comment.Type != CommentTypeComment { + return nil } - _, err := createComment(e, &CreateCommentOptions{ - Type: ctx.Type, - Doer: ctx.Doer, - Repo: xref.Issue.Repo, - Issue: xref.Issue, - RefRepoID: ctx.OrigIssue.RepoID, - RefIssueID: ctx.OrigIssue.ID, - RefCommentID: refCommentID, - RefAction: xref.Action, - RefIsPull: xref.Issue.IsPull, - }) - return err + if err := comment.loadIssue(e); err != nil { + return err + } + ctx := &crossReferencesContext{ + Type: CommentTypeCommentRef, + Doer: doer, + OrigIssue: comment.Issue, + OrigComment: comment, + } + return comment.Issue.findCrossReferences(e, ctx, comment.Content) } -func (issue *Issue) neuterReferencingComments(e Engine) error { - return neuterReferencingComments(e, issue.ID, 0) +func (comment *Comment) neuterCrossReferences(e Engine) error { + return neuterCrossReferences(e, 0, comment.ID) } -func (comment *Comment) neuterReferencingComments(e Engine) error { - return neuterReferencingComments(e, 0, comment.ID) +// LoadRefComment loads comment that created this reference from database +func (comment *Comment) LoadRefComment() (err error) { + if comment.RefComment != nil { + return nil + } + comment.RefComment, err = GetCommentByID(comment.RefCommentID) + return } -func neuterReferencingComments(e Engine, issueID int64, commentID int64) error { - active := make([]*Comment, 0, 10) - sess := e.Where("`ref_action` IN (?, ?, ?)", XRefActionNone, XRefActionCloses, XRefActionReopens) - if issueID != 0 { - sess = sess.And("`ref_issue_id` = ?", issueID) +// LoadRefIssue loads comment that created this reference from database +func (comment *Comment) LoadRefIssue() (err error) { + if comment.RefIssue != nil { + return nil } - if commentID != 0 { - sess = sess.And("`ref_comment_id` = ?", commentID) + comment.RefIssue, err = GetIssueByID(comment.RefIssueID) + if err == nil { + err = comment.RefIssue.loadRepo(x) } - err := sess.Find(&active) - if err != nil || len(active) == 0 { - return err + return +} + +// CommentTypeIsRef returns true if CommentType is a reference from another issue +func CommentTypeIsRef(t CommentType) bool { + return t == CommentTypeCommentRef || t == CommentTypePullRef || t == CommentTypeIssueRef +} + +// RefCommentHTMLURL returns the HTML URL for the comment that created this reference +func (comment *Comment) RefCommentHTMLURL() string { + if err := comment.LoadRefComment(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefComment(%d): %v", comment.RefCommentID, err) + return "" } - ids := make([]int64, len(active)) - for i, c := range active { - ids[i] = c.ID + return comment.RefComment.HTMLURL() +} + +// RefIssueHTMLURL returns the HTML URL of the issue where this reference was created +func (comment *Comment) RefIssueHTMLURL() string { + if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err) + return "" } - _, err = e.In("id", ids).Cols("`ref_action`").Update(&Comment{RefAction: XRefActionNeutered}) - return err + return comment.RefIssue.HTMLURL() +} + +// RefIssueTitle returns the title of the issue where this reference was created +func (comment *Comment) RefIssueTitle() string { + if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err) + return "" + } + return comment.RefIssue.Title +} + +// RefIssueIdent returns the user friendly identity (e.g. "#1234") of the issue where this reference was created +func (comment *Comment) RefIssueIdent() string { + if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err) + return "" + } + // FIXME: check this name for cross-repository references (#7901 if it gets merged) + return "#" + com.ToStr(comment.RefIssue.Index) } From f7ffc51d46545514e0fc6f8fc96d4ce64790cbfc Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Wed, 18 Sep 2019 01:13:55 -0300 Subject: [PATCH 26/30] Rename findCrossReferences to createCrossReferences --- models/issue_xref.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/issue_xref.go b/models/issue_xref.go index ea0a68ec90ace..6d935dc0613a9 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -111,10 +111,10 @@ func (issue *Issue) addCrossReferences(e *xorm.Session, doer *User) error { Doer: doer, OrigIssue: issue, } - return issue.findCrossReferences(e, ctx, issue.Title+"\n"+issue.Content) + return issue.createCrossReferences(e, ctx, issue.Title+"\n"+issue.Content) } -func (issue *Issue) findCrossReferences(e *xorm.Session, ctx *crossReferencesContext, content string) error { +func (issue *Issue) createCrossReferences(e *xorm.Session, ctx *crossReferencesContext, content string) error { xreflist, err := ctx.OrigIssue.getCrossReferences(e, ctx, content) if err != nil { return err @@ -238,7 +238,7 @@ func (comment *Comment) addCrossReferences(e *xorm.Session, doer *User) error { OrigIssue: comment.Issue, OrigComment: comment, } - return comment.Issue.findCrossReferences(e, ctx, comment.Content) + return comment.Issue.createCrossReferences(e, ctx, comment.Content) } func (comment *Comment) neuterCrossReferences(e Engine) error { From 9ffb74b04f8ce9f4b45a307993c7ca0541cc82a5 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Wed, 18 Sep 2019 01:22:07 -0300 Subject: [PATCH 27/30] Delete xrefs when repository is deleted --- models/repo.go | 1 + 1 file changed, 1 insertion(+) diff --git a/models/repo.go b/models/repo.go index 5574659535f7f..ffd1c30606b14 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1810,6 +1810,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error { &Notification{RepoID: repoID}, &CommitStatus{RepoID: repoID}, &RepoIndexerStatus{RepoID: repoID}, + &Comment{RefRepoID: repoID}, ); err != nil { return fmt.Errorf("deleteBeans: %v", err) } From 66392e158f7b53f458017ac2114e8956229ba68c Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Wed, 18 Sep 2019 02:43:35 -0300 Subject: [PATCH 28/30] Corrections as suggested by @lafriks --- models/issue_xref.go | 5 ++--- routers/repo/issue.go | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/models/issue_xref.go b/models/issue_xref.go index 6d935dc0613a9..3631bf3246ad7 100644 --- a/models/issue_xref.go +++ b/models/issue_xref.go @@ -79,15 +79,14 @@ func neuterCrossReferences(e Engine, issueID int64, commentID int64) error { if commentID != 0 { sess = sess.And("`ref_comment_id` = ?", commentID) } - err := sess.Find(&active) - if err != nil || len(active) == 0 { + if err := sess.Find(&active); err != nil || len(active) == 0 { return err } ids := make([]int64, len(active)) for i, c := range active { ids[i] = c.ID } - _, err = e.In("id", ids).Cols("`ref_action`").Update(&Comment{RefAction: XRefActionNeutered}) + _, err := e.In("id", ids).Cols("`ref_action`").Update(&Comment{RefAction: XRefActionNeutered}) return err } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 28a512834010d..b1113602bab4d 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -642,14 +642,12 @@ func ViewIssue(ctx *context.Context) { ctx.Data["RequireTribute"] = true renderAttachmentSettings(ctx) - err = issue.LoadAttributes() - if err != nil { + if err = issue.LoadAttributes(); err != nil { ctx.ServerError("GetIssueByIndex", err) return } - err = filterXRefComments(ctx, issue) - if err != nil { + if err = filterXRefComments(ctx, issue); err != nil { ctx.ServerError("GetIssueByIndex", err) return } From 49bbe79262069e4ffdcc19f0b1bedc178f9f6fc1 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Wed, 18 Sep 2019 02:48:17 -0300 Subject: [PATCH 29/30] Prepare for merge --- models/migrations/migrations.go | 2 +- models/migrations/{v94.go => v95.go} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename models/migrations/{v94.go => v95.go} (100%) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index c7f748e8092ba..0a242af75b2a8 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -242,7 +242,7 @@ var migrations = []Migration{ NewMigration("remove orphaned repository index statuses", removeLingeringIndexStatus), // v93 -> v94 NewMigration("add email notification enabled preference to user", addEmailNotificationEnabledToUser), - // v94 -> v95 + // v95 -> v96 NewMigration("add table columns for cross referencing issues", addCrossReferenceColumns), } diff --git a/models/migrations/v94.go b/models/migrations/v95.go similarity index 100% rename from models/migrations/v94.go rename to models/migrations/v95.go From 92246b51159a9afcde218435006aa4c778775665 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Thu, 19 Sep 2019 13:17:57 -0300 Subject: [PATCH 30/30] Fix log for errors --- routers/repo/issue.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/repo/issue.go b/routers/repo/issue.go index edaa6c659956a..b9083e20e92b1 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -644,12 +644,12 @@ func ViewIssue(ctx *context.Context) { renderAttachmentSettings(ctx) if err = issue.LoadAttributes(); err != nil { - ctx.ServerError("GetIssueByIndex", err) + ctx.ServerError("LoadAttributes", err) return } if err = filterXRefComments(ctx, issue); err != nil { - ctx.ServerError("GetIssueByIndex", err) + ctx.ServerError("filterXRefComments", err) return }