contrib: patch review plugin for vim 7.0

The plugin takes an 'hg export'ed patch (in fact any
single or multi file patch) and opens multiple tabs
containing vim diff/merge windows for each affected
file in the patch allowing full visual code reviews.
This commit is contained in:
Manpreet Singh 2006-05-27 20:44:53 -07:00
parent 57e471e17a
commit acca4070a9
2 changed files with 429 additions and 0 deletions

View File

@ -0,0 +1,97 @@
*patchreview.txt* Vim global plugin for doing single or multipatch code reviews
Author: Manpreet Singh (junkblocker-CAT-yahoo-DOG-com)
(Replace -CAT- and -DOG- with @ and . first)
Copyright (C) 2006 by Manpreet Singh
License : This file is placed in the public domain.
=============================================================================
CONTENTS *patchreview* *patchreview-contents*
1. Contents.........................................: |patchreview-contents|
2. Introduction.....................................: |patchreview-intro|
3. PatchReview options..............................: |patchreview-options|
4. PatchReview Usage................................: |patchreview-usage|
4.1 PatchReview Usage............................: |:PatchReview|
4.2 PatchReview Usage............................: |:PatchReviewCleanup|
=============================================================================
PatchReview Introduction *patchreview-intro*
The Patch Review plugin allows single or multipatch code review to be done in
VIM. VIM provides the |:diffpatch| command to do single file reviews but can
not handle patch files containing multiple patches as is common with software
development projects. This plugin provides that missing functionality. It also
tries to improve on |:diffpatch|'s behaviour of creating the patched files in
the same directory as original file which can lead to project workspace
pollution.
=============================================================================
PatchReview Options *patchreview-options*
g:patchreview_filterdiff : Optional path to filterdiff binary. PatchReview
tries to locate filterdiff on system path
automatically. If the binary is not on system
path, this option tell PatchReview the full path
to the binary. This option, if specified,
overrides the default filterdiff binary on the
path.
examples:
(On Windows with Cygwin)
let g:patchreview_filterdiff = 'c:\\cygwin\\bin\\filterdiff.exe'
(On *nix systems)
let g:patchreview_filterdiff = '/usr/bin/filterdiff'
g:patchreview_patch : Optional path to patch binary. PatchReview tries
to locate patch on system path automatically. If
the binary is not on system path, this option
tell PatchReview the full path to the binary.
This option, if specified, overrides the default
patch binary on the path.
examples:
(On Windows with Cygwin)
let g:patchreview_patch = 'c:\\cygwin\\bin\\patch.exe'
(On *nix systems)
let g:patchreview_patch = '/usr/bin/gpatch'
g:patchreview_tmpdir : Optional path where the plugin can save temporary
files. If this is not specified, the plugin tries to
use TMP, TEMP and TMPDIR environment variables in
succession.
examples:
(On Windows) let g:patchreview_tmpdir = 'c:\\tmp'
(On *nix systems) let g:patchreview_tmpdir = '~/tmp'
=============================================================================
PatchReview Usage *patchreview-usage*
*:PatchReview*
:PatchReview patchfile_path [optional_source_directory]
Perform a patch review in the current directory based on the supplied
patchfile_path. If optional_source_directory is specified, patchreview is
done on that directory. Othewise, the current directory is assumed to be
the source directory.
*:PatchReviewCleanup*
:PatchReviewCleanup
After you are done using the :PatchReview command, you can cleanup the
temporary files in the temporary directory using this command.
=============================================================================
vim: ft=help:ts=2:sts=2:sw=2:tw=78:tw=78

332
contrib/vim/patchreview.vim Normal file
View File

