Skip to content

Commit

Permalink
fix(attach): check cache status between async calls
Browse files Browse the repository at this point in the history
  • Loading branch information
lewis6991 committed Sep 7, 2023
1 parent bf6b0bb commit 3885901
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 77 deletions.
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

0 comments on commit 3885901

Please sign in to comment.