Skip to content
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

fix(attach): check cache status between async calls #869

Merged
merged 1 commit into from
Sep 7, 2023
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
2 changes: 1 addition & 1 deletion lua/gitsigns/attach.lua
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ local attach_throttled = throttle_by_id(function(cbuf, ctx, aucmd)
})

-- Initial update
manager.update(cbuf, cache[cbuf])
manager.update(cbuf)
end)

--- Detach Gitsigns from all buffers it is attached to.
Expand Down
4 changes: 0 additions & 4 deletions lua/gitsigns/cache.lua
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ function CacheEntry:get_compare_rev(base)
return string.format(':%d', stage)
end

function CacheEntry:get_staged_compare_rev()
return self.commit and string.format('%s^', self.commit) or 'HEAD'
end

function CacheEntry:get_rev_bufname(rev)
rev = rev or self:get_compare_rev()
return string.format('gitsigns://%s/%s:%s', self.git_obj.repo.gitdir, rev, self.git_obj.relpath)
Expand Down
40 changes: 16 additions & 24 deletions lua/gitsigns/debounce.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,31 @@ local M = {}
--- @generic F: function
--- @param ms number Timeout in ms
--- @param fn F Function to debounce
--- @param hash? integer|fun(...): any Function that determines id from arguments to fn
--- @return F Debounced function.
function M.debounce_trailing(ms, fn)
local timer = assert(uv.new_timer())
function M.debounce_trailing(ms, fn, hash)
local running = {} --- @type table<any,uv.uv_timer_t>
if type(hash) == 'number' then
local hash_i = hash
hash = function(...)
return select(hash_i, ...)
end
end
return function(...)
local id = hash and hash(...) or true
if running[id] == nil then
running[id] = assert(uv.new_timer())
end
local timer = running[id]
local argv = { ... }
timer:start(ms, 0, function()
timer:stop()
fn(unpack(argv))
running[id] = nil
fn(unpack(argv, 1, table.maxn(argv)))
end)
end
end

--- Throttles a function on the leading edge.
---
--- @generic F: function
--- @param ms number Timeout in ms
--- @param fn F Function to throttle
--- @return F throttled function.
function M.throttle_leading(ms, fn)
local timer = assert(uv.new_timer())
local running = false
return function(...)
if not running then
timer:start(ms, 0, function()
running = false
timer:stop()
end)
running = true
fn(...)
end
end
end

--- Throttles a function using the first argument as an ID
---
--- If function is already running then the function will be scheduled to run
Expand Down
56 changes: 35 additions & 21 deletions lua/gitsigns/manager.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ local throttle_by_id = require('gitsigns.debounce').throttle_by_id
local log = require('gitsigns.debug.log')
local dprint = log.dprint
local dprintf = log.dprintf
local eprint = log.eprint

local subprocess = require('gitsigns.subprocess')
local util = require('gitsigns.util')
Expand Down Expand Up @@ -63,12 +62,13 @@ end
--- @param top integer
--- @param bot integer
--- @param clear boolean
--- @param untracked boolean
local function apply_win_signs(bufnr, top, bot, clear, untracked)
local function apply_win_signs(bufnr, top, bot, clear)
local bcache = cache[bufnr]
if not bcache then
return
end

local untracked = bcache.git_obj.object_name == nil
apply_win_signs0(bufnr, signs_normal, bcache.hunks, top, bot, clear, untracked)
if signs_staged then
apply_win_signs0(bufnr, signs_staged, bcache.hunks_staged, top, bot, clear, false)
Expand Down Expand Up @@ -106,7 +106,7 @@ function M.on_lines(buf, first, last_orig, last_new)
end
end

M.update_debounced(buf, cache[buf])
M.update_debounced(buf)
end

local ns = api.nvim_create_namespace('gitsigns')
Expand Down Expand Up @@ -394,45 +394,61 @@ local function update_show_deleted(bufnr)
end
end

--- @param bufnr? integer
--- @param cb function
M.buf_check = async.wrap(function(bufnr, cb)
vim.schedule(function()
if bufnr then
if not api.nvim_buf_is_valid(bufnr) then
dprint('Buffer not valid, aborting')
return
end
if not cache[bufnr] then
dprint('Has detached, aborting')
return
end
end
cb()
end)
end, 2)

local update_cnt = 0

