浏览代码

Update plug.vim to a recent version

Emmanuel Bouthenot 8 年之前
父节点
当前提交
976cd607d4
共有 1 个文件被更改,包括 624 次插入315 次删除
  1. 624 315
      .vim/autoload/plug.vim

+ 624 - 315
.vim/autoload/plug.vim

@@ -11,9 +11,13 @@
 "   call plug#begin('~/.vim/plugged')
 "   call plug#begin('~/.vim/plugged')
 "
 "
 "   " Make sure you use single quotes
 "   " Make sure you use single quotes
-"   Plug 'junegunn/seoul256.vim'
+"
+"   " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align
 "   Plug 'junegunn/vim-easy-align'
 "   Plug 'junegunn/vim-easy-align'
 "
 "
+"   " Any valid git URL is allowed
+"   Plug 'https://github.com/junegunn/vim-github-dashboard.git'
+"
 "   " Group dependencies, vim-snippets depends on ultisnips
 "   " Group dependencies, vim-snippets depends on ultisnips
 "   Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'
 "   Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'
 "
 "
@@ -21,25 +25,43 @@
 "   Plug 'scrooloose/nerdtree', { 'on':  'NERDTreeToggle' }
 "   Plug 'scrooloose/nerdtree', { 'on':  'NERDTreeToggle' }
 "   Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
 "   Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
 "
 "
-"   " Using git URL
-"   Plug 'https://github.com/junegunn/vim-github-dashboard.git'
+"   " Using a non-master branch
+"   Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' }
+"
+"   " Using a tagged release; wildcard allowed (requires git 1.9.2 or above)
+"   Plug 'fatih/vim-go', { 'tag': '*' }
 "
 "
 "   " Plugin options
 "   " Plugin options
 "   Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }
 "   Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }
 "
 "
 "   " Plugin outside ~/.vim/plugged with post-update hook
 "   " Plugin outside ~/.vim/plugged with post-update hook
-"   Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': 'yes \| ./install' }
+"   Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
 "
 "
 "   " Unmanaged plugin (manually installed and updated)
 "   " Unmanaged plugin (manually installed and updated)
 "   Plug '~/my-prototype-plugin'
 "   Plug '~/my-prototype-plugin'
 "
 "
+"   " Add plugins to &runtimepath
 "   call plug#end()
 "   call plug#end()
 "
 "
 " Then reload .vimrc and :PlugInstall to install plugins.
 " Then reload .vimrc and :PlugInstall to install plugins.
-" Visit https://github.com/junegunn/vim-plug for more information.
 "
 "
+" Plug options:
+"
+"| Option                  | Description                                      |
+"| ----------------------- | ------------------------------------------------ |
+"| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use       |
+"| `rtp`                   | Subdirectory that contains Vim plugin            |
+"| `dir`                   | Custom directory for the plugin                  |
+"| `as`                    | Use different name for the plugin                |
+"| `do`                    | Post-update hook (string or funcref)             |
+"| `on`                    | On-demand loading: Commands or `<Plug>`-mappings |
+"| `for`                   | On-demand loading: File types                    |
+"| `frozen`                | Do not update unless explicitly specified        |
 "
 "
-" Copyright (c) 2015 Junegunn Choi
+" More information: https://github.com/junegunn/vim-plug
+"
+"
+" Copyright (c) 2016 Junegunn Choi
 "
 "
 " MIT License
 " MIT License
 "
 "
@@ -109,17 +131,17 @@ function! plug#begin(...)
 endfunction
 endfunction
 
 
 function! s:define_commands()
 function! s:define_commands()
-  command! -nargs=+ -bar Plug call s:add(<args>)
+  command! -nargs=+ -bar Plug call plug#(<args>)
   if !executable('git')
   if !executable('git')
-    return s:err('`git` executable not found. vim-plug requires git.')
+    return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
   endif
   endif
-  command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install('<bang>' == '!', [<f-args>])
-  command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate  call s:update('<bang>' == '!', [<f-args>])
-  command! -nargs=0 -bar -bang PlugClean call s:clean('<bang>' == '!')
+  command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
+  command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate  call s:update(<bang>0, [<f-args>])
+  command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
   command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
   command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
   command! -nargs=0 -bar PlugStatus  call s:status()
   command! -nargs=0 -bar PlugStatus  call s:status()
   command! -nargs=0 -bar PlugDiff    call s:diff()
   command! -nargs=0 -bar PlugDiff    call s:diff()
-  command! -nargs=? -bar PlugSnapshot call s:snapshot(<f-args>)
+  command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
 endfunction
 endfunction
 
 
 function! s:to_a(v)
 function! s:to_a(v)
@@ -130,18 +152,43 @@ function! s:to_s(v)
   return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
   return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
 endfunction
 endfunction
 
 
+function! s:glob(from, pattern)
+  return s:lines(globpath(a:from, a:pattern))
+endfunction
+
 function! s:source(from, ...)
 function! s:source(from, ...)
+  let found = 0
   for pattern in a:000
   for pattern in a:000
-    for vim in s:lines(globpath(a:from, pattern))
+    for vim in s:glob(a:from, pattern)
       execute 'source' s:esc(vim)
       execute 'source' s:esc(vim)
+      let found = 1
     endfor
     endfor
   endfor
   endfor
+  return found
 endfunction
 endfunction
 
 
 function! s:assoc(dict, key, val)
 function! s:assoc(dict, key, val)
   let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
   let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
 endfunction
 endfunction
 
 
+function! s:ask(message, ...)
+  call inputsave()
+  echohl WarningMsg
+  let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
+  echohl None
+  call inputrestore()
+  echo "\r"
+  return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
+endfunction
+
+function! s:ask_no_interrupt(...)
+  try
+    return call('s:ask', a:000)
+  catch
+    return 0
+  endtry
+endfunction
+
 function! plug#end()
 function! plug#end()
   if !exists('g:plugs')
   if !exists('g:plugs')
     return s:err('Call plug#begin() first')
     return s:err('Call plug#begin() first')
@@ -157,6 +204,9 @@ function! plug#end()
 
 
   filetype off
   filetype off
   for name in g:plugs_order
   for name in g:plugs_order
+    if !has_key(g:plugs, name)
+      continue
+    endif
     let plug = g:plugs[name]
     let plug = g:plugs[name]
     if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for')
     if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for')
       let s:loaded[name] = 1
       let s:loaded[name] = 1
@@ -171,11 +221,14 @@ function! plug#end()
             call s:assoc(lod.map, cmd, name)
             call s:assoc(lod.map, cmd, name)
           endif
           endif
           call add(s:triggers[name].map, cmd)
           call add(s:triggers[name].map, cmd)
-        elseif cmd =~ '^[A-Z]'
+        elseif cmd =~# '^[A-Z]'
           if exists(':'.cmd) != 2
           if exists(':'.cmd) != 2
             call s:assoc(lod.cmd, cmd, name)
             call s:assoc(lod.cmd, cmd, name)
           endif
           endif
           call add(s:triggers[name].cmd, cmd)
           call add(s:triggers[name].cmd, cmd)
+        else
+          call s:err('Invalid `on` option: '.cmd.
+          \ '. Should start with an uppercase letter or `<Plug>`.')
         endif
         endif
       endfor
       endfor
     endif
     endif
@@ -183,7 +236,9 @@ function! plug#end()
     if has_key(plug, 'for')
     if has_key(plug, 'for')
       let types = s:to_a(plug.for)
       let types = s:to_a(plug.for)
       if !empty(types)
       if !empty(types)
+        augroup filetypedetect
         call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
         call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
+        augroup END
       endif
       endif
       for type in types
       for type in types
         call s:assoc(lod.ft, type, name)
         call s:assoc(lod.ft, type, name)
@@ -216,9 +271,11 @@ function! plug#end()
   call s:reorg_rtp()
   call s:reorg_rtp()
   filetype plugin indent on
   filetype plugin indent on
   if has('vim_starting')
   if has('vim_starting')
-    syntax enable
+    if has('syntax') && !exists('g:syntax_on')
+      syntax enable
+    end
   else
   else
-    call s:reload()
+    call s:reload_plugins()
   endif
   endif
 endfunction
 endfunction
 
 
@@ -226,9 +283,13 @@ function! s:loaded_names()
   return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
   return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
 endfunction
 endfunction
 
 
-function! s:reload()
+function! s:load_plugin(spec)
+  call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
+endfunction
+
+function! s:reload_plugins()
   for name in s:loaded_names()
   for name in s:loaded_names()
