Fix staging of hunks coming after deleted lines.

Previously vim-gitgutter generated context-free patches and applied
those to the index.  However when staging a hunk situated after any
deleted lines, the line numbers on the patch were out by the number
of lines deleted, and without any context git would apply the patch
to the wrong part of the file in the index.

This commit ensure patches are generated with 1 line of context,
allowing git to adjust the line numbers appropriately and apply the
patch to the right location.

More lines of context would help git more to adjust line numbers; but
the more context we have the more we group together hunks we would
like to treat separately.
This commit is contained in:
Andy Stewart
2014-10-16 11:50:20 +02:00
parent 1e8201963c
commit e5eb9e6ecf
3 changed files with 32 additions and 40 deletions

View File

@@ -19,7 +19,7 @@ function! gitgutter#process_buffer(file, realtime)
endif endif
try try
if !a:realtime || gitgutter#utility#has_fresh_changes(a:file) if !a:realtime || gitgutter#utility#has_fresh_changes(a:file)
let diff = gitgutter#diff#run_diff(a:realtime || gitgutter#utility#has_unsaved_changes(a:file), 1) let diff = gitgutter#diff#run_diff(a:realtime || gitgutter#utility#has_unsaved_changes(a:file), 1, 0)
call gitgutter#hunk#set_hunks(gitgutter#diff#parse_diff(diff)) call gitgutter#hunk#set_hunks(gitgutter#diff#parse_diff(diff))
let modified_lines = gitgutter#diff#process_hunks(gitgutter#hunk#hunks()) let modified_lines = gitgutter#diff#process_hunks(gitgutter#hunk#hunks())
@@ -156,17 +156,11 @@ function! gitgutter#stage_hunk()
" It doesn't make sense to stage a hunk otherwise. " It doesn't make sense to stage a hunk otherwise.
silent write silent write
" find current hunk
let current_hunk = gitgutter#hunk#current_hunk()
if empty(current_hunk)
return
endif
" construct a diff " construct a diff
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(current_hunk, 1) let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(1, 1)
" apply the diff " apply the diff
call gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file('git apply --cached --unidiff-zero - '), diff_for_hunk) call gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file('git apply --cached --recount --allow-overlap - '), diff_for_hunk)
" refresh gitgutter's view of buffer " refresh gitgutter's view of buffer
silent execute "GitGutter" silent execute "GitGutter"
@@ -179,17 +173,11 @@ function! gitgutter#revert_hunk()
" It doesn't make sense to stage a hunk otherwise. " It doesn't make sense to stage a hunk otherwise.
silent write silent write
" find current hunk
let current_hunk = gitgutter#hunk#current_hunk()
if empty(current_hunk)
return
endif
" construct a diff " construct a diff
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(current_hunk, 1) let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(1, 1)
" apply the diff " apply the diff
call gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file('git apply --reverse --unidiff-zero - '), diff_for_hunk) call gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file('git apply --reverse - '), diff_for_hunk)
" reload file " reload file
silent edit silent edit
@@ -200,14 +188,8 @@ function! gitgutter#preview_hunk()
if gitgutter#utility#is_active() if gitgutter#utility#is_active()
silent write silent write
" find current hunk
let current_hunk = gitgutter#hunk#current_hunk()
if empty(current_hunk)
return
endif
" construct a diff " construct a diff
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(current_hunk, 0) let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(0, 0)
" preview the diff " preview the diff
silent! wincmd P silent! wincmd P

View File