@ -0,0 +1,332 @@
" Vim global plugin for doing single or multipatch code reviews"{{{
" Version : 0.1 "{{{
" Last Modified : Thu 25 May 2006 10:15:11 PM PDT
" Author : Manpreet Singh (junkblocker AT yahoo DOT com)
" Copyright : 2006 by Manpreet Singh
" License : This file is placed in the public domain.
"
" History : 0.1 - First released
"}}}
" Documentation: "{{{
" ===========================================================================
" This plugin allows single or multipatch code reviews to be done in VIM. Vim
" has :diffpatch command to do single file reviews but can not handle patch
" files containing multiple patches. This plugin provides that missing
" functionality and doesn't require the original file to be open.
"
" Installing: "{{{
"
" For a quick start...
"
" Requirements: "{{{
"
" 1) (g)vim 7.0 or higher built with +diff option.
" 2) patch and patchutils ( http://cyberelk.net/tim/patchutils/ ) installed
" for your OS. For windows it is availble from Cygwin (
" http://www.cygwin.com ) or GnuWin32 ( http://gnuwin32.sourceforge.net/
" ).
""}}}
" Install: "{{{
"
" 1) Extract this in your $VIM/vimfiles or $HOME/.vim directory and restart
" vim.
"
" 2) Make sure that you have filterdiff from patchutils and patch commands
" installed.
"
" 3) Optinally, specify the locations to filterdiff and patch commands and
" location of a temporary directory to use in your .vimrc.
"
" let g:patchreview_filterdiff = '/path/to/filterdiff'
" let g:patchreview_patch = '/path/to/patch'
" let g:patchreview_tmpdir = '/tmp/or/something'
"
" 4) Optionally, generate help tags to use help
"
" :helptags ~/.vim/doc
" or
" :helptags c:\vim\vimfiles\doc
""}}}
""}}}
" Usage: "{{{
"
" :PatchReview path_to_submitted_patchfile [optional_source_directory]
"
" after review is done
"
" :PatchReviewCleanup
"
" See :help patchreview for details after you've created help tags.
""}}}
"}}}
" Code "{{{
" Enabled only during development "{{{
" unlet! g:loaded_patchreview " DEBUG
" unlet! g:patchreview_tmpdir " DEBUG
" unlet! g:patchreview_filterdiff " DEBUG
" unlet! g:patchreview_patch " DEBUG
"}}}
" load only once "{{{
if exists('g:loaded_patchreview')
finish
endif
let g:loaded_patchreview=1
let s:msgbufname = 'Patch Review Messages'
"}}}
function! <SID>PR_wipeMsgBuf() "{{{
let s:winnum = bufwinnr(s:msgbufname)
if s:winnum != -1 " If the window is already open, jump to it
let s:cur_winnr = winnr()
if winnr() != s:winnum
exe s:winnum . 'wincmd w'
exe 'bw'
exe s:cur_winnr . 'wincmd w'
endif
endif
endfunction
"}}}
function! <SID>PR_echo(...) "{{{
" Usage: PR_echo(msg, [return_to_original_window_flag])
" default return_to_original_window_flag = 0
"
let s:cur_winnr = winnr()
let s:winnum = bufwinnr(s:msgbufname)
if s:winnum != -1 " If the window is already open, jump to it
if winnr() != s:winnum
exe s:winnum . 'wincmd w'
endif
else
let s:bufnum = bufnr(s:msgbufname)
if s:bufnum == -1
let s:wcmd = s:msgbufname
else
let s:wcmd = '+buffer' . s:bufnum
endif
exe 'silent! botright 5split ' . s:wcmd
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
exe s:cur_winnr . 'wincmd w'
endif
endfunction
"}}}
function! <SID>PR_checkBinary(BinaryName) "{{{
" 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
call s:PR_echo('g:patchreview_' . a:BinaryName . ' is not defined and could not be found on path. Please define it in your .vimrc.')
return 0
endif
elseif ! executable(g:patchreview_{a:BinaryName})
call s:PR_echo('Specified g:patchreview_' . a:BinaryName . ' [' . g:patchreview_{a.BinaryName} . '] is not executable.')
return 0
else
return 1
endif
endfunction
"}}}
function! <SID>PR_GetTempDirLocation(Quiet) "{{{
if exists('g:patchreview_tmpdir')
if ! isdirectory(g:patchreview_tmpdir) || ! filewritable(g:patchreview_tmpdir)
if ! a:Quiet
call s:PR_echo('Temporary directory specified by g:patchreview_tmpdir [' . g:patchreview_tmpdir . '] is not accessible.')
return 0
endif
endif
elseif exists("$TMP") && isdirectory($TMP) && filewritable($TMP)
let g:patchreview_tmpdir = $TMP
elseif exists("$TEMP") && isdirectory($TEMP) && filewritable($TEMP)
let g:patchreview_tmpdir = $TEMP
elseif exists("$TMPDIR") && isdirectory($TMPDIR) && filewritable($TMPDIR)
let g:patchreview_tmpdir = $TMPDIR
else
if ! a:Quiet
call s:PR_echo('Could not figure out a temporary directory to use. Please specify g:patchreview_tmpdir in your .vimrc.')
return 0
endif
endif
let g:patchreview_tmpdir = g:patchreview_tmpdir . '/'
let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '\\', '/', 'g')
let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '/+$', '/', '')
if has('win32')
let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '/', '\\', 'g')
endif
return 1
endfunction
"}}}
function! <SID>PatchReview(...) "{{{
" VIM 7+ required"{{{
if version < 700
call s:PR_echo('This plugin needs VIM 7 or higher')
return
endif
"}}}
let s:save_shortmess = &shortmess
set shortmess+=aW
call s:PR_wipeMsgBuf()
" Check passed arguments "{{{
if a:0 == 0
call s:PR_echo('PatchReview command needs at least one argument specifying a patchfile path.')
let &shortmess = s:save_shortmess
return
endif
if a:0 >= 1 && a:0 <= 2
let s:PatchFilePath = expand(a:1, ':p')
if ! filereadable(s:PatchFilePath)
call s:PR_echo('File [' . s:PatchFilePath . '] is not accessible.')
let &shortmess = s:save_shortmess
return
endif
if a:0 == 2
let s:SrcDirectory = expand(a:2, ':p')
if ! isdirectory(s:SrcDirectory)
call s:PR_echo('[' . s:SrcDirectory . '] is not a directory')
let &shortmess = s:save_shortmess
return
endif
try
exe 'cd ' . s:SrcDirectory
catch /^.*E344.*/
call s:PR_echo('Could not change to directory [' . s:SrcDirectory . ']')
let &shortmess = s:save_shortmess
return
endtry
endif
else
call s:PR_echo('PatchReview command needs at most two arguments: patchfile path and optional source directory path.')
let &shortmess = s:save_shortmess
return
endif
"}}}
" Verify that filterdiff and patch are specified or available "{{{
if ! s:PR_checkBinary('filterdiff') || ! s:PR_checkBinary('patch')
let &shortmess = s:save_shortmess
return
endif
let s:retval = s:PR_GetTempDirLocation(0)
if ! s:retval
let &shortmess = s:save_shortmess
return
endif
"}}}
" Requirements met, now execute "{{{
let s:PatchFilePath = fnamemodify(s:PatchFilePath, ':p')
call s:PR_echo('Patch file : ' . s:PatchFilePath)
call s:PR_echo('Source directory: ' . getcwd())
call s:PR_echo('------------------')
let s:theFilterDiffCommand = '' . g:patchreview_filterdiff . ' --list -s ' . s:PatchFilePath
let s:theFilesString = system(s:theFilterDiffCommand)
let s:theFilesList = split(s:theFilesString, '[\r\n]')
for s:filewithchangetype in s:theFilesList
if s:filewithchangetype !~ '^[!+-] '
call s:PR_echo('*** Skipping review generation due to understood change for [' . s:filewithchangetype . ']', 1)
continue
endif
unlet! s:RelativeFilePath
let s:RelativeFilePath = substitute(s:filewithchangetype, '^. ', '', '')
let s:RelativeFilePath = substitute(s:RelativeFilePath, '^[a-z][^\\\/]*[\\\/]' , '' , '')
if s:filewithchangetype =~ '^! '
let s:msgtype = 'Modification : '
elseif s:filewithchangetype =~ '^+ '
let s:msgtype = 'Addition : '
elseif s:filewithchangetype =~ '^- '
let s:msgtype = 'Deletion : '
endif
let s:bufnum = bufnr(s:RelativeFilePath)
if buflisted(s:bufnum) && getbufvar(s:bufnum, '&mod')
call s:PR_echo('Old buffer for file [' . s:RelativeFilePath . '] exists in modified state. Skipping review.', 1)
continue
endif
let s:tmpname = substitute(s:RelativeFilePath, '/', '_', 'g')
let s:tmpname = substitute(s:tmpname, '\\', '_', 'g')
let s:tmpname = g:patchreview_tmpdir . 'PatchReview.' . s:tmpname . '.' . strftime('%Y%m%d%H%M%S')
if has('win32')
let s:tmpname = substitute(s:tmpname, '/', '\\', 'g')
endif
if ! exists('s:patchreview_tmpfiles')
let s:patchreview_tmpfiles = []
endif
let s:patchreview_tmpfiles = s:patchreview_tmpfiles + [s:tmpname]
let s:filterdiffcmd = '!' . g:patchreview_filterdiff . ' -i ' . s:RelativeFilePath . ' ' . s:PatchFilePath . ' > ' . s:tmpname
silent! exe s:filterdiffcmd
if s:filewithchangetype =~ '^+ '
if has('win32')
let s:inputfile = 'nul'
else
let s:inputfile = '/dev/null'
endif
else
let s:inputfile = expand(s:RelativeFilePath, ':p')
endif
silent exe '!' . g:patchreview_patch . ' -o ' . s:tmpname . '.file ' . s:inputfile . ' < ' . s:tmpname
let s:origtabpagenr = tabpagenr()
silent! exe 'tabedit ' . s:RelativeFilePath
silent! exe 'vert diffsplit ' . s:tmpname . '.file'
if filereadable(s:tmpname . '.file.rej')
silent! exe 'topleft 5split ' . s:tmpname . '.file.rej'
call s:PR_echo(s:msgtype . '*** REJECTED *** ' . s:RelativeFilePath, 1)
else
call s:PR_echo(s:msgtype . ' ' . s:RelativeFilePath, 1)
endif
silent! exe 'tabn ' . s:origtabpagenr
endfor
call s:PR_echo('-----')
call s:PR_echo('Done.')
let &shortmess = s:save_shortmess
"}}}
endfunction
"}}}
function! <SID>PatchReviewCleanup() "{{{
let s:retval = s:PR_GetTempDirLocation(1)
if s:retval && exists('g:patchreview_tmpdir') && isdirectory(g:patchreview_tmpdir) && filewritable(g:patchreview_tmpdir)
let s:zefilestr = globpath(g:patchreview_tmpdir, 'PatchReview.*')
let s:theFilesList = split(s:zefilestr, '\m[\r\n]\+')
for s:thefile in s:theFilesList
call delete(s:thefile)
endfor
endif
endfunction
"}}}
" Commands "{{{
"============================================================================
" :PatchReview
command! -nargs=* -complete=file PatchReview call s:PatchReview (<f-args>)
" :PatchReviewCleanup
command! -nargs=0 PatchReviewCleanup call s:PatchReviewCleanup ()
"}}}
"}}}
" vim: textwidth=78 nowrap tabstop=2 shiftwidth=2 softtabstop=2 expandtab
" vim: filetype=vim encoding=latin1 fileformat=unix foldlevel=0 foldmethod=marker
"}}}