Skip to content

Gitsigns diffthis stages files with an extra trailing newline #1145

@daephx

Description

@daephx

Description

I've experienced basically the same thing described in #1023 (comment) for a good while now when using :Gitsigns diffthis. Namely, this section:

Gitsigns stages files with an extra trailing newline. I'll delete that newline from the stage side, but it is still added despite not existing in the source buffer. The source buffer then shows the modification in the signcolumn on the last line after staging any file.

I tested on most of my devices, separate versions of Neovim and finally, within a VM to be certain it isn't a weird quirk of my config files.
After ruling that out, I decided to bisect the plugin and the bad commit that I landed on was: 79127db.
From reading the linked issue, I would assume that the upstream issue mention should be resolved?
I've tested with and without an EditorConfig file present with no noticeable change.

Neovim version

NVIM v0.10.2, v0.11.0-dev

Operating system and version

ArchLinux, Ubuntu 24.04.1 LTS, Windows 10

Expected behavior

Staging from Gitsigns diffthis by writing the index buffer should work as expected.
Ideally, without creating extranous newlines.

Actual behavior

Writing the index buffer will create and stage a newline, regardless of if one is necessary.

icfIWsVo14

Minimal config

for name, url in pairs{
  gitsigns = 'https://github.com/lewis6991/gitsigns.nvim',
} do
local install_path = vim.fn.fnamemodify('gitsigns_issue/'..name, ':p')
if vim.fn.isdirectory(install_path) == 0 then
vim.fn.system { 'git', 'clone', '--depth=1', url, install_path }
end
vim.opt.runtimepath:append(install_path)
end

require('gitsigns').setup{
  debug_mode = true, -- You must add this to enable debug messages
}

-- Set default options
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.signcolumn = "yes:2"

Steps to reproduce

I have provided a small script to further simplify setting up the issue:

reproduce.sh
#!/usr/bin/env bash

# Create temporary issue directory.
mkdir "gitsigns_issue"
cd "gitsigns_issue" || exit

# Create basic test file.
echo "Hello world!" > file

# Create gitignore file.
cat << EOF > .gitignore
gitsigns_issue/
minimal.lua
EOF

# Create minimal config file.
cat << EOF > minimal.lua
for name, url in pairs{
  gitsigns = 'https://github.com/lewis6991/gitsigns.nvim',
} do
local install_path = vim.fn.fnamemodify('gitsigns_issue/'..name, ':p')
if vim.fn.isdirectory(install_path) == 0 then
vim.fn.system { 'git', 'clone', '--depth=1', url, install_path }
end
vim.opt.runtimepath:append(install_path)
end

require('gitsigns').setup{
  debug_mode = true, -- You must add this to enable debug messages
}

-- Set default options
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.signcolumn = "yes:2"
EOF

# Initialize testing repo
git init
git add .
git commit -m "initial commit"

# Start neovim
nvim --clean -u minimal.lua file
  1. Run the script, this will set up the test repo and open the test file in Neovim.
  2. Execute: :Gitsigns diffthis
  3. Focus the index buffer: gitsigns:///.../gitsigns_issue/.git/:0:file
  4. Write the index buffer: :write
  5. The sign column should update as a newline is created and staged.

From here, you can check the diff/status of the gitsigns_issue/ directory, and you should notice the newline has been staged, and the working file has it removed (since that file was never modified).

Gitsigns debug messages

