Compare commits

20 Commits

Author SHA1 Message Date
Peter Vandenberk
488c0555e4 Update README.mkd: offno for signcolumn
The valid values for `signcolumn` are '`auto`' ,'`no`', '`yes'` and '`number'` but not '`off'`:

```
E474: Invalid argument: signcolumn=off
```

Thanks!
2025-08-29 11:45:59 +01:00
Fred Hornsey
85ca3a0872 Use shellescape on paths in run_diff
In Neovim 0.11.1 within MSYS2 on Windows, gitgutter wasn't showing up.
After some flailing around, I found adding
`set shellcmdflag=-c shellxquote= shellxescape=`
[from here](https://github.com/neovim/neovim/issues/28384#issuecomment-2135921829),
got `:echo system('git --version')` working. The signs were still not
showing up, but there were files being dumped in my current working
directory that looked like corrupted Windows paths.

After using `let g:gitgutter_log=1` and looking at the log I found where
these temp files were made and I think I fixed it. I haven't tried the
non-msys2 Neovim (assuming that's different) or normal vim, but I
brought the change over and made sure it worked in Neovim on Linux.
2025-05-26 14:33:03 +01:00
Andy Stewart
a5ae0a5a18 Handle quickfix autocmd changing buffer
To keep things fast we turn off gitgutter in the current buffer during
quickfix commands, e.g. :vimgrep, and turn it back on afterwards.

However the user may have a quickfix autocmd which makes a different
buffer the current one, e.g. :cwindow. Previously this caused an error
because gitgutter expected the current buffer to remain current; now we
explicitly enable the original current buffer.

Fixes #904.
2025-05-05 15:15:44 +01:00
Andy Stewart
6620e5fbbe Document :write in preview window 2025-03-07 09:47:02 +00:00
Andy Stewart
33cb7744c3 Document re-preview moving into Nvim floating window
See #903.
2025-03-07 09:45:42 +00:00
Touko Hallasmaa
d3a9986fe8 Fix moving to floating window 2025-03-07 09:39:31 +00:00
Andy Stewart
7b0b5098e3 Fix async commands with Neovim on Windows
Closes #894.
2024-07-16 16:16:19 +01:00
Andy Stewart
bed580ab8b Use git -C instead of cd 2024-07-13 06:20:56 +01:00
Luke Davis
e801371917 Respect &foldopen during next/prev hunk jumps 2024-04-29 20:55:22 +01:00
Andy Stewart
67ef116100 Fix the fix for colons in basepath
Commit 84bc2d6 tried to handle basepath values where the path contained
colons (see #877).  However the fix had two (!) bugs.

- It used strridx() to find the colon separating the diffbase and the
  path; it should have used stridx() because the base comes first.
- It used substring indexes incorrectly: foo[0:-1] returns the whole of
  foo, not an empty string (:help exr-[:]).

Closes #878.
2024-01-05 12:43:02 +00:00
Andy Stewart
84bc2d68c0 Fix base_path() to handle filenames with colons
This bug was introduced when teaching gitgutter to handle file moves
in #872.

Fixes #877.
2024-01-03 16:06:27 +00:00
Andy Stewart
4b49965897 Warn user when unable to list renamed files 2023-12-07 13:56:55 +00:00
Andy Stewart
fe0e8a2630 Add comments 2023-11-11 10:56:40 +00:00
Andy Stewart
3b5acc05a1 Guard config flag in file moves check 2023-11-10 18:23:47 +00:00
Andy Stewart
1e7be38a3c Make command line config flag global 2023-11-10 18:19:48 +00:00
Andy Stewart
b9e9ad2ae2 Fix -c flag to apply to git command not diff subcommand
Fixes #874.
2023-11-10 18:09:44 +00:00
Andy Stewart
6efb835aa2 Suppress crlf warning when obtaining file moves
See comment above gitgutter#diff#run_diff() for rationale.
2023-11-10 15:55:55 +00:00
Andy Stewart
61f80c80ba Mention integration with fugitive's :0Gclog
I actually added this 3.5 years ago (0e509fb) but never updated the
readme.
2023-11-10 12:48:54 +00:00
Andy Stewart
6a95f1b57c Mention file moves in readme 2023-11-10 12:44:11 +00:00
Andy Stewart
c3b99e52f5 Add test for file moves
See #872.
2023-11-10 12:42:19 +00:00
9 changed files with 126 additions and 58 deletions

View File

@@ -18,6 +18,7 @@ Features:
* Stage partial hunks.
* Provides a hunk text object.
* Diffs against index (default) or any commit.
* Handles file moves / renames.
* Heeds git's "assume unchanged" bit.
* Allows folding all unchanged text.
* Provides fold text showing whether folded lines have been changed.
@@ -29,6 +30,7 @@ Features:
* Fully customisable (signs, sign column, line (number) highlights, mappings, extra git-diff arguments, etc).
* Can be toggled on/off, globally or per buffer.
* Preserves signs from other plugins.
* Does the right thing when viewing revisions with [fugitive](https://github.com/tpope/vim-fugitive)'s `:0Gclog`.
* Easy to integrate diff stats into status line; built-in integration with [vim-airline](https://github.com/bling/vim-airline/).
* Works with fish shell (in addition to the usual shells).
@@ -79,7 +81,7 @@ Second, ensure your `updatetime` and `signcolumn` options are set appropriately.
When you make a change to a file tracked by git, the diff markers should appear automatically after a short delay. The delay is governed by vim's `updatetime` option; the default value is `4000`, i.e. 4 seconds, but I suggest reducing it to around 100ms (add `set updatetime=100` to your vimrc). Note `updatetime` also controls the delay before vim writes its swap file (see `:help updatetime`).
The `signcolumn` option can have any value except `'off'`.
The `signcolumn` option can have any value except `'no'`.
### Windows
@@ -531,6 +533,8 @@ let g:gitgutter_async = 0
Add `let g:gitgutter_preview_win_floating = 1` to your `~/.vimrc`. Note that on Vim this prevents you staging (partial) hunks via the preview window.
On Neovim, the preview hunk command will move the cursor into the floating window if it is already open.
#### The appearance of a floating/popup window for hunk previews

View File

@@ -118,11 +118,16 @@ endfunction
" }}}
function! gitgutter#git()
" Optional argument is buffer number
function! gitgutter#git(...)
let git = g:gitgutter_git_executable
if a:0
let git .= ' -C '.gitgutter#utility#dir(a:1)
endif
if empty(g:gitgutter_git_args)
return g:gitgutter_git_executable
return git
else
return g:gitgutter_git_executable.' '.g:gitgutter_git_args
return git.' '.g:gitgutter_git_args
endif
endfunction
@@ -222,9 +227,11 @@ function! gitgutter#quickfix(current_file)
let lnum = 0
for line in diff
if line =~ '^diff --git [^"]'
" No quotation mark therefore no spaces in filenames
let [fnamel, fnamer] = split(line)[2:3]
let fname = fnamel ==# fnamer ? fnamer : fnamer[2:]
elseif line =~ '^diff --git "'
" Quotation mark therefore do not split on space
let [_, fnamel, _, fnamer] = split(line, '"')
let fname = fnamel ==# fnamer ? fnamer : fnamer[2:]
elseif line =~ '^diff --cc [^"]'
@@ -256,9 +263,7 @@ function! gitgutter#difforig()
if g:gitgutter_diff_relative_to ==# 'index'
let index_name = gitgutter#utility#get_diff_base(bufnr).':'.gitgutter#utility#base_path(bufnr)
let cmd = gitgutter#utility#cd_cmd(bufnr,
\ gitgutter#git().' --no-pager show '.index_name
\ )
let cmd = gitgutter#git(bufnr).' --no-pager show '.index_name
" NOTE: this uses &shell to execute cmd. Perhaps we should use instead
" gitgutter#utility's use_known_shell() / restore_shell() functions.
silent! execute "read ++edit !" cmd

View File

@@ -46,7 +46,7 @@ function! s:build_command(cmd)
endif
if has('win32')
return has('nvim') ? ['cmd.exe', '/c', a:cmd] : 'cmd.exe /c '.a:cmd
return has('nvim') ? a:cmd : 'cmd.exe /c '.a:cmd
endif
throw 'unknown os'

View File

@@ -4,14 +4,6 @@ let s:nomodeline = (v:version > 703 || (v:version == 703 && has('patch442'))) ?
let s:hunk_re = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@'
" True for git v1.7.2+.
function! s:git_supports_command_line_config_override() abort
let [_, error_code] = gitgutter#utility#system(gitgutter#git().' -c foo.bar=baz --version')
return !error_code
endfunction
let s:c_flag = s:git_supports_command_line_config_override()
let s:temp_from = tempname()
let s:temp_buffer = tempname()
let s:counter = 0
@@ -124,20 +116,22 @@ function! gitgutter#diff#run_diff(bufnr, from, preserve_full_diff) abort
" Write file from index to temporary file.
let index_name = gitgutter#utility#get_diff_base(a:bufnr).':'.gitgutter#utility#base_path(a:bufnr)
let cmd .= gitgutter#git().' --no-pager show --textconv '.index_name.' > '.from_file.' || exit 0) && ('
let cmd .= gitgutter#git(a:bufnr).' --no-pager show --textconv '.index_name
let cmd .= ' > '.gitgutter#utility#shellescape(from_file).' || exit 0) && ('
elseif a:from ==# 'working_tree'
let from_file = gitgutter#utility#repo_path(a:bufnr, 1)
endif
" Call git-diff.
let cmd .= gitgutter#git().' --no-pager'
if s:c_flag
let cmd .= gitgutter#git(a:bufnr).' --no-pager'
if gitgutter#utility#git_supports_command_line_config_override()
let cmd .= ' -c "diff.autorefreshindex=0"'
let cmd .= ' -c "diff.noprefix=false"'
let cmd .= ' -c "core.safecrlf=false"'
endif
let cmd .= ' diff --no-ext-diff --no-color -U0 '.g:gitgutter_diff_args.' -- '.from_file.' '.buff_file
let cmd .= ' diff --no-ext-diff --no-color -U0 '.g:gitgutter_diff_args
let cmd .= ' -- '.gitgutter#utility#shellescape(from_file).' '.gitgutter#utility#shellescape(buff_file)
" Pipe git-diff output into grep.
if !a:preserve_full_diff && !empty(g:gitgutter_grep)
@@ -152,8 +146,6 @@ function! gitgutter#diff#run_diff(bufnr, from, preserve_full_diff) abort
let cmd .= ')'
let cmd = gitgutter#utility#cd_cmd(a:bufnr, cmd)
if g:gitgutter_async && gitgutter#async#available()
call gitgutter#async#execute(cmd, a:bufnr, {
\ 'out': function('gitgutter#diff#handler'),

View File

@@ -60,7 +60,8 @@ function! gitgutter#hunk#next_hunk(count) abort
if hunk[2] > current_line
let hunk_count += 1
if hunk_count == a:count
execute 'normal!' hunk[2] . 'Gzv'
let keys = &foldopen =~# '\<block\>' ? 'zv' : ''
execute 'normal!' hunk[2] . 'G' . keys
if g:gitgutter_show_msg_on_hunk_jumping
redraw | echo printf('Hunk %d of %d', index(hunks, hunk) + 1, len(hunks))
endif
@@ -90,8 +91,9 @@ function! gitgutter#hunk#prev_hunk(count) abort
if hunk[2] < current_line
let hunk_count += 1
if hunk_count == a:count
let keys = &foldopen =~# '\<block\>' ? 'zv' : ''
let target = hunk[2] == 0 ? 1 : hunk[2]
execute 'normal!' target . 'Gzv'
execute 'normal!' target . 'G' . keys
if g:gitgutter_show_msg_on_hunk_jumping
redraw | echo printf('Hunk %d of %d', index(hunks, hunk) + 1, len(hunks))
endif
@@ -306,9 +308,8 @@ function! s:stage(hunk_diff)
write
let path = gitgutter#utility#repo_path(bufnr, 1)
" Add file to index.
let cmd = gitgutter#utility#cd_cmd(bufnr,
\ gitgutter#git().' add '.
\ gitgutter#utility#shellescape(gitgutter#utility#filename(bufnr)))
let cmd = gitgutter#git(bufnr).' add '.
\ gitgutter#utility#shellescape(gitgutter#utility#filename(bufnr))
let [_, error_code] = gitgutter#utility#system(cmd)
else
return
@@ -318,7 +319,7 @@ function! s:stage(hunk_diff)
let diff = s:adjust_header(bufnr, a:hunk_diff)
" Apply patch to index.
let [_, error_code] = gitgutter#utility#system(
\ gitgutter#utility#cd_cmd(bufnr, gitgutter#git().' apply --cached --unidiff-zero - '),
\ gitgutter#git(bufnr).' apply --cached --unidiff-zero - ',
\ diff)
endif
@@ -358,6 +359,11 @@ endfunction
function! s:preview(hunk_diff)
if g:gitgutter_preview_win_floating && exists('*nvim_set_current_win') && s:winid != 0
call nvim_set_current_win(s:winid)
return
endif
let lines = split(a:hunk_diff, '\r\?\n')
let header = lines[0:4]
let body = lines[5:]

View File

@@ -6,6 +6,15 @@ function! gitgutter#utility#supports_overscore_sign()
endif
endfunction
" True for git v1.7.2+.
function! gitgutter#utility#git_supports_command_line_config_override() abort
if !exists('s:c_flag')
let [_, error_code] = gitgutter#utility#system(gitgutter#git().' -c foo.bar=baz --version')
let s:c_flag = !error_code
endif
return s:c_flag
endfunction
function! gitgutter#utility#setbufvar(buffer, varname, val)
let buffer = +a:buffer
" Default value for getbufvar() was introduced in Vim 7.3.831.
@@ -57,7 +66,7 @@ function! gitgutter#utility#is_active(bufnr) abort
endfunction
function! s:not_git_dir(bufnr) abort
return s:dir(a:bufnr) !~ '[/\\]\.git\($\|[/\\]\)'
return gitgutter#utility#dir(a:bufnr) !~ '[/\\]\.git\($\|[/\\]\)'
endfunction
function! s:is_file_buffer(bufnr) abort
@@ -153,9 +162,8 @@ function! gitgutter#utility#set_repo_path(bufnr, continuation) abort
" * -3 - assume unchanged
call gitgutter#utility#setbufvar(a:bufnr, 'path', -1)
let cmd = gitgutter#utility#cd_cmd(a:bufnr,
\ gitgutter#git().' ls-files -v --error-unmatch --full-name -z -- '.
\ gitgutter#utility#shellescape(gitgutter#utility#filename(a:bufnr)))
let cmd = gitgutter#git(a:bufnr).' ls-files -v --error-unmatch --full-name -z -- '.
\ gitgutter#utility#shellescape(gitgutter#utility#filename(a:bufnr))
if g:gitgutter_async && gitgutter#async#available() && !has('vim_starting')
let handler = copy(s:set_path_handler)
@@ -184,9 +192,8 @@ endfunction
function! gitgutter#utility#clean_smudge_filter_applies(bufnr)
let filtered = gitgutter#utility#getbufvar(a:bufnr, 'filter', -1)
if filtered == -1
let cmd = gitgutter#utility#cd_cmd(a:bufnr,
\ gitgutter#git().' check-attr filter -- '.
\ gitgutter#utility#shellescape(gitgutter#utility#filename(a:bufnr)))
let cmd = gitgutter#git(a:bufnr).' check-attr filter -- '.
\ gitgutter#utility#shellescape(gitgutter#utility#filename(a:bufnr))
let [out, _] = gitgutter#utility#system(cmd)
let filtered = out !~ 'unspecified'
call gitgutter#utility#setbufvar(a:bufnr, 'filter', filtered)
@@ -195,19 +202,6 @@ function! gitgutter#utility#clean_smudge_filter_applies(bufnr)
endfunction
function! gitgutter#utility#cd_cmd(bufnr, cmd) abort
let cd = s:unc_path(a:bufnr) ? 'pushd' : (gitgutter#utility#windows() && s:dos_shell() ? 'cd /d' : 'cd')
return cd.' '.s:dir(a:bufnr).' && '.a:cmd
endfunction
function! s:unc_path(bufnr)
return s:abs_path(a:bufnr, 0) =~ '^\\\\'
endfunction
function! s:dos_shell()
return &shell == 'cmd.exe' || &shell == 'command.com'
endfunction
function! s:use_known_shell() abort
if has('unix') && &shell !=# 'sh'
let [s:shell, s:shellcmdflag, s:shellredir, s:shellpipe, s:shellquote, s:shellxquote] = [&shell, &shellcmdflag, &shellredir, &shellpipe, &shellquote, &shellxquote]
@@ -243,7 +237,14 @@ function! gitgutter#utility#base_path(bufnr)
" If we already know the original path at this diff base, return it.
let basepath = gitgutter#utility#getbufvar(a:bufnr, 'basepath', '')
if !empty(basepath)
let [base, bpath] = split(basepath, ':', 1)
" basepath is diffbase:path
" Note that path can also contain colons.
" List destructuring / unpacking where the remaining items are assigned
" to a single variable (:help let-unpack) is only available in v8.2.0540.
let parts = split(basepath, ':', 1)
let base = parts[0]
let bpath = join(parts[1:], ':')
if base == diffbase
return gitgutter#utility#shellescape(bpath)
endif
@@ -287,15 +288,24 @@ endfunction
" Returns a dict of current path to original path at the given base.
function! s:obtain_file_renames(bufnr, base)
let renames = {}
let cmd = gitgutter#git().' diff --diff-filter=R --name-status '.a:base
let [out, error_code] = gitgutter#utility#system(gitgutter#utility#cd_cmd(a:bufnr, cmd))
let cmd = gitgutter#git(a:bufnr)
if gitgutter#utility#git_supports_command_line_config_override()
let cmd .= ' -c "core.safecrlf=false"'
endif
let cmd .= ' diff --diff-filter=R --name-status '.a:base
let [out, error_code] = gitgutter#utility#system(cmd)
if error_code
" Assume the problem is the diff base.
call gitgutter#utility#warn('g:gitgutter_diff_base ('.a:base.') is invalid')
return {}
endif
for line in split(out, '\n')
let [original, current] = split(line)[1:]
let fields = split(line)
if len(fields) != 3
call gitgutter#utility#warn('gitgutter: unable to list renamed files: '.line)
return {}
endif
let [original, current] = fields[1:]
let renames[current] = original
endfor
return renames
@@ -310,7 +320,8 @@ function! s:abs_path(bufnr, shellesc)
return a:shellesc ? gitgutter#utility#shellescape(p) : p
endfunction
function! s:dir(bufnr) abort
" Shellescaped
function! gitgutter#utility#dir(bufnr) abort
return gitgutter#utility#shellescape(fnamemodify(s:abs_path(a:bufnr, 0), ':h'))
endfunction

View File

@@ -201,11 +201,13 @@ Commands for operating on a hunk:~
:GitGutterUndoHunk Undo the hunk the cursor is in.
*gitgutter-:GitGutterPreviewHunk*
:GitGutterPreviewHunk Preview the hunk the cursor is in.
:GitGutterPreviewHunk Preview the hunk the cursor is in or, if you are using
floating preview windows in Neovim and the window is
already open, move the cursor into the window.
To stage part of the hunk, move to the preview window,
delete any lines you do not want to stage, and
|GitGutterStageHunk|.
delete any lines you do not want to stage, and |write|
or |GitGutterStageHunk|.
To close a non-floating preview window use |:pclose|
or |CTRL-W_z| or |CTRL-W_CTRL-Z|; or normal window-

View File

@@ -341,8 +341,18 @@ augroup gitgutter
autocmd BufFilePre * call s:on_buffilepre(expand('<abuf>'))
autocmd BufFilePost * call s:on_buffilepost(expand('<abuf>'))
autocmd QuickFixCmdPre *vimgrep* let b:gitgutter_was_enabled = gitgutter#utility#getbufvar(expand('<abuf>'), 'enabled') | GitGutterBufferDisable
autocmd QuickFixCmdPost *vimgrep* if b:gitgutter_was_enabled | GitGutterBufferEnable | endif | unlet b:gitgutter_was_enabled
autocmd QuickFixCmdPre *vimgrep*
\ if gitgutter#utility#getbufvar(expand('<abuf>'), 'enabled') |
\ let s:gitgutter_was_enabled = expand('<abuf>') |
\ else |
\ let s:gitgutter_was_enabled = 0 |
\ endif |
\ GitGutterBufferDisable
autocmd QuickFixCmdPost *vimgrep*
\ if s:gitgutter_was_enabled |
\ call gitgutter#buffer_enable(s:gitgutter_was_enabled) |
\ endif |
\ unlet s:gitgutter_was_enabled
augroup END
" }}}

View File

@@ -52,6 +52,7 @@ endfunction
"
function SetUp()
let g:gitgutter_diff_base = ''
call system("git init ".s:test_repo.
\ " && cd ".s:test_repo.
\ " && cp ../.gitconfig .".
@@ -195,6 +196,20 @@ function Test_filename_with_equals()
endfunction
function Test_filename_with_colon()
call system('touch fix:ture.txt && git add fix:ture.txt')
edit fix:ture.txt
normal ggo*
call s:trigger_gitgutter()
let expected = [
\ {'lnum': 1, 'name': 'GitGutterLineAdded'},
\ {'lnum': 2, 'name': 'GitGutterLineAdded'}
\ ]
call s:assert_signs(expected, 'fix:ture.txt')
endfunction
function Test_filename_with_square_brackets()
call system('touch fix[tu]re.txt && git add fix[tu]re.txt')
edit fix[tu]re.txt
@@ -280,6 +295,29 @@ function Test_saveas()
endfunction
function Test_file_mv()
call system('git mv fixture.txt fixture_moved.txt')
edit fixture_moved.txt
normal ggo*
call s:trigger_gitgutter()
let expected = [{'lnum': 2, 'name': 'GitGutterLineAdded'}]
call s:assert_signs(expected, 'fixture_moved.txt')
write
call system('git add fixture_moved.txt && git commit -m "moved and edited"')
GitGutterDisable
GitGutterEnable
let expected = []
call s:assert_signs(expected, 'fixture_moved.txt')
GitGutterDisable
let g:gitgutter_diff_base = 'HEAD^'
GitGutterEnable
let expected = [{'lnum': 2, 'name': 'GitGutterLineAdded'}]
call s:assert_signs(expected, 'fixture_moved.txt')
endfunction
" FIXME: this test fails when it is the first (or only) test to be run
function Test_follow_symlink()
let tmp = 'symlink'