@@ -10,6 +10,7 @@ import (
10
10
"path"
11
11
"path/filepath"
12
12
"regexp"
13
+ "slices"
13
14
"strings"
14
15
"sync"
15
16
54
55
shortLinkPattern = regexp .MustCompile (`\[\[(.*?)\]\](\w*)` )
55
56
56
57
// anyHashPattern splits url containing SHA into parts
57
- anyHashPattern = regexp .MustCompile (`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~_%.a-zA-Z0-9/ ]+)?(# [-+~_%.a-zA-Z0-9 ]+)?` )
58
+ anyHashPattern = regexp .MustCompile (`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~%./\w ]+)?(\? [-+~%.\w&=]+)?(#[-+~%.\w ]+)?` )
58
59
59
60
// comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash"
60
61
comparePattern = regexp .MustCompile (`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?` )
@@ -591,7 +592,8 @@ func replaceContentList(node *html.Node, i, j int, newNodes []*html.Node) {
591
592
592
593
func mentionProcessor (ctx * RenderContext , node * html.Node ) {
593
594
start := 0
594
- for node != nil {
595
+ nodeStop := node .NextSibling
596
+ for node != nodeStop {
595
597
found , loc := references .FindFirstMentionBytes (util .UnsafeStringToBytes (node .Data [start :]))
596
598
if ! found {
597
599
node = node .NextSibling
@@ -962,57 +964,68 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
962
964
}
963
965
}
964
966
965
- // fullHashPatternProcessor renders SHA containing URLs
966
- func fullHashPatternProcessor (ctx * RenderContext , node * html.Node ) {
967
- if ctx .Metas == nil {
968
- return
967
+ type anyHashPatternResult struct {
968
+ PosStart int
969
+ PosEnd int
970
+ FullURL string
971
+ CommitID string
972
+ SubPath string
973
+ QueryHash string
974
+ }
975
+
976
+ func anyHashPatternExtract (s string ) (ret anyHashPatternResult , ok bool ) {
977
+ m := anyHashPattern .FindStringSubmatchIndex (s )
978
+ if m == nil {
979
+ return ret , false
969
980
}
970
981
971
- next := node .NextSibling
972
- for node != nil && node != next {
973
- m := anyHashPattern .FindStringSubmatchIndex (node .Data )
974
- if m == nil {
975
- return
982
+ ret .PosStart , ret .PosEnd = m [0 ], m [1 ]
983
+ ret .FullURL = s [ret .PosStart :ret .PosEnd ]
984
+ if strings .HasSuffix (ret .FullURL , "." ) {
985
+ // if url ends in '.', it's very likely that it is not part of the actual url but used to finish a sentence.
986
+ ret .PosEnd --
987
+ ret .FullURL = ret .FullURL [:len (ret .FullURL )- 1 ]
988
+ for i := 0 ; i < len (m ); i ++ {
989
+ m [i ] = min (m [i ], ret .PosEnd )
976
990
}
991
+ }
977
992
978
- urlFull := node .Data [m [0 ]:m [1 ]]
979
- text := base .ShortSha (node .Data [m [2 ]:m [3 ]])
993
+ ret .CommitID = s [m [2 ]:m [3 ]]
994
+ if m [5 ] > 0 {
995
+ ret .SubPath = s [m [4 ]:m [5 ]]
996
+ }
980
997
981
- // 3rd capture group matches a optional path
982
- subpath := ""
983
- if m [5 ] > 0 {
984
- subpath = node .Data [m [4 ]:m [5 ]]
985
- }
998
+ lastStart , lastEnd := m [len (m )- 2 ], m [len (m )- 1 ]
999
+ if lastEnd > 0 {
1000
+ ret .QueryHash = s [lastStart :lastEnd ][1 :]
1001
+ }
1002
+ return ret , true
1003
+ }
986
1004
987
- // 4th capture group matches a optional url hash
988
- hash := ""
989
- if m [7 ] > 0 {
990
- hash = node .Data [m [6 ]:m [7 ]][1 :]
1005
+ // fullHashPatternProcessor renders SHA containing URLs
1006
+ func fullHashPatternProcessor (ctx * RenderContext , node * html.Node ) {
1007
+ if ctx .Metas == nil {
1008
+ return
1009
+ }
1010
+ nodeStop := node .NextSibling
1011
+ for node != nodeStop {
1012
+ if node .Type != html .TextNode {
1013
+ node = node .NextSibling
1014
+ continue
991
1015
}
992
-
993
- start := m [0 ]
994
- end := m [1 ]
995
-
996
- // If url ends in '.', it's very likely that it is not part of the
997
- // actual url but used to finish a sentence.
998
- if strings .HasSuffix (urlFull , "." ) {
999
- end --
1000
- urlFull = urlFull [:len (urlFull )- 1 ]
1001
- if hash != "" {
1002
- hash = hash [:len (hash )- 1 ]
1003
- } else if subpath != "" {
1004
- subpath = subpath [:len (subpath )- 1 ]
1005
- }
1016
+ ret , ok := anyHashPatternExtract (node .Data )
1017
+ if ! ok {
1018
+ node = node .NextSibling
1019
+ continue
1006
1020
}
1007
-
1008
- if subpath != "" {
1009
- text += subpath
1021
+ text := base . ShortSha ( ret . CommitID )
1022
+ if ret . SubPath != "" {
1023
+ text += ret . SubPath
1010
1024
}
1011
-
1012
- if hash != "" {
1013
- text += " (" + hash + ")"
1025
+ if ret .QueryHash != "" {
1026
+ text += " (" + ret .QueryHash + ")"
1014
1027
}
1015
- replaceContent (node , start , end , createCodeLink (urlFull , text , "commit" ))
1028
+ replaceContent (node , ret . PosStart , ret . PosEnd , createCodeLink (ret . FullURL , text , "commit" ))
1016
1029
node = node .NextSibling .NextSibling
1017
1030
}
1018
1031
}
@@ -1021,19 +1034,16 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
1021
1034
if ctx .Metas == nil {
1022
1035
return
1023
1036
}
1024
-
1025
- next := node .NextSibling
1026
- for node != nil && node != next {
1027
- m := comparePattern .FindStringSubmatchIndex (node .Data )
1028
- if m == nil {
1029
- return
1037
+ nodeStop := node .NextSibling
1038
+ for node != nodeStop {
1039
+ if node .Type != html .TextNode {
1040
+ node = node .NextSibling
1041
+ continue
1030
1042
}
1031
-
1032
- // Ensure that every group (m[0]...m[7]) has a match
1033
- for i := 0 ; i < 8 ; i ++ {
1034
- if m [i ] == - 1 {
1035
- return
1036
- }
1043
+ m := comparePattern .FindStringSubmatchIndex (node .Data )
1044
+ if m == nil || slices .Contains (m [:8 ], - 1 ) { // ensure that every group (m[0]...m[7]) has a match
1045
+ node = node .NextSibling
1046
+ continue
1037
1047
}
1038
1048
1039
1049
urlFull := node .Data [m [0 ]:m [1 ]]
0 commit comments