From c6ed14c6622ff8e319fd627c6a1c26668d9ff730 Mon Sep 17 00:00:00 2001 From: Andy Stewart Date: Thu, 5 Mar 2015 10:17:38 +0100 Subject: [PATCH] Finer granularity for staging/reverting hunks. --- autoload/gitgutter.vim | 20 ++++--------- autoload/gitgutter/diff.vim | 60 ++++++++++++++++++++++++++++++------- autoload/gitgutter/hunk.vim | 13 ++++++++ 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/autoload/gitgutter.vim b/autoload/gitgutter.vim index a37a584..83413f6 100644 --- a/autoload/gitgutter.vim +++ b/autoload/gitgutter.vim @@ -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()) @@ -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" @@ -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 @@ -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' diff --git a/autoload/gitgutter/diff.vim b/autoload/gitgutter/diff.vim index 2524c20..01b447b 100644 --- a/autoload/gitgutter/diff.vim +++ b/autoload/gitgutter/diff.vim @@ -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 = '(' @@ -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 @@ -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 @@ -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 + diff --git a/autoload/gitgutter/hunk.vim b/autoload/gitgutter/hunk.vim index 94a05ac..e50516e 100644 --- a/autoload/gitgutter/hunk.vim +++ b/autoload/gitgutter/hunk.vim @@ -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