@@ -10,15 +10,14 @@ import (
1010 "fmt"
1111 "html"
1212 "path"
13- "regexp"
1413 "strconv"
1514 "strings"
1615 "time"
17- "unicode"
1816
1917 "code.gitea.io/gitea/modules/base"
2018 "code.gitea.io/gitea/modules/git"
2119 "code.gitea.io/gitea/modules/log"
20+ "code.gitea.io/gitea/modules/references"
2221 "code.gitea.io/gitea/modules/setting"
2322 api "code.gitea.io/gitea/modules/structs"
2423 "code.gitea.io/gitea/modules/timeutil"
@@ -54,29 +53,6 @@ const (
5453 ActionMirrorSyncDelete // 20
5554)
5655
57- var (
58- // Same as GitHub. See
59- // https://help.github.com/articles/closing-issues-via-commit-messages
60- issueCloseKeywords = []string {"close" , "closes" , "closed" , "fix" , "fixes" , "fixed" , "resolve" , "resolves" , "resolved" }
61- issueReopenKeywords = []string {"reopen" , "reopens" , "reopened" }
62-
63- issueCloseKeywordsPat , issueReopenKeywordsPat * regexp.Regexp
64- issueReferenceKeywordsPat * regexp.Regexp
65- )
66-
67- const issueRefRegexpStr = `(?:([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+))?(#[0-9]+)+`
68- const issueRefRegexpStrNoKeyword = `(?:\s|^|\(|\[)(?:([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+))?(#[0-9]+)(?:\s|$|\)|\]|:|\.(\s|$))`
69-
70- func assembleKeywordsPattern (words []string ) string {
71- return fmt .Sprintf (`(?i)(?:%s)(?::?) %s` , strings .Join (words , "|" ), issueRefRegexpStr )
72- }
73-
74- func init () {
75- issueCloseKeywordsPat = regexp .MustCompile (assembleKeywordsPattern (issueCloseKeywords ))
76- issueReopenKeywordsPat = regexp .MustCompile (assembleKeywordsPattern (issueReopenKeywords ))
77- issueReferenceKeywordsPat = regexp .MustCompile (issueRefRegexpStrNoKeyword )
78- }
79-
8056// Action represents user operation type and other information to
8157// repository. It implemented interface base.Actioner so that can be
8258// used in template render.
@@ -351,10 +327,6 @@ func RenameRepoAction(actUser *User, oldRepoName string, repo *Repository) error
351327 return renameRepoAction (x , actUser , oldRepoName , repo )
352328}
353329
354- func issueIndexTrimRight (c rune ) bool {
355- return ! unicode .IsDigit (c )
356- }
357-
358330// PushCommit represents a commit in a push operation.
359331type PushCommit struct {
360332 Sha1 string
@@ -480,39 +452,9 @@ func (pc *PushCommits) AvatarLink(email string) string {
480452}
481453
482454// getIssueFromRef returns the issue referenced by a ref. Returns a nil *Issue
483- // if the provided ref is misformatted or references a non-existent issue.
484- func getIssueFromRef (repo * Repository , ref string ) (* Issue , error ) {
485- ref = ref [strings .IndexByte (ref , ' ' )+ 1 :]
486- ref = strings .TrimRightFunc (ref , issueIndexTrimRight )
487-
488- var refRepo * Repository
489- poundIndex := strings .IndexByte (ref , '#' )
490- if poundIndex < 0 {
491- return nil , nil
492- } else if poundIndex == 0 {
493- refRepo = repo
494- } else {
495- slashIndex := strings .IndexByte (ref , '/' )
496- if slashIndex < 0 || slashIndex >= poundIndex {
497- return nil , nil
498- }
499- ownerName := ref [:slashIndex ]
500- repoName := ref [slashIndex + 1 : poundIndex ]
501- var err error
502- refRepo , err = GetRepositoryByOwnerAndName (ownerName , repoName )
503- if err != nil {
504- if IsErrRepoNotExist (err ) {
505- return nil , nil
506- }
507- return nil , err
508- }
509- }
510- issueIndex , err := strconv .ParseInt (ref [poundIndex + 1 :], 10 , 64 )
511- if err != nil {
512- return nil , nil
513- }
514-
515- issue , err := GetIssueByIndex (refRepo .ID , issueIndex )
455+ // if the provided ref references a non-existent issue.
456+ func getIssueFromRef (repo * Repository , index int64 ) (* Issue , error ) {
457+ issue , err := GetIssueByIndex (repo .ID , index )
516458 if err != nil {
517459 if IsErrIssueNotExist (err ) {
518460 return nil , nil
@@ -522,20 +464,7 @@ func getIssueFromRef(repo *Repository, ref string) (*Issue, error) {
522464 return issue , nil
523465}
524466
525- func changeIssueStatus (repo * Repository , doer * User , ref string , refMarked map [int64 ]bool , status bool ) error {
526- issue , err := getIssueFromRef (repo , ref )
527- if err != nil {
528- return err
529- }
530-
531- if issue == nil || refMarked [issue .ID ] {
532- return nil
533- }
534- refMarked [issue .ID ] = true
535-
536- if issue .RepoID != repo .ID || issue .IsClosed == status {
537- return nil
538- }
467+ func changeIssueStatus (repo * Repository , issue * Issue , doer * User , status bool ) error {
539468
540469 stopTimerIfAvailable := func (doer * User , issue * Issue ) error {
541470
@@ -549,7 +478,7 @@ func changeIssueStatus(repo *Repository, doer *User, ref string, refMarked map[i
549478 }
550479
551480 issue .Repo = repo
552- if err = issue .ChangeStatus (doer , status ); err != nil {
481+ if err : = issue .ChangeStatus (doer , status ); err != nil {
553482 // Don't return an error when dependencies are open as this would let the push fail
554483 if IsErrDependenciesLeft (err ) {
555484 return stopTimerIfAvailable (doer , issue )
@@ -566,99 +495,67 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra
566495 for i := len (commits ) - 1 ; i >= 0 ; i -- {
567496 c := commits [i ]
568497
569- refMarked := make (map [int64 ]bool )
498+ type markKey struct {
499+ ID int64
500+ Action references.XRefAction
501+ }
502+
503+ refMarked := make (map [markKey ]bool )
570504 var refRepo * Repository
505+ var refIssue * Issue
571506 var err error
572- for _ , m := range issueReferenceKeywordsPat .FindAllStringSubmatch (c .Message , - 1 ) {
573- if len (m [3 ]) == 0 {
574- continue
575- }
576- ref := m [3 ]
507+ for _ , ref := range references .FindAllIssueReferences (c .Message ) {
577508
578509 // issue is from another repo
579- if len (m [ 1 ] ) > 0 && len (m [ 2 ] ) > 0 {
580- refRepo , err = GetRepositoryFromMatch (m [ 1 ], m [ 2 ] )
510+ if len (ref . Owner ) > 0 && len (ref . Name ) > 0 {
511+ refRepo , err = GetRepositoryFromMatch (ref . Owner , ref . Name )
581512 if err != nil {
582513 continue
583514 }
584515 } else {
585516 refRepo = repo
586517 }
587- issue , err := getIssueFromRef (refRepo , ref )
588- if err != nil {
518+ if refIssue , err = getIssueFromRef (refRepo , ref .Index ); err != nil {
589519 return err
590520 }
591-
592- if issue == nil || refMarked [issue .ID ] {
521+ if refIssue == nil {
593522 continue
594523 }
595- refMarked [issue .ID ] = true
596524
597- message := fmt . Sprintf ( `<a href="%s/commit/%s">%s</a>` , repo . Link (), c . Sha1 , html . EscapeString ( c . Message ) )
598- if err = CreateRefComment ( doer , refRepo , issue , message , c . Sha1 ); err != nil {
525+ perm , err := GetUserRepoPermission ( refRepo , doer )
526+ if err != nil {
599527 return err
600528 }
601- }
602529
603- // Change issue status only if the commit has been pushed to the default branch.
604- // and if the repo is configured to allow only that
605- if repo .DefaultBranch != branchName && ! repo .CloseIssuesViaCommitInAnyBranch {
606- continue
607- }
608- refMarked = make (map [int64 ]bool )
609- for _ , m := range issueCloseKeywordsPat .FindAllStringSubmatch (c .Message , - 1 ) {
610- if len (m [3 ]) == 0 {
530+ key := markKey {ID : refIssue .ID , Action : ref .Action }
531+ if refMarked [key ] {
611532 continue
612533 }
613- ref := m [ 3 ]
534+ refMarked [ key ] = true
614535
615- // issue is from another repo
616- if len (m [1 ]) > 0 && len (m [2 ]) > 0 {
617- refRepo , err = GetRepositoryFromMatch (m [1 ], m [2 ])
618- if err != nil {
619- continue
620- }
621- } else {
622- refRepo = repo
623- }
624-
625- perm , err := GetUserRepoPermission (refRepo , doer )
626- if err != nil {
627- return err
628- }
629- // only close issues in another repo if user has push access
630- if perm .CanWrite (UnitTypeCode ) {
631- if err := changeIssueStatus (refRepo , doer , ref , refMarked , true ); err != nil {
536+ // only create comments for issues if user has permission for it
537+ if perm .IsAdmin () || perm .IsOwner () || perm .CanWrite (UnitTypeIssues ) {
538+ message := fmt .Sprintf (`<a href="%s/commit/%s">%s</a>` , repo .Link (), c .Sha1 , html .EscapeString (c .Message ))
539+ if err = CreateRefComment (doer , refRepo , refIssue , message , c .Sha1 ); err != nil {
632540 return err
633541 }
634542 }
635- }
636543
637- // It is conflict to have close and reopen at same time, so refsMarked doesn't need to reinit here.
638- for _ , m := range issueReopenKeywordsPat .FindAllStringSubmatch (c .Message , - 1 ) {
639- if len (m [3 ]) == 0 {
544+ // Process closing/reopening keywords
545+ if ref .Action != references .XRefActionCloses && ref .Action != references .XRefActionReopens {
640546 continue
641547 }
642- ref := m [3 ]
643548
644- // issue is from another repo
645- if len (m [1 ]) > 0 && len (m [2 ]) > 0 {
646- refRepo , err = GetRepositoryFromMatch (m [1 ], m [2 ])
647- if err != nil {
648- continue
649- }
650- } else {
651- refRepo = repo
652- }
653-
654- perm , err := GetUserRepoPermission (refRepo , doer )
655- if err != nil {
656- return err
549+ // Change issue status only if the commit has been pushed to the default branch.
550+ // and if the repo is configured to allow only that
551+ // FIXME: we should be using Issue.ref if set instead of repo.DefaultBranch
552+ if repo .DefaultBranch != branchName && ! repo .CloseIssuesViaCommitInAnyBranch {
553+ continue
657554 }
658555
659- // only reopen issues in another repo if user has push access
660- if perm .CanWrite (UnitTypeCode ) {
661- if err := changeIssueStatus (refRepo , doer , ref , refMarked , false ); err != nil {
556+ // only close issues in another repo if user has push access
557+ if perm .IsAdmin () || perm . IsOwner () || perm . CanWrite (UnitTypeCode ) {
558+ if err := changeIssueStatus (refRepo , refIssue , doer , ref . Action == references . XRefActionCloses ); err != nil {
662559 return err
663560 }
664561 }
0 commit comments