changeset 10549:b97736016273 stable

Merge with hg-i18n
author Martin Geisler <mg@lazybytes.net>
date Thu, 25 Feb 2010 17:06:32 +0100
parents bae9bb09166b (diff) 167030b1757b (current diff)
children 8036bc1871c2
files
diffstat 14 files changed, 1218 insertions(+), 283 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/shrink-revlog.py	Wed Feb 24 19:47:51 2010 +0100
+++ b/contrib/shrink-revlog.py	Thu Feb 25 17:06:32 2010 +0100
@@ -19,7 +19,7 @@
 # e.g. by comparing "before" and "after" states of random changesets
 # (maybe: export before, shrink, export after, diff).
 
-import os, tempfile
+import os, tempfile, errno
 from mercurial import revlog, transaction, node, util
 from mercurial import changegroup
 from mercurial.i18n import _
@@ -91,9 +91,19 @@
     finally:
         ui.progress(_('writing'), None, len(order))
 
-def report(ui, olddatafn, newdatafn):
-    oldsize = float(os.stat(olddatafn).st_size)
-    newsize = float(os.stat(newdatafn).st_size)
+def report(ui, r1, r2):
+    def getsize(r):
+        s = 0
+        for fn in (r.indexfile, r.datafile):
+            try:
+                s += os.stat(fn).st_size
+            except OSError, inst:
+                if inst.errno != errno.ENOENT:
+                    raise
+        return s
+
+    oldsize = float(getsize(r1))
+    newsize = float(getsize(r2))
 
     # argh: have to pass an int to %d, because a float >= 2^32
     # blows up under Python 2.5 or earlier
@@ -129,17 +139,23 @@
             raise util.Abort(_('--revlog option must specify a revlog in %s, '
                                'not %s') % (store, indexfn))
 
-    datafn = indexfn[:-2] + '.d'
     if not os.path.exists(indexfn):
         raise util.Abort(_('no such file: %s') % indexfn)
     if '00changelog' in indexfn:
         raise util.Abort(_('shrinking the changelog '
                            'will corrupt your repository'))
-    if not os.path.exists(datafn):
-        # This is just a lazy shortcut because I can't be bothered to
-        # handle all the special cases that entail from no .d file.
-        raise util.Abort(_('%s does not exist: revlog not big enough '
-                           'to be worth shrinking') % datafn)
+
+    ui.write(_('shrinking %s\n') % indexfn)
+    prefix = os.path.basename(indexfn)[:-1]
+    (tmpfd, tmpindexfn) = tempfile.mkstemp(dir=os.path.dirname(indexfn),
+                                           prefix=prefix,
+                                           suffix='.i')
+    os.close(tmpfd)
+
+    r1 = revlog.revlog(util.opener(os.getcwd(), audit=False), indexfn)
+    r2 = revlog.revlog(util.opener(os.getcwd(), audit=False), tmpindexfn)
+
+    datafn, tmpdatafn = r1.datafile, r2.datafile
 
     oldindexfn = indexfn + '.old'
     olddatafn = datafn + '.old'
@@ -150,17 +166,6 @@
                            'exists from a previous run; please clean up '
                            'before running again') % (oldindexfn, olddatafn))
 
-    ui.write(_('shrinking %s\n') % indexfn)
-    prefix = os.path.basename(indexfn)[:-1]
-    (tmpfd, tmpindexfn) = tempfile.mkstemp(dir=os.path.dirname(indexfn),
-                                           prefix=prefix,
-                                           suffix='.i')
-    tmpdatafn = tmpindexfn[:-2] + '.d'
-    os.close(tmpfd)
-
-    r1 = revlog.revlog(util.opener(os.getcwd(), audit=False), indexfn)
-    r2 = revlog.revlog(util.opener(os.getcwd(), audit=False), tmpindexfn)
-
     # Don't use repo.transaction(), because then things get hairy with
     # paths: some need to be relative to .hg, and some need to be
     # absolute. Doing it this way keeps things simple: everything is an
@@ -170,30 +175,44 @@
                                  open,
                                  repo.sjoin('journal'))
 
+    def ignoremissing(func):
+        def f(*args, **kw):
+            try:
+                return func(*args, **kw)
+            except OSError, inst:
+                if inst.errno != errno.ENOENT:
+                    raise
+        return f
+
     try:
         try:
             order = toposort(ui, r1)
             writerevs(ui, r1, r2, order, tr)
-            report(ui, datafn, tmpdatafn)
+            report(ui, r1, r2)
             tr.close()
         except:
             # Abort transaction first, so we truncate the files before
             # deleting them.
             tr.abort()