-    call s:source(s:rtp(g:plugs[name]), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
+    call s:load_plugin(g:plugs[name])
   endfor
   endfor
 endfunction
 endfunction
 
 
@@ -247,8 +308,9 @@ function! s:version_requirement(val, min)
 endfunction
 endfunction
 
 
 function! s:git_version_requirement(...)
 function! s:git_version_requirement(...)
-  let s:git_version = get(s:, 'git_version',
-    \ map(split(split(s:system('git --version'))[-1], '\.'), 'str2nr(v:val)'))
+  if !exists('s:git_version')
+    let s:git_version = map(split(split(s:system('git --version'))[-1], '\.'), 'str2nr(v:val)')
+  endif
   return s:version_requirement(s:git_version, a:000)
   return s:version_requirement(s:git_version, a:000)
 endfunction
 endfunction
 
 
@@ -293,9 +355,14 @@ endif
 
 
 function! s:err(msg)
 function! s:err(msg)
   echohl ErrorMsg
   echohl ErrorMsg
-  echom a:msg
+  echom '[vim-plug] '.a:msg
+  echohl None
+endfunction
+
+function! s:warn(cmd, msg)
+  echohl WarningMsg
+  execute a:cmd 'a:msg'
   echohl None
   echohl None
-  return 0
 endfunction
 endfunction
 
 
 function! s:esc(path)
 function! s:esc(path)
@@ -344,6 +411,23 @@ function! s:reorg_rtp()
   endif
   endif
 endfunction
 endfunction
 
 
+function! s:doautocmd(...)
+  if exists('#'.join(a:000, '#'))
+    execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
+  endif
+endfunction
+
+function! s:dobufread(names)
+  for name in a:names
+    let path = s:rtp(g:plugs[name]).'/**'
+    for dir in ['ftdetect', 'ftplugin']
+      if len(finddir(dir, path))
+        return s:doautocmd('BufRead')
+      endif
+    endfor
+  endfor
+endfunction
+
 function! plug#load(...)
 function! plug#load(...)
   if a:0 == 0
   if a:0 == 0
     return s:err('Argument missing: plugin name(s) required')
     return s:err('Argument missing: plugin name(s) required')
@@ -359,9 +443,7 @@ function! plug#load(...)
   for name in a:000
   for name in a:000
     call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
     call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
   endfor
   endfor
-  if exists('#BufRead')
-    doautocmd BufRead
-  endif
+  call s:dobufread(a:000)
   return 1
   return 1
 endfunction
 endfunction
 
 
@@ -379,7 +461,7 @@ function! s:remove_triggers(name)
   call remove(s:triggers, a:name)
   call remove(s:triggers, a:name)
 endfunction
 endfunction
 
 
-function! s:lod(names, types)
+function! s:lod(names, types, ...)
   for name in a:names
   for name in a:names
     call s:remove_triggers(name)
     call s:remove_triggers(name)
     let s:loaded[name] = 1
     let s:loaded[name] = 1
@@ -391,30 +473,33 @@ function! s:lod(names, types)
     for dir in a:types
     for dir in a:types
       call s:source(rtp, dir.'/**/*.vim')
       call s:source(rtp, dir.'/**/*.vim')
     endfor
     endfor
-    if exists('#User#'.name)
-      execute 'doautocmd User' name
+    if a:0
+      if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
+        execute 'runtime' a:1
+      endif
+      call s:source(rtp, a:2)
     endif
     endif
+    call s:doautocmd('User', name)
   endfor
   endfor
 endfunction
 endfunction
 
 
 function! s:lod_ft(pat, names)
 function! s:lod_ft(pat, names)
-  call s:lod(a:names, ['plugin', 'after/plugin'])
+  let syn = 'syntax/'.a:pat.'.vim'
+  call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
   execute 'autocmd! PlugLOD FileType' a:pat
   execute 'autocmd! PlugLOD FileType' a:pat
-  if exists('#filetypeplugin#FileType')
-    doautocmd filetypeplugin FileType
-  endif
-  if exists('#filetypeindent#FileType')
-    doautocmd filetypeindent FileType
-  endif
+  call s:doautocmd('filetypeplugin', 'FileType')
+  call s:doautocmd('filetypeindent', 'FileType')
 endfunction
 endfunction
 
 
 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
   call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
   call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
+  call s:dobufread(a:names)
   execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
   execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
 endfunction
 endfunction
 
 
 function! s:lod_map(map, names, prefix)
 function! s:lod_map(map, names, prefix)
   call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
   call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
+  call s:dobufread(a:names)
   let extra = ''
   let extra = ''
   while 1
   while 1
     let c = getchar(0)
     let c = getchar(0)
@@ -423,19 +508,26 @@ function! s:lod_map(map, names, prefix)
     endif
     endif
     let extra .= nr2char(c)
     let extra .= nr2char(c)
   endwhile
   endwhile
+  if v:count
+    call feedkeys(v:count, 'n')
+  endif
+  call feedkeys('"'.v:register, 'n')
+  if mode(1) == 'no'
+    call feedkeys(v:operator)
+  endif
   call feedkeys(a:prefix . substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
   call feedkeys(a:prefix . substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
 endfunction
 endfunction
 
 
-function! s:add(repo, ...)
+function! plug#(repo, ...)
   if a:0 > 1
   if a:0 > 1
     return s:err('Invalid number of arguments (1..2)')
     return s:err('Invalid number of arguments (1..2)')
   endif
   endif
 
 
   try
   try
     let repo = s:trim(a:repo)
     let repo = s:trim(a:repo)
-    let name = fnamemodify(repo, ':t:s?\.git$??')
-    let spec = extend(s:infer_properties(name, repo),
-                    \ a:0 == 1 ? s:parse_options(a:1) : s:base_spec)
+    let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
+    let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??'))
+    let spec = extend(s:infer_properties(name, repo), opts)
     if !has_key(g:plugs, name)
     if !has_key(g:plugs, name)
       call add(g:plugs_order, name)
       call add(g:plugs_order, name)
     endif
     endif
@@ -514,16 +606,23 @@ function! s:syntax()
   syn match plugStar /^*/
   syn match plugStar /^*/
   syn match plugMessage /\(^- \)\@<=.*/
   syn match plugMessage /\(^- \)\@<=.*/
   syn match plugName /\(^- \)\@<=[^ ]*:/
   syn match plugName /\(^- \)\@<=[^ ]*:/
+  syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
+  syn match plugTag /(tag: [^)]\+)/
   syn match plugInstall /\(^+ \)\@<=[^:]*/
   syn match plugInstall /\(^+ \)\@<=[^:]*/
   syn match plugUpdate /\(^* \)\@<=[^:]*/
   syn match plugUpdate /\(^* \)\@<=[^:]*/
-  syn match plugCommit /^  [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
-  syn match plugSha /\(^  \)\@<=[0-9a-z]\{7}/ contained
+  syn match plugCommit /^  \X*[0-9a-f]\{7} .*/ contains=plugRelDate,plugEdge,plugTag
+  syn match plugEdge /^  \X\+$/
+  syn match plugEdge /^  \X*/ contained nextgroup=plugSha
+  syn match plugSha /[0-9a-f]\{7}/ contained
   syn match plugRelDate /([^)]*)$/ contained
   syn match plugRelDate /([^)]*)$/ contained
   syn match plugNotLoaded /(not loaded)$/
   syn match plugNotLoaded /(not loaded)$/
   syn match plugError /^x.*/
   syn match plugError /^x.*/
+  syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
+  syn match plugH2 /^.*:\n-\+$/
   syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
   syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
   hi def link plug1       Title
   hi def link plug1       Title
   hi def link plug2       Repeat
   hi def link plug2       Repeat
+  hi def link plugH2      Type
   hi def link plugX       Exception
   hi def link plugX       Exception
   hi def link plugBracket Structure
   hi def link plugBracket Structure
   hi def link plugNumber  Number
   hi def link plugNumber  Number
@@ -538,8 +637,11 @@ function! s:syntax()
   hi def link plugUpdate  Type
   hi def link plugUpdate  Type
 
 
   hi def link plugError   Error
   hi def link plugError   Error
+  hi def link plugDeleted Ignore
   hi def link plugRelDate Comment
   hi def link plugRelDate Comment
+  hi def link plugEdge    PreProc
   hi def link plugSha     Identifier
   hi def link plugSha     Identifier
+  hi def link plugTag     Constant
 
 
   hi def link plugNotLoaded Comment
   hi def link plugNotLoaded Comment
 endfunction
 endfunction
@@ -598,32 +700,50 @@ function! s:switch_out(...)
   endif
   endif
 endfunction
 endfunction
 
 
-function! s:prepare()
+function! s:finish_bindings()
+  nnoremap <silent> <buffer> R  :call <SID>retry()<cr>
+  nnoremap <silent> <buffer> D  :PlugDiff<cr>
+  nnoremap <silent> <buffer> S  :PlugStatus<cr>
+  nnoremap <silent> <buffer> U  :call <SID>status_update()<cr>
+  xnoremap <silent> <buffer> U  :call <SID>status_update()<cr>
+  nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
+  nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
+endfunction
+
+function! s:prepare(...)
+  if empty(getcwd())
+    throw 'Invalid current working directory. Cannot proceed.'
+  endif
+
+  for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
+    if exists(evar)
+      throw evar.' detected. Cannot proceed.'
+    endif
+  endfor
+
   call s:job_abort()
   call s:job_abort()
   if s:switch_in()
   if s:switch_in()
-    silent %d _
-  else
-    call s:new_window()
-    nnoremap <silent> <buffer> q  :if b:plug_preview==1<bar>pc<bar>endif<bar>echo<bar>q<cr>
-    nnoremap <silent> <buffer> R  :silent! call <SID>retry()<cr>
-    nnoremap <silent> <buffer> D  :PlugDiff<cr>
-    nnoremap <silent> <buffer> S  :PlugStatus<cr>
-    nnoremap <silent> <buffer> U  :call <SID>status_update()<cr>
-    xnoremap <silent> <buffer> U  :call <SID>status_update()<cr>
-    nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
-    nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
-    let b:plug_preview = -1
-    let s:plug_tab = tabpagenr()
-    let s:plug_buf = winbufnr(0)
-    call s:assign_name()
-  endif
-  silent! unmap <buffer> <cr>
-  silent! unmap <buffer> L
-  silent! unmap <buffer> o
-  silent! unmap <buffer> X
+    normal q
+  endif
+
+  call s:new_window()
+  nnoremap <silent> <buffer> q  :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr>
+  if a:0 == 0
+    call s:finish_bindings()
+  endif
+  let b:plug_preview = -1
+  let s:plug_tab = tabpagenr()
+  let s:plug_buf = winbufnr(0)
+  call s:assign_name()
+
+  for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
+    execute 'silent! unmap <buffer>' k
+  endfor
   setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline modifiable
   setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline modifiable
   setf vim-plug
   setf vim-plug
-  call s:syntax()
+  if exists('g:syntax_on')
+    call s:syntax()
+  endif
 endfunction
 endfunction
 
 
 function! s:assign_name()
 function! s:assign_name()
@@ -638,6 +758,38 @@ function! s:assign_name()
   silent! execute 'f' fnameescape(name)
   silent! execute 'f' fnameescape(name)
 endfunction
 endfunction
 
 
+function! s:chsh(swap)
+  let prev = [&shell, &shellredir]
+  if !s:is_win && a:swap
+    set shell=sh shellredir=>%s\ 2>&1
+  endif
+  return prev
+endfunction
+
+function! s:bang(cmd, ...)
+  try
+    let [sh, shrd] = s:chsh(a:0)
+    " FIXME: Escaping is incomplete. We could use shellescape with eval,
+    "        but it won't work on Windows.
+    let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
+    let g:_plug_bang = '!'.escape(cmd, '#!%')
+    execute "normal! :execute g:_plug_bang\<cr>\<cr>"
+  finally
+    unlet g:_plug_bang
+    let [&shell, &shellredir] = [sh, shrd]
+  endtry
+  return v:shell_error ? 'Exit status: ' . v:shell_error : ''
+endfunction
+
+function! s:regress_bar()
+  let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
+  call s:progress_bar(2, bar, len(bar))
+endfunction
+
+function! s:is_updated(dir)
+  return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir))
+endfunction
+
 function! s:do(pull, force, todo)
 function! s:do(pull, force, todo)
   for [name, spec] in items(a:todo)
   for [name, spec] in items(a:todo)
     if !isdirectory(spec.dir)
     if !isdirectory(spec.dir)
