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