mirror of
https://github.com/airblade/vim-gitgutter.git
synced 2025-11-08 11:33:48 -05:00
Before the plugin tries to diff a file, it checks whether git is tracking the file. If git isn't tracking the file, it stops there and doesn't display any signs. If git is tracking the file, the plugin remembers so next time it can skip the check. When I introduced asynchronous diffing for NeoVim (18b78361), I made a refactoring mistake which caused the plugin on second and subsequent runs [to always think git is tracking a file][1]. The non-realtime diffs – the ones you get when you save a buffer – basically run `git diff FILE`. With an untracked file git returns nothing and exits successfully. So although the plugin erroneously thinks git is tracking the file, it gets an error-free, empty diff back and so removes any and all signs. Which means that the bug doesn't make any difference. However the realtime diffs write the buffer's contents to a temporary file, and write the file as staged in the index to a temporary file, then run `git diff FILE1 FILE2`. To write the staged version of the file we use `git show :FILE > TMPFILE`. When `FILE` isn't known to git, `git show :FILE` exits with an error. Unless, that is, [the filename contains square brackets and you're using git v2.5.0+][2], in which case git exits successfully with empty output. So if you're using git v2.5.0+, and you're editing an untracked file in a repository, and the filename contains square brackets, the plugin will think: git is tracking the file; the realtime diff is successful; the file in the index is empty; so every line in the the working copy must be an addition; hence a `+` sign on every line. [1]:18b7836168/autoload/gitgutter/diff.vim (L119-L121)[2]: http://comments.gmane.org/gmane.comp.version-control.git/285686 Closes #325.
257 lines
6.8 KiB
VimL
257 lines
6.8 KiB
VimL
" Primary functions {{{
|
|
|
|
function! gitgutter#all()
|
|
for buffer_id in tabpagebuflist()
|
|
let file = expand('#' . buffer_id . ':p')
|
|
if !empty(file)
|
|
call gitgutter#process_buffer(buffer_id, 0)
|
|
endif
|
|
endfor
|
|
endfunction
|
|
|
|
" bufnr: (integer) the buffer to process.
|
|
" realtime: (boolean) when truthy, do a realtime diff; otherwise do a disk-based diff.
|
|
function! gitgutter#process_buffer(bufnr, realtime)
|
|
call gitgutter#utility#set_buffer(a:bufnr)
|
|
if gitgutter#utility#is_active()
|
|
if g:gitgutter_sign_column_always
|
|
call gitgutter#sign#add_dummy_sign()
|
|
endif
|
|
try
|
|
if !a:realtime || gitgutter#utility#has_fresh_changes()
|
|
let diff = gitgutter#diff#run_diff(a:realtime || gitgutter#utility#has_unsaved_changes(), 0)
|
|
if diff != 'async'
|
|
call gitgutter#handle_diff(diff)
|
|
endif
|
|
endif
|
|
catch /diff failed/
|
|
call gitgutter#hunk#reset()
|
|
endtry
|
|
else
|
|
call gitgutter#hunk#reset()
|
|
endif
|
|
endfunction
|
|
|
|
|
|
function! gitgutter#handle_diff_job(job_id, data, event)
|
|
if a:event == 'stdout'
|
|
" a:data is a list
|
|
call gitgutter#utility#job_output_received(a:job_id, 'stdout')
|
|
call gitgutter#handle_diff(join(a:data,"\n")."\n")
|
|
|
|
elseif a:event == 'exit'
|
|
" If the exit event is triggered without a preceding stdout event,
|
|
" the diff was empty.
|
|
if gitgutter#utility#is_pending_job(a:job_id)
|
|
call gitgutter#handle_diff("")
|
|
call gitgutter#utility#job_output_received(a:job_id, 'exit')
|
|
endif
|
|
|
|
else
|
|
call gitgutter#hunk#reset()
|
|
call gitgutter#utility#job_output_received(a:job_id, 'stderr')
|
|
|
|
endif
|
|
endfunction
|
|
|
|
|
|
function! gitgutter#handle_diff(diff)
|
|
call setbufvar(gitgutter#utility#bufnr(), 'gitgutter_tracked', 1)
|
|
|
|
call gitgutter#hunk#set_hunks(gitgutter#diff#parse_diff(a:diff))
|
|
let modified_lines = gitgutter#diff#process_hunks(gitgutter#hunk#hunks())
|
|
|
|
if len(modified_lines) > g:gitgutter_max_signs
|
|
call gitgutter#utility#warn_once('exceeded maximum number of signs (configured by g:gitgutter_max_signs).', 'max_signs')
|
|
call gitgutter#sign#clear_signs()
|
|
return
|
|
endif
|
|
|
|
if g:gitgutter_signs || g:gitgutter_highlight_lines
|
|
call gitgutter#sign#update_signs(modified_lines)
|
|
endif
|
|
|
|
call gitgutter#utility#save_last_seen_change()
|
|
endfunction
|
|
|
|
function! gitgutter#disable()
|
|
" get list of all buffers (across all tabs)
|
|
let buflist = []
|
|
for i in range(tabpagenr('$'))
|
|
call extend(buflist, tabpagebuflist(i + 1))
|
|
endfor
|
|
|
|
for buffer_id in buflist
|
|
let file = expand('#' . buffer_id . ':p')
|
|
if !empty(file)
|
|
call gitgutter#utility#set_buffer(buffer_id)
|
|
call gitgutter#sign#clear_signs()
|
|
call gitgutter#sign#remove_dummy_sign(1)
|
|
call gitgutter#hunk#reset()
|
|
endif
|
|
endfor
|
|
|
|
let g:gitgutter_enabled = 0
|
|
endfunction
|
|
|
|
function! gitgutter#enable()
|
|
let g:gitgutter_enabled = 1
|
|
call gitgutter#all()
|
|
endfunction
|
|
|
|
function! gitgutter#toggle()
|
|
if g:gitgutter_enabled
|
|
call gitgutter#disable()
|
|
else
|
|
call gitgutter#enable()
|
|
endif
|
|
endfunction
|
|
|
|
" }}}
|
|
|
|
" Line highlights {{{
|
|
|
|
function! gitgutter#line_highlights_disable()
|
|
let g:gitgutter_highlight_lines = 0
|
|
call gitgutter#highlight#define_sign_line_highlights()
|
|
|
|
if !g:gitgutter_signs
|
|
call gitgutter#sign#clear_signs()
|
|
call gitgutter#sign#remove_dummy_sign(0)
|
|
endif
|
|
|
|
redraw!
|
|
endfunction
|
|
|
|
function! gitgutter#line_highlights_enable()
|
|
let old_highlight_lines = g:gitgutter_highlight_lines
|
|
|
|
let g:gitgutter_highlight_lines = 1
|
|
call gitgutter#highlight#define_sign_line_highlights()
|
|
|
|
if !old_highlight_lines && !g:gitgutter_signs
|
|
call gitgutter#all()
|
|
endif
|
|
|
|
redraw!
|
|
endfunction
|
|
|
|
function! gitgutter#line_highlights_toggle()
|
|
if g:gitgutter_highlight_lines
|
|
call gitgutter#line_highlights_disable()
|
|
else
|
|
call gitgutter#line_highlights_enable()
|
|
endif
|
|
endfunction
|
|
|
|
" }}}
|
|
|
|
" Signs {{{
|
|
|
|
function! gitgutter#signs_enable()
|
|
let old_signs = g:gitgutter_signs
|
|
|
|
let g:gitgutter_signs = 1
|
|
call gitgutter#highlight#define_sign_text_highlights()
|
|
|
|
if !old_signs && !g:gitgutter_highlight_lines
|
|
call gitgutter#all()
|
|
endif
|
|
endfunction
|
|
|
|
function! gitgutter#signs_disable()
|
|
let g:gitgutter_signs = 0
|
|
call gitgutter#highlight#define_sign_text_highlights()
|
|
|
|
if !g:gitgutter_highlight_lines
|
|
call gitgutter#sign#clear_signs()
|
|
call gitgutter#sign#remove_dummy_sign(0)
|
|
endif
|
|
endfunction
|
|
|
|
function! gitgutter#signs_toggle()
|
|
if g:gitgutter_signs
|
|
call gitgutter#signs_disable()
|
|
else
|
|
call gitgutter#signs_enable()
|
|
endif
|
|
endfunction
|
|
|
|
" }}}
|
|
|
|
" Hunks {{{
|
|
|
|
function! gitgutter#stage_hunk()
|
|
if gitgutter#utility#is_active()
|
|
" Ensure the working copy of the file is up to date.
|
|
" It doesn't make sense to stage a hunk otherwise.
|
|
noautocmd silent write
|
|
let diff = gitgutter#diff#run_diff(0, 1)
|
|
call gitgutter#handle_diff(diff)
|
|
|
|
if empty(gitgutter#hunk#current_hunk())
|
|
call gitgutter#utility#warn('cursor is not in a hunk')
|
|
else
|
|
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(diff, 'stage')
|
|
call gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file('git apply --cached --unidiff-zero - '), diff_for_hunk)
|
|
|
|
" refresh gitgutter's view of buffer
|
|
silent execute "GitGutter"
|
|
endif
|
|
|
|
silent! call repeat#set("\<Plug>GitGutterStageHunk", -1)<CR>
|
|
endif
|
|
endfunction
|
|
|
|
function! gitgutter#undo_hunk()
|
|
if gitgutter#utility#is_active()
|
|
" Ensure the working copy of the file is up to date.
|
|
" It doesn't make sense to stage a hunk otherwise.
|
|
noautocmd silent write
|
|
let diff = gitgutter#diff#run_diff(0, 1)
|
|
call gitgutter#handle_diff(diff)
|
|
|
|
if empty(gitgutter#hunk#current_hunk())
|
|
call gitgutter#utility#warn('cursor is not in a hunk')
|
|
else
|
|
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(diff, 'undo')
|
|
call gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file('git apply --reverse --unidiff-zero - '), diff_for_hunk)
|
|
|
|
" reload file
|
|
silent edit
|
|
endif
|
|
|
|
silent! call repeat#set("\<Plug>GitGutterUndoHunk", -1)<CR>
|
|
endif
|
|
endfunction
|
|
|
|
function! gitgutter#preview_hunk()
|
|
if gitgutter#utility#is_active()
|
|
" Ensure the working copy of the file is up to date.
|
|
" It doesn't make sense to stage a hunk otherwise.
|
|
noautocmd silent write
|
|
let diff = gitgutter#diff#run_diff(0, 1)
|
|
call gitgutter#handle_diff(diff)
|
|
|
|
if empty(gitgutter#hunk#current_hunk())
|
|
call gitgutter#utility#warn('cursor is not in a hunk')
|
|
else
|
|
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(diff, 'preview')
|
|
|
|
silent! wincmd P
|
|
if !&previewwindow
|
|
execute 'bo ' . &previewheight . ' new'
|
|
set previewwindow
|
|
endif
|
|
|
|
setlocal noro modifiable filetype=diff buftype=nofile bufhidden=delete noswapfile
|
|
execute "%delete_"
|
|
call append(0, split(diff_for_hunk, "\n"))
|
|
|
|
wincmd p
|
|
endif
|
|
endif
|
|
endfunction
|
|
|
|
" }}}
|