@@ -645,45 +797,62 @@ function! s:do(pull, force, todo)
     endif
     endif
     let installed = has_key(s:update.new, name)
     let installed = has_key(s:update.new, name)
     let updated = installed ? 0 :
     let updated = installed ? 0 :
-      \ (a:pull && !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', spec.dir)))
+      \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
     if a:force || installed || updated
     if a:force || installed || updated
       execute 'cd' s:esc(spec.dir)
       execute 'cd' s:esc(spec.dir)
       call append(3, '- Post-update hook for '. name .' ... ')
       call append(3, '- Post-update hook for '. name .' ... ')
+      let error = ''
       let type = type(spec.do)
       let type = type(spec.do)
       if type == s:TYPE.string
       if type == s:TYPE.string
-        try
-          " FIXME: Escaping is incomplete. We could use shellescape with eval,
-          "        but it won't work on Windows.
-          let g:_plug_do = '!'.escape(spec.do, '#!%')
-          execute "normal! :execute g:_plug_do\<cr>\<cr>"
-        finally
-          let result = v:shell_error ? ('Exit status: '.v:shell_error) : 'Done!'
-          unlet g:_plug_do
-        endtry
+        if spec.do[0] == ':'
+          call s:load_plugin(spec)
+          execute spec.do[1:]
+        else
+          let error = s:bang(spec.do)
+        endif
       elseif type == s:TYPE.funcref
       elseif type == s:TYPE.funcref
         try
         try
           let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
           let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
           call spec.do({ 'name': name, 'status': status, 'force': a:force })
           call spec.do({ 'name': name, 'status': status, 'force': a:force })
-          let result = 'Done!'
         catch
         catch
-          let result = 'Error: ' . v:exception
+          let error = v:exception
         endtry
         endtry
       else
       else
-        let result = 'Error: Invalid type!'
+        let error = 'Invalid hook type'
+      endif
+      call s:switch_in()
+      call setline(4, empty(error) ? (getline(4) . 'OK')
+                                 \ : ('x' . getline(4)[1:] . error))
+      if !empty(error)
+        call add(s:update.errors, name)
+        call s:regress_bar()
       endif
       endif
-      call setline(4, getline(4) . result)
       cd -
       cd -
     endif
     endif
   endfor
   endfor
 endfunction
 endfunction
 
 
+function! s:hash_match(a, b)
+  return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
+endfunction
+
+function! s:checkout(spec)
+  let sha = a:spec.commit
+  let output = s:system('git rev-parse HEAD', a:spec.dir)
+  if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
+    let output = s:system(
+          \ 'git fetch --depth 999999 && git checkout '.s:esc(sha), a:spec.dir)
+  endif
+  return output
+endfunction
+
 function! s:finish(pull)
 function! s:finish(pull)
   let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
   let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
   if new_frozen
   if new_frozen
     let s = new_frozen > 1 ? 's' : ''
     let s = new_frozen > 1 ? 's' : ''
     call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
     call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
   endif
   endif
-  call append(3, '- Finishing ... ')
+  call append(3, '- Finishing ... ') | 4
   redraw
   redraw
   call plug#helptags()
   call plug#helptags()
   call plug#end()
   call plug#end()
@@ -698,12 +867,14 @@ function! s:finish(pull)
     call add(msgs, "Press 'D' to see the updated changes.")
     call add(msgs, "Press 'D' to see the updated changes.")
   endif
   endif
   echo join(msgs, ' ')
   echo join(msgs, ' ')
+  call s:finish_bindings()
 endfunction
 endfunction
 
 
 function! s:retry()
 function! s:retry()
   if empty(s:update.errors)
   if empty(s:update.errors)
     return
     return
   endif
   endif
+  echo
   call s:update_impl(s:update.pull, s:update.force,
   call s:update_impl(s:update.pull, s:update.force,
         \ extend(copy(s:update.errors), [s:update.threads]))
         \ extend(copy(s:update.errors), [s:update.threads]))
 endfunction
 endfunction
@@ -716,20 +887,28 @@ function! s:names(...)
   return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
   return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
 endfunction
 endfunction
 
 
+function! s:check_ruby()
+  silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
+  if !exists('g:plug_ruby')
+    redraw!
+    return s:warn('echom', 'Warning: Ruby interface is broken')
+  endif
+  let ruby_version = split(g:plug_ruby, '\.')
+  unlet g:plug_ruby
+  return s:version_requirement(ruby_version, [1, 8, 7])
+endfunction
+
 function! s:update_impl(pull, force, args) abort
 function! s:update_impl(pull, force, args) abort
   let args = copy(a:args)
   let args = copy(a:args)
   let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
   let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
-                  \ remove(args, -1) : get(g:, 'plug_threads', s:is_win ? 1 : 16)
+                  \ remove(args, -1) : get(g:, 'plug_threads', 16)
 
 
   let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
   let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
   let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
   let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
                          \ filter(managed, 'index(args, v:key) >= 0')
                          \ filter(managed, 'index(args, v:key) >= 0')
 
 
   if empty(todo)
   if empty(todo)
-    echohl WarningMsg
-    echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.'
-    echohl None
-    return
+    return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
   endif
   endif
 
 
   if !s:is_win && s:git_version_requirement(2, 3)
   if !s:is_win && s:git_version_requirement(2, 3)
@@ -751,14 +930,11 @@ function! s:update_impl(pull, force, args) abort
   endif
   endif
 
 
   if has('nvim') && !exists('*jobwait') && threads > 1
   if has('nvim') && !exists('*jobwait') && threads > 1
-    echohl WarningMsg
-    echomsg 'vim-plug: update Neovim for parallel installer'
-    echohl None
+    call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
   endif
   endif
 
 
-  let python = (has('python') || has('python3')) && !s:is_win && !has('win32unix')
-      \ && (!s:nvim || has('vim_starting'))
-  let ruby = has('ruby') && !s:nvim && (v:version >= 703 || v:version == 702 && has('patch374'))
+  let python = (has('python') || has('python3')) && (!s:nvim || has('vim_starting'))
+  let ruby = has('ruby') && !s:nvim && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && s:check_ruby()
 
 
   let s:update = {
   let s:update = {
     \ 'start':   reltime(),
     \ 'start':   reltime(),
@@ -773,7 +949,7 @@ function! s:update_impl(pull, force, args) abort
     \ 'fin':     0
     \ 'fin':     0
   \ }
   \ }
 
 
-  call s:prepare()
+  call s:prepare(1)
   call append(0, ['', ''])
   call append(0, ['', ''])
   normal! 2G
   normal! 2G
   silent! redraw
   silent! redraw
@@ -784,7 +960,7 @@ function! s:update_impl(pull, force, args) abort
   " Python version requirement (>= 2.7)
   " Python version requirement (>= 2.7)
   if python && !has('python3') && !ruby && !s:nvim && s:update.threads > 1
   if python && !has('python3') && !ruby && !s:nvim && s:update.threads > 1
     redir => pyv
     redir => pyv
-    silent python import platform; print(platform.python_version())
+    silent python import platform; print platform.python_version()
     redir END
     redir END
     let python = s:version_requirement(
     let python = s:version_requirement(
           \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
           \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
@@ -826,12 +1002,61 @@ function! s:update_impl(pull, force, args) abort
   endif
   endif
 endfunction
 endfunction
 
 
+function! s:log4(name, msg)
+  call setline(4, printf('- %s (%s)', a:msg, a:name))
+  redraw
+endfunction
+
 function! s:update_finish()
 function! s:update_finish()
   if exists('s:git_terminal_prompt')
   if exists('s:git_terminal_prompt')
     let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
     let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
   endif
   endif
   if s:switch_in()
   if s:switch_in()
-    call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'has_key(v:val, "do")'))
+    call append(3, '- Updating ...') | 4
+    for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))'))
+      let pos = s:logpos(name)
+      if !pos
+        continue
+      endif
+      if has_key(spec, 'commit')
+        call s:log4(name, 'Checking out '.spec.commit)
+        let out = s:checkout(spec)
+      elseif has_key(spec, 'tag')
+        let tag = spec.tag
+        if tag =~ '\*'
+          let tags = s:lines(s:system('git tag --list '.string(tag).' --sort -version:refname 2>&1', spec.dir))
+          if !v:shell_error && !empty(tags)
+            let tag = tags[0]
+            call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
+            call append(3, '')
+          endif
+        endif
+        call s:log4(name, 'Checking out '.tag)
+        let out = s:system('git checkout -q '.s:esc(tag).' 2>&1', spec.dir)
+      else
+        let branch = s:esc(get(spec, 'branch', 'master'))
+        call s:log4(name, 'Merging origin/'.branch)
+        let out = s:system('git checkout -q '.branch.' 2>&1'
+              \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir)
+      endif
+      if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
+            \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
+        call s:log4(name, 'Updating submodules. This may take a while.')
+        let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir)
+      endif
+      let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
+      if v:shell_error
+        call add(s:update.errors, name)
+        call s:regress_bar()
+        silent execute pos 'd _'
+        call append(4, msg) | 4
+      elseif !empty(out)
+        call setline(pos, msg[0])
+      endif
+      redraw
+    endfor
+    silent 4 d _
+    call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")'))
     call s:finish(s:update.pull)
     call s:finish(s:update.pull)
     call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
     call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
     call s:switch_out('normal! gg')
     call s:switch_out('normal! gg')
