vim-fugitive
[my-vim-dotfolder.git] / bundle / vim-fugitive / plugin / fugitive.vim
blob41477f1b97e8947a635379abf54397b3c6769bda
1 " fugitive.vim - A Git wrapper so awesome, it should be illegal
2 " Maintainer:   Tim Pope <http://tpo.pe/>
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'
33     return '"'.s:gsub(s:gsub(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:recall()
68   let rev = s:sub(s:buffer().rev(), '^/', '')
69   if rev ==# ':'
70     return matchstr(getline('.'),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$\|^\d\{6} \x\{40\} \d\t\zs.*')
71   endif
72   return rev
73 endfunction
75 function! s:add_methods(namespace, method_names) abort
76   for name in a:method_names
77     let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
78   endfor
79 endfunction
81 let s:commands = []
82 function! s:command(definition) abort
83   let s:commands += [a:definition]
84 endfunction
86 function! s:define_commands()
87   for command in s:commands
88     exe 'command! -buffer '.command
89   endfor
90 endfunction
92 augroup fugitive_utility
93   autocmd!
94   autocmd User Fugitive call s:define_commands()
95 augroup END
97 let s:abstract_prototype = {}
99 " }}}1
100 " Initialization {{{1
102 function! fugitive#is_git_dir(path) abort
103   let path = s:sub(a:path, '[\/]$', '') . '/'
104   return isdirectory(path.'objects') && isdirectory(path.'refs') && getfsize(path.'HEAD') > 10
105 endfunction
107 function! fugitive#extract_git_dir(path) abort
108   if s:shellslash(a:path) =~# '^fugitive://.*//'
109     return matchstr(s:shellslash(a:path), '\C^fugitive://\zs.\{-\}\ze//')
110   endif
111   let root = s:shellslash(simplify(fnamemodify(a:path, ':p:s?[\/]$??')))
112   let previous = ""
113   while root !=# previous
114     let dir = s:sub(root, '[\/]$', '') . '/.git'
115     let type = getftype(dir)
116     if type ==# 'dir' && fugitive#is_git_dir(dir)
117       return dir
118     elseif type ==# 'link' && fugitive#is_git_dir(dir)
119       return resolve(dir)
120     elseif type !=# '' && filereadable(dir)
121       let line = get(readfile(dir, '', 1), 0, '')
122       if line =~# '^gitdir: \.' && fugitive#is_git_dir(root.'/'.line[8:-1])
123         return simplify(root.'/'.line[8:-1])
124       elseif line =~# '^gitdir: ' && fugitive#is_git_dir(line[8:-1])
125         return line[8:-1]
126       endif
127     elseif fugitive#is_git_dir(root)
128       return root
129     endif
130     let previous = root
131     let root = fnamemodify(root, ':h')
132   endwhile
133   return ''
134 endfunction
136 function! s:Detect(path)
137   if exists('b:git_dir') && (b:git_dir ==# '' || b:git_dir =~# '/$')
138     unlet b:git_dir
139   endif
140   if !exists('b:git_dir')
141     let dir = fugitive#extract_git_dir(a:path)
142     if dir !=# ''
143       let b:git_dir = dir
144     endif
145   endif
146   if exists('b:git_dir')
147     silent doautocmd User Fugitive
148     cnoremap <buffer> <expr> <C-R><C-G> <SID>recall()
149     nnoremap <buffer> <silent> y<C-G> :call setreg(v:register, <SID>recall())<CR>
150     let buffer = fugitive#buffer()
151     if expand('%:p') =~# '//'
152       call buffer.setvar('&path', s:sub(buffer.getvar('&path'), '^\.%(,|$)', ''))
153     endif
154     " Look for tags file in .git dir and add them to &tags
155     " See http://tbaggery.com/2011/08/08/effortless-ctags-with-git.html
156     let tagsfile = b:git_dir.'/tags'
157     if stridx(buffer.getvar('&tags'), escape(tagsfile, ', ')) == -1
158       if filereadable(tagsfile)
159         call buffer.setvar('&tags', escape(tagsfile, ', ').','.buffer.getvar('&tags'))
160       endif
161       if &filetype !=# ''
162         let tagsfile = b:git_dir.'/'.&filetype.'.tags'
163         if filereadable(tagsfile)
164           call buffer.setvar('&tags', escape(tagsfile, ', ').','.buffer.getvar('&tags'))
165         endif
166       endif
167     endif
168   endif
169 endfunction
171 augroup fugitive
172   autocmd!
173   autocmd BufNewFile,BufReadPost * call s:Detect(expand('<amatch>:p'))
174   autocmd FileType           netrw call s:Detect(expand('%:p'))
175   autocmd User NERDTreeInit,NERDTreeNewRoot call s:Detect(b:NERDTreeRoot.path.str())
176   autocmd VimEnter * if expand('<amatch>')==''|call s:Detect(getcwd())|endif
177   autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
178 augroup END
180 " }}}1
181 " Repository {{{1
183 let s:repo_prototype = {}
184 let s:repos = {}
186 function! s:repo(...) abort
187   let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : fugitive#extract_git_dir(expand('%:p')))
188   if dir !=# ''
189     if has_key(s:repos, dir)
190       let repo = get(s:repos, dir)
191     else
192       let repo = {'git_dir': dir}
193       let s:repos[dir] = repo
194     endif
195     return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
196   endif
197   call s:throw('not a git repository: '.expand('%:p'))
198 endfunction
200 function! fugitive#repo(...)
201   return call('s:repo', a:000)
202 endfunction
204 function! s:repo_dir(...) dict abort
205   return join([self.git_dir]+a:000,'/')
206 endfunction
208 function! s:repo_configured_tree() dict abort
209   if !has_key(self,'_tree')
210     let self._tree = ''
211     if filereadable(self.dir('config'))
212       let config = readfile(self.dir('config'),'',10)
213       call filter(config,'v:val =~# "^\\s*worktree *="')
214       if len(config) == 1
215         let self._tree = matchstr(config[0], '= *\zs.*')
216       endif
217     endif
218   endif
219   if self._tree =~# '^\.'
220     return simplify(self.dir(self._tree))
221   else
222     return self._tree
223   endif
224 endfunction
226 function! s:repo_tree(...) dict abort
227   if self.dir() =~# '/\.git$'
228     let dir = self.dir()[0:-6]
229   else
230     let dir = self.configured_tree()
231   endif
232   if dir ==# ''
233     call s:throw('no work tree')
234   else
235     return join([dir]+a:000,'/')
236   endif
237 endfunction
239 function! s:repo_bare() dict abort
240   if self.dir() =~# '/\.git$'
241     return 0
242   else
243     return self.configured_tree() ==# ''
244   endtry
245 endfunction
247 function! s:repo_translate(spec) dict abort
248   if a:spec ==# '.' || a:spec ==# '/.'
249     return self.bare() ? self.dir() : self.tree()
250   elseif a:spec =~# '^/\=\.git$' && self.bare()
251     return self.dir()
252   elseif a:spec =~# '^/\=\.git/'
253     return self.dir(s:sub(a:spec, '^/=\.git/', ''))
254   elseif a:spec =~# '^/'
255     return self.tree().a:spec
256   elseif a:spec =~# '^:[0-3]:'
257     return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
258   elseif a:spec ==# ':'
259     if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(self.dir())] ==# self.dir('') && filereadable($GIT_INDEX_FILE)
260       return fnamemodify($GIT_INDEX_FILE,':p')
261     else
262       return self.dir('index')
263     endif
264   elseif a:spec =~# '^:/'
265     let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
266     return 'fugitive://'.self.dir().'//'.ref
267   elseif a:spec =~# '^:'
268     return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
269   elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec))
270     return self.dir(a:spec)
271   elseif filereadable(self.dir('refs/'.a:spec))
272     return self.dir('refs/'.a:spec)
273   elseif filereadable(self.dir('refs/tags/'.a:spec))
274     return self.dir('refs/tags/'.a:spec)
275   elseif filereadable(self.dir('refs/heads/'.a:spec))
276     return self.dir('refs/heads/'.a:spec)
277   elseif filereadable(self.dir('refs/remotes/'.a:spec))
278     return self.dir('refs/remotes/'.a:spec)
279   elseif filereadable(self.dir('refs/remotes/'.a:spec.'/HEAD'))
280     return self.dir('refs/remotes/'.a:spec,'/HEAD')
281   else
282     try
283       let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
284       let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
285       return 'fugitive://'.self.dir().'//'.ref.path
286     catch /^fugitive:/
287       return self.tree(a:spec)
288     endtry
289   endif
290 endfunction
292 function! s:repo_head(...) dict abort
293     let head = s:repo().head_ref()
295     if head =~# '^ref: '
296       let branch = s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','')
297     elseif head =~# '^\x\{40\}$'
298       " truncate hash to a:1 characters if we're in detached head mode
299       let len = a:0 ? a:1 : 0
300       let branch = len ? head[0:len-1] : ''
301     endif
303     return branch
304 endfunction
306 call s:add_methods('repo',['dir','configured_tree','tree','bare','translate','head'])
308 function! s:repo_git_command(...) dict abort
309   let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
310   return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
311 endfunction
313 function! s:repo_git_chomp(...) dict abort
314   return s:sub(system(call(self.git_command,a:000,self)),'\n$','')
315 endfunction
317 function! s:repo_git_chomp_in_tree(...) dict abort
318   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
319   let dir = getcwd()
320   try
321     execute cd.'`=s:repo().tree()`'
322     return call(s:repo().git_chomp, a:000, s:repo())
323   finally
324     execute cd.'`=dir`'
325   endtry
326 endfunction
328 function! s:repo_rev_parse(rev) dict abort
329   let hash = self.git_chomp('rev-parse','--verify',a:rev)
330   if hash =~ '\<\x\{40\}$'
331     return matchstr(hash,'\<\x\{40\}$')
332   endif
333   call s:throw('rev-parse '.a:rev.': '.hash)
334 endfunction
336 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
338 function! s:repo_dirglob(base) dict abort
339   let base = s:sub(a:base,'^/','')
340   let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
341   call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
342   return matches
343 endfunction
345 function! s:repo_superglob(base) dict abort
346   if a:base =~# '^/' || a:base !~# ':'
347     let results = []
348     if a:base !~# '^/'
349       let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
350       let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
351       call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
352       let results += heads
353     endif
354     if !self.bare()
355       let base = s:sub(a:base,'^/','')
356       let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
357       call map(matches,'s:shellslash(v:val)')
358       call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
359       call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
360       let results += matches
361     endif
362     return results
364   elseif a:base =~# '^:'
365     let entries = split(self.git_chomp('ls-files','--stage'),"\n")
366     call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
367     if a:base !~# '^:[0-3]\%(:\|$\)'
368       call filter(entries,'v:val[1] == "0"')
369       call map(entries,'v:val[2:-1]')
370     endif
371     call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
372     return entries
374   else
375     let tree = matchstr(a:base,'.*[:/]')
376     let entries = split(self.git_chomp('ls-tree',tree),"\n")
377     call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
378     call map(entries,'tree.s:sub(v:val,".*\t","")')
379     return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
380   endif
381 endfunction
383 call s:add_methods('repo',['dirglob','superglob'])
385 function! s:repo_config(conf) dict abort
386   return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
387 endfun
389 function! s:repo_user() dict abort
390   let username = s:repo().config('user.name')
391   let useremail = s:repo().config('user.email')
392   return username.' <'.useremail.'>'
393 endfun
395 function! s:repo_aliases() dict abort
396   if !has_key(self,'_aliases')
397     let self._aliases = {}
398     for line in split(self.git_chomp('config','--get-regexp','^alias[.]'),"\n")
399       let self._aliases[matchstr(line,'\.\zs\S\+')] = matchstr(line,' \zs.*')
400     endfor
401   endif
402   return self._aliases
403 endfunction
405 call s:add_methods('repo',['config', 'user', 'aliases'])
407 function! s:repo_keywordprg() dict abort
408   let args = ' --git-dir='.escape(self.dir(),"\\\"' ")
409   if has('gui_running') && !has('win32')
410     return g:fugitive_git_executable . ' --no-pager' . args . ' log -1'
411   else
412     return g:fugitive_git_executable . args . ' show'
413   endif
414 endfunction
416 call s:add_methods('repo',['keywordprg'])
418 " }}}1
419 " Buffer {{{1
421 let s:buffer_prototype = {}
423 function! s:buffer(...) abort
424   let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
425   call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
426   if buffer.getvar('git_dir') !=# ''
427     return buffer
428   endif
429   call s:throw('not a git repository: '.expand('%:p'))
430 endfunction
432 function! fugitive#buffer(...) abort
433   return s:buffer(a:0 ? a:1 : '%')
434 endfunction
436 function! s:buffer_getvar(var) dict abort
437   return getbufvar(self['#'],a:var)
438 endfunction
440 function! s:buffer_setvar(var,value) dict abort
441   return setbufvar(self['#'],a:var,a:value)
442 endfunction
444 function! s:buffer_getline(lnum) dict abort
445   return getbufline(self['#'],a:lnum)[0]
446 endfunction
448 function! s:buffer_repo() dict abort
449   return s:repo(self.getvar('git_dir'))
450 endfunction
452 function! s:buffer_type(...) dict abort
453   if self.getvar('fugitive_type') != ''
454     let type = self.getvar('fugitive_type')
455   elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
456     let type = 'head'
457   elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
458     let type = 'tree'
459   elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
460     let type = 'tree'
461   elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
462     let type = 'index'
463   elseif isdirectory(self.spec())
464     let type = 'directory'
465   elseif self.spec() == ''
466     let type = 'null'
467   else
468     let type = 'file'
469   endif
470   if a:0
471     return !empty(filter(copy(a:000),'v:val ==# type'))
472   else
473     return type
474   endif
475 endfunction
477 if has('win32')
479   function! s:buffer_spec() dict abort
480     let bufname = bufname(self['#'])
481     let retval = ''
482     for i in split(bufname,'[^:]\zs\\')
483       let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
484     endfor
485     return s:shellslash(fnamemodify(retval,':p'))
486   endfunction
488 else
490   function! s:buffer_spec() dict abort
491     let bufname = bufname(self['#'])
492     return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
493   endfunction
495 endif
497 function! s:buffer_name() dict abort
498   return self.spec()
499 endfunction
501 function! s:buffer_commit() dict abort
502   return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
503 endfunction
505 function! s:buffer_path(...) dict abort
506   let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
507   if rev != ''
508     let rev = s:sub(rev,'\w*','')
509   elseif self.repo().bare()
510     let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
511   else
512     let rev = self.spec()[strlen(self.repo().tree()) : -1]
513   endif
514   return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
515 endfunction
517 function! s:buffer_rev() dict abort
518   let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
519   if rev =~ '^\x/'
520     return ':'.rev[0].':'.rev[2:-1]
521   elseif rev =~ '.'
522     return s:sub(rev,'/',':')
523   elseif self.spec() =~ '\.git/index$'
524     return ':'
525   elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
526     return self.spec()[strlen(self.repo().dir())+1 : -1]
527   else
528     return self.path('/')
529   endif
530 endfunction
532 function! s:buffer_sha1() dict abort
533   if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
534     return self.repo().rev_parse(self.rev())
535   else
536     return ''
537   endif
538 endfunction
540 function! s:buffer_expand(rev) dict abort
541   if a:rev =~# '^:[0-3]$'
542     let file = a:rev.self.path(':')
543   elseif a:rev =~# '^[-:]/$'
544     let file = '/'.self.path()
545   elseif a:rev =~# '^-'
546     let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
547   elseif a:rev =~# '^@{'
548     let file = 'HEAD'.a:rev.self.path(':')
549   elseif a:rev =~# '^[~^]'
550     let commit = s:sub(self.commit(),'^\d=$','HEAD')
551     let file = commit.a:rev.self.path(':')
552   else
553     let file = a:rev
554   endif
555   return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','')
556 endfunction
558 function! s:buffer_containing_commit() dict abort
559   if self.commit() =~# '^\d$'
560     return ':'
561   elseif self.commit() =~# '.'
562     return self.commit()
563   else
564     return 'HEAD'
565   endif
566 endfunction
568 function! s:buffer_up(...) dict abort
569   let rev = self.rev()
570   let c = a:0 ? a:1 : 1
571   while c
572     if rev =~# '^[/:]$'
573       let rev = 'HEAD'
574     elseif rev =~# '^:'
575       let rev = ':'
576     elseif rev =~# '^refs/[^^~:]*$\|^[^^~:]*HEAD$'
577       let rev .= '^{}'
578     elseif rev =~# '^/\|:.*/'
579       let rev = s:sub(rev, '.*\zs/.*', '')
580     elseif rev =~# ':.'
581       let rev = matchstr(rev, '^[^:]*:')
582     elseif rev =~# ':$'
583       let rev = rev[0:-2]
584     else
585       return rev.'~'.c
586     endif
587     let c -= 1
588   endwhile
589   return rev
590 endfunction
592 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit','up'])
594 " }}}1
595 " Git {{{1
597 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
599 function! s:ExecuteInTree(cmd) abort
600   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
601   let dir = getcwd()
602   try
603     execute cd.'`=s:repo().tree()`'
604     execute a:cmd
605   finally
606     execute cd.'`=dir`'
607   endtry
608 endfunction
610 function! s:Git(bang,cmd) abort
611   if a:bang
612     return s:Edit('edit',1,a:cmd)
613   endif
614   let git = s:repo().git_command()
615   if has('gui_running') && !has('win32')
616     let git .= ' --no-pager'
617   endif
618   let cmd = matchstr(a:cmd,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
619   call s:ExecuteInTree('!'.git.' '.cmd)
620   call fugitive#reload_status()
621   return matchstr(a:cmd,'\v\C\\@<!%(\\\\)*\|\zs.*')
622 endfunction
624 function! s:GitComplete(A,L,P) abort
625   if !exists('s:exec_path')
626     let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
627   endif
628   let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
629   if a:L =~ ' [[:alnum:]-]\+ '
630     return s:repo().superglob(a:A)
631   elseif a:A == ''
632     return sort(cmds+keys(s:repo().aliases()))
633   else
634     return filter(sort(cmds+keys(s:repo().aliases())),'v:val[0:strlen(a:A)-1] ==# a:A')
635   endif
636 endfunction
638 " }}}1
639 " Gcd, Glcd {{{1
641 function! s:DirComplete(A,L,P) abort
642   let matches = s:repo().dirglob(a:A)
643   return matches
644 endfunction
646 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>)`")
647 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>)`")
649 " }}}1
650 " Gstatus {{{1
652 call s:command("-bar Gstatus :execute s:Status()")
654 function! s:Status() abort
655   try
656     Gpedit :
657     wincmd P
658     nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
659   catch /^fugitive:/
660     return 'echoerr v:errmsg'
661   endtry
662   return ''
663 endfunction
665 function! fugitive#reload_status() abort
666   let mytab = tabpagenr()
667   for tab in [mytab] + range(1,tabpagenr('$'))
668     for winnr in range(1,tabpagewinnr(tab,'$'))
669       if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
670         execute 'tabnext '.tab
671         if winnr != winnr()
672           execute winnr.'wincmd w'
673           let restorewinnr = 1
674         endif
675         try
676           if !&modified
677             call s:BufReadIndex()
678           endif
679         finally
680           if exists('restorewinnr')
681             wincmd p
682           endif
683           execute 'tabnext '.mytab
684         endtry
685       endif
686     endfor
687   endfor
688 endfunction
690 function! s:stage_info(lnum) abort
691   let filename = matchstr(getline(a:lnum),'^#\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
692   let lnum = a:lnum
693   while lnum && getline(lnum) !~# '^#.*:$'
694     let lnum -= 1
695   endwhile
696   if !lnum
697     return ['', '']
698   elseif getline(lnum+1) =~# '^# .*"git \%(reset\|rm --cached\) ' || getline(lnum) ==# '# Changes to be committed:'
699     return [matchstr(filename, ': *\zs.*'), 'staged']
700   elseif getline(lnum+2) =~# '^# .*"git checkout ' || getline(lnum) ==# '# Changes not staged for commit:'
701     return [matchstr(filename, ': *\zs.*'), 'unstaged']
702   elseif getline(lnum+1) =~# '^# .*"git add/rm ' || getline(lnum) ==# '# Unmerged paths:'
703     return [matchstr(filename, ': *\zs.*'), 'unmerged']
704   else
705     return [filename, 'untracked']
706   endif
707 endfunction
709 function! s:StageNext(count)
710   for i in range(a:count)
711     call search('^#\t.*','W')
712   endfor
713   return '.'
714 endfunction
716 function! s:StagePrevious(count)
717   if line('.') == 1 && exists(':CtrlP')
718     return 'CtrlP '.fnameescape(s:repo().tree())
719   else
720     for i in range(a:count)
721       call search('^#\t.*','Wbe')
722     endfor
723     return '.'
724   endif
725 endfunction
727 function! s:StageReloadSeek(target,lnum1,lnum2)
728   let jump = a:target
729   let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
730   if f !=# '' | let jump = f | endif
731   let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
732   if f !=# '' | let jump = f | endif
733   silent! edit!
734   1
735   redraw
736   call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
737 endfunction
739 function! s:StageDiff(diff) abort
740   let [filename, section] = s:stage_info(line('.'))
741   if filename ==# '' && section ==# 'staged'
742     return 'Git! diff --cached'
743   elseif filename ==# ''
744     return 'Git! diff'
745   elseif filename =~# ' -> '
746     let [old, new] = split(filename,' -> ')
747     execute 'Gedit '.s:fnameescape(':0:'.new)
748     return a:diff.' HEAD:'.s:fnameescape(old)
749   elseif section ==# 'staged'
750     execute 'Gedit '.s:fnameescape(':0:'.filename)
751     return a:diff.' -'
752   else
753     execute 'Gedit '.s:fnameescape('/'.filename)
754     return a:diff
755   endif
756 endfunction
758 function! s:StageDiffEdit() abort
759   let [filename, section] = s:stage_info(line('.'))
760   let arg = (filename ==# '' ? '.' : filename)
761   if section ==# 'staged'
762     return 'Git! diff --cached '.s:shellesc(arg)
763   elseif section ==# 'untracked'
764     let repo = s:repo()
765     call repo.git_chomp_in_tree('add','--intent-to-add',arg)
766     if arg ==# '.'
767       silent! edit!
768       1
769       if !search('^# .*:\n#.*\n# .*"git checkout \|^# Changes not staged for commit:$','W')
770         call search('^# .*:$','W')
771       endif
772     else
773       call s:StageReloadSeek(arg,line('.'),line('.'))
774     endif
775     return ''
776   else
777     return 'Git! diff '.s:shellesc(arg)
778   endif
779 endfunction
781 function! s:StageToggle(lnum1,lnum2) abort
782   try
783     let output = ''
784     for lnum in range(a:lnum1,a:lnum2)
785       let [filename, section] = s:stage_info(lnum)
786       let repo = s:repo()
787       if getline('.') =~# '^# .*:$'
788         if section ==# 'staged'
789           call repo.git_chomp_in_tree('reset','-q')
790           silent! edit!
791           1
792           if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
793             call search('^# .*:$','W')
794           endif
795           return ''
796         elseif section ==# 'unstaged'
797           call repo.git_chomp_in_tree('add','-u')
798           silent! edit!
799           1
800           if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
801             call search('^# .*:$','W')
802           endif
803           return ''
804         else
805           call repo.git_chomp_in_tree('add','.')
806           silent! edit!
807           1
808           call search('^# .*:$','W')
809           return ''
810         endif
811       endif
812       if filename ==# ''
813         continue
814       endif
815       if !exists('first_filename')
816         let first_filename = filename
817       endif
818       execute lnum
819       if filename =~ ' -> '
820         let cmd = ['mv','--'] + reverse(split(filename,' -> '))
821         let filename = cmd[-1]
822       elseif section ==# 'staged'
823         let cmd = ['reset','-q','--',filename]
824       elseif getline(lnum) =~# '^#\tdeleted:'
825         let cmd = ['rm','--',filename]
826       elseif getline(lnum) =~# '^#\tmodified:'
827         let cmd = ['add','--',filename]
828       else
829         let cmd = ['add','-A','--',filename]
830       endif
831       let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
832     endfor
833     if exists('first_filename')
834       call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
835     endif
836     echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
837   catch /^fugitive:/
838     return 'echoerr v:errmsg'
839   endtry
840   return 'checktime'
841 endfunction
843 function! s:StagePatch(lnum1,lnum2) abort
844   let add = []
845   let reset = []
847   for lnum in range(a:lnum1,a:lnum2)
848     let [filename, section] = s:stage_info(lnum)
849     if getline('.') =~# '^# .*:$' && section ==# 'staged'
850       return 'Git reset --patch'
851     elseif getline('.') =~# '^# .*:$' && section ==# 'unstaged'
852       return 'Git add --patch'
853     elseif getline('.') =~# '^# .*:$' && section ==# 'untracked'
854       return 'Git add -N .'
855     elseif filename ==# ''
856       continue
857     endif
858     if !exists('first_filename')
859       let first_filename = filename
860     endif
861     execute lnum
862     if filename =~ ' -> '
863       let reset += [split(filename,' -> ')[1]]
864     elseif section ==# 'staged'
865       let reset += [filename]
866     elseif getline(lnum) !~# '^#\tdeleted:'
867       let add += [filename]
868     endif
869   endfor
870   try
871     if !empty(add)
872       execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
873     endif
874     if !empty(reset)
875       execute "Git reset --patch -- ".join(map(add,'s:shellesc(v:val)'))
876     endif
877     if exists('first_filename')
878       silent! edit!
879       1
880       redraw
881       call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
882     endif
883   catch /^fugitive:/
884     return 'echoerr v:errmsg'
885   endtry
886   return 'checktime'
887 endfunction
889 " }}}1
890 " Gcommit {{{1
892 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
894 function! s:Commit(args) abort
895   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
896   let dir = getcwd()
897   let msgfile = s:repo().dir('COMMIT_EDITMSG')
898   let outfile = tempname()
899   let errorfile = tempname()
900   try
901     try
902       execute cd.s:fnameescape(s:repo().tree())
903       if &shell =~# 'cmd'
904         let command = ''
905         let old_editor = $GIT_EDITOR
906         let $GIT_EDITOR = 'false'
907       else
908         let command = 'env GIT_EDITOR=false '
909       endif
910       let command .= s:repo().git_command('commit').' '.a:args
911       if &shell =~# 'csh'
912         silent execute '!('.command.' > '.outfile.') >& '.errorfile
913       elseif a:args =~# '\%(^\| \)--interactive\>'
914         execute '!'.command.' 2> '.errorfile
915       else
916         silent execute '!'.command.' > '.outfile.' 2> '.errorfile
917       endif
918     finally
919       execute cd.'`=dir`'
920     endtry
921     if !has('gui_running')
922       redraw!
923     endif
924     if !v:shell_error
925       if filereadable(outfile)
926         for line in readfile(outfile)
927           echo line
928         endfor
929       endif
930       return ''
931     else
932       let errors = readfile(errorfile)
933       let error = get(errors,-2,get(errors,-1,'!'))
934       if error =~# '\<false''\=\.$'
935         let args = a:args
936         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[se]|--edit|--interactive)%($| )','')
937         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
938         let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
939         let args = '-F '.s:shellesc(msgfile).' '.args
940         if args !~# '\%(^\| \)--cleanup\>'
941           let args = '--cleanup=strip '.args
942         endif
943         if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
944           execute 'keepalt edit '.s:fnameescape(msgfile)
945         elseif s:buffer().type() ==# 'index'
946           execute 'keepalt edit '.s:fnameescape(msgfile)
947           execute (search('^#','n')+1).'wincmd+'
948           setlocal nopreviewwindow
949         else
950           execute 'keepalt split '.s:fnameescape(msgfile)
951         endif
952         let b:fugitive_commit_arguments = args
953         setlocal bufhidden=wipe filetype=gitcommit
954         return '1'
955       elseif error ==# '!'
956         return s:Status()
957       else
958         call s:throw(error)
959       endif
960     endif
961   catch /^fugitive:/
962     return 'echoerr v:errmsg'
963   finally
964     if exists('old_editor')
965       let $GIT_EDITOR = old_editor
966     endif
967     call delete(outfile)
968     call delete(errorfile)
969     call fugitive#reload_status()
970   endtry
971 endfunction
973 function! s:CommitComplete(A,L,P) abort
974   if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
975     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']
976     return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
977   else
978     return s:repo().superglob(a:A)
979   endif
980 endfunction
982 function! s:FinishCommit()
983   let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
984   if !empty(args)
985     call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
986     return s:Commit(args)
987   endif
988   return ''
989 endfunction
991 augroup fugitive_commit
992   autocmd!
993   autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
994 augroup END
996 " }}}1
997 " Ggrep, Glog {{{1
999 if !exists('g:fugitive_summary_format')
1000   let g:fugitive_summary_format = '%s'
1001 endif
1003 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1004 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1005 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Glog :execute s:Log('grep<bang>',<f-args>)")
1006 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gllog :execute s:Log('lgrep<bang>',<f-args>)")
1008 function! s:Grep(cmd,bang,arg) abort
1009   let grepprg = &grepprg
1010   let grepformat = &grepformat
1011   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1012   let dir = getcwd()
1013   try
1014     execute cd.'`=s:repo().tree()`'
1015     let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n')
1016     let &grepformat = '%f:%l:%m'
1017     exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
1018     let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
1019     for entry in list
1020       if bufname(entry.bufnr) =~ ':'
1021         let entry.filename = s:repo().translate(bufname(entry.bufnr))
1022         unlet! entry.bufnr
1023       elseif a:arg =~# '\%(^\| \)--cached\>'
1024         let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
1025         unlet! entry.bufnr
1026       endif
1027     endfor
1028     if a:cmd =~# '^l'
1029       call setloclist(0, list, 'r')
1030     else
1031       call setqflist(list, 'r')
1032     endif
1033     if !a:bang && !empty(list)
1034       return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
1035     else
1036       return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
1037     endif
1038   finally
1039     let &grepprg = grepprg
1040     let &grepformat = grepformat
1041     execute cd.'`=dir`'
1042   endtry
1043 endfunction
1045 function! s:Log(cmd,...)
1046   let path = s:buffer().path('/')
1047   if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
1048     let path = ''
1049   endif
1050   let cmd = ['--no-pager', 'log', '--no-color']
1051   let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format]
1052   if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
1053     if s:buffer().commit() =~# '\x\{40\}'
1054       let cmd += [s:buffer().commit()]
1055     elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
1056       let cmd += [s:buffer().path()[5:-1]]
1057     endif
1058   end
1059   let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
1060   if path =~# '/.'
1061     let cmd += ['--',path[1:-1]]
1062   endif
1063   let grepformat = &grepformat
1064   let grepprg = &grepprg
1065   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1066   let dir = getcwd()
1067   try
1068     execute cd.'`=s:repo().tree()`'
1069     let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
1070     let &grepformat = '%f::%m'
1071     exe a:cmd
1072   finally
1073     let &grepformat = grepformat
1074     let &grepprg = grepprg
1075     execute cd.'`=dir`'
1076   endtry
1077 endfunction
1079 " }}}1
1080 " Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread {{{1
1082 function! s:Edit(cmd,bang,...) abort
1083   let buffer = s:buffer()
1084   if a:cmd !~# 'read'
1085     if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
1086       wincmd p
1087       if &diff
1088         let mywinnr = winnr()
1089         for winnr in range(winnr('$'),1,-1)
1090           if winnr != mywinnr && getwinvar(winnr,'&diff')
1091             execute winnr.'wincmd w'
1092             close
1093             wincmd p
1094           endif
1095         endfor
1096       endif
1097     endif
1098   endif
1100   if a:bang
1101     let args = s:gsub(a:0 ? a:1 : '', '\\@<!%(\\\\)*\zs[%#]', '\=s:buffer().expand(submatch(0))')
1102     if a:cmd =~# 'read'
1103       let git = buffer.repo().git_command()
1104       let last = line('$')
1105       silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
1106       if a:cmd ==# 'read'
1107         silent execute '1,'.last.'delete_'
1108       endif
1109       call fugitive#reload_status()
1110       diffupdate
1111       return 'redraw|echo '.string(':!'.git.' '.args)
1112     else
1113       let temp = resolve(tempname())
1114       let s:temp_files[temp] = buffer.repo().dir()
1115       silent execute a:cmd.' '.temp
1116       if a:cmd =~# 'pedit'
1117         wincmd P
1118       endif
1119       let echo = s:Edit('read',1,args)
1120       silent write!
1121       setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
1122       if getline(1) !~# '^diff '
1123         setlocal readonly nomodifiable
1124       endif
1125       if a:cmd =~# 'pedit'
1126         wincmd p
1127       endif
1128       return echo
1129     endif
1130     return ''
1131   endif
1133   if a:0 && a:1 == ''
1134     return ''
1135   elseif a:0
1136     let file = buffer.expand(a:1)
1137   elseif expand('%') ==# ''
1138     let file = ':'
1139   elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
1140     let file = buffer.path(':')
1141   else
1142     let file = buffer.path('/')
1143   endif
1144   try
1145     let file = buffer.repo().translate(file)
1146   catch /^fugitive:/
1147     return 'echoerr v:errmsg'
1148   endtry
1149   if a:cmd ==# 'read'
1150     return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
1151   else
1152     return a:cmd.' '.s:fnameescape(file)
1153   endif
1154 endfunction
1156 function! s:EditComplete(A,L,P) abort
1157   return s:repo().superglob(a:A)
1158 endfunction
1160 function! s:EditRunComplete(A,L,P) abort
1161   if a:L =~# '^\w\+!'
1162     return s:GitComplete(a:A,a:L,a:P)
1163   else
1164     return s:repo().superglob(a:A)
1165   endif
1166 endfunction
1168 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Ge       :execute s:Edit('edit<bang>',0,<f-args>)")
1169 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gedit    :execute s:Edit('edit<bang>',0,<f-args>)")
1170 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gpedit   :execute s:Edit('pedit',<bang>0,<f-args>)")
1171 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gsplit   :execute s:Edit('split',<bang>0,<f-args>)")
1172 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gvsplit  :execute s:Edit('vsplit',<bang>0,<f-args>)")
1173 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
1174 call s:command("-bar -bang -nargs=? -count -complete=customlist,s:EditRunComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read',<bang>0,<f-args>)")
1176 " }}}1
1177 " Gwrite, Gwq {{{1
1179 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
1180 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
1181 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
1183 function! s:Write(force,...) abort
1184   if exists('b:fugitive_commit_arguments')
1185     return 'write|bdelete'
1186   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
1187     return 'wq'
1188   elseif s:buffer().type() == 'index'
1189     return 'Gcommit'
1190   elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
1191     let filename = getline(4)[6:-1]
1192     setlocal buftype=
1193     silent write
1194     setlocal buftype=nowrite
1195     if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
1196       let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
1197     else
1198       let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
1199     endif
1200     if err !=# ''
1201       let v:errmsg = split(err,"\n")[0]
1202       return 'echoerr v:errmsg'
1203     elseif a:force
1204       return 'bdelete'
1205     else
1206       return 'Gedit '.fnameescape(filename)
1207     endif
1208   endif
1209   let mytab = tabpagenr()
1210   let mybufnr = bufnr('')
1211   let path = a:0 ? a:1 : s:buffer().path()
1212   if path =~# '^:\d\>'
1213     return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
1214   endif
1215   let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
1216   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) !=# ''
1217     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
1218     return 'echoerr v:errmsg'
1219   endif
1220   let file = s:repo().translate(path)
1221   let treebufnr = 0
1222   for nr in range(1,bufnr('$'))
1223     if fnamemodify(bufname(nr),':p') ==# file
1224       let treebufnr = nr
1225     endif
1226   endfor
1228   if treebufnr > 0 && treebufnr != bufnr('')
1229     let temp = tempname()
1230     silent execute '%write '.temp
1231     for tab in [mytab] + range(1,tabpagenr('$'))
1232       for winnr in range(1,tabpagewinnr(tab,'$'))
1233         if tabpagebuflist(tab)[winnr-1] == treebufnr
1234           execute 'tabnext '.tab
1235           if winnr != winnr()
1236             execute winnr.'wincmd w'
1237             let restorewinnr = 1
1238           endif
1239           try
1240             let lnum = line('.')
1241             let last = line('$')
1242             silent execute '$read '.temp
1243             silent execute '1,'.last.'delete_'
1244             silent write!
1245             silent execute lnum
1246             let did = 1
1247           finally
1248             if exists('restorewinnr')
1249               wincmd p
1250             endif
1251             execute 'tabnext '.mytab
1252           endtry
1253         endif
1254       endfor
1255     endfor
1256     if !exists('did')
1257       call writefile(readfile(temp,'b'),file,'b')
1258     endif
1259   else
1260     execute 'write! '.s:fnameescape(s:repo().translate(path))
1261   endif
1263   if a:force
1264     let error = s:repo().git_chomp_in_tree('add', '--force', file)
1265   else
1266     let error = s:repo().git_chomp_in_tree('add', file)
1267   endif
1268   if v:shell_error
1269     let v:errmsg = 'fugitive: '.error
1270     return 'echoerr v:errmsg'
1271   endif
1272   if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1273     set nomodified
1274   endif
1276   let one = s:repo().translate(':1:'.path)
1277   let two = s:repo().translate(':2:'.path)
1278   let three = s:repo().translate(':3:'.path)
1279   for nr in range(1,bufnr('$'))
1280     let name = fnamemodify(bufname(nr), ':p')
1281     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
1282       execute nr.'bdelete'
1283     endif
1284   endfor
1286   unlet! restorewinnr
1287   let zero = s:repo().translate(':0:'.path)
1288   for tab in range(1,tabpagenr('$'))
1289     for winnr in range(1,tabpagewinnr(tab,'$'))
1290       let bufnr = tabpagebuflist(tab)[winnr-1]
1291       let bufname = fnamemodify(bufname(bufnr), ':p')
1292       if bufname ==# zero && bufnr != mybufnr
1293         execute 'tabnext '.tab
1294         if winnr != winnr()
1295           execute winnr.'wincmd w'
1296           let restorewinnr = 1
1297         endif
1298         try
1299           let lnum = line('.')
1300           let last = line('$')
1301           silent execute '$read '.s:fnameescape(file)
1302           silent execute '1,'.last.'delete_'
1303           silent execute lnum
1304           set nomodified
1305           diffupdate
1306         finally
1307           if exists('restorewinnr')
1308             wincmd p
1309           endif
1310           execute 'tabnext '.mytab
1311         endtry
1312         break
1313       endif
1314     endfor
1315   endfor
1316   call fugitive#reload_status()
1317   return 'checktime'
1318 endfunction
1320 function! s:Wq(force,...) abort
1321   let bang = a:force ? '!' : ''
1322   if exists('b:fugitive_commit_arguments')
1323     return 'wq'.bang
1324   endif
1325   let result = call(s:function('s:Write'),[a:force]+a:000)
1326   if result =~# '^\%(write\|wq\|echoerr\)'
1327     return s:sub(result,'^write','wq')
1328   else
1329     return result.'|quit'.bang
1330   endif
1331 endfunction
1333 " }}}1
1334 " Gdiff {{{1
1336 call s:command("-bang -bar -nargs=? -complete=customlist,s:EditComplete Gdiff :execute s:Diff(<bang>0,<f-args>)")
1337 call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gvdiff :execute s:Diff(0,<f-args>)")
1338 call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gsdiff :execute s:Diff(1,<f-args>)")
1340 augroup fugitive_diff
1341   autocmd!
1342   autocmd BufWinLeave * if s:diff_window_count() == 2 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) | endif
1343   autocmd BufWinEnter * if s:diff_window_count() == 1 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | call s:diffoff() | endif
1344 augroup END
1346 function! s:diff_window_count()
1347   let c = 0
1348   for nr in range(1,winnr('$'))
1349     let c += getwinvar(nr,'&diff')
1350   endfor
1351   return c
1352 endfunction
1354 function! s:diffthis()
1355   if !&diff
1356     let w:fugitive_diff_restore = 'setlocal nodiff noscrollbind'
1357     let w:fugitive_diff_restore .= ' scrollopt=' . &l:scrollopt
1358     let w:fugitive_diff_restore .= &l:wrap ? ' wrap' : ' nowrap'
1359     let w:fugitive_diff_restore .= ' foldmethod=' . &l:foldmethod
1360     let w:fugitive_diff_restore .= ' foldcolumn=' . &l:foldcolumn
1361     if has('cursorbind')
1362       let w:fugitive_diff_restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
1363     endif
1364     diffthis
1365   endif
1366 endfunction
1368 function! s:diffoff()
1369   if exists('w:fugitive_diff_restore')
1370     execute w:fugitive_diff_restore
1371     unlet w:fugitive_diff_restore
1372   else
1373     diffoff
1374   endif
1375 endfunction
1377 function! s:diffoff_all(dir)
1378   for nr in range(1,winnr('$'))
1379     if getwinvar(nr,'&diff')
1380       if nr != winnr()
1381         execute nr.'wincmd w'
1382         let restorewinnr = 1
1383       endif
1384       if exists('b:git_dir') && b:git_dir ==# a:dir
1385         call s:diffoff()
1386       endif
1387     endif
1388   endfor
1389 endfunction
1391 function! s:buffer_compare_age(commit) dict abort
1392   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1393   let my_score    = get(scores,':'.self.commit(),0)
1394   let their_score = get(scores,':'.a:commit,0)
1395   if my_score || their_score
1396     return my_score < their_score ? -1 : my_score != their_score
1397   elseif self.commit() ==# a:commit
1398     return 0
1399   endif
1400   let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1401   if base ==# self.commit()
1402     return -1
1403   elseif base ==# a:commit
1404     return 1
1405   endif
1406   let my_time    = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1407   let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1408   return my_time < their_time ? -1 : my_time != their_time
1409 endfunction
1411 call s:add_methods('buffer',['compare_age'])
1413 function! s:Diff(bang,...)
1414   let split = a:bang ? 'split' : 'vsplit'
1415   if exists(':DiffGitCached')
1416     return 'DiffGitCached'
1417   elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1418     let nr = bufnr('')
1419     execute 'leftabove '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1420     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1421     call s:diffthis()
1422     wincmd p
1423     execute 'rightbelow '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1424     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1425     call s:diffthis()
1426     wincmd p
1427     call s:diffthis()
1428     return ''
1429   elseif a:0
1430     if a:1 ==# ''
1431       return ''
1432     elseif a:1 ==# '/'
1433       let file = s:buffer().path('/')
1434     elseif a:1 ==# ':'
1435       let file = s:buffer().path(':0:')
1436     elseif a:1 =~# '^:/.'
1437       try
1438         let file = s:repo().rev_parse(a:1).s:buffer().path(':')
1439       catch /^fugitive:/
1440         return 'echoerr v:errmsg'
1441       endtry
1442     else
1443       let file = s:buffer().expand(a:1)
1444     endif
1445     if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1446       let file = file.s:buffer().path(':')
1447     endif
1448   else
1449     let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1450   endif
1451   try
1452     let spec = s:repo().translate(file)
1453     let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1454     if s:buffer().compare_age(commit) < 0
1455       execute 'rightbelow '.split.' '.s:fnameescape(spec)
1456     else
1457       execute 'leftabove '.split.' '.s:fnameescape(spec)
1458     endif
1459     call s:diffthis()
1460     wincmd p
1461     call s:diffthis()
1462     return ''
1463   catch /^fugitive:/
1464     return 'echoerr v:errmsg'
1465   endtry
1466 endfunction
1468 " }}}1
1469 " Gmove, Gremove {{{1
1471 function! s:Move(force,destination)
1472   if a:destination =~# '^/'
1473     let destination = a:destination[1:-1]
1474   else
1475     let destination = fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p')
1476     if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1477       let destination = destination[strlen(s:repo().tree('')):-1]
1478     endif
1479   endif
1480   if isdirectory(s:buffer().spec())
1481     " Work around Vim parser idiosyncrasy
1482     let discarded = s:buffer().setvar('&swapfile',0)
1483   endif
1484   let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1485   if v:shell_error
1486     let v:errmsg = 'fugitive: '.message
1487     return 'echoerr v:errmsg'
1488   endif
1489   let destination = s:repo().tree(destination)
1490   if isdirectory(destination)
1491     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1492   endif
1493   call fugitive#reload_status()
1494   if s:buffer().commit() == ''
1495     if isdirectory(destination)
1496       return 'keepalt edit '.s:fnameescape(destination)
1497     else
1498       return 'keepalt saveas! '.s:fnameescape(destination)
1499     endif
1500   else
1501     return 'file '.s:fnameescape(s:repo().translate(':0:'.destination)
1502   endif
1503 endfunction
1505 function! s:MoveComplete(A,L,P)
1506   if a:A =~ '^/'
1507     return s:repo().superglob(a:A)
1508   else
1509     let matches = split(glob(a:A.'*'),"\n")
1510     call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1511     return matches
1512   endif
1513 endfunction
1515 function! s:Remove(force)
1516   if s:buffer().commit() ==# ''
1517     let cmd = ['rm']
1518   elseif s:buffer().commit() ==# '0'
1519     let cmd = ['rm','--cached']
1520   else
1521     let v:errmsg = 'fugitive: rm not supported here'
1522     return 'echoerr v:errmsg'
1523   endif
1524   if a:force
1525     let cmd += ['--force']
1526   endif
1527   let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1528   if v:shell_error
1529     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1530     return 'echoerr '.string(v:errmsg)
1531   else
1532     call fugitive#reload_status()
1533     return 'bdelete'.(a:force ? '!' : '')
1534   endif
1535 endfunction
1537 augroup fugitive_remove
1538   autocmd!
1539   autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1540         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1541         \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1542         \ endif
1543 augroup END
1545 " }}}1
1546 " Gblame {{{1
1548 augroup fugitive_blame
1549   autocmd!
1550   autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1551   autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1552   autocmd Syntax fugitiveblame call s:BlameSyntax()
1553   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
1554 augroup END
1556 function! s:linechars(pattern)
1557   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
1558   if exists('*synconcealed') && &conceallevel > 1
1559     for col in range(1, chars)
1560       let chars -= synconcealed(line('.'), col)[0]
1561     endfor
1562   endif
1563   return chars
1564 endfunction
1566 function! s:Blame(bang,line1,line2,count,args) abort
1567   try
1568     if s:buffer().path() == ''
1569       call s:throw('file or blob required')
1570     endif
1571     if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
1572       call s:throw('unsupported option')
1573     endif
1574     call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1575     let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1576     if s:buffer().commit() =~# '\D\|..'
1577       let cmd += [s:buffer().commit()]
1578     else
1579       let cmd += ['--contents', '-']
1580     endif
1581     let basecmd = escape(call(s:repo().git_command,cmd+['--',s:buffer().path()],s:repo()),'!')
1582     try
1583       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1584       if !s:repo().bare()
1585         let dir = getcwd()
1586         execute cd.'`=s:repo().tree()`'
1587       endif
1588       if a:count
1589         execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1590       else
1591         let error = resolve(tempname())
1592         let temp = error.'.fugitiveblame'
1593         if &shell =~# 'csh'
1594           silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1595         else
1596           silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1597         endif
1598         if exists('l:dir')
1599           execute cd.'`=dir`'
1600           unlet dir
1601         endif
1602         if v:shell_error
1603           call s:throw(join(readfile(error),"\n"))
1604         endif
1605         for winnr in range(winnr('$'),1,-1)
1606           call setwinvar(winnr, '&scrollbind', 0)
1607           if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
1608             execute winbufnr(winnr).'bdelete'
1609           endif
1610         endfor
1611         let bufnr = bufnr('')
1612         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1613         if &l:wrap
1614           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1615         endif
1616         if &l:foldenable
1617           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1618         endif
1619         setlocal scrollbind nowrap nofoldenable
1620         let top = line('w0') + &scrolloff
1621         let current = line('.')
1622         let s:temp_files[temp] = s:repo().dir()
1623         exe 'keepalt leftabove vsplit '.temp
1624         let b:fugitive_blamed_bufnr = bufnr
1625         let w:fugitive_leave = restore
1626         let b:fugitive_blame_arguments = join(a:args,' ')
1627         execute top
1628         normal! zt
1629         execute current
1630         setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable filetype=fugitiveblame
1631         if exists('+concealcursor')
1632           setlocal concealcursor=nc conceallevel=2
1633         endif
1634         if exists('+relativenumber')
1635           setlocal norelativenumber
1636         endif
1637         execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
1638         nnoremap <buffer> <silent> q    :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
1639         nnoremap <buffer> <silent> gq   :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete<Bar>if expand("%:p") =~# "^fugitive:[\\/][\\/]"<Bar>Gedit<Bar>endif','^-1','','')<CR>
1640         nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameJump('')<CR>
1641         nnoremap <buffer> <silent> -    :<C-U>exe <SID>BlameJump('')<CR>
1642         nnoremap <buffer> <silent> P    :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
1643         nnoremap <buffer> <silent> ~    :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1644         nnoremap <buffer> <silent> i    :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1645         nnoremap <buffer> <silent> o    :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
1646         nnoremap <buffer> <silent> O    :<C-U>exe <SID>BlameCommit("tabedit")<CR>
1647         nnoremap <buffer> <silent> A    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
1648         nnoremap <buffer> <silent> C    :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
1649         nnoremap <buffer> <silent> D    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
1650         redraw
1651         syncbind
1652       endif
1653     finally
1654       if exists('l:dir')
1655         execute cd.'`=dir`'
1656       endif
1657     endtry
1658     return ''
1659   catch /^fugitive:/
1660     return 'echoerr v:errmsg'
1661   endtry
1662 endfunction
1664 function! s:BlameCommit(cmd) abort
1665   let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
1666   if cmd =~# '^echoerr'
1667     return cmd
1668   endif
1669   let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
1670   let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1671   if path ==# ''
1672     let path = s:buffer(b:fugitive_blamed_bufnr).path()
1673   endif
1674   execute cmd
1675   if search('^diff .* b/\M'.escape(path,'\').'$','W')
1676     call search('^+++')
1677     let head = line('.')
1678     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
1679       let top = +matchstr(getline('.'),' +\zs\d\+')
1680       let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
1681       if lnum >= top && lnum <= top + len
1682         let offset = lnum - top
1683         if &scrolloff
1684           +
1685           normal! zt
1686         else
1687           normal! zt
1688           +
1689         endif
1690         while offset > 0 && line('.') < line('$')
1691           +
1692           if getline('.') =~# '^[ +]'
1693             let offset -= 1
1694           endif
1695         endwhile
1696         return 'if foldlevel(".")|foldopen!|endif'
1697       endif
1698     endwhile
1699     execute head
1700     normal! zt
1701   endif
1702   return ''
1703 endfunction
1705 function! s:BlameJump(suffix) abort
1706   let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
1707   if commit =~# '^0\+$'
1708     let commit = ':0'
1709   endif
1710   let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
1711   let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1712   if path ==# ''
1713     let path = s:buffer(b:fugitive_blamed_bufnr).path()
1714   endif
1715   let args = b:fugitive_blame_arguments
1716   let offset = line('.') - line('w0')
1717   let bufnr = bufnr('%')
1718   let winnr = bufwinnr(b:fugitive_blamed_bufnr)
1719   if winnr > 0
1720     exe winnr.'wincmd w'
1721   endif
1722   execute s:Edit('edit', 0, commit.a:suffix.':'.path)
1723   execute lnum
1724   if winnr > 0
1725     exe bufnr.'bdelete'
1726   endif
1727   execute 'Gblame '.args
1728   execute lnum
1729   let delta = line('.') - line('w0') - offset
1730   if delta > 0
1731     execute 'normal! '.delta."\<C-E>"
1732   elseif delta < 0
1733     execute 'normal! '.(-delta)."\<C-Y>"
1734   endif
1735   syncbind
1736   return ''
1737 endfunction
1739 function! s:BlameSyntax() abort
1740   let b:current_syntax = 'fugitiveblame'
1741   let conceal = has('conceal') ? ' conceal' : ''
1742   let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
1743   syn match FugitiveblameBoundary "^\^"
1744   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
1745   syn match FugitiveblameHash       "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1746   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1747   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
1748   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
1749   exec 'syn match FugitiveblameLineNumber         " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
1750   exec 'syn match FugitiveblameOriginalFile       " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
1751   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
1752   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
1753   syn match FugitiveblameShort              " \d\+)" contained contains=FugitiveblameLineNumber
1754   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
1755   hi def link FugitiveblameBoundary           Keyword
1756   hi def link FugitiveblameHash               Identifier
1757   hi def link FugitiveblameUncommitted        Function
1758   hi def link FugitiveblameTime               PreProc
1759   hi def link FugitiveblameLineNumber         Number
1760   hi def link FugitiveblameOriginalFile       String
1761   hi def link FugitiveblameOriginalLineNumber Float
1762   hi def link FugitiveblameShort              FugitiveblameDelimiter
1763   hi def link FugitiveblameDelimiter          Delimiter
1764   hi def link FugitiveblameNotCommittedYet    Comment
1765 endfunction
1767 " }}}1
1768 " Gbrowse {{{1
1770 call s:command("-bar -bang -range -nargs=? -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
1772 function! s:Browse(bang,line1,count,...) abort
1773   try
1774     let rev = a:0 ? substitute(a:1,'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
1775     if rev ==# ''
1776       let expanded = s:buffer().rev()
1777     elseif rev ==# ':'
1778       let expanded = s:buffer().path('/')
1779     else
1780       let expanded = s:buffer().expand(rev)
1781     endif
1782     let full = s:repo().translate(expanded)
1783     let commit = ''
1784     if full =~# '^fugitive://'
1785       let commit = matchstr(full,'://.*//\zs\w\+')
1786       let path = matchstr(full,'://.*//\w\+\zs/.*')
1787       if commit =~ '..'
1788         let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
1789       else
1790         let type = 'blob'
1791       endif
1792       let path = path[1:-1]
1793     elseif s:repo().bare()
1794       let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
1795       let type = ''
1796     else
1797       let path = full[strlen(s:repo().tree())+1:-1]
1798       if path =~# '^\.git/'
1799         let type = ''
1800       elseif isdirectory(full)
1801         let type = 'tree'
1802       else
1803         let type = 'blob'
1804       endif
1805     endif
1806     if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
1807       let body = readfile(s:repo().dir(path[5:-1]))[0]
1808       if body =~# '^\x\{40\}$'
1809         let commit = body
1810         let type = 'commit'
1811         let path = ''
1812       elseif body =~# '^ref: refs/'
1813         let path = '.git/' . matchstr(body,'ref: \zs.*')
1814       endif
1815     endif
1817     if a:0 && a:1 =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
1818       let remote = matchstr(a:1,'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
1819     elseif path =~# '^\.git/refs/remotes/.'
1820       let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
1821     else
1822       let remote = 'origin'
1823       let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
1824       if branch ==# '' && path =~# '^\.git/refs/\w\+/'
1825         let branch = s:sub(path,'^\.git/refs/\w+/','')
1826       endif
1827       if filereadable(s:repo().dir('refs/remotes/'.branch))
1828         let remote = matchstr(branch,'[^/]\+')
1829         let rev = rev[strlen(remote)+1:-1]
1830       else
1831         if branch ==# ''
1832           let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
1833         endif
1834         if branch != ''
1835           let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
1836           if remote =~# '^\.\=$'
1837             let remote = 'origin'
1838           elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
1839             let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
1840           endif
1841         endif
1842       endif
1843     endif
1845     let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
1846     if raw ==# ''
1847       let raw = remote
1848     endif
1850     let url = s:github_url(s:repo(),raw,rev,commit,path,type,a:line1,a:count)
1851     if url == ''
1852       let url = s:instaweb_url(s:repo(),rev,commit,path,type,a:count > 0 ? a:line1 : 0)
1853     endif
1855     if url == ''
1856       call s:throw("Instaweb failed to start and '".remote."' is not a GitHub remote")
1857     endif
1859     if a:bang
1860       let @* = url
1861       return 'echomsg '.string(url)
1862     else
1863       return 'echomsg '.string(url).'|call fugitive#buffer().repo().git_chomp("web--browse",'.string(url).')'
1864     endif
1865   catch /^fugitive:/
1866     return 'echoerr v:errmsg'
1867   endtry
1868 endfunction
1870 function! s:github_url(repo,url,rev,commit,path,type,line1,line2) abort
1871   let path = a:path
1872   let domain_pattern = 'github\.com'
1873   for domain in exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
1874     let domain_pattern .= '\|' . escape(domain, '.')
1875   endfor
1876   let repo = matchstr(a:url,'^\%(https\=://\|git://\|git@\)\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
1877   if repo ==# ''
1878     return ''
1879   endif
1880   let root = 'https://' . s:sub(repo,':','/')
1881   if path =~# '^\.git/refs/heads/'
1882     let branch = a:repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
1883     if branch ==# ''
1884       return root . '/commits/' . path[16:-1]
1885     else
1886       return root . '/commits/' . branch
1887     endif
1888   elseif path =~# '^\.git/refs/.'
1889     return root . '/commits/' . matchstr(path,'[^/]\+$')
1890   elseif path =~# '.git/\%(config$\|hooks\>\)'
1891     return root . '/admin'
1892   elseif path =~# '^\.git\>'
1893     return root
1894   endif
1895   if a:rev =~# '^[[:alnum:]._-]\+:'
1896     let commit = matchstr(a:rev,'^[^:]*')
1897   elseif a:commit =~# '^\d\=$'
1898     let local = matchstr(a:repo.head_ref(),'\<refs/heads/\zs.*')
1899     let commit = a:repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
1900     if commit ==# ''
1901       let commit = local
1902     endif
1903   else
1904     let commit = a:commit
1905   endif
1906   if a:type == 'tree'
1907     let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
1908   elseif a:type == 'blob'
1909     let url = root . '/blob/' . commit . '/' . path
1910     if a:line2 > 0 && a:line1 == a:line2
1911       let url .= '#L' . a:line1
1912     elseif a:line2 > 0
1913       let url .= '#L' . a:line1 . '-' . a:line2
1914     endif
1915   elseif a:type == 'tag'
1916     let commit = matchstr(getline(3),'^tag \zs.*')
1917     let url = root . '/tree/' . commit
1918   else
1919     let url = root . '/commit/' . commit
1920   endif
1921   return url
1922 endfunction
1924 function! s:instaweb_url(repo,rev,commit,path,type,...) abort
1925   let output = a:repo.git_chomp('instaweb','-b','unknown')
1926   if output =~# 'http://'
1927     let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:repo.dir(),':t')
1928   else
1929     return ''
1930   endif
1931   if a:path =~# '^\.git/refs/.'
1932     return root . ';a=shortlog;h=' . matchstr(a:path,'^\.git/\zs.*')
1933   elseif a:path =~# '^\.git\>'
1934     return root
1935   endif
1936   let url = root
1937   if a:commit =~# '^\x\{40\}$'
1938     if a:type ==# 'commit'
1939       let url .= ';a=commit'
1940     endif
1941     let url .= ';h=' . a:repo.rev_parse(a:commit . (a:path == '' ? '' : ':' . a:path))
1942   else
1943     if a:type ==# 'blob'
1944       let tmp = tempname()
1945       silent execute 'write !'.a:repo.git_command('hash-object','-w','--stdin').' > '.tmp
1946       let url .= ';h=' . readfile(tmp)[0]
1947     else
1948       try
1949         let url .= ';h=' . a:repo.rev_parse((a:commit == '' ? 'HEAD' : ':' . a:commit) . ':' . a:path)
1950       catch /^fugitive:/
1951         call s:throw('fugitive: cannot browse uncommitted file')
1952       endtry
1953     endif
1954     let root .= ';hb=' . matchstr(a:repo.head_ref(),'[^ ]\+$')
1955   endif
1956   if a:path !=# ''
1957     let url .= ';f=' . a:path
1958   endif
1959   if a:0 && a:1
1960     let url .= '#l' . a:1
1961   endif
1962   return url
1963 endfunction
1965 " }}}1
1966 " File access {{{1
1968 function! s:ReplaceCmd(cmd,...) abort
1969   let fn = expand('%:p')
1970   let tmp = tempname()
1971   let prefix = ''
1972   try
1973     if a:0 && a:1 != ''
1974       if &shell =~# 'cmd'
1975         let old_index = $GIT_INDEX_FILE
1976         let $GIT_INDEX_FILE = a:1
1977       else
1978         let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
1979       endif
1980     endif
1981     if &shell =~# 'cmd'
1982       let cmd_escape_char = &shellxquote == '(' ?  '^' : '^^^'
1983       call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').' > '.tmp.'"')
1984     else
1985       call system(' ('.prefix.a:cmd.' > '.tmp.') ')
1986     endif
1987   finally
1988     if exists('old_index')
1989       let $GIT_INDEX_FILE = old_index
1990     endif
1991   endtry
1992   silent exe 'keepalt file '.tmp
1993   try
1994     silent edit!
1995   finally
1996     silent exe 'keepalt file '.s:fnameescape(fn)
1997     call delete(tmp)
1998     if fnamemodify(bufname('$'), ':p') ==# tmp
1999       silent execute 'bwipeout '.bufnr('$')
2000     endif
2001     silent exe 'doau BufReadPost '.s:fnameescape(fn)
2002   endtry
2003 endfunction
2005 function! s:BufReadIndex()
2006   if !exists('b:fugitive_display_format')
2007     let b:fugitive_display_format = filereadable(expand('%').'.lock')
2008   endif
2009   let b:fugitive_display_format = b:fugitive_display_format % 2
2010   let b:fugitive_type = 'index'
2011   try
2012     let b:git_dir = s:repo().dir()
2013     setlocal noro ma nomodeline
2014     if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
2015       let index = ''
2016     else
2017       let index = expand('%:p')
2018     endif
2019     if b:fugitive_display_format
2020       call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
2021       set ft=git nospell
2022     else
2023       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
2024       let dir = getcwd()
2025       try
2026         execute cd.'`=s:repo().tree()`'
2027         call s:ReplaceCmd(s:repo().git_command('status'),index)
2028       finally
2029         execute cd.'`=dir`'
2030       endtry
2031       set ft=gitcommit
2032       set foldtext=fugitive#foldtext() foldmethod=syntax foldlevel=1
2033     endif
2034     setlocal ro noma nomod noswapfile
2035     if &bufhidden ==# ''
2036       setlocal bufhidden=delete
2037     endif
2038     call s:JumpInit()
2039     nunmap   <buffer>          P
2040     nunmap   <buffer>          ~
2041     nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
2042     nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
2043     nnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
2044     xnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line("'<"),line("'>"))<CR>
2045     nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
2046     nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
2047     nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
2048     nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>
2049     nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
2050     nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
2051     nnoremap <buffer> <silent> cva :<C-U>Gcommit --amend --verbose<CR>
2052     nnoremap <buffer> <silent> cvc :<C-U>Gcommit --verbose<CR>
2053     nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2054     nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2055     nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2056     nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2057     nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
2058     nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2059     nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2060     xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2061     nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
2062     nnoremap <buffer> <silent> R :<C-U>edit<CR>
2063   catch /^fugitive:/
2064     return 'echoerr v:errmsg'
2065   endtry
2066 endfunction
2068 function! s:FileRead()
2069   try
2070     let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
2071     let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
2072     let hash = repo.rev_parse(path)
2073     if path =~ '^:'
2074       let type = 'blob'
2075     else
2076       let type = repo.git_chomp('cat-file','-t',hash)
2077     endif
2078     " TODO: use count, if possible
2079     return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
2080   catch /^fugitive:/
2081     return 'echoerr v:errmsg'
2082   endtry
2083 endfunction
2085 function! s:BufReadIndexFile()
2086   try
2087     let b:fugitive_type = 'blob'
2088     let b:git_dir = s:repo().dir()
2089     try
2090       call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
2091     finally
2092       if &bufhidden ==# ''
2093         setlocal bufhidden=delete
2094       endif
2095     endtry
2096     return ''
2097   catch /^fugitive: rev-parse/
2098     silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
2099     return ''
2100   catch /^fugitive:/
2101     return 'echoerr v:errmsg'
2102   endtry
2103 endfunction
2105 function! s:BufWriteIndexFile()
2106   let tmp = tempname()
2107   try
2108     let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
2109     let stage = matchstr(expand('<amatch>'),'//\zs\d')
2110     silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
2111     let sha1 = readfile(tmp)[0]
2112     let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
2113     if old_mode == ''
2114       let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
2115     endif
2116     let info = old_mode.' '.sha1.' '.stage."\t".path
2117     call writefile([info],tmp)
2118     if has('win32')
2119       let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
2120     else
2121       let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
2122     endif
2123     if v:shell_error == 0
2124       setlocal nomodified
2125       silent execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
2126       call fugitive#reload_status()
2127       return ''
2128     else
2129       return 'echoerr '.string('fugitive: '.error)
2130     endif
2131   finally
2132     call delete(tmp)
2133   endtry
2134 endfunction
2136 function! s:BufReadObject()
2137   try
2138     setlocal noro ma
2139     let b:git_dir = s:repo().dir()
2140     let hash = s:buffer().sha1()
2141     if !exists("b:fugitive_type")
2142       let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
2143     endif
2144     if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
2145       return "echoerr 'fugitive: unrecognized git type'"
2146     endif
2147     let firstline = getline('.')
2148     if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
2149       let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
2150     endif
2152     if b:fugitive_type !=# 'blob'
2153       setlocal nomodeline
2154     endif
2156     let pos = getpos('.')
2157     silent %delete
2158     setlocal endofline
2160     try
2161       if b:fugitive_type ==# 'tree'
2162         let b:fugitive_display_format = b:fugitive_display_format % 2
2163         if b:fugitive_display_format
2164           call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
2165         else
2166           call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
2167         endif
2168       elseif b:fugitive_type ==# 'tag'
2169         let b:fugitive_display_format = b:fugitive_display_format % 2
2170         if b:fugitive_display_format
2171           call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2172         else
2173           call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
2174         endif
2175       elseif b:fugitive_type ==# 'commit'
2176         let b:fugitive_display_format = b:fugitive_display_format % 2
2177         if b:fugitive_display_format
2178           call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2179         else
2180           call s:ReplaceCmd(s:repo().git_command('show','--no-color','--pretty=format:tree %T%nparent %P%nauthor %an <%ae> %ad%ncommitter %cn <%ce> %cd%nencoding %e%n%n%s%n%n%b',hash))
2181           call search('^parent ')
2182           if getline('.') ==# 'parent '
2183             silent delete_
2184           else
2185             silent s/\%(^parent\)\@<! /\rparent /ge
2186           endif
2187           if search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2188             silent delete_
2189           end
2190           1
2191         endif
2192       elseif b:fugitive_type ==# 'blob'
2193         call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2194       endif
2195     finally
2196       call setpos('.',pos)
2197       setlocal ro noma nomod
2198       if &bufhidden ==# ''
2199         setlocal bufhidden=delete
2200       endif
2201       if b:fugitive_type !=# 'blob'
2202         set filetype=git
2203         nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
2204         nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
2205       else
2206         call s:JumpInit()
2207       endif
2208     endtry
2210     return ''
2211   catch /^fugitive:/
2212     return 'echoerr v:errmsg'
2213   endtry
2214 endfunction
2216 augroup fugitive_files
2217   autocmd!
2218   autocmd BufReadCmd  index{,.lock}
2219         \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
2220         \   exe s:BufReadIndex() |
2221         \ elseif filereadable(expand('<amatch>')) |
2222         \   read <amatch> |
2223         \   1delete |
2224         \ endif
2225   autocmd FileReadCmd fugitive://**//[0-3]/**          exe s:FileRead()
2226   autocmd BufReadCmd  fugitive://**//[0-3]/**          exe s:BufReadIndexFile()
2227   autocmd BufWriteCmd fugitive://**//[0-3]/**          exe s:BufWriteIndexFile()
2228   autocmd BufReadCmd  fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
2229   autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
2230   autocmd FileType git
2231         \ if exists('b:git_dir') |
2232         \  call s:JumpInit() |
2233         \ endif
2234 augroup END
2236 " }}}1
2237 " Temp files {{{1
2239 if !exists('s:temp_files')
2240   let s:temp_files = {}
2241 endif
2243 augroup fugitive_temp
2244   autocmd!
2245   autocmd BufNewFile,BufReadPost *
2246         \ if has_key(s:temp_files,expand('<amatch>:p')) |
2247         \   let b:git_dir = s:temp_files[expand('<amatch>:p')] |
2248         \   let b:git_type = 'temp' |
2249         \   call s:Detect(expand('<amatch>:p')) |
2250         \   setlocal bufhidden=delete |
2251         \   nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>|
2252         \ endif
2253 augroup END
2255 " }}}1
2256 " Go to file {{{1
2258 function! s:JumpInit() abort
2259   nnoremap <buffer> <silent> <CR>    :<C-U>exe <SID>GF("edit")<CR>
2260   if !&modifiable
2261     if exists(':CtrlP')
2262       nnoremap <buffer> <silent> <C-P> :<C-U>exe 'CtrlP '.fnameescape(<SID>repo().tree())<CR>
2263     endif
2264     nnoremap <buffer> <silent> o     :<C-U>exe <SID>GF("split")<CR>
2265     nnoremap <buffer> <silent> S     :<C-U>exe <SID>GF("vsplit")<CR>
2266     nnoremap <buffer> <silent> O     :<C-U>exe <SID>GF("tabedit")<CR>
2267     nnoremap <buffer> <silent> -     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().up(v:count1))<CR>
2268     nnoremap <buffer> <silent> P     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
2269     nnoremap <buffer> <silent> ~     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
2270     nnoremap <buffer> <silent> C     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2271     nnoremap <buffer> <silent> cc    :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2272     nnoremap <buffer> <silent> co    :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
2273     nnoremap <buffer> <silent> cS    :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
2274     nnoremap <buffer> <silent> cO    :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
2275     nnoremap <buffer> <silent> cp    :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
2276   endif
2277 endfunction
2279 function! s:GF(mode) abort
2280   try
2281     let buffer = s:buffer()
2282     let myhash = buffer.sha1()
2283     if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
2284       let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
2285     endif
2287     if buffer.type('tree')
2288       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2289       if showtree && line('.') == 1
2290         return ""
2291       elseif showtree && line('.') > 2
2292         return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$',''))
2293       elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
2294         return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
2295       endif
2297     elseif buffer.type('blob')
2298       let ref = expand("<cfile>")
2299       try
2300         let sha1 = buffer.repo().rev_parse(ref)
2301       catch /^fugitive:/
2302       endtry
2303       if exists('sha1')
2304         return s:Edit(a:mode,0,ref)
2305       endif
2307     else
2309       " Index
2310       if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
2311         let ref = matchstr(getline('.'),'\x\{40\}')
2312         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
2313         return s:Edit(a:mode,0,file)
2315       elseif getline('.') =~# '^#\trenamed:.* -> '
2316         let file = '/'.matchstr(getline('.'),' -> \zs.*')
2317         return s:Edit(a:mode,0,file)
2318       elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
2319         let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
2320         return s:Edit(a:mode,0,file)
2321       elseif getline('.') =~# '^#\t.'
2322         let file = '/'.matchstr(getline('.'),'#\t\zs.*')
2323         return s:Edit(a:mode,0,file)
2324       elseif getline('.') =~# ': needs merge$'
2325         let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
2326         return s:Edit(a:mode,0,file).'|Gdiff'
2328       elseif getline('.') ==# '# Not currently on any branch.'
2329         return s:Edit(a:mode,0,'HEAD')
2330       elseif getline('.') =~# '^# On branch '
2331         let file = 'refs/heads/'.getline('.')[12:]
2332         return s:Edit(a:mode,0,file)
2333       elseif getline('.') =~# "^# Your branch .*'"
2334         let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
2335         return s:Edit(a:mode,0,file)
2336       endif
2338       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2340       if getline('.') =~# '^ref: '
2341         let ref = strpart(getline('.'),5)
2343       elseif getline('.') =~# '^commit \x\{40\}\>'
2344         let ref = matchstr(getline('.'),'\x\{40\}')
2345         return s:Edit(a:mode,0,ref)
2347       elseif getline('.') =~# '^parent \x\{40\}\>'
2348         let ref = matchstr(getline('.'),'\x\{40\}')
2349         let line = line('.')
2350         let parent = 0
2351         while getline(line) =~# '^parent '
2352           let parent += 1
2353           let line -= 1
2354         endwhile
2355         return s:Edit(a:mode,0,ref)
2357       elseif getline('.') =~ '^tree \x\{40\}$'
2358         let ref = matchstr(getline('.'),'\x\{40\}')
2359         if s:repo().rev_parse(myhash.':') == ref
2360           let ref = myhash.':'
2361         endif
2362         return s:Edit(a:mode,0,ref)
2364       elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
2365         let ref = matchstr(getline('.'),'\x\{40\}')
2366         let type = matchstr(getline(line('.')+1),'type \zs.*')
2368       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
2369         return ''
2371       elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
2372         let ref = matchstr(getline('.'),'\x\{40\}')
2373         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
2375       elseif getline('.') =~# '^[+-]\{3\} [ab/]'
2376         let ref = getline('.')[4:]
2378       elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
2379         let type = getline('.')[0]
2380         let lnum = line('.') - 1
2381         let offset = -1
2382         while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
2383           if getline(lnum) =~# '^[ '.type.']'
2384             let offset += 1
2385           endif
2386           let lnum -= 1
2387         endwhile
2388         let offset += matchstr(getline(lnum), type.'\zs\d\+')
2389         let ref = getline(search('^'.type.'\{3\} [ab]/','bnW'))[4:-1]
2390         let dcmd = '+'.offset.'|if foldlevel(".")|foldopen!|endif'
2391         let dref = ''
2393       elseif getline('.') =~# '^rename from '
2394         let ref = 'a/'.getline('.')[12:]
2395       elseif getline('.') =~# '^rename to '
2396         let ref = 'b/'.getline('.')[10:]
2398       elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2399         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2400         let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2401         let dcmd = 'Gdiff'
2403       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2404         let line = getline(line('.')-1)
2405         let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2406         let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2407         let dcmd = 'Gdiff!'
2409       elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
2410         let ref = getline('.')
2411       else
2412         let ref = ''
2413       endif
2415       if myhash ==# ''
2416         let ref = s:sub(ref,'^a/','HEAD:')
2417         let ref = s:sub(ref,'^b/',':0:')
2418         if exists('dref')
2419           let dref = s:sub(dref,'^a/','HEAD:')
2420         endif
2421       else
2422         let ref = s:sub(ref,'^a/',myhash.'^:')
2423         let ref = s:sub(ref,'^b/',myhash.':')
2424         if exists('dref')
2425           let dref = s:sub(dref,'^a/',myhash.'^:')
2426         endif
2427       endif
2429       if ref ==# '/dev/null'
2430         " Empty blob
2431         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
2432       endif
2434       if exists('dref')
2435         return s:Edit(a:mode,0,ref) . '|'.dcmd.' '.s:fnameescape(dref)
2436       elseif ref != ""
2437         return s:Edit(a:mode,0,ref)
2438       endif
2440     endif
2441     return ''
2442   catch /^fugitive:/
2443     return 'echoerr v:errmsg'
2444   endtry
2445 endfunction
2447 " }}}1
2448 " Statusline {{{1
2450 function! s:repo_head_ref() dict abort
2451   return readfile(self.dir('HEAD'))[0]
2452 endfunction
2454 call s:add_methods('repo',['head_ref'])
2456 function! fugitive#statusline(...)
2457   if !exists('b:git_dir')
2458     return ''
2459   endif
2460   let status = ''
2461   if s:buffer().commit() != ''
2462     let status .= ':' . s:buffer().commit()[0:7]
2463   endif
2464   let status .= '('.fugitive#head(7).')'
2465   if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2466     return ',GIT'.status
2467   else
2468     return '[Git'.status.']'
2469   endif
2470 endfunction
2472 function! fugitive#head(...)
2473   if !exists('b:git_dir')
2474     return ''
2475   endif
2477   return s:repo().head(a:0 ? a:1 : 0)
2478 endfunction
2480 " }}}1
2481 " Folding {{{1
2483 function! fugitive#foldtext() abort
2484   if &foldmethod !=# 'syntax'
2485     return foldtext()
2486   elseif getline(v:foldstart) =~# '^diff '
2487     let [add, remove] = [-1, -1]
2488     let filename = ''
2489     for lnum in range(v:foldstart, v:foldend)
2490       if filename ==# '' && getline(lnum) =~# '^[+-]\{3\} [abciow12]/'
2491         let filename = getline(lnum)[6:-1]
2492       endif
2493       if getline(lnum) =~# '^+'
2494         let add += 1
2495       elseif getline(lnum) =~# '^-'
2496         let remove += 1
2497       elseif getline(lnum) =~# '^Binary '
2498         let binary = 1
2499       endif
2500     endfor
2501     if filename ==# ''
2502       let filename = matchstr(getline(v:foldstart), '^diff .\{-\} a/\zs.*\ze b/')
2503     endif
2504     if filename ==# ''
2505       let filename = getline(v:foldstart)[5:-1]
2506     endif
2507     if exists('binary')
2508       return 'Binary: '.filename
2509     else
2510       return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
2511     endif
2512   elseif getline(v:foldstart) =~# '^# .*:$'
2513     let lines = getline(v:foldstart, v:foldend)
2514     call filter(lines, 'v:val =~# "^#\t"')
2515     cal map(lines,'s:sub(v:val, "^#\t%(modified: +|renamed: +)=", "")')
2516     cal map(lines,'s:sub(v:val, "^([[:alpha:] ]+): +(.*)", "\\2 (\\1)")')
2517     return getline(v:foldstart).' '.join(lines, ', ')
2518   endif
2519   return foldtext()
2520 endfunction
2522 augroup fugitive_foldtext
2523   autocmd!
2524   autocmd User Fugitive
2525         \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
2526         \    set foldtext=fugitive#foldtext() |
2527         \ endif
2528 augroup END
2530 " }}}1
2532 " vim:set et sw=2: