@@ -10,15 +10,14 @@ import (
10
10
"fmt"
11
11
"html"
12
12
"path"
13
- "regexp"
14
13
"strconv"
15
14
"strings"
16
15
"time"
17
- "unicode"
18
16
19
17
"code.gitea.io/gitea/modules/base"
20
18
"code.gitea.io/gitea/modules/git"
21
19
"code.gitea.io/gitea/modules/log"
20
+ "code.gitea.io/gitea/modules/references"
22
21
"code.gitea.io/gitea/modules/setting"
23
22
api "code.gitea.io/gitea/modules/structs"
24
23
"code.gitea.io/gitea/modules/timeutil"
@@ -54,29 +53,6 @@ const (
54
53
ActionMirrorSyncDelete // 20
55
54
)
56
55
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
-
80
56
// Action represents user operation type and other information to
81
57
// repository. It implemented interface base.Actioner so that can be
82
58
// used in template render.
@@ -351,10 +327,6 @@ func RenameRepoAction(actUser *User, oldRepoName string, repo *Repository) error
351
327
return renameRepoAction (x , actUser , oldRepoName , repo )
352
328
}
353
329
354
- func issueIndexTrimRight (c rune ) bool {
355
- return ! unicode .IsDigit (c )
356
- }
357
-
358
330
// PushCommit represents a commit in a push operation.
359
331
type PushCommit struct {
360
332
Sha1 string
@@ -480,39 +452,9 @@ func (pc *PushCommits) AvatarLink(email string) string {
480
452
}
481
453
482
454
// 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 )
516
458
if err != nil {
517
459
if IsErrIssueNotExist (err ) {
518
460
return nil , nil
@@ -522,20 +464,7 @@ func getIssueFromRef(repo *Repository, ref string) (*Issue, error) {
522
464
return issue , nil
523
465
}
524
466
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 {
539
468
540
469
stopTimerIfAvailable := func (doer * User , issue * Issue ) error {
541
470
@@ -549,7 +478,7 @@ func changeIssueStatus(repo *Repository, doer *User, ref string, refMarked map[i
549
478
}
550
479
551
480
issue .Repo = repo
552
- if err = issue .ChangeStatus (doer , status ); err != nil {
481
+ if err : = issue .ChangeStatus (doer , status ); err != nil {
553
482
// Don't return an error when dependencies are open as this would let the push fail
554
483
if IsErrDependenciesLeft (err ) {
555
484
return stopTimerIfAvailable (doer , issue )
@@ -566,99 +495,67 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra
566
495
for i := len (commits ) - 1 ; i >= 0 ; i -- {
567
496
c := commits [i ]
568
497
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 )
570
504
var refRepo * Repository
505
+ var refIssue * Issue
571
506
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 ) {
577
508
578
509
// 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 )
581
512
if err != nil {
582
513
continue
583
514
}
584
515
} else {
585
516
refRepo = repo
586
517
}
587
- issue , err := getIssueFromRef (refRepo , ref )
588
- if err != nil {
518
+ if refIssue , err = getIssueFromRef (refRepo , ref .Index ); err != nil {
589
519
return err
590
520
}
591
-
592
- if issue == nil || refMarked [issue .ID ] {
521
+ if refIssue == nil {
593
522
continue
594
523
}
595
- refMarked [issue .ID ] = true
596
524
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 {
599
527
return err
600
528
}
601
- }
602
529
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 ] {
611
532
continue
612
533
}
613
- ref := m [ 3 ]
534
+ refMarked [ key ] = true
614
535
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 {
632
540
return err
633
541
}
634
542
}
635
- }
636
543
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 {
640
546
continue
641
547
}
642
- ref := m [3 ]
643
548
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
657
554
}
658
555
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 {
662
559
return err
663
560
}
664
561
}
0 commit comments