--- a/contrib/vim/patchreview.txt Wed Feb 24 12:35:26 2010 -0500
+++ b/contrib/vim/patchreview.txt Wed Feb 24 13:12:17 2010 -0800
@@ -1,30 +1,38 @@
-*patchreview.txt* Vim global plugin for doing single or multipatch code reviews
+*patchreview.txt* Vim global plugin for doing single, multi-patch or diff code reviews
+ Version v0.2.1 (for Vim version 7.0 or higher)
- Author: Manpreet Singh (junkblocker-CAT-yahoo-DOG-com)
- (Replace -CAT- and -DOG- with @ and . first)
- Copyright (C) 2006 by Manpreet Singh
+ Author: Manpreet Singh < junkblocker@yahoo.com >
+ Copyright (C) 2006-2010 by Manpreet Singh
License : This file is placed in the public domain.
=============================================================================
-CONTENTS *patchreview* *patchreview-contents*
+CONTENTS *patchreview* *diffreview* *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|
+ 4.1 DiffReview Usage.............................: |:DiffReview|
+ 4.2 PatchReview Usage............................: |:PatchReview|
+ 4.3 DiffReviewCleanup Usage......................: |:DiffReviewCleanup|
+ 4.4 PatchReviewCleanup 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 Patch Review plugin allows easy single or multipatch code or diff reviews.
+
+It opens each affected file in the patch or in a workspace diff in a diff view
+in a separate tab.
+
+VIM provides the |:diffpatch| and related commands 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 improves on |:diffpatch|'s behaviour of creating the patched files in
the same directory as original file which can lead to project workspace
pollution.
@@ -32,66 +40,81 @@
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.
+ g:patchreview_tmpdir = {string}
+ 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'
+<
+
+ g:patchreview_filterdiff = {string}
+ 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.
+<
+ g:patchreview_patch = {string}
+ 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) let g:patchreview_tmpdir = 'c:\\tmp'
- (On *nix systems) let g:patchreview_tmpdir = '~/tmp'
+ (On Windows with Cygwin) >
+ let g:patchreview_patch = 'c:\\cygwin\\bin\\patch.exe'
+<
+ (On *nix systems) >
+ let g:patchreview_patch = '/usr/bin/gpatch'
+<
=============================================================================
PatchReview Usage *patchreview-usage*
+ *:DiffReview*
+
+ :DiffReview
+
+ Perform a diff review in the current directory under version control.
+ Currently supports Mercurial (hg), Subversion (svn), CVS, Bazaar (bzr) and
+ Monotone.
+
*: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
+ done on that directory. Otherwise, the current directory is assumed to be
the source directory.
+
+ Only supports context or unified format patches.
+
+ *:DiffReviewCleanup*
*:PatchReviewCleanup*
+ :DiffReviewCleanup
:PatchReviewCleanup
- After you are done using the :PatchReview command, you can cleanup the
- temporary files in the temporary directory using this command.
+ After you are done using the :DiffReview or :PatchReview command, you can
+ cleanup the temporary files in the temporary directory using either of
+ these commands.
-=============================================================================
-vim: ft=help:ts=2:sts=2:sw=2:tw=78:tw=78
+------------------------------------------------------------------------------
+
+ vim: ft=help:ts=2:sts=2:sw=2:tw=78:norl:
--- a/contrib/vim/patchreview.vim Wed Feb 24 12:35:26 2010 -0500
+++ b/contrib/vim/patchreview.vim Wed Feb 24 13:12:17 2010 -0800
@@ -1,113 +1,148 @@
-" Vim global plugin for doing single or multipatch code reviews"{{{
+" VIM plugin for doing single, multi-patch or diff code reviews {{{
+" Home: http://www.vim.org/scripts/script.php?script_id=1563
-" 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
+" Version : 0.2.1 "{{{
+" Author : Manpreet Singh < junkblocker@yahoo.com >
+" Copyright : 2006-2010 by Manpreet Singh
" License : This file is placed in the public domain.
+" No warranties express or implied. Use at your own risk.
"
-" History : 0.1 - First released
+" Changelog :
+"
+" 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
"}}}
+
" 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.
+" 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:
"
-" Installing: "{{{
+" For a quick start...
+"
+" Requirements:
+"
+" 1) VIM 7.0 or higher built with +diff option.
"
-" For a quick start...
+" 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.
"
-" Requirements: "{{{
+" 3) Optional (but recommended for speed)
"
-" 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: "{{{
+" Install patchutils ( http://cyberelk.net/tim/patchutils/ ) 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.
+" 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) Make sure that you have filterdiff from patchutils and patch commands
-" installed.
+" 2) Restart vim.
"
-" 3) Optinally, specify the locations to filterdiff and patch commands and
-" location of a temporary directory to use in your .vimrc.
+" Configuration:
"
-" let g:patchreview_filterdiff = '/path/to/filterdiff'
-" let g:patchreview_patch = '/path/to/patch'
+" 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'
" let g:patchreview_tmpdir = '/tmp/or/something'
"
-" 4) Optionally, generate help tags to use help
+" " If you are using filterdiff
+" let g:patchreview_filterdiff = '/path/to/filterdiff'
"
-" :helptags ~/.vim/doc
-" or
-" :helptags c:\vim\vimfiles\doc
-""}}}
-""}}}
-" Usage: "{{{
"
-" :PatchReview path_to_submitted_patchfile [optional_source_directory]
"
-" after review is done
+" Usage:
"
-" :PatchReviewCleanup
+" Please see :help patchreview or :help diffreview for details.
"
-" See :help patchreview for details after you've created help tags.
""}}}
-"}}}
-" Code "{{{
-" Enabled only during development "{{{
+" Enabled only during development
" unlet! g:loaded_patchreview " DEBUG
" unlet! g:patchreview_tmpdir " DEBUG
+" unlet! g:patchreview_patch " DEBUG
" unlet! g:patchreview_filterdiff " DEBUG
-" unlet! g:patchreview_patch " DEBUG
-"}}}
+" let g:patchreview_patch = 'patch' " DEBUG
-" load only once "{{{
-if exists('g:loaded_patchreview')
+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
finish
endif
-let g:loaded_patchreview=1
-let s:msgbufname = 'Patch Review Messages'
+let g:loaded_patchreview="0.2.1"
+
+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>)
"}}}
-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'
+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'
exe 'bw'
- exe s:cur_winnr . 'wincmd w'
+ exe cur_winnr . 'wincmd w'
endif
endif
endfunction
"}}}
-function! <SID>PR_echo(...) "{{{
- " Usage: PR_echo(msg, [return_to_original_window_flag])
+function! <SID>Pecho(...) "{{{
+ " Usage: Pecho(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'
+ 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'
endif
else
- let s:bufnum = bufnr(s:msgbufname)
- if s:bufnum == -1
- let s:wcmd = s:msgbufname
+ let bufnum = bufnr(s:msgbufname)
+ if bufnum == -1
+ let wcmd = s:msgbufname
else
- let s:wcmd = '+buffer' . s:bufnum
+ let wcmd = '+buffer' . bufnum
endif
- exe 'silent! botright 5split ' . s:wcmd
+ exe 'silent! botright 5split ' . wcmd
endif
setlocal modifiable
setlocal buftype=nofile
@@ -121,23 +156,26 @@
exe ':$'
setlocal nomodifiable
if a:0 > 1 && a:2
- exe s:cur_winnr . 'wincmd w'
+ exe cur_winnr . 'wincmd w'
endif
endfunction
+
+command! -nargs=+ -complete=expression Pecho call s:Pecho(<args>)
"}}}
-function! <SID>PR_checkBinary(BinaryName) "{{{
+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.')
+ 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.'
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.')
+ Pecho 'Specified g:patchreview_' . a:BinaryName . ' [' . g:patchreview_{a:BinaryName} . '] is not executable.'
return 0
else
return 1
@@ -145,11 +183,11 @@
endfunction
"}}}
-function! <SID>PR_GetTempDirLocation(Quiet) "{{{
+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.')
+ Pecho 'Temporary directory specified by g:patchreview_tmpdir [' . g:patchreview_tmpdir . '] is not accessible.'
return 0
endif
endif
@@ -160,14 +198,34 @@
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.')
+ if has("unix")
+ if isdirectory("/tmp")
+ let g:patchreview_tmpdir = "/tmp"
+ elseif isdirectory(expand("~/tmp"))
+ let g:patchreview_tmpdir = expand("~/tmp")
+ endif
+ elseif has("win32")
+ if isdirectory('c:\\tmp')
+ let g:patchreview_tmpdir = 'c:\\tmp'
+ elseif isdirectory('c:\\temp')
+ let g:patchreview_tmpdir = 'c:\\temp'
+ elseif isdirectory('c:\\windows\\temp')
+ let g:patchreview_tmpdir = 'c:\\windows\\temp'
+ elseif isdirectory($USERPROFILE . '\Local Settings\Temp') # NOTE : No \ issue here
+ let g:patchreview_tmpdir = $USERPROFILE . '\Local Settings\Temp'
+ endif
+ endif
+ if !exists('g:patchreview_tmpdir')
+ if ! a:Quiet
+ Pecho 'Could not figure out a temporary directory to use. Please specify g:patchreview_tmpdir in your .vimrc.'
+ endif
return 0
endif
endif
+ let g:patchreview_tmpdir = expand(g:patchreview_tmpdir, ':p')
let g:patchreview_tmpdir = g:patchreview_tmpdir . '/'
let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '\\', '/', 'g')
- let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '/+$', '/', '')
+ let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '/\+$', '/', '')
if has('win32')
let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '/', '\\', 'g')
endif
@@ -175,158 +233,694 @@
endfunction
"}}}
-function! <SID>PatchReview(...) "{{{
- " VIM 7+ required"{{{
- if version < 700
- call s:PR_echo('This plugin needs VIM 7 or higher')
+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"
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
"}}}
- let s:save_shortmess = &shortmess
- set shortmess+=aW
- call s:PR_wipeMsgBuf()
-
- " Check passed arguments "{{{
+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
- call s:PR_echo('PatchReview command needs at least one argument specifying a patchfile path.')
- let &shortmess = s:save_shortmess
+ 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
- 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
+ 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
- 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
+ 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
- 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
+ return s:STATE
endif
+endfunction
+com! -nargs=+ -complete=expression State call State(<args>)
"}}}
- " Verify that filterdiff and patch are specified or available "{{{
- if ! s:PR_checkBinary('filterdiff') || ! s:PR_checkBinary('patch')
- let &shortmess = s:save_shortmess
+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'
return
endif
- let s:retval = s:PR_GetTempDirLocation(0)
- if ! s:retval
- let &shortmess = s:save_shortmess
+ " +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.'
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.'
+ 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
- " 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)
+ " Verify that patch command and temporary directory are available or specified
+ if ! s:PR_checkBinary('patch')
+ return
+ endif
+
+ let retval = s:PR_GetTempDirLocation(0)
+ if ! retval
+ 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
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 : '
+ 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
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)
+ let bufnum = bufnr(relpath)
+ if buflisted(bufnum) && getbufvar(bufnum, '&mod')
+ Pecho 'Old buffer for file [' . relpath . '] 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')
+ let tmpname = substitute(relpath, '/', '_', 'g')
+ let tmpname = substitute(tmpname, '\\', '_', 'g')
+ let tmpname = g:patchreview_tmpdir . 'PatchReview.' . tmpname . '.' . strftime('%Y%m%d%H%M%S')
if has('win32')
- let s:tmpname = substitute(s:tmpname, '/', '\\', 'g')
+ let tmpname = substitute(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
+ " 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
else
- let s:inputfile = expand(s:RelativeFilePath, ':p')
+ let inputfile = expand(StrippedRelativeFilePath, ':p')
+ let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
endif
- silent exe '!' . g:patchreview_patch . ' -o ' . s:tmpname . '.file ' . s:inputfile . ' < ' . s:tmpname
+ 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
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)
+ silent! exe 'tabedit ' . StrippedRelativeFilePath
+ if exists('patchcmd')
+ silent! exe 'vert diffsplit ' . tmpname . '.file'
else
- call s:PR_echo(s:msgtype . ' ' . s:RelativeFilePath, 1)
+ 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
endif
silent! exe 'tabn ' . s:origtabpagenr
endfor
- call s:PR_echo('-----')
- call s:PR_echo('Done.')
- let &shortmess = s:save_shortmess
-"}}}
+ Pecho '-----'
+ Pecho 'Done.'
+
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)
+function! <SID>PatchReviewCleanup() "{{{
+ let retval = s:PR_GetTempDirLocation(1)
+ if retval && exists('g:patchreview_tmpdir') && isdirectory(g:patchreview_tmpdir) && filewritable(g:patchreview_tmpdir)
+ let zefilestr = globpath(g:patchreview_tmpdir, 'PatchReview.*')
+ let fileslist = split(zefilestr, '\m[\r\n]\+')
+ for thefile in fileslist
+ call delete(thefile)
endfor
endif
endfunction
"}}}
-" Commands "{{{
+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
+
+ let retval = s:PR_GetTempDirLocation(0)
+ if ! retval
+ Pecho 'DiffReview aborted.'
+ let &shortmess = s:save_shortmess
+ return
+ endif
+ let outfile = g:patchreview_tmpdir . 'PatchReview.diff.' . strftime('%Y%m%d%H%M%S')
+ let cmd = '!' . s:theDiffCmd . ' > "' . outfile . '"'
+ let v:errmsg = ''
+ silent exe cmd
+ if v:errmsg == '' && exists('l:vcs') && l:vcs == 'cvs' && v:shell_error == 1
+ " Ignoring CVS non-error
+ elseif v:errmsg != '' || v:shell_error
+ Pecho 'Could not execute [' . s:theDiffCmd . ']'
+ Pecho v:errmsg
+ 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 "{{{
"============================================================================
" :PatchReview
command! -nargs=* -complete=file PatchReview call s:PatchReview (<f-args>)
+" :DiffReview
+command! -nargs=0 DiffReview call s:DiffReview()
" :PatchReviewCleanup
command! -nargs=0 PatchReviewCleanup call s:PatchReviewCleanup ()
-"}}}
+command! -nargs=0 DiffReviewCleanup 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
+" 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
"}}}
+
+" modeline
+" vim: set et fdl=0 fdm=marker fenc=latin ff=unix ft=vim sw=2 sts=0 ts=2 textwidth=78 nowrap :