-            if os.path.exists(tmpindexfn):
-                os.unlink(tmpindexfn)
-            if os.path.exists(tmpdatafn):
-                os.unlink(tmpdatafn)
+            for fn in (tmpindexfn, tmpdatafn):
+                ignoremissing(os.unlink)(fn)
             raise
         if not opts.get('dry_run'):
-            # Racy since both files cannot be renamed atomically
+            # racy, both files cannot be renamed atomically
+            # copy files
             util.os_link(indexfn, oldindexfn)
-            util.os_link(datafn, olddatafn)
+            ignoremissing(util.os_link)(datafn, olddatafn)
+            # rename
             util.rename(tmpindexfn, indexfn)
-            util.rename(tmpdatafn, datafn)
+            try:
+                util.rename(tmpdatafn, datafn)
+            except OSError, inst:
+                if inst.errno != errno.ENOENT:
+                    raise
+                ignoremissing(os.unlink)(datafn)
         else:
-            os.unlink(tmpindexfn)
-            os.unlink(tmpdatafn)
+            for fn in (tmpindexfn, tmpdatafn):
+                ignoremissing(os.unlink)(fn)
     finally:
         lock.release()
 
--- a/contrib/vim/patchreview.txt	Wed Feb 24 19:47:51 2010 +0100
+++ b/contrib/vim/patchreview.txt	Thu Feb 25 17:06:32 2010 +0100
@@ -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 19:47:51 2010 +0100
+++ b/contrib/vim/patchreview.vim	Thu Feb 25 17:06:32 2010 +0100
@@ -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 :
--- a/hgext/gpg.py	Wed Feb 24 19:47:51 2010 +0100
+++ b/hgext/gpg.py	Thu Feb 25 17:06:32 2010 +0100
@@ -6,7 +6,7 @@
 '''commands to sign and verify changesets'''
 
 import os, tempfile, binascii
-from mercurial import util, commands, match, cmdutil
+from mercurial import util, commands, match
 from mercurial import node as hgnode
 from mercurial.i18n import _
 
@@ -237,7 +237,7 @@
         repo.opener("localsigs", "ab").write(sigmessage)
         return
 
