Skip to content

Reference issues from pull requests and other issues #8137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Sep 20, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
78c3778
Update ref comment
guillep2k Sep 9, 2019
b4d5dd3
Generate comment on simple ref
guillep2k Sep 10, 2019
235694a
Make fmt + remove unneeded repo load
guillep2k Sep 10, 2019
64e4b42
Add TODO comments
guillep2k Sep 10, 2019
7edef31
Add ref-check in issue creation; re-arrange template
guillep2k Sep 10, 2019
2fbdf4c
Merge branch 'master' of github.com:go-gitea/gitea into xref-issues
guillep2k Sep 11, 2019
11eb10c
Make unit tests pass; rearrange code
guillep2k Sep 11, 2019
e4c21f6
Make fmt
guillep2k Sep 11, 2019
9f85400
Filter out xref comment if user can't see the referencing issue
guillep2k Sep 11, 2019
dd8cbe2
Add TODOs
guillep2k Sep 11, 2019
272aeda
Merge branch 'master' of github.com:go-gitea/gitea into xref-issues
guillep2k Sep 11, 2019
ecba703
Add cross reference
guillep2k Sep 11, 2019
cc85b0f
Rearrange code; add cross-repository references
guillep2k Sep 12, 2019
c92b919
Merge branch 'master' of github.com:go-gitea/gitea into xref-issues
guillep2k Sep 12, 2019
145e1a9
Striketrhough obsolete references
guillep2k Sep 12, 2019
3fd20eb
Remove unnecesary TODO
guillep2k Sep 12, 2019
ba36bc6
Add "not supported" note
guillep2k Sep 12, 2019
0bf9216
Support for edits and deletes, and issue title
guillep2k Sep 12, 2019
fefd405
Revert changes to go.mod
guillep2k Sep 12, 2019
7f4aedf
Fix fmt
guillep2k Sep 12, 2019
7fef0b7
Merge branch 'master' of github.com:go-gitea/gitea into xref-issues
guillep2k Sep 12, 2019
4f24ee9
Add support for xref from API
guillep2k Sep 12, 2019
2c83866
Add first integration test
guillep2k Sep 12, 2019
c4fcdd6
Add integration tests
guillep2k Sep 12, 2019
31304f5
Correct formatting
guillep2k Sep 12, 2019
8b2b0d6
Fix add comment test
guillep2k Sep 12, 2019
fcc6bf0
Add migration
guillep2k Sep 12, 2019
610291c
Remove outdated comments; fix typo
guillep2k Sep 12, 2019
5312037
Some code refactoring and rearranging
guillep2k Sep 14, 2019
ca2f7bf
Merge branch 'master' into xref-issues
guillep2k Sep 14, 2019
f7ffc51
Rename findCrossReferences to createCrossReferences
guillep2k Sep 18, 2019
9ffb74b
Delete xrefs when repository is deleted
guillep2k Sep 18, 2019
66392e1
Corrections as suggested by @lafriks
guillep2k Sep 18, 2019
49bbe79
Prepare for merge
guillep2k Sep 18, 2019
d157ea8
Merge master into xref-issues
guillep2k Sep 18, 2019
92246b5
Fix log for errors
guillep2k Sep 19, 2019
ec51461
Merge branch 'master' into xref-issues
guillep2k Sep 19, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Make unit tests pass; rearrange code
  • Loading branch information
guillep2k committed Sep 11, 2019
commit 11eb10c4c8408ddcc9431ff2a1e6006c0d2670b5
66 changes: 0 additions & 66 deletions models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"path"
"regexp"
"sort"
"strconv"
"strings"

"code.gitea.io/gitea/modules/base"
Expand Down Expand Up @@ -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.)`
Expand Down Expand Up @@ -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
}
19 changes: 13 additions & 6 deletions models/issue_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand All @@ -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{
Expand Down Expand Up @@ -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.
Expand Down
143 changes: 143 additions & 0 deletions models/issue_xref.go
Original file line number Diff line number Diff line change
@@ -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
}