2010-02-25 00:12:17 +03:00
" VIM plugin for doing single, multi-patch or diff code reviews {{{
" Home: http://www.vim.org/scripts/script.php?script_id=1563
2006-05-28 07:44:53 +04:00
2010-02-25 06:43:13 +03:00
" Version : 0.2.2 "{{{
2010-02-25 00:12:17 +03:00
" Author : Manpreet Singh < junkblocker@yahoo.com >
" Copyright : 2006-2010 by Manpreet Singh
2006-05-28 07:44:53 +04:00
" License : This file is placed in the public domain.
2010-02-25 00:12:17 +03:00
" No warranties express or implied. Use at your own risk.
2006-05-28 07:44:53 +04:00
"
2010-02-25 00:12:17 +03:00
" Changelog :
"
2010-02-25 06:43:13 +03:00
" 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
"
2010-02-25 00:12:17 +03:00
" 0.2.1 - Minor temp directory autodetection logic and cleanup
"
" 0.2 - Removed the need for filterdiff by implemeting it in pure vim script
" - 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
2006-05-28 07:44:53 +04:00
"}}}
2010-02-25 00:12:17 +03:00
2006-05-28 07:44:53 +04:00
" Documentation: "{{{
" ===========================================================================
2010-02-25 00:12:17 +03:00
" 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.
2006-05-28 07:44:53 +04:00
"
2010-02-25 00:12:17 +03:00
" Installing:
2006-05-28 07:44:53 +04:00
"
2010-02-25 06:43:13 +03:00
" For a quick start, unzip patchreview.zip into your ~/.vim directory and
" restart Vim.
"
" Details:
2006-05-28 07:44:53 +04:00
"
2010-02-25 00:12:17 +03:00
" Requirements:
2006-05-28 07:44:53 +04:00
"
2010-02-25 00:12:17 +03:00
" 1) VIM 7.0 or higher built with +diff option.
2006-05-28 07:44:53 +04:00
"
2010-02-25 00:12:17 +03:00
" 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.
2006-05-28 07:44:53 +04:00
"
2010-02-25 00:12:17 +03:00
" 3) Optional (but recommended for speed)
2006-05-28 07:44:53 +04:00
"
2010-02-25 00:12:17 +03:00
" Install patchutils ( http://cyberelk.net/tim/patchutils/ ) for your
" OS. For windows it is availble from Cygwin
2006-05-28 07:44:53 +04:00
"
2010-02-25 00:12:17 +03:00
" http://www.cygwin.com
"
" or GnuWin32
"
" http://gnuwin32.sourceforge.net/
"
" Install:
"
" 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.
"
" 2) Restart vim.
"
" Configuration:
"
" 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'
2006-05-28 07:44:53 +04:00
"
2010-02-25 00:12:17 +03:00
" " If you are using filterdiff
" let g:patchreview_filterdiff = '/path/to/filterdiff'
2006-05-28 07:44:53 +04:00
"
"
2010-02-25 00:12:17 +03:00
" Usage:
2006-05-28 07:44:53 +04:00
"
2010-02-25 00:12:17 +03:00
" Please see :help patchreview or :help diffreview for details.
2006-05-28 07:44:53 +04:00
"
""}}}
2010-02-25 00:12:17 +03:00
" Enabled only during development
2006-05-28 07:44:53 +04:00
" unlet! g:loaded_patchreview " DEBUG
" unlet! g:patchreview_patch " DEBUG
2010-02-25 00:12:17 +03:00
" unlet! g:patchreview_filterdiff " DEBUG
" let g:patchreview_patch = 'patch' " DEBUG
2006-05-28 07:44:53 +04:00
2010-02-25 00:12:17 +03:00
if v :version < 700
2006-05-28 07:44:53 +04:00
finish
endif
2010-02-25 00:12:17 +03:00
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
finish
endif
2010-02-25 06:43:13 +03:00
let g :loaded_patchreview = "0.2.2"
2010-02-25 00:12:17 +03:00
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 > )
2006-05-28 07:44:53 +04:00
"}}}
2010-02-25 00:12:17 +03:00
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'
2006-05-28 07:44:53 +04:00
exe 'bw'
2010-02-25 00:12:17 +03:00
exe cur_winnr . 'wincmd w'
2006-05-28 07:44:53 +04:00
endif
endif
endfunction
"}}}
2010-02-25 00:12:17 +03:00
function ! < SID > Pecho ( ...) "{{{
" Usage: Pecho(msg, [return_to_original_window_flag])
2006-05-28 07:44:53 +04:00
" default return_to_original_window_flag = 0
"
2010-02-25 00:12:17 +03:00
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'
2006-05-28 07:44:53 +04:00
endif
else
2010-02-25 00:12:17 +03:00
let bufnum = bufnr ( s :msgbufname )
if bufnum = = -1
let wcmd = s :msgbufname
2006-05-28 07:44:53 +04:00
else
2010-02-25 00:12:17 +03:00
let wcmd = '+buffer' . bufnum
2006-05-28 07:44:53 +04:00
endif
2010-02-25 00:12:17 +03:00
exe 'silent! botright 5split ' . wcmd
2006-05-28 07:44:53 +04:00
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
2010-02-25 00:12:17 +03:00
exe cur_winnr . 'wincmd w'
2006-05-28 07:44:53 +04:00
endif
endfunction
2010-02-25 00:12:17 +03:00
command ! - nargs = + - complete = expression Pecho call s :Pecho ( < args > )
2006-05-28 07:44:53 +04:00
"}}}
2010-02-25 00:12:17 +03:00
function ! < SID > PR_checkBinary ( BinaryName ) "{{{
2006-05-28 07:44:53 +04:00
" 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
2010-02-25 00:12:17 +03:00
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.'
2006-05-28 07:44:53 +04:00
return 0
endif
elseif ! executable ( g :patchreview_ {a :BinaryName })
2010-02-25 00:12:17 +03:00
Pecho 'Specified g:patchreview_' . a :BinaryName . ' [' . g :patchreview_ {a :BinaryName } . '] is not executable.'
2006-05-28 07:44:53 +04:00
return 0
else
return 1
endif
endfunction
"}}}
2010-02-25 00:12:17 +03:00
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"
2006-05-28 07:44:53 +04:00
return
endif
2010-02-25 00:12:17 +03:00
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"
return
endif
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
2006-05-28 07:44:53 +04:00
"}}}
2010-02-25 00:12:17 +03:00
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
if a :0 = = 0
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"
return
endif
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>"
return
endif
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'
endif
return s :STATE
endif
endfunction
com ! - nargs = + - complete = expression State call State ( < args > )
"}}}
function ! < SID > PatchReview ( ...) "{{{
2006-05-28 07:44:53 +04:00
let s :save_shortmess = &shortmess
2010-02-25 00:12:17 +03:00
let s :save_aw = &autowrite
let s :save_awa = &autowriteall
set shortmess = aW
2006-05-28 07:44:53 +04:00
call s :PR_wipeMsgBuf ( )
2010-02-25 00:12:17 +03:00
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
"}}}
2006-05-28 07:44:53 +04:00
2010-02-25 00:12:17 +03:00
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'
return
endif
" +diff required
if ! has ( 'diff' )
Pecho 'This plugin needs VIM built with +diff feature.'
2006-05-28 07:44:53 +04:00
return
endif
2010-02-25 00:12:17 +03:00
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.'
return
endif
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.'
2006-05-28 07:44:53 +04:00
return
endif
2010-02-25 00:12:17 +03:00
if len ( a :argslist ) > = 2 && s :reviewmode = = 'patch'
let s :SrcDirectory = expand ( a :argslist [1 ], ':p' )
2006-05-28 07:44:53 +04:00
if ! isdirectory ( s :SrcDirectory )
2010-02-25 00:12:17 +03:00
Pecho '[' . s :SrcDirectory . '] is not a directory'
2006-05-28 07:44:53 +04:00
return
endif
try
2010-02-25 00:12:17 +03:00
" Command line has already escaped the path
2006-05-28 07:44:53 +04:00
exe 'cd ' . s :SrcDirectory
catch /^.*E344.*/
2010-02-25 00:12:17 +03:00
Pecho 'Could not change to directory [' . s :SrcDirectory . ']'
2006-05-28 07:44:53 +04:00
return
endtry
endif
2010-02-25 00:12:17 +03:00
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
2006-05-28 07:44:53 +04:00
else
2010-02-25 00:12:17 +03:00
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
2006-05-28 07:44:53 +04:00
return
endif
2010-02-25 00:12:17 +03:00
" Verify that patch command and temporary directory are available or specified
if ! s :PR_checkBinary ( 'patch' )
2006-05-28 07:44:53 +04:00
return
endif
2010-02-25 00:12:17 +03:00
" 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
2006-05-28 07:44:53 +04:00
continue
endif
2010-02-25 00:12:17 +03:00
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
2006-05-28 07:44:53 +04:00
endif
2010-02-25 00:12:17 +03:00
let bufnum = bufnr ( relpath )
if buflisted ( bufnum ) && getbufvar ( bufnum , '&mod' )
Pecho 'Old buffer for file [' . relpath . '] exists in modified state. Skipping review.' , 1
2006-05-28 07:44:53 +04:00
continue
endif
2010-02-25 06:43:13 +03:00
let tmpname = tempname ( )
2006-05-28 07:44:53 +04:00
2010-02-25 00:12:17 +03:00
" 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
2006-05-28 07:44:53 +04:00
else
2010-02-25 00:12:17 +03:00
let inputfile = expand ( StrippedRelativeFilePath , ':p' )
let patchcmd = '!' . g :patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
endif
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
2006-05-28 07:44:53 +04:00
endif
2010-02-25 06:43:13 +03:00
call delete ( tmpname )
2006-05-28 07:44:53 +04:00
let s :origtabpagenr = tabpagenr ( )
2010-02-25 00:12:17 +03:00
silent ! exe 'tabedit ' . StrippedRelativeFilePath
if exists ( 'patchcmd' )
2010-02-25 06:43:13 +03:00
" modelines in loaded files mess with diff comparision
let s :keep_modeline = &modeline
let &modeline = 0
2010-02-25 00:12:17 +03:00
silent ! exe 'vert diffsplit ' . tmpname . '.file'
2010-02-25 06:43:13 +03:00
setlocal buftype = nofile
setlocal noswapfile
setlocal syntax = none
setlocal bufhidden = delete
setlocal nobuflisted
setlocal modifiable
setlocal nowrap
" Remove buffer name
silent ! 0 f
" Switch to original to get a nice tab title
silent ! wincmd p
let &modeline = s :keep_modeline
2010-02-25 00:12:17 +03:00
else
silent ! exe 'vnew'
endif
if filereadable ( tmpname . '.file.rej' )
silent ! exe 'topleft 5split ' . tmpname . '.file.rej'
Pecho msgtype . '*** REJECTED *** ' . relpath , 1
2006-05-28 07:44:53 +04:00
else
2010-02-25 00:12:17 +03:00
Pecho msgtype . ' ' . relpath , 1
2006-05-28 07:44:53 +04:00
endif
silent ! exe 'tabn ' . s :origtabpagenr
endfor
2010-02-25 00:12:17 +03:00
Pecho '-----'
Pecho 'Done.'
endfunction
2006-05-28 07:44:53 +04:00
"}}}
2010-02-25 00:12:17 +03:00
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
2006-05-28 07:44:53 +04:00
endfor
2010-02-25 00:12:17 +03:00
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
2010-02-25 06:43:13 +03:00
let outfile = tempname ( )
let cmd = s :theDiffCmd . ' > "' . outfile . '"'
2010-02-25 00:12:17 +03:00
let v :errmsg = ''
2010-02-25 06:43:13 +03:00
let cout = system ( cmd )
2010-02-25 00:12:17 +03:00
if v :errmsg = = '' && exists ( 'l:vcs' ) && l :vcs = = 'cvs' && v :shell_error = = 1
" Ignoring CVS non-error
elseif v :errmsg ! = '' | | v :shell_error
Pecho v :errmsg
2010-02-25 06:43:13 +03:00
Pecho 'Could not execute [' . s :theDiffCmd . ']'
Pecho 'Error code: ' . v :shell_error
Pecho cout
2010-02-25 00:12:17 +03:00
Pecho 'Diff review aborted.'
let &shortmess = s :save_shortmess
return
2006-05-28 07:44:53 +04:00
endif
2010-02-25 00:12:17 +03:00
let s :reviewmode = 'diff'
call s :_GenericReview ( [outfile , strip ])
let &shortmess = s :save_shortmess
2006-05-28 07:44:53 +04:00
endfunction
"}}}
2010-02-25 00:12:17 +03:00
" End user commands "{{{
2006-05-28 07:44:53 +04:00
"============================================================================
" :PatchReview
command ! - nargs = * - complete = file PatchReview call s :PatchReview ( < f - args > )
2010-02-25 00:12:17 +03:00
" :DiffReview
command ! - nargs = 0 DiffReview call s :DiffReview ( )
2006-05-28 07:44:53 +04:00
"}}}
2010-02-25 00:12:17 +03:00
" 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
2006-05-28 07:44:53 +04:00
"}}}
2010-02-25 00:12:17 +03:00
" modeline
" vim: set et fdl=0 fdm=marker fenc=latin ff=unix ft=vim sw=2 sts=0 ts=2 textwidth=78 nowrap :