diff --git a/lua/neogit.lua b/lua/neogit.lua index 7be5f8909..7ea3b2b5a 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -6,6 +6,7 @@ local hl = require("neogit.lib.hl") local status = require("neogit.status") local state = require("neogit.lib.state") local input = require("neogit.lib.input") +local watcher = require("neogit.watcher") local logger = require("neogit.logger") local cli = require("neogit.lib.git.cli") @@ -39,8 +40,7 @@ local setup = function(opts) hl.setup() signs.setup() state.setup() - - require("neogit.autocmds").setup() + watcher.setup() end ---@param opts OpenOpts @@ -71,6 +71,8 @@ local open = function(opts) end end + require("neogit.lib.git").repo:dispatch_refresh { source = "open" } + if opts[1] ~= nil then local popup_name = opts[1] local has_pop, popup = pcall(require, "neogit.popups." .. popup_name) diff --git a/lua/neogit/autocmds.lua b/lua/neogit/autocmds.lua deleted file mode 100644 index 522489987..000000000 --- a/lua/neogit/autocmds.lua +++ /dev/null @@ -1,35 +0,0 @@ -local M = {} - -local api = vim.api -local group = api.nvim_create_augroup("Neogit", { clear = true }) - -local a = require("plenary.async") -local status = require("neogit.status") -local fs = require("neogit.lib.fs") - -function M.setup() - api.nvim_create_autocmd({ "BufWritePost", "ShellCmdPost", "VimResume" }, { - callback = function(o) - -- Skip update if the buffer is not open - if not status.status_buffer then - return - end - - -- Do not trigger on neogit buffers such as commit - if api.nvim_buf_get_option(o.buf, "filetype"):find("Neogit") then - return - end - - a.run(function() - local path = fs.relpath_from_repository(o.file) - if not path then - return - end - status.refresh({ status = true, diffs = { "*:" .. path } }, string.format("%s:%s", o.event, o.file)) - end, function() end) - end, - group = group, - }) -end - -return M diff --git a/lua/neogit/client.lua b/lua/neogit/client.lua index 8c2e1cf82..085273f05 100644 --- a/lua/neogit/client.lua +++ b/lua/neogit/client.lua @@ -103,9 +103,6 @@ function M.wrap(cmd, opts) else notif.create(opts.msg.fail) end - - a.util.scheduler() - require("neogit.status").refresh(true, opts.refresh) end return M diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index 34636d7ee..a67f14a60 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -9,7 +9,6 @@ local dv_lib = require("diffview.lib") local dv_utils = require("diffview.utils") local neogit = require("neogit") -local status = require("neogit.status") local a = require("plenary.async") local old_config @@ -132,7 +131,6 @@ local function get_local_diff_view(selected_file_name) } view:on_files_staged(a.void(function(_) - status.refresh({ status = true, diffs = true }, "on_files_staged") view:update_files() end)) diff --git a/lua/neogit/lib/git.lua b/lua/neogit/lib/git.lua index b23c014eb..7f2210538 100644 --- a/lua/neogit/lib/git.lua +++ b/lua/neogit/lib/git.lua @@ -1,24 +1,24 @@ return { - repo = require("neogit.lib.git.repository"), + branch = require("neogit.lib.git.branch"), + cherry_pick = require("neogit.lib.git.cherry_pick"), cli = require("neogit.lib.git.cli"), - init = require("neogit.lib.git.init"), - status = require("neogit.lib.git.status"), - stash = require("neogit.lib.git.stash"), - files = require("neogit.lib.git.files"), + config = require("neogit.lib.git.config"), + diff = require("neogit.lib.git.diff"), fetch = require("neogit.lib.git.fetch"), + files = require("neogit.lib.git.files"), + index = require("neogit.lib.git.index"), + init = require("neogit.lib.git.init"), log = require("neogit.lib.git.log"), - reflog = require("neogit.lib.git.reflog"), - branch = require("neogit.lib.git.branch"), - diff = require("neogit.lib.git.diff"), - rebase = require("neogit.lib.git.rebase"), merge = require("neogit.lib.git.merge"), - cherry_pick = require("neogit.lib.git.cherry_pick"), + pull = require("neogit.lib.git.pull"), + push = require("neogit.lib.git.push"), + rebase = require("neogit.lib.git.rebase"), + reflog = require("neogit.lib.git.reflog"), + remote = require("neogit.lib.git.remote"), + repo = require("neogit.lib.git.repository"), reset = require("neogit.lib.git.reset"), revert = require("neogit.lib.git.revert"), - remote = require("neogit.lib.git.remote"), - config = require("neogit.lib.git.config"), sequencer = require("neogit.lib.git.sequencer"), - pull = require("neogit.lib.git.pull"), - push = require("neogit.lib.git.push"), - index = require("neogit.lib.git.index"), + stash = require("neogit.lib.git.stash"), + status = require("neogit.lib.git.status"), } diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index b7d3c73db..fbc2fdfee 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -187,6 +187,7 @@ local function build_metatable(f, raw_output_fn) end, }) + logger.trace("[DIFF] Adding metatable for: " .. f.name) f.has_diff = true end @@ -215,9 +216,16 @@ local function raw_staged(name) end end +-- When there is _no_ filter, invalidate all diffs +-- When there _is_ a filter, only invalidate matching items +-- And, of course, don't worry about items that haven't loaded diffs local function invalidate_diff(filter, section, item) + if not rawget(item, "diff") then + return + end + if not filter or filter:accepts(section, item.name) then - logger.debug("[DIFF] Invalidating cached diff for: " .. item.name) + logger.debug(string.format("[DIFF] Invalidating diff for: %s", item.name)) item.diff = nil end end @@ -225,10 +233,10 @@ end return { parse = parse_diff, register = function(meta) - meta.update_diffs = function(repo, filter) - filter = filter or false - if filter and type(filter) == "table" then - filter = ItemFilter.create(filter) + meta.update_diffs = function(repo) + local filter + if repo.invalidate[1] then + filter = ItemFilter.create(repo.invalidate) end for _, f in ipairs(repo.untracked.items) do diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 3dcc42c20..43302877e 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -109,4 +109,10 @@ function M.update() :spawn_async() end +function M.register(meta) + meta.update_index = function(state) + state.index.timestamp = state.index_stat() + end +end + return M diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index 2db9aa836..081bcefe2 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -49,8 +49,7 @@ M.init_repo = function() vim.cmd(string.format("cd %s", directory)) M.create(directory) - - status.refresh(true, "InitRepo") + status.refresh() end return M diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index b8ae0ae88..2d3f24f36 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -255,7 +255,9 @@ function M.list(options, show_popup) table.insert(options, "--max-count=256") end - local output = cli.log.format(format).graph.arg_list(options or {}).show_popup(show_popup).call():trim() + local output = + cli.log.format(format).graph.arg_list(options or {}).show_popup(show_popup).call_sync():trim() + return parse_log(output.stdout, graph) end diff --git a/lua/neogit/lib/git/merge.lua b/lua/neogit/lib/git/merge.lua index 88e811892..603c0ac37 100644 --- a/lua/neogit/lib/git/merge.lua +++ b/lua/neogit/lib/git/merge.lua @@ -7,6 +7,7 @@ local M = {} local a = require("plenary.async") +-- TODO: client.wrap() local function merge_command(cmd) local envs = client.get_envs_git_editor() return cmd.env(envs).show_popup(true):in_pty(true).call(true) diff --git a/lua/neogit/lib/git/pull.lua b/lua/neogit/lib/git/pull.lua index 11c287397..32c72a6b5 100644 --- a/lua/neogit/lib/git/pull.lua +++ b/lua/neogit/lib/git/pull.lua @@ -3,6 +3,7 @@ local util = require("neogit.lib.util") local M = {} +-- TODO: client.wrap() function M.pull_interactive(remote, branch, args) local client = require("neogit.client") local envs = client.get_envs_git_editor() @@ -14,7 +15,7 @@ local function update_unpulled(state) return end - local result = cli.log.oneline.for_range("..@{upstream}").show_popup(false).call():trim().stdout + local result = cli.log.oneline.for_range("..@{upstream}").show_popup(false).call_sync():trim().stdout state.unpulled.items = util.map(result, function(x) return { name = x } diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index bc25772ea..2390cffc2 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -6,6 +6,7 @@ local M = {} local a = require("plenary.async") +-- TODO: client.wrap() local function rebase_command(cmd) local git = require("neogit.lib.git") cmd = cmd or git.cli.rebase @@ -22,9 +23,6 @@ function M.rebase_interactive(commit, args) else notif.create("Rebased successfully", vim.log.levels.INFO) end - a.util.scheduler() - local status = require("neogit.status") - status.refresh(true, "rebase_interactive") end function M.rebase_onto(branch, args) diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index ba8043171..83dedb90a 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -15,8 +15,17 @@ local function empty_state() git_path = function(path) return Path.new(root):joinpath(".git", path) end, + index_stat = function() + local index = Path.new(root):joinpath(".git", "index") + if index:exists() then + return index:_stat().mtime.sec + end + end, cwd = vim.fn.getcwd(), git_root = root, + rev_toplevel = nil, + invalidate = {}, + index = { timestamp = 0 }, head = { branch = nil, commit_message = "" }, upstream = { branch = nil, commit_message = "", remote = nil }, untracked = { items = {} }, @@ -48,106 +57,83 @@ function M.reset(self) self.state = empty_state() end -function M.refresh(self, lib) - local refreshes = {} +-- Invalidates a cached diff for a file +function M.invalidate(self, ...) + local files = { ... } + for _, path in ipairs(files) do + table.insert(self.state.invalidate, string.format("*:%s", path)) + end +end - if lib and type(lib) == "table" then - if lib.status then - self.lib.update_status(self.state) - a.util.scheduler() - end +local refresh_lock = a.control.Semaphore.new(1) +local lock_holder - if lib.branch_information then - table.insert(refreshes, function() - logger.debug("[REPO]: Refreshing branch information") - self.lib.update_branch_information(self.state) - end) - end +M.dispatch_refresh = a.void(function(self, opts) + opts = opts or {} - if lib.rebase then - table.insert(refreshes, function() - logger.debug("[REPO]: Refreshing rebase information") - self.lib.update_rebase_status(self.state) - end) - end + logger.info(string.format("[REPO]: Refreshing START (source: %s)", opts.source or "UNKNOWN")) - if lib.cherry_pick then - table.insert(refreshes, function() - logger.debug("[REPO]: Refreshing cherry-pick information") - self.lib.update_cherry_pick_status(self.state) - end) - end + if refresh_lock.permits == 0 then + logger.debug(string.format("[REPO]: Refreshing ABORTED - refresh_lock held by %s", lock_holder)) + return + end - if lib.merge then - table.insert(refreshes, function() - logger.debug("[REPO]: Refreshing merge information") - self.lib.update_merge_status(self.state) - end) - end + -- stylua: ignore + if + self.state.index.timestamp == self.state.index_stat() and + opts.source == "watcher" + then + logger.debug("[REPO]: Refreshing ABORTED - .git/index hasn't been modified since last refresh") + return + end - if lib.stashes then - table.insert(refreshes, function() - logger.debug("[REPO]: Refreshing stash") - self.lib.update_stashes(self.state) - end) - end + if self.state.git_root == "" then + logger.debug("[REPO]: Refreshing ABORTED - No git_root") + return + end - if lib.unpulled then - table.insert(refreshes, function() - logger.debug("[REPO]: Refreshing unpulled commits") - self.lib.update_unpulled(self.state) - end) - end + lock_holder = opts.source or "UNKNOWN" + logger.debug(string.format("[REPO]: Acquiring refresh lock (source: %s)", lock_holder)) + local permit = refresh_lock:acquire() - if lib.unmerged then - table.insert(refreshes, function() - logger.debug("[REPO]: Refreshing unpushed commits") - self.lib.update_unmerged(self.state) + a.util.scheduler() + -- Status needs to run first because other update fn's depend on it + logger.trace("[REPO]: Refreshing %s", "update_status") + self.lib.update_status(self.state) + + local updates = {} + for name, fn in pairs(self.lib) do + if name ~= "update_status" then + table.insert(updates, function() + logger.trace(string.format("[REPO]: Refreshing %s", name)) + fn(self.state) end) end + end - if lib.recent then - table.insert(refreshes, function() - logger.debug("[REPO]: Refreshing recent commits") - self.lib.update_recent(self.state) - end) - end + a.util.run_all(updates, function() + self.state.invalidate = {} - if lib.diffs then - local filter = (type(lib) == "table" and type(lib.diffs) == "table") and lib.diffs or nil + logger.info("[REPO]: Refreshes completed - freeing refresh lock") + permit:forget() + lock_holder = nil - table.insert(refreshes, function() - logger.debug("[REPO]: Refreshing diffs") - self.lib.update_diffs(self.state, filter) - end) - end - else - logger.debug("[REPO]: Refreshing ALL") - self.lib.update_status(self.state) - a.util.scheduler() - - for name, fn in pairs(self.lib) do - table.insert(refreshes, function() - logger.debug("[REPO]: Refreshing " .. name) - fn(self.state) - end) + if opts.callback then + logger.debug("[REPO]: Running refresh callback") + opts.callback() end - end - - logger.debug(string.format("[REPO]: Running %d refresh(es)", #refreshes)) - a.util.join(refreshes) - a.util.scheduler() - logger.debug("[REPO]: Refreshes completed") -end + end) +end) if not M.initialized then - logger.debug("[REPO]: Initializing Repository") + logger.info("[REPO]: Initializing Repository") M.initialized = true setmetatable(M, meta) local modules = { "status", + "index", "diff", "stash", "pull", diff --git a/lua/neogit/lib/git/revert.lua b/lua/neogit/lib/git/revert.lua index 14007952e..37e49f2cf 100644 --- a/lua/neogit/lib/git/revert.lua +++ b/lua/neogit/lib/git/revert.lua @@ -7,7 +7,6 @@ local M = {} function M.commits(commits, args) client.wrap(cli.revert.args(table.concat(commits, " ")).arg_list(args), { autocmd = "NeogitRevertComplete", - refresh = "do_revert", msg = { setup = "Reverting...", success = "Reverted!", diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index fa078884f..f1c1d520a 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -105,7 +105,7 @@ function M.drop(stash) end function M.list() - return cli.stash.args("list").call():trim().stdout + return cli.stash.args("list").call_sync():trim().stdout end function M.register(meta) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index a3e80a597..f2304ebb4 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -2,7 +2,6 @@ local git = { cli = require("neogit.lib.git.cli"), stash = require("neogit.lib.git.stash"), } -local a = require("plenary.async") local Collection = require("neogit.lib.collection") local function update_file(file, mode, name) @@ -20,13 +19,7 @@ local function update_file(file, mode, name) end local function update_status(state) - -- git-status outputs files relative to the cwd. - -- - -- Save the working directory to allow resolution to absolute paths since the - -- cwd may change after the status is refreshed and used, especially if using - -- rooter plugins with lsp integration - local cwd = vim.fn.getcwd() - local result = git.cli.status.porcelain(2).branch.call():trim() + local result = git.cli.status.porcelain(2).branch.call_sync():trim() local untracked_files, unstaged_files, staged_files = {}, {}, {} local old_files_hash = { @@ -54,9 +47,7 @@ local function update_status(state) else local kind, rest = l:match("(.) (.+)") if kind == "?" then - table.insert(untracked_files, { - name = rest, - }) + table.insert(untracked_files, { name = rest }) elseif kind == "u" then local mode, _, _, _, _, _, _, _, _, name = rest:match("(..) (....) (%d+) (%d+) (%d+) (%d+) (%w+) (%w+) (%w+) (.+)") @@ -110,7 +101,6 @@ local function update_status(state) upstream.commit_message = state.upstream.commit_message end - state.cwd = cwd state.head = head state.upstream = upstream state.untracked.items = untracked_files @@ -119,49 +109,45 @@ local function update_status(state) end local function update_branch_information(state) - local tasks = {} - if state.head.oid ~= "(initial)" then - table.insert(tasks, function() - local result = git.cli.log.max_count(1).pretty("%B").call():trim() - state.head.commit_message = result.stdout[1] - end) + local result = git.cli.log.max_count(1).pretty("%B").call_sync():trim() + state.head.commit_message = result.stdout[1] if state.upstream.ref then - table.insert(tasks, function() - local result = - git.cli.log.max_count(1).pretty("%B").for_range("@{upstream}").show_popup(false).call():trim() - state.upstream.commit_message = result.stdout[1] - end) + local result = + git.cli.log.max_count(1).pretty("%B").for_range("@{upstream}").show_popup(false).call_sync():trim() + state.upstream.commit_message = result.stdout[1] end end +end - if #tasks > 0 then - a.util.join(tasks) - end +local M = {} + +function M.stage(...) + require("neogit.lib.git.repository"):invalidate(...) + git.cli.add.files(...).call() end -local status = { - stage = function(...) - git.cli.add.files(...).call() - end, - stage_modified = function() - git.cli.add.update.call() - end, - stage_all = function() - git.cli.add.all.call() - end, - unstage = function(...) - git.cli.reset.files(...).call() - end, - unstage_all = function() - git.cli.reset.call() - end, -} +function M.stage_modified() + git.cli.add.update.call() +end + +function M.stage_all() + git.cli.add.all.call() +end + +function M.unstage(...) + require("neogit.lib.git.repository"):invalidate(...) + git.cli.reset.files(...).call() +end + +function M.unstage_all() + git.cli.reset.call() +end -status.register = function(meta) +function M.register(meta) meta.update_status = update_status meta.update_branch_information = update_branch_information end -return status +return M diff --git a/lua/neogit/lib/notification.lua b/lua/neogit/lib/notification.lua index ada4bf091..e45c78bed 100644 --- a/lua/neogit/lib/notification.lua +++ b/lua/neogit/lib/notification.lua @@ -3,6 +3,11 @@ local message_history = {} local notifications = {} local notification_count = 0 +local levels = {} +for k, v in pairs(vim.log.levels) do + table.insert(levels, v, k) +end + ---@param message string local function create(message, level, delay) if not level then @@ -119,10 +124,7 @@ local function create(message, level, delay) end end - table.insert(message_history, { - content = message, - level = level, - }) + table.insert(message_history, { content = message, level = level, kind = levels[level] }) if vim.fn.winbufnr(window) ~= -1 then vim.api.nvim_win_close(window, false) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index de77997b4..c479e6435 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -1,7 +1,9 @@ local a = require("plenary.async") +local git = require("neogit.lib.git") local state = require("neogit.lib.state") local config = require("neogit.lib.git.config") local util = require("neogit.lib.util") +local logger = require("neogit.logger") local M = {} @@ -256,10 +258,23 @@ end ---@return self function M:action(key, description, callback) if not self.state.keys[key] then + local action + + if callback then + action = a.void(function(...) + callback(...) + + git.repo:dispatch_refresh { + callback = require("neogit.status").dispatch_refresh, + source = "action", + } + end) + end + table.insert(self.state.actions[#self.state.actions], { key = key, description = description, - callback = callback and a.void(callback) or nil, + callback = action, }) self.state.keys[key] = true @@ -285,6 +300,7 @@ function M:build() error("A popup needs to have a name!") end + logger.debug(string.format("[BUILDER] Building %s Popup", self.state.name)) return self.builder_fn(self.state) end diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index 42a8d418a..437c863d2 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -1,4 +1,5 @@ local a = require("plenary.async") +local uv = vim.loop ---@generic T: any ---@generic U: any @@ -249,6 +250,31 @@ local function build_reverse_lookup(tbl) return result end +--- Debounces a function on the trailing edge. +--- +--- @generic F: function +--- @param ms number Timeout in ms +--- @param fn F Function to debounce +--- @return F Debounced function. +local function debounce_trailing(ms, fn) + local timer = assert(uv.new_timer()) + return function(...) + local argv = { ... } + timer:start(ms, 0, function() + timer:stop() + fn(unpack(argv)) + end) + end +end + +-- Ensure a string is a minimum width +---@param s string +---@param len integer +---@return string +local function pad_right(s, len) + return s .. string.rep(" ", math.max(len - #s, 0)) +end + return { time = time, time_async = time_async, @@ -276,4 +302,6 @@ return { merge = merge, str_min_width = str_min_width, str_clamp = str_clamp, + debounce_trailing = debounce_trailing, + pad_right = pad_right, } diff --git a/lua/neogit/logger.lua b/lua/neogit/logger.lua index 71d285407..ee7e28d50 100644 --- a/lua/neogit/logger.lua +++ b/lua/neogit/logger.lua @@ -2,7 +2,7 @@ local log = require("plenary.log") return log.new { plugin = "neogit", - highlights = false, + highlights = vim.env.NEOGIT_LOG_HIGHLIGHTS or false, use_console = vim.env.NEOGIT_LOG_CONSOLE or false, use_file = vim.env.NEOGIT_LOG_FILE or false, level = vim.env.NEOGIT_LOG_LEVEL or "info", diff --git a/lua/neogit/operations.lua b/lua/neogit/operations.lua index 9602d3fea..d47799a11 100644 --- a/lua/neogit/operations.lua +++ b/lua/neogit/operations.lua @@ -17,7 +17,7 @@ function M.wait(key, time) if M[key] == nil then return end - vim.fn.wait(time or 1000, function() + vim.wait(time or 1000, function() return M[key] == false end, 100) end diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index fe2a1c1c6..1d6bacc17 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -1,6 +1,5 @@ local M = {} -local status = require("neogit.status") local git = require("neogit.lib.git") local input = require("neogit.lib.input") local util = require("neogit.lib.util") @@ -28,8 +27,7 @@ M.checkout_branch_revision = operation("checkout_branch_revision", function(popu return end - git.cli.checkout.branch(selected_branch).arg_list(popup:get_arguments()).call_sync():trim() - status.refresh(true, "checkout_branch") + git.cli.checkout.branch(selected_branch).arg_list(popup:get_arguments()).call_sync() end) M.checkout_local_branch = operation("checkout_local_branch", function(popup) @@ -55,8 +53,6 @@ M.checkout_local_branch = operation("checkout_local_branch", function(popup) elseif target then git.cli.checkout.branch(target).arg_list(popup:get_arguments()).call_sync() end - - status.refresh(true, "branch_checkout") end) M.checkout_create_branch = operation("checkout_create_branch", function() @@ -78,12 +74,10 @@ M.checkout_create_branch = operation("checkout_create_branch", function() end git.cli.checkout.new_branch_with_start_point(name, base_branch).call_sync():trim() - status.refresh(true, "branch_create") end) M.create_branch = operation("create_branch", function() git.branch.create() - status.refresh(true, "create_branch") end) M.configure_branch = operation("configure_branch", function() @@ -114,7 +108,6 @@ M.rename_branch = operation("rename_branch", function() new_name, _ = new_name:gsub("%s", "-") git.cli.branch.move.args(selected_branch, new_name).call_sync():trim() - status.refresh(true, "rename_branch") end) M.reset_branch = operation("reset_branch", function() @@ -145,7 +138,6 @@ M.reset_branch = operation("reset_branch", function() git.cli["update-ref"].message(string.format("reset: moving to %s", to)).args(from, to).call_sync() notif.create(string.format("Reset '%s'", git.repo.head.branch), vim.log.levels.INFO) - status.refresh(true, "reset_branch") end) M.delete_branch = operation("delete_branch", function() @@ -185,8 +177,6 @@ M.delete_branch = operation("delete_branch", function() notif.create(string.format("Deleted branch '%s'", branch_name), vim.log.levels.INFO) end end - - status.refresh(true, "delete_branch") end) return M diff --git a/lua/neogit/popups/cherry_pick/actions.lua b/lua/neogit/popups/cherry_pick/actions.lua index bb0c235e9..069b98909 100644 --- a/lua/neogit/popups/cherry_pick/actions.lua +++ b/lua/neogit/popups/cherry_pick/actions.lua @@ -1,6 +1,5 @@ local M = {} -local a = require("plenary.async") local git = require("neogit.lib.git") local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") @@ -23,11 +22,7 @@ function M.pick(popup) return end - a.util.scheduler() git.cherry_pick.pick(commits, popup:get_arguments()) - - a.util.scheduler() - require("neogit.status").refresh(true, "cherry_pick_pick") end function M.apply(popup) @@ -36,29 +31,19 @@ function M.apply(popup) return end - a.util.scheduler() git.cherry_pick.apply(commits, popup:get_arguments()) - - a.util.scheduler() - require("neogit.status").refresh(true, "cherry_pick_apply") end function M.continue() git.cherry_pick.continue() - a.util.scheduler() - require("neogit.status").refresh(true, "cherry_pick_continue") end function M.skip() git.cherry_pick.skip() - a.util.scheduler() - require("neogit.status").refresh(true, "cherry_pick_skip") end function M.abort() git.cherry_pick.abort() - a.util.scheduler() - require("neogit.status").refresh(true, "cherry_pick_abort") end return M diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 3e87befaf..fd201a96e 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -4,7 +4,6 @@ local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") local git = require("neogit.lib.git") local client = require("neogit.client") local input = require("neogit.lib.input") -local a = require("plenary.async") local function confirm_modifications() if @@ -40,9 +39,7 @@ local function commit_special(popup, method) return end - a.util.scheduler() do_commit(popup, git.cli.commit.args(method, commit)) - a.util.scheduler() return commit end diff --git a/lua/neogit/popups/fetch/actions.lua b/lua/neogit/popups/fetch/actions.lua index 3c56ba848..6c01725b7 100644 --- a/lua/neogit/popups/fetch/actions.lua +++ b/lua/neogit/popups/fetch/actions.lua @@ -1,10 +1,8 @@ local M = {} -local a = require("plenary.async") local git = require("neogit.lib.git") local logger = require("neogit.logger") local notif = require("neogit.lib.notification") -local status = require("neogit.status") local util = require("neogit.lib.util") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -14,10 +12,8 @@ local function fetch_from(name, remote, branch, args) local res = git.fetch.fetch_interactive(remote, branch, args) if res and res.code == 0 then - a.util.scheduler() notif.create("Fetched from " .. name) logger.debug("Fetched from " .. name) - status.refresh(true, "fetch_from") vim.api.nvim_exec_autocmds("User", { pattern = "NeogitFetchComplete", modeline = false }) else logger.error("Failed to fetch from " .. name) @@ -106,7 +102,6 @@ end function M.fetch_submodules(_) notif.create("Fetching submodules") git.cli.fetch.recurse_submodules().verbose().jobs(4).call() - status.refresh(true, "fetch_submodules") end function M.set_variables() diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index 1651802ca..3a478f3f3 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -71,13 +71,7 @@ end M.essential = function() return present { - { - "RefreshBuffer", - "Refresh", - function() - require("neogit.status").refresh(true, "user_refresh") - end, - }, + { "RefreshBuffer", "Refresh", require("neogit.status").refresh }, { "GoToFile", "Go to file", NONE }, { "Toggle", "Toggle", NONE }, } diff --git a/lua/neogit/popups/log/actions.lua b/lua/neogit/popups/log/actions.lua index 7a5a91e25..4b73a5783 100644 --- a/lua/neogit/popups/log/actions.lua +++ b/lua/neogit/popups/log/actions.lua @@ -19,6 +19,7 @@ function M.log_head(popup) :open() end +-- TODO: Verify if branch is nil or empty with detatched head function M.log_local_branches(popup) LogViewBuffer.new( git.log.list(util.merge(popup:get_arguments(), { diff --git a/lua/neogit/popups/merge/actions.lua b/lua/neogit/popups/merge/actions.lua index 8d2eebeb9..d8452018d 100644 --- a/lua/neogit/popups/merge/actions.lua +++ b/lua/neogit/popups/merge/actions.lua @@ -3,8 +3,6 @@ local M = {} local git = require("neogit.lib.git") local input = require("neogit.lib.input") -local a = require("plenary.async") - local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") function M.in_merge() @@ -13,8 +11,6 @@ end function M.commit() git.merge.continue() - a.util.scheduler() - require("neogit.status").refresh(true, "merge_continue") end function M.abort() @@ -23,8 +19,6 @@ function M.abort() end git.merge.abort() - a.util.scheduler() - require("neogit.status").refresh(true, "merge_abort") end function M.merge(popup) @@ -34,8 +28,6 @@ function M.merge(popup) end git.merge.merge(branch, popup:get_arguments()) - a.util.scheduler() - require("neogit.status").refresh(true, "merge") end return M diff --git a/lua/neogit/popups/pull/actions.lua b/lua/neogit/popups/pull/actions.lua index 3ea8296ff..c4b8d8e99 100644 --- a/lua/neogit/popups/pull/actions.lua +++ b/lua/neogit/popups/pull/actions.lua @@ -1,8 +1,6 @@ -local a = require("plenary.async") local git = require("neogit.lib.git") local logger = require("neogit.logger") local notif = require("neogit.lib.notification") -local status = require("neogit.status") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -23,10 +21,8 @@ local function pull_from(args, remote, branch, opts) local res = git.pull.pull_interactive(remote, branch, args) if res and res.code == 0 then - a.util.scheduler() notif.create("Pulled from " .. name) logger.debug("Pulled from " .. name) - status.refresh(true, "pull_from") vim.api.nvim_exec_autocmds("User", { pattern = "NeogitPullComplete", modeline = false }) else logger.error("Failed to pull from " .. name) diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index cdadf5592..63f73db86 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -1,8 +1,6 @@ -local a = require("plenary.async") local git = require("neogit.lib.git") local logger = require("neogit.logger") local notif = require("neogit.lib.notification") -local status = require("neogit.status") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -23,10 +21,8 @@ local function push_to(args, remote, branch, opts) local res = git.push.push_interactive(remote, branch, args) if res and res.code == 0 then - a.util.scheduler() logger.error("Pushed to " .. name) notif.create("Pushed to " .. name) - status.refresh(true, "push_to") vim.api.nvim_exec_autocmds("User", { pattern = "NeogitPushComplete", modeline = false }) else logger.error("Failed to push to " .. name) diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index 150f4866a..132969543 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -1,7 +1,5 @@ -local a = require("plenary.async") local git = require("neogit.lib.git") local input = require("neogit.lib.input") -local status = require("neogit.status") local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -15,8 +13,6 @@ end function M.onto_base(popup) git.rebase.rebase_onto(M.base_branch(), popup:get_arguments()) - a.util.scheduler() - status.refresh(true, "rebase_master") end function M.onto_pushRemote(popup) @@ -30,9 +26,6 @@ function M.onto_pushRemote(popup) string.format("refs/remotes/%s/%s", pushRemote, git.branch.current()), popup:get_arguments() ) - - a.util.scheduler() - status.refresh(true, "rebase_pushremote") end end @@ -50,16 +43,12 @@ function M.onto_upstream(popup) end git.rebase.rebase_onto(upstream, popup:get_arguments()) - a.util.scheduler() - status.refresh(true, "rebase_upstream") end function M.onto_elsewhere(popup) local target = FuzzyFinderBuffer.new(git.branch.get_all_branches()):open_async() if target then git.rebase.rebase_onto(target, popup:get_arguments()) - a.util.scheduler() - status.refresh(true, "rebase_elsewhere") end end @@ -67,29 +56,21 @@ function M.interactively(popup) local commit = popup.state.env.commit[1] or CommitSelectViewBuffer.new(git.log.list()):open_async() if commit then git.rebase.rebase_interactive(commit, popup:get_arguments()) - a.util.scheduler() - status.refresh(true, "rebase_interactive") end end function M.continue() git.rebase.continue() - a.util.scheduler() - status.refresh(true, "rebase_continue") end function M.skip() git.rebase.skip() - a.util.scheduler() - status.refresh(true, "rebase_skip") end -- TODO: Extract to rebase lib? function M.abort() if input.get_confirmation("Abort rebase?", { values = { "&Yes", "&No" }, default = 2 }) then git.cli.rebase.abort.call_sync():trim() - a.util.scheduler() - status.refresh(true, "rebase_abort") end end diff --git a/lua/neogit/popups/rebase/init.lua b/lua/neogit/popups/rebase/init.lua index b6f2d61b2..73743ec59 100644 --- a/lua/neogit/popups/rebase/init.lua +++ b/lua/neogit/popups/rebase/init.lua @@ -42,7 +42,7 @@ function M.create(commit) :action_if(not in_rebase, "f", "to autosquash") :env({ commit = commit, - highlight = { branch, git.repo.upstream.ref }, + highlight = { branch, git.repo.upstream.ref, base_branch }, bold = { "@{upstream}", "pushRemote" }, }) :build() diff --git a/lua/neogit/popups/remote/actions.lua b/lua/neogit/popups/remote/actions.lua index 1fc088616..1490f9fcf 100644 --- a/lua/neogit/popups/remote/actions.lua +++ b/lua/neogit/popups/remote/actions.lua @@ -1,10 +1,8 @@ local M = {} -local a = require("plenary.async") local git = require("neogit.lib.git") local input = require("neogit.lib.input") local notification = require("neogit.lib.notification") -local status = require("neogit.status") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local RemoteConfigPopup = require("neogit.popups.remote_config") @@ -52,9 +50,7 @@ function M.rename(_) end git.remote.rename(selected_remote, new_name) - a.util.scheduler() notification.create("Renamed remote " .. selected_remote .. " to " .. new_name) - status.refresh(true, "rename_remote") end function M.remove(_) @@ -64,9 +60,7 @@ function M.remove(_) end git.remote.remove(selected_remote) - a.util.scheduler() notification.create("Removed remote " .. selected_remote) - status.refresh(true, "remove_remote") end function M.configure(_) @@ -86,8 +80,6 @@ function M.prune_branches(_) notification.create("Pruning remote " .. selected_remote) git.remote.prune(selected_remote) - a.util.scheduler() - status.refresh(true, "prune_remote") end -- https://github.com/magit/magit/blob/main/lisp/magit-remote.el#L159 diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index 1b42986ac..126e64f60 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -1,6 +1,5 @@ local a = require("plenary.async") local git = require("neogit.lib.git") -local status = require("neogit.status") local util = require("neogit.lib.util") local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") @@ -15,8 +14,6 @@ local function reset(type) end git.reset[type](commit) - a.util.scheduler() - status.refresh(true, "reset_" .. type) end function M.mixed() @@ -63,8 +60,6 @@ function M.a_file() end git.reset.file(commit, files) - a.util.scheduler() - status.refresh(true, "reset_file") end return M diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index 227dccd5a..7d318953e 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -26,8 +26,6 @@ function M.commits(popup) end git.revert.commits(commits, popup:get_arguments()) - a.util.scheduler() - require("neogit.status").refresh(true, "revert_commits") end return M diff --git a/lua/neogit/popups/stash/actions.lua b/lua/neogit/popups/stash/actions.lua index c6f1d3d2d..28d8ecdf5 100644 --- a/lua/neogit/popups/stash/actions.lua +++ b/lua/neogit/popups/stash/actions.lua @@ -1,5 +1,3 @@ -local a = require("plenary.async") -local status = require("neogit.status") local git = require("neogit.lib.git") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -8,14 +6,10 @@ local M = {} function M.both(popup) git.stash.stash_all(popup:get_arguments()) - a.util.scheduler() - status.refresh(true, "stash_both") end function M.index(popup) git.stash.stash_index(popup:get_arguments()) - a.util.scheduler() - status.refresh(true, "stash_index") end function M.push(popup) @@ -25,8 +19,6 @@ function M.push(popup) end git.stash.push(popup:get_arguments(), files) - a.util.scheduler() - status.refresh(true, "stash_push") end local function use(action, stash) @@ -45,8 +37,6 @@ local function use(action, stash) if name then git.stash[action](name) - a.util.scheduler() - status.refresh(true, "stash_" .. action) end end diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index 5954c2013..a5c489869 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -66,7 +66,7 @@ local function create_preview_buffer() -- May be called multiple times due to scheduling if preview_buffer then if preview_buffer.buffer then - logger.debug("Preview buffer already exists. Focusing the existing one") + logger.trace("[PROCESS] Preview buffer already exists. Focusing the existing one") preview_buffer.buffer:focus() end return @@ -360,7 +360,7 @@ function Process:spawn(cb) end end - logger.debug("Spawning: " .. vim.inspect(self.cmd)) + logger.trace("[PROCESS] Spawning: " .. vim.inspect(self.cmd)) local job = vim.fn.jobstart(self.cmd, { cwd = self.cwd, env = self.env, diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index bd7e650c3..80eb0c3ce 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -13,7 +13,8 @@ local LineBuffer = require("neogit.lib.line_buffer") local fs = require("neogit.lib.fs") local input = require("neogit.lib.input") -local map = require("neogit.lib.util").map +local util = require("neogit.lib.util") +local map = util.map local api = vim.api local fn = vim.fn @@ -32,8 +33,6 @@ M.commit_view = nil ---@field files StatusItem[] M.locations = {} -M.outdated = {} - ---@class StatusItem ---@field name string ---@field first number @@ -84,7 +83,7 @@ local function get_section_item_for_line(linenr) end ---@return Section|nil, StatusItem|nil -local function get_current_section_item() +function M.get_current_section_item() return get_section_item_for_line(vim.fn.line(".")) end @@ -145,10 +144,6 @@ local function format_mode(mode) return mode end -local function pad_right(s, len) - return s .. string.rep(" ", math.max(len - #s, 0)) -end - local function draw_buffer() M.status_buffer:clear_sign_group("hl") M.status_buffer:clear_sign_group("fold_markers") @@ -207,7 +202,7 @@ local function draw_buffer() location.files = {} for _, f in ipairs(data.items) do - local label = pad_right(format_mode(f.mode), max_len) + local label = util.pad_right(format_mode(f.mode), max_len) if label and vim.o.columns < 120 then label = vim.trim(label) end @@ -373,7 +368,7 @@ local function refresh_status_buffer() M.status_buffer:unlock() - logger.debug("[STATUS BUFFER]: Redrawing") + logger.debug("[STATUS BUFFER]: Starting Redrawing") draw_buffer() draw_signs() @@ -385,37 +380,13 @@ local function refresh_status_buffer() vim.cmd("redraw") end -local refresh_lock = a.control.Semaphore.new(1) -local lock_holder = nil - -local function refresh(which, reason) +function M.refresh() logger.info("[STATUS BUFFER]: Starting refresh") - if refresh_lock.permits == 0 then - logger.debug( - string.format( - "[STATUS BUFFER]: Refresh lock not available. Aborting refresh. Lock held by: %q", - lock_holder - ) - ) - --- Undo the deadlock fix - --- This is because refresh wont properly wait but return immediately if - --- refresh is already in progress. This breaks as waiting for refresh does - --- not mean that a status buffer is drawn and ready - a.util.scheduler() - -- refresh_status() - -- return - end - - local permit = refresh_lock:acquire() - lock_holder = reason or "unknown" - logger.debug("[STATUS BUFFER]: Acquired refresh lock: " .. lock_holder) - a.util.scheduler() local s, f, h = save_cursor_location() - if cli.git_root() ~= "" then - git.repo:refresh(which) + if git.repo.git_root ~= "" then refresh_status_buffer() vim.api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) end @@ -426,17 +397,13 @@ local function refresh(which, reason) end logger.info("[STATUS BUFFER]: Finished refresh") - - lock_holder = nil - permit:forget() - logger.info("[STATUS BUFFER]: Refresh lock is now free") end -local dispatch_refresh = a.void(function(v, reason) - refresh(v, reason) +M.dispatch_refresh = a.void(function() + M.refresh() end) -local refresh_manually = a.void(function(fname) +M.refresh_manually = a.void(function(fname) if not fname or fname == "" then return end @@ -445,23 +412,21 @@ local refresh_manually = a.void(function(fname) if not path then return end - refresh({ status = true, diffs = { "*:" .. path } }, "manually") + + git.repo:dispatch_refresh { callback = M.refresh } end) --- Compatibility endpoint to refresh data from an autocommand. -- `fname` should be `` in this case. This function will take care of -- resolving the file name to the path relative to the repository root and -- refresh that file's cache data. -local function refresh_viml_compat(fname) +function M.refresh_viml_compat(fname) logger.info("[STATUS BUFFER]: refresh_viml_compat") - if not config.values.auto_refresh then - return - end if #vim.fs.find(".git/", { upward = true }) == 0 then -- not a git repository return end - refresh_manually(fname) + M.refresh_manually(fname) end local function current_line_is_hunk() @@ -495,8 +460,8 @@ local function get_current_hunk_of_item(item) return get_hunk_of_item_for_line(item, vim.fn.line(".")) end -local function toggle() - local section, item = get_current_section_item() +function M.toggle() + local section, item = M.get_current_section_item() if section == nil then return end @@ -517,18 +482,15 @@ local function toggle() refresh_status_buffer() end -local reset = function() +M.reset = function() git.repo:reset() M.locations = {} - if not config.values.auto_refresh then - return - end - refresh(true, "reset") + M.refresh() end -local dispatch_reset = a.void(reset) +M.dispatch_reset = a.void(M.reset) -local function close(skip_close) +function M.close(skip_close) if not skip_close then M.status_buffer:close() end @@ -643,7 +605,7 @@ end local stage = function() M.current_operation = "stage" - local section, item = get_current_section_item() + local section, item = M.get_current_section_item() local mode = vim.api.nvim_get_mode() if @@ -666,7 +628,6 @@ local stage = function() return item.name end)) end - refresh(true, "stage") M.current_operation = nil return else @@ -680,12 +641,11 @@ local stage = function() end assert(item, "Stage item is nil") - refresh({ status = true, diffs = { "*:" .. item.name } }, "stage_finish") M.current_operation = nil end local unstage = function() - local section, item = get_current_section_item() + local section, item = M.get_current_section_item() local mode = vim.api.nvim_get_mode() if section == nil or section.name ~= "staged" or (mode.mode == "V" and item == nil) then @@ -698,7 +658,6 @@ local unstage = function() else if item == nil then git.status.unstage_all() - refresh(true, "unstage") M.current_operation = nil return else @@ -717,7 +676,6 @@ local unstage = function() end assert(item, "Unstage item is nil") - refresh({ status = true, diffs = { "*:" .. item.name } }, "unstage_finish") M.current_operation = nil end @@ -742,7 +700,7 @@ local function discard_selected_files(files, section) if section == "untracked" then a.util.scheduler() for _, file in ipairs(filenames) do - vim.fn.delete(cli.git_root() .. "/" .. file) + vim.fn.delete(string.format("%s/%s", git.repo.git_root, file)) end elseif section == "unstaged" then git.index.checkout(filenames) @@ -783,7 +741,7 @@ local function discard_hunk(section, item, lines, hunk) end local discard = function() - local section, item = get_current_section_item() + local section, item = M.get_current_section_item() if section == nil or item == nil then return end @@ -822,7 +780,6 @@ local discard = function() discard_selected_files({ item }, section.name) end - refresh(true, "discard") M.current_operation = nil a.util.scheduler() @@ -841,7 +798,7 @@ local set_folds = function(to) end end) end) - refresh(true, "set_folds") + M.refresh() end local function cherry_pick() @@ -873,21 +830,42 @@ local cmd_func_map = function() ["Depth4"] = a.void(function() set_folds { false, false, false } end), - ["Toggle"] = toggle, - ["Discard"] = { "nv", a.void(discard), true }, - ["Stage"] = { "nv", a.void(stage), true }, + ["Toggle"] = M.toggle, + ["Discard"] = { + "nv", + a.void(function() + discard() + M.update() + end), + true, + }, + ["Stage"] = { + "nv", + a.void(function() + stage() + M.update() + end), + true, + }, ["StageUnstaged"] = a.void(function() git.status.stage_modified() - refresh({ status = true, diffs = true }, "StageUnstaged") + M.update() end), ["StageAll"] = a.void(function() git.status.stage_all() - refresh { status = true, diffs = true } + M.update() end), - ["Unstage"] = { "nv", a.void(unstage), true }, + ["Unstage"] = { + "nv", + a.void(function() + unstage() + M.update() + end), + true, + }, ["UnstageStaged"] = a.void(function() git.status.unstage_all() - refresh({ status = true, diffs = true }, "UnstageStaged") + M.update() end), ["CommandHistory"] = function() GitCommandHistory:new():show() @@ -897,25 +875,25 @@ local cmd_func_map = function() process.show_console() end, ["TabOpen"] = function() - local _, item = get_current_section_item() + local _, item = M.get_current_section_item() if item then vim.cmd("tabedit " .. item.name) end end, ["VSplitOpen"] = function() - local _, item = get_current_section_item() + local _, item = M.get_current_section_item() if item then vim.cmd("vsplit " .. item.name) end end, ["SplitOpen"] = function() - local _, item = get_current_section_item() + local _, item = M.get_current_section_item() if item then vim.cmd("split " .. item.name) end end, ["GoToPreviousHunkHeader"] = function() - local section, item = get_current_section_item() + local section, item = M.get_current_section_item() if not section then return end @@ -946,7 +924,7 @@ local cmd_func_map = function() end end, ["GoToNextHunkHeader"] = function() - local section, item = get_current_section_item() + local section, item = M.get_current_section_item() if not section then return end @@ -974,7 +952,7 @@ local cmd_func_map = function() ["GoToFile"] = a.void(function() -- local repo_root = cli.git_root() a.util.scheduler() - local section, item = get_current_section_item() + local section, item = M.get_current_section_item() if item and section then if section.name == "unstaged" or section.name == "staged" or section.name == "untracked" then @@ -1020,7 +998,8 @@ local cmd_func_map = function() end end), ["RefreshBuffer"] = function() - dispatch_refresh(true) + notif.create("Refreshing Status") + git.repo:dispatch_refresh { callback = M.dispatch_refresh } end, ["HelpPopup"] = function() local line = M.status_buffer:get_current_line() @@ -1039,7 +1018,7 @@ local cmd_func_map = function() return end local dv = require("neogit.integrations.diffview") - local section, item = get_current_section_item() + local section, item = M.get_current_section_item() if section and item then dv.open(section.name, item.name) @@ -1199,20 +1178,14 @@ function M.create(kind, cwd) set_decoration_provider(buffer) logger.debug("[STATUS BUFFER]: Dispatching initial render") - refresh(true, "Buffer.create") + M.refresh() end, } end -M.toggle = toggle -M.reset = reset -M.dispatch_reset = dispatch_reset -M.refresh = refresh -M.dispatch_refresh = dispatch_refresh -M.refresh_viml_compat = refresh_viml_compat -M.refresh_manually = refresh_manually -M.get_current_section_item = get_current_section_item -M.close = close +function M.update() + git.repo:dispatch_refresh { source = "status", callback = M.dispatch_refresh } +end function M.enable() M.disabled = false @@ -1222,10 +1195,6 @@ function M.disable() M.disabled = true end -function M.get_status() - return M.status -end - function M.wait_on_current_operation(ms) vim.wait(ms or 1000, function() return not M.current_operation diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua new file mode 100644 index 000000000..a69b81dfd --- /dev/null +++ b/lua/neogit/watcher.lua @@ -0,0 +1,85 @@ +local M = {} + +local uv = vim.loop + +local config = require("neogit.config") +local logger = require("neogit.logger") +local util = require("neogit.lib.util") +local status = require("neogit.status") +local git = require("neogit.lib.git") + +local Path = require("plenary.path") +local a = require("plenary.async") + +M.watcher = {} + +local function git_dir() + return Path.new(require("neogit.lib.git").repo.git_root, ".git"):absolute() +end + +function M.setup() + local gitdir = git_dir() + local watcher = M.watch_git_dir(gitdir) + + M.watcher[gitdir] = watcher +end + +-- Adapted from https://github.com/lewis6991/gitsigns.nvim/blob/main/lua/gitsigns/manager.lua#L575 +--- @param gitdir string +--- @return uv_fs_event_t? +function M.watch_git_dir(gitdir) + if not config.values.auto_refresh then + return + end + + if M.watcher[gitdir] then + logger.debug(string.format("[WATCHER] for '%s' already setup! Bailing.", gitdir)) + return + end + + local watch_gitdir_handler_db = util.debounce_trailing( + 200, + a.void(function() + logger.debug("[WATCHER] Dispatching Refresh") + git.repo:dispatch_refresh { callback = status.dispatch_refresh, source = "watcher" } + end) + ) + + logger.debug("[WATCHER] Watching git dir: " .. gitdir) + + local w = assert(uv.new_fs_event()) + + w:start( + gitdir, + {}, + a.void(function(err, filename, events) + if err then + logger.error(string.format("[WATCHER] Git dir update error: %s", err)) + return + end + + local info = string.format( + "[WATCHER] Git dir update: '%s' %s", + filename, + vim.inspect(events, { indent = "", newline = " " }) + ) + + -- stylua: ignore + if + filename:match("%.lock$") or + filename:match("COMMIT_EDITMSG") or + filename:match("~$") + then + logger.debug(string.format("%s (ignoring)", info)) + return + end + + logger.debug(info) + watch_gitdir_handler_db() + end) + ) + + return w +end + +return M diff --git a/plugin/neogit.lua b/plugin/neogit.lua index 4a09965c1..d85df47d1 100644 --- a/plugin/neogit.lua +++ b/plugin/neogit.lua @@ -11,3 +11,19 @@ end, { return neogit.complete(arglead) end, }) + +api.nvim_create_user_command("NeogitRepoState", function() + vim.print(require("neogit.lib.git").repo.state) +end, { + nargs = "*", + desc = "Open Neogit Repo State", +}) + +api.nvim_create_user_command("NeogitMessages", function() + for _, message in ipairs(require("neogit.lib.notification").get_history()) do + print(string.format("[%s]: %s", message.kind, table.concat(message.content, " - "))) + end +end, { + nargs = "*", + desc = "Prints neogit message history", +}) diff --git a/tests/.repo/.git.orig/config b/tests/.repo/.git.orig/config index 515f48362..576d19be5 100644 --- a/tests/.repo/.git.orig/config +++ b/tests/.repo/.git.orig/config @@ -3,3 +3,14 @@ filemode = true bare = false logallrefupdates = true +[remote "origin"] + url = git@github.com:NeogitOrg/neogit.git + fetch = +refs/heads/*:refs/remotes/origin/* +[remote "fake"] + url = git@github.com:NeogitOrgFake/neogit.git + fetch = +refs/heads/*:refs/remotes/fake/* +[branch "master"] + remote = origin + pushRemote = origin + merge = refs/heads/master + rebase = true diff --git a/tests/.repo/.git.orig/index b/tests/.repo/.git.orig/index index cc4527378..7ce0add91 100644 Binary files a/tests/.repo/.git.orig/index and b/tests/.repo/.git.orig/index differ diff --git a/tests/branch_popup_spec.lua b/tests/branch_popup_spec.lua index d78321720..e1736cbb1 100644 --- a/tests/branch_popup_spec.lua +++ b/tests/branch_popup_spec.lua @@ -1,49 +1,135 @@ require("plenary.async").tests.add_to_env() + local eq = assert.are.same local operations = require("neogit.operations") + local harness = require("tests.git_harness") local in_prepared_repo = harness.in_prepared_repo local get_current_branch = harness.get_current_branch +local get_git_branches = harness.get_git_branches +local get_git_rev = harness.get_git_rev local FuzzyFinderBuffer = require("tests.mocks.fuzzy_finder") -local status = require("neogit.status") local input = require("tests.mocks.input") local function act(normal_cmd) print("Feeding keys: ", normal_cmd) vim.fn.feedkeys(vim.api.nvim_replace_termcodes(normal_cmd, true, true, true)) vim.fn.feedkeys("", "x") -- flush typeahead - status.wait_on_current_operation() end describe("branch popup", function() - it( - "can switch to another branch in the repository", - in_prepared_repo(function() - FuzzyFinderBuffer.value = "second-branch" - act("bb") - operations.wait("checkout_branch_revision") - eq("second-branch", get_current_branch()) - end) - ) - - it( - "can switch to another local branch in the repository", - in_prepared_repo(function() - FuzzyFinderBuffer.value = "second-branch" - act("bl") - operations.wait("checkout_branch_local") - eq("second-branch", get_current_branch()) - end) - ) - - it( - "can create a new branch", - in_prepared_repo(function() - input.value = "branch-from-test" - act("bc") - operations.wait("checkout_create_branch") - eq("branch-from-test", get_current_branch()) - end) - ) + describe("actions", function() + it( + "can switch to another branch in the repository", + in_prepared_repo(function() + FuzzyFinderBuffer.value = "second-branch" + act("bb") + operations.wait("checkout_branch_revision") + eq("second-branch", get_current_branch()) + end) + ) + + it( + "can switch to another local branch in the repository", + in_prepared_repo(function() + FuzzyFinderBuffer.value = "second-branch" + act("bl") + operations.wait("checkout_branch_local") + eq("second-branch", get_current_branch()) + end) + ) + + it( + "can create a new branch", + in_prepared_repo(function() + input.value = "branch-from-test" + act("bc") + operations.wait("checkout_create_branch") + eq("branch-from-test", get_current_branch()) + end) + ) + + it( + "can create a new branch without checking it out", + in_prepared_repo(function() + input.value = "branch-from-test-create" + act("bn") + operations.wait("create_branch") + eq("master", get_current_branch()) + eq(true, vim.tbl_contains(get_git_branches(), "branch-from-test-create")) + end) + ) + + it( + "can rename a branch", + in_prepared_repo(function() + FuzzyFinderBuffer.value = "second-branch" + input.value = "second-branch-renamed" + + act("bm") + operations.wait("rename_branch") + eq(true, vim.tbl_contains(get_git_branches(), "second-branch-renamed")) + eq(false, vim.tbl_contains(get_git_branches(), "second-branch")) + end) + ) + + it( + "can reset a branch", + in_prepared_repo(function() + FuzzyFinderBuffer.value = "third-branch" + + eq("e2c2a1c0e5858a690c1dc13edc1fd5de103409d9", get_git_rev("HEAD")) + act("bXy") + operations.wait("reset_branch") + eq("1e9b765da30ad45ef0b863470c73104bb7161e23", get_git_rev("HEAD")) + end) + ) + + it( + "can delete a branch", + in_prepared_repo(function() + FuzzyFinderBuffer.value = "third-branch" + + act("bD") + operations.wait("delete_branch") + eq(false, vim.tbl_contains(get_git_branches(), "third-branch")) + end) + ) + end) + + describe("variables", function() + -- it("can change branch.*.description", in_prepared_repo(function() + -- input.value = "branch description" + -- act("bd") + -- eq("branch description", harness.get_git_config("branch.master.description")) + -- end)) + + it( + "can change branch.*.merge", + in_prepared_repo(function() + FuzzyFinderBuffer.value = "second-branch" + + eq("refs/heads/master", harness.get_git_config("branch.master.merge")) + act("bu") + eq("refs/heads/second-branch", harness.get_git_config("branch.master.merge")) + end) + ) + + -- it( + -- "can change branch.*.rebase", + -- in_prepared_repo(function() + -- eq("true", harness.get_git_config("branch.master.rebase")) + -- act("br") + -- eq("false", harness.get_git_config("branch.master.rebase")) + -- end) + -- ) + + -- it( + -- "can change branch.*.pushRemote", + -- in_prepared_repo(function() + -- act("bp") + -- end) + -- ) + end) end) diff --git a/tests/git_harness.lua b/tests/git_harness.lua index 3f5f7ab4f..700cb20e8 100644 --- a/tests/git_harness.lua +++ b/tests/git_harness.lua @@ -72,6 +72,18 @@ function M.get_git_diff(files, flags) return table.concat(output, "\n") end +function M.get_git_rev(rev) + local result = vim.api.nvim_exec("!git rev-parse " .. rev, true) + local lines = vim.split(result, "\n") + return lines[3] +end + +function M.get_git_config(var) + local result = vim.api.nvim_exec("!git config --get --local " .. var, true) + local lines = vim.split(result, "\n") + return lines[3] +end + function M.get_git_branches() local result = vim.api.nvim_exec("!git branch --list --all", true) local lines = vim.split(result, "\n") diff --git a/tests/init.lua b/tests/init.lua index 62fe24df5..3580a164d 100644 --- a/tests/init.lua +++ b/tests/init.lua @@ -22,5 +22,5 @@ end require("plenary.test_harness").test_directory("tests", { minimal_init = "tests/minimal_init.lua", - sequential = true, + -- sequential = true, }) diff --git a/todo.md b/todo.md index 8d6434523..939846f56 100644 --- a/todo.md +++ b/todo.md @@ -34,4 +34,9 @@ ## Highlighting -## Jobs +## Process +Rewrite process to use `vim.system` instead of `vim.fn.jobstart` +https://github.com/neovim/neovim/commit/c0952e62fd0ee16a3275bb69e0de04c836b39015 +https://github.com/neovim/neovim/blob/1de82e16c1216e1dbe22cf7a8ec9ea9e9e69b631/runtime/lua/vim/_editor.lua#L84 +https://github.com/nvim-treesitter/nvim-treesitter/pull/4921/files +https://github.com/neovim/neovim/pull/23827