lewis6991 / gitsigns.nvim

Git integration for buffers
MIT License
5.19k stars 191 forks source link

Gitsigns gives up on a buffer #738

Closed j-xella closed 11 months ago

j-xella commented 1 year ago

Description

Something happens to a buffer, and gitsigns is unable to work with it any more. Other buffers are ok, but not this particular one. There could be several such buffers at a time.

Closing the window or even deleting the buffer with :bdel doesn't help - after the buffer is reopened, the problems continue.

If the fix is not awailable, I would like to be able to resolve this issue without restarting neovim at least - I have so many buffers and terminal windows open. Is it possible to restart/reset gitsigns without reopening neovim?

Neovim version

NVIM v0.9.0-dev-650+g870ca1de5 Build type: Release LuaJIT 2.1.0-beta3

Operating system and version

Red Hat Enterprise Linux Server 7.9 (Maipo)

Expected behavior

Gitsigns works as usual with a buffer. Displays hints in a gutter, jumps between hunks, etc...

Actual behavior

Nothing is displayed in a gutter.

When I try jumping between diffs, I get this:

Error executing vim.schedule lua callback: ...e/pack/packer/start/gitsigns.nvim/lua/gitsigns/async.lua:64: The coroutine failed with this message: vim/shared.lua:0: Expected table, got nil
stack traceback:
        [C]: in function 'assert'
        vim/shared.lua: in function 'tbl_isempty'
        ...ack/packer/start/gitsigns.nvim/lua/gitsigns/diff_int.lua:20: in function 'run_diff0'
        ...ack/packer/start/gitsigns.nvim/lua/gitsigns/diff_int.lua:73: in function 'run_diff'
        ...pack/packer/start/gitsigns.nvim/lua/gitsigns/actions.lua:242: in function 'get_hunks'
        ...pack/packer/start/gitsigns.nvim/lua/gitsigns/actions.lua:514: in function <...pack/packer/start/gitsigns.nvim/lua/gitsigns/actions.lua:506>
stack traceback:
        [C]: in function 'error'
        ...e/pack/packer/start/gitsigns.nvim/lua/gitsigns/async.lua:64: in function 'step'
        ...e/pack/packer/start/gitsigns.nvim/lua/gitsigns/async.lua:79: in function 'nav_hunk'
        ...pack/packer/start/gitsigns.nvim/lua/gitsigns/actions.lua:584: in function 'next_hunk'
        /users/xxxxxxx/.config/nvim/lua/aj_personal/gitsigns.lua:25: in function </users/xxxxxxx/.config/nvim/lua/aj_personal/gitsigns.lua:25>

Minimal config

This is my config:

