patchreview.vim
868 lines
| 26.7 KiB
| text/x-vim
|
VimLexer
Manpreet Singh
|
r10545 | " VIM plugin for doing single, multi-patch or diff code reviews {{{ | ||
" Home: http://www.vim.org/scripts/script.php?script_id=1563 | ||||
Manpreet Singh
|
r2350 | |||
Manpreet Singh
|
r10550 | " Version : 0.2.2 "{{{ | ||
Manpreet Singh
|
r10545 | " Author : Manpreet Singh < junkblocker@yahoo.com > | ||
" Copyright : 2006-2010 by Manpreet Singh | ||||
Manpreet Singh
|
r2350 | " License : This file is placed in the public domain. | ||
Manpreet Singh
|
r10545 | " No warranties express or implied. Use at your own risk. | ||
Manpreet Singh
|
r2350 | " | ||
Manpreet Singh
|
r10545 | " Changelog : | ||
" | ||||
Manpreet Singh
|
r10550 | " 0.2.2 - Security fixes by removing custom tempfile creation | ||
" - Removed need for DiffReviewCleanup/PatchReviewCleanup | ||||
" - Better command execution error detection and display | ||||
" - Improved diff view and folding by ignoring modelines | ||||
" - Improved tab labels display | ||||
" | ||||
Manpreet Singh
|
r10545 | " 0.2.1 - Minor temp directory autodetection logic and cleanup | ||
" | ||||
timeless@mozdev.org
|
r17496 | " 0.2 - Removed the need for filterdiff by implementing it in pure vim script | ||
Manpreet Singh
|
r10545 | " - Added DiffReview command for reverse (changed repository to | ||
" pristine state) reviews. | ||||
" (PatchReview does pristine repository to patch review) | ||||
" - DiffReview does automatic detection and generation of diffs for | ||||
" various Source Control systems | ||||
" - Skip load if VIM 7.0 or higher unavailable | ||||
" | ||||
" 0.1 - First released | ||||
Manpreet Singh
|
r2350 | "}}} | ||
Manpreet Singh
|
r10545 | |||
Manpreet Singh
|
r2350 | " Documentation: "{{{ | ||
" =========================================================================== | ||||
Manpreet Singh
|
r10545 | " This plugin allows single or multiple, patch or diff based code reviews to | ||
" be easily done in VIM. VIM has :diffpatch command to do single file reviews | ||||
" but a) can not handle patch files containing multiple patches or b) do | ||||
" automated diff generation for various version control systems. This plugin | ||||
" attempts to provide those functionalities. It opens each changed / added or | ||||
" removed file diff in new tabs. | ||||
" | ||||
" Installing: | ||||
Manpreet Singh
|
r2350 | " | ||
Manpreet Singh
|
r10550 | " For a quick start, unzip patchreview.zip into your ~/.vim directory and | ||
" restart Vim. | ||||
" | ||||
" Details: | ||||
Manpreet Singh
|
r10545 | " | ||
" Requirements: | ||||
" | ||||
" 1) VIM 7.0 or higher built with +diff option. | ||||
Manpreet Singh
|
r2350 | " | ||
Manpreet Singh
|
r10545 | " 2) A gnu compatible patch command installed. This is the standard patch | ||
" command on Linux, Mac OS X, *BSD, Cygwin or /usr/bin/gpatch on newer | ||||
" Solaris. | ||||
Manpreet Singh
|
r2350 | " | ||
Manpreet Singh
|
r10545 | " 3) Optional (but recommended for speed) | ||
Manpreet Singh
|
r2350 | " | ||
Manpreet Singh
|
r10545 | " Install patchutils ( http://cyberelk.net/tim/patchutils/ ) for your | ||
Mads Kiilerich
|
r17424 | " OS. For windows it is available from Cygwin | ||
Manpreet Singh
|
r10545 | " | ||
" http://www.cygwin.com | ||||
" | ||||
" or GnuWin32 | ||||
" | ||||
" http://gnuwin32.sourceforge.net/ | ||||
" | ||||
" Install: | ||||
Manpreet Singh
|
r2350 | " | ||
Manpreet Singh
|
r10545 | " 1) Extract the zip in your $HOME/.vim or $VIM/vimfiles directory and | ||
" restart vim. The directory location relevant to your platform can be | ||||
" seen by running :help add-global-plugin in vim. | ||||
Manpreet Singh
|
r2350 | " | ||
Manpreet Singh
|
r10545 | " 2) Restart vim. | ||
Manpreet Singh
|
r2350 | " | ||
Manpreet Singh
|
r10545 | " Configuration: | ||
Manpreet Singh
|
r2350 | " | ||
Manpreet Singh
|
r10545 | " Optionally, specify the locations to these filterdiff and patch commands | ||
" and location of a temporary directory to use in your .vimrc. | ||||
" | ||||
" let g:patchreview_patch = '/path/to/gnu/patch' | ||||
Manpreet Singh
|
r2350 | " | ||
Manpreet Singh
|
r10545 | " " If you are using filterdiff | ||
" let g:patchreview_filterdiff = '/path/to/filterdiff' | ||||
Manpreet Singh
|
r2350 | " | ||
" | ||||
Manpreet Singh
|
r10545 | " Usage: | ||
Manpreet Singh
|
r2350 | " | ||
Manpreet Singh
|
r10545 | " Please see :help patchreview or :help diffreview for details. | ||
Manpreet Singh
|
r2350 | " | ||
""}}} | ||||
Manpreet Singh
|
r10545 | " Enabled only during development | ||
Manpreet Singh
|
r2350 | " unlet! g:loaded_patchreview " DEBUG | ||
Manpreet Singh
|
r10545 | " unlet! g:patchreview_patch " DEBUG | ||
Manpreet Singh
|
r2350 | " unlet! g:patchreview_filterdiff " DEBUG | ||
Manpreet Singh
|
r10545 | " let g:patchreview_patch = 'patch' " DEBUG | ||
Manpreet Singh
|
r2350 | |||
Manpreet Singh
|
r10545 | if v:version < 700 | ||
finish | ||||
endif | ||||
if ! has('diff') | ||||
call confirm('patchreview.vim plugin needs (G)VIM built with +diff support to work.') | ||||
finish | ||||
endif | ||||
" load only once | ||||
if (! exists('g:patchreview_debug') && exists('g:loaded_patchreview')) || &compatible | ||||
Manpreet Singh
|
r2350 | finish | ||
endif | ||||
Manpreet Singh
|
r10550 | let g:loaded_patchreview="0.2.2" | ||
Manpreet Singh
|
r10545 | |||
let s:msgbufname = '-PatchReviewMessages-' | ||||
function! <SID>Debug(str) "{{{ | ||||
if exists('g:patchreview_debug') | ||||
Pecho 'DEBUG: ' . a:str | ||||
endif | ||||
endfunction | ||||
command! -nargs=+ -complete=expression Debug call s:Debug(<args>) | ||||
Manpreet Singh
|
r2350 | "}}} | ||
Manpreet Singh
|
r10545 | function! <SID>PR_wipeMsgBuf() "{{{ | ||
let winnum = bufwinnr(s:msgbufname) | ||||
if winnum != -1 " If the window is already open, jump to it | ||||
let cur_winnr = winnr() | ||||
if winnr() != winnum | ||||
exe winnum . 'wincmd w' | ||||
Manpreet Singh
|
r2350 | exe 'bw' | ||
Manpreet Singh
|
r10545 | exe cur_winnr . 'wincmd w' | ||
Manpreet Singh
|
r2350 | endif | ||
endif | ||||
endfunction | ||||
"}}} | ||||
Manpreet Singh
|
r10545 | function! <SID>Pecho(...) "{{{ | ||
" Usage: Pecho(msg, [return_to_original_window_flag]) | ||||
Manpreet Singh
|
r2350 | " default return_to_original_window_flag = 0 | ||
" | ||||
Manpreet Singh
|
r10545 | let cur_winnr = winnr() | ||
let winnum = bufwinnr(s:msgbufname) | ||||
if winnum != -1 " If the window is already open, jump to it | ||||
if winnr() != winnum | ||||
exe winnum . 'wincmd w' | ||||
Manpreet Singh
|
r2350 | endif | ||
else | ||||
Manpreet Singh
|
r10545 | let bufnum = bufnr(s:msgbufname) | ||
if bufnum == -1 | ||||
let wcmd = s:msgbufname | ||||
Manpreet Singh
|
r2350 | else | ||
Manpreet Singh
|
r10545 | let wcmd = '+buffer' . bufnum | ||
Manpreet Singh
|
r2350 | endif | ||
Manpreet Singh
|
r10545 | exe 'silent! botright 5split ' . wcmd | ||
Manpreet Singh
|
r2350 | endif | ||
setlocal modifiable | ||||
setlocal buftype=nofile | ||||
setlocal bufhidden=delete | ||||
setlocal noswapfile | ||||
setlocal nowrap | ||||
setlocal nobuflisted | ||||
if a:0 != 0 | ||||
silent! $put =a:1 | ||||
endif | ||||
exe ':$' | ||||
setlocal nomodifiable | ||||
if a:0 > 1 && a:2 | ||||
Manpreet Singh
|
r10545 | exe cur_winnr . 'wincmd w' | ||
Manpreet Singh
|
r2350 | endif | ||
endfunction | ||||
Manpreet Singh
|
r10545 | |||
command! -nargs=+ -complete=expression Pecho call s:Pecho(<args>) | ||||
Manpreet Singh
|
r2350 | "}}} | ||
Manpreet Singh
|
r10545 | function! <SID>PR_checkBinary(BinaryName) "{{{ | ||
Manpreet Singh
|
r2350 | " Verify that BinaryName is specified or available | ||
if ! exists('g:patchreview_' . a:BinaryName) | ||||
if executable(a:BinaryName) | ||||
let g:patchreview_{a:BinaryName} = a:BinaryName | ||||
return 1 | ||||
else | ||||
Manpreet Singh
|
r10545 | Pecho 'g:patchreview_' . a:BinaryName . ' is not defined and ' . a:BinaryName . ' command could not be found on path.' | ||
Pecho 'Please define it in your .vimrc.' | ||||
Manpreet Singh
|
r2350 | return 0 | ||
endif | ||||
elseif ! executable(g:patchreview_{a:BinaryName}) | ||||
Manpreet Singh
|
r10545 | Pecho 'Specified g:patchreview_' . a:BinaryName . ' [' . g:patchreview_{a:BinaryName} . '] is not executable.' | ||
Manpreet Singh
|
r2350 | return 0 | ||
else | ||||
return 1 | ||||
endif | ||||
endfunction | ||||
"}}} | ||||
Manpreet Singh
|
r10545 | function! <SID>ExtractDiffsNative(...) "{{{ | ||
" Sets g:patches = {'reason':'', 'patch':[ | ||||
" { | ||||
" 'filename': filepath | ||||
" 'type' : '+' | '-' | '!' | ||||
" 'content' : patch text for this file | ||||
" }, | ||||
" ... | ||||
" ]} | ||||
let g:patches = {'reason' : '', 'patch' : []} | ||||
" TODO : User pointers into lines list rather then use collect | ||||
if a:0 == 0 | ||||
let g:patches['reason'] = "ExtractDiffsNative expects at least a patchfile argument" | ||||
return | ||||
endif | ||||
let patchfile = expand(a:1, ':p') | ||||
if a:0 > 1 | ||||
let patch = a:2 | ||||
endif | ||||
if ! filereadable(patchfile) | ||||
let g:patches['reason'] = "File " . patchfile . " is not readable" | ||||
Manpreet Singh
|
r2350 | return | ||
endif | ||||
Manpreet Singh
|
r10545 | unlet! filterdiffcmd | ||
let filterdiffcmd = '' . g:patchreview_filterdiff . ' --list -s ' . patchfile | ||||
let fileslist = split(system(filterdiffcmd), '[\r\n]') | ||||
for filewithchangetype in fileslist | ||||
if filewithchangetype !~ '^[!+-] ' | ||||
Pecho '*** Skipping review generation due to unknown change for [' . filewithchangetype . ']' | ||||
continue | ||||
endif | ||||
unlet! this_patch | ||||
let this_patch = {} | ||||
unlet! relpath | ||||
let relpath = substitute(filewithchangetype, '^. ', '', '') | ||||
let this_patch['filename'] = relpath | ||||
if filewithchangetype =~ '^! ' | ||||
let this_patch['type'] = '!' | ||||
elseif filewithchangetype =~ '^+ ' | ||||
let this_patch['type'] = '+' | ||||
elseif filewithchangetype =~ '^- ' | ||||
let this_patch['type'] = '-' | ||||
endif | ||||
unlet! filterdiffcmd | ||||
let filterdiffcmd = '' . g:patchreview_filterdiff . ' -i ' . relpath . ' ' . patchfile | ||||
let this_patch['content'] = split(system(filterdiffcmd), '[\n\r]') | ||||
let g:patches['patch'] += [this_patch] | ||||
Debug "Patch collected for " . relpath | ||||
endfor | ||||
endfunction | ||||
Manpreet Singh
|
r2350 | "}}} | ||
Manpreet Singh
|
r10545 | function! <SID>ExtractDiffsPureVim(...) "{{{ | ||
" Sets g:patches = {'reason':'', 'patch':[ | ||||
" { | ||||
" 'filename': filepath | ||||
" 'type' : '+' | '-' | '!' | ||||
" 'content' : patch text for this file | ||||
" }, | ||||
" ... | ||||
" ]} | ||||
let g:patches = {'reason' : '', 'patch' : []} | ||||
" TODO : User pointers into lines list rather then use collect | ||||
Manpreet Singh
|
r2350 | if a:0 == 0 | ||
Manpreet Singh
|
r10545 | let g:patches['reason'] = "ExtractDiffsPureVim expects at least a patchfile argument" | ||
return | ||||
endif | ||||
let patchfile = expand(a:1, ':p') | ||||
if a:0 > 1 | ||||
let patch = a:2 | ||||
endif | ||||
if ! filereadable(patchfile) | ||||
let g:patches['reason'] = "File " . patchfile . " is not readable" | ||||
Manpreet Singh
|
r2350 | return | ||
endif | ||||
Manpreet Singh
|
r10545 | call s:PR_wipeMsgBuf() | ||
let collect = [] | ||||
let linum = 0 | ||||
let lines = readfile(patchfile) | ||||
let linescount = len(lines) | ||||
State 'START' | ||||
while linum < linescount | ||||
let line = lines[linum] | ||||
let linum += 1 | ||||
if State() == 'START' | ||||
let mat = matchlist(line, '^--- \([^\t]\+\).*$') | ||||
if ! empty(mat) && mat[1] != '' | ||||
State 'MAYBE_UNIFIED_DIFF' | ||||
let p_first_file = mat[1] | ||||
let collect = [line] | ||||
Debug line . State() | ||||
continue | ||||
endif | ||||
let mat = matchlist(line, '^\*\*\* \([^\t]\+\).*$') | ||||
if ! empty(mat) && mat[1] != '' | ||||
State 'MAYBE_CONTEXT_DIFF' | ||||
let p_first_file = mat[1] | ||||
let collect = [line] | ||||
Debug line . State() | ||||
continue | ||||
endif | ||||
continue | ||||
elseif State() == 'MAYBE_CONTEXT_DIFF' | ||||
let mat = matchlist(line, '^--- \([^\t]\+\).*$') | ||||
if empty(mat) || mat[1] == '' | ||||
State 'START' | ||||
let linum -= 1 | ||||
continue | ||||
Debug 'Back to square one ' . line() | ||||
endif | ||||
let p_second_file = mat[1] | ||||
if p_first_file == '/dev/null' | ||||
if p_second_file == '/dev/null' | ||||
let g:patches['reason'] = "Malformed diff found at line " . linum | ||||
return | ||||
endif | ||||
let p_type = '+' | ||||
let filepath = p_second_file | ||||
else | ||||
if p_second_file == '/dev/null' | ||||
let p_type = '-' | ||||
let filepath = p_first_file | ||||
else | ||||
let p_type = '!' | ||||
let filepath = p_first_file | ||||
endif | ||||
endif | ||||
State 'EXPECT_15_STARS' | ||||
let collect += [line] | ||||
Debug line . State() | ||||
elseif State() == 'EXPECT_15_STARS' | ||||
if line !~ '^*\{15}$' | ||||
State 'START' | ||||
let linum -= 1 | ||||
Debug line . State() | ||||
continue | ||||
endif | ||||
State 'EXPECT_CONTEXT_CHUNK_HEADER_1' | ||||
let collect += [line] | ||||
Debug line . State() | ||||
elseif State() == 'EXPECT_CONTEXT_CHUNK_HEADER_1' | ||||
let mat = matchlist(line, '^\*\*\* \(\d\+,\)\?\(\d\+\) \*\*\*\*$') | ||||
if empty(mat) || mat[1] == '' | ||||
State 'START' | ||||
let linum -= 1 | ||||
Debug line . State() | ||||
continue | ||||
endif | ||||
let collect += [line] | ||||
State 'SKIP_CONTEXT_STUFF_1' | ||||
Debug line . State() | ||||
continue | ||||
elseif State() == 'SKIP_CONTEXT_STUFF_1' | ||||
if line !~ '^[ !+].*$' | ||||
let mat = matchlist(line, '^--- \(\d\+\),\(\d\+\) ----$') | ||||
if ! empty(mat) && mat[1] != '' && mat[2] != '' | ||||
let goal_count = mat[2] - mat[1] + 1 | ||||
let c_count = 0 | ||||
State 'READ_CONTEXT_CHUNK' | ||||
let collect += [line] | ||||
Debug line . State() . " Goal count set to " . goal_count | ||||
continue | ||||
endif | ||||
State 'START' | ||||
let linum -= 1 | ||||
Debug line . State() | ||||
continue | ||||
endif | ||||
let collect += [line] | ||||
continue | ||||
elseif State() == 'READ_CONTEXT_CHUNK' | ||||
let c_count += 1 | ||||
if c_count == goal_count | ||||
let collect += [line] | ||||
State 'BACKSLASH_OR_CRANGE_EOF' | ||||
continue | ||||
else " goal not met yet | ||||
let mat = matchlist(line, '^\([\\!+ ]\).*$') | ||||
if empty(mat) || mat[1] == '' | ||||
let linum -= 1 | ||||
State 'START' | ||||
Debug line . State() | ||||
continue | ||||
endif | ||||
let collect += [line] | ||||
continue | ||||
endif | ||||
elseif State() == 'BACKSLASH_OR_CRANGE_EOF' | ||||
if line =~ '^\\ No newline.*$' " XXX: Can we go to another chunk from here?? | ||||
let collect += [line] | ||||
let this_patch = {} | ||||
let this_patch['filename'] = filepath | ||||
let this_patch['type'] = p_type | ||||
let this_patch['content'] = collect | ||||
let g:patches['patch'] += [this_patch] | ||||
Debug "Patch collected for " . filepath | ||||
State 'START' | ||||
continue | ||||
endif | ||||
if line =~ '^\*\{15}$' | ||||
let collect += [line] | ||||
State 'EXPECT_CONTEXT_CHUNK_HEADER_1' | ||||
Debug line . State() | ||||
continue | ||||
endif | ||||
let this_patch = {} | ||||
let this_patch['filename'] = filepath | ||||
let this_patch['type'] = p_type | ||||
let this_patch['content'] = collect | ||||
let g:patches['patch'] += [this_patch] | ||||
let linum -= 1 | ||||
State 'START' | ||||
Debug "Patch collected for " . filepath | ||||
Debug line . State() | ||||
continue | ||||
elseif State() == 'MAYBE_UNIFIED_DIFF' | ||||
let mat = matchlist(line, '^+++ \([^\t]\+\).*$') | ||||
if empty(mat) || mat[1] == '' | ||||
State 'START' | ||||
let linum -= 1 | ||||
Debug line . State() | ||||
continue | ||||
endif | ||||
let p_second_file = mat[1] | ||||
if p_first_file == '/dev/null' | ||||
if p_second_file == '/dev/null' | ||||
let g:patches['reason'] = "Malformed diff found at line " . linum | ||||
return | ||||
endif | ||||
let p_type = '+' | ||||
let filepath = p_second_file | ||||
else | ||||
if p_second_file == '/dev/null' | ||||
let p_type = '-' | ||||
let filepath = p_first_file | ||||
else | ||||
let p_type = '!' | ||||
let filepath = p_first_file | ||||
endif | ||||
endif | ||||
State 'EXPECT_UNIFIED_RANGE_CHUNK' | ||||
let collect += [line] | ||||
Debug line . State() | ||||
continue | ||||
elseif State() == 'EXPECT_UNIFIED_RANGE_CHUNK' | ||||
let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$') | ||||
if ! empty(mat) | ||||
let old_goal_count = mat[2] | ||||
let new_goal_count = mat[4] | ||||
let o_count = 0 | ||||
let n_count = 0 | ||||
Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count | ||||
State 'READ_UNIFIED_CHUNK' | ||||
let collect += [line] | ||||
Debug line . State() | ||||
continue | ||||
endif | ||||
State 'START' | ||||
Debug line . State() | ||||
continue | ||||
elseif State() == 'READ_UNIFIED_CHUNK' | ||||
if o_count == old_goal_count && n_count == new_goal_count | ||||
if line =~ '^\\.*$' " XXX: Can we go to another chunk from here?? | ||||
let collect += [line] | ||||
let this_patch = {} | ||||
let this_patch['filename'] = filepath | ||||
let this_patch['type'] = p_type | ||||
let this_patch['content'] = collect | ||||
let g:patches['patch'] += [this_patch] | ||||
Debug "Patch collected for " . filepath | ||||
State 'START' | ||||
continue | ||||
endif | ||||
let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$') | ||||
if ! empty(mat) | ||||
let old_goal_count = mat[2] | ||||
let new_goal_count = mat[4] | ||||
let o_count = 0 | ||||
let n_count = 0 | ||||
Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count | ||||
let collect += [line] | ||||
Debug line . State() | ||||
continue | ||||
endif | ||||
let this_patch = {} | ||||
let this_patch['filename'] = filepath | ||||
let this_patch['type'] = p_type | ||||
let this_patch['content'] = collect | ||||
let g:patches['patch'] += [this_patch] | ||||
Debug "Patch collected for " . filepath | ||||
let linum -= 1 | ||||
State 'START' | ||||
Debug line . State() | ||||
continue | ||||
else " goal not met yet | ||||
let mat = matchlist(line, '^\([\\+ -]\).*$') | ||||
if empty(mat) || mat[1] == '' | ||||
let linum -= 1 | ||||
State 'START' | ||||
continue | ||||
endif | ||||
let chr = mat[1] | ||||
if chr == '+' | ||||
let n_count += 1 | ||||
endif | ||||
if chr == ' ' | ||||
let o_count += 1 | ||||
let n_count += 1 | ||||
endif | ||||
if chr == '-' | ||||
let o_count += 1 | ||||
endif | ||||
let collect += [line] | ||||
Debug line . State() | ||||
continue | ||||
endif | ||||
else | ||||
let g:patches['reason'] = "Internal error: Do not use the plugin anymore and if possible please send the diff or patch file you tried it with to Manpreet Singh <junkblocker@yahoo.com>" | ||||
Manpreet Singh
|
r2350 | return | ||
endif | ||||
Manpreet Singh
|
r10545 | endwhile | ||
"Pecho State() | ||||
if (State() == 'READ_CONTEXT_CHUNK' && c_count == goal_count) || (State() == 'READ_UNIFIED_CHUNK' && n_count == new_goal_count && o_count == old_goal_count) | ||||
let this_patch = {} | ||||
let this_patch['filename'] = filepath | ||||
let this_patch['type'] = p_type | ||||
let this_patch['content'] = collect | ||||
let g:patches['patch'] += [this_patch] | ||||
Debug "Patch collected for " . filepath | ||||
endif | ||||
return | ||||
endfunction | ||||
"}}} | ||||
function! State(...) " For easy manipulation of diff extraction state "{{{ | ||||
if a:0 != 0 | ||||
let s:STATE = a:1 | ||||
else | ||||
if ! exists('s:STATE') | ||||
let s:STATE = 'START' | ||||
Manpreet Singh
|
r2350 | endif | ||
Manpreet Singh
|
r10545 | return s:STATE | ||
Manpreet Singh
|
r2350 | endif | ||
Manpreet Singh
|
r10545 | endfunction | ||
com! -nargs=+ -complete=expression State call State(<args>) | ||||
Manpreet Singh
|
r2350 | "}}} | ||
Manpreet Singh
|
r10545 | function! <SID>PatchReview(...) "{{{ | ||
let s:save_shortmess = &shortmess | ||||
let s:save_aw = &autowrite | ||||
let s:save_awa = &autowriteall | ||||
set shortmess=aW | ||||
call s:PR_wipeMsgBuf() | ||||
let s:reviewmode = 'patch' | ||||
call s:_GenericReview(a:000) | ||||
let &autowriteall = s:save_awa | ||||
let &autowrite = s:save_aw | ||||
let &shortmess = s:save_shortmess | ||||
endfunction | ||||
"}}} | ||||
function! <SID>_GenericReview(argslist) "{{{ | ||||
" diff mode: | ||||
" arg1 = patchfile | ||||
" arg2 = strip count | ||||
" patch mode: | ||||
" arg1 = patchfile | ||||
" arg2 = strip count | ||||
" arg3 = directory | ||||
" VIM 7+ required | ||||
if version < 700 | ||||
Pecho 'This plugin needs VIM 7 or higher' | ||||
Manpreet Singh
|
r2350 | return | ||
endif | ||||
Manpreet Singh
|
r10545 | " +diff required | ||
if ! has('diff') | ||||
Pecho 'This plugin needs VIM built with +diff feature.' | ||||
return | ||||
endif | ||||
if s:reviewmode == 'diff' | ||||
let patch_R_option = ' -t -R ' | ||||
elseif s:reviewmode == 'patch' | ||||
let patch_R_option = '' | ||||
else | ||||
Pecho 'Fatal internal error in patchreview.vim plugin' | ||||
return | ||||
endif | ||||
" Check passed arguments | ||||
if len(a:argslist) == 0 | ||||
Pecho 'PatchReview command needs at least one argument specifying a patchfile path.' | ||||
Manpreet Singh
|
r2350 | return | ||
endif | ||||
Manpreet Singh
|
r10545 | let StripCount = 0 | ||
if len(a:argslist) >= 1 && ((s:reviewmode == 'patch' && len(a:argslist) <= 3) || (s:reviewmode == 'diff' && len(a:argslist) == 2)) | ||||
let PatchFilePath = expand(a:argslist[0], ':p') | ||||
if ! filereadable(PatchFilePath) | ||||
Pecho 'File [' . PatchFilePath . '] is not accessible.' | ||||
return | ||||
endif | ||||
if len(a:argslist) >= 2 && s:reviewmode == 'patch' | ||||
let s:SrcDirectory = expand(a:argslist[1], ':p') | ||||
if ! isdirectory(s:SrcDirectory) | ||||
Pecho '[' . s:SrcDirectory . '] is not a directory' | ||||
return | ||||
endif | ||||
try | ||||
" Command line has already escaped the path | ||||
exe 'cd ' . s:SrcDirectory | ||||
catch /^.*E344.*/ | ||||
Pecho 'Could not change to directory [' . s:SrcDirectory . ']' | ||||
return | ||||
endtry | ||||
endif | ||||
if s:reviewmode == 'diff' | ||||
" passed in by default | ||||
let StripCount = eval(a:argslist[1]) | ||||
elseif s:reviewmode == 'patch' | ||||
let StripCount = 1 | ||||
" optional strip count | ||||
if len(a:argslist) == 3 | ||||
let StripCount = eval(a:argslist[2]) | ||||
endif | ||||
endif | ||||
else | ||||
if s:reviewmode == 'patch' | ||||
Pecho 'PatchReview command needs at most three arguments: patchfile path, optional source directory path and optional strip count.' | ||||
elseif s:reviewmode == 'diff' | ||||
Pecho 'DiffReview command accepts no arguments.' | ||||
endif | ||||
return | ||||
endif | ||||
Manpreet Singh
|
r2350 | |||
Manpreet Singh
|
r10545 | " Verify that patch command and temporary directory are available or specified | ||
if ! s:PR_checkBinary('patch') | ||||
return | ||||
endif | ||||
" Requirements met, now execute | ||||
let PatchFilePath = fnamemodify(PatchFilePath, ':p') | ||||
if s:reviewmode == 'patch' | ||||
Pecho 'Patch file : ' . PatchFilePath | ||||
endif | ||||
Pecho 'Source directory: ' . getcwd() | ||||
Pecho '------------------' | ||||
if s:PR_checkBinary('filterdiff') | ||||
Debug "Using filterdiff" | ||||
call s:ExtractDiffsNative(PatchFilePath) | ||||
else | ||||
Debug "Using own diff extraction (slower)" | ||||
call s:ExtractDiffsPureVim(PatchFilePath) | ||||
endif | ||||
for patch in g:patches['patch'] | ||||
if patch.type !~ '^[!+-]$' | ||||
Pecho '*** Skipping review generation due to unknown change [' . patch.type . ']', 1 | ||||
Manpreet Singh
|
r2350 | continue | ||
endif | ||||
Manpreet Singh
|
r10545 | unlet! relpath | ||
let relpath = patch.filename | ||||
" XXX: svn diff and hg diff produce different kind of outputs, one requires | ||||
" XXX: stripping but the other doesn't. We need to take care of that | ||||
let stripmore = StripCount | ||||
let StrippedRelativeFilePath = relpath | ||||
while stripmore > 0 | ||||
" strip one | ||||
let StrippedRelativeFilePath = substitute(StrippedRelativeFilePath, '^[^\\\/]\+[^\\\/]*[\\\/]' , '' , '') | ||||
let stripmore -= 1 | ||||
endwhile | ||||
if patch.type == '!' | ||||
if s:reviewmode == 'patch' | ||||
let msgtype = 'Patch modifies file: ' | ||||
elseif s:reviewmode == 'diff' | ||||
let msgtype = 'File has changes: ' | ||||
endif | ||||
elseif patch.type == '+' | ||||
if s:reviewmode == 'patch' | ||||
let msgtype = 'Patch adds file : ' | ||||
elseif s:reviewmode == 'diff' | ||||
let msgtype = 'New file : ' | ||||
endif | ||||
elseif patch.type == '-' | ||||
if s:reviewmode == 'patch' | ||||
let msgtype = 'Patch removes file : ' | ||||
elseif s:reviewmode == 'diff' | ||||
let msgtype = 'Removed file : ' | ||||
endif | ||||
Manpreet Singh
|
r2350 | endif | ||
Manpreet Singh
|
r10545 | let bufnum = bufnr(relpath) | ||
if buflisted(bufnum) && getbufvar(bufnum, '&mod') | ||||
Pecho 'Old buffer for file [' . relpath . '] exists in modified state. Skipping review.', 1 | ||||
Manpreet Singh
|
r2350 | continue | ||
endif | ||||
Manpreet Singh
|
r10550 | let tmpname = tempname() | ||
Manpreet Singh
|
r2350 | |||
Manpreet Singh
|
r10545 | " write patch for patch.filename into tmpname | ||
call writefile(patch.content, tmpname) | ||||
if patch.type == '+' && s:reviewmode == 'patch' | ||||
let inputfile = '' | ||||
let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"' | ||||
elseif patch.type == '+' && s:reviewmode == 'diff' | ||||
let inputfile = '' | ||||
unlet! patchcmd | ||||
Manpreet Singh
|
r2350 | else | ||
Manpreet Singh
|
r10545 | let inputfile = expand(StrippedRelativeFilePath, ':p') | ||
let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"' | ||||
Manpreet Singh
|
r2350 | endif | ||
Manpreet Singh
|
r10545 | if exists('patchcmd') | ||
let v:errmsg = '' | ||||
Debug patchcmd | ||||
silent exe patchcmd | ||||
if v:errmsg != '' || v:shell_error | ||||
Pecho 'ERROR: Could not execute patch command.' | ||||
Pecho 'ERROR: ' . patchcmd | ||||
Pecho 'ERROR: ' . v:errmsg | ||||
Pecho 'ERROR: Diff skipped.' | ||||
continue | ||||
endif | ||||
endif | ||||
Manpreet Singh
|
r10550 | call delete(tmpname) | ||
Manpreet Singh
|
r2350 | let s:origtabpagenr = tabpagenr() | ||
Manpreet Singh
|
r10545 | silent! exe 'tabedit ' . StrippedRelativeFilePath | ||
if exists('patchcmd') | ||||
Manpreet Singh
|
r10550 | " modelines in loaded files mess with diff comparision | ||
let s:keep_modeline=&modeline | ||||
let &modeline=0 | ||||
Manpreet Singh
|
r10545 | silent! exe 'vert diffsplit ' . tmpname . '.file' | ||
Manpreet Singh
|
r10550 | setlocal buftype=nofile | ||
setlocal noswapfile | ||||
setlocal syntax=none | ||||
setlocal bufhidden=delete | ||||
setlocal nobuflisted | ||||
setlocal modifiable | ||||
setlocal nowrap | ||||
" Remove buffer name | ||||
silent! 0f | ||||
" Switch to original to get a nice tab title | ||||
silent! wincmd p | ||||
let &modeline=s:keep_modeline | ||||
Manpreet Singh
|
r2350 | else | ||
Manpreet Singh
|
r10545 | silent! exe 'vnew' | ||
endif | ||||
if filereadable(tmpname . '.file.rej') | ||||
silent! exe 'topleft 5split ' . tmpname . '.file.rej' | ||||
Pecho msgtype . '*** REJECTED *** ' . relpath, 1 | ||||
else | ||||
Pecho msgtype . ' ' . relpath, 1 | ||||
Manpreet Singh
|
r2350 | endif | ||
silent! exe 'tabn ' . s:origtabpagenr | ||||
endfor | ||||
Manpreet Singh
|
r10545 | Pecho '-----' | ||
Pecho 'Done.' | ||||
Manpreet Singh
|
r2350 | endfunction | ||
"}}} | ||||
Manpreet Singh
|
r10545 | function! <SID>DiffReview(...) "{{{ | ||
let s:save_shortmess = &shortmess | ||||
set shortmess=aW | ||||
call s:PR_wipeMsgBuf() | ||||
let vcsdict = { | ||||
\'Mercurial' : {'dir' : '.hg', 'binary' : 'hg', 'diffargs' : 'diff' , 'strip' : 1}, | ||||
\'Bazaar-NG' : {'dir' : '.bzr', 'binary' : 'bzr', 'diffargs' : 'diff' , 'strip' : 0}, | ||||
\'monotone' : {'dir' : '_MTN', 'binary' : 'mtn', 'diffargs' : 'diff --unified', 'strip' : 0}, | ||||
\'Subversion' : {'dir' : '.svn', 'binary' : 'svn', 'diffargs' : 'diff' , 'strip' : 0}, | ||||
\'cvs' : {'dir' : 'CVS', 'binary' : 'cvs', 'diffargs' : '-q diff -u' , 'strip' : 0}, | ||||
\} | ||||
unlet! s:theDiffCmd | ||||
unlet! l:vcs | ||||
if ! exists('g:patchreview_diffcmd') | ||||
for key in keys(vcsdict) | ||||
if isdirectory(vcsdict[key]['dir']) | ||||
if ! s:PR_checkBinary(vcsdict[key]['binary']) | ||||
Pecho 'Current directory looks like a ' . vcsdict[key] . ' repository but ' . vcsdist[key]['binary'] . ' command was not found on path.' | ||||
let &shortmess = s:save_shortmess | ||||
return | ||||
else | ||||
let s:theDiffCmd = vcsdict[key]['binary'] . ' ' . vcsdict[key]['diffargs'] | ||||
let strip = vcsdict[key]['strip'] | ||||
Pecho 'Using [' . s:theDiffCmd . '] to generate diffs for this ' . key . ' review.' | ||||
let &shortmess = s:save_shortmess | ||||
let l:vcs = vcsdict[key]['binary'] | ||||
break | ||||
endif | ||||
else | ||||
continue | ||||
endif | ||||
endfor | ||||
else | ||||
let s:theDiffCmd = g:patchreview_diffcmd | ||||
let strip = 0 | ||||
endif | ||||
if ! exists('s:theDiffCmd') | ||||
Pecho 'Please define g:patchreview_diffcmd and make sure you are in a VCS controlled top directory.' | ||||
let &shortmess = s:save_shortmess | ||||
return | ||||
endif | ||||
Manpreet Singh
|
r10550 | let outfile = tempname() | ||
let cmd = s:theDiffCmd . ' > "' . outfile . '"' | ||||
Manpreet Singh
|
r10545 | let v:errmsg = '' | ||
Manpreet Singh
|
r10550 | let cout = system(cmd) | ||
Manpreet Singh
|
r10545 | if v:errmsg == '' && exists('l:vcs') && l:vcs == 'cvs' && v:shell_error == 1 | ||
" Ignoring CVS non-error | ||||
elseif v:errmsg != '' || v:shell_error | ||||
Manpreet Singh
|
r10550 | Pecho v:errmsg | ||
Manpreet Singh
|
r10545 | Pecho 'Could not execute [' . s:theDiffCmd . ']' | ||
Manpreet Singh
|
r10550 | Pecho 'Error code: ' . v:shell_error | ||
Pecho cout | ||||
Manpreet Singh
|
r10545 | Pecho 'Diff review aborted.' | ||
let &shortmess = s:save_shortmess | ||||
return | ||||
endif | ||||
let s:reviewmode = 'diff' | ||||
call s:_GenericReview([outfile, strip]) | ||||
let &shortmess = s:save_shortmess | ||||
endfunction | ||||
"}}} | ||||
" End user commands "{{{ | ||||
Manpreet Singh
|
r2350 | "============================================================================ | ||
" :PatchReview | ||||
command! -nargs=* -complete=file PatchReview call s:PatchReview (<f-args>) | ||||
Manpreet Singh
|
r10545 | " :DiffReview | ||
command! -nargs=0 DiffReview call s:DiffReview() | ||||
Manpreet Singh
|
r2350 | "}}} | ||
Manpreet Singh
|
r10545 | " Development "{{{ | ||
if exists('g:patchreview_debug') | ||||
" Tests | ||||
function! <SID>PRExtractTestNative(...) | ||||
"let patchfiles = glob(expand(a:1) . '/?*') | ||||
"for fname in split(patchfiles) | ||||
call s:PR_wipeMsgBuf() | ||||
let fname = a:1 | ||||
call s:ExtractDiffsNative(fname) | ||||
for patch in g:patches['patch'] | ||||
for line in patch.content | ||||
Pecho line | ||||
endfor | ||||
endfor | ||||
"endfor | ||||
endfunction | ||||
function! <SID>PRExtractTestVim(...) | ||||
"let patchfiles = glob(expand(a:1) . '/?*') | ||||
"for fname in split(patchfiles) | ||||
call s:PR_wipeMsgBuf() | ||||
let fname = a:1 | ||||
call s:ExtractDiffsPureVim(fname) | ||||
for patch in g:patches['patch'] | ||||
for line in patch.content | ||||
Pecho line | ||||
endfor | ||||
endfor | ||||
"endfor | ||||
endfunction | ||||
command! -nargs=+ -complete=file PRTestVim call s:PRExtractTestVim(<f-args>) | ||||
command! -nargs=+ -complete=file PRTestNative call s:PRExtractTestNative(<f-args>) | ||||
endif | ||||
Manpreet Singh
|
r2350 | "}}} | ||
Manpreet Singh
|
r10545 | |||
" modeline | ||||
" vim: set et fdl=0 fdm=marker fenc=latin ff=unix ft=vim sw=2 sts=0 ts=2 textwidth=78 nowrap : | ||||