@@ -3,7 +3,7 @@ let s:grep_command = ' | ' . (g:gitgutter_escape_grep ? '\grep' : 'grep') . ' -e
let s:hunk_re = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@' let s:hunk_re = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@'
function! gitgutter#diff#run_diff(realtime, use_external_grep) function! gitgutter#diff#run_diff(realtime, use_external_grep, lines_of_context)
" Wrap compound command in parentheses to make Windows happy. " Wrap compound command in parentheses to make Windows happy.
let cmd = '(git ls-files --error-unmatch ' . gitgutter#utility#shellescape(gitgutter#utility#filename()) . ' && (' let cmd = '(git ls-files --error-unmatch ' . gitgutter#utility#shellescape(gitgutter#utility#filename()) . ' && ('
@@ -11,9 +11,9 @@ function! gitgutter#diff#run_diff(realtime, use_external_grep)
let blob_name = ':' . gitgutter#utility#shellescape(gitgutter#utility#file_relative_to_repo_root()) let blob_name = ':' . gitgutter#utility#shellescape(gitgutter#utility#file_relative_to_repo_root())
let blob_file = tempname() let blob_file = tempname()
let cmd .= 'git show ' . blob_name . ' > ' . blob_file . let cmd .= 'git show ' . blob_name . ' > ' . blob_file .
\ ' && diff -U0 ' . g:gitgutter_diff_args . ' ' . blob_file . ' - ' \ ' && diff -U'.a:lines_of_context.' ' . g:gitgutter_diff_args . ' ' . blob_file . ' - '
else else
let cmd .= 'git diff --no-ext-diff --no-color -U0 ' . g:gitgutter_diff_args . ' ' . gitgutter#utility#shellescape(gitgutter#utility#filename()) let cmd .= 'git diff --no-ext-diff --no-color -U'.a:lines_of_context.' ' . g:gitgutter_diff_args . ' ' . gitgutter#utility#shellescape(gitgutter#utility#filename())
endif endif
if a:use_external_grep && s:grep_available if a:use_external_grep && s:grep_available
@@ -182,22 +182,24 @@ function! gitgutter#diff#process_modified_and_removed(modifications, from_count,
let a:modifications[-1] = [a:to_line + offset - 1, 'modified_removed'] let a:modifications[-1] = [a:to_line + offset - 1, 'modified_removed']
endfunction endfunction
function! gitgutter#diff#generate_diff_for_hunk(hunk, keep_header) function! gitgutter#diff#generate_diff_for_hunk(keep_header, lines_of_context)
let diff = gitgutter#diff#discard_hunks(gitgutter#diff#run_diff(0, 0), a:hunk, a:keep_header) let diff = gitgutter#diff#run_diff(0, 0, a:lines_of_context)
let diff_for_hunk = gitgutter#diff#discard_hunks(diff, a:keep_header)
if !a:keep_header if !a:keep_header
" Discard summary line " Discard summary line
let diff = join(split(diff, '\n')[1:-1], "\n") let diff_for_hunk = join(split(diff_for_hunk, '\n')[1:-1], "\n")
endif endif
return diff return diff_for_hunk
endfunction endfunction
function! gitgutter#diff#discard_hunks(diff, hunk_to_keep, keep_header) " diff - with non-zero lines of context
function! gitgutter#diff#discard_hunks(diff, keep_header)
let modified_diff = [] let modified_diff = []
let keep_line = a:keep_header let keep_line = a:keep_header
for line in split(a:diff, '\n') for line in split(a:diff, '\n')
let hunk_info = gitgutter#diff#parse_hunk(line) let hunk_info = gitgutter#diff#parse_hunk(line)
if len(hunk_info) == 4 " start of new hunk if len(hunk_info) == 4 " start of new hunk
let keep_line = (hunk_info == a:hunk_to_keep) let keep_line = gitgutter#hunk#cursor_in_hunk(hunk_info)
endif endif
if keep_line if keep_line
call add(modified_diff, line) call add(modified_diff, line)

View File

@@ -67,15 +67,9 @@ endfunction
" hunk. " hunk.
function! gitgutter#hunk#current_hunk() function! gitgutter#hunk#current_hunk()
let current_hunk = [] let current_hunk = []
let current_line = line('.')
for hunk in s:hunks for hunk in s:hunks
if current_line == 1 && hunk[2] == 0 if gitgutter#hunk#cursor_in_hunk(hunk)
let current_hunk = hunk
break
endif
if current_line >= hunk[2] && current_line < hunk[2] + (hunk[3] == 0 ? 1 : hunk[3])
let current_hunk = hunk let current_hunk = hunk
break break
endif endif
@@ -86,3 +80,17 @@ function! gitgutter#hunk#current_hunk()
endif endif
endfunction endfunction
function! gitgutter#hunk#cursor_in_hunk(hunk)
let current_line = line('.')
if current_line == 1 && a:hunk[2] == 0
return 1
endif
if current_line >= a:hunk[2] && current_line < a:hunk[2] + (a:hunk[3] == 0 ? 1 : a:hunk[3])
return 1
endif
return 0
endfunction