@@ -860,7 +1085,13 @@ function! s:job_handler(job_id, data, event) abort
   endif
   endif
 
 
   if a:event == 'stdout'
   if a:event == 'stdout'
-    let self.result .= substitute(s:to_s(a:data), '[\r\n]', '', 'g') . "\n"
+    let complete = empty(a:data[-1])
+    let lines = map(filter(a:data, 'len(v:val) > 0'), 'split(v:val, "[\r\n]")[-1]')
+    call extend(self.lines, lines)
+    let self.result = join(self.lines, "\n")
+    if !complete
+      call remove(self.lines, -1)
+    endif
     " To reduce the number of buffer updates
     " To reduce the number of buffer updates
     let self.tick = get(self, 'tick', -1) + 1
     let self.tick = get(self, 'tick', -1) + 1
     if self.tick % len(s:jobs) == 0
     if self.tick % len(s:jobs) == 0
@@ -877,7 +1108,7 @@ function! s:job_handler(job_id, data, event) abort
 endfunction
 endfunction
 
 
 function! s:spawn(name, cmd, opts)
 function! s:spawn(name, cmd, opts)
-  let job = { 'name': a:name, 'running': 1, 'error': 0, 'result': '',
+  let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [], 'result': '',
             \ 'new': get(a:opts, 'new', 0),
             \ 'new': get(a:opts, 'new', 0),
             \ 'on_stdout': function('s:job_handler'),
             \ 'on_stdout': function('s:job_handler'),
             \ 'on_exit' : function('s:job_handler'),
             \ 'on_exit' : function('s:job_handler'),
@@ -913,7 +1144,7 @@ function! s:reap(name)
   endif
   endif
   let s:update.bar .= job.error ? 'x' : '='
   let s:update.bar .= job.error ? 'x' : '='
 
 
-  call s:log(job.error ? 'x' : '-', a:name, job.result)
+  call s:log(job.error ? 'x' : '-', a:name, empty(job.result) ? 'OK' : job.result)
   call s:bar()
   call s:bar()
 
 
   call remove(s:jobs, a:name)
   call remove(s:jobs, a:name)
@@ -930,19 +1161,18 @@ function! s:bar()
 endfunction
 endfunction
 
 
 function! s:logpos(name)
 function! s:logpos(name)
-  for i in range(1, line('$'))
+  for i in range(4, line('$'))
     if getline(i) =~# '^[-+x*] '.a:name.':'
     if getline(i) =~# '^[-+x*] '.a:name.':'
       return i
       return i
     endif
     endif
   endfor
   endfor
-  return 0
 endfunction
 endfunction
 
 
 function! s:log(bullet, name, lines)
 function! s:log(bullet, name, lines)
   if s:switch_in()
   if s:switch_in()
     let pos = s:logpos(a:name)
     let pos = s:logpos(a:name)
     if pos > 0
     if pos > 0
-      execute pos 'd _'
+      silent execute pos 'd _'
       if pos > winheight('.')
       if pos > winheight('.')
         let pos = 4
         let pos = 4
       endif
       endif
@@ -981,30 +1211,24 @@ while 1 " Without TCO, Vim stack is bound to explode
   redraw
   redraw
 
 
   let has_tag = has_key(spec, 'tag')
   let has_tag = has_key(spec, 'tag')
-  let checkout = s:shellesc(has_tag ? spec.tag : spec.branch)
-  let merge = s:shellesc(has_tag ? spec.tag : 'origin/'.spec.branch)
-
   if !new
   if !new
-    let [valid, msg] = s:git_valid(spec, 0)
-    if valid
+    let [error, _] = s:git_validate(spec, 0)
+    if empty(error)
       if pull
       if pull
         let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : ''
         let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : ''
-        call s:spawn(name,
-          \ printf('(git fetch %s %s 2>&1 && git checkout -q %s 2>&1 && git merge --ff-only %s 2>&1 && git submodule update --init --recursive 2>&1)',
-          \ fetch_opt, prog, checkout, merge), { 'dir': spec.dir })
+        call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir })
       else
       else
         let s:jobs[name] = { 'running': 0, 'result': 'Already installed', 'error': 0 }
         let s:jobs[name] = { 'running': 0, 'result': 'Already installed', 'error': 0 }
       endif
       endif
     else
     else
-      let s:jobs[name] = { 'running': 0, 'result': msg, 'error': 1 }
+      let s:jobs[name] = { 'running': 0, 'result': error, 'error': 1 }
     endif
     endif
   else
   else
     call s:spawn(name,
     call s:spawn(name,
-          \ printf('git clone %s %s --recursive %s -b %s %s 2>&1',
+          \ printf('git clone %s %s %s %s 2>&1',
           \ has_tag ? '' : s:clone_opt,
           \ has_tag ? '' : s:clone_opt,
           \ prog,
           \ prog,
           \ s:shellesc(spec.uri),
           \ s:shellesc(spec.uri),
-          \ checkout,
           \ s:shellesc(s:trim(spec.dir))), { 'new': 1 })
           \ s:shellesc(s:trim(spec.dir))), { 'new': 1 })
   endif
   endif
 
 
@@ -1018,9 +1242,8 @@ endwhile
 endfunction
 endfunction
 
 
 function! s:update_python()
 function! s:update_python()
-let py_exe = has('python3') ? 'python3' : 'python'
+let py_exe = has('python') ? 'python' : 'python3'
 execute py_exe "<< EOF"
 execute py_exe "<< EOF"
-""" Due to use of signals this function is POSIX only. """
 import datetime
 import datetime
 import functools
 import functools
 import os
 import os
@@ -1047,33 +1270,29 @@ G_CLONE_OPT = vim.eval('s:clone_opt')
 G_PROGRESS = vim.eval('s:progress_opt(1)')
 G_PROGRESS = vim.eval('s:progress_opt(1)')
 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
 G_STOP = thr.Event()
 G_STOP = thr.Event()
-G_THREADS = {}
+G_IS_WIN = vim.eval('s:is_win') == '1'
 
 
-class BaseExc(Exception):
+class PlugError(Exception):
   def __init__(self, msg):
   def __init__(self, msg):
