Skip to content

Commit

Permalink
feat: move watchers to repo objects
Browse files Browse the repository at this point in the history
  • Loading branch information
lewis6991 committed Jul 13, 2024
1 parent e9c4187 commit 0a1ef65
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 167 deletions.
105 changes: 103 additions & 2 deletions lua/gitsigns/attach.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ local config = require('gitsigns.config').config
local dprint = log.dprint
local dprintf = log.dprintf
local throttle_by_id = require('gitsigns.debounce').throttle_by_id
local debounce_trailing = require('gitsigns.debounce').debounce_trailing

local api = vim.api
local uv = vim.loop
Expand Down Expand Up @@ -216,6 +217,105 @@ local function get_buf_context(bufnr)
}
end

--- @param bufnr integer
--- @param old_relpath string
local function handle_moved(bufnr, old_relpath)
local bcache = assert(cache[bufnr])
local git_obj = bcache.git_obj

local new_name = git_obj:has_moved()
if new_name then
dprintf('File moved to %s', new_name)
git_obj.relpath = new_name
if not git_obj.orig_relpath then
git_obj.orig_relpath = old_relpath
end
elseif git_obj.orig_relpath then
local orig_file = git_obj.repo.toplevel .. util.path_sep .. git_obj.orig_relpath
if not git_obj:file_info(orig_file).relpath then
return
end
--- File was moved in the index, but then reset
dprintf('Moved file reset')
git_obj.relpath = git_obj.orig_relpath
git_obj.orig_relpath = nil
else
-- File removed from index, do nothing
return
end

git_obj.file = git_obj.repo.toplevel .. util.path_sep .. git_obj.relpath
bcache.file = git_obj.file
git_obj:update()
if not manager.schedule(bufnr) then
return
end

local bufexists = util.bufexists(bcache.file)
local old_name = api.nvim_buf_get_name(bufnr)

if not bufexists then
-- Do not trigger BufFilePre/Post
-- TODO(lewis6991): figure out how to avoid reattaching without
-- disabling all autocommands.
util.noautocmd({ 'BufFilePre', 'BufFilePost' }, function()
util.buf_rename(bufnr, bcache.file)
end)
end

local msg = bufexists and 'Cannot rename' or 'Renamed'
dprintf('%s buffer %d from %s to %s', msg, bufnr, old_name, bcache.file)
end

--- @async
--- @param bufnr integer
local function watcher_handler0(bufnr)
local __FUNC__ = 'watcher_handler'

-- Avoid cache hit for detached buffer
-- ref: https://github.com/lewis6991/gitsigns.nvim/issues/956
if not manager.schedule(bufnr) then
dprint('buffer invalid (1)')
return
end

local git_obj = cache[bufnr].git_obj

Status:update(bufnr, { head = git_obj.repo.abbrev_head })

local was_tracked = git_obj.object_name ~= nil
local old_relpath = git_obj.relpath

git_obj:update()
if not manager.schedule(bufnr) then
dprint('buffer invalid (3)')
return
end

if config.watch_gitdir.follow_files and was_tracked and not git_obj.object_name then
-- File was tracked but is no longer tracked. Must of been removed or
-- moved. Check if it was moved and switch to it.
handle_moved(bufnr, old_relpath)
if not manager.schedule(bufnr) then
dprint('buffer invalid (4)')
return
end
end

cache[bufnr]:invalidate(true)

require('gitsigns.manager').update(bufnr)
end

--- Debounce to:
--- - wait for all changes to the gitdir to complete.
--- Throttle to:
--- - ensure handler is only triggered once per git operation.
--- - prevent updates to the same buffer from interleaving as the handler is
--- async.
local watcher_handler =
debounce_trailing(200, async.create(1, throttle_by_id(watcher_handler0, true)), 1)

--- Ensure attaches cannot be interleaved for the same buffer.
--- Since attaches are asynchronous we need to make sure an attach isn't
--- performed whilst another one is in progress.
Expand Down Expand Up @@ -325,8 +425,9 @@ local attach_throttled = throttle_by_id(function(cbuf, ctx, aucmd)
})

if config.watch_gitdir.enable then
local watcher = require('gitsigns.watcher')
cache[cbuf].gitdir_watcher = watcher.watch_gitdir(cbuf, git_obj.repo.gitdir)
cache[cbuf].deregister_watcher = git_obj.repo:register_callback(function()
watcher_handler(cbuf)
end)
end

if not api.nvim_buf_is_loaded(cbuf) then
Expand Down
7 changes: 3 additions & 4 deletions lua/gitsigns/cache.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ local M = {
--- @field hunks_staged? Gitsigns.Hunk.Hunk[]
---
--- @field staged_diffs? Gitsigns.Hunk.Hunk[]
--- @field gitdir_watcher? uv.uv_fs_event_t
--- @field deregister_watcher? fun()
--- @field git_obj Gitsigns.GitObj
--- @field blame? table<integer,Gitsigns.BlameInfo?>
local CacheEntry = M.CacheEntry
Expand Down Expand Up @@ -127,9 +127,8 @@ function CacheEntry:get_blame(lnum, opts)
end

function CacheEntry:destroy()
local w = self.gitdir_watcher
if w and not w:is_closing() then
w:close()
if self.deregister_watcher then
self.deregister_watcher()
end
self.git_obj.repo:unref()
end
Expand Down
49 changes: 49 additions & 0 deletions lua/gitsigns/git/repo.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ local uv = vim.uv or vim.loop
--- Username configured for the repo.
--- Needed for to determine "You" in current line blame.
--- @field username string
--- @field watcher_callbacks table<fun(), true>
local M = {}

--- Run git command the with the objects gitdir and toplevel
Expand Down Expand Up @@ -96,6 +97,48 @@ function M:update_abbrev_head()
self.abbrev_head = info.abbrev_head
end

--- vim.inspect but on one line
--- @param x any
--- @return string
local function inspect(x)
return vim.inspect(x, { indent = '', newline = ' ' })
end

function M:_watcher_cb(err, filename, events)
if err then
log.dprintf('Git dir update error: %s', err)
return
end

-- 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 not filename then
log.eprint('No filename')
return
end

log.dprintf("Git dir update: '%s' %s", filename, inspect(events))

async.run(function()
self:update_abbrev_head()

for cb in pairs(self.watcher_callbacks) do
cb()
end
end)
end

--- @param cb fun()
--- @return fun() deregister
function M:register_callback(cb)
self.watcher_callbacks[cb] = true

return function()
self.watcher_callbacks[cb] = nil
end
end

--- @async
--- @private
--- @param info Gitsigns.RepoInfo
Expand All @@ -110,6 +153,12 @@ local function new(info)
end

self.username = self:command({ 'config', 'user.name' }, { ignore_error = true })[1]
self.watcher_callbacks = {}

local w = assert(uv.new_fs_event())
w:start(self.gitdir, {}, function(err, filename, events)
self:_watcher_cb(err, filename, events)
end)

return self
end
Expand Down
161 changes: 0 additions & 161 deletions lua/gitsigns/watcher.lua

This file was deleted.

0 comments on commit 0a1ef65

Please sign in to comment.