PageRenderTime 49ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/contrib/vim/patchreview.vim

https://bitbucket.org/mirror/mercurial/
Vim Script | 868 lines | 665 code | 38 blank | 165 comment | 195 complexity | 87ba632e4c666b94a8443a0a1292e89e MD5 | raw file
Possible License(s): GPL-2.0
  1. " VIM plugin for doing single, multi-patch or diff code reviews {{{
  2. " Home: http://www.vim.org/scripts/script.php?script_id=1563
  3. " Version : 0.2.2 "{{{
  4. " Author : Manpreet Singh < junkblocker@yahoo.com >
  5. " Copyright : 2006-2010 by Manpreet Singh
  6. " License : This file is placed in the public domain.
  7. " No warranties express or implied. Use at your own risk.
  8. "
  9. " Changelog :
  10. "
  11. " 0.2.2 - Security fixes by removing custom tempfile creation
  12. " - Removed need for DiffReviewCleanup/PatchReviewCleanup
  13. " - Better command execution error detection and display
  14. " - Improved diff view and folding by ignoring modelines
  15. " - Improved tab labels display
  16. "
  17. " 0.2.1 - Minor temp directory autodetection logic and cleanup
  18. "
  19. " 0.2 - Removed the need for filterdiff by implementing it in pure vim script
  20. " - Added DiffReview command for reverse (changed repository to
  21. " pristine state) reviews.
  22. " (PatchReview does pristine repository to patch review)
  23. " - DiffReview does automatic detection and generation of diffs for
  24. " various Source Control systems
  25. " - Skip load if VIM 7.0 or higher unavailable
  26. "
  27. " 0.1 - First released
  28. "}}}
  29. " Documentation: "{{{
  30. " ===========================================================================
  31. " This plugin allows single or multiple, patch or diff based code reviews to
  32. " be easily done in VIM. VIM has :diffpatch command to do single file reviews
  33. " but a) can not handle patch files containing multiple patches or b) do
  34. " automated diff generation for various version control systems. This plugin
  35. " attempts to provide those functionalities. It opens each changed / added or
  36. " removed file diff in new tabs.
  37. "
  38. " Installing:
  39. "
  40. " For a quick start, unzip patchreview.zip into your ~/.vim directory and
  41. " restart Vim.
  42. "
  43. " Details:
  44. "
  45. " Requirements:
  46. "
  47. " 1) VIM 7.0 or higher built with +diff option.
  48. "
  49. " 2) A gnu compatible patch command installed. This is the standard patch
  50. " command on Linux, Mac OS X, *BSD, Cygwin or /usr/bin/gpatch on newer
  51. " Solaris.
  52. "
  53. " 3) Optional (but recommended for speed)
  54. "
  55. " Install patchutils ( http://cyberelk.net/tim/patchutils/ ) for your
  56. " OS. For windows it is available from Cygwin
  57. "
  58. " http://www.cygwin.com
  59. "
  60. " or GnuWin32
  61. "
  62. " http://gnuwin32.sourceforge.net/
  63. "
  64. " Install:
  65. "
  66. " 1) Extract the zip in your $HOME/.vim or $VIM/vimfiles directory and
  67. " restart vim. The directory location relevant to your platform can be
  68. " seen by running :help add-global-plugin in vim.
  69. "
  70. " 2) Restart vim.
  71. "
  72. " Configuration:
  73. "
  74. " Optionally, specify the locations to these filterdiff and patch commands
  75. " and location of a temporary directory to use in your .vimrc.
  76. "
  77. " let g:patchreview_patch = '/path/to/gnu/patch'
  78. "
  79. " " If you are using filterdiff
  80. " let g:patchreview_filterdiff = '/path/to/filterdiff'
  81. "
  82. "
  83. " Usage:
  84. "
  85. " Please see :help patchreview or :help diffreview for details.
  86. "
  87. ""}}}
  88. " Enabled only during development
  89. " unlet! g:loaded_patchreview " DEBUG
  90. " unlet! g:patchreview_patch " DEBUG
  91. " unlet! g:patchreview_filterdiff " DEBUG
  92. " let g:patchreview_patch = 'patch' " DEBUG
  93. if v:version < 700
  94. finish
  95. endif
  96. if ! has('diff')
  97. call confirm('patchreview.vim plugin needs (G)VIM built with +diff support to work.')
  98. finish
  99. endif
  100. " load only once
  101. if (! exists('g:patchreview_debug') && exists('g:loaded_patchreview')) || &compatible
  102. finish
  103. endif
  104. let g:loaded_patchreview="0.2.2"
  105. let s:msgbufname = '-PatchReviewMessages-'
  106. function! <SID>Debug(str) "{{{
  107. if exists('g:patchreview_debug')
  108. Pecho 'DEBUG: ' . a:str
  109. endif
  110. endfunction
  111. command! -nargs=+ -complete=expression Debug call s:Debug(<args>)
  112. "}}}
  113. function! <SID>PR_wipeMsgBuf() "{{{
  114. let winnum = bufwinnr(s:msgbufname)
  115. if winnum != -1 " If the window is already open, jump to it
  116. let cur_winnr = winnr()
  117. if winnr() != winnum
  118. exe winnum . 'wincmd w'
  119. exe 'bw'
  120. exe cur_winnr . 'wincmd w'
  121. endif
  122. endif
  123. endfunction
  124. "}}}
  125. function! <SID>Pecho(...) "{{{
  126. " Usage: Pecho(msg, [return_to_original_window_flag])
  127. " default return_to_original_window_flag = 0
  128. "
  129. let cur_winnr = winnr()
  130. let winnum = bufwinnr(s:msgbufname)
  131. if winnum != -1 " If the window is already open, jump to it
  132. if winnr() != winnum
  133. exe winnum . 'wincmd w'
  134. endif
  135. else
  136. let bufnum = bufnr(s:msgbufname)
  137. if bufnum == -1
  138. let wcmd = s:msgbufname
  139. else
  140. let wcmd = '+buffer' . bufnum
  141. endif
  142. exe 'silent! botright 5split ' . wcmd
  143. endif
  144. setlocal modifiable
  145. setlocal buftype=nofile
  146. setlocal bufhidden=delete
  147. setlocal noswapfile
  148. setlocal nowrap
  149. setlocal nobuflisted
  150. if a:0 != 0
  151. silent! $put =a:1
  152. endif
  153. exe ':$'
  154. setlocal nomodifiable
  155. if a:0 > 1 && a:2
  156. exe cur_winnr . 'wincmd w'
  157. endif
  158. endfunction
  159. command! -nargs=+ -complete=expression Pecho call s:Pecho(<args>)
  160. "}}}
  161. function! <SID>PR_checkBinary(BinaryName) "{{{
  162. " Verify that BinaryName is specified or available
  163. if ! exists('g:patchreview_' . a:BinaryName)
  164. if executable(a:BinaryName)
  165. let g:patchreview_{a:BinaryName} = a:BinaryName
  166. return 1
  167. else
  168. Pecho 'g:patchreview_' . a:BinaryName . ' is not defined and ' . a:BinaryName . ' command could not be found on path.'
  169. Pecho 'Please define it in your .vimrc.'
  170. return 0
  171. endif
  172. elseif ! executable(g:patchreview_{a:BinaryName})
  173. Pecho 'Specified g:patchreview_' . a:BinaryName . ' [' . g:patchreview_{a:BinaryName} . '] is not executable.'
  174. return 0
  175. else
  176. return 1
  177. endif
  178. endfunction
  179. "}}}
  180. function! <SID>ExtractDiffsNative(...) "{{{
  181. " Sets g:patches = {'reason':'', 'patch':[
  182. " {
  183. " 'filename': filepath
  184. " 'type' : '+' | '-' | '!'
  185. " 'content' : patch text for this file
  186. " },
  187. " ...
  188. " ]}
  189. let g:patches = {'reason' : '', 'patch' : []}
  190. " TODO : User pointers into lines list rather then use collect
  191. if a:0 == 0
  192. let g:patches['reason'] = "ExtractDiffsNative expects at least a patchfile argument"
  193. return
  194. endif
  195. let patchfile = expand(a:1, ':p')
  196. if a:0 > 1
  197. let patch = a:2
  198. endif
  199. if ! filereadable(patchfile)
  200. let g:patches['reason'] = "File " . patchfile . " is not readable"
  201. return
  202. endif
  203. unlet! filterdiffcmd
  204. let filterdiffcmd = '' . g:patchreview_filterdiff . ' --list -s ' . patchfile
  205. let fileslist = split(system(filterdiffcmd), '[\r\n]')
  206. for filewithchangetype in fileslist
  207. if filewithchangetype !~ '^[!+-] '
  208. Pecho '*** Skipping review generation due to unknown change for [' . filewithchangetype . ']'
  209. continue
  210. endif
  211. unlet! this_patch
  212. let this_patch = {}
  213. unlet! relpath
  214. let relpath = substitute(filewithchangetype, '^. ', '', '')
  215. let this_patch['filename'] = relpath
  216. if filewithchangetype =~ '^! '
  217. let this_patch['type'] = '!'
  218. elseif filewithchangetype =~ '^+ '
  219. let this_patch['type'] = '+'
  220. elseif filewithchangetype =~ '^- '
  221. let this_patch['type'] = '-'
  222. endif
  223. unlet! filterdiffcmd
  224. let filterdiffcmd = '' . g:patchreview_filterdiff . ' -i ' . relpath . ' ' . patchfile
  225. let this_patch['content'] = split(system(filterdiffcmd), '[\n\r]')
  226. let g:patches['patch'] += [this_patch]
  227. Debug "Patch collected for " . relpath
  228. endfor
  229. endfunction
  230. "}}}
  231. function! <SID>ExtractDiffsPureVim(...) "{{{
  232. " Sets g:patches = {'reason':'', 'patch':[
  233. " {
  234. " 'filename': filepath
  235. " 'type' : '+' | '-' | '!'
  236. " 'content' : patch text for this file
  237. " },
  238. " ...
  239. " ]}
  240. let g:patches = {'reason' : '', 'patch' : []}
  241. " TODO : User pointers into lines list rather then use collect
  242. if a:0 == 0
  243. let g:patches['reason'] = "ExtractDiffsPureVim expects at least a patchfile argument"
  244. return
  245. endif
  246. let patchfile = expand(a:1, ':p')
  247. if a:0 > 1
  248. let patch = a:2
  249. endif
  250. if ! filereadable(patchfile)
  251. let g:patches['reason'] = "File " . patchfile . " is not readable"
  252. return
  253. endif
  254. call s:PR_wipeMsgBuf()
  255. let collect = []
  256. let linum = 0
  257. let lines = readfile(patchfile)
  258. let linescount = len(lines)
  259. State 'START'
  260. while linum < linescount
  261. let line = lines[linum]
  262. let linum += 1
  263. if State() == 'START'
  264. let mat = matchlist(line, '^--- \([^\t]\+\).*$')
  265. if ! empty(mat) && mat[1] != ''
  266. State 'MAYBE_UNIFIED_DIFF'
  267. let p_first_file = mat[1]
  268. let collect = [line]
  269. Debug line . State()
  270. continue
  271. endif
  272. let mat = matchlist(line, '^\*\*\* \([^\t]\+\).*$')
  273. if ! empty(mat) && mat[1] != ''
  274. State 'MAYBE_CONTEXT_DIFF'
  275. let p_first_file = mat[1]
  276. let collect = [line]
  277. Debug line . State()
  278. continue
  279. endif
  280. continue
  281. elseif State() == 'MAYBE_CONTEXT_DIFF'
  282. let mat = matchlist(line, '^--- \([^\t]\+\).*$')
  283. if empty(mat) || mat[1] == ''
  284. State 'START'
  285. let linum -= 1
  286. continue
  287. Debug 'Back to square one ' . line()
  288. endif
  289. let p_second_file = mat[1]
  290. if p_first_file == '/dev/null'
  291. if p_second_file == '/dev/null'
  292. let g:patches['reason'] = "Malformed diff found at line " . linum
  293. return
  294. endif
  295. let p_type = '+'
  296. let filepath = p_second_file
  297. else
  298. if p_second_file == '/dev/null'
  299. let p_type = '-'
  300. let filepath = p_first_file
  301. else
  302. let p_type = '!'
  303. let filepath = p_first_file
  304. endif
  305. endif
  306. State 'EXPECT_15_STARS'
  307. let collect += [line]
  308. Debug line . State()
  309. elseif State() == 'EXPECT_15_STARS'
  310. if line !~ '^*\{15}$'
  311. State 'START'
  312. let linum -= 1
  313. Debug line . State()
  314. continue
  315. endif
  316. State 'EXPECT_CONTEXT_CHUNK_HEADER_1'
  317. let collect += [line]
  318. Debug line . State()
  319. elseif State() == 'EXPECT_CONTEXT_CHUNK_HEADER_1'
  320. let mat = matchlist(line, '^\*\*\* \(\d\+,\)\?\(\d\+\) \*\*\*\*$')
  321. if empty(mat) || mat[1] == ''
  322. State 'START'
  323. let linum -= 1
  324. Debug line . State()
  325. continue
  326. endif
  327. let collect += [line]
  328. State 'SKIP_CONTEXT_STUFF_1'
  329. Debug line . State()
  330. continue
  331. elseif State() == 'SKIP_CONTEXT_STUFF_1'
  332. if line !~ '^[ !+].*$'
  333. let mat = matchlist(line, '^--- \(\d\+\),\(\d\+\) ----$')
  334. if ! empty(mat) && mat[1] != '' && mat[2] != ''
  335. let goal_count = mat[2] - mat[1] + 1
  336. let c_count = 0
  337. State 'READ_CONTEXT_CHUNK'
  338. let collect += [line]
  339. Debug line . State() . " Goal count set to " . goal_count
  340. continue
  341. endif
  342. State 'START'
  343. let linum -= 1
  344. Debug line . State()
  345. continue
  346. endif
  347. let collect += [line]
  348. continue
  349. elseif State() == 'READ_CONTEXT_CHUNK'
  350. let c_count += 1
  351. if c_count == goal_count
  352. let collect += [line]
  353. State 'BACKSLASH_OR_CRANGE_EOF'
  354. continue
  355. else " goal not met yet
  356. let mat = matchlist(line, '^\([\\!+ ]\).*$')
  357. if empty(mat) || mat[1] == ''
  358. let linum -= 1
  359. State 'START'
  360. Debug line . State()
  361. continue
  362. endif
  363. let collect += [line]
  364. continue
  365. endif
  366. elseif State() == 'BACKSLASH_OR_CRANGE_EOF'
  367. if line =~ '^\\ No newline.*$' " XXX: Can we go to another chunk from here??
  368. let collect += [line]
  369. let this_patch = {}
  370. let this_patch['filename'] = filepath
  371. let this_patch['type'] = p_type
  372. let this_patch['content'] = collect
  373. let g:patches['patch'] += [this_patch]
  374. Debug "Patch collected for " . filepath
  375. State 'START'
  376. continue
  377. endif
  378. if line =~ '^\*\{15}$'
  379. let collect += [line]
  380. State 'EXPECT_CONTEXT_CHUNK_HEADER_1'
  381. Debug line . State()
  382. continue
  383. endif
  384. let this_patch = {}
  385. let this_patch['filename'] = filepath
  386. let this_patch['type'] = p_type
  387. let this_patch['content'] = collect
  388. let g:patches['patch'] += [this_patch]
  389. let linum -= 1
  390. State 'START'
  391. Debug "Patch collected for " . filepath
  392. Debug line . State()
  393. continue
  394. elseif State() == 'MAYBE_UNIFIED_DIFF'
  395. let mat = matchlist(line, '^+++ \([^\t]\+\).*$')
  396. if empty(mat) || mat[1] == ''
  397. State 'START'
  398. let linum -= 1
  399. Debug line . State()
  400. continue
  401. endif
  402. let p_second_file = mat[1]
  403. if p_first_file == '/dev/null'
  404. if p_second_file == '/dev/null'
  405. let g:patches['reason'] = "Malformed diff found at line " . linum
  406. return
  407. endif
  408. let p_type = '+'
  409. let filepath = p_second_file
  410. else
  411. if p_second_file == '/dev/null'
  412. let p_type = '-'
  413. let filepath = p_first_file
  414. else
  415. let p_type = '!'
  416. let filepath = p_first_file
  417. endif
  418. endif
  419. State 'EXPECT_UNIFIED_RANGE_CHUNK'
  420. let collect += [line]
  421. Debug line . State()
  422. continue
  423. elseif State() == 'EXPECT_UNIFIED_RANGE_CHUNK'
  424. let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$')
  425. if ! empty(mat)
  426. let old_goal_count = mat[2]
  427. let new_goal_count = mat[4]
  428. let o_count = 0
  429. let n_count = 0
  430. Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count
  431. State 'READ_UNIFIED_CHUNK'
  432. let collect += [line]
  433. Debug line . State()
  434. continue
  435. endif
  436. State 'START'
  437. Debug line . State()
  438. continue
  439. elseif State() == 'READ_UNIFIED_CHUNK'
  440. if o_count == old_goal_count && n_count == new_goal_count
  441. if line =~ '^\\.*$' " XXX: Can we go to another chunk from here??
  442. let collect += [line]
  443. let this_patch = {}
  444. let this_patch['filename'] = filepath
  445. let this_patch['type'] = p_type
  446. let this_patch['content'] = collect
  447. let g:patches['patch'] += [this_patch]
  448. Debug "Patch collected for " . filepath
  449. State 'START'
  450. continue
  451. endif
  452. let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$')
  453. if ! empty(mat)
  454. let old_goal_count = mat[2]
  455. let new_goal_count = mat[4]
  456. let o_count = 0
  457. let n_count = 0
  458. Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count
  459. let collect += [line]
  460. Debug line . State()
  461. continue
  462. endif
  463. let this_patch = {}
  464. let this_patch['filename'] = filepath
  465. let this_patch['type'] = p_type
  466. let this_patch['content'] = collect
  467. let g:patches['patch'] += [this_patch]
  468. Debug "Patch collected for " . filepath
  469. let linum -= 1
  470. State 'START'
  471. Debug line . State()
  472. continue
  473. else " goal not met yet
  474. let mat = matchlist(line, '^\([\\+ -]\).*$')
  475. if empty(mat) || mat[1] == ''
  476. let linum -= 1
  477. State 'START'
  478. continue
  479. endif
  480. let chr = mat[1]
  481. if chr == '+'
  482. let n_count += 1
  483. endif
  484. if chr == ' '
  485. let o_count += 1
  486. let n_count += 1
  487. endif
  488. if chr == '-'
  489. let o_count += 1
  490. endif
  491. let collect += [line]
  492. Debug line . State()
  493. continue
  494. endif
  495. else
  496. 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>"
  497. return
  498. endif
  499. endwhile
  500. "Pecho State()
  501. if (State() == 'READ_CONTEXT_CHUNK' && c_count == goal_count) || (State() == 'READ_UNIFIED_CHUNK' && n_count == new_goal_count && o_count == old_goal_count)
  502. let this_patch = {}
  503. let this_patch['filename'] = filepath
  504. let this_patch['type'] = p_type
  505. let this_patch['content'] = collect
  506. let g:patches['patch'] += [this_patch]
  507. Debug "Patch collected for " . filepath
  508. endif
  509. return
  510. endfunction
  511. "}}}
  512. function! State(...) " For easy manipulation of diff extraction state "{{{
  513. if a:0 != 0
  514. let s:STATE = a:1
  515. else
  516. if ! exists('s:STATE')
  517. let s:STATE = 'START'
  518. endif
  519. return s:STATE
  520. endif
  521. endfunction
  522. com! -nargs=+ -complete=expression State call State(<args>)
  523. "}}}
  524. function! <SID>PatchReview(...) "{{{
  525. let s:save_shortmess = &shortmess
  526. let s:save_aw = &autowrite
  527. let s:save_awa = &autowriteall
  528. set shortmess=aW
  529. call s:PR_wipeMsgBuf()
  530. let s:reviewmode = 'patch'
  531. call s:_GenericReview(a:000)
  532. let &autowriteall = s:save_awa
  533. let &autowrite = s:save_aw
  534. let &shortmess = s:save_shortmess
  535. endfunction
  536. "}}}
  537. function! <SID>_GenericReview(argslist) "{{{
  538. " diff mode:
  539. " arg1 = patchfile
  540. " arg2 = strip count
  541. " patch mode:
  542. " arg1 = patchfile
  543. " arg2 = strip count
  544. " arg3 = directory
  545. " VIM 7+ required
  546. if version < 700
  547. Pecho 'This plugin needs VIM 7 or higher'
  548. return
  549. endif
  550. " +diff required
  551. if ! has('diff')
  552. Pecho 'This plugin needs VIM built with +diff feature.'
  553. return
  554. endif
  555. if s:reviewmode == 'diff'
  556. let patch_R_option = ' -t -R '
  557. elseif s:reviewmode == 'patch'
  558. let patch_R_option = ''
  559. else
  560. Pecho 'Fatal internal error in patchreview.vim plugin'
  561. return
  562. endif
  563. " Check passed arguments
  564. if len(a:argslist) == 0
  565. Pecho 'PatchReview command needs at least one argument specifying a patchfile path.'
  566. return
  567. endif
  568. let StripCount = 0
  569. if len(a:argslist) >= 1 && ((s:reviewmode == 'patch' && len(a:argslist) <= 3) || (s:reviewmode == 'diff' && len(a:argslist) == 2))
  570. let PatchFilePath = expand(a:argslist[0], ':p')
  571. if ! filereadable(PatchFilePath)
  572. Pecho 'File [' . PatchFilePath . '] is not accessible.'
  573. return
  574. endif
  575. if len(a:argslist) >= 2 && s:reviewmode == 'patch'
  576. let s:SrcDirectory = expand(a:argslist[1], ':p')
  577. if ! isdirectory(s:SrcDirectory)
  578. Pecho '[' . s:SrcDirectory . '] is not a directory'
  579. return
  580. endif
  581. try
  582. " Command line has already escaped the path
  583. exe 'cd ' . s:SrcDirectory
  584. catch /^.*E344.*/
  585. Pecho 'Could not change to directory [' . s:SrcDirectory . ']'
  586. return
  587. endtry
  588. endif
  589. if s:reviewmode == 'diff'
  590. " passed in by default
  591. let StripCount = eval(a:argslist[1])
  592. elseif s:reviewmode == 'patch'
  593. let StripCount = 1
  594. " optional strip count
  595. if len(a:argslist) == 3
  596. let StripCount = eval(a:argslist[2])
  597. endif
  598. endif
  599. else
  600. if s:reviewmode == 'patch'
  601. Pecho 'PatchReview command needs at most three arguments: patchfile path, optional source directory path and optional strip count.'
  602. elseif s:reviewmode == 'diff'
  603. Pecho 'DiffReview command accepts no arguments.'
  604. endif
  605. return
  606. endif
  607. " Verify that patch command and temporary directory are available or specified
  608. if ! s:PR_checkBinary('patch')
  609. return
  610. endif
  611. " Requirements met, now execute
  612. let PatchFilePath = fnamemodify(PatchFilePath, ':p')
  613. if s:reviewmode == 'patch'
  614. Pecho 'Patch file : ' . PatchFilePath
  615. endif
  616. Pecho 'Source directory: ' . getcwd()
  617. Pecho '------------------'
  618. if s:PR_checkBinary('filterdiff')
  619. Debug "Using filterdiff"
  620. call s:ExtractDiffsNative(PatchFilePath)
  621. else
  622. Debug "Using own diff extraction (slower)"
  623. call s:ExtractDiffsPureVim(PatchFilePath)
  624. endif
  625. for patch in g:patches['patch']
  626. if patch.type !~ '^[!+-]$'
  627. Pecho '*** Skipping review generation due to unknown change [' . patch.type . ']', 1
  628. continue
  629. endif
  630. unlet! relpath
  631. let relpath = patch.filename
  632. " XXX: svn diff and hg diff produce different kind of outputs, one requires
  633. " XXX: stripping but the other doesn't. We need to take care of that
  634. let stripmore = StripCount
  635. let StrippedRelativeFilePath = relpath
  636. while stripmore > 0
  637. " strip one
  638. let StrippedRelativeFilePath = substitute(StrippedRelativeFilePath, '^[^\\\/]\+[^\\\/]*[\\\/]' , '' , '')
  639. let stripmore -= 1
  640. endwhile
  641. if patch.type == '!'
  642. if s:reviewmode == 'patch'
  643. let msgtype = 'Patch modifies file: '
  644. elseif s:reviewmode == 'diff'
  645. let msgtype = 'File has changes: '
  646. endif
  647. elseif patch.type == '+'
  648. if s:reviewmode == 'patch'
  649. let msgtype = 'Patch adds file : '
  650. elseif s:reviewmode == 'diff'
  651. let msgtype = 'New file : '
  652. endif
  653. elseif patch.type == '-'
  654. if s:reviewmode == 'patch'
  655. let msgtype = 'Patch removes file : '
  656. elseif s:reviewmode == 'diff'
  657. let msgtype = 'Removed file : '
  658. endif
  659. endif
  660. let bufnum = bufnr(relpath)
  661. if buflisted(bufnum) && getbufvar(bufnum, '&mod')
  662. Pecho 'Old buffer for file [' . relpath . '] exists in modified state. Skipping review.', 1
  663. continue
  664. endif
  665. let tmpname = tempname()
  666. " write patch for patch.filename into tmpname
  667. call writefile(patch.content, tmpname)
  668. if patch.type == '+' && s:reviewmode == 'patch'
  669. let inputfile = ''
  670. let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
  671. elseif patch.type == '+' && s:reviewmode == 'diff'
  672. let inputfile = ''
  673. unlet! patchcmd
  674. else
  675. let inputfile = expand(StrippedRelativeFilePath, ':p')
  676. let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
  677. endif
  678. if exists('patchcmd')
  679. let v:errmsg = ''
  680. Debug patchcmd
  681. silent exe patchcmd
  682. if v:errmsg != '' || v:shell_error
  683. Pecho 'ERROR: Could not execute patch command.'
  684. Pecho 'ERROR: ' . patchcmd
  685. Pecho 'ERROR: ' . v:errmsg
  686. Pecho 'ERROR: Diff skipped.'
  687. continue
  688. endif
  689. endif
  690. call delete(tmpname)
  691. let s:origtabpagenr = tabpagenr()
  692. silent! exe 'tabedit ' . StrippedRelativeFilePath
  693. if exists('patchcmd')
  694. " modelines in loaded files mess with diff comparision
  695. let s:keep_modeline=&modeline
  696. let &modeline=0
  697. silent! exe 'vert diffsplit ' . tmpname . '.file'
  698. setlocal buftype=nofile
  699. setlocal noswapfile
  700. setlocal syntax=none
  701. setlocal bufhidden=delete
  702. setlocal nobuflisted
  703. setlocal modifiable
  704. setlocal nowrap
  705. " Remove buffer name
  706. silent! 0f
  707. " Switch to original to get a nice tab title
  708. silent! wincmd p
  709. let &modeline=s:keep_modeline
  710. else
  711. silent! exe 'vnew'
  712. endif
  713. if filereadable(tmpname . '.file.rej')
  714. silent! exe 'topleft 5split ' . tmpname . '.file.rej'
  715. Pecho msgtype . '*** REJECTED *** ' . relpath, 1
  716. else
  717. Pecho msgtype . ' ' . relpath, 1
  718. endif
  719. silent! exe 'tabn ' . s:origtabpagenr
  720. endfor
  721. Pecho '-----'
  722. Pecho 'Done.'
  723. endfunction
  724. "}}}
  725. function! <SID>DiffReview(...) "{{{
  726. let s:save_shortmess = &shortmess
  727. set shortmess=aW
  728. call s:PR_wipeMsgBuf()
  729. let vcsdict = {
  730. \'Mercurial' : {'dir' : '.hg', 'binary' : 'hg', 'diffargs' : 'diff' , 'strip' : 1},
  731. \'Bazaar-NG' : {'dir' : '.bzr', 'binary' : 'bzr', 'diffargs' : 'diff' , 'strip' : 0},
  732. \'monotone' : {'dir' : '_MTN', 'binary' : 'mtn', 'diffargs' : 'diff --unified', 'strip' : 0},
  733. \'Subversion' : {'dir' : '.svn', 'binary' : 'svn', 'diffargs' : 'diff' , 'strip' : 0},
  734. \'cvs' : {'dir' : 'CVS', 'binary' : 'cvs', 'diffargs' : '-q diff -u' , 'strip' : 0},
  735. \}
  736. unlet! s:theDiffCmd
  737. unlet! l:vcs
  738. if ! exists('g:patchreview_diffcmd')
  739. for key in keys(vcsdict)
  740. if isdirectory(vcsdict[key]['dir'])
  741. if ! s:PR_checkBinary(vcsdict[key]['binary'])
  742. Pecho 'Current directory looks like a ' . vcsdict[key] . ' repository but ' . vcsdist[key]['binary'] . ' command was not found on path.'
  743. let &shortmess = s:save_shortmess
  744. return
  745. else
  746. let s:theDiffCmd = vcsdict[key]['binary'] . ' ' . vcsdict[key]['diffargs']
  747. let strip = vcsdict[key]['strip']
  748. Pecho 'Using [' . s:theDiffCmd . '] to generate diffs for this ' . key . ' review.'
  749. let &shortmess = s:save_shortmess
  750. let l:vcs = vcsdict[key]['binary']
  751. break
  752. endif
  753. else
  754. continue
  755. endif
  756. endfor
  757. else
  758. let s:theDiffCmd = g:patchreview_diffcmd
  759. let strip = 0
  760. endif
  761. if ! exists('s:theDiffCmd')
  762. Pecho 'Please define g:patchreview_diffcmd and make sure you are in a VCS controlled top directory.'
  763. let &shortmess = s:save_shortmess
  764. return
  765. endif
  766. let outfile = tempname()
  767. let cmd = s:theDiffCmd . ' > "' . outfile . '"'
  768. let v:errmsg = ''
  769. let cout = system(cmd)
  770. if v:errmsg == '' && exists('l:vcs') && l:vcs == 'cvs' && v:shell_error == 1
  771. " Ignoring CVS non-error
  772. elseif v:errmsg != '' || v:shell_error
  773. Pecho v:errmsg
  774. Pecho 'Could not execute [' . s:theDiffCmd . ']'
  775. Pecho 'Error code: ' . v:shell_error
  776. Pecho cout
  777. Pecho 'Diff review aborted.'
  778. let &shortmess = s:save_shortmess
  779. return
  780. endif
  781. let s:reviewmode = 'diff'
  782. call s:_GenericReview([outfile, strip])
  783. let &shortmess = s:save_shortmess
  784. endfunction
  785. "}}}
  786. " End user commands "{{{
  787. "============================================================================
  788. " :PatchReview
  789. command! -nargs=* -complete=file PatchReview call s:PatchReview (<f-args>)
  790. " :DiffReview
  791. command! -nargs=0 DiffReview call s:DiffReview()
  792. "}}}
  793. " Development "{{{
  794. if exists('g:patchreview_debug')
  795. " Tests
  796. function! <SID>PRExtractTestNative(...)
  797. "let patchfiles = glob(expand(a:1) . '/?*')
  798. "for fname in split(patchfiles)
  799. call s:PR_wipeMsgBuf()
  800. let fname = a:1
  801. call s:ExtractDiffsNative(fname)
  802. for patch in g:patches['patch']
  803. for line in patch.content
  804. Pecho line
  805. endfor
  806. endfor
  807. "endfor
  808. endfunction
  809. function! <SID>PRExtractTestVim(...)
  810. "let patchfiles = glob(expand(a:1) . '/?*')
  811. "for fname in split(patchfiles)
  812. call s:PR_wipeMsgBuf()
  813. let fname = a:1
  814. call s:ExtractDiffsPureVim(fname)
  815. for patch in g:patches['patch']
  816. for line in patch.content
  817. Pecho line
  818. endfor
  819. endfor
  820. "endfor
  821. endfunction
  822. command! -nargs=+ -complete=file PRTestVim call s:PRExtractTestVim(<f-args>)
  823. command! -nargs=+ -complete=file PRTestNative call s:PRExtractTestNative(<f-args>)
  824. endif
  825. "}}}
  826. " modeline
  827. " vim: set et fdl=0 fdm=marker fenc=latin ff=unix ft=vim sw=2 sts=0 ts=2 textwidth=78 nowrap :