Skip to content

Commit a8170f9

Browse files
committed
feat: section's default owner
1 parent 9c1815f commit a8170f9

2 files changed

Lines changed: 195 additions & 101 deletions

File tree

lua/gitlab-codeowners/main.lua

Lines changed: 96 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ local function path_relative_to_root(path, root_repo)
3939
end
4040

4141
function M.get_codeowners_file(path)
42+
-- TODO: this approach will not work great with files in .gitlab or in docs
4243
local locations_from_root = {
4344
"/CODEOWNERS",
4445
"/docs/CODEOWNERS",
@@ -85,69 +86,104 @@ local function glob_to_regex(p)
8586
end
8687

8788
function M.read_sections(codeowners_file)
88-
local sections = {}
89-
local current_section = {}
90-
91-
for line in io.lines(codeowners_file) do
92-
local trimmed = line:match("^%s*(.-)%s*$")
93-
if trimmed ~= "" then
94-
local section_name = trimmed:match("^%[(.-)%]$")
95-
if section_name then
96-
if #current_section > 0 then
97-
table.insert(sections, current_section)
98-
end
99-
current_section = {}
100-
elseif not trimmed:match("^#") then
101-
trimmed = trimmed:gsub("%s+#.*$", "")
102-
local pattern, owners = trimmed:match("([^%s]+)%s+(.+)$")
103-
if pattern and owners then
104-
local list = {}
105-
for o in owners:gmatch("%S+") do
106-
table.insert(list, o)
107-
end
108-
table.insert(current_section, { pattern = pattern, owners = list })
109-
end
110-
end
111-
end
112-
end
113-
114-
if #current_section > 0 then
115-
table.insert(sections, current_section)
116-
end
117-
return sections
89+
local sections = {}
90+
local current_section = {}
91+
local current_defowner = {}
92+
93+
local function handle_line(line)
94+
line = line:match("^[^#]*")
95+
line = line:match("^%s*(.-)%s*$")
96+
if line == "" then
97+
return
98+
end
99+
100+
local section_name, owner_str = line:match("^%[(.-)%](.*)$")
101+
if section_name then
102+
if #current_section > 0 then
103+
table.insert(sections, current_section)
104+
end
105+
106+
current_section = {}
107+
current_defowner = {}
108+
109+
for o in owner_str:gmatch("%S+") do
110+
table.insert(current_defowner, o)
111+
end
112+
113+
return
114+
end
115+
116+
local pattern, owners_str = line:match("([^%s]+)%s*(.*)")
117+
118+
local owners = {}
119+
if owners_str then
120+
for o in owners_str:gmatch("%S+") do
121+
table.insert(owners, o)
122+
end
123+
end
124+
125+
if #owners == 0 then
126+
for _, o in ipairs(current_defowner) do
127+
table.insert(owners, o)
128+
end
129+
end
130+
131+
if not pattern or #owners == 0 then
132+
return
133+
end
134+
135+
table.insert(current_section, {
136+
pattern = pattern,
137+
owners = owners,
138+
})
139+
end
140+
141+
for line in io.lines(codeowners_file) do
142+
handle_line(line)
143+
end
144+
145+
if #current_section > 0 then
146+
table.insert(sections, current_section)
147+
end
148+
149+
return sections
118150
end
119151

120152
function M.get_codeowners(path)
121-
local result = M.get_codeowners_file(path)
122-
if not result or not result.co_file then
123-
return nil
124-
end
125-
126-
local codeowners_file = result.co_file
127-
local root_repo = result.repo
128-
path = path_relative_to_root(path, root_repo)
129-
if not path then return nil end
130-
131-
local sections = M.read_sections(codeowners_file)
132-
local owners = {}
133-
134-
for _, rules in ipairs(sections) do
135-
local best = nil
136-
for _, r in ipairs(rules) do
137-
local regex = glob_to_regex(r.pattern)
138-
if path:match(regex) then
139-
best = r
140-
end
141-
end
142-
if best then
143-
for _, o in ipairs(best.owners) do
144-
table.insert(owners, o)
145-
end
146-
end
147-
end
148-
149-
owners = unique(owners)
150-
return owners
153+
local result = M.get_codeowners_file(path)
154+
if not result or not result.co_file then
155+
return nil
156+
end
157+
158+
local codeowners_file = result.co_file
159+
local root_repo = result.repo
160+
path = path_relative_to_root(path, root_repo)
161+
if not path then
162+
return nil
163+
end
164+
165+
local sections = M.read_sections(codeowners_file)
166+
local owners = {}
167+
168+
for _, rules in ipairs(sections) do
169+
local best = nil
170+
171+
for _, r in ipairs(rules) do
172+
local regex = glob_to_regex(r.pattern)
173+
if path:match(regex) then
174+
best = r
175+
end
176+
end
177+
178+
if best then
179+
for _, o in ipairs(best.owners) do
180+
table.insert(owners, o)
181+
end
182+
end
183+
end
184+
185+
owners = unique(owners)
186+
return owners
151187
end
152188

153189
return M

test.lua

Lines changed: 99 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1+
C = {
2+
reset = "\27[0m",
3+
red = "\27[31m",
4+
green = "\27[32m",
5+
yellow = "\27[33m",
6+
}
7+
18
local health = {
2-
has_error = false,
9+
has_error = false,
310

4-
start = function(_, s)
5-
print("START: " .. s)
6-
end,
11+
start = function(_, s)
12+
print("START: " .. s)
13+
end,
714

8-
error = function(self, s)
9-
print("ERROR: " .. s)
10-
self.has_error = true
11-
end,
15+
error = function(self, s)
16+
print(C.red .. "ERROR: " .. C.reset .. s)
17+
self.has_error = true
18+
end,
1219

13-
ok = function(_, s)
14-
print("OK: " .. s)
15-
end,
20+
ok = function(_, s)
21+
print(C.green .. "OK: ".. C.reset .. s)
22+
end,
1623
}
1724

1825
local function same_set(a, b)
@@ -41,7 +48,7 @@ local function run(label, fn)
4148
health:start("Tests " .. label)
4249
local ok, err = pcall(fn)
4350
if not ok then
44-
health:error("Failed")
51+
health:error("Failed " .. err)
4552
return
4653
end
4754
health:ok("Passed")
@@ -52,7 +59,6 @@ end
5259
----------------------------------------------------
5360
local repo = "/tmp/codeowners_test"
5461
os.execute("rm -rf " .. repo)
55-
os.execute("mkdir -p " .. repo .. "/.gitlab")
5662

5763
local main = require("lua.gitlab-codeowners.main")
5864

@@ -80,6 +86,7 @@ local function write(path, content)
8086
end
8187

8288
local function test_1()
89+
os.execute("mkdir -p " .. repo .. "/.gitlab")
8390
write(
8491
repo .. "/.gitlab/CODEOWNERS",
8592
[[
@@ -93,15 +100,17 @@ local function test_1()
93100
94101
README.md @readme-md
95102
96-
/docs/ @all-docs
97-
/docs/* @root-docs
98-
/docs/**/*.md @markdown-docs # Match specific file types in any subdirectory
103+
/documents/ @all-docs
104+
/documents/* @root-docs
105+
/documents/**/*.md @markdown-docs # Match specific file types in any subdirectory
99106
]]
100107
)
101108

102109
run(".gitlab/CODEOWNERS found", function()
103110
local co_file = main.get_codeowners_file(repo .. "/something")
104-
assert(co_file and co_file.co_file == repo .. "/.gitlab/CODEOWNERS")
111+
assert(co_file)
112+
assert(co_file.co_file == repo .. "/.gitlab/CODEOWNERS")
113+
assert(co_file.repo == repo)
105114
end)
106115

107116
run("README.md matches both top-level and nested", function()
@@ -118,13 +127,13 @@ local function test_1()
118127
end)
119128

120129
run("docs directory patterns", function()
121-
assert_owner("/docs/a/file.txt", "@all-docs")
122-
assert_owner("/docs/b/c/d.md", "@markdown-docs")
123-
assert_owner("/docs/file1.txt", "@root-docs")
124-
assert_owner("/docs/subdir/file2.txt", "@all-docs")
125-
assert_owner("/docs/subdir/file.md", "@markdown-docs")
126-
assert_owner("/docs/file.md", "@markdown-docs")
127-
assert_owners("/sth/docs/file.md", { "@md-owner", "@md-owner2" })
130+
assert_owner("/documents/a/file.txt", "@all-docs")
131+
assert_owner("/documents/b/c/d.md", "@markdown-docs")
132+
assert_owner("/documents/file1.txt", "@root-docs")
133+
assert_owner("/documents/subdir/file2.txt", "@all-docs")
134+
assert_owner("/documents/subdir/file.md", "@markdown-docs")
135+
assert_owner("/documents/file.md", "@markdown-docs")
136+
assert_owners("/sth/documents/file.md", { "@md-owner", "@md-owner2" })
128137
end)
129138

130139
run("default wildcard *", function()
@@ -136,54 +145,103 @@ local function test_1()
136145
run("priority tests", function()
137146
local md_owners = { "@md-owner", "@md-owner2" }
138147
assert_owner("/README.md", "@readme-md")
139-
assert_owner("/docs/README.md", "@markdown-docs")
148+
assert_owner("/documents/README.md", "@markdown-docs")
140149
assert_owners("/notes.md", md_owners)
141-
assert_owner("/docs/notes.md", "@markdown-docs")
142-
assert_owners("/something/docs/notes.md", md_owners)
150+
assert_owner("/documents/notes.md", "@markdown-docs")
151+
assert_owners("/something/documents/notes.md", md_owners)
143152
end)
144153
end
145154

146155
local function test_2()
156+
os.execute("mkdir -p " .. repo .. "/docs")
147157
write(
148-
repo .. "/CODEOWNERS",
158+
repo .. "/docs/CODEOWNERS",
149159
[[
150160
* @default-owner
151161
*.md @md-owner
152-
/docs/ @all-docs
162+
/documents/ @all-docs
153163
154164
[Docs]
155-
/docs/ @all-docs
156-
/docs/*.md @all-docs
165+
/documents/ @all-docs
166+
/documents/*.md @all-docs
157167
README.md @readme
158168
159169
[Misc]
160-
/docs/*.misc @misc
170+
/documents/*.misc @misc
161171
]]
162172
)
163173

164-
run("CODEOWNERS overwrites .gitlab/", function()
174+
run("docs/CODEOWNERS overwrites .gitlab/", function()
165175
local co_file = main.get_codeowners_file(repo .. "/something/a/b/c")
166-
assert(co_file and co_file.co_file == repo .. "/CODEOWNERS")
176+
assert(co_file)
177+
assert(co_file.co_file == repo .. "/docs/CODEOWNERS")
178+
assert(co_file.repo == repo)
167179
end)
168180

169181
run("section", function()
170182
assert_owners("/README.md", { "@md-owner", "@readme" })
171-
assert_owners("/docs/README.md", { "@all-docs", "@readme" })
183+
assert_owners("/documents/README.md", { "@all-docs", "@readme" })
172184
assert_owners("/sth/docs/README.md", { "@md-owner", "@readme" })
173185
assert_owner("/something.txt", "@default-owner")
174186

175187
-- repeated entry
176-
assert_owners("/docs/something.misc", { "@all-docs", "@misc" })
177-
assert_owner("/docs/file.md", "@all-docs")
188+
assert_owners("/documents/something.misc", { "@all-docs", "@misc" })
189+
assert_owner("/documents/file.md", "@all-docs")
190+
end)
191+
end
192+
193+
local function test_3()
194+
write(
195+
repo .. "/CODEOWNERS",
196+
[[
197+
* @default-owner
198+
*.md @md-owner
199+
/documents/ @all-docs
200+
201+
[Docs] @doc-default
202+
/documents/
203+
/documents/*.md @all-docs
204+
README.md
205+
206+
[Algorithm] @cpp-master @algo-team
207+
/cpp/
208+
/algo/*.md @all-docs
209+
/algo/*.txt @algo-team
210+
/cpp/includes/ @cpp-master
211+
]]
212+
)
213+
214+
run("/CODEOWNERS overwrites docs/", function()
215+
local co_file = main.get_codeowners_file(repo .. "/something/a/b/c")
216+
assert(co_file and co_file.co_file == repo .. "/CODEOWNERS")
217+
end)
218+
219+
run("section default ", function()
220+
assert_owners("/README.md", { "@md-owner", "@doc-default" })
221+
assert_owners("/documents/README.md", { "@all-docs", "@doc-default" })
222+
assert_owner("/documents/FILE.md", "@all-docs")
223+
assert_owners("/sth/documents/README.md", { "@md-owner", "@doc-default" })
224+
assert_owner("/something.txt", "@default-owner")
178225
end)
226+
227+
run("section default more than 1 owners", function()
228+
assert_owners("/cpp/main.cpp", { "@default-owner", "@cpp-master", "@algo-team" })
229+
assert_owners("/algo/foo.md", { "@md-owner", "@all-docs" })
230+
assert_owners("/algo/bar.txt", { "@default-owner", "@algo-team" })
231+
assert_owners("/cpp/includes/header.h", { "@default-owner", "@cpp-master" })
232+
assert_owner("/other/path/file.txt", "@default-owner")
233+
end)
234+
235+
179236
end
180237

181238
local function check()
182239
test_1()
183240
test_2()
184-
if health.has_error then
185-
os.exit(1)
186-
end
241+
test_3()
242+
if health.has_error then
243+
os.exit(1)
244+
end
187245
end
188246

189-
check();
247+
check()

0 commit comments

Comments
 (0)