-    self._msg = msg
-  @property
-  def msg(self):
-    return self._msg
-class CmdTimedOut(BaseExc):
+    self.msg = msg
+class CmdTimedOut(PlugError):
   pass
   pass
-class CmdFailed(BaseExc):
+class CmdFailed(PlugError):
   pass
   pass
-class InvalidURI(BaseExc):
+class InvalidURI(PlugError):
   pass
   pass
 class Action(object):
 class Action(object):
   INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
   INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
 
 
 class Buffer(object):
 class Buffer(object):
-  def __init__(self, lock, num_plugs, is_pull, is_win):
+  def __init__(self, lock, num_plugs, is_pull):
     self.bar = ''
     self.bar = ''
     self.event = 'Updating' if is_pull else 'Installing'
     self.event = 'Updating' if is_pull else 'Installing'
-    self.is_win = is_win
     self.lock = lock
     self.lock = lock
     self.maxy = int(vim.eval('winheight(".")'))
     self.maxy = int(vim.eval('winheight(".")'))
     self.num_plugs = num_plugs
     self.num_plugs = num_plugs
 
 
-  def _where(self, name):
+  def __where(self, name):
     """ Find first line with name in current buffer. Return line num. """
     """ Find first line with name in current buffer. Return line num. """
     found, lnum = False, 0
     found, lnum = False, 0
     matcher = re.compile('^[-+x*] {0}:'.format(name))
     matcher = re.compile('^[-+x*] {0}:'.format(name))
@@ -1096,14 +1315,12 @@ class Buffer(object):
 
 
     with self.lock:
     with self.lock:
       vim.command('normal! 2G')
       vim.command('normal! 2G')
-      if not self.is_win:
-        vim.command('redraw')
+      vim.command('redraw')
 
 
   def write(self, action, name, lines):
   def write(self, action, name, lines):
     first, rest = lines[0], lines[1:]
     first, rest = lines[0], lines[1:]
     msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
     msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
-    padded_rest = ['    ' + line for line in rest]
-    msg.extend(padded_rest)
+    msg.extend(['    ' + line for line in rest])
 
 
     try:
     try:
       if action == Action.ERROR:
       if action == Action.ERROR:
@@ -1113,7 +1330,7 @@ class Buffer(object):
         self.bar += '='
         self.bar += '='
 
 
       curbuf = vim.current.buffer
       curbuf = vim.current.buffer
-      lnum = self._where(name)
+      lnum = self.__where(name)
       if lnum != -1: # Found matching line num
       if lnum != -1: # Found matching line num
         del curbuf[lnum]
         del curbuf[lnum]
         if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
         if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
@@ -1127,62 +1344,84 @@ class Buffer(object):
       pass
       pass
 
 
 class Command(object):
 class Command(object):
-  def __init__(self, cmd, cmd_dir=None, timeout=60, ntries=3, cb=None, clean=None):
+  CD = 'cd /d' if G_IS_WIN else 'cd'
+
+  def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
     self.cmd = cmd
     self.cmd = cmd
-    self.cmd_dir = cmd_dir
+    if cmd_dir:
+      self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
     self.timeout = timeout
     self.timeout = timeout
-    self.ntries = ntries
     self.callback = cb if cb else (lambda msg: None)
     self.callback = cb if cb else (lambda msg: None)
-    self.clean = clean
+    self.clean = clean if clean else (lambda: None)
+    self.proc = None
 
 
-  def attempt_cmd(self):
-    """ Tries to run the command, returns result if no exceptions. """
-    attempt = 0
-    finished = False
-    limit = self.timeout
+  @property
+  def alive(self):
+    """ Returns true only if command still running. """
+    return self.proc and self.proc.poll() is None
+
+  def execute(self, ntries=3):
+    """ Execute the command with ntries if CmdTimedOut.
+        Returns the output of the command if no Exception.
+    """
+    attempt, finished, limit = 0, False, self.timeout
 
 
     while not finished:
     while not finished:
       try:
       try:
         attempt += 1
         attempt += 1
-        result = self.timeout_cmd()
+        result = self.try_command()
         finished = True
         finished = True
+        return result
       except CmdTimedOut:
       except CmdTimedOut:
-        if attempt != self.ntries:
-          for count in range(3, 0, -1):
-            if G_STOP.is_set():
-              raise KeyboardInterrupt
-            msg = 'Timeout. Will retry in {0} second{1} ...'.format(
-                count, 's' if count != 1 else '')
-            self.callback([msg])
-            time.sleep(1)
+        if attempt != ntries:
+          self.notify_retry()
           self.timeout += limit
           self.timeout += limit
-          self.callback(['Retrying ...'])
         else:
         else:
           raise
           raise
 
 
-    return result
-
-  def timeout_cmd(self):
+  def notify_retry(self):
+    """ Retry required for command, notify user. """
+    for count in range(3, 0, -1):
+      if G_STOP.is_set():
+        raise KeyboardInterrupt
+      msg = 'Timeout. Will retry in {0} second{1} ...'.format(
+            count, 's' if count != 1 else '')
+      self.callback([msg])
+      time.sleep(1)
+    self.callback(['Retrying ...'])
+
+  def try_command(self):
     """ Execute a cmd & poll for callback. Returns list of output.
     """ Execute a cmd & poll for callback. Returns list of output.
-    Raises CmdFailed   -> return code for Popen isn't 0
-    Raises CmdTimedOut -> command exceeded timeout without new output
+        Raises CmdFailed   -> return code for Popen isn't 0
+        Raises CmdTimedOut -> command exceeded timeout without new output
     """
     """
-    proc = None
     first_line = True
     first_line = True
+
     try:
     try:
-      tfile = tempfile.NamedTemporaryFile(mode='w+b', delete=False)
-      proc = subprocess.Popen(self.cmd, cwd=self.cmd_dir, stdout=tfile,
-          stderr=subprocess.STDOUT, shell=True, preexec_fn=os.setsid)
-      while proc.poll() is None:
-        # Yield this thread
-        time.sleep(0.2)
+      tfile = tempfile.NamedTemporaryFile(mode='w+b')
+      preexec_fn = not G_IS_WIN and os.setsid or None
+      self.proc = subprocess.Popen(self.cmd, stdout=tfile,
+                                   stderr=subprocess.STDOUT,
+                                   stdin=subprocess.PIPE, shell=True,
+                                   preexec_fn=preexec_fn)
+      thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
+      thrd.start()
+
+      thread_not_started = True
+      while thread_not_started:
+        try:
+          thrd.join(0.1)
+          thread_not_started = False
+        except RuntimeError:
+          pass
 
 
+      while self.alive:
         if G_STOP.is_set():
         if G_STOP.is_set():
           raise KeyboardInterrupt
           raise KeyboardInterrupt
 
 
         if first_line or random.random() < G_LOG_PROB:
         if first_line or random.random() < G_LOG_PROB:
           first_line = False
           first_line = False
-          line = nonblock_read(tfile.name)
+          line = '' if G_IS_WIN else nonblock_read(tfile.name)
           if line:
           if line:
             self.callback([line])
             self.callback([line])
 
 
@@ -1190,23 +1429,27 @@ class Command(object):
         if time_diff > self.timeout:
         if time_diff > self.timeout:
           raise CmdTimedOut(['Timeout!'])
           raise CmdTimedOut(['Timeout!'])
 
 
+        thrd.join(0.5)
+
       tfile.seek(0)
       tfile.seek(0)
-      result = [line.decode().rstrip() for line in tfile]
+      result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
+
+      if self.proc.returncode != 0:
+        raise CmdFailed([''] + result)
 
 
-      if proc.returncode != 0:
-        msg = ['']
-        msg.extend(result)
-        raise CmdFailed(msg)
+      return result
     except:
     except:
-      if proc and proc.poll() is None:
-        os.killpg(proc.pid, signal.SIGTERM)
-      if self.clean:
-        self.clean()
+      self.terminate()
       raise
       raise
-    finally:
-      os.remove(tfile.name)
 
 
-    return result
+  def terminate(self):
+    """ Terminate process and cleanup. """
+    if self.alive:
+      if G_IS_WIN:
+        os.kill(self.proc.pid, signal.SIGINT)
+      else:
+        os.killpg(self.proc.pid, signal.SIGTERM)
+    self.clean()
 
 
 class Plugin(object):
 class Plugin(object):
   def __init__(self, name, args, buf_q, lock):
   def __init__(self, name, args, buf_q, lock):
@@ -1214,10 +1457,7 @@ class Plugin(object):
     self.args = args
     self.args = args
     self.buf_q = buf_q
     self.buf_q = buf_q
     self.lock = lock
     self.lock = lock
-    tag = args.get('tag', 0)
-    self.checkout = esc(tag if tag else args['branch'])
-    self.merge = esc(tag if tag else 'origin/' + args['branch'])
-    self.tag = tag
+    self.tag = args.get('tag', 0)
 
 
   def manage(self):
   def manage(self):
     try:
     try:
@@ -1227,7 +1467,7 @@ class Plugin(object):
         self.install()
         self.install()
         with self.lock:
         with self.lock:
           thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
           thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
