Skip to content

fix(files): fixes #78, correctly handle .gitignore #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions lua/neo-tree/git/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
local Path = require("plenary.path")
local utils = require("neo-tree.utils")

local os_sep = Path.path.sep

local M = {}

local function execute_command(cmd)
local result = vim.fn.systemlist(cmd)

-- An empty result is ok
if vim.v.shell_error ~= 0 or (#result > 0 and vim.startswith(result[1], "fatal:")) then
return false, {}
else
return true, result
end
end

local function windowize_path(path)
return path:gsub("/", "\\")
end

M.get_repository_root = function(path)
local cmd = "git rev-parse --show-toplevel"
if utils.truthy(path) then
cmd = "git -C " .. path .. " rev-parse --show-toplevel"
end
local ok, git_root = execute_command(cmd)
if not ok then
return nil
end
git_root = git_root[1]

if utils.is_windows then
git_root = windowize_path(git_root)
end

return git_root
end

local function get_simple_git_status_code(status)
-- Prioritze M then A over all others
if status:match("U") or status == "AA" or status == "DD" then
return "U"
elseif status:match("M") then
return "M"
elseif status:match("[ACR]") then
return "A"
elseif status:match("!$") then
return "!"
elseif status:match("?$") then
return "?"
else
local len = #status
while len > 0 do
local char = status:sub(len, len)
if char ~= " " then
return char
end
len = len - 1
end
return status
end
end

local function get_priority_git_status_code(status, other_status)
if not status then
return other_status
elseif not other_status then
return status
elseif status == "U" or other_status == "U" then
return "U"
elseif status == "?" or other_status == "?" then
return "?"
elseif status == "M" or other_status == "M" then
return "M"
elseif status == "A" or other_status == "A" then
return "A"
else
return status
end
end

---Parse "git status" output for the current working directory.
---@return table table Table with the path as key and the status as value.
M.status = function(exclude_directories)
local git_root = M.get_repository_root()
if not git_root then
return {}
end
local ok, result = execute_command("git status --porcelain=v1")
if not ok then
return {}
end

local git_status = {}
for _, line in ipairs(result) do
local status = line:sub(1, 2)
local relative_path = line:sub(4)
local arrow_pos = relative_path:find(" -> ")
if arrow_pos ~= nil then
relative_path = line:sub(arrow_pos + 5)
end
-- remove any " due to whitespace in the path
relative_path = relative_path:gsub('^"', ""):gsub('$"', "")
if utils.is_windows == true then
relative_path = windowize_path(relative_path)
end
local absolute_path = string.format("%s%s%s", git_root, os_sep, relative_path)
git_status[absolute_path] = status

if not exclude_directories then
-- Now bubble this status up to the parent directories
local file_status = get_simple_git_status_code(status)
local parents = Path:new(absolute_path):parents()
for i = #parents, 1, -1 do
local path = parents[i]
local path_status = git_status[path]
git_status[path] = get_priority_git_status_code(path_status, file_status)
end
end
end

return git_status, git_root
end

M.load_ignored = function(path)
local git_root = M.get_repository_root(path)
if not git_root then
return {}
end
local ok, result = execute_command(
"git --no-optional-locks status --porcelain=v1 --ignored=matching --untracked-files=normal"
)
if not ok then
return {}
end

local ignored = {}
for _, v in ipairs(result) do
-- git ignore format:
-- !! path/to/file
-- !! path/to/path/
-- with paths relative to the repository root
if v:sub(1, 2) == "!!" then
local entry = v:sub(4)
-- remove any " due to whitespace in the path
entry = entry:gsub('^"', ""):gsub('$"', "")
if utils.is_windows then
entry = windowize_path(entry)
end
-- use the absolute path
table.insert(ignored, string.format("%s%s%s", git_root, os_sep, entry))
end
end

return ignored
end

M.is_ignored = function(ignored, path, _type)
path = _type == "directory" and (path .. os_sep) or path
for _, v in ipairs(ignored) do
if v:sub(-1) == os_sep then
-- directory ignore
if vim.startswith(path, v) then
return true
end
else
-- file ignore
if path == v then
return true
end
end
end
end

return M
3 changes: 2 additions & 1 deletion lua/neo-tree/sources/buffers/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ local renderer = require("neo-tree.ui.renderer")
local items = require("neo-tree.sources.buffers.lib.items")
local events = require("neo-tree.events")
local manager = require("neo-tree.sources.manager")
local git = require("neo-tree.git")

local M = { name = "buffers" }

Expand Down Expand Up @@ -75,7 +76,7 @@ M.setup = function(config, global_config)
handler = function(state)
local this_state = get_state()
if state == this_state then
state.git_status_lookup = utils.get_git_status()
state.git_status_lookup = git.status()
end
end,
})
Expand Down
4 changes: 2 additions & 2 deletions lua/neo-tree/sources/filesystem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ require("neo-tree").setup({
-- This function is called after the file system has been scanned,
-- but before the tree is rendered. You can use this to gather extra
-- data that can be used in the renderers.
local utils = require("neo-tree.utils")
state.git_status_lookup = utils.get_git_status()
local git = require("neo-tree.git")
state.git_status_lookup = git.status()
end,
-- The components section provides custom functions that may be called by
-- the renderers below. Each componment is a function that takes the
Expand Down
3 changes: 2 additions & 1 deletion lua/neo-tree/sources/filesystem/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ local inputs = require("neo-tree.ui.inputs")
local events = require("neo-tree.events")
local log = require("neo-tree.log")
local manager = require("neo-tree.sources.manager")
local git = require("neo-tree.git")

local M = { name = "filesystem" }

Expand Down Expand Up @@ -251,7 +252,7 @@ M.setup = function(config, global_config)
handler = function(state)
local this_state = get_state()
if state == this_state then
state.git_status_lookup = utils.get_git_status()
state.git_status_lookup = git.status()
end
end,
})
Expand Down
12 changes: 8 additions & 4 deletions lua/neo-tree/sources/filesystem/lib/fs_scan.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ local filter_external = require("neo-tree.sources.filesystem.lib.filter_external
local file_items = require("neo-tree.sources.common.file-items")
local log = require("neo-tree.log")
local fs_watch = require("neo-tree.sources.filesystem.lib.fs_watch")
local git = require("neo-tree.git")

local M = {}

Expand All @@ -20,14 +21,15 @@ local function do_scan(context, path_to_scan)

scan.scan_dir_async(path_to_scan, {
hidden = filters.show_hidden or false,
respect_gitignore = filters.respect_gitignore or false,
search_pattern = state.search_pattern or nil,
add_dirs = true,
depth = 1,
on_insert = function(path, _type)
local success, _ = pcall(file_items.create_item, context, path, _type)
if not success then
log.error("error creating item for ", path)
if not filters.respect_gitignore or not git.is_ignored(state.git_ignored, path, _type) then
local success, _ = pcall(file_items.create_item, context, path, _type)
if not success then
log.error("error creating item for ", path)
end
end
end,
on_exit = vim.schedule_wrap(function()
Expand Down Expand Up @@ -145,6 +147,8 @@ M.get_items_async = function(state, parent_id, path_to_reveal, callback)
end)
context.paths_to_load = utils.unique(context.paths_to_load)
end

state.git_ignored = state.filters.respect_gitignore and git.load_ignored(state.path) or {}
end
do_scan(context, parent_id or state.path)
end
Expand Down
4 changes: 2 additions & 2 deletions lua/neo-tree/sources/git_status/lib/items.lua
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
local vim = vim
local renderer = require("neo-tree.ui.renderer")
local utils = require("neo-tree.utils")
local file_items = require("neo-tree.sources.common.file-items")
local popups = require("neo-tree.ui.popups")
local log = require("neo-tree.log")
local git = require("neo-tree.git")

local M = {}

Expand All @@ -14,7 +14,7 @@ M.get_git_status = function(state)
return
end
state.loading = true
local status_lookup, project_root = utils.get_git_status(true)
local status_lookup, project_root = git.status(true)
state.path = project_root or state.path or vim.fn.getcwd()
local context = file_items.create_context(state)
-- Create root folder
Expand Down
2 changes: 1 addition & 1 deletion lua/neo-tree/ui/highlights.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ M.NORMALNC = "NeoTreeNormalNC"
M.ROOT_NAME = "NeoTreeRootName"
M.TITLE_BAR = "NeoTreeTitleBar"

function dec_to_hex(n)
local function dec_to_hex(n)
local hex = string.format("%06x", n)
if n < 16 then
hex = "0" .. hex
Expand Down
Loading