summaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
authormrsdizzie <info@mrsdizzie.com>2019-05-01 12:21:05 -0400
committerLauris BH <lauris@nix.lv>2019-05-01 19:21:05 +0300
commitcaba2829ef3c6a49385da64c7c8dff348734ce67 (patch)
treecc06aaa0b3624adeddb63d6e01aaf4cf874e1110 /models
parent5be17800457528e8047677c0e3d33cda6896140b (diff)
downloadgitea-caba2829ef3c6a49385da64c7c8dff348734ce67.tar.gz
gitea-caba2829ef3c6a49385da64c7c8dff348734ce67.zip
Improve issue reference on commit (#6694)
* Improve issue reference on commit Allow commits to properly reference issues in other repositories and also to close/reopen those issues if user has code permission. Should match Github behavior described here: https://help.github.com/en/articles/closing-issues-using-keywords Fixes 6664 * Fix missing return * Match user/repo directly in regex
Diffstat (limited to 'models')
-rw-r--r--models/action.go94
-rw-r--r--models/action_test.go70
2 files changed, 155 insertions, 9 deletions
diff --git a/models/action.go b/models/action.go
index e496166c42..b089870c74 100644
--- a/models/action.go
+++ b/models/action.go
@@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
+// 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.
@@ -62,7 +63,7 @@ var (
issueReferenceKeywordsPat *regexp.Regexp
)
-const issueRefRegexpStr = `(?:\S+/\S=)?#\d+`
+const issueRefRegexpStr = `(?:([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+))?(#[0-9]+)+`
func assembleKeywordsPattern(words []string) string {
return fmt.Sprintf(`(?i)(?:%s) %s`, strings.Join(words, "|"), issueRefRegexpStr)
@@ -192,6 +193,21 @@ func (a *Action) GetRepoLink() string {
return "/" + a.GetRepoPath()
}
+// GetRepositoryFromMatch returns a *Repository from a username and repo strings
+func GetRepositoryFromMatch(ownerName string, repoName string) (*Repository, error) {
+ var err error
+ refRepo, err := GetRepositoryByOwnerAndName(ownerName, repoName)
+ if err != nil {
+ if IsErrRepoNotExist(err) {
+ log.Warn("Repository referenced in commit but does not exist: %v", err)
+ return nil, err
+ }
+ log.Error("GetRepositoryByOwnerAndName: %v", err)
+ return nil, err
+ }
+ return refRepo, nil
+}
+
// GetCommentLink returns link to action comment.
func (a *Action) GetCommentLink() string {
return a.getCommentLink(x)
@@ -521,8 +537,24 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra
c := commits[i]
refMarked := make(map[int64]bool)
- for _, ref := range issueReferenceKeywordsPat.FindAllString(c.Message, -1) {
- issue, err := getIssueFromRef(repo, ref)
+ var refRepo *Repository
+ var err error
+ for _, m := range issueReferenceKeywordsPat.FindAllStringSubmatch(c.Message, -1) {
+ if len(m[3]) == 0 {
+ continue
+ }
+ ref := m[3]
+
+ // issue is from another repo
+ if len(m[1]) > 0 && len(m[2]) > 0 {
+ refRepo, err = GetRepositoryFromMatch(string(m[1]), string(m[2]))
+ if err != nil {
+ continue
+ }
+ } else {
+ refRepo = repo
+ }
+ issue, err := getIssueFromRef(refRepo, ref)
if err != nil {
return err
}
@@ -533,7 +565,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra
refMarked[issue.ID] = true
message := fmt.Sprintf(`<a href="%s/commit/%s">%s</a>`, repo.Link(), c.Sha1, c.Message)
- if err = CreateRefComment(doer, repo, issue, message, c.Sha1); err != nil {
+ if err = CreateRefComment(doer, refRepo, issue, message, c.Sha1); err != nil {
return err
}
}
@@ -543,19 +575,63 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra
if repo.DefaultBranch != branchName && !repo.CloseIssuesViaCommitInAnyBranch {
continue
}
-
refMarked = make(map[int64]bool)
- for _, ref := range issueCloseKeywordsPat.FindAllString(c.Message, -1) {
- if err := changeIssueStatus(repo, doer, ref, refMarked, true); err != nil {
+ for _, m := range issueCloseKeywordsPat.FindAllStringSubmatch(c.Message, -1) {
+ if len(m[3]) == 0 {
+ continue
+ }
+ ref := m[3]
+
+ // issue is from another repo
+ if len(m[1]) > 0 && len(m[2]) > 0 {
+ refRepo, err = GetRepositoryFromMatch(string(m[1]), string(m[2]))
+ if err != nil {
+ continue
+ }
+ } else {
+ refRepo = repo
+ }
+
+ perm, err := GetUserRepoPermission(refRepo, doer)
+ if err != nil {
return err
}
+ // only close issues in another repo if user has push access
+ if perm.CanWrite(UnitTypeCode) {
+ if err := changeIssueStatus(refRepo, doer, ref, refMarked, true); err != nil {
+ return err
+ }
+ }
}
// It is conflict to have close and reopen at same time, so refsMarked doesn't need to reinit here.
- for _, ref := range issueReopenKeywordsPat.FindAllString(c.Message, -1) {
- if err := changeIssueStatus(repo, doer, ref, refMarked, false); err != nil {
+ for _, m := range issueReopenKeywordsPat.FindAllStringSubmatch(c.Message, -1) {
+ if len(m[3]) == 0 {
+ continue
+ }
+ ref := m[3]
+
+ // issue is from another repo
+ if len(m[1]) > 0 && len(m[2]) > 0 {
+ refRepo, err = GetRepositoryFromMatch(string(m[1]), string(m[2]))
+ if err != nil {
+ continue
+ }
+ } else {
+ refRepo = repo
+ }
+
+ perm, err := GetUserRepoPermission(refRepo, doer)
+ if err != nil {
return err
}
+
+ // only reopen issues in another repo if user has push access
+ if perm.CanWrite(UnitTypeCode) {
+ if err := changeIssueStatus(refRepo, doer, ref, refMarked, false); err != nil {
+ return err
+ }
+ }
}
}
return nil
diff --git a/models/action_test.go b/models/action_test.go
index e30b706809..9ba2057318 100644
--- a/models/action_test.go
+++ b/models/action_test.go
@@ -294,6 +294,76 @@ func TestUpdateIssuesCommit_Issue5957(t *testing.T) {
CheckConsistencyFor(t, &Action{})
}
+func TestUpdateIssuesCommit_AnotherRepo(t *testing.T) {
+ assert.NoError(t, PrepareTestDatabase())
+ user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
+
+ // Test that a push to default branch closes issue in another repo
+ // If the user also has push permissions to that repo
+ pushCommits := []*PushCommit{
+ {
+ Sha1: "abcdef1",
+ CommitterEmail: "user2@example.com",
+ CommitterName: "User Two",
+ AuthorEmail: "user2@example.com",
+ AuthorName: "User Two",
+ Message: "close user2/repo1#1",
+ },
+ }
+
+ repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository)
+ commentBean := &Comment{
+ Type: CommentTypeCommitRef,
+ CommitSHA: "abcdef1",
+ PosterID: user.ID,
+ IssueID: 1,
+ }
+
+ issueBean := &Issue{RepoID: 1, Index: 1, ID: 1}
+
+ AssertNotExistsBean(t, commentBean)
+ AssertNotExistsBean(t, issueBean, "is_closed=1")
+ assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch))
+ AssertExistsAndLoadBean(t, commentBean)
+ AssertExistsAndLoadBean(t, issueBean, "is_closed=1")
+ CheckConsistencyFor(t, &Action{})
+}
+
+func TestUpdateIssuesCommit_AnotherRepoNoPermission(t *testing.T) {
+ assert.NoError(t, PrepareTestDatabase())
+ user := AssertExistsAndLoadBean(t, &User{ID: 10}).(*User)
+
+ // Test that a push with close reference *can not* close issue
+ // If the commiter doesn't have push rights in that repo
+ pushCommits := []*PushCommit{
+ {
+ Sha1: "abcdef3",
+ CommitterEmail: "user10@example.com",
+ CommitterName: "User Ten",
+ AuthorEmail: "user10@example.com",
+ AuthorName: "User Ten",
+ Message: "close user3/repo3#1",
+ },
+ }
+
+ repo := AssertExistsAndLoadBean(t, &Repository{ID: 6}).(*Repository)
+ commentBean := &Comment{
+ Type: CommentTypeCommitRef,
+ CommitSHA: "abcdef3",
+ PosterID: user.ID,
+ IssueID: 6,
+ }
+
+ issueBean := &Issue{RepoID: 3, Index: 1, ID: 6}
+
+ AssertNotExistsBean(t, commentBean)
+ AssertNotExistsBean(t, issueBean, "is_closed=1")
+ assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch))
+ AssertExistsAndLoadBean(t, commentBean)
+ AssertNotExistsBean(t, issueBean, "is_closed=1")
+ CheckConsistencyFor(t, &Action{})
+}
+
func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *Action) {
AssertNotExistsBean(t, actionBean)
assert.NoError(t, CommitRepoAction(opts))