0.61 D dprintf: Deriving GitSignsAdd from Added
0.63 D derive: Deriving GitSignsChange from Changed
0.67 D derive: Deriving GitSignsDelete from Removed
0.69 D derive: Deriving GitSignsChangedelete from GitSignsChange
0.73 D derive: Deriving GitSignsTopdelete from GitSignsDelete
0.75 D derive: Deriving GitSignsUntracked from GitSignsAdd
0.77 D derive: Deriving GitSignsAddNr from GitSignsAdd
0.79 D derive: Deriving GitSignsChangeNr from GitSignsChange
0.82 D derive: Deriving GitSignsDeleteNr from GitSignsDelete
0.83 D derive: Deriving GitSignsChangedeleteNr from GitSignsChangeNr
0.85 D derive: Deriving GitSignsTopdeleteNr from GitSignsDeleteNr
0.87 D derive: Deriving GitSignsUntrackedNr from GitSignsAddNr
0.89 D derive: Deriving GitSignsAddLn from DiffAdd
0.90 D derive: Deriving GitSignsChangeLn from DiffChange
0.92 D derive: Deriving GitSignsChangedeleteLn from GitSignsChangeLn
0.94 D derive: Deriving GitSignsUntrackedLn from GitSignsAddLn
0.95 D derive: Deriving GitSignsAddCul from GitSignsAdd
0.96 D derive: Deriving GitSignsChangeCul from GitSignsChange
0.97 D derive: Deriving GitSignsDeleteCul from GitSignsDelete
0.98 D derive: Deriving GitSignsChangedeleteCul from GitSignsChangeCul
0.99 D derive: Deriving GitSignsTopdeleteCul from GitSignsDeleteCul
1.00 D derive: Deriving GitSignsUntrackedCul from GitSignsAddCul
1.02 D derive: Deriving GitSignsStagedAdd from GitSignsAdd
1.03 D derive: Deriving GitSignsStagedChange from GitSignsChange
1.05 D derive: Deriving GitSignsStagedDelete from GitSignsDelete
1.08 D derive: Deriving GitSignsStagedChangedelete from GitSignsChangedelete
1.10 D derive: Deriving GitSignsStagedTopdelete from GitSignsTopdelete
1.11 D derive: Deriving GitSignsStagedAddNr from GitSignsAddNr
1.12 D derive: Deriving GitSignsStagedChangeNr from GitSignsChangeNr
1.14 D derive: Deriving GitSignsStagedDeleteNr from GitSignsDeleteNr
1.15 D derive: Deriving GitSignsStagedChangedeleteNr from GitSignsChangedeleteNr
1.16 D derive: Deriving GitSignsStagedTopdeleteNr from GitSignsTopdeleteNr
1.18 D derive: Deriving GitSignsStagedAddLn from GitSignsAddLn
1.20 D derive: Deriving GitSignsStagedChangeLn from GitSignsChangeLn
1.21 D derive: Could not derive GitSignsStagedDeleteLn
1.22 D derive: Deriving GitSignsStagedChangedeleteLn from GitSignsChangedeleteLn
1.24 D derive: Could not derive GitSignsStagedTopdeleteLn
1.25 D derive: Deriving GitSignsStagedAddCul from GitSignsAddCul
1.26 D derive: Deriving GitSignsStagedChangeCul from GitSignsChangeCul
1.27 D derive: Deriving GitSignsStagedDeleteCul from GitSignsDeleteCul
1.29 D derive: Deriving GitSignsStagedChangedeleteCul from GitSignsChangedeleteCul
1.30 D derive: Deriving GitSignsStagedTopdeleteCul from GitSignsTopdeleteCul
1.32 D derive: Deriving GitSignsAddPreview from DiffAdd
1.33 D derive: Deriving GitSignsDeletePreview from DiffDelete
1.36 D derive: Deriving GitSignsCurrentLineBlame from NonText
1.39 D derive: Deriving GitSignsAddInline from TermCursor
1.40 D derive: Deriving GitSignsDeleteInline from TermCursor
1.41 D derive: Deriving GitSignsChangeInline from TermCursor
1.43 D derive: Deriving GitSignsAddLnInline from GitSignsAddInline
1.46 D derive: Deriving GitSignsChangeLnInline from GitSignsChangeInline
1.47 D derive: Deriving GitSignsDeleteLnInline from GitSignsDeleteInline
1.49 D derive: Deriving GitSignsDeleteVirtLn from DiffDelete
1.51 D derive: Deriving GitSignsDeleteVirtLnInLine from GitSignsDeleteLnInline
1.52 D derive: Deriving GitSignsVirtLnum from GitSignsDeleteVirtLn
5.81 D attach(1): Attaching (trigger=BufReadPost)
5.86 D run_job: git --version
10.07 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD
13.53 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git config user.name
14.53 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git -c core.quotepath=off ls-files --stage --others --exclude-standard --eol /home/daephx/Projects/test/gitsigns_issue/file
16.03 D watch_gitdir(1): Watching git dir
16.09 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git show cd0875583aabe89ee197ea133980a9085d08e497
18.27 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git show HEAD:file
5456.64 D cli.run: Running action 'diffthis' with arguments {}
5456.88 D detach(2): Detached
5456.88 D detach(2): Cache was nil
5456.92 D attach(2): Attaching (trigger=BufFilePost)
5457.01 D get_buf_path(2): Gitsigns buffer for file '/home/daephx/Projects/test/gitsigns_issue//file' from path 'gitsigns:///home/daephx/Projects/test/gitsigns_issue/.git//:0:file' on commit 'nil'
5457.98 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD
5459.55 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git -c core.quotepath=off ls-files --stage --others --exclude-standard --eol /home/daephx/Projects/test/gitsigns_issue/file
5460.82 D watch_gitdir(2): Watching git dir
5460.85 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git show cd0875583aabe89ee197ea133980a9085d08e497
5462.44 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git show HEAD:file
6992.10 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git hash-object -w --path file --stdin
6993.57 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git update-index --cacheinfo 100644,47ee7698c336ba5b163c193ae6309f0a7d7e9662,file
6994.97 D watcher_cb(1): Git dir update: 'index.lock' { rename = true }
6994.99 D watcher_cb(2): Git dir update: 'index.lock' { rename = true }
6995.00 D watcher_cb(1): Git dir update: 'index.lock' { change = true }
6995.03 D watcher_cb(2): Git dir update: 'index.lock' { change = true }
6995.05 D watcher_cb(1): Git dir update: 'index.lock' { rename = true }
6995.06 D watcher_cb(2): Git dir update: 'index.lock' { rename = true }
6995.07 D watcher_cb(1): Git dir update: 'index' { rename = true }
6995.09 D watcher_cb(2): Git dir update: 'index' { rename = true }
7194.79 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD
7195.39 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD
7196.33 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git -c core.quotepath=off ls-files --stage --others --exclude-standard --eol /home/daephx/Projects/test/gitsigns_issue/file
7196.88 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git -c core.quotepath=off ls-files --stage --others --exclude-standard --eol /home/daephx/Projects/test/gitsigns_issue/file
7197.63 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git show 47ee7698c336ba5b163c193ae6309f0a7d7e9662
7198.20 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git show 47ee7698c336ba5b163c193ae6309f0a7d7e9662
7198.95 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git show HEAD:file
7199.57 D run_job: git --no-pager --no-optional-locks --literal-pathspecs -c gc.auto=0 --git-dir /home/daephx/Projects/test/gitsigns_issue/.git show HEAD:file
9059.13 D cli.run: Running action 'debug_messages' with arguments {}

Gitsigns cache

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions