Skip to content

Commit 27bfc91

Browse files
authored
Merge pull request #7 from comatory/fix-pattern-matching
fix pattern matching
2 parents 9ef19a7 + f8cc75a commit 27bfc91

File tree

12 files changed

+509
-18
lines changed

12 files changed

+509
-18
lines changed

.github/workflows/pr.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: PR
2+
3+
on:
4+
pull_request:
5+
branches: [ master ]
6+
7+
jobs:
8+
checks:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Setup Lua
15+
uses: leafo/gh-actions-lua@v10
16+
with:
17+
luaVersion: "5.4"
18+
19+
- name: Setup LuaRocks
20+
uses: leafo/gh-actions-luarocks@v4
21+
22+
- name: Install dependencies
23+
run: luarocks install --deps-only gh-co.nvim-0.0.5-1.rockspec
24+
25+
- name: Lint
26+
run: PATH="./lua_modules/bin:$PATH" luacheck lua
27+
28+
- name: Test
29+
run: luarocks test

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/luarocks
2+
/lua_modules
3+
/.luarocks

.luacheckrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
std = "luajit"
2+
globals = {"vim"}
3+
4+
ignore = {
5+
-- unused loop variable
6+
"213",
7+
-- ignore unused test functions
8+
"[Tt]est[%w_]+",
9+
}

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1+
## v0.0.5
2+
3+
- add luarocks & unit tests
4+
- fix incompatibility with CODEOWNERS spec, the plugin can now work with various patterns, can handle paths marked with no owner(s) or wildcard patterns
5+
- fix highlighting, add treesitter support
6+
17
## v0.0.4
8+
29
- syntax highlighting for `CODEOWNERS` file
310

411
## v0.0.3

gh-co.nvim-0.0.4-1.rockspec

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
rockspec_format = "3.0"
2+
package = "gh-co.nvim"
3+
version = "0.0.4-1"
4+
source = {
5+
url = "git+ssh://git@github.com/comatory/gh-co.nvim.git"
6+
}
7+
description = {
8+
summary = "Github CODEOWNERS Neovim plugin",
9+
detailed = "Displays the code owners for current buffer, all opened buffers or lists owners by providing git SHAs",
10+
homepage = "https://github.com/comatory/gh-co.nvim",
11+
license = "CC0 1.0 Universal"
12+
}
13+
dependencies = {
14+
"lua >= 5.1",
15+
"luacheck",
16+
}
17+
test_dependencies = {
18+
"luaunit >= 3.4"
19+
}
20+
build = {
21+
type = "builtin",
22+
modules = {
23+
["gh-co.co"] = "lua/gh-co/co.lua",
24+
["gh-co.fs"] = "lua/gh-co/fs.lua",
25+
["gh-co.git"] = "lua/gh-co/git.lua",
26+
["gh-co.init"] = "lua/gh-co/init.lua",
27+
["gh-co.syntax"] = "lua/gh-co/syntax.lua"
28+
}
29+
}
30+
test = {
31+
type = "command",
32+
command = "lua -l lua/setup lua/gh-co/co.test.lua -o TAP"
33+
}

gh-co.nvim-0.0.5-1.rockspec

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
rockspec_format = "3.0"
2+
package = "gh-co.nvim"
3+
version = "0.0.5-1"
4+
source = {
5+
url = "git+ssh://git@github.com/comatory/gh-co.nvim.git"
6+
}
7+
description = {
8+
summary = "Github CODEOWNERS Neovim plugin",
9+
detailed = "Displays the code owners for current buffer, all opened buffers or lists owners by providing git SHAs",
10+
homepage = "https://github.com/comatory/gh-co.nvim",
11+
license = "CC0 1.0 Universal"
12+
}
13+
dependencies = {
14+
"lua >= 5.1",
15+
"luacheck",
16+
}
17+
test_dependencies = {
18+
"luaunit >= 3.4"
19+
}
20+
build = {
21+
type = "builtin",
22+
modules = {
23+
["gh-co.co"] = "lua/gh-co/co.lua",
24+
["gh-co.fs"] = "lua/gh-co/fs.lua",
25+
["gh-co.git"] = "lua/gh-co/git.lua",
26+
["gh-co.init"] = "lua/gh-co/init.lua",
27+
["gh-co.syntax"] = "lua/gh-co/syntax.lua"
28+
}
29+
}
30+
test = {
31+
type = "command",
32+
command = "lua -l lua/setup lua/gh-co/co.test.lua -o TAP"
33+
}

lua/gh-co/co.lua

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,52 @@ local function isComment(pathPattern)
77
end
88

99
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
1139
end
1240

1341
-- matches file path substrings
1442
local function isMatch(filePath, pathPattern)
1543
if pathPattern == nil or pathPattern == "" then return false end
1644
if isComment(pathPattern) then return false end
1745

18-
return string.match(filePath, buildEscapedPattern(pathPattern)) ~= nil
46+
local pattern = buildEscapedPattern(pathPattern)
47+
return string.match(filePath, pattern) ~= nil
1948
end
2049

21-
-- Detects `*` pattern
50+
-- Detects `*` pattern (global match - only exact "*")
2251
local function isGlobalMatch(pathPattern)
2352
if pathPattern == nil or pathPattern == "" then return false end
2453
if isComment(pathPattern) then return false end
2554

26-
return string.match(pathPattern, "*") ~= nil
55+
return pathPattern == "*"
2756
end
2857

2958
local function collectCodeowners(group)
@@ -74,10 +103,10 @@ CO.matchFilesToCodeowner = function(filePaths)
74103
local pathPattern = split[1]
75104

76105
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
80107
globalCodeowners = collectCodeowners(split)
108+
elseif isMatch(filePath, pathPattern) then
109+
table.insert(matches, { pathPattern = pathPattern, codeowners = collectCodeowners(split) })
81110
end
82111
end
83112
end
@@ -87,8 +116,18 @@ CO.matchFilesToCodeowner = function(filePaths)
87116

88117
sortMatches(matches)
89118

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
91129

130+
local codeownersList = mapCodeowners(mostSpecificMatches)
92131
return codeownersList
93132
end
94133

0 commit comments

Comments
 (0)