Skip to content

Commit

Permalink
Finer granularity for staging/reverting hunks.
Browse files Browse the repository at this point in the history
  • Loading branch information
airblade committed Mar 5, 2015
1 parent 4e22ad5 commit c6ed14c
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 24 deletions.
20 changes: 6 additions & 14 deletions autoload/gitgutter.vim
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function! gitgutter#process_buffer(bufnr, realtime)
endif
try
if !a:realtime || gitgutter#utility#has_fresh_changes()
let diff = gitgutter#diff#run_diff(a:realtime || gitgutter#utility#has_unsaved_changes(), 1, 0)
let diff = gitgutter#diff#run_diff(a:realtime || gitgutter#utility#has_unsaved_changes(), 1)
call gitgutter#hunk#set_hunks(gitgutter#diff#parse_diff(diff))
let modified_lines = gitgutter#diff#process_hunks(gitgutter#hunk#hunks())

Expand Down Expand Up @@ -156,11 +156,8 @@ function! gitgutter#stage_hunk()
" It doesn't make sense to stage a hunk otherwise.
silent write

" construct a diff
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(1, 1)

" apply the diff
call gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file('git apply --cached --recount --allow-overlap - '), diff_for_hunk)
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk('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"
Expand All @@ -175,11 +172,8 @@ function! gitgutter#revert_hunk()
" It doesn't make sense to stage a hunk otherwise.
silent write

" construct a diff
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(1, 1)

" apply the diff
call gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file('git apply --reverse - '), diff_for_hunk)
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk('revert')
call gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file('git apply --reverse --unidiff-zero - '), diff_for_hunk)

" reload file
silent edit
Expand All @@ -192,10 +186,8 @@ function! gitgutter#preview_hunk()
if gitgutter#utility#is_active()
silent write

" construct a diff
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(0, 0)
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk('preview')

" preview the diff
silent! wincmd P
if !&previewwindow
execute 'bo ' . &previewheight . ' new'
Expand Down
60 changes: 50 additions & 10 deletions autoload/gitgutter/diff.vim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ endif
let s:hunk_re = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@'


function! gitgutter#diff#run_diff(realtime, use_external_grep, lines_of_context)
function! gitgutter#diff#run_diff(realtime, use_external_grep)
" Wrap compound commands in parentheses to make Windows happy.
let cmd = '('

Expand All @@ -33,7 +33,7 @@ function! gitgutter#diff#run_diff(realtime, use_external_grep, lines_of_context)
execute 'silent write' buff_file
endif

let cmd .= 'git diff --no-ext-diff --no-color -U'.a:lines_of_context.' '.g:gitgutter_diff_args.' -- '
let cmd .= 'git diff --no-ext-diff --no-color -U0 '.g:gitgutter_diff_args.' -- '
if a:realtime
let cmd .= blob_file.' '.buff_file
else
Expand Down Expand Up @@ -214,17 +214,26 @@ function! gitgutter#diff#process_modified_and_removed(modifications, from_count,
let a:modifications[-1] = [a:to_line + offset - 1, 'modified_removed']
endfunction

function! gitgutter#diff#generate_diff_for_hunk(keep_header, lines_of_context)
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
" Discard summary line
let diff_for_hunk = join(split(diff_for_hunk, '\n')[1:-1], "\n")
" Generates a zero-context diff for the current hunk.
"
" type - stage | revert | preview
function! gitgutter#diff#generate_diff_for_hunk(type)
" Although (we assume) diff is up to date, we don't store it anywhere so we
" have to regenerate it now...
let diff = gitgutter#diff#run_diff(0, 0)
let diff_for_hunk = gitgutter#diff#discard_hunks(diff, a:type == 'stage' || a:type == 'revert')

if a:type == 'stage' || a:type == 'revert'
let diff_for_hunk = gitgutter#diff#adjust_hunk_summary(diff_for_hunk, a:type == 'stage')
endif

return diff_for_hunk
endfunction

" diff - with non-zero lines of context
" Returns the diff with all hunks discarded except the current.
"
" diff - the diff to process
" keep_header - truthy to keep the diff header and hunk summary, falsy to discard it
function! gitgutter#diff#discard_hunks(diff, keep_header)
let modified_diff = []
let keep_line = a:keep_header
Expand All @@ -237,5 +246,36 @@ function! gitgutter#diff#discard_hunks(diff, keep_header)
call add(modified_diff, line)
endif
endfor
return join(modified_diff, "\n") . "\n"

if a:keep_header
return join(modified_diff, "\n") . "\n"
else
" Discard hunk summary too.
return join(modified_diff[1:], "\n") . "\n"
endif
endfunction

" Adjust hunk summary (from's / to's line number) to ignore changes above/before this one.
"
" diff_for_hunk - a diff containing only the hunk of interest
" staging - truthy if the hunk is to be staged, falsy if it is to be reverted
"
" TODO: push this down to #discard_hunks?
function! gitgutter#diff#adjust_hunk_summary(diff_for_hunk, staging)
let line_adjustment = gitgutter#hunk#line_adjustment_for_current_hunk()
let adj_diff = []
for line in split(a:diff_for_hunk, '\n')
if match(line, s:hunk_re) != -1
if a:staging
" increment 'to' line number
let line = substitute(line, '+\@<=\(\d\+\)', '\=submatch(1)+line_adjustment', '')
else
" decrement 'from' line number
let line = substitute(line, '-\@<=\(\d\+\)', '\=submatch(1)-line_adjustment', '')
endif
endif
call add(adj_diff, line)
endfor
return join(adj_diff, "\n") . "\n"
endfunction

13 changes: 13 additions & 0 deletions autoload/gitgutter/hunk.vim
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,16 @@ function! gitgutter#hunk#cursor_in_hunk(hunk)
return 0
endfunction

" Returns the number of lines the current hunk is offset from where it would
" be if any changes above it in the file didn't exist.
function! gitgutter#hunk#line_adjustment_for_current_hunk()
let adj = 0
for hunk in s:hunks
if gitgutter#hunk#cursor_in_hunk(hunk)
break
else
let adj += hunk[1] - hunk[3]
endif
endfor
return adj
endfunction

0 comments on commit c6ed14c

Please sign in to comment.