summaryrefslogtreecommitdiff
path: root/lua
diff options
context:
space:
mode:
authorStephanie Gredell <s.raide@gmail.com>2026-02-22 13:11:19 -0800
committerStephanie Gredell <s.raide@gmail.com>2026-02-22 13:11:19 -0800
commit3e2bdb3e6663d77126bacc3019ab1e122fdea481 (patch)
tree48708b8113aec445adcad7e4c282ea5298b9585b /lua
parente057505a1adbc0c27dd10006970157e738c0ea4b (diff)
downloada4-3e2bdb3e6663d77126bacc3019ab1e122fdea481.tar.xz
a4-3e2bdb3e6663d77126bacc3019ab1e122fdea481.zip
stuff
Diffstat (limited to 'lua')
-rw-r--r--lua/99/extensions/files/init.lua9
-rw-r--r--lua/99/test/files_spec.lua275
2 files changed, 280 insertions, 4 deletions
diff --git a/lua/99/extensions/files/init.lua b/lua/99/extensions/files/init.lua
index 03afb03..8805a12 100644
--- a/lua/99/extensions/files/init.lua
+++ b/lua/99/extensions/files/init.lua
@@ -76,7 +76,8 @@ end
local function is_git_repo(root)
local git_dir = vim.fs.joinpath(root, ".git")
local stat = vim.uv.fs_stat(git_dir)
- return stat ~= nil and stat.type == "directory"
+ -- Check if .git exists (can be directory OR file for worktrees/submodules)
+ return stat ~= nil
end
--- @param root string
@@ -88,10 +89,14 @@ local function scan_with_git_sync(root)
)
local output = vim.fn.system(cmd)
- if vim.v.shell_error ~= 0 or output == "" then
+ if vim.v.shell_error ~= 0 then
return nil
end
+ if output == "" then
+ return {}
+ end
+
local files = {}
local count = 0
diff --git a/lua/99/test/files_spec.lua b/lua/99/test/files_spec.lua
index e033abe..b0063f2 100644
--- a/lua/99/test/files_spec.lua
+++ b/lua/99/test/files_spec.lua
@@ -33,13 +33,11 @@ describe("files", function()
paths[f.path] = f
end
- -- known fixture files must be present
assert.is_not_nil(paths["scratch/refresh.lua"])
assert.is_not_nil(paths["scratch/test.ts"])
eq("refresh.lua", paths["scratch/refresh.lua"].name)
eq("test.ts", paths["scratch/test.ts"].name)
- -- .git must be excluded
for path, _ in pairs(paths) do
assert.is_nil(
path:match("^%.git/"),
@@ -199,3 +197,276 @@ describe("files", function()
)
end)
end)
+
+describe("files git integration", function()
+ -- Mock storage
+ local _mocks = {
+ system_output = "",
+ system_exit = 0,
+ stat_type = nil,
+ stat_exists = false,
+ orig_system = nil,
+ orig_stat = nil,
+ orig_shell_error = nil,
+ system_calls = {},
+ }
+
+ before_each(function()
+ _mocks.orig_system = vim.fn.system
+ _mocks.orig_stat = vim.uv.fs_stat
+
+ _mocks.system_output = ""
+ _mocks.system_exit = 0
+ _mocks.stat_type = nil
+ _mocks.stat_exists = false
+ _mocks.system_calls = {}
+
+ vim.fn.system = function(cmd)
+ table.insert(_mocks.system_calls, cmd)
+
+ pcall(function()
+ rawset(vim.v, "shell_error", _mocks.system_exit)
+ end)
+ return _mocks.system_output
+ end
+
+ vim.uv.fs_stat = function(path)
+ if _mocks.stat_exists then
+ return { type = _mocks.stat_type }
+ end
+ return nil
+ end
+
+ Files.set_project_root("/test/repo")
+ end)
+
+ after_each(function()
+ vim.fn.system = _mocks.orig_system
+ vim.uv.fs_stat = _mocks.orig_stat
+
+ pcall(function()
+ rawset(vim.v, "shell_error", 0)
+ end)
+ Files.set_project_root("")
+ end)
+
+ it(
+ "detects git repo with .git directory and uses git-based discovery",
+ function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = "README.md\n"
+ _mocks.system_exit = 0
+
+ local files = Files.discover_files()
+
+ assert.is_true(
+ #_mocks.system_calls > 0,
+ "git command should have been called"
+ )
+ local git_called = false
+ for _, cmd in ipairs(_mocks.system_calls) do
+ if cmd:match("git.*ls%-files") then
+ git_called = true
+ break
+ end
+ end
+ assert.is_true(git_called, "git ls-files should have been executed")
+
+ assert.is_true(#files > 0, "should return files from git")
+ eq("README.md", files[1].path)
+ end
+ )
+
+ it(
+ "detects git repo with .git file (worktree) and uses git-based discovery",
+ function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "file"
+ _mocks.system_output = "src/main.lua\ntest/file.lua\n"
+ _mocks.system_exit = 0
+
+ local files = Files.discover_files()
+
+ assert.is_true(
+ #_mocks.system_calls > 0,
+ "git command should have been called for worktree"
+ )
+ local git_called = false
+ for _, cmd in ipairs(_mocks.system_calls) do
+ if cmd:match("git.*ls%-files") then
+ git_called = true
+ break
+ end
+ end
+ assert.is_true(git_called, "should use git for worktree .git file")
+
+ assert.are.equal(2, #files)
+ eq("src/main.lua", files[1].path)
+ eq("test/file.lua", files[2].path)
+ end
+ )
+
+ it("returns files when git command succeeds", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = "README.md\nsrc/init.lua\nsrc/utils.lua\n"
+ _mocks.system_exit = 0
+
+ local files = Files.discover_files()
+
+ assert.are.equal(3, #files)
+ eq("README.md", files[1].path)
+ eq("src/init.lua", files[2].path)
+ eq("src/utils.lua", files[3].path)
+
+ for _, f in ipairs(files) do
+ assert.is_not_nil(f.path, "file should have path")
+ assert.is_not_nil(f.name, "file should have name")
+ assert.is_not_nil(f.absolute_path, "file should have absolute_path")
+ end
+ end)
+
+ it("returns empty table (not nil) for empty repo", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = ""
+ _mocks.system_exit = 0
+
+ local files = Files.discover_files()
+
+ assert.is_not_nil(files, "should return table, not nil")
+ assert.are.equal(0, #files, "should return empty table for empty repo")
+
+ for _, cmd in ipairs(_mocks.system_calls) do
+ assert.is_true(
+ cmd:match("git") ~= nil,
+ "should only call git, not fs commands"
+ )
+ end
+ end)
+
+ it("returns nil on git command failure", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = "fatal: not a git repository"
+ _mocks.system_exit = 128 -- Git failure
+
+ local orig_scandir = vim.uv.fs_scandir
+ vim.uv.fs_scandir = function(dir)
+ return nil -- Empty directory
+ end
+
+ local files = Files.discover_files()
+
+ vim.uv.fs_scandir = orig_scandir
+
+ local git_failed = false
+ for _, cmd in ipairs(_mocks.system_calls) do
+ if cmd:match("git") and vim.v.shell_error ~= 0 then
+ git_failed = true
+ break
+ end
+ end
+ assert.is_true(git_failed, "git should have been called and failed")
+ end)
+
+ it("applies manual excludes on top of git output", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output =
+ "README.md\n.env\n.env.local\nnode_modules/package.json\nsrc/main.lua\n"
+ _mocks.system_exit = 0
+
+ Files.setup({
+ enabled = true,
+ exclude = { ".env", ".env.*", "node_modules" },
+ }, {})
+
+ local files = Files.discover_files()
+
+ assert.are.equal(2, #files)
+ eq("README.md", files[1].path)
+ eq("src/main.lua", files[2].path)
+
+ for _, f in ipairs(files) do
+ assert.is_nil(f.path:match("%.env"), ".env files should be excluded")
+ assert.is_nil(
+ f.path:match("node_modules"),
+ "node_modules should be excluded"
+ )
+ end
+ end)
+
+ it("uses git-based discovery in git repo", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = "tracked.txt\n"
+ _mocks.system_exit = 0
+
+ Files.discover_files()
+
+ local git_called = false
+ for _, cmd in ipairs(_mocks.system_calls) do
+ if cmd:match("git") then
+ git_called = true
+ break
+ end
+ end
+ assert.is_true(git_called, "should use git ls-files in git repo")
+ end)
+
+ it("falls back to filesystem when not in git repo", function()
+ _mocks.stat_exists = false -- .git doesn't exist
+
+ local orig_scandir = vim.uv.fs_scandir
+ local fs_called = false
+ vim.uv.fs_scandir = function(dir)
+ fs_called = true
+ return nil -- Empty
+ end
+
+ Files.discover_files()
+
+ vim.uv.fs_scandir = orig_scandir
+
+ for _, cmd in ipairs(_mocks.system_calls) do
+ assert.is_nil(
+ cmd:match("git"),
+ "git should not be called in non-git repo"
+ )
+ end
+
+ assert.is_true(fs_called, "filesystem fallback should be used")
+ end)
+
+ it("returns cached files on subsequent calls", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = "file.txt\n"
+ _mocks.system_exit = 0
+
+ local first = Files.get_files()
+ local first_call_count = #_mocks.system_calls
+
+ local second = Files.get_files()
+ local second_call_count = #_mocks.system_calls
+
+ eq(first, second)
+
+ assert.are.equal(
+ first_call_count,
+ second_call_count,
+ "should not re-scan, use cache"
+ )
+ end)
+
+ it("handles empty root gracefully", function()
+ Files.set_project_root("")
+
+ local files = Files.discover_files()
+
+ assert.is_not_nil(files, "should return table")
+ assert.are.equal(0, #files, "should return empty for empty root")
+ end)
+end)