--- Ensure updates cannot be interleaved.
--- Since updates are asynchronous we need to make sure an update isn't performed
--- whilst another one is in progress. If this happens then schedule another
--- update after the current one has completed.
--- @param bufnr integer
--- @param bcache? Gitsigns.CacheEntry
M.update = throttle_by_id(function(bufnr, bcache)
M.update = throttle_by_id(function(bufnr)
local __FUNC__ = 'update'
bcache = bcache or cache[bufnr]
if not bcache then
eprint('Cache for buffer ' .. bufnr .. ' was nil')
return
end
M.buf_check(bufnr)
local bcache = cache[bufnr]
local old_hunks, old_hunks_staged = bcache.hunks, bcache.hunks_staged
bcache.hunks, bcache.hunks_staged = nil, nil

async.scheduler_if_buf_valid(bufnr)
local buftext = util.buf_lines(bufnr)
local git_obj = bcache.git_obj

if not bcache.compare_text or config._refresh_staged_on_update then
bcache.compare_text = git_obj:get_show_text(bcache:get_compare_rev())
async.scheduler_if_buf_valid(bufnr)
M.buf_check(bufnr)
end

local buftext = util.buf_lines(bufnr)

bcache.hunks = run_diff(bcache.compare_text, buftext)
M.buf_check(bufnr)

if config._signs_staged_enable then
if not bcache.compare_text_head or config._refresh_staged_on_update then
bcache.compare_text_head = git_obj:get_show_text(bcache:get_staged_compare_rev())
local staged_compare_rev = bcache.commit and string.format('%s^', bcache.commit) or 'HEAD'
bcache.compare_text_head = git_obj:get_show_text(staged_compare_rev)
M.buf_check(bufnr)
end
local hunks_head = run_diff(bcache.compare_text_head, buftext)
M.buf_check(bufnr)
bcache.hunks_staged = gs_hunks.filter_common(hunks_head, bcache.hunks)
end

async.scheduler_if_buf_valid(bufnr)

-- Note the decoration provider may have invalidated bcache.hunks at this
-- point
if
Expand All @@ -442,7 +458,7 @@ M.update = throttle_by_id(function(bufnr, bcache)
then
-- Apply signs to the window. Other signs will be added by the decoration
-- provider as they are drawn.
apply_win_signs(bufnr, vim.fn.line('w0'), vim.fn.line('w$'), true, git_obj.object_name == nil)
apply_win_signs(bufnr, vim.fn.line('w0'), vim.fn.line('w$'), true)

update_show_deleted(bufnr)
bcache.force_next_update = false
Expand Down Expand Up @@ -497,9 +513,7 @@ local function on_win(_cb, _winid, bufnr, topline, botline_guess)
end
local botline = math.min(botline_guess, api.nvim_buf_line_count(bufnr))

local untracked = bcache.git_obj.object_name == nil

apply_win_signs(bufnr, topline + 1, botline + 1, false, untracked)
apply_win_signs(bufnr, topline + 1, botline + 1, false)

if not (config.word_diff and config.diff_opts.internal) then
return false
Expand Down
42 changes: 15 additions & 27 deletions lua/gitsigns/watcher.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,13 @@ local util = require('gitsigns.util')
local cache = require('gitsigns.cache').cache
local config = require('gitsigns.config').config
local debounce_trailing = require('gitsigns.debounce').debounce_trailing
local manager = require('gitsigns.manager')

local buf_check = manager.buf_check

local dprint = log.dprint
local dprintf = log.dprintf

--- @param bufnr? integer
--- @param cb function
local buf_check = async.wrap(function(bufnr, cb)
vim.schedule(function()
if bufnr then
if not api.nvim_buf_is_valid(bufnr) then
dprint('Buffer not valid, aborting')
return
end
if not cache[bufnr] then
dprint('Has detached, aborting')
return
end
end
cb()
end)
end, 2)

--- @param bufnr integer
--- @param old_relpath string
local function handle_moved(bufnr, old_relpath)
Expand Down Expand Up @@ -74,7 +59,9 @@ local function handle_moved(bufnr, old_relpath)
dprintf('%s buffer %d from %s to %s', msg, bufnr, old_name, bcache.file)
end

local watch_gitdir_handler = async.void(function(bufnr)
--- @param bufnr integer
local handler = debounce_trailing(200, async.void(function(bufnr)
local __FUNC__ = 'watcher_handler'
buf_check(bufnr)

local git_obj = cache[bufnr].git_obj
Expand All @@ -100,8 +87,8 @@ local watch_gitdir_handler = async.void(function(bufnr)

cache[bufnr]:invalidate()

require('gitsigns.manager').update(bufnr, cache[bufnr])
end)
require('gitsigns.manager').update(bufnr)
end), 1)

-- vim.inspect but on one line
--- @param x any
Expand All @@ -112,14 +99,15 @@ end

local M = {}

local WATCH_IGNORE = {
ORIG_HEAD = true,
FETCH_HEAD = true
}

--- @param bufnr integer
--- @param gitdir string
--- @return uv.uv_fs_event_t
function M.watch_gitdir(bufnr, gitdir)
-- Setup debounce as we create the luv object so the debounce is independent
-- to each watcher
local watch_gitdir_handler_db = debounce_trailing(200, watch_gitdir_handler)

dprintf('Watching git dir')
local w = assert(uv.new_fs_event())
w:start(gitdir, {}, function(err, filename, events)
Expand All @@ -134,14 +122,14 @@ function M.watch_gitdir(bufnr, gitdir)
-- The luv docs say filename is passed as a string but it has been observed
-- to sometimes be nil.
-- https://github.com/lewis6991/gitsigns.nvim/issues/848
if filename == nil or vim.endswith(filename, '.lock') then
if filename == nil or WATCH_IGNORE[filename] or vim.endswith(filename, '.lock') then
dprintf('%s (ignoring)', info)
return
end

dprint(info)

watch_gitdir_handler_db(bufnr)
handler(bufnr)
end)
return w
end
Expand Down
Loading