-    except (CmdTimedOut, CmdFailed, InvalidURI) as exc:
+    except PlugError as exc:
       self.write(Action.ERROR, self.name, exc.msg)
       self.write(Action.ERROR, self.name, exc.msg)
     except KeyboardInterrupt:
     except KeyboardInterrupt:
       G_STOP.set()
       G_STOP.set()
@@ -1240,6 +1480,8 @@ class Plugin(object):
 
 
   def install(self):
   def install(self):
     target = self.args['dir']
     target = self.args['dir']
+    if target[-1] == '\\':
+      target = target[0:-1]
 
 
     def clean(target):
     def clean(target):
       def _clean():
       def _clean():
@@ -1251,12 +1493,19 @@ class Plugin(object):
 
 
     self.write(Action.INSTALL, self.name, ['Installing ...'])
     self.write(Action.INSTALL, self.name, ['Installing ...'])
     callback = functools.partial(self.write, Action.INSTALL, self.name)
     callback = functools.partial(self.write, Action.INSTALL, self.name)
-    cmd = 'git clone {0} {1} --recursive {2} -b {3} {4} 2>&1'.format(
-        '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], self.checkout, esc(target))
-    com = Command(cmd, None, G_TIMEOUT, G_RETRIES, callback, clean(target))
-    result = com.attempt_cmd()
+    cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
+          '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
+          esc(target))
+    com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
+    result = com.execute(G_RETRIES)
     self.write(Action.DONE, self.name, result[-1:])
     self.write(Action.DONE, self.name, result[-1:])
 
 
+  def repo_uri(self):
+    cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
+    command = Command(cmd, self.args['dir'], G_TIMEOUT,)
+    result = command.execute(G_RETRIES)
+    return result[-1]
+
   def update(self):
   def update(self):
     match = re.compile(r'git::?@')
     match = re.compile(r'git::?@')
     actual_uri = re.sub(match, '', self.repo_uri())
     actual_uri = re.sub(match, '', self.repo_uri())
@@ -1272,23 +1521,13 @@ class Plugin(object):
       self.write(Action.UPDATE, self.name, ['Updating ...'])
       self.write(Action.UPDATE, self.name, ['Updating ...'])
       callback = functools.partial(self.write, Action.UPDATE, self.name)
       callback = functools.partial(self.write, Action.UPDATE, self.name)
       fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
       fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
-      cmds = ['git fetch {0} {1}'.format(fetch_opt, G_PROGRESS),
-              'git checkout -q {0}'.format(self.checkout),
-              'git merge --ff-only {0}'.format(self.merge),
-              'git submodule update --init --recursive']
-      cmd = ' 2>&1 && '.join(cmds)
-      com = Command(cmd, self.args['dir'], G_TIMEOUT, G_RETRIES, callback)
-      result = com.attempt_cmd()
+      cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
+      com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
+      result = com.execute(G_RETRIES)
       self.write(Action.DONE, self.name, result[-1:])
       self.write(Action.DONE, self.name, result[-1:])
     else:
     else:
       self.write(Action.DONE, self.name, ['Already installed'])
       self.write(Action.DONE, self.name, ['Already installed'])
 
 
-  def repo_uri(self):
-    cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url'
-    command = Command(cmd, self.args['dir'], G_TIMEOUT, G_RETRIES)
-    result = command.attempt_cmd()
-    return result[-1]
-
   def write(self, action, name, msg):
   def write(self, action, name, msg):
     self.buf_q.put((action, name, msg))
     self.buf_q.put((action, name, msg))
 
 
@@ -1310,10 +1549,6 @@ class PlugThread(thr.Thread):
         work_q.task_done()
         work_q.task_done()
     except queue.Empty:
     except queue.Empty:
       pass
       pass
-    finally:
-      global G_THREADS
-      with lock:
-        del G_THREADS[thr.current_thread().name]
 
 
 class RefreshThread(thr.Thread):
 class RefreshThread(thr.Thread):
   def __init__(self, lock):
   def __init__(self, lock):
@@ -1325,7 +1560,7 @@ class RefreshThread(thr.Thread):
     while self.running:
     while self.running:
       with self.lock:
       with self.lock:
         thread_vim_command('noautocmd normal! a')
         thread_vim_command('noautocmd normal! a')
-      time.sleep(0.2)
+      time.sleep(0.33)
 
 
   def stop(self):
   def stop(self):
     self.running = False
     self.running = False
@@ -1343,7 +1578,7 @@ def esc(name):
 def nonblock_read(fname):
 def nonblock_read(fname):
   """ Read a file with nonblock flag. Return the last line. """
   """ Read a file with nonblock flag. Return the last line. """
   fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
   fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
-  buf = os.read(fread, 100000).decode()
+  buf = os.read(fread, 100000).decode('utf-8', 'replace')
   os.close(fread)
   os.close(fread)
 
 
   line = buf.rstrip('\r\n')
   line = buf.rstrip('\r\n')
@@ -1359,28 +1594,26 @@ def main():
   nthreads = int(vim.eval('s:update.threads'))
   nthreads = int(vim.eval('s:update.threads'))
   plugs = vim.eval('s:update.todo')
   plugs = vim.eval('s:update.todo')
   mac_gui = vim.eval('s:mac_gui') == '1'
   mac_gui = vim.eval('s:mac_gui') == '1'
-  is_win = vim.eval('s:is_win') == '1'
 
 
   lock = thr.Lock()
   lock = thr.Lock()
-  buf = Buffer(lock, len(plugs), G_PULL, is_win)
+  buf = Buffer(lock, len(plugs), G_PULL)
   buf_q, work_q = queue.Queue(), queue.Queue()
   buf_q, work_q = queue.Queue(), queue.Queue()
   for work in plugs.items():
   for work in plugs.items():
     work_q.put(work)
     work_q.put(work)
 
 
-  global G_THREADS
+  start_cnt = thr.active_count()
   for num in range(nthreads):
   for num in range(nthreads):
     tname = 'PlugT-{0:02}'.format(num)
     tname = 'PlugT-{0:02}'.format(num)
     thread = PlugThread(tname, (buf_q, work_q, lock))
     thread = PlugThread(tname, (buf_q, work_q, lock))
     thread.start()
     thread.start()
-    G_THREADS[tname] = thread
   if mac_gui:
   if mac_gui:
     rthread = RefreshThread(lock)
     rthread = RefreshThread(lock)
     rthread.start()
     rthread.start()
 
 
-  while not buf_q.empty() or len(G_THREADS) != 0:
+  while not buf_q.empty() or thr.active_count() != start_cnt:
     try:
     try:
       action, name, msg = buf_q.get(True, 0.25)
       action, name, msg = buf_q.get(True, 0.25)
-      buf.write(action, name, msg)
+      buf.write(action, name, ['OK'] if not msg else msg)
       buf_q.task_done()
       buf_q.task_done()
     except queue.Empty:
     except queue.Empty:
       pass
       pass
@@ -1420,16 +1653,20 @@ function! s:update_ruby()
 
 
   def killall pid
   def killall pid
     pids = [pid]
     pids = [pid]
-    unless `which pgrep 2> /dev/null`.empty?
-      children = pids
-      until children.empty?
-        children = children.map { |pid|
-          `pgrep -P #{pid}`.lines.map { |l| l.chomp }
-        }.flatten
-        pids += children
+    if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
+      pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
+    else
+      unless `which pgrep 2> /dev/null`.empty?
+        children = pids
+        until children.empty?
+          children = children.map { |pid|
+            `pgrep -P #{pid}`.lines.map { |l| l.chomp }
+          }.flatten
+          pids += children
+        end
       end
       end
+      pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
     end
     end
-    pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
   end
   end
 
 
   require 'thread'
   require 'thread'
@@ -1455,7 +1692,7 @@ function! s:update_ruby()
     $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
     $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
     $curbuf[2] = '[' + bar.ljust(tot) + ']'
     $curbuf[2] = '[' + bar.ljust(tot) + ']'
     VIM::command('normal! 2G')
     VIM::command('normal! 2G')
