@@ -7,23 +7,52 @@ local function isComment(pathPattern)
7
7
end
8
8
9
9
local function buildEscapedPattern (rawPattern )
10
- return string.gsub (rawPattern , " %-" , " %%-" )
10
+ -- Escape Lua pattern special characters except *
11
+ local escaped = string.gsub (rawPattern , " ([%-%+%?%(%)])" , " %%%1" )
12
+
13
+ -- Handle ** first (before single *) - use placeholder to avoid conflicts
14
+ escaped = string.gsub (escaped , " %*%*" , " __DOUBLESTAR__" )
15
+
16
+ -- Convert remaining * to match any character except /
17
+ escaped = string.gsub (escaped , " %*" , " [^/]*" )
18
+
19
+ -- Replace placeholder with pattern that matches any path including /
20
+ escaped = string.gsub (escaped , " __DOUBLESTAR__" , " .*" )
21
+
22
+ -- Special handling for **/name patterns - they should match directories
23
+ if string.match (rawPattern , " %*%*/[^/]+$" ) then
24
+ -- **/logs should match files within logs directories
25
+ escaped = escaped .. " /"
26
+ end
27
+
28
+ -- Handle trailing slash - directory patterns should match everything within
29
+ if string.match (escaped , " /$" ) then
30
+ -- Remove trailing slash and match anything that starts with this path
31
+ escaped = string.gsub (escaped , " /$" , " /" )
32
+ -- Don't anchor with $ - allow matching subdirectories
33
+ elseif not string.match (escaped , " ^/" ) then
34
+ -- Anchor non-directory patterns to match exactly
35
+ escaped = escaped .. " $"
36
+ end
37
+
38
+ return escaped
11
39
end
12
40
13
41
-- matches file path substrings
14
42
local function isMatch (filePath , pathPattern )
15
43
if pathPattern == nil or pathPattern == " " then return false end
16
44
if isComment (pathPattern ) then return false end
17
45
18
- return string.match (filePath , buildEscapedPattern (pathPattern )) ~= nil
46
+ local pattern = buildEscapedPattern (pathPattern )
47
+ return string.match (filePath , pattern ) ~= nil
19
48
end
20
49
21
- -- Detects `*` pattern
50
+ -- Detects `*` pattern (global match - only exact "*")
22
51
local function isGlobalMatch (pathPattern )
23
52
if pathPattern == nil or pathPattern == " " then return false end
24
53
if isComment (pathPattern ) then return false end
25
54
26
- return string.match ( pathPattern , " *" ) ~= nil
55
+ return pathPattern == " *"
27
56
end
28
57
29
58
local function collectCodeowners (group )
@@ -74,10 +103,10 @@ CO.matchFilesToCodeowner = function(filePaths)
74
103
local pathPattern = split [1 ]
75
104
76
105
for _ , filePath in ipairs (filePaths ) do
77
- if isMatch (filePath , pathPattern ) then
78
- table.insert (matches , { pathPattern = pathPattern , codeowners = collectCodeowners (split ) })
79
- elseif isGlobalMatch (pathPattern ) then
106
+ if isGlobalMatch (pathPattern ) then
80
107
globalCodeowners = collectCodeowners (split )
108
+ elseif isMatch (filePath , pathPattern ) then
109
+ table.insert (matches , { pathPattern = pathPattern , codeowners = collectCodeowners (split ) })
81
110
end
82
111
end
83
112
end
@@ -87,8 +116,18 @@ CO.matchFilesToCodeowner = function(filePaths)
87
116
88
117
sortMatches (matches )
89
118
90
- local codeownersList = mapCodeowners (matches )
119
+ -- Only use the most specific pattern(s) - those with the longest pathPattern
120
+ local maxLength = # matches [1 ].pathPattern
121
+ local mostSpecificMatches = {}
122
+ for _ , match in ipairs (matches ) do
123
+ if # match .pathPattern == maxLength then
124
+ table.insert (mostSpecificMatches , match )
125
+ else
126
+ break -- Since sorted by length, we can break early
127
+ end
128
+ end
91
129
130
+ local codeownersList = mapCodeowners (mostSpecificMatches )
92
131
return codeownersList
93
132
end
94
133
0 commit comments