Patch to update contrib/vim/patchreview.* to 0.2
Manpreet Singh
junkblocker at yahoo.com
Sun Feb 21 16:47:34 CST 2010
This update
1) adds a :DiffReview command to review code changes
in the current workspace.
2) removes the need to have patchutils (specifically filterdiff)
installed on the system by implementing patch extraction in
pure vim script.
I've been using the changes for many months now and only got around
just now to sending it. The patch against crew follows inline. In
case this gets mangled, I can send the export in a different format.
Thanks
Manpreet Singh
# HG changeset patch
# User Manpreet Singh <junkblocker at yahoo.com>
# Date 1266790871 28800
# Node ID 15b8b2dfdff52a32a6d150479d2bf00072e419b5
# Parent 261cc6b0f15c7e83dcff7dd7d5f3607c098c29bd
Updated contrib/vim/patchreview to 0.2
diff -r 261cc6b0f15c -r 15b8b2dfdff5 contrib/vim/patchreview.txt
--- a/contrib/vim/patchreview.txt Thu Feb 18 23:23:17 2010 -0600
+++ b/contrib/vim/patchreview.txt Sun Feb 21 14:21:11 2010 -0800
@@ -1,20 +1,22 @@
-*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 (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 at 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 PatchReview Usage............................:
|:PatchReviewCleanup|
=============================================================================
@@ -32,66 +34,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:
diff -r 261cc6b0f15c -r 15b8b2dfdff5 contrib/vim/patchreview.vim
--- a/contrib/vim/patchreview.vim Thu Feb 18 23:23:17 2010 -0600
+++ b/contrib/vim/patchreview.vim Sun Feb 21 14:21:11 2010 -0800
@@ -1,113 +1,144 @@
-" 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 "{{{
+" Author : Manpreet Singh < junkblocker at 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 - 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...
+" For a quick start...
"
-" Requirements:
"{{{
+" 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) VIM 7.0 or higher built with +diff option.
"
-" 1) Extract this in your $VIM/vimfiles or $HOME/.vim directory and
restart
-" vim.
+" 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.
"
-" 2) Make sure that you have filterdiff from patchutils and patch
commands
-" installed.
+" 3) Optional (but recommended for speed)
"
-" 3) Optinally, specify the locations to filterdiff and patch
commands and
-" location of a temporary directory to use in your .vimrc.
+" Install patchutils ( http://cyberelk.net/tim/patchutils/ ) for your
+" OS. For windows it is availble from Cygwin
"
-" let g:patchreview_filterdiff = '/path/to/filterdiff'
-" let g:patchreview_patch = '/path/to/patch'
+" http://www.cygwin.com
+"
+" or GnuWin32
+"
+" http://gnuwin32.sourceforge.net/
+"
+" Install:
+"
+" 1) Extract this in your $VIM/vimfiles or $HOME/.vim directory.
+"
+" 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'
" 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:
+"
+" Please see :help patchreview or :help diffreview for details.
+"
""}}}
-""}}}
-" 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
"{{{
+" 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
+
+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="0.2"
+
+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>)
"}}}
-" 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'
+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 +152,25 @@
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
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.')
+ Pecho 'Specified g:patchreview_' . a:BinaryName . ' [' .
g:patchreview_{a:BinaryName} . '] is not executable.'
return 0
else
return 1
@@ -145,11 +178,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
@@ -161,13 +194,14 @@
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.')
+ Pecho '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 = 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 +209,684 @@
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
- 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
+ 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 at yahoo.com>"
return
endif
- if a:0 == 2
- let s:SrcDirectory = expand(a:2, ':p')
+ 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(...)
"{{{
+ 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
+
+ " +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)
- call s:PR_echo('[' . s:SrcDirectory . '] is not a directory')
- let &shortmess = s:save_shortmess
+ Pecho '[' . s:SrcDirectory . '] is not a directory'
return
endif
try
+ " Command line has already escaped the path
exe 'cd ' . s:SrcDirectory
catch /^.*E344.*/
- call s:PR_echo('Could not change to directory [' .
s:SrcDirectory . ']')
- let &shortmess = s:save_shortmess
+ 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
- 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
+ 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
- let s:retval = s:PR_GetTempDirLocation(0)
- if ! s:retval
- let &shortmess = s:save_shortmess
+
+ " Verify that patch command and temporary directory are available or
specified
+ if ! s:PR_checkBinary('patch')
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)
+ 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 == '!'
+ let msgtype = 'Changed file: '
+ elseif patch.type == '+'
+ let msgtype = 'New file : '
+ elseif patch.type == '-'
+ let msgtype = 'Deleted file: '
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 = []
+
+ " 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 inputfile = expand(StrippedRelativeFilePath, ':p')
+ let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o
"' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
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'
+ 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 ' . StrippedRelativeFilePath
+ if exists('patchcmd')
+ silent! exe 'vert diffsplit ' . tmpname . '.file'
else
- let s:inputfile = expand(s:RelativeFilePath, ':p')
+ silent! exe 'vnew'
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)
+ if filereadable(tmpname . '.file.rej')
+ silent! exe 'topleft 5split ' . tmpname . '.file.rej'
+ Pecho msgtype . '*** REJECTED *** ' . relpath, 1
else
- call s:PR_echo(s:msgtype . ' ' . s:RelativeFilePath, 1)
+ 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 :
More information about the Mercurial-devel
mailing list