-    VIM::command('redraw') unless iswin
+    VIM::command('redraw')
   }
   }
   where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
   where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
   log   = proc { |name, result, type|
   log   = proc { |name, result, type|
@@ -1470,7 +1707,7 @@ function! s:update_ruby()
           end
           end
       result =
       result =
         if type || type.nil?
         if type || type.nil?
-          ["#{b} #{name}: #{result.lines.to_a.last}"]
+          ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
         elsif result =~ /^Interrupted|^Timeout/
         elsif result =~ /^Interrupted|^Timeout/
           ["#{b} #{name}: #{result}"]
           ["#{b} #{name}: #{result}"]
         else
         else
@@ -1560,15 +1797,12 @@ function! s:update_ruby()
       threads << Thread.new {
       threads << Thread.new {
         while pair = take1.call
         while pair = take1.call
           name = pair.first
           name = pair.first
-          dir, uri, branch, tag = pair.last.values_at *%w[dir uri branch tag]
-          checkout = esc(tag ? tag : branch)
-          merge = esc(tag ? tag : "origin/#{branch}")
-          subm = "git submodule update --init --recursive 2>&1"
+          dir, uri, tag = pair.last.values_at *%w[dir uri tag]
           exists = File.directory? dir
           exists = File.directory? dir
           ok, result =
           ok, result =
             if exists
             if exists
-              dir = iswin ? dir : esc(dir)
-              ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil, nil
+              chdir = "#{cd} #{iswin ? dir : esc(dir)}"
+              ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
               current_uri = data.lines.to_a.last
               current_uri = data.lines.to_a.last
               if !ret
               if !ret
                 if data =~ /^Interrupted|^Timeout/
                 if data =~ /^Interrupted|^Timeout/
@@ -1584,7 +1818,7 @@ function! s:update_ruby()
                 if pull
                 if pull
                   log.call name, 'Updating ...', :update
                   log.call name, 'Updating ...', :update
                   fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
                   fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
-                  bt.call "#{cd} #{dir} && git fetch #{fetch_opt} #{progress} 2>&1 && git checkout -q #{checkout} 2>&1 && git merge --ff-only #{merge} 2>&1 && #{subm}", name, :update, nil
+                  bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
                 else
                 else
                   [true, skip]
                   [true, skip]
                 end
                 end
@@ -1592,7 +1826,7 @@ function! s:update_ruby()
             else
             else
               d = esc dir.sub(%r{[\\/]+$}, '')
               d = esc dir.sub(%r{[\\/]+$}, '')
               log.call name, 'Installing ...', :install
               log.call name, 'Installing ...', :install
-              bt.call "git clone #{clone_opt unless tag} #{progress} --recursive #{uri} -b #{checkout} #{d} 2>&1", name, :install, proc {
+              bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
                 FileUtils.rm_rf dir
                 FileUtils.rm_rf dir
               }
               }
             end
             end
@@ -1614,7 +1848,7 @@ function! s:shellesc(arg)
 endfunction
 endfunction
 
 
 function! s:glob_dir(path)
 function! s:glob_dir(path)
-  return map(filter(s:lines(globpath(a:path, '**')), 'isdirectory(v:val)'), 's:dirpath(v:val)')
+  return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
 endfunction
 endfunction
 
 
 function! s:progress_bar(line, bar, total)
 function! s:progress_bar(line, bar, total)
@@ -1642,10 +1876,7 @@ endfunction
 
 
 function! s:system(cmd, ...)
 function! s:system(cmd, ...)
   try
   try
-    let [sh, shrd] = [&shell, &shellredir]
-    if !s:is_win
-      set shell=sh shellredir=>%s\ 2>&1
-    endif
+    let [sh, shrd] = s:chsh(1)
     let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
     let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
     return system(s:is_win ? '('.cmd.')' : cmd)
     return system(s:is_win ? '('.cmd.')' : cmd)
   finally
   finally
@@ -1658,42 +1889,53 @@ function! s:system_chomp(...)
   return v:shell_error ? '' : substitute(ret, '\n$', '', '')
   return v:shell_error ? '' : substitute(ret, '\n$', '', '')
 endfunction
 endfunction
 
 
-function! s:git_valid(spec, check_branch)
-  let ret = 1
-  let msg = 'OK'
+function! s:git_validate(spec, check_branch)
+  let err = ''
   if isdirectory(a:spec.dir)
   if isdirectory(a:spec.dir)
-    let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url', a:spec.dir))
+    let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir))
     let remote = result[-1]
     let remote = result[-1]
     if v:shell_error
     if v:shell_error
-      let msg = join([remote, 'PlugClean required.'], "\n")
-      let ret = 0
+      let err = join([remote, 'PlugClean required.'], "\n")
     elseif !s:compare_git_uri(remote, a:spec.uri)
     elseif !s:compare_git_uri(remote, a:spec.uri)
-      let msg = join(['Invalid URI: '.remote,
+      let err = join(['Invalid URI: '.remote,
                     \ 'Expected:    '.a:spec.uri,
                     \ 'Expected:    '.a:spec.uri,
                     \ 'PlugClean required.'], "\n")
                     \ 'PlugClean required.'], "\n")
-      let ret = 0
+    elseif a:check_branch && has_key(a:spec, 'commit')
+      let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir))
+      let sha = result[-1]
+      if v:shell_error
+        let err = join(add(result, 'PlugClean required.'), "\n")
+      elseif !s:hash_match(sha, a:spec.commit)
+        let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
+                              \ a:spec.commit[:6], sha[:6]),
+                      \ 'PlugUpdate required.'], "\n")
+      endif
     elseif a:check_branch
     elseif a:check_branch
       let branch = result[0]
       let branch = result[0]
       " Check tag
       " Check tag
       if has_key(a:spec, 'tag')
       if has_key(a:spec, 'tag')
         let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
         let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
         if a:spec.tag !=# tag
         if a:spec.tag !=# tag
-          let msg = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
+          let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
                 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
                 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
-          let ret = 0
         endif
         endif
       " Check branch
       " Check branch
       elseif a:spec.branch !=# branch
       elseif a:spec.branch !=# branch
-        let msg = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
+        let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
               \ branch, a:spec.branch)
               \ branch, a:spec.branch)
-        let ret = 0
+      endif
+      if empty(err)
+        let commits = len(s:lines(s:system(printf('git rev-list origin/%s..HEAD', a:spec.branch), a:spec.dir)))
+        if !v:shell_error && commits
+          let err = join([printf('Diverged from origin/%s by %d commit(s).', a:spec.branch, commits),
+                        \ 'Reinstall after PlugClean.'], "\n")
+        endif
       endif
       endif
     endif
     endif
   else
   else
-    let msg = 'Not found'
-    let ret = 0
+    let err = 'Not found'
   endif
   endif
