citadel

My dotfiles, scripts and nix configs
git clone git://jb55.com/citadel
Log | Files | Refs | README | LICENSE

fugitive.vim (58590B)


      1 " fugitive.vim - A Git wrapper so awesome, it should be illegal
      2 " Maintainer:   Tim Pope <vimNOSPAM@tpope.org>
      3 " Version:      1.1
      4 " GetLatestVimScripts: 2975 1 :AutoInstall: fugitive.vim
      5 
      6 if exists('g:loaded_fugitive') || &cp
      7   finish
      8 endif
      9 let g:loaded_fugitive = 1
     10 
     11 if !exists('g:fugitive_git_executable')
     12   let g:fugitive_git_executable = 'git'
     13 endif
     14 
     15 " Utility {{{1
     16 
     17 function! s:function(name) abort
     18   return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
     19 endfunction
     20 
     21 function! s:sub(str,pat,rep) abort
     22   return substitute(a:str,'\v\C'.a:pat,a:rep,'')
     23 endfunction
     24 
     25 function! s:gsub(str,pat,rep) abort
     26   return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
     27 endfunction
     28 
     29 function! s:shellesc(arg) abort
     30   if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
     31     return a:arg
     32   elseif &shell =~# 'cmd' && a:arg !~# '"'
     33     return '"'.a:arg.'"'
     34   else
     35     return shellescape(a:arg)
     36   endif
     37 endfunction
     38 
     39 function! s:fnameescape(file) abort
     40   if exists('*fnameescape')
     41     return fnameescape(a:file)
     42   else
     43     return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
     44   endif
     45 endfunction
     46 
     47 function! s:throw(string) abort
     48   let v:errmsg = 'fugitive: '.a:string
     49   throw v:errmsg
     50 endfunction
     51 
     52 function! s:warn(str)
     53   echohl WarningMsg
     54   echomsg a:str
     55   echohl None
     56   let v:warningmsg = a:str
     57 endfunction
     58 
     59 function! s:shellslash(path)
     60   if exists('+shellslash') && !&shellslash
     61     return s:gsub(a:path,'\\','/')
     62   else
     63     return a:path
     64   endif
     65 endfunction
     66 
     67 function! s:add_methods(namespace, method_names) abort
     68   for name in a:method_names
     69     let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
     70   endfor
     71 endfunction
     72 
     73 let s:commands = []
     74 function! s:command(definition) abort
     75   let s:commands += [a:definition]
     76 endfunction
     77 
     78 function! s:define_commands()
     79   for command in s:commands
     80     exe 'command! -buffer '.command
     81   endfor
     82 endfunction
     83 
     84 function! s:compatibility_check()
     85   if exists('b:git_dir') && exists('*GitBranchInfoCheckGitDir') && !exists('g:fugitive_did_compatibility_warning')
     86     let g:fugitive_did_compatibility_warning = 1
     87     call s:warn("See http://github.com/tpope/vim-fugitive/issues#issue/1 for why you should remove git-branch-info.vim")
     88   endif
     89 endfunction
     90 
     91 augroup fugitive_utility
     92   autocmd!
     93   autocmd User Fugitive call s:define_commands()
     94   autocmd VimEnter * call s:compatibility_check()
     95 augroup END
     96 
     97 let s:abstract_prototype = {}
     98 
     99 " }}}1
    100 " Initialization {{{1
    101 
    102 function! s:ExtractGitDir(path) abort
    103   let path = s:shellslash(a:path)
    104   if path =~? '^fugitive://.*//'
    105     return matchstr(path,'fugitive://\zs.\{-\}\ze//')
    106   endif
    107   let fn = fnamemodify(path,':s?[\/]$??')
    108   let ofn = ""
    109   let nfn = fn
    110   while fn != ofn
    111     if isdirectory(fn . '/.git')
    112       return s:sub(simplify(fnamemodify(fn . '/.git',':p')),'\W$','')
    113     elseif fn =~ '\.git$' && filereadable(fn . '/HEAD')
    114       return s:sub(simplify(fnamemodify(fn,':p')),'\W$','')
    115     endif
    116     let ofn = fn
    117     let fn = fnamemodify(ofn,':h')
    118   endwhile
    119   return ''
    120 endfunction
    121 
    122 function! s:Detect(path)
    123   if exists('b:git_dir') && b:git_dir ==# ''
    124     unlet b:git_dir
    125   endif
    126   if !exists('b:git_dir')
    127     let dir = s:ExtractGitDir(a:path)
    128     if dir != ''
    129       let b:git_dir = dir
    130     endif
    131   endif
    132   if exists('b:git_dir')
    133     silent doautocmd User Fugitive
    134     cnoremap <expr> <buffer> <C-R><C-G> fugitive#buffer().rev()
    135     let buffer = fugitive#buffer()
    136     if expand('%:p') =~# '//'
    137       call buffer.setvar('&path',s:sub(buffer.getvar('&path'),'^\.%(,|$)',''))
    138     endif
    139     if b:git_dir !~# ',' && stridx(buffer.getvar('&tags'),b:git_dir.'/tags') == -1
    140       call buffer.setvar('&tags',buffer.getvar('&tags').','.b:git_dir.'/tags')
    141     endif
    142   endif
    143 endfunction
    144 
    145 augroup fugitive
    146   autocmd!
    147   autocmd BufNewFile,BufReadPost * call s:Detect(expand('<amatch>:p'))
    148   autocmd FileType           netrw call s:Detect(expand('<afile>:p'))
    149   autocmd VimEnter * if expand('<amatch>')==''|call s:Detect(getcwd())|endif
    150   autocmd BufWinLeave * execute getwinvar(+winnr(), 'fugitive_restore')
    151 augroup END
    152 
    153 " }}}1
    154 " Repository {{{1
    155 
    156 let s:repo_prototype = {}
    157 let s:repos = {}
    158 
    159 function! s:repo(...) abort
    160   let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : s:ExtractGitDir(expand('%:p')))
    161   if dir !=# ''
    162     if has_key(s:repos,dir)
    163       let repo = get(s:repos,dir)
    164     else
    165       let repo = {'git_dir': dir}
    166       let s:repos[dir] = repo
    167     endif
    168     return extend(extend(repo,s:repo_prototype,'keep'),s:abstract_prototype,'keep')
    169   endif
    170   call s:throw('not a git repository: '.expand('%:p'))
    171 endfunction
    172 
    173 function! s:repo_dir(...) dict abort
    174   return join([self.git_dir]+a:000,'/')
    175 endfunction
    176 
    177 function! s:repo_tree(...) dict abort
    178   if !self.bare()
    179     let dir = fnamemodify(self.git_dir,':h')
    180     return join([dir]+a:000,'/')
    181   endif
    182   call s:throw('no work tree')
    183 endfunction
    184 
    185 function! s:repo_bare() dict abort
    186   return self.dir() !~# '/\.git$'
    187 endfunction
    188 
    189 function! s:repo_translate(spec) dict abort
    190   if a:spec ==# '.' || a:spec ==# '/.'
    191     return self.bare() ? self.dir() : self.tree()
    192   elseif a:spec =~# '^/'
    193     return fnamemodify(self.dir(),':h').a:spec
    194   elseif a:spec =~# '^:[0-3]:'
    195     return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
    196   elseif a:spec ==# ':'
    197     if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(s:repo().dir())] ==# s:repo().dir('') && filereadable($GIT_INDEX_FILE)
    198       return fnamemodify($GIT_INDEX_FILE,':p')
    199     else
    200       return self.dir('index')
    201     endif
    202   elseif a:spec =~# '^:/'
    203     let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
    204     return 'fugitive://'.self.dir().'//'.ref
    205   elseif a:spec =~# '^:'
    206     return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
    207   elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec))
    208     return self.dir(a:spec)
    209   elseif filereadable(s:repo().dir('refs/'.a:spec))
    210     return self.dir('refs/'.a:spec)
    211   elseif filereadable(s:repo().dir('refs/tags/'.a:spec))
    212     return self.dir('refs/tags/'.a:spec)
    213   elseif filereadable(s:repo().dir('refs/heads/'.a:spec))
    214     return self.dir('refs/heads/'.a:spec)
    215   elseif filereadable(s:repo().dir('refs/remotes/'.a:spec))
    216     return self.dir('refs/remotes/'.a:spec)
    217   elseif filereadable(s:repo().dir('refs/remotes/'.a:spec.'/HEAD'))
    218     return self.dir('refs/remotes/'.a:spec,'/HEAD')
    219   else
    220     try
    221       let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
    222       let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
    223       return 'fugitive://'.self.dir().'//'.ref.path
    224     catch /^fugitive:/
    225       return self.tree(a:spec)
    226     endtry
    227   endif
    228 endfunction
    229 
    230 call s:add_methods('repo',['dir','tree','bare','translate'])
    231 
    232 function! s:repo_git_command(...) dict abort
    233   let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
    234   return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
    235 endfunction
    236 
    237 function! s:repo_git_chomp(...) dict abort
    238   return s:sub(system(call(self.git_command,a:000,self)),'\n$','')
    239 endfunction
    240 
    241 function! s:repo_git_chomp_in_tree(...) dict abort
    242   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
    243   let dir = getcwd()
    244   try
    245     execute cd.'`=s:repo().tree()`'
    246     return call(s:repo().git_chomp, a:000, s:repo())
    247   finally
    248     execute cd.'`=dir`'
    249   endtry
    250 endfunction
    251 
    252 function! s:repo_rev_parse(rev) dict abort
    253   let hash = self.git_chomp('rev-parse','--verify',a:rev)
    254   if hash =~ '^\x\{40\}$'
    255     return hash
    256   endif
    257   call s:throw('rev-parse '.a:rev.': '.hash)
    258 endfunction
    259 
    260 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
    261 
    262 function! s:repo_dirglob(base) dict abort
    263   let base = s:sub(a:base,'^/','')
    264   let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
    265   call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
    266   return matches
    267 endfunction
    268 
    269 function! s:repo_superglob(base) dict abort
    270   if a:base =~# '^/' || a:base !~# ':'
    271     let results = []
    272     if a:base !~# '^/'
    273       let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
    274       let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
    275       call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
    276       let results += heads
    277     endif
    278     if !self.bare()
    279       let base = s:sub(a:base,'^/','')
    280       let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
    281       call map(matches,'s:shellslash(v:val)')
    282       call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
    283       call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
    284       let results += matches
    285     endif
    286     return results
    287 
    288   elseif a:base =~# '^:'
    289     let entries = split(self.git_chomp('ls-files','--stage'),"\n")
    290     call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
    291     if a:base !~# '^:[0-3]\%(:\|$\)'
    292       call filter(entries,'v:val[1] == "0"')
    293       call map(entries,'v:val[2:-1]')
    294     endif
    295     call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
    296     return entries
    297 
    298   else
    299     let tree = matchstr(a:base,'.*[:/]')
    300     let entries = split(self.git_chomp('ls-tree',tree),"\n")
    301     call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
    302     call map(entries,'tree.s:sub(v:val,".*\t","")')
    303     return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
    304   endif
    305 endfunction
    306 
    307 call s:add_methods('repo',['dirglob','superglob'])
    308 
    309 function! s:repo_keywordprg() dict abort
    310   let args = ' --git-dir='.escape(self.dir(),"\\\"' ").' show'
    311   if has('gui_running') && !has('win32')
    312     return g:fugitive_git_executable . ' --no-pager' . args
    313   else
    314     return g:fugitive_git_executable . args
    315   endif
    316 endfunction
    317 
    318 call s:add_methods('repo',['keywordprg'])
    319 
    320 " }}}1
    321 " Buffer {{{1
    322 
    323 let s:buffer_prototype = {}
    324 
    325 function! s:buffer(...) abort
    326   let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
    327   call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
    328   if buffer.getvar('git_dir') !=# ''
    329     return buffer
    330   endif
    331   call s:throw('not a git repository: '.expand('%:p'))
    332 endfunction
    333 
    334 function! fugitive#buffer(...) abort
    335   return s:buffer(a:0 ? a:1 : '%')
    336 endfunction
    337 
    338 function! s:buffer_getvar(var) dict abort
    339   return getbufvar(self['#'],a:var)
    340 endfunction
    341 
    342 function! s:buffer_setvar(var,value) dict abort
    343   return setbufvar(self['#'],a:var,a:value)
    344 endfunction
    345 
    346 function! s:buffer_getline(lnum) dict abort
    347   return getbufline(self['#'],a:lnum)[0]
    348 endfunction
    349 
    350 function! s:buffer_repo() dict abort
    351   return s:repo(self.getvar('git_dir'))
    352 endfunction
    353 
    354 function! s:buffer_type(...) dict abort
    355   if self.getvar('fugitive_type') != ''
    356     let type = self.getvar('fugitive_type')
    357   elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
    358     let type = 'head'
    359   elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
    360     let type = 'tree'
    361   elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
    362     let type = 'tree'
    363   elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
    364     let type = 'index'
    365   elseif isdirectory(self.spec())
    366     let type = 'directory'
    367   elseif self.spec() == ''
    368     let type = 'null'
    369   elseif filereadable(self.spec())
    370     let type = 'file'
    371   else
    372     let type = ''
    373   endif
    374   if a:0
    375     return !empty(filter(copy(a:000),'v:val ==# type'))
    376   else
    377     return type
    378   endif
    379 endfunction
    380 
    381 function! s:buffer_spec() dict abort
    382   let bufname = bufname(self['#'])
    383   return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
    384 endfunction
    385 
    386 function! s:buffer_name() dict abort
    387   return self.spec()
    388 endfunction
    389 
    390 function! s:buffer_commit() dict abort
    391   return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
    392 endfunction
    393 
    394 function! s:buffer_path(...) dict abort
    395   let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
    396   if rev != ''
    397     let rev = s:sub(rev,'\w*','')
    398   else
    399     let rev = self.spec()[strlen(self.repo().tree()) : -1]
    400   endif
    401   return s:sub(rev,'^/',a:0 ? a:1 : '')
    402 endfunction
    403 
    404 function! s:buffer_rev() dict abort
    405   let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
    406   if rev =~ '^\x/'
    407     return ':'.rev[0].':'.rev[2:-1]
    408   elseif rev =~ '.'
    409     return s:sub(rev,'/',':')
    410   elseif self.spec() =~ '\.git/index$'
    411     return ':'
    412   elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
    413     return self.spec()[strlen(self.repo().dir())+1 : -1]
    414   else
    415     return self.path()
    416   endif
    417 endfunction
    418 
    419 function! s:buffer_sha1() dict abort
    420   if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
    421     return self.repo().rev_parse(self.rev())
    422   else
    423     return ''
    424   endif
    425 endfunction
    426 
    427 function! s:buffer_expand(rev) dict abort
    428   if a:rev =~# '^:[0-3]$'
    429     let file = a:rev.self.path(':')
    430   elseif a:rev =~# '^-'
    431     let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
    432   elseif a:rev =~# '^@{'
    433     let file = 'HEAD'.a:rev.self.path(':')
    434   elseif a:rev =~# '^[~^]'
    435     let commit = s:sub(self.commit(),'^\d=$','HEAD')
    436     let file = commit.a:rev.self.path(':')
    437   else
    438     let file = a:rev
    439   endif
    440   return s:sub(file,'\%$',self.path())
    441 endfunction
    442 
    443 function! s:buffer_containing_commit() dict abort
    444   if self.commit() =~# '^\d$'
    445     return ':'
    446   elseif self.commit() =~# '.'
    447     return self.commit()
    448   else
    449     return 'HEAD'
    450   endif
    451 endfunction
    452 
    453 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit'])
    454 
    455 " }}}1
    456 " Git {{{1
    457 
    458 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
    459 
    460 function! s:ExecuteInTree(cmd) abort
    461   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
    462   let dir = getcwd()
    463   try
    464     execute cd.'`=s:repo().tree()`'
    465     execute a:cmd
    466   finally
    467     execute cd.'`=dir`'
    468   endtry
    469 endfunction
    470 
    471 function! s:Git(bang,cmd) abort
    472   let git = s:repo().git_command()
    473   if has('gui_running') && !has('win32')
    474     let git .= ' --no-pager'
    475   endif
    476   let cmd = matchstr(a:cmd,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
    477   call s:ExecuteInTree('!'.git.' '.cmd)
    478   call fugitive#reload_status()
    479   return matchstr(a:cmd,'\v\C\\@<!%(\\\\)*\|\zs.*')
    480 endfunction
    481 
    482 function! s:GitComplete(A,L,P) abort
    483   if !exists('s:exec_path')
    484     let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
    485   endif
    486   let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
    487   if a:L =~ ' [[:alnum:]-]\+ '
    488     return s:repo().superglob(a:A)
    489   elseif a:A == ''
    490     return cmds
    491   else
    492     return filter(cmds,'v:val[0 : strlen(a:A)-1] ==# a:A')
    493   endif
    494 endfunction
    495 
    496 " }}}1
    497 " Gcd, Glcd {{{1
    498 
    499 function! s:DirComplete(A,L,P) abort
    500   let matches = s:repo().dirglob(a:A)
    501   return matches
    502 endfunction
    503 
    504 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd  :cd<bang>  `=s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>)`")
    505 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :lcd<bang> `=s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>)`")
    506 
    507 " }}}1
    508 " Gstatus {{{1
    509 
    510 call s:command("-bar Gstatus :execute s:Status()")
    511 
    512 function! s:Status() abort
    513   try
    514     Gpedit :
    515     wincmd P
    516     nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
    517   catch /^fugitive:/
    518     return 'echoerr v:errmsg'
    519   endtry
    520   return ''
    521 endfunction
    522 
    523 function! fugitive#reload_status() abort
    524   let mytab = tabpagenr()
    525   for tab in [mytab] + range(1,tabpagenr('$'))
    526     for winnr in range(1,tabpagewinnr(tab,'$'))
    527       if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
    528         execute 'tabnext '.tab
    529         if winnr != winnr()
    530           execute winnr.'wincmd w'
    531           let restorewinnr = 1
    532         endif
    533         try
    534           if !&modified
    535             call s:BufReadIndex()
    536           endif
    537         finally
    538           if exists('restorewinnr')
    539             wincmd p
    540           endif
    541           execute 'tabnext '.mytab
    542         endtry
    543       endif
    544     endfor
    545   endfor
    546 endfunction
    547 
    548 function! s:StageDiff(bang) abort
    549   let section = getline(search('^# .*:$','bnW'))
    550   let line = getline('.')
    551   let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
    552   if filename ==# '' && section == '# Changes to be committed:'
    553     return 'Git diff --cached'
    554   elseif filename ==# ''
    555     return 'Git diff'
    556   elseif line =~# '^#\trenamed:' && filename =~ ' -> '
    557     let [old, new] = split(filename,' -> ')
    558     execute 'Gedit '.s:fnameescape(':0:'.new)
    559     return 'Gdiff'.a:bang.' HEAD:'.s:fnameescape(old)
    560   elseif section == '# Changes to be committed:'
    561     execute 'Gedit '.s:fnameescape(':0:'.filename)
    562     return 'Gdiff'.a:bang.' -'
    563   else
    564     execute 'Gedit '.s:fnameescape('/'.filename)
    565     return 'Gdiff'.a:bang
    566   endif
    567 endfunction
    568 
    569 function! s:StageToggle(lnum1,lnum2) abort
    570   try
    571     let output = ''
    572     for lnum in range(a:lnum1,a:lnum2)
    573       let line = getline(lnum)
    574       if getline('.') == '# Changes to be committed:'
    575         return 'Gcommit'
    576       endif
    577       let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
    578       if filename ==# ''
    579         continue
    580       endif
    581       if !exists('first_filename')
    582         let first_filename = filename
    583       endif
    584       execute lnum
    585       let section = getline(search('^# .*:$','bnW'))
    586       if line =~# '^#\trenamed:' && filename =~ ' -> '
    587         let cmd = ['mv','--'] + reverse(split(filename,' -> '))
    588         let filename = cmd[-1]
    589       elseif section =~? ' to be '
    590         let cmd = ['reset','-q','--',filename]
    591       elseif line =~# '^#\tdeleted:'
    592         let cmd = ['rm','--',filename]
    593       else
    594         let cmd = ['add','--',filename]
    595       endif
    596       let output .= call(s:repo().git_chomp_in_tree,cmd,s:repo())."\n"
    597     endfor
    598     if exists('first_filename')
    599       let jump = first_filename
    600       let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
    601       if f !=# '' | let jump = f | endif
    602       let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
    603       if f !=# '' | let jump = f | endif
    604       silent! edit!
    605       1
    606       redraw
    607       call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.jump.'\$','W')
    608     endif
    609     echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
    610   catch /^fugitive:/
    611     return 'echoerr v:errmsg'
    612   endtry
    613   return 'checktime'
    614 endfunction
    615 
    616 function! s:StagePatch(lnum1,lnum2) abort
    617   let add = []
    618   let reset = []
    619 
    620   for lnum in range(a:lnum1,a:lnum2)
    621     let line = getline(lnum)
    622     if line == '# Changes to be committed:'
    623       return 'Git reset --patch'
    624     elseif line == '# Changed but not updated:'
    625       return 'Git add --patch'
    626     endif
    627     let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
    628     if filename ==# ''
    629       continue
    630     endif
    631     if !exists('first_filename')
    632       let first_filename = filename
    633     endif
    634     execute lnum
    635     let section = getline(search('^# .*:$','bnW'))
    636     if line =~# '^#\trenamed:' && filename =~ ' -> '
    637       let reset += [split(filename,' -> ')[1]]
    638     elseif section =~? ' to be '
    639       let reset += [filename]
    640     elseif line !~# '^#\tdeleted:'
    641       let add += [filename]
    642     endif
    643   endfor
    644   try
    645     if !empty(add)
    646       execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
    647     endif
    648     if !empty(reset)
    649       execute "Git reset --patch -- ".join(map(add,'s:shellesc(v:val)'))
    650     endif
    651     if exists('first_filename')
    652       silent! edit!
    653       1
    654       redraw
    655       call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\$','W')
    656     endif
    657   catch /^fugitive:/
    658     return 'echoerr v:errmsg'
    659   endtry
    660   return 'checktime'
    661 endfunction
    662 
    663 " }}}1
    664 " Gcommit {{{1
    665 
    666 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
    667 
    668 function! s:Commit(args) abort
    669   let old_type = s:buffer().type()
    670   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
    671   let dir = getcwd()
    672   let msgfile = s:repo().dir('COMMIT_EDITMSG')
    673   let outfile = tempname()
    674   let errorfile = tempname()
    675   try
    676     execute cd.'`=s:repo().tree()`'
    677     let command = ''
    678     if &shell =~# 'cmd'
    679       let old_editor = $GIT_EDITOR
    680       let $GIT_EDITOR = 'false'
    681     elseif &shell !~# 'csh'
    682       let command = 'GIT_EDITOR=false '
    683     endif
    684     let command .= s:repo().git_command('commit').' '.a:args
    685     if &shell =~# 'csh'
    686       silent execute '!setenv GIT_EDITOR false; ('.command.' > '.outfile.') >& '.errorfile
    687     elseif a:args =~# '\%(^\| \)--interactive\>'
    688       execute '!'.command.' 2> '.errorfile
    689     else
    690       silent execute '!'.command.' > '.outfile.' 2> '.errorfile
    691     endif
    692     if !v:shell_error
    693       if filereadable(outfile)
    694         for line in readfile(outfile)
    695           echo line
    696         endfor
    697       endif
    698       return ''
    699     else
    700       let error = get(readfile(errorfile),-2,'!')
    701       if error =~# "'false'\\.$"
    702         let args = a:args
    703         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[se]|--edit|--interactive)%($| )','')
    704         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
    705         let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
    706         let args = '-F '.s:shellesc(msgfile).' '.args
    707         if args !~# '\%(^\| \)--cleanup\>'
    708           let args = '--cleanup=strip '.args
    709         endif
    710         if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
    711           edit `=msgfile`
    712         else
    713           split `=msgfile`
    714         endif
    715         if old_type ==# 'index'
    716           bdelete #
    717         endif
    718         let b:fugitive_commit_arguments = args
    719         setlocal bufhidden=delete filetype=gitcommit
    720         return '1'
    721       elseif error ==# '!'
    722         return s:Status()
    723       else
    724         call s:throw(error)
    725       endif
    726     endif
    727   catch /^fugitive:/
    728     return 'echoerr v:errmsg'
    729   finally
    730     if exists('old_editor')
    731       let $GIT_EDITOR = old_editor
    732     endif
    733     call delete(outfile)
    734     call delete(errorfile)
    735     execute cd.'`=dir`'
    736     call fugitive#reload_status()
    737   endtry
    738 endfunction
    739 
    740 function! s:CommitComplete(A,L,P) abort
    741   if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
    742     let args = ['-C', '-F', '-a', '-c', '-e', '-i', '-m', '-n', '-o', '-q', '-s', '-t', '-u', '-v', '--all', '--allow-empty', '--amend', '--author=', '--cleanup=', '--dry-run', '--edit', '--file=', '--include', '--interactive', '--message=', '--no-verify', '--only', '--quiet', '--reedit-message=', '--reuse-message=', '--signoff', '--template=', '--untracked-files', '--verbose']
    743     return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
    744   else
    745     return s:repo().superglob(a:A)
    746   endif
    747 endfunction
    748 
    749 function! s:FinishCommit()
    750   let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
    751   let g:args = args
    752   if !empty(args)
    753     call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
    754     return s:Commit(args)
    755   endif
    756   return ''
    757 endfunction
    758 
    759 augroup fugitive_commit
    760   autocmd!
    761   autocmd VimLeavePre,BufDelete *.git/COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
    762 augroup END
    763 
    764 " }}}1
    765 " Ggrep, Glog {{{1
    766 
    767 if !exists('g:fugitive_summary_format')
    768   let g:fugitive_summary_format = '%s'
    769 endif
    770 
    771 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep(<bang>0,<q-args>)")
    772 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Glog :execute s:Log('grep<bang>',<f-args>)")
    773 
    774 function! s:Grep(bang,arg) abort
    775   let grepprg = &grepprg
    776   let grepformat = &grepformat
    777   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
    778   let dir = getcwd()
    779   try
    780     execute cd.'`=s:repo().tree()`'
    781     let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n')
    782     let &grepformat = '%f:%l:%m'
    783     exe 'grep! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
    784     let list = getqflist()
    785     for entry in list
    786       if bufname(entry.bufnr) =~ ':'
    787         let entry.filename = s:repo().translate(bufname(entry.bufnr))
    788         unlet! entry.bufnr
    789       elseif a:arg =~# '\%(^\| \)--cached\>'
    790         let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
    791         unlet! entry.bufnr
    792       endif
    793     endfor
    794     call setqflist(list,'r')
    795     if !a:bang && !empty(list)
    796       return 'cfirst'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
    797     else
    798       return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
    799     endif
    800   finally
    801     let &grepprg = grepprg
    802     let &grepformat = grepformat
    803     execute cd.'`=dir`'
    804   endtry
    805 endfunction
    806 
    807 function! s:Log(cmd,...)
    808   let path = s:buffer().path('/')
    809   if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
    810     let path = ''
    811   endif
    812   let cmd = ['--no-pager', 'log', '--no-color']
    813   let cmd += [escape('--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format,'%')]
    814   if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
    815     if s:buffer().commit() =~# '\x\{40\}'
    816       let cmd += [s:buffer().commit()]
    817     elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
    818       let cmd += [s:buffer().path()[5:-1]]
    819     endif
    820   end
    821   let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
    822   if path =~# '/.'
    823     let cmd += ['--',path[1:-1]]
    824   endif
    825   let grepformat = &grepformat
    826   let grepprg = &grepprg
    827   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
    828   let dir = getcwd()
    829   try
    830     execute cd.'`=s:repo().tree()`'
    831     let &grepprg = call(s:repo().git_command,cmd,s:repo())
    832     let &grepformat = '%f::%m'
    833     exe a:cmd
    834   finally
    835     let &grepformat = grepformat
    836     let &grepprg = grepprg
    837     execute cd.'`=dir`'
    838   endtry
    839 endfunction
    840 
    841 " }}}1
    842 " Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread {{{1
    843 
    844 function! s:Edit(cmd,...) abort
    845   if a:0 && a:1 == ''
    846     return ''
    847   elseif a:0
    848     let file = s:buffer().expand(a:1)
    849   elseif s:buffer().commit() ==# '' && s:buffer().path('/') !~# '^/.git\>'
    850     let file = s:buffer().path(':')
    851   else
    852     let file = s:buffer().path('/')
    853   endif
    854   try
    855     let file = s:repo().translate(file)
    856   catch /^fugitive:/
    857     return 'echoerr v:errmsg'
    858   endtry
    859   if a:cmd =~# 'read!$' || a:cmd ==# 'read'
    860     if a:cmd =~# '!$'
    861       call s:warn(':Gread! is deprecated. Use :Gread')
    862     endif
    863     return 'silent %delete|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
    864   else
    865     if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
    866       wincmd p
    867     endif
    868     return a:cmd.' '.s:fnameescape(file)
    869   endif
    870 endfunction
    871 
    872 function! s:EditComplete(A,L,P) abort
    873   return s:repo().superglob(a:A)
    874 endfunction
    875 
    876 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Ge       :execute s:Edit('edit<bang>',<f-args>)")
    877 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gedit    :execute s:Edit('edit<bang>',<f-args>)")
    878 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gpedit   :execute s:Edit('pedit<bang>',<f-args>)")
    879 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gsplit   :execute s:Edit('split<bang>',<f-args>)")
    880 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gvsplit  :execute s:Edit('vsplit<bang>',<f-args>)")
    881 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gtabedit :execute s:Edit('tabedit<bang>',<f-args>)")
    882 call s:command("-bar -bang -nargs=? -count -complete=customlist,s:EditComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read<bang>',<f-args>)")
    883 
    884 " }}}1
    885 " Gwrite {{{1
    886 
    887 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
    888 
    889 function! s:Write(force,...) abort
    890   if exists('b:fugitive_commit_arguments')
    891     return 'write|bdelete'
    892   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
    893     return 'wq'
    894   elseif s:buffer().type() == 'index'
    895     return 'Gcommit'
    896   endif
    897   let mytab = tabpagenr()
    898   let mybufnr = bufnr('')
    899   let path = a:0 ? a:1 : s:buffer().path()
    900   if path =~# '^:\d\>'
    901     return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
    902   endif
    903   let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
    904   if !always_permitted && !a:force && s:repo().git_chomp_in_tree('diff','--name-status','HEAD','--',path) . s:repo().git_chomp_in_tree('ls-files','--others','--',path) !=# ''
    905     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
    906     return 'echoerr v:errmsg'
    907   endif
    908   let file = s:repo().translate(path)
    909   let treebufnr = 0
    910   for nr in range(1,bufnr('$'))
    911     if fnamemodify(bufname(nr),':p') ==# file
    912       let treebufnr = nr
    913     endif
    914   endfor
    915 
    916   if treebufnr > 0 && treebufnr != bufnr('')
    917     let temp = tempname()
    918     silent execute '%write '.temp
    919     for tab in [mytab] + range(1,tabpagenr('$'))
    920       for winnr in range(1,tabpagewinnr(tab,'$'))
    921         if tabpagebuflist(tab)[winnr-1] == treebufnr
    922           execute 'tabnext '.tab
    923           if winnr != winnr()
    924             execute winnr.'wincmd w'
    925             let restorewinnr = 1
    926           endif
    927           try
    928             let lnum = line('.')
    929             let last = line('$')
    930             silent execute '$read '.temp
    931             silent execute '1,'.last.'delete_'
    932             silent write!
    933             silent execute lnum
    934             let did = 1
    935           finally
    936             if exists('restorewinnr')
    937               wincmd p
    938             endif
    939             execute 'tabnext '.mytab
    940           endtry
    941         endif
    942       endfor
    943     endfor
    944     if !exists('did')
    945       call writefile(readfile(temp,'b'),file,'b')
    946     endif
    947   else
    948     execute 'write! '.s:fnameescape(s:repo().translate(path))
    949   endif
    950 
    951   if a:force
    952     let error = s:repo().git_chomp_in_tree('add', '--force', file)
    953   else
    954     let error = s:repo().git_chomp_in_tree('add', file)
    955   endif
    956   if v:shell_error
    957     let v:errmsg = 'fugitive: '.error
    958     return 'echoerr v:errmsg'
    959   endif
    960   if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
    961     set nomodified
    962   endif
    963 
    964   let one = s:repo().translate(':1:'.path)
    965   let two = s:repo().translate(':2:'.path)
    966   let three = s:repo().translate(':3:'.path)
    967   for nr in range(1,bufnr('$'))
    968     if bufloaded(nr) && !getbufvar(nr,'&modified') && (bufname(nr) == one || bufname(nr) == two || bufname(nr) == three)
    969       execute nr.'bdelete'
    970     endif
    971   endfor
    972 
    973   unlet! restorewinnr
    974   let zero = s:repo().translate(':0:'.path)
    975   for tab in range(1,tabpagenr('$'))
    976     for winnr in range(1,tabpagewinnr(tab,'$'))
    977       let bufnr = tabpagebuflist(tab)[winnr-1]
    978       let bufname = bufname(bufnr)
    979       if bufname ==# zero && bufnr != mybufnr
    980         execute 'tabnext '.tab
    981         if winnr != winnr()
    982           execute winnr.'wincmd w'
    983           let restorewinnr = 1
    984         endif
    985         try
    986           let lnum = line('.')
    987           let last = line('$')
    988           silent $read `=file`
    989           silent execute '1,'.last.'delete_'
    990           silent execute lnum
    991           set nomodified
    992           diffupdate
    993         finally
    994           if exists('restorewinnr')
    995             wincmd p
    996           endif
    997           execute 'tabnext '.mytab
    998         endtry
    999         break
   1000       endif
   1001     endfor
   1002   endfor
   1003   call fugitive#reload_status()
   1004   return 'checktime'
   1005 endfunction
   1006 
   1007 " }}}1
   1008 " Gdiff {{{1
   1009 
   1010 call s:command("-bang -bar -nargs=? -complete=customlist,s:EditComplete Gdiff :execute s:Diff(<bang>0,<f-args>)")
   1011 
   1012 augroup fugitive_diff
   1013   autocmd!
   1014   autocmd BufWinLeave * if s:diff_window_count() == 2 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | execute 'windo call s:diff_off()' | endif
   1015   autocmd BufWinEnter * if s:diff_window_count() == 1 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | call s:diff_off() | endif
   1016 augroup END
   1017 
   1018 function! s:diff_window_count()
   1019   let c = 0
   1020   for nr in range(1,winnr('$'))
   1021     let c += getwinvar(nr,'&diff')
   1022   endfor
   1023   return c
   1024 endfunction
   1025 
   1026 function! s:diff_off()
   1027   if &l:diff
   1028     diffoff
   1029   endif
   1030 endfunction
   1031 
   1032 function! s:buffer_compare_age(commit) dict abort
   1033   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
   1034   let my_score    = get(scores,':'.self.commit(),0)
   1035   let their_score = get(scores,':'.a:commit,0)
   1036   if my_score || their_score
   1037     return my_score < their_score ? -1 : my_score != their_score
   1038   elseif self.commit() ==# a:commit
   1039     return 0
   1040   endif
   1041   let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
   1042   if base ==# self.commit()
   1043     return -1
   1044   elseif base ==# a:commit
   1045     return 1
   1046   endif
   1047   let my_time    = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
   1048   let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
   1049   return my_time < their_time ? -1 : my_time != their_time
   1050 endfunction
   1051 
   1052 call s:add_methods('buffer',['compare_age'])
   1053 
   1054 function! s:Diff(bang,...) abort
   1055   let split = a:bang ? 'split' : 'vsplit'
   1056   if exists(':DiffGitCached')
   1057     return 'DiffGitCached'
   1058   elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
   1059     execute 'leftabove '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
   1060     diffthis
   1061     wincmd p
   1062     execute 'rightbelow '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
   1063     diffthis
   1064     wincmd p
   1065     diffthis
   1066     return ''
   1067   elseif a:0
   1068     if a:1 ==# ''
   1069       return ''
   1070     elseif a:1 ==# '/'
   1071       let file = s:buffer().path('/')
   1072     elseif a:1 ==# ':'
   1073       let file = s:buffer().path(':0:')
   1074     elseif a:1 =~# '^:/'
   1075       try
   1076         let file = s:repo().rev_parse(a:1).s:buffer().path(':')
   1077       catch /^fugitive:/
   1078         return 'echoerr v:errmsg'
   1079       endtry
   1080     else
   1081       let file = s:buffer().expand(a:1)
   1082     endif
   1083     if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
   1084       let file = file.s:buffer().path(':')
   1085     endif
   1086   else
   1087     let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
   1088   endif
   1089   try
   1090     let spec = s:repo().translate(file)
   1091     let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
   1092     if s:buffer().compare_age(commit) < 0
   1093       execute 'rightbelow '.split.' `=spec`'
   1094     else
   1095       execute 'leftabove '.split.' `=spec`'
   1096     endif
   1097     diffthis
   1098     wincmd p
   1099     diffthis
   1100     return ''
   1101   catch /^fugitive:/
   1102     return 'echoerr v:errmsg'
   1103   endtry
   1104 endfunction
   1105 
   1106 " }}}1
   1107 " Gmove, Gremove {{{1
   1108 
   1109 function! s:Move(force,destination)
   1110   if a:destination =~# '^/'
   1111     let destination = a:destination[1:-1]
   1112   else
   1113     let destination = fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p')
   1114     if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
   1115       let destination = destination[strlen(s:repo().tree('')):-1]
   1116     endif
   1117   endif
   1118   let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
   1119   if v:shell_error
   1120     let v:errmsg = 'fugitive: '.message
   1121     return 'echoerr v:errmsg'
   1122   endif
   1123   let destination = s:repo().tree(destination)
   1124   if isdirectory(destination)
   1125     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
   1126   endif
   1127   call fugitive#reload_status()
   1128   if s:buffer().commit() == ''
   1129     return 'saveas! '.s:fnameescape(destination)
   1130   else
   1131     return 'file '.s:fnameescape(s:repo().translate(':0:'.destination)
   1132   endif
   1133 endfunction
   1134 
   1135 function! s:MoveComplete(A,L,P)
   1136   if a:A =~ '^/'
   1137     return s:repo().superglob(a:A)
   1138   else
   1139     let matches = split(glob(a:A.'*'),"\n")
   1140     call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
   1141     return matches
   1142   endif
   1143 endfunction
   1144 
   1145 function! s:Remove(force)
   1146   if s:buffer().commit() ==# ''
   1147     let cmd = ['rm']
   1148   elseif s:buffer().commit() ==# '0'
   1149     let cmd = ['rm','--cached']
   1150   else
   1151     let v:errmsg = 'fugitive: rm not supported here'
   1152     return 'echoerr v:errmsg'
   1153   endif
   1154   if a:force
   1155     let cmd += ['--force']
   1156   endif
   1157   let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
   1158   if v:shell_error
   1159     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
   1160     return 'echoerr '.string(v:errmsg)
   1161   else
   1162     call fugitive#reload_status()
   1163     return 'bdelete'.(a:force ? '!' : '')
   1164   endif
   1165 endfunction
   1166 
   1167 augroup fugitive_remove
   1168   autocmd!
   1169   autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
   1170         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
   1171         \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
   1172         \ endif
   1173 augroup END
   1174 
   1175 " }}}1
   1176 " Gblame {{{1
   1177 
   1178 augroup fugitive_blame
   1179   autocmd!
   1180   autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
   1181   autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
   1182   autocmd Syntax fugitiveblame call s:BlameSyntax()
   1183   autocmd User Fugitive if s:buffer().type('file', 'blob') | exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,[<f-args>])" | endif
   1184 augroup END
   1185 
   1186 function! s:Blame(bang,line1,line2,count,args) abort
   1187   try
   1188     if s:buffer().path() == ''
   1189       call s:throw('file or blob required')
   1190     endif
   1191     if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltwfs]\\|[MC]\\d*\\)\\+\\)$"') != []
   1192       call s:throw('unsupported option')
   1193     endif
   1194     call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
   1195     let git_dir = s:repo().dir()
   1196     let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
   1197     if s:buffer().commit() =~# '\D\|..'
   1198       let cmd += [s:buffer().commit()]
   1199     else
   1200       let cmd += ['--contents', '-']
   1201     endif
   1202     let basecmd = call(s:repo().git_command,cmd+['--',s:buffer().path()],s:repo())
   1203     try
   1204       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
   1205       if !s:repo().bare()
   1206         let dir = getcwd()
   1207         execute cd.'`=s:repo().tree()`'
   1208       endif
   1209       if a:count
   1210         execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
   1211       else
   1212         let error = tempname()
   1213         let temp = error.'.fugitiveblame'
   1214         if &shell =~# 'csh'
   1215           silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
   1216         else
   1217           silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
   1218         endif
   1219         if v:shell_error
   1220           call s:throw(join(readfile(error),"\n"))
   1221         endif
   1222         let bufnr = bufnr('')
   1223         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
   1224         if &l:wrap
   1225           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
   1226         endif
   1227         if &l:foldenable
   1228           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
   1229         endif
   1230         let winnr = winnr()
   1231         windo set noscrollbind
   1232         exe winnr.'wincmd w'
   1233         setlocal scrollbind nowrap nofoldenable
   1234         let top = line('w0') + &scrolloff
   1235         let current = line('.')
   1236         exe 'leftabove vsplit '.temp
   1237         let b:git_dir = git_dir
   1238         let b:fugitive_type = 'blame'
   1239         let b:fugitive_blamed_bufnr = bufnr
   1240         let w:fugitive_restore = restore
   1241         let b:fugitive_blame_arguments = join(a:args,' ')
   1242         call s:Detect(expand('%:p'))
   1243         execute top
   1244         normal! zt
   1245         execute current
   1246         execute "vertical resize ".(match(getline('.'),'\s\+\d\+)')+1)
   1247         setlocal nomodified nomodifiable bufhidden=delete nonumber scrollbind nowrap foldcolumn=0 nofoldenable filetype=fugitiveblame
   1248         nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
   1249         nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameJump('')<CR>
   1250         nnoremap <buffer> <silent> P    :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
   1251         nnoremap <buffer> <silent> ~    :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
   1252         nnoremap <buffer> <silent> o    :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft")." split", matchstr(getline('.'),'\x\+'))<CR>
   1253         nnoremap <buffer> <silent> O    :<C-U>exe <SID>Edit("tabedit", matchstr(getline('.'),'\x\+'))<CR>
   1254         syncbind
   1255       endif
   1256     finally
   1257       if exists('l:dir')
   1258         execute cd.'`=dir`'
   1259       endif
   1260     endtry
   1261     return ''
   1262   catch /^fugitive:/
   1263     return 'echoerr v:errmsg'
   1264   endtry
   1265 endfunction
   1266 
   1267 function! s:BlameJump(suffix) abort
   1268   let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
   1269   if commit =~# '^0\+$'
   1270     let commit = ':0'
   1271   endif
   1272   let lnum = matchstr(getline('.'),'\d\+\ze\s\+[([:digit:]]')
   1273   let path = matchstr(getline('.'),'^\^\=\zs\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
   1274   if path ==# ''
   1275     let path = s:buffer(b:fugitive_blamed_bufnr).path()
   1276   endif
   1277   let args = b:fugitive_blame_arguments
   1278   let offset = line('.') - line('w0')
   1279   let bufnr = bufnr('%')
   1280   let winnr = bufwinnr(b:fugitive_blamed_bufnr)
   1281   if winnr > 0
   1282     exe winnr.'wincmd w'
   1283   endif
   1284   execute s:Edit('edit',commit.a:suffix.':'.path)
   1285   if winnr > 0
   1286     exe bufnr.'bdelete'
   1287   endif
   1288   execute 'Gblame '.args
   1289   execute lnum
   1290   let delta = line('.') - line('w0') - offset
   1291   if delta > 0
   1292     execute 'norm! 'delta."\<C-E>"
   1293   elseif delta < 0
   1294     execute 'norm! '(-delta)."\<C-Y>"
   1295   endif
   1296   syncbind
   1297   return ''
   1298 endfunction
   1299 
   1300 function! s:BlameSyntax() abort
   1301   let b:current_syntax = 'fugitiveblame'
   1302   syn match FugitiveblameBoundary "^\^"
   1303   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
   1304   syn match FugitiveblameHash       "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
   1305   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
   1306   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
   1307   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
   1308   syn match FugitiveblameLineNumber         " \@<=\d\+)\@=" contained containedin=FugitiveblameAnnotation
   1309   syn match FugitiveblameOriginalFile       " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite
   1310   syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite
   1311   syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite
   1312   syn match FugitiveblameShort              "\d\+)" contained contains=FugitiveblameLineNumber
   1313   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
   1314   hi def link FugitiveblameBoundary           Keyword
   1315   hi def link FugitiveblameHash               Identifier
   1316   hi def link FugitiveblameUncommitted        Function
   1317   hi def link FugitiveblameTime               PreProc
   1318   hi def link FugitiveblameLineNumber         Number
   1319   hi def link FugitiveblameOriginalFile       String
   1320   hi def link FugitiveblameOriginalLineNumber Float
   1321   hi def link FugitiveblameShort              FugitiveblameDelimiter
   1322   hi def link FugitiveblameDelimiter          Delimiter
   1323   hi def link FugitiveblameNotCommittedYet    Comment
   1324 endfunction
   1325 
   1326 " }}}1
   1327 " File access {{{1
   1328 
   1329 function! s:ReplaceCmd(cmd,...) abort
   1330   let fn = bufname('')
   1331   let tmp = tempname()
   1332   let aw = &autowrite
   1333   let prefix = ''
   1334   try
   1335     if a:0 && a:1 != ''
   1336       if &shell =~# 'cmd'
   1337         let old_index = $GIT_INDEX_FILE
   1338         let $GIT_INDEX_FILE = a:1
   1339       elseif &shell =~# 'csh'
   1340         let prefix = 'setenv GIT_INDEX_FILE '.s:shellesc(a:1).'; '
   1341       else
   1342         let prefix = 'GIT_INDEX_FILE='.s:shellesc(a:1).' '
   1343       endif
   1344     endif
   1345     set noautowrite
   1346     silent exe '!'.escape(prefix.a:cmd,'%#').' > '.tmp
   1347   finally
   1348     let &autowrite = aw
   1349     if exists('old_index')
   1350       let $GIT_INDEX_FILE = old_index
   1351     endif
   1352   endtry
   1353   silent exe 'keepalt file '.tmp
   1354   silent edit!
   1355   silent exe 'keepalt file '.s:fnameescape(fn)
   1356   call delete(tmp)
   1357   silent exe 'doau BufReadPost '.s:fnameescape(fn)
   1358 endfunction
   1359 
   1360 function! s:BufReadIndex()
   1361   if !exists('b:fugitive_display_format')
   1362     let b:fugitive_display_format = filereadable(expand('%').'.lock')
   1363   endif
   1364   let b:fugitive_display_format = b:fugitive_display_format % 2
   1365   let b:fugitive_type = 'index'
   1366   try
   1367     let b:git_dir = s:repo().dir()
   1368     setlocal noro ma
   1369     if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
   1370       let index = ''
   1371     else
   1372       let index = expand('%:p')
   1373     endif
   1374     if b:fugitive_display_format
   1375       call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
   1376       set ft=git nospell
   1377     else
   1378       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
   1379       let dir = getcwd()
   1380       try
   1381         execute cd.'`=s:repo().tree()`'
   1382         call s:ReplaceCmd(s:repo().git_command('status'),index)
   1383       finally
   1384         execute cd.'`=dir`'
   1385       endtry
   1386       set ft=gitcommit
   1387     endif
   1388     setlocal ro noma nomod nomodeline bufhidden=delete
   1389     nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
   1390     nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
   1391     nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('')<CR>
   1392     nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('')<CR>
   1393     nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('!')<CR>
   1394     nnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
   1395     xnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line("'<"),line("'>"))<CR>
   1396     nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
   1397     xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
   1398     nnoremap <buffer> <silent> <C-N> :call search('^#\t.*','W')<Bar>.<CR>
   1399     nnoremap <buffer> <silent> <C-P> :call search('^#\t.*','Wbe')<Bar>.<CR>
   1400     call s:JumpInit()
   1401     nunmap   <buffer>          P
   1402     nunmap   <buffer>          ~
   1403     nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
   1404   catch /^fugitive:/
   1405     return 'echoerr v:errmsg'
   1406   endtry
   1407 endfunction
   1408 
   1409 function! s:FileRead()
   1410   try
   1411     let repo = s:repo(s:ExtractGitDir(expand('<amatch>')))
   1412     let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
   1413     let hash = repo.rev_parse(path)
   1414     if path =~ '^:'
   1415       let type = 'blob'
   1416     else
   1417       let type = repo.git_chomp('cat-file','-t',hash)
   1418     endif
   1419     " TODO: use count, if possible
   1420     return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
   1421   catch /^fugitive:/
   1422     return 'echoerr v:errmsg'
   1423   endtry
   1424 endfunction
   1425 
   1426 function! s:BufReadIndexFile()
   1427   try
   1428     let b:fugitive_type = 'blob'
   1429     let b:git_dir = s:repo().dir()
   1430     call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
   1431     return ''
   1432   catch /^fugitive: rev-parse/
   1433     silent exe 'doau BufNewFile '.s:fnameescape(bufname(''))
   1434     return ''
   1435   catch /^fugitive:/
   1436     return 'echoerr v:errmsg'
   1437   endtry
   1438 endfunction
   1439 
   1440 function! s:BufWriteIndexFile()
   1441   let tmp = tempname()
   1442   try
   1443     let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
   1444     let stage = matchstr(expand('<amatch>'),'//\zs\d')
   1445     silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
   1446     let sha1 = readfile(tmp)[0]
   1447     let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
   1448     if old_mode == ''
   1449       let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
   1450     endif
   1451     let info = old_mode.' '.sha1.' '.stage."\t".path
   1452     call writefile([info],tmp)
   1453     if has('win32')
   1454       let error = system('type '.tmp.'|'.s:repo().git_command('update-index','--index-info'))
   1455     else
   1456       let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
   1457     endif
   1458     if v:shell_error == 0
   1459       setlocal nomodified
   1460       silent execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
   1461       call fugitive#reload_status()
   1462       return ''
   1463     else
   1464       return 'echoerr '.string('fugitive: '.error)
   1465     endif
   1466   finally
   1467     call delete(tmp)
   1468   endtry
   1469 endfunction
   1470 
   1471 function! s:BufReadObject()
   1472   try
   1473     setlocal noro ma
   1474     let b:git_dir = s:repo().dir()
   1475     let hash = s:buffer().sha1()
   1476     if !exists("b:fugitive_type")
   1477       let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
   1478     endif
   1479     if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
   1480       return "echoerr 'fugitive: unrecognized git type'"
   1481     endif
   1482     let firstline = getline('.')
   1483     if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
   1484       let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
   1485     endif
   1486 
   1487     let pos = getpos('.')
   1488     silent %delete
   1489     setlocal endofline
   1490 
   1491     if b:fugitive_type == 'tree'
   1492       let b:fugitive_display_format = b:fugitive_display_format % 2
   1493       if b:fugitive_display_format
   1494         call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
   1495       else
   1496         call s:ReplaceCmd(s:repo().git_command('show',hash))
   1497       endif
   1498     elseif b:fugitive_type == 'tag'
   1499       let b:fugitive_display_format = b:fugitive_display_format % 2
   1500       if b:fugitive_display_format
   1501         call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
   1502       else
   1503         call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
   1504       endif
   1505     elseif b:fugitive_type == 'commit'
   1506       let b:fugitive_display_format = b:fugitive_display_format % 2
   1507       if b:fugitive_display_format
   1508         call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
   1509       else
   1510         call s:ReplaceCmd(s:repo().git_command('show','--pretty=format:tree %T%nparent %P%nauthor %an <%ae> %ad%ncommitter %cn <%ce> %cd%nencoding %e%n%n%s%n%n%b',hash))
   1511         call search('^parent ')
   1512         if getline('.') ==# 'parent '
   1513           silent delete_
   1514         else
   1515           silent s/\%(^parent\)\@<! /\rparent /ge
   1516         endif
   1517         if search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
   1518           silent delete_
   1519         end
   1520         1
   1521       endif
   1522     elseif b:fugitive_type ==# 'blob'
   1523       call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
   1524     endif
   1525     call setpos('.',pos)
   1526     setlocal ro noma nomod nomodeline
   1527     if b:fugitive_type !=# 'blob'
   1528       set filetype=git
   1529       nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
   1530       nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
   1531     else
   1532       call s:JumpInit()
   1533     endif
   1534 
   1535     return ''
   1536   catch /^fugitive:/
   1537     return 'echoerr v:errmsg'
   1538   endtry
   1539 endfunction
   1540 
   1541 augroup fugitive_files
   1542   autocmd!
   1543   autocmd BufReadCmd  *.git/index                      exe s:BufReadIndex()
   1544   autocmd BufReadCmd  *.git/*index*.lock               exe s:BufReadIndex()
   1545   autocmd FileReadCmd fugitive://**//[0-3]/**          exe s:FileRead()
   1546   autocmd BufReadCmd  fugitive://**//[0-3]/**          exe s:BufReadIndexFile()
   1547   autocmd BufWriteCmd fugitive://**//[0-3]/**          exe s:BufWriteIndexFile()
   1548   autocmd BufReadCmd  fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
   1549   autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
   1550   autocmd FileType git       call s:JumpInit()
   1551 augroup END
   1552 
   1553 " }}}1
   1554 " Go to file {{{1
   1555 
   1556 function! s:JumpInit() abort
   1557   nnoremap <buffer> <silent> <CR>    :<C-U>exe <SID>GF("edit")<CR>
   1558   if !&modifiable
   1559     nnoremap <buffer> <silent> o     :<C-U>exe <SID>GF("split")<CR>
   1560     nnoremap <buffer> <silent> O     :<C-U>exe <SID>GF("tabedit")<CR>
   1561     nnoremap <buffer> <silent> P     :<C-U>exe <SID>Edit('edit',<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
   1562     nnoremap <buffer> <silent> ~     :<C-U>exe <SID>Edit('edit',<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
   1563     nnoremap <buffer> <silent> C     :<C-U>exe <SID>Edit('edit',<SID>buffer().containing_commit())<CR>
   1564     nnoremap <buffer> <silent> cc    :<C-U>exe <SID>Edit('edit',<SID>buffer().containing_commit())<CR>
   1565     nnoremap <buffer> <silent> co    :<C-U>exe <SID>Edit('split',<SID>buffer().containing_commit())<CR>
   1566     nnoremap <buffer> <silent> cO    :<C-U>exe <SID>Edit('tabedit',<SID>buffer().containing_commit())<CR>
   1567     nnoremap <buffer> <silent> cp    :<C-U>exe <SID>Edit('pedit',<SID>buffer().containing_commit())<CR>
   1568   endif
   1569 endfunction
   1570 
   1571 function! s:GF(mode) abort
   1572   try
   1573     let buffer = s:buffer()
   1574     let myhash = buffer.sha1()
   1575 
   1576     if buffer.type('tree')
   1577       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
   1578       if showtree && line('.') == 1
   1579         return ""
   1580       elseif showtree && line('.') > 2
   1581         return s:Edit(a:mode,buffer.commit().':'.(buffer.path() == '' ? '' : buffer.path().'/').s:sub(getline('.'),'/$',''))
   1582       elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
   1583         return s:Edit(a:mode,buffer.commit().':'.(buffer.path() == '' ? '' : buffer.path().'/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
   1584       endif
   1585 
   1586     elseif buffer.type('blob')
   1587       let ref = expand("<cfile>")
   1588       try
   1589         let sha1 = buffer.repo().rev_parse(ref)
   1590       catch /^fugitive:/
   1591       endtry
   1592       if exists('sha1')
   1593         return s:Edit(a:mode,ref)
   1594       endif
   1595 
   1596     else
   1597 
   1598       " Index
   1599       if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
   1600         let ref = matchstr(getline('.'),'\x\{40\}')
   1601         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
   1602         return s:Edit(a:mode,file)
   1603 
   1604       elseif getline('.') =~# '^#\trenamed:.* -> '
   1605         let file = '/'.matchstr(getline('.'),' -> \zs.*')
   1606         return s:Edit(a:mode,file)
   1607       elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
   1608         let file = '/'.matchstr(getline('.'),': *\zs.*')
   1609         return s:Edit(a:mode,file)
   1610       elseif getline('.') =~# '^#\t.'
   1611         let file = '/'.matchstr(getline('.'),'#\t\zs.*')
   1612         return s:Edit(a:mode,file)
   1613       elseif getline('.') =~# ': needs merge$'
   1614         let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
   1615         return s:Edit(a:mode,file).'|Gdiff'
   1616 
   1617       elseif getline('.') ==# '# Not currently on any branch.'
   1618         return s:Edit(a:mode,'HEAD')
   1619       elseif getline('.') =~# '^# On branch '
   1620         let file = 'refs/heads/'.getline('.')[12:]
   1621         return s:Edit(a:mode,file)
   1622       elseif getline('.') =~# "^# Your branch .*'"
   1623         let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
   1624         return s:Edit(a:mode,file)
   1625       endif
   1626 
   1627       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
   1628 
   1629       if getline('.') =~# '^ref: '
   1630         let ref = strpart(getline('.'),5)
   1631 
   1632       elseif getline('.') =~# '^parent \x\{40\}\>'
   1633         let ref = matchstr(getline('.'),'\x\{40\}')
   1634         let line = line('.')
   1635         let parent = 0
   1636         while getline(line) =~# '^parent '
   1637           let parent += 1
   1638           let line -= 1
   1639         endwhile
   1640         return s:Edit(a:mode,ref)
   1641 
   1642       elseif getline('.') =~ '^tree \x\{40\}$'
   1643         let ref = matchstr(getline('.'),'\x\{40\}')
   1644         if s:repo().rev_parse(myhash.':') == ref
   1645           let ref = myhash.':'
   1646         endif
   1647         return s:Edit(a:mode,ref)
   1648 
   1649       elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
   1650         let ref = matchstr(getline('.'),'\x\{40\}')
   1651         let type = matchstr(getline(line('.')+1),'type \zs.*')
   1652 
   1653       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
   1654         return ''
   1655 
   1656       elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
   1657         let ref = matchstr(getline('.'),'\x\{40\}')
   1658         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
   1659 
   1660       elseif getline('.') =~# '^[+-]\{3\} [ab/]'
   1661         let ref = getline('.')[4:]
   1662 
   1663       elseif getline('.') =~# '^rename from '
   1664         let ref = 'a/'.getline('.')[12:]
   1665       elseif getline('.') =~# '^rename to '
   1666         let ref = 'b/'.getline('.')[10:]
   1667 
   1668       elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
   1669         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
   1670         let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
   1671         let dcmd = 'Gdiff'
   1672 
   1673       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
   1674         let line = getline(line('.')-1)
   1675         let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
   1676         let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
   1677         let dcmd = 'Gdiff!'
   1678 
   1679       elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
   1680         let ref = getline('.')
   1681       else
   1682         let ref = ''
   1683       endif
   1684 
   1685       if myhash ==# ''
   1686         let ref = s:sub(ref,'^a/','HEAD:')
   1687         let ref = s:sub(ref,'^b/',':0:')
   1688         if exists('dref')
   1689           let dref = s:sub(dref,'^a/','HEAD:')
   1690         endif
   1691       else
   1692         let ref = s:sub(ref,'^a/',myhash.'^:')
   1693         let ref = s:sub(ref,'^b/',myhash.':')
   1694         if exists('dref')
   1695           let dref = s:sub(dref,'^a/',myhash.'^:')
   1696         endif
   1697       endif
   1698 
   1699       if ref ==# '/dev/null'
   1700         " Empty blob
   1701         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
   1702       endif
   1703 
   1704       if exists('dref')
   1705         return s:Edit(a:mode,ref) . '|'.dcmd.' '.s:fnameescape(dref)
   1706       elseif ref != ""
   1707         return s:Edit(a:mode,ref)
   1708       endif
   1709 
   1710     endif
   1711     return ''
   1712   catch /^fugitive:/
   1713     return 'echoerr v:errmsg'
   1714   endtry
   1715 endfunction
   1716 
   1717 " }}}1
   1718 " Statusline {{{1
   1719 
   1720 function! s:repo_head_ref() dict abort
   1721   return readfile(s:repo().dir('HEAD'))[0]
   1722 endfunction
   1723 
   1724 call s:add_methods('repo',['head_ref'])
   1725 
   1726 function! fugitive#statusline(...)
   1727   if !exists('b:git_dir')
   1728     return ''
   1729   endif
   1730   let status = ''
   1731   if s:buffer().commit() != ''
   1732     let status .= ':' . s:buffer().commit()[0:7]
   1733   endif
   1734   let head = s:repo().head_ref()
   1735   if head =~# '^ref: '
   1736     let status .= s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','(').')'
   1737   elseif head =~# '^\x\{40\}$'
   1738     let status .= '('.head[0:7].')'
   1739   endif
   1740   if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
   1741     return ',GIT'.status
   1742   else
   1743     return '[Git'.status.']'
   1744   endif
   1745 endfunction
   1746 
   1747 function! s:repo_config(conf) dict abort
   1748   return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
   1749 endfun
   1750 
   1751 function! s:repo_user() dict abort
   1752   let username = s:repo().config('user.name')
   1753   let useremail = s:repo().config('user.email')
   1754   return username.' <'.useremail.'>'
   1755 endfun
   1756 
   1757 call s:add_methods('repo',['config', 'user'])
   1758 
   1759 " }}}1
   1760 
   1761 " vim:set ft=vim ts=8 sw=2 sts=2: