contrib/vim/patchreview.vim
changeset 2350 091d555653a4
equal deleted inserted replaced
2349:88c881bda888 2350:091d555653a4
       
     1 " Vim global plugin for doing single or multipatch code reviews"{{{
       
     2 
       
     3 " Version       : 0.1                                          "{{{
       
     4 " Last Modified : Thu 25 May 2006 10:15:11 PM PDT
       
     5 " Author        : Manpreet Singh (junkblocker AT yahoo DOT com)
       
     6 " Copyright     : 2006 by Manpreet Singh
       
     7 " License       : This file is placed in the public domain.
       
     8 "
       
     9 " History       : 0.1 - First released
       
    10 "}}}
       
    11 " Documentation:                                                         "{{{
       
    12 " ===========================================================================
       
    13 " This plugin allows single or multipatch code reviews to be done in VIM. Vim
       
    14 " has :diffpatch command to do single file reviews but can not handle patch
       
    15 " files containing multiple patches. This plugin provides that missing
       
    16 " functionality and doesn't require the original file to be open.
       
    17 "
       
    18 " Installing:                                                            "{{{
       
    19 "
       
    20 "  For a quick start...
       
    21 "
       
    22 "   Requirements:                                                        "{{{
       
    23 "
       
    24 "   1) (g)vim 7.0 or higher built with +diff option.
       
    25 "   2) patch and patchutils ( http://cyberelk.net/tim/patchutils/ ) installed
       
    26 "      for your OS. For windows it is availble from Cygwin (
       
    27 "      http://www.cygwin.com ) or GnuWin32 ( http://gnuwin32.sourceforge.net/
       
    28 "      ).
       
    29 ""}}}
       
    30 "   Install:                                                            "{{{
       
    31 "
       
    32 "   1) Extract this in your $VIM/vimfiles or $HOME/.vim directory and restart
       
    33 "      vim.
       
    34 "
       
    35 "   2) Make sure that you have filterdiff from patchutils and patch commands
       
    36 "      installed.
       
    37 "
       
    38 "   3) Optinally, specify the locations to filterdiff and patch commands and
       
    39 "      location of a temporary directory to use in your .vimrc.
       
    40 "
       
    41 "      let g:patchreview_filterdiff  = '/path/to/filterdiff'
       
    42 "      let g:patchreview_patch       = '/path/to/patch'
       
    43 "      let g:patchreview_tmpdir      = '/tmp/or/something'
       
    44 "
       
    45 "   4) Optionally, generate help tags to use help
       
    46 "
       
    47 "      :helptags ~/.vim/doc
       
    48 "      or
       
    49 "      :helptags c:\vim\vimfiles\doc
       
    50 ""}}}
       
    51 ""}}}
       
    52 " Usage:                                                                 "{{{
       
    53 "
       
    54 "  :PatchReview path_to_submitted_patchfile [optional_source_directory]
       
    55 "
       
    56 "  after review is done
       
    57 "
       
    58 "  :PatchReviewCleanup
       
    59 "
       
    60 " See :help patchreview for details after you've created help tags.
       
    61 ""}}}
       
    62 "}}}
       
    63 " Code                                                                   "{{{
       
    64 
       
    65 " Enabled only during development                                        "{{{
       
    66 " unlet! g:loaded_patchreview " DEBUG
       
    67 " unlet! g:patchreview_tmpdir " DEBUG
       
    68 " unlet! g:patchreview_filterdiff " DEBUG
       
    69 " unlet! g:patchreview_patch " DEBUG
       
    70 "}}}
       
    71 
       
    72 " load only once                                                         "{{{
       
    73 if exists('g:loaded_patchreview')
       
    74   finish
       
    75 endif
       
    76 let g:loaded_patchreview=1
       
    77 let s:msgbufname = 'Patch Review Messages'
       
    78 "}}}
       
    79 
       
    80 function! <SID>PR_wipeMsgBuf()                                           "{{{
       
    81   let s:winnum = bufwinnr(s:msgbufname)
       
    82   if s:winnum != -1 " If the window is already open, jump to it
       
    83     let s:cur_winnr = winnr()
       
    84     if winnr() != s:winnum
       
    85       exe s:winnum . 'wincmd w'
       
    86       exe 'bw'
       
    87       exe s:cur_winnr . 'wincmd w'
       
    88     endif
       
    89   endif
       
    90 endfunction
       
    91 "}}}
       
    92 
       
    93 function! <SID>PR_echo(...)                                              "{{{
       
    94   " Usage: PR_echo(msg, [return_to_original_window_flag])
       
    95   "            default return_to_original_window_flag = 0
       
    96   "
       
    97   let s:cur_winnr = winnr()
       
    98   let s:winnum = bufwinnr(s:msgbufname)
       
    99   if s:winnum != -1 " If the window is already open, jump to it
       
   100     if winnr() != s:winnum
       
   101       exe s:winnum . 'wincmd w'
       
   102     endif
       
   103   else
       
   104     let s:bufnum = bufnr(s:msgbufname)
       
   105     if s:bufnum == -1
       
   106       let s:wcmd = s:msgbufname
       
   107     else
       
   108       let s:wcmd = '+buffer' . s:bufnum
       
   109     endif
       
   110     exe 'silent! botright 5split ' . s:wcmd
       
   111   endif
       
   112   setlocal modifiable
       
   113   setlocal buftype=nofile
       
   114   setlocal bufhidden=delete
       
   115   setlocal noswapfile
       
   116   setlocal nowrap
       
   117   setlocal nobuflisted
       
   118   if a:0 != 0
       
   119     silent! $put =a:1
       
   120   endif
       
   121   exe ':$'
       
   122   setlocal nomodifiable
       
   123   if a:0 > 1 && a:2
       
   124     exe s:cur_winnr . 'wincmd w'
       
   125   endif
       
   126 endfunction
       
   127 "}}}
       
   128 
       
   129 function! <SID>PR_checkBinary(BinaryName)                                "{{{
       
   130   " Verify that BinaryName is specified or available
       
   131   if ! exists('g:patchreview_' . a:BinaryName)
       
   132     if executable(a:BinaryName)
       
   133       let g:patchreview_{a:BinaryName} = a:BinaryName
       
   134       return 1
       
   135     else
       
   136       call s:PR_echo('g:patchreview_' . a:BinaryName . ' is not defined and could not be found on path. Please define it in your .vimrc.')
       
   137       return 0
       
   138     endif
       
   139   elseif ! executable(g:patchreview_{a:BinaryName})
       
   140     call s:PR_echo('Specified g:patchreview_' . a:BinaryName . ' [' . g:patchreview_{a.BinaryName} . '] is not executable.')
       
   141     return 0
       
   142   else
       
   143     return 1
       
   144   endif
       
   145 endfunction
       
   146 "}}}
       
   147 
       
   148 function! <SID>PR_GetTempDirLocation(Quiet)                              "{{{
       
   149   if exists('g:patchreview_tmpdir')
       
   150     if ! isdirectory(g:patchreview_tmpdir) || ! filewritable(g:patchreview_tmpdir)
       
   151       if ! a:Quiet
       
   152         call s:PR_echo('Temporary directory specified by g:patchreview_tmpdir [' . g:patchreview_tmpdir . '] is not accessible.')
       
   153         return 0
       
   154       endif
       
   155     endif
       
   156   elseif exists("$TMP") && isdirectory($TMP) && filewritable($TMP)
       
   157     let g:patchreview_tmpdir = $TMP
       
   158   elseif exists("$TEMP") && isdirectory($TEMP) && filewritable($TEMP)
       
   159     let g:patchreview_tmpdir = $TEMP
       
   160   elseif exists("$TMPDIR") && isdirectory($TMPDIR) && filewritable($TMPDIR)
       
   161     let g:patchreview_tmpdir = $TMPDIR
       
   162   else
       
   163     if ! a:Quiet
       
   164       call s:PR_echo('Could not figure out a temporary directory to use. Please specify g:patchreview_tmpdir in your .vimrc.')
       
   165       return 0
       
   166     endif
       
   167   endif
       
   168   let g:patchreview_tmpdir = g:patchreview_tmpdir . '/'
       
   169   let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '\\', '/', 'g')
       
   170   let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '/+$', '/', '')
       
   171   if has('win32')
       
   172     let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '/', '\\', 'g')
       
   173   endif
       
   174   return 1
       
   175 endfunction
       
   176 "}}}
       
   177 
       
   178 function! <SID>PatchReview(...)                                          "{{{
       
   179   " VIM 7+ required"{{{
       
   180   if version < 700
       
   181     call s:PR_echo('This plugin needs VIM 7 or higher')
       
   182     return
       
   183   endif
       
   184 "}}}
       
   185 
       
   186   let s:save_shortmess = &shortmess
       
   187   set shortmess+=aW
       
   188   call s:PR_wipeMsgBuf()
       
   189 
       
   190   " Check passed arguments                                               "{{{
       
   191   if a:0 == 0
       
   192     call s:PR_echo('PatchReview command needs at least one argument specifying a patchfile path.')
       
   193     let &shortmess = s:save_shortmess
       
   194     return
       
   195   endif
       
   196   if a:0 >= 1 && a:0 <= 2
       
   197     let s:PatchFilePath = expand(a:1, ':p')
       
   198     if ! filereadable(s:PatchFilePath)
       
   199       call s:PR_echo('File [' . s:PatchFilePath . '] is not accessible.')
       
   200       let &shortmess = s:save_shortmess
       
   201       return
       
   202     endif
       
   203     if a:0 == 2
       
   204       let s:SrcDirectory = expand(a:2, ':p')
       
   205       if ! isdirectory(s:SrcDirectory)
       
   206         call s:PR_echo('[' . s:SrcDirectory . '] is not a directory')
       
   207         let &shortmess = s:save_shortmess
       
   208         return
       
   209       endif
       
   210       try
       
   211         exe 'cd ' . s:SrcDirectory
       
   212       catch /^.*E344.*/
       
   213         call s:PR_echo('Could not change to directory [' . s:SrcDirectory . ']')
       
   214         let &shortmess = s:save_shortmess
       
   215         return
       
   216       endtry
       
   217     endif
       
   218   else
       
   219     call s:PR_echo('PatchReview command needs at most two arguments: patchfile path and optional source directory path.')
       
   220     let &shortmess = s:save_shortmess
       
   221     return
       
   222   endif
       
   223 "}}}
       
   224 
       
   225   " Verify that filterdiff and patch are specified or available          "{{{
       
   226   if ! s:PR_checkBinary('filterdiff') || ! s:PR_checkBinary('patch')
       
   227     let &shortmess = s:save_shortmess
       
   228     return
       
   229   endif
       
   230 
       
   231   let s:retval = s:PR_GetTempDirLocation(0)
       
   232   if ! s:retval
       
   233     let &shortmess = s:save_shortmess
       
   234     return
       
   235   endif
       
   236 "}}}
       
   237 
       
   238   " Requirements met, now execute                                        "{{{
       
   239   let s:PatchFilePath = fnamemodify(s:PatchFilePath, ':p')
       
   240   call s:PR_echo('Patch file      : ' . s:PatchFilePath)
       
   241   call s:PR_echo('Source directory: ' . getcwd())
       
   242   call s:PR_echo('------------------')
       
   243   let s:theFilterDiffCommand = '' . g:patchreview_filterdiff . ' --list -s ' . s:PatchFilePath
       
   244   let s:theFilesString = system(s:theFilterDiffCommand)
       
   245   let s:theFilesList = split(s:theFilesString, '[\r\n]')
       
   246   for s:filewithchangetype in s:theFilesList
       
   247     if s:filewithchangetype !~ '^[!+-] '
       
   248       call s:PR_echo('*** Skipping review generation due to understood change for [' . s:filewithchangetype . ']', 1)
       
   249       continue
       
   250     endif
       
   251     unlet! s:RelativeFilePath
       
   252     let s:RelativeFilePath = substitute(s:filewithchangetype, '^. ', '', '')
       
   253     let s:RelativeFilePath = substitute(s:RelativeFilePath, '^[a-z][^\\\/]*[\\\/]' , '' , '')
       
   254     if s:filewithchangetype =~ '^! '
       
   255       let s:msgtype = 'Modification : '
       
   256     elseif s:filewithchangetype =~ '^+ '
       
   257       let s:msgtype = 'Addition     : '
       
   258     elseif s:filewithchangetype =~ '^- '
       
   259       let s:msgtype = 'Deletion     : '
       
   260     endif
       
   261     let s:bufnum = bufnr(s:RelativeFilePath)
       
   262     if buflisted(s:bufnum) && getbufvar(s:bufnum, '&mod')
       
   263       call s:PR_echo('Old buffer for file [' . s:RelativeFilePath . '] exists in modified state. Skipping review.', 1)
       
   264       continue
       
   265     endif
       
   266     let s:tmpname = substitute(s:RelativeFilePath, '/', '_', 'g')
       
   267     let s:tmpname = substitute(s:tmpname, '\\', '_', 'g')
       
   268     let s:tmpname = g:patchreview_tmpdir . 'PatchReview.' . s:tmpname . '.' . strftime('%Y%m%d%H%M%S')
       
   269     if has('win32')
       
   270       let s:tmpname = substitute(s:tmpname, '/', '\\', 'g')
       
   271     endif
       
   272     if ! exists('s:patchreview_tmpfiles')
       
   273       let s:patchreview_tmpfiles = []
       
   274     endif
       
   275     let s:patchreview_tmpfiles = s:patchreview_tmpfiles + [s:tmpname]
       
   276 
       
   277     let s:filterdiffcmd = '!' . g:patchreview_filterdiff . ' -i ' . s:RelativeFilePath . ' ' . s:PatchFilePath . ' > ' . s:tmpname
       
   278     silent! exe s:filterdiffcmd
       
   279     if s:filewithchangetype =~ '^+ '
       
   280       if has('win32')
       
   281         let s:inputfile = 'nul'
       
   282       else
       
   283         let s:inputfile = '/dev/null'
       
   284       endif
       
   285     else
       
   286       let s:inputfile = expand(s:RelativeFilePath, ':p')
       
   287     endif
       
   288     silent exe '!' . g:patchreview_patch . ' -o ' . s:tmpname . '.file ' . s:inputfile . ' < ' . s:tmpname
       
   289     let s:origtabpagenr = tabpagenr()
       
   290     silent! exe 'tabedit ' . s:RelativeFilePath
       
   291     silent! exe 'vert diffsplit ' . s:tmpname . '.file'
       
   292     if filereadable(s:tmpname . '.file.rej')
       
   293       silent! exe 'topleft 5split ' . s:tmpname . '.file.rej'
       
   294       call s:PR_echo(s:msgtype . '*** REJECTED *** ' . s:RelativeFilePath, 1)
       
   295     else
       
   296       call s:PR_echo(s:msgtype . ' ' . s:RelativeFilePath, 1)
       
   297     endif
       
   298     silent! exe 'tabn ' . s:origtabpagenr
       
   299   endfor
       
   300   call s:PR_echo('-----')
       
   301   call s:PR_echo('Done.')
       
   302   let &shortmess = s:save_shortmess
       
   303 "}}}
       
   304 endfunction
       
   305 "}}}
       
   306 
       
   307 function! <SID>PatchReviewCleanup()                                      "{{{
       
   308   let s:retval = s:PR_GetTempDirLocation(1)
       
   309   if s:retval && exists('g:patchreview_tmpdir') && isdirectory(g:patchreview_tmpdir) && filewritable(g:patchreview_tmpdir)
       
   310     let s:zefilestr = globpath(g:patchreview_tmpdir, 'PatchReview.*')
       
   311     let s:theFilesList = split(s:zefilestr, '\m[\r\n]\+')
       
   312     for s:thefile in s:theFilesList
       
   313       call delete(s:thefile)
       
   314     endfor
       
   315   endif
       
   316 endfunction
       
   317 "}}}
       
   318 
       
   319 " Commands                                                               "{{{
       
   320 "============================================================================
       
   321 " :PatchReview
       
   322 command! -nargs=* -complete=file PatchReview call s:PatchReview (<f-args>)
       
   323 
       
   324 
       
   325 " :PatchReviewCleanup
       
   326 command! -nargs=0 PatchReviewCleanup call s:PatchReviewCleanup ()
       
   327 "}}}
       
   328 "}}}
       
   329 
       
   330 " vim: textwidth=78 nowrap tabstop=2 shiftwidth=2 softtabstop=2 expandtab
       
   331 " vim: filetype=vim encoding=latin1 fileformat=unix foldlevel=0 foldmethod=marker
       
   332 "}}}