-  return [ret, msg]
+  return [err, err =~# 'PlugClean']
 endfunction
 endfunction
 
 
 function! s:rm_rf(dir)
 function! s:rm_rf(dir)
@@ -1704,15 +1946,23 @@ endfunction
 
 
 function! s:clean(force)
 function! s:clean(force)
   call s:prepare()
   call s:prepare()
-  call append(0, 'Searching for unused plugins in '.g:plug_home)
+  call append(0, 'Searching for invalid plugins in '.g:plug_home)
   call append(1, '')
   call append(1, '')
 
 
   " List of valid directories
   " List of valid directories
   let dirs = []
   let dirs = []
+  let errs = {}
   let [cnt, total] = [0, len(g:plugs)]
   let [cnt, total] = [0, len(g:plugs)]
   for [name, spec] in items(g:plugs)
   for [name, spec] in items(g:plugs)
-    if !s:is_managed(name) || s:git_valid(spec, 0)[0]
+    if !s:is_managed(name)
       call add(dirs, spec.dir)
       call add(dirs, spec.dir)
+    else
+      let [err, clean] = s:git_validate(spec, 1)
+      if clean
+        let errs[spec.dir] = s:lines(err)[0]
+      else
+        call add(dirs, spec.dir)
+      endif
     endif
     endif
     let cnt += 1
     let cnt += 1
     call s:progress_bar(2, repeat('=', cnt), total)
     call s:progress_bar(2, repeat('=', cnt), total)
@@ -1736,28 +1986,60 @@ function! s:clean(force)
     if !has_key(allowed, f) && isdirectory(f)
     if !has_key(allowed, f) && isdirectory(f)
       call add(todo, f)
       call add(todo, f)
       call append(line('$'), '- ' . f)
       call append(line('$'), '- ' . f)
+      if has_key(errs, f)
+        call append(line('$'), '    ' . errs[f])
+      endif
       let found = filter(found, 'stridx(v:val, f) != 0')
       let found = filter(found, 'stridx(v:val, f) != 0')
     end
     end
   endwhile
   endwhile
 
 
-  normal! G
+  4
   redraw
   redraw
   if empty(todo)
   if empty(todo)
     call append(line('$'), 'Already clean.')
     call append(line('$'), 'Already clean.')
   else
   else
-    call inputsave()
-    let yes = a:force || (input('Proceed? (y/N) ') =~? '^y')
-    call inputrestore()
-    if yes
-      for dir in todo
-        call s:rm_rf(dir)
-      endfor
-      call append(line('$'), 'Removed.')
+    let s:clean_count = 0
+    call append(3, ['Directories to delete:', ''])
+    redraw!
+    if a:force || s:ask_no_interrupt('Delete all directories?')
+      call s:delete([6, line('$')], 1)
     else
     else
-      call append(line('$'), 'Cancelled.')
+      call setline(4, 'Cancelled.')
+      nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
+      nmap     <silent> <buffer> dd d_
+      xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
+      echo 'Delete the lines (d{motion}) to delete the corresponding directories'
     endif
     endif
   endif
   endif
-  normal! G
+  4
+  setlocal nomodifiable
+endfunction
+
+function! s:delete_op(type, ...)
+  call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
+endfunction
+
+function! s:delete(range, force)
+  let [l1, l2] = a:range
+  let force = a:force
+  while l1 <= l2
+    let line = getline(l1)
+    if line =~ '^- ' && isdirectory(line[2:])
+      execute l1
+      redraw!
+      let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
+      let force = force || answer > 1
+      if answer
+        call s:rm_rf(line[2:])
+        setlocal modifiable
+        call setline(l1, '~'.line[1:])
+        let s:clean_count += 1
+        call setline(4, printf('Removed %d directories.', s:clean_count))
+        setlocal nomodifiable
+      endif
+    endif
+    let l1 += 1
+  endwhile
 endfunction
 endfunction
 
 
 function! s:upgrade()
 function! s:upgrade()
@@ -1804,7 +2086,8 @@ function! s:status()
   for [name, spec] in items(g:plugs)
   for [name, spec] in items(g:plugs)
     if has_key(spec, 'uri')
     if has_key(spec, 'uri')
       if isdirectory(spec.dir)
       if isdirectory(spec.dir)
-        let [valid, msg] = s:git_valid(spec, 1)
+        let [err, _] = s:git_validate(spec, 1)
+        let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
       else
       else
         let [valid, msg] = [0, 'Not found. Try PlugInstall.']
         let [valid, msg] = [0, 'Not found. Try PlugInstall.']
       endif
       endif
@@ -1867,7 +2150,6 @@ function! s:is_preview_window_open()
     wincmd p
     wincmd p
     return 1
     return 1
   endif
   endif
-  return 0
 endfunction
 endfunction
 
 
 function! s:find_name(lnum)
 function! s:find_name(lnum)
@@ -1889,7 +2171,7 @@ function! s:preview_commit()
     let b:plug_preview = !s:is_preview_window_open()
     let b:plug_preview = !s:is_preview_window_open()
   endif
   endif
 
 
-  let sha = matchstr(getline('.'), '\(^  \)\@<=[0-9a-z]\{7}')
+  let sha = matchstr(getline('.'), '^  \X*\zs[0-9a-f]\{7}')
   if empty(sha)
   if empty(sha)
     return
     return
   endif
   endif
@@ -1899,11 +2181,15 @@ function! s:preview_commit()
     return
     return
   endif
   endif
 
 
-  execute 'pedit' sha
-  wincmd P
-  setlocal filetype=git buftype=nofile nobuflisted modifiable
-  execute 'silent read !cd' s:shellesc(g:plugs[name].dir) '&& git show --pretty=medium' sha
-  normal! gg"_dd
+  if exists('g:plug_pwindow') && !s:is_preview_window_open()
+    execute g:plug_pwindow
+    execute 'e' sha
+  else
+    execute 'pedit' sha
+    wincmd P
+  endif
+  setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
+  execute 'silent %!cd' s:shellesc(g:plugs[name].dir) '&& git show --no-color --pretty=medium' sha
   setlocal nomodifiable
   setlocal nomodifiable
   nnoremap <silent> <buffer> q :q<cr>
   nnoremap <silent> <buffer> q :q<cr>
   wincmd p
   wincmd p
@@ -1913,41 +2199,72 @@ function! s:section(flags)
   call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
   call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
 endfunction
 endfunction
 
 
+function! s:format_git_log(line)
+  let indent = '  '
+  let tokens = split(a:line, nr2char(1))
+  if len(tokens) != 5
+    return indent.substitute(a:line, '\s*$', '', '')
+  endif
+  let [graph, sha, refs, subject, date] = tokens
+  let tag = matchstr(refs, 'tag: [^,)]\+')
+  let tag = empty(tag) ? ' ' : ' ('.tag.') '
+  return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
+endfunction
+
+function! s:append_ul(lnum, text)
+  call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
+endfunction
+
 function! s:diff()
 function! s:diff()
   call s:prepare()
   call s:prepare()
-  call append(0, 'Collecting updated changes ...')
-  normal! gg
-  redraw
-
-  let cnt = 0
-  for [k, v] in items(g:plugs)
-    if !isdirectory(v.dir) || !s:is_managed(k)
+  call append(0, ['Collecting changes ...', ''])
+  let cnts = [0, 0]
+  let bar = ''
+  let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
+  call s:progress_bar(2, bar, len(total))
+  for origin in [1, 0]
+    let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
+    if empty(plugs)
       continue
       continue
     endif
     endif
-
-    let diff = s:system_chomp('git log --pretty=format:"%h %s (%cr)" "HEAD...HEAD@{1}"', v.dir)
-    if !empty(diff)
-      call append(1, '')
-      call append(2, '- '.k.':')
-      call append(3, map(s:lines(diff), '"  ". v:val'))
-      let cnt += 1
-      normal! gg
+    call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
+    for [k, v] in plugs
+      let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..'
+      let diff = s:system_chomp('git log --graph --color=never --pretty=format:"%x01%h%x01%d%x01%s%x01%cr" '.s:shellesc(range), v.dir)
+      if !empty(diff)
+        let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
+        call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
+        let cnts[origin] += 1
+      endif
+      let bar .= '='
+      call s:progress_bar(2, bar, len(total))
+      normal! 2G
       redraw
       redraw
+    endfor
+    if !cnts[origin]
+      call append(5, ['', 'N/A'])
     endif
     endif
   endfor
   endfor
+  call setline(1, printf('%d plugin(s) updated.', cnts[0])
+        \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
 
 
-  call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
-  nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
-  nnoremap <silent> <buffer> o    :silent! call <SID>preview_commit()<cr>
-  nnoremap <silent> <buffer> X    :call <SID>revert()<cr>
-  normal! gg
-  setlocal nomodifiable
-  if cnt > 0
+  if cnts[0] || cnts[1]
+    nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
+    nnoremap <silent> <buffer> o    :silent! call <SID>preview_commit()<cr>
+  endif
+  if cnts[0]
+    nnoremap <silent> <buffer> X :call <SID>revert()<cr>
     echo "Press 'X' on each block to revert the update"
     echo "Press 'X' on each block to revert the update"
   endif
   endif
+  normal! gg
+  setlocal nomodifiable
 endfunction
 endfunction
 
 
 function! s:revert()
 function! s:revert()
+  if search('^Pending updates', 'bnW')
+    return
+  endif
+
   let name = s:find_name(line('.'))
   let name = s:find_name(line('.'))
   if empty(name) || !has_key(g:plugs, name) ||
   if empty(name) || !has_key(g:plugs, name) ||
     \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
     \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
@@ -1958,45 +2275,38 @@ function! s:revert()
   setlocal modifiable
   setlocal modifiable
   normal! "_dap
   normal! "_dap
   setlocal nomodifiable
   setlocal nomodifiable
-  echo 'Reverted.'
+  echo 'Reverted'
 endfunction
 endfunction
 
 
-function! s:snapshot(...) abort
-  let home = get(s:, 'plug_home_org', g:plug_home)
-  let [type, var, header] = s:is_win ?
-    \ ['dosbatch', '%PLUG_HOME%',
-    \   ['@echo off', ':: Generated by vim-plug', ':: '.strftime("%c"), '',
-    \    ':: Make sure to PlugUpdate first', '', 'set PLUG_HOME='.home]] :
-    \ ['sh', '$PLUG_HOME',
-    \   ['#!/bin/sh',  '# Generated by vim-plug', '# '.strftime("%c"), '',
-    \    'vim +PlugUpdate +qa', '', 'PLUG_HOME='.s:esc(home)]]
-
+function! s:snapshot(force, ...) abort
   call s:prepare()
   call s:prepare()
-  execute 'setf' type
-  call append(0, header)
-  call append('$', '')
+  setf vim
+  call append(0, ['" Generated by vim-plug',
+                \ '" '.strftime("%c"),
+                \ '" :source this file in vim to restore the snapshot',
+                \ '" or execute: vim -S snapshot.vim',
+                \ '', '', 'PlugUpdate!'])
   1
   1
-  redraw
-
-  let dirs = sort(map(values(filter(copy(g:plugs),
-        \'has_key(v:val, "uri") && isdirectory(v:val.dir)')), 'v:val.dir'))
-  let anchor = line('$') - 1
-  for dir in reverse(dirs)
-    let sha = s:system_chomp('git rev-parse --short HEAD', dir)
+  let anchor = line('$') - 3
+  let names = sort(keys(filter(copy(g:plugs),
+        \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
+  for name in reverse(names)
+    let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir)
     if !empty(sha)
     if !empty(sha)
-      call append(anchor, printf('cd %s && git reset --hard %s',
-            \ substitute(dir, '^\V'.escape(g:plug_home, '\'), var, ''), sha))
+      call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
       redraw
       redraw
     endif
     endif
   endfor
   endfor
 
 
   if a:0 > 0
   if a:0 > 0
     let fn = expand(a:1)
     let fn = expand(a:1)
-    let fne = s:esc(fn)
+    if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
+      return
+    endif
     call writefile(getline(1, '$'), fn)
     call writefile(getline(1, '$'), fn)
-    if !s:is_win | call s:system('chmod +x ' . fne) | endif
-    echo 'Saved to '.a:1
-    silent execute 'e' fne
+    echo 'Saved as '.a:1
+    silent execute 'e' s:esc(fn)
+    setf vim
   endif
   endif
 endfunction
 endfunction
 
 
@@ -2015,4 +2325,3 @@ endif
 
 
 let &cpo = s:cpo_save
 let &cpo = s:cpo_save
 unlet s:cpo_save
 unlet s:cpo_save
-