-    msigs = cmdutil.matchfiles(repo, ['.hgsigs'])
+    msigs = match.exact(repo.root, '', ['.hgsigs'])
     s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
     if util.any(s) and not opts["force"]:
         raise util.Abort(_("working copy of .hgsigs is changed "
--- a/hgext/progress.py	Wed Feb 24 19:47:51 2010 +0100
+++ b/hgext/progress.py	Thu Feb 25 17:06:32 2010 +0100
@@ -170,6 +170,10 @@
 sharedprog = None
 
 def uisetup(ui):
+    # Apps that derive a class from ui.ui() can use
+    # setconfig('progress', 'disable', 'True') to disable this extension
+    if ui.configbool('progress', 'disable'):
+        return
     if ui.interactive() and not ui.debugflag and not ui.quiet:
         # we instantiate one globally shared progress bar to avoid
         # competing progress bars when multiple UI objects get created
--- a/mercurial/commands.py	Wed Feb 24 19:47:51 2010 +0100
+++ b/mercurial/commands.py	Thu Feb 25 17:06:32 2010 +0100
@@ -3334,7 +3334,8 @@
      _('do not prompt, assume \'yes\' for any required answers')),
     ('q', 'quiet', None, _('suppress output')),
     ('v', 'verbose', None, _('enable additional output')),
-    ('', 'config', [], _('set/override config option')),
+    ('', 'config', [],
+     _('set/override config option (use \'section.name=value\')')),
     ('', 'debug', None, _('enable debugging output')),
     ('', 'debugger', None, _('start debugger')),
     ('', 'encoding', encoding.encoding, _('set the charset encoding')),
--- a/mercurial/httprepo.py	Wed Feb 24 19:47:51 2010 +0100
+++ b/mercurial/httprepo.py	Thu Feb 25 17:06:32 2010 +0100
@@ -239,7 +239,8 @@
                 except ValueError, err:
                     raise error.ResponseError(
                             _('push failed (unexpected response):'), resp)
-                self.ui.write(output)
+                for l in output.splitlines(True):
+                    self.ui.status(_('remote: '), l)
                 return ret
             except socket.error, err:
                 if err[0] in (errno.ECONNRESET, errno.EPIPE):
--- a/mercurial/localrepo.py	Wed Feb 24 19:47:51 2010 +0100
+++ b/mercurial/localrepo.py	Thu Feb 25 17:06:32 2010 +0100
@@ -622,16 +622,19 @@
         finally:
             release(lock, wlock)
 
-    def invalidate(self):
-        for a in "changelog manifest".split():
-            if a in self.__dict__:
-                delattr(self, a)
+    def invalidatecaches(self):
         self._tags = None
         self._tagtypes = None
         self.nodetagscache = None
         self._branchcache = None # in UTF-8
         self._branchcachetip = None
 
+    def invalidate(self):
+        for a in "changelog manifest".split():
+            if a in self.__dict__:
+                delattr(self, a)
+        self.invalidatecaches()
+
     def _lock(self, lockname, wait, releasefn, acquirefn, desc):
         try:
             l = lock.lock(lockname, 0, releasefn, desc=desc)
@@ -957,7 +960,7 @@
         # head, refresh the tag cache, then immediately add a new head.
         # But I think doing it this way is necessary for the "instant
         # tag cache retrieval" case to work.
-        tags_.findglobaltags(self.ui, self, {}, {})
+        self.invalidatecaches()
 
     def walk(self, match, node=None):
         '''
--- a/tests/test-branchmap.out	Wed Feb 24 19:47:51 2010 +0100
+++ b/tests/test-branchmap.out	Thu Feb 25 17:06:32 2010 +0100
@@ -17,10 +17,10 @@
 
 pushing to http://localhost:PORT
 searching for changes
-adding changesets
-adding manifests
-adding file changes
-added 1 changesets with 1 changes to 1 files
+remote: adding changesets
+remote: adding manifests
+remote: adding file changes
+remote: added 1 changesets with 1 changes to 1 files
 changeset:   1:58e7c90d67cb
 branch:      æ
 tag:         tip
--- a/tests/test-extension.out	Wed Feb 24 19:47:51 2010 +0100
+++ b/tests/test-extension.out	Thu Feb 25 17:06:32 2010 +0100
@@ -56,7 +56,7 @@
  -y --noninteractive  do not prompt, assume 'yes' for any required answers
  -q --quiet           suppress output
  -v --verbose         enable additional output
-    --config          set/override config option
+    --config          set/override config option (use 'section.name=value')
     --debug           enable debugging output
     --debugger        start debugger
     --encoding        set the charset encoding (default: ascii)
@@ -82,7 +82,7 @@
  -y --noninteractive  do not prompt, assume 'yes' for any required answers
  -q --quiet           suppress output
  -v --verbose         enable additional output
-    --config          set/override config option
+    --config          set/override config option (use 'section.name=value')
     --debug           enable debugging output
     --debugger        start debugger
     --encoding        set the charset encoding (default: ascii)
--- a/tests/test-help.out	Wed Feb 24 19:47:51 2010 +0100
+++ b/tests/test-help.out	Thu Feb 25 17:06:32 2010 +0100
@@ -220,7 +220,7 @@
  -y --noninteractive  do not prompt, assume 'yes' for any required answers
  -q --quiet           suppress output
  -v --verbose         enable additional output
-    --config          set/override config option
+    --config          set/override config option (use 'section.name=value')
     --debug           enable debugging output
     --debugger        start debugger
     --encoding        set the charset encoding (default: ascii)
@@ -288,7 +288,7 @@
  -y --noninteractive  do not prompt, assume 'yes' for any required answers
  -q --quiet           suppress output
  -v --verbose         enable additional output
-    --config          set/override config option
+    --config          set/override config option (use 'section.name=value')
     --debug           enable debugging output
     --debugger        start debugger
     --encoding        set the charset encoding (default: ascii)
--- a/tests/test-push-http.out	Wed Feb 24 19:47:51 2010 +0100
+++ b/tests/test-push-http.out	Thu Feb 25 17:06:32 2010 +0100
@@ -4,7 +4,7 @@
 % expect ssl error
 pushing to http://localhost:$HGPORT/
 searching for changes
-ssl required
+remote: ssl required
 % serve errors
 % expect authorization error
 abort: authorization failed
@@ -19,10 +19,10 @@
 % expect success
 pushing to http://localhost:$HGPORT/
 searching for changes
-adding changesets
-adding manifests
-adding file changes
-added 1 changesets with 1 changes to 1 files
+remote: adding changesets
+remote: adding manifests
+remote: adding file changes
+remote: added 1 changesets with 1 changes to 1 files
 % serve errors
 changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http 
 rolling back last transaction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rebase-cache	Thu Feb 25 17:06:32 2010 +0100
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+createrepo() {
+    rm -rf repo
+    hg init repo
+    cd repo
+
+    echo "a" > a
+    hg commit -d '0 0' -A -m 'A'
+
+    hg branch branch1
+    hg commit -d '1 0' -m 'Branch1'
+
+    echo "b" > b
+    hg commit -A -d '2 0' -m 'B'
+
+    hg up 0
+    hg branch branch2
+    hg commit -d '3 0' -m 'Branch2'
+
+    echo "c" > C
+    hg commit -A -d '4 0' -m 'C'
+
+    hg up 2
+    hg branch -f branch2
+    echo "d" > d
+    hg commit -A -d '5 0' -m 'D'
+
+    echo "e" > e
+    hg commit -A -d '6 0' -m 'E'
+
+    hg update default
+
+    hg branch branch3
+    hg commit -d '7 0' -m 'Branch3'
+
+    echo "f" > f
+    hg commit -A -d '8 0' -m 'F'
+}
+
+echo
+createrepo > /dev/null 2>&1
+hg --config extensions.hgext.graphlog= glog --template '{rev}:{node|short} {desc} branch: {branches}\n'
+
+echo
+echo '% Branches'
+hg branches
+
+echo
+echo '% Heads'
+hg heads --template '{rev}:{node|short} {desc} branch: {branches}\n'
+
+echo
+echo '% Rebase part of branch2 (5-6) onto branch3 (8)'
+hg --config extensions.hgext.rebase= rebase --detach -s 5 -d 8 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+
+echo
+echo '% Branches'
+hg branches
+
+echo
+echo '% Heads'
+hg heads --template '{rev}:{node|short} {desc} branch: {branches}\n'
+
+echo
+hg --config extensions.hgext.graphlog= glog --template '{rev}:{node|short} {desc} branch: {branches}\n'
+
+echo
+echo '% Rebase head of branch3 (8) onto branch2 (6)'
+createrepo > /dev/null 2>&1
+hg --config extensions.hgext.graphlog= glog --template '{rev}:{node|short} {desc} branch: {branches}\n'
+
+hg --config extensions.hgext.rebase= rebase --detach -s 8 -d 6 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+
+echo
+echo '% Branches'
+hg branches
+
+echo
+echo '% Heads'
+hg heads --template '{rev}:{node|short} {desc} branch: {branches}\n'
+
+echo
+hg --config extensions.hgext.graphlog= glog --template '{rev}:{node|short} {desc} branch: {branches}\n'
+hg verify -q
+
+echo
+echo '% Rebase entire branch3 (7-8) onto branch2 (6)'
+createrepo > /dev/null 2>&1
+hg --config extensions.hgext.graphlog= glog --template '{rev}:{node|short} {desc} branch: {branches}\n'
+
+hg --config extensions.hgext.rebase= rebase --detach -s 7 -d 6 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+
+echo
+echo '% Branches'
+hg branches
+
+echo
+echo '% Heads'
+hg heads --template '{rev}:{node|short} {desc} branch: {branches}\n'
+
+echo
+hg --config extensions.hgext.graphlog= glog --template '{rev}:{node|short} {desc} branch: {branches}\n'
+hg verify -q
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rebase-cache.out	Thu Feb 25 17:06:32 2010 +0100
@@ -0,0 +1,186 @@
+
+@  8:c11d5b3e9c00 F branch: branch3
+|
+o  7:33c9da881988 Branch3 branch: branch3
+|
+| o  6:0e4064ab11a3 E branch: branch2
+| |
+| o  5:5ac035cb5d8f D branch: branch2
+| |
+| | o  4:8e66061486ee C branch: branch2
+| | |
++---o  3:99567862abbe Branch2 branch: branch2
+| |
+| o  2:65a26a4d12f6 B branch: branch1
+| |
+| o  1:0f3f3010ee16 Branch1 branch: branch1
+|/
+o  0:1994f17a630e A branch:
+
+
+% Branches
+branch3                        8:c11d5b3e9c00
+branch2                        6:0e4064ab11a3
+branch1                        2:65a26a4d12f6 (inactive)
+default                        0:1994f17a630e (inactive)
+
+% Heads
+8:c11d5b3e9c00 F branch: branch3
+6:0e4064ab11a3 E branch: branch2
+4:8e66061486ee C branch: branch2
+2:65a26a4d12f6 B branch: branch1
+0:1994f17a630e A branch: 
+
+% Rebase part of branch2 (5-6) onto branch3 (8)
+saving bundle to 
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 4 changesets with 3 changes to 3 files (+1 heads)
+rebase completed
+
+% Branches
+branch3                        8:c9bfa9beb84e
+branch2                        4:8e66061486ee
+branch1                        2:65a26a4d12f6
+default                        0:1994f17a630e (inactive)
+
+% Heads
+8:c9bfa9beb84e E branch: branch3
+4:8e66061486ee C branch: branch2
+2:65a26a4d12f6 B branch: branch1
+0:1994f17a630e A branch: 
+
+@  8:c9bfa9beb84e E branch: branch3
+|
+o  7:bf9037384081 D branch: branch3
+|
+o  6:c11d5b3e9c00 F branch: branch3
+|
+o  5:33c9da881988 Branch3 branch: branch3
+|
+| o  4:8e66061486ee C branch: branch2
+| |
+| o  3:99567862abbe Branch2 branch: branch2
+|/
+| o  2:65a26a4d12f6 B branch: branch1
+| |
+| o  1:0f3f3010ee16 Branch1 branch: branch1
+|/
+o  0:1994f17a630e A branch:
+
+
+% Rebase head of branch3 (8) onto branch2 (6)
+@  8:c11d5b3e9c00 F branch: branch3
+|
+o  7:33c9da881988 Branch3 branch: branch3
+|
+| o  6:0e4064ab11a3 E branch: branch2
+| |
+| o  5:5ac035cb5d8f D branch: branch2
+| |
+| | o  4:8e66061486ee C branch: branch2
+| | |
++---o  3:99567862abbe Branch2 branch: branch2
+| |
+| o  2:65a26a4d12f6 B branch: branch1
+| |
+| o  1:0f3f3010ee16 Branch1 branch: branch1
+|/
+o  0:1994f17a630e A branch:
+
+saving bundle to 
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+rebase completed
+
+% Branches
+branch2                        8:b44d3024f247
+branch3                        7:33c9da881988
+branch1                        2:65a26a4d12f6 (inactive)
+default                        0:1994f17a630e (inactive)
+
+% Heads
+8:b44d3024f247 F branch: branch2
+7:33c9da881988 Branch3 branch: branch3
+4:8e66061486ee C branch: branch2
+2:65a26a4d12f6 B branch: branch1
+0:1994f17a630e A branch: 
+
+@  8:b44d3024f247 F branch: branch2
+|
+| o  7:33c9da881988 Branch3 branch: branch3
+| |
+o |  6:0e4064ab11a3 E branch: branch2
+| |
+o |  5:5ac035cb5d8f D branch: branch2
+| |
+| | o  4:8e66061486ee C branch: branch2
+| | |
+| | o  3:99567862abbe Branch2 branch: branch2
+| |/
+o |  2:65a26a4d12f6 B branch: branch1
+| |
+o |  1:0f3f3010ee16 Branch1 branch: branch1
+|/
+o  0:1994f17a630e A branch:
+
+
+% Rebase entire branch3 (7-8) onto branch2 (6)
+@  8:c11d5b3e9c00 F branch: branch3
+|
+o  7:33c9da881988 Branch3 branch: branch3
+|
+| o  6:0e4064ab11a3 E branch: branch2
+| |
+| o  5:5ac035cb5d8f D branch: branch2
+| |
+| | o  4:8e66061486ee C branch: branch2
+| | |
++---o  3:99567862abbe Branch2 branch: branch2
+| |
+| o  2:65a26a4d12f6 B branch: branch1
+| |
+| o  1:0f3f3010ee16 Branch1 branch: branch1
+|/
+o  0:1994f17a630e A branch:
+
+saving bundle to 
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+rebase completed
+
+% Branches
+branch2                        7:b44d3024f247
+branch1                        2:65a26a4d12f6 (inactive)
+default                        0:1994f17a630e (inactive)
+
+% Heads
+7:b44d3024f247 F branch: branch2
+4:8e66061486ee C branch: branch2
+2:65a26a4d12f6 B branch: branch1
+0:1994f17a630e A branch: 
+
+@  7:b44d3024f247 F branch: branch2
+|
+o  6:0e4064ab11a3 E branch: branch2
+|
+o  5:5ac035cb5d8f D branch: branch2
+|
+| o  4:8e66061486ee C branch: branch2
+| |
+| o  3:99567862abbe Branch2 branch: branch2
+| |
+o |  2:65a26a4d12f6 B branch: branch1
+| |
+o |  1:0f3f3010ee16 Branch1 branch: branch1
+|/
+o  0:1994f17a630e A branch:
+