require('gitsigns').setup {$                                                                                             
  numhl                        = true,  -- Toggle with `:Gitsigns toggle_numhl`$                                         
  signcolumn                   = false, -- Toggle with `:Gitsigns toggle_signs`$                                         
  attach_to_untracked          = false,$                                                                                 
  current_line_blame           = true,  -- Toggle with `:Gitsigns toggle_current_line_blame`$                            
  current_line_blame_formatter = '<author>, <abbrev_sha> <author_time:%y-%m-%d> - <summary>',$                           
$                                                                                                                        
  on_attach = function(bufnr)$                                                                                           
    local gs = package.loaded.gitsigns$                                                                                  
$                                                                                                                        
    local function map(mode, l, r, opts)$                                                                                
      opts = opts or {}$                                                                                                 
      opts.buffer = bufnr$                                                                                               
      vim.keymap.set(mode, l, r, opts)$                                                                                  
    end$                                                                                                                 
$                                                                                                                        
    -- Navigation$                                                                                                       
    map('n', ']c', function()$                                                                                           
      if vim.wo.diff then return ']c' end$                                                                               
      vim.schedule(function() gs.next_hunk() end)$                                                                       
      return '<Ignore>'$                                                                                                 
    end, {expr=true, desc="gitsigns: jump to the next diff or hunk"})$                                                   
$                                                                                                                        
    map('n', '[c', function()$                                                                                           
      if vim.wo.diff then return '[c' end$                                                                               
      vim.schedule(function() gs.prev_hunk() end)$                                                                       
      return '<Ignore>'$                                                                                                 
    end, {expr=true, desc="gitsigns: jump to the previous diff or hunk"})$                                               
$                                                                                                                        
    -- Actions$                                                                                                          
    map({'n', 'v'}, '<leader>hs', ':Gitsigns stage_hunk<CR>') -- it gets line info that way$                             
    map({'n', 'v'}, '<leader>hr', ':Gitsigns reset_hunk<CR>') -- it gets line info that way$                             
    map('n', '<leader>hS', gs.stage_buffer,    { desc = "gitsigns: stage the whole buffer" } )$                          
    map('n', '<leader>hu', gs.undo_stage_hunk, { desc = "gitsigns: undo the hunk staging (if possible)" })$              
    map('n', '<leader>hR', gs.reset_buffer,    { desc = "gitsigns: reset the whole buffer" } )$                          
    map('n', '<leader>hp', gs.preview_hunk,    { desc = "gitsigns: preview hunk (read-only)" } )$                        
    map('n', '<leader>td', gs.toggle_deleted,  { desc = "gitsigns: toggle inline display of old hunk versions" } )$      
$                                                                                                                        
    -- Text object$                                                                                                      
    map({'o', 'x'}, 'ih', ':<C-U>Gitsigns select_hunk<CR>')$                                                             
  end$                                                                                                                   require('gitsigns').setup {$                                                                                             
  numhl                        = true,  -- Toggle with `:Gitsigns toggle_numhl`$                                         
  signcolumn                   = false, -- Toggle with `:Gitsigns toggle_signs`$                                         
  attach_to_untracked          = false,$                                                                                 
  current_line_blame           = true,  -- Toggle with `:Gitsigns toggle_current_line_blame`$                            
  current_line_blame_formatter = '<author>, <abbrev_sha> <author_time:%y-%m-%d> - <summary>',$                           
$                                                                                                                        
  on_attach = function(bufnr)$                                                                                           
    local gs = package.loaded.gitsigns$                                                                                  
$                                                                                                                        
    local function map(mode, l, r, opts)$                                                                                
      opts = opts or {}$                                                                                                 
      opts.buffer = bufnr$                                                                                               
      vim.keymap.set(mode, l, r, opts)$                                                                                  
    end$                                                                                                                 
$                                                                                                                        
    -- Navigation$                                                                                                       
    map('n', ']c', function()$                                                                                           
      if vim.wo.diff then return ']c' end$                                                                               
      vim.schedule(function() gs.next_hunk() end)$                                                                       
      return '<Ignore>'$                                                                                                 
    end, {expr=true, desc="gitsigns: jump to the next diff or hunk"})$                                                   
$                                                                                                                        
    map('n', '[c', function()$                                                                                           
      if vim.wo.diff then return '[c' end$                                                                               
      vim.schedule(function() gs.prev_hunk() end)$                                                                       
      return '<Ignore>'$                                                                                                 
    end, {expr=true, desc="gitsigns: jump to the previous diff or hunk"})$                                               
$                                                                                                                        
    -- Actions$                                                                                                          
    map({'n', 'v'}, '<leader>hs', ':Gitsigns stage_hunk<CR>') -- it gets line info that way$                             
    map({'n', 'v'}, '<leader>hr', ':Gitsigns reset_hunk<CR>') -- it gets line info that way$                             
    map('n', '<leader>hS', gs.stage_buffer,    { desc = "gitsigns: stage the whole buffer" } )$                          
    map('n', '<leader>hu', gs.undo_stage_hunk, { desc = "gitsigns: undo the hunk staging (if possible)" })$              
    map('n', '<leader>hR', gs.reset_buffer,    { desc = "gitsigns: reset the whole buffer" } )$                          
    map('n', '<leader>hp', gs.preview_hunk,    { desc = "gitsigns: preview hunk (read-only)" } )$                        
    map('n', '<leader>td', gs.toggle_deleted,  { desc = "gitsigns: toggle inline display of old hunk versions" } )$      
$                                                                                                                        
    -- Text object$                                                                                                      
    map({'o', 'x'}, 'ih', ':<C-U>Gitsigns select_hunk<CR>')$                                                             
  end$

Steps to reproduce

Not sure. This happens rarely and inconsistently.

My guess would be that background git operations could be responsible somehow. I think the problems started when I did some command-line rebasing of the code opened in neovim buffers.

Gitsigns debug messages

No response

lewis6991 commented 1 year ago

Not enough information. You haven't even provided the debug messages.

j-xella commented 1 year ago

Not enough information. You haven't even provided the debug messages.

Yes, I did not provide debug messages. This issue does not happen consistently, and I normally do not run it with debug enabled...

I understand that this issue is very difficult to reproduce. But my quesion also was if there is a workaround possible to restore the gitsigns behaviour without restarting neovim? Clearly some internal gitsigns data becomes wrong. I wonder if providing the error message should at least give a clue where it happens, and maybe something can be done to fix inside neovim session it so that the work could continue?

lewis6991 commented 1 year ago

I would expect closing and opening the buffer again to fix the issue. Perhaps Gitsigns isn't cleaning up the state properly.

fcopa commented 1 year ago

Any fix?

mystilleef commented 1 year ago

To recreate this bug detach on InsertEnter and attach on InsertLeave over the course of an editing session.

This often happens because I dynamically attach and detach Gitsigns when entering and leaving Insert mode.

In Insert mode I like to turn off colors, highlights, and other visual distractions. I enable them back on in Normal mode.

Unfortunately, restarting Nvim is the only way I've found to resolve this issue.

j-xella commented 1 year ago

I think the problem could be in throttle_by_id function. It creates a function wrapper that ensures that the function does not get invoked multiple times in parallel for any particular buffer. One of the ways to do it is via local running[id] map, where id is the buffer number. While the function is being executed, running[id] is true, and that ensures that the function will not be called again for that buffer. Once the function is finished, running[id] is set to nil/false, and the function can be called again.

The problem is that if an exception is raised while the function was running, running[id] is never reset, and the function for that buffer can't be called any more. I believe the wrapper should use pcall or xpcall to call the function, and ensure that running[id] is reset no matter what.

A workaround is to use bwipeout on the buffer. bdelete does not delete the buffer completely. When the buffer is loaded again, it retains the buffer id and hence gitsigns still doesn't work on it. bwipeout, on the other hand, does much better purge, and when the buffer is loaded again, it gets new buffer id assigned. Hence it is possible to make gitsigns work on it without restarting nvim. The drawback is that bwipeout deletes all buffer bookmarks and other buffer-specific settings.

lewis6991 commented 1 year ago

Good find.

The problem is that the function being wrapped is async so pcall won't necessarily work.

j-xella commented 1 year ago

I hope it is still possible to ensure that running[id] is reset at the end...

But if not, maybe it is possible to move the running map to the unit level and expose another function that could reset running[id] - to use in cases of emergency? Still better than bwipeout.

Another solution could be to use timeouts - store the timestamp of the last invocation, and, if the function is "running" for longer than certain amount of time, assume that it is not anymore and proceed.

lewis6991 commented 1 year ago

Knowing the issue is coming from running[id] is valuable so I'm sure something can be figured out.

It may just be the case to make sure the async framework can properly handle pcall which is on my to-do list anyway.

j-xella commented 1 year ago

I am not sure if this is the same issue or a slightly different one, but I am noticing much more cases where gitsigns stops working on a buffer. Like before, it does not happen every time, but multiple times a day. bwipeout helps, so it could be this issue. However, there are no visible error messages, only at some point I notice that gitsigns just does not work on a buffer any more.

GiorgosXou commented 1 year ago

Assuming that you have this keybinding:

    map.set('v', '<leader>gs', function() gs.stage_hunk {vim.fn.line('.'), vim.fn.line('v')} end)

Here's how to replicate the issue:

  1. It is important to start a new neovim instance
  2. Search for something you want to stage repeatedly eg. "/#undef"
  3. Record a macro like qq<shift-v>gsn
  4. Run the macro 10-30 times like 30@q

gitsigns_issue_Peek 2023-11-21 22-08 image

I think It has to do with the speed or something too, it can't handle too many calls too fast, you can also test the issue by hammering the @@ afterwords and you will have the same effect

lewis6991 commented 1 year ago

That stacktrace is from a version of gitsigns from at least 5 months a go. Please re-run on the latest.

lewis6991 commented 11 months ago

This should be fixed by #925