lewis6991 / gitsigns.nvim

Git integration for buffers
MIT License
5.01k stars 189 forks source link

`stage_buffer` can leave unstaged part #1025

Open przepompownia opened 4 months ago

przepompownia commented 4 months ago

Description

I found unstaged new line deletion after staging new empty buffer with stage_buffer subcommand. At the moment I cannot explain myself how nvim or gitsigns can cause it.

No .editorconfig in working directory.

Neovim version

v0.11.0-dev-124+g90a4b1a59

Operating system and version

Debian Sid

Expected behavior

It seems that git diff output on such file should be empty.

Actual behavior

 $ GIT_PAGER= git diff -- new2
diff --git a/new2 b/new2
index 8b13789..e69de29 100644
--- a/new2
+++ b/new2
@@ -1 +0,0 @@
-

Minimal config

local thisInitFile = debug.getinfo(1).source:match('@?(.*)')
local configDir = vim.fs.dirname(thisInitFile)

vim.env['XDG_CONFIG_HOME'] = configDir
vim.env['XDG_DATA_HOME'] = vim.fs.joinpath(configDir, '.xdg', 'data')
vim.env['XDG_STATE_HOME'] = vim.fs.joinpath(configDir, '.xdg', 'state')
vim.env['XDG_CACHE_HOME'] = vim.fs.joinpath(configDir, '.xdg', 'cache')
local stdPathConfig = vim.fn.stdpath('config')

for _, env in ipairs({'XDG_CACHE_HOME', 'XDG_STATE_HOME', 'XDG_DATA_HOME'}) do
  vim.fn.mkdir(vim.env[env], 'p')
end

vim.opt.runtimepath:prepend(stdPathConfig)
vim.opt.packpath:prepend(stdPathConfig)

local function gitClone(url, installPath, branch)
  if vim.fn.isdirectory(installPath) ~= 0 then
    return
  end

  local command = {'git', 'clone', '--', url, installPath}
  if branch then
    table.insert(command, 3, '--branch')
    table.insert(command, 4, branch)
  end

  vim.notify(('Cloning %s dependency into %s...'):format(url, installPath), vim.log.levels.INFO, {})
  local sysObj = vim.system(command, {}):wait()
  if sysObj.code ~= 0 then
    error(sysObj.stderr)
  end
  vim.notify(sysObj.stdout)
  vim.notify(sysObj.stderr, vim.log.levels.WARN)
end

local pluginsPath = vim.fs.joinpath(configDir, 'nvim/pack/plugins/opt')
vim.fn.mkdir(pluginsPath, 'p')
pluginsPath = vim.uv.fs_realpath(pluginsPath)

--- @type table<string, {url:string, branch: string?}>
local plugins = {
  ['gitsigns'] = {url = 'https://github.com/lewis6991/gitsigns.nvim'},
}

for name, repo in pairs(plugins) do
  local installPath = vim.fs.joinpath(pluginsPath, name)
  gitClone(repo.url, installPath, repo.branch)
  -- vim.opt.runtimepath:append(installPath)
  vim.cmd.packadd({args = {name}, bang = true})
end

require('gitsigns').setup({
  debug_mode = true,
  attach_to_untracked = true,
})

Steps to reproduce

Inside some Git repo

  1. nvim --clean -u minimal.lua new2
  2. :Gistigns stage_buffer
  3. quit or suspend nvim
  4. GIT_PAGER= git diff -- new2

Gitsigns debug messages

attach(1): Attaching (trigger=BufReadPost)
run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 config user.name
run_job: git --version
run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD
run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir .../.git -c core.quotepath=off ls-files --stage --others --exclude-standard --eol .../new2
watch_gitdir(1): Watching git dir
get_show_text: no revision or object_name
cli.run: Running action 'stage_buffer' with arguments {}
run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir .../.git add --intent-to-add .../new2
watcher_cb(1): Git dir update: 'index.lock' { rename = true } (ignoring)
watcher_cb(1): Git dir update: 'index.lock' { change = true } (ignoring)
watcher_cb(1): Git dir update: 'index.lock' { rename = true } (ignoring)
watcher_cb(1): Git dir update: 'index' { rename = true }
run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir .../.git -c core.quotepath=off ls-files --stage --others --exclude-standard --eol .../new2
run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir .../.git apply --whitespace=nowarn --cached --unidiff-zero -
watcher_cb(1): Git dir update: 'index.lock' { rename = true } (ignoring)
watcher_cb(1): Git dir update: 'index.lock' { change = true } (ignoring)
watcher_cb(1): Git dir update: 'index.lock' { rename = true } (ignoring)
watcher_cb(1): Git dir update: 'index' { rename = true }
run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir .../.git show e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD
run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir .../.git -c core.quotepath=off ls-files --stage --others --exclude-standard --eol .../new2
run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir .../.git show 8b137891791fe96927ad78e64b0aad7bded08bdc
lewis6991 commented 4 months ago

Very nice report and repro steps 👍

lewis6991 commented 4 months ago

I'm not sure this is a bug. Nvim cannot display buffers with no lines as the cursor needs to go somewhere. When you run stage_buffer you are going to stage at least 1 empty line, however when you save the buffer, since their are no modifications, the newline doesn't get written. If you insert a character, save, remove, save again, the file will be saved with 1 empty line.

przepompownia commented 3 months ago

…but how to explain deletion?

I tried to reproduce it again and noticed one missing assumption: empty file new2 exists - otherwise GIT_PAGER= git diff -- new2 shows empty output.

It seems that if the buffer contains nothing or some white character but the corresponding file is empty, the result of stage_buffer should be addition or nothing.