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