Fix :Gcommit -c
[vim-fugitive.git] / plugin / fugitive.vim
blob6458d595c930d72fcafb217747e72c3120ec3484
1 " fugitive.vim - A Git wrapper so awesome, it should be illegal
2 " Maintainer:   Tim Pope <http://tpo.pe/>
3 " Version:      1.2
4 " GetLatestVimScripts: 2975 1 :AutoInstall: fugitive.vim
6 if exists('g:loaded_fugitive') || &cp
7   finish
8 endif
9 let g:loaded_fugitive = 1
11 if !exists('g:fugitive_git_executable')
12   let g:fugitive_git_executable = 'git'
13 endif
15 " 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 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.get_chomp('checkout', '--', filename)
796     else
797       call repo.get_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       if !exists('first_filename')
886         let first_filename = filename
887       endif
888       execute lnum
889       if filename =~ ' -> '
890         let cmd = ['mv','--'] + reverse(split(filename,' -> '))
891         let filename = cmd[-1]
892       elseif section ==# 'staged'
893         let cmd = ['reset','-q','--',filename]
894       elseif getline(lnum) =~# '^#\tdeleted:'
895         let cmd = ['rm','--',filename]
896       elseif getline(lnum) =~# '^#\tmodified:'
897         let cmd = ['add','--',filename]
898       else
899         let cmd = ['add','-A','--',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 augroup fugitive_commit
1061   autocmd!
1062   autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
1063 augroup END
1065 " Section: Ggrep, Glog
1067 if !exists('g:fugitive_summary_format')
1068   let g:fugitive_summary_format = '%s'
1069 endif
1071 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1072 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1073 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Glog :call s:Log('grep<bang>',<count>,<f-args>)")
1074 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gllog :call s:Log('lgrep<bang>',<count>,<f-args>)")
1076 function! s:Grep(cmd,bang,arg) abort
1077   let grepprg = &grepprg
1078   let grepformat = &grepformat
1079   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1080   let dir = getcwd()
1081   try
1082     execute cd.'`=s:repo().tree()`'
1083     let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n')
1084     let &grepformat = '%f:%l:%m'
1085     exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
1086     let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
1087     for entry in list
1088       if bufname(entry.bufnr) =~ ':'
1089         let entry.filename = s:repo().translate(bufname(entry.bufnr))
1090         unlet! entry.bufnr
1091         let changed = 1
1092       elseif a:arg =~# '\%(^\| \)--cached\>'
1093         let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
1094         unlet! entry.bufnr
1095         let changed = 1
1096       endif
1097     endfor
1098     if a:cmd =~# '^l' && exists('changed')
1099       call setloclist(0, list, 'r')
1100     elseif exists('changed')
1101       call setqflist(list, 'r')
1102     endif
1103     if !a:bang && !empty(list)
1104       return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
1105     else
1106       return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
1107     endif
1108   finally
1109     let &grepprg = grepprg
1110     let &grepformat = grepformat
1111     execute cd.'`=dir`'
1112   endtry
1113 endfunction
1115 function! s:Log(cmd, count, ...) abort
1116   let path = s:buffer().path('/')
1117   if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
1118     let path = ''
1119   endif
1120   let cmd = ['--no-pager', 'log', '--no-color']
1121   let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.':'.(a:count ? a:count : '').'::'.g:fugitive_summary_format]
1122   if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
1123     if s:buffer().commit() =~# '\x\{40\}'
1124       let cmd += [s:buffer().commit()]
1125     elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
1126       let cmd += [s:buffer().path()[5:-1]]
1127     endif
1128   end
1129   let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
1130   if path =~# '/.'
1131     let cmd += ['--',path[1:-1]]
1132   endif
1133   let grepformat = &grepformat
1134   let grepprg = &grepprg
1135   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1136   let dir = getcwd()
1137   try
1138     execute cd.'`=s:repo().tree()`'
1139     let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
1140     let &grepformat = '%f:%l::%m,%f:::%m'
1141     exe a:cmd
1142   finally
1143     let &grepformat = grepformat
1144     let &grepprg = grepprg
1145     execute cd.'`=dir`'
1146   endtry
1147 endfunction
1149 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
1151 function! s:Edit(cmd,bang,...) abort
1152   let buffer = s:buffer()
1153   if a:cmd !~# 'read'
1154     if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
1155       wincmd p
1156       if &diff
1157         let mywinnr = winnr()
1158         for winnr in range(winnr('$'),1,-1)
1159           if winnr != mywinnr && getwinvar(winnr,'&diff')
1160             execute winnr.'wincmd w'
1161             close
1162             wincmd p
1163           endif
1164         endfor
1165       endif
1166     endif
1167   endif
1169   if a:bang
1170     let arglist = map(copy(a:000), 's:gsub(v:val, ''\\@<!%(\\\\)*\zs[%#]'', ''\=s:buffer().expand(submatch(0))'')')
1171     let args = join(arglist, ' ')
1172     if a:cmd =~# 'read'
1173       let git = buffer.repo().git_command()
1174       let last = line('$')
1175       silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
1176       if a:cmd ==# 'read'
1177         silent execute '1,'.last.'delete_'
1178       endif
1179       call fugitive#reload_status()
1180       diffupdate
1181       return 'redraw|echo '.string(':!'.git.' '.args)
1182     else
1183       let temp = resolve(tempname())
1184       let s:temp_files[tolower(temp)] = { 'dir': buffer.repo().dir(), 'args': arglist }
1185       silent execute a:cmd.' '.temp
1186       if a:cmd =~# 'pedit'
1187         wincmd P
1188       endif
1189       let echo = s:Edit('read',1,args)
1190       silent write!
1191       setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
1192       if getline(1) !~# '^diff '
1193         setlocal readonly nomodifiable
1194       endif
1195       if a:cmd =~# 'pedit'
1196         wincmd p
1197       endif
1198       return echo
1199     endif
1200     return ''
1201   endif
1203   if a:0 && a:1 == ''
1204     return ''
1205   elseif a:0
1206     let file = buffer.expand(join(a:000, ' '))
1207   elseif expand('%') ==# ''
1208     let file = ':'
1209   elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
1210     let file = buffer.path(':')
1211   else
1212     let file = buffer.path('/')
1213   endif
1214   try
1215     let file = buffer.repo().translate(file)
1216   catch /^fugitive:/
1217     return 'echoerr v:errmsg'
1218   endtry
1219   if a:cmd ==# 'read'
1220     return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
1221   else
1222     return a:cmd.' '.s:fnameescape(file)
1223   endif
1224 endfunction
1226 function! s:EditComplete(A,L,P) abort
1227   return map(s:repo().superglob(a:A), 'fnameescape(v:val)')
1228 endfunction
1230 function! s:EditRunComplete(A,L,P) abort
1231   if a:L =~# '^\w\+!'
1232     return s:GitComplete(a:A,a:L,a:P)
1233   else
1234     return s:repo().superglob(a:A)
1235   endif
1236 endfunction
1238 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Ge       :execute s:Edit('edit<bang>',0,<f-args>)")
1239 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gedit    :execute s:Edit('edit<bang>',0,<f-args>)")
1240 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit   :execute s:Edit('pedit',<bang>0,<f-args>)")
1241 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gsplit   :execute s:Edit('split',<bang>0,<f-args>)")
1242 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gvsplit  :execute s:Edit('vsplit',<bang>0,<f-args>)")
1243 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
1244 call s:command("-bar -bang -nargs=* -count -complete=customlist,s:EditRunComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read',<bang>0,<f-args>)")
1246 " Section: Gwrite, Gwq
1248 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
1249 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
1250 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
1252 function! s:Write(force,...) abort
1253   if exists('b:fugitive_commit_arguments')
1254     return 'write|bdelete'
1255   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
1256     return 'wq'
1257   elseif s:buffer().type() == 'index'
1258     return 'Gcommit'
1259   elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
1260     let filename = getline(4)[6:-1]
1261     setlocal buftype=
1262     silent write
1263     setlocal buftype=nowrite
1264     if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
1265       let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
1266     else
1267       let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
1268     endif
1269     if err !=# ''
1270       let v:errmsg = split(err,"\n")[0]
1271       return 'echoerr v:errmsg'
1272     elseif a:force
1273       return 'bdelete'
1274     else
1275       return 'Gedit '.fnameescape(filename)
1276     endif
1277   endif
1278   let mytab = tabpagenr()
1279   let mybufnr = bufnr('')
1280   let path = a:0 ? join(a:000, ' ') : s:buffer().path()
1281   if path =~# '^:\d\>'
1282     return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
1283   endif
1284   let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
1285   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) !=# ''
1286     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
1287     return 'echoerr v:errmsg'
1288   endif
1289   let file = s:repo().translate(path)
1290   let treebufnr = 0
1291   for nr in range(1,bufnr('$'))
1292     if fnamemodify(bufname(nr),':p') ==# file
1293       let treebufnr = nr
1294     endif
1295   endfor
1297   if treebufnr > 0 && treebufnr != bufnr('')
1298     let temp = tempname()
1299     silent execute '%write '.temp
1300     for tab in [mytab] + range(1,tabpagenr('$'))
1301       for winnr in range(1,tabpagewinnr(tab,'$'))
1302         if tabpagebuflist(tab)[winnr-1] == treebufnr
1303           execute 'tabnext '.tab
1304           if winnr != winnr()
1305             execute winnr.'wincmd w'
1306             let restorewinnr = 1
1307           endif
1308           try
1309             let lnum = line('.')
1310             let last = line('$')
1311             silent execute '$read '.temp
1312             silent execute '1,'.last.'delete_'
1313             silent write!
1314             silent execute lnum
1315             let did = 1
1316           finally
1317             if exists('restorewinnr')
1318               wincmd p
1319             endif
1320             execute 'tabnext '.mytab
1321           endtry
1322         endif
1323       endfor
1324     endfor
1325     if !exists('did')
1326       call writefile(readfile(temp,'b'),file,'b')
1327     endif
1328   else
1329     execute 'write! '.s:fnameescape(s:repo().translate(path))
1330   endif
1332   if a:force
1333     let error = s:repo().git_chomp_in_tree('add', '--force', file)
1334   else
1335     let error = s:repo().git_chomp_in_tree('add', file)
1336   endif
1337   if v:shell_error
1338     let v:errmsg = 'fugitive: '.error
1339     return 'echoerr v:errmsg'
1340   endif
1341   if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1342     set nomodified
1343   endif
1345   let one = s:repo().translate(':1:'.path)
1346   let two = s:repo().translate(':2:'.path)
1347   let three = s:repo().translate(':3:'.path)
1348   for nr in range(1,bufnr('$'))
1349     let name = fnamemodify(bufname(nr), ':p')
1350     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
1351       execute nr.'bdelete'
1352     endif
1353   endfor
1355   unlet! restorewinnr
1356   let zero = s:repo().translate(':0:'.path)
1357   for tab in range(1,tabpagenr('$'))
1358     for winnr in range(1,tabpagewinnr(tab,'$'))
1359       let bufnr = tabpagebuflist(tab)[winnr-1]
1360       let bufname = fnamemodify(bufname(bufnr), ':p')
1361       if bufname ==# zero && bufnr != mybufnr
1362         execute 'tabnext '.tab
1363         if winnr != winnr()
1364           execute winnr.'wincmd w'
1365           let restorewinnr = 1
1366         endif
1367         try
1368           let lnum = line('.')
1369           let last = line('$')
1370           silent execute '$read '.s:fnameescape(file)
1371           silent execute '1,'.last.'delete_'
1372           silent execute lnum
1373           set nomodified
1374           diffupdate
1375         finally
1376           if exists('restorewinnr')
1377             wincmd p
1378           endif
1379           execute 'tabnext '.mytab
1380         endtry
1381         break
1382       endif
1383     endfor
1384   endfor
1385   call fugitive#reload_status()
1386   return 'checktime'
1387 endfunction
1389 function! s:Wq(force,...) abort
1390   let bang = a:force ? '!' : ''
1391   if exists('b:fugitive_commit_arguments')
1392     return 'wq'.bang
1393   endif
1394   let result = call(s:function('s:Write'),[a:force]+a:000)
1395   if result =~# '^\%(write\|wq\|echoerr\)'
1396     return s:sub(result,'^write','wq')
1397   else
1398     return result.'|quit'.bang
1399   endif
1400 endfunction
1402 " Section: Gdiff
1404 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gdiff :execute s:Diff('keepalt ',<f-args>)")
1405 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gvdiff :execute s:Diff('keepalt vert ',<f-args>)")
1406 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gsdiff :execute s:Diff('',<f-args>)")
1408 augroup fugitive_diff
1409   autocmd!
1410   autocmd BufWinLeave *
1411         \ if getwinvar(bufwinnr(+expand('<abuf>')), '&diff') &&
1412         \     s:diff_window_count() == 2 &&
1413         \     !empty(getbufvar(+expand('<abuf>'), 'git_dir')) |
1414         \   call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
1415         \ endif
1416   autocmd BufWinEnter *
1417         \ if getwinvar(bufwinnr(+expand('<abuf>')), '&diff') &&
1418         \     s:diff_window_count() == 1 &&
1419         \     !empty(getbufvar(+expand('<abuf>'), 'git_dir')) |
1420         \   call s:diffoff() |
1421         \ endif
1422 augroup END
1424 function! s:diff_horizontal(count) abort
1425   let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
1426   if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
1427     return 'keepalt vert '
1428   elseif &diffopt =~# 'vertical'
1429     return 'keepalt '
1430   elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
1431     return 'keepalt '
1432   else
1433     return 'keepalt vert '
1434   endif
1435 endfunction
1437 function! s:diff_window_count() abort
1438   let c = 0
1439   for nr in range(1,winnr('$'))
1440     let c += getwinvar(nr,'&diff')
1441   endfor
1442   return c
1443 endfunction
1445 function! s:diff_restore() abort
1446   let restore = 'setlocal nodiff noscrollbind'
1447         \ . ' scrollopt=' . &l:scrollopt
1448         \ . (&l:wrap ? ' wrap' : ' nowrap')
1449         \ . ' foldlevel=999'
1450         \ . ' foldmethod=' . &l:foldmethod
1451         \ . ' foldcolumn=' . &l:foldcolumn
1452         \ . ' foldlevel=' . &l:foldlevel
1453         \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
1454   if has('cursorbind')
1455     let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
1456   endif
1457   return restore
1458 endfunction
1460 function! s:diffthis() abort
1461   if !&diff
1462     let w:fugitive_diff_restore = s:diff_restore()
1463     diffthis
1464   endif
1465 endfunction
1467 function! s:diffoff() abort
1468   if exists('w:fugitive_diff_restore')
1469     execute w:fugitive_diff_restore
1470     unlet w:fugitive_diff_restore
1471   else
1472     diffoff
1473   endif
1474 endfunction
1476 function! s:diffoff_all(dir) abort
1477   for nr in range(1,winnr('$'))
1478     if getwinvar(nr,'&diff')
1479       if nr != winnr()
1480         execute nr.'wincmd w'
1481         let restorewinnr = 1
1482       endif
1483       if exists('b:git_dir') && b:git_dir ==# a:dir
1484         call s:diffoff()
1485       endif
1486     endif
1487   endfor
1488 endfunction
1490 function! s:buffer_compare_age(commit) dict abort
1491   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1492   let my_score    = get(scores,':'.self.commit(),0)
1493   let their_score = get(scores,':'.a:commit,0)
1494   if my_score || their_score
1495     return my_score < their_score ? -1 : my_score != their_score
1496   elseif self.commit() ==# a:commit
1497     return 0
1498   endif
1499   let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1500   if base ==# self.commit()
1501     return -1
1502   elseif base ==# a:commit
1503     return 1
1504   endif
1505   let my_time    = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1506   let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1507   return my_time < their_time ? -1 : my_time != their_time
1508 endfunction
1510 call s:add_methods('buffer',['compare_age'])
1512 function! s:Diff(vert,...) abort
1513   let vert = empty(a:vert) ? s:diff_horizontal(2) : a:vert
1514   if exists(':DiffGitCached')
1515     return 'DiffGitCached'
1516   elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1517     let vert = empty(a:vert) ? s:diff_horizontal(3) : a:vert
1518     let nr = bufnr('')
1519     execute 'leftabove '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1520     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1521     call s:diffthis()
1522     wincmd p
1523     execute 'rightbelow '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1524     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1525     call s:diffthis()
1526     wincmd p
1527     call s:diffthis()
1528     return ''
1529   elseif a:0
1530     let arg = join(a:000, ' ')
1531     if arg ==# ''
1532       return ''
1533     elseif arg ==# '/'
1534       let file = s:buffer().path('/')
1535     elseif arg ==# ':'
1536       let file = s:buffer().path(':0:')
1537     elseif arg =~# '^:/.'
1538       try
1539         let file = s:repo().rev_parse(arg).s:buffer().path(':')
1540       catch /^fugitive:/
1541         return 'echoerr v:errmsg'
1542       endtry
1543     else
1544       let file = s:buffer().expand(arg)
1545     endif
1546     if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1547       let file = file.s:buffer().path(':')
1548     endif
1549   else
1550     let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1551   endif
1552   try
1553     let spec = s:repo().translate(file)
1554     let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1555     let restore = s:diff_restore()
1556     if exists('+cursorbind')
1557       setlocal cursorbind
1558     endif
1559     let w:fugitive_diff_restore = restore
1560     if s:buffer().compare_age(commit) < 0
1561       execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
1562     else
1563       execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
1564     endif
1565     let w:fugitive_diff_restore = restore
1566     let winnr = winnr()
1567     if getwinvar('#', '&diff')
1568       wincmd p
1569       call feedkeys("\<C-W>p", 'n')
1570     endif
1571     return ''
1572   catch /^fugitive:/
1573     return 'echoerr v:errmsg'
1574   endtry
1575 endfunction
1577 " Section: Gmove, Gremove
1579 function! s:Move(force,destination) abort
1580   if a:destination =~# '^/'
1581     let destination = a:destination[1:-1]
1582   else
1583     let destination = s:shellslash(fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p'))
1584     if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1585       let destination = destination[strlen(s:repo().tree('')):-1]
1586     endif
1587   endif
1588   if isdirectory(s:buffer().spec())
1589     " Work around Vim parser idiosyncrasy
1590     let discarded = s:buffer().setvar('&swapfile',0)
1591   endif
1592   let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1593   if v:shell_error
1594     let v:errmsg = 'fugitive: '.message
1595     return 'echoerr v:errmsg'
1596   endif
1597   let destination = s:repo().tree(destination)
1598   if isdirectory(destination)
1599     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1600   endif
1601   call fugitive#reload_status()
1602   if s:buffer().commit() == ''
1603     if isdirectory(destination)
1604       return 'keepalt edit '.s:fnameescape(destination)
1605     else
1606       return 'keepalt saveas! '.s:fnameescape(destination)
1607     endif
1608   else
1609     return 'file '.s:fnameescape(s:repo().translate(':0:'.destination))
1610   endif
1611 endfunction
1613 function! s:MoveComplete(A,L,P) abort
1614   if a:A =~ '^/'
1615     return s:repo().superglob(a:A)
1616   else
1617     let matches = split(glob(a:A.'*'),"\n")
1618     call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1619     return matches
1620   endif
1621 endfunction
1623 function! s:Remove(force) abort
1624   if s:buffer().commit() ==# ''
1625     let cmd = ['rm']
1626   elseif s:buffer().commit() ==# '0'
1627     let cmd = ['rm','--cached']
1628   else
1629     let v:errmsg = 'fugitive: rm not supported here'
1630     return 'echoerr v:errmsg'
1631   endif
1632   if a:force
1633     let cmd += ['--force']
1634   endif
1635   let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1636   if v:shell_error
1637     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1638     return 'echoerr '.string(v:errmsg)
1639   else
1640     call fugitive#reload_status()
1641     return 'bdelete'.(a:force ? '!' : '')
1642   endif
1643 endfunction
1645 augroup fugitive_remove
1646   autocmd!
1647   autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1648         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1649         \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1650         \ endif
1651 augroup END
1653 " Section: Gblame
1655 augroup fugitive_blame
1656   autocmd!
1657   autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1658   autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1659   autocmd Syntax fugitiveblame call s:BlameSyntax()
1660   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
1661 augroup END
1663 function! s:linechars(pattern) abort
1664   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
1665   if exists('*synconcealed') && &conceallevel > 1
1666     for col in range(1, chars)
1667       let chars -= synconcealed(line('.'), col)[0]
1668     endfor
1669   endif
1670   return chars
1671 endfunction
1673 function! s:Blame(bang,line1,line2,count,args) abort
1674   try
1675     if s:buffer().path() == ''
1676       call s:throw('file or blob required')
1677     endif
1678     if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
1679       call s:throw('unsupported option')
1680     endif
1681     call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1682     let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1683     if s:buffer().commit() =~# '\D\|..'
1684       let cmd += [s:buffer().commit()]
1685     else
1686       let cmd += ['--contents', '-']
1687     endif
1688     let cmd += ['--', s:buffer().path()]
1689     let basecmd = escape(call(s:repo().git_command,cmd,s:repo()),'!')
1690     try
1691       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1692       if !s:repo().bare()
1693         let dir = getcwd()
1694         execute cd.'`=s:repo().tree()`'
1695       endif
1696       if a:count
1697         execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1698       else
1699         let error = resolve(tempname())
1700         let temp = error.'.fugitiveblame'
1701         if &shell =~# 'csh'
1702           silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1703         else
1704           silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1705         endif
1706         if exists('l:dir')
1707           execute cd.'`=dir`'
1708           unlet dir
1709         endif
1710         if v:shell_error
1711           call s:throw(join(readfile(error),"\n"))
1712         endif
1713         for winnr in range(winnr('$'),1,-1)
1714           call setwinvar(winnr, '&scrollbind', 0)
1715           if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
1716             execute winbufnr(winnr).'bdelete'
1717           endif
1718         endfor
1719         let bufnr = bufnr('')
1720         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1721         if &l:wrap
1722           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1723         endif
1724         if &l:foldenable
1725           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1726         endif
1727         setlocal scrollbind nowrap nofoldenable
1728         let top = line('w0') + &scrolloff
1729         let current = line('.')
1730         let s:temp_files[tolower(temp)] = { 'dir': s:repo().dir(), 'args': cmd }
1731         exe 'keepalt leftabove vsplit '.temp
1732         let b:fugitive_blamed_bufnr = bufnr
1733         let w:fugitive_leave = restore
1734         let b:fugitive_blame_arguments = join(a:args,' ')
1735         execute top
1736         normal! zt
1737         execute current
1738         setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame
1739         if exists('+concealcursor')
1740           setlocal concealcursor=nc conceallevel=2
1741         endif
1742         if exists('+relativenumber')
1743           setlocal norelativenumber
1744         endif
1745         execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
1746         nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
1747         nnoremap <buffer> <silent> g?   :help fugitive-:Gblame<CR>
1748         nnoremap <buffer> <silent> q    :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
1749         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>
1750         nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1751         nnoremap <buffer> <silent> -    :<C-U>exe <SID>BlameJump('')<CR>
1752         nnoremap <buffer> <silent> P    :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
1753         nnoremap <buffer> <silent> ~    :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1754         nnoremap <buffer> <silent> i    :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1755         nnoremap <buffer> <silent> o    :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
1756         nnoremap <buffer> <silent> O    :<C-U>exe <SID>BlameCommit("tabedit")<CR>
1757         nnoremap <buffer> <silent> A    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
1758         nnoremap <buffer> <silent> C    :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
1759         nnoremap <buffer> <silent> D    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
1760         redraw
1761         syncbind
1762       endif
1763     finally
1764       if exists('l:dir')
1765         execute cd.'`=dir`'
1766       endif
1767     endtry
1768     return ''
1769   catch /^fugitive:/
1770     return 'echoerr v:errmsg'
1771   endtry
1772 endfunction
1774 function! s:BlameCommit(cmd) abort
1775   let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
1776   if cmd =~# '^echoerr'
1777     return cmd
1778   endif
1779   let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
1780   let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1781   if path ==# ''
1782     let path = s:buffer(b:fugitive_blamed_bufnr).path()
1783   endif
1784   execute cmd
1785   if search('^diff .* b/\M'.escape(path,'\').'$','W')
1786     call search('^+++')
1787     let head = line('.')
1788     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
1789       let top = +matchstr(getline('.'),' +\zs\d\+')
1790       let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
1791       if lnum >= top && lnum <= top + len
1792         let offset = lnum - top
1793         if &scrolloff
1794           +
1795           normal! zt
1796         else
1797           normal! zt
1798           +
1799         endif
1800         while offset > 0 && line('.') < line('$')
1801           +
1802           if getline('.') =~# '^[ +]'
1803             let offset -= 1
1804           endif
1805         endwhile
1806         return 'normal! zv'
1807       endif
1808     endwhile
1809     execute head
1810     normal! zt
1811   endif
1812   return ''
1813 endfunction
1815 function! s:BlameJump(suffix) abort
1816   let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
1817   if commit =~# '^0\+$'
1818     let commit = ':0'
1819   endif
1820   let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
1821   let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1822   if path ==# ''
1823     let path = s:buffer(b:fugitive_blamed_bufnr).path()
1824   endif
1825   let args = b:fugitive_blame_arguments
1826   let offset = line('.') - line('w0')
1827   let bufnr = bufnr('%')
1828   let winnr = bufwinnr(b:fugitive_blamed_bufnr)
1829   if winnr > 0
1830     exe winnr.'wincmd w'
1831   endif
1832   execute s:Edit('edit', 0, commit.a:suffix.':'.path)
1833   execute lnum
1834   if winnr > 0
1835     exe bufnr.'bdelete'
1836   endif
1837   execute 'Gblame '.args
1838   execute lnum
1839   let delta = line('.') - line('w0') - offset
1840   if delta > 0
1841     execute 'normal! '.delta."\<C-E>"
1842   elseif delta < 0
1843     execute 'normal! '.(-delta)."\<C-Y>"
1844   endif
1845   syncbind
1846   return ''
1847 endfunction
1849 function! s:BlameSyntax() abort
1850   let b:current_syntax = 'fugitiveblame'
1851   let conceal = has('conceal') ? ' conceal' : ''
1852   let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
1853   syn match FugitiveblameBoundary "^\^"
1854   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
1855   syn match FugitiveblameHash       "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1856   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1857   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
1858   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
1859   exec 'syn match FugitiveblameLineNumber         " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
1860   exec 'syn match FugitiveblameOriginalFile       " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
1861   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
1862   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
1863   syn match FugitiveblameShort              " \d\+)" contained contains=FugitiveblameLineNumber
1864   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
1865   hi def link FugitiveblameBoundary           Keyword
1866   hi def link FugitiveblameHash               Identifier
1867   hi def link FugitiveblameUncommitted        Function
1868   hi def link FugitiveblameTime               PreProc
1869   hi def link FugitiveblameLineNumber         Number
1870   hi def link FugitiveblameOriginalFile       String
1871   hi def link FugitiveblameOriginalLineNumber Float
1872   hi def link FugitiveblameShort              FugitiveblameDelimiter
1873   hi def link FugitiveblameDelimiter          Delimiter
1874   hi def link FugitiveblameNotCommittedYet    Comment
1875 endfunction
1877 " Section: Gbrowse
1879 call s:command("-bar -bang -range -nargs=* -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
1881 function! s:Browse(bang,line1,count,...) abort
1882   try
1883     let rev = a:0 ? substitute(join(a:000, ' '),'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
1884     if rev ==# ''
1885       let expanded = s:buffer().rev()
1886     elseif rev ==# ':'
1887       let expanded = s:buffer().path('/')
1888     else
1889       let expanded = s:buffer().expand(rev)
1890     endif
1891     let full = s:repo().translate(expanded)
1892     let commit = ''
1893     if full =~# '^fugitive://'
1894       let commit = matchstr(full,'://.*//\zs\w\+')
1895       let path = matchstr(full,'://.*//\w\+\zs/.*')
1896       if commit =~ '..'
1897         let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
1898       else
1899         let type = 'blob'
1900       endif
1901       let path = path[1:-1]
1902     elseif s:repo().bare()
1903       let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
1904       let type = ''
1905     else
1906       let path = full[strlen(s:repo().tree())+1:-1]
1907       if path =~# '^\.git/'
1908         let type = ''
1909       elseif isdirectory(full)
1910         let type = 'tree'
1911       else
1912         let type = 'blob'
1913       endif
1914     endif
1915     if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
1916       let body = readfile(s:repo().dir(path[5:-1]))[0]
1917       if body =~# '^\x\{40\}$'
1918         let commit = body
1919         let type = 'commit'
1920         let path = ''
1921       elseif body =~# '^ref: refs/'
1922         let path = '.git/' . matchstr(body,'ref: \zs.*')
1923       endif
1924     endif
1926     if a:0 && join(a:000, ' ') =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
1927       let remote = matchstr(join(a:000, ' '),'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
1928     elseif path =~# '^\.git/refs/remotes/.'
1929       let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
1930     else
1931       let remote = 'origin'
1932       let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
1933       if branch ==# '' && path =~# '^\.git/refs/\w\+/'
1934         let branch = s:sub(path,'^\.git/refs/\w+/','')
1935       endif
1936       if filereadable(s:repo().dir('refs/remotes/'.branch))
1937         let remote = matchstr(branch,'[^/]\+')
1938         let rev = rev[strlen(remote)+1:-1]
1939       else
1940         if branch ==# ''
1941           let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
1942         endif
1943         if branch != ''
1944           let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
1945           if remote =~# '^\.\=$'
1946             let remote = 'origin'
1947           elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
1948             let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
1949           endif
1950         endif
1951       endif
1952     endif
1954     let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
1955     if raw ==# ''
1956       let raw = remote
1957     endif
1959     let url = s:github_url(s:repo(),raw,rev,commit,path,type,a:line1,a:count)
1960     if url == ''
1961       let url = s:instaweb_url(s:repo(),rev,commit,path,type,a:count > 0 ? a:line1 : 0)
1962     endif
1964     if url == ''
1965       call s:throw("Instaweb failed to start and '".remote."' is not a GitHub remote")
1966     endif
1968     if a:bang
1969       let @* = url
1970       return 'echomsg '.string(url)
1971     else
1972       return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
1973     endif
1974   catch /^fugitive:/
1975     return 'echoerr v:errmsg'
1976   endtry
1977 endfunction
1979 function! s:github_url(repo,url,rev,commit,path,type,line1,line2) abort
1980   let path = a:path
1981   let domain_pattern = 'github\.com'
1982   let domains = exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
1983   for domain in domains
1984     let domain_pattern .= '\|' . escape(split(domain, '://')[-1], '.')
1985   endfor
1986   let repo = matchstr(a:url,'^\%(https\=://\|git://\|git@\)\=\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
1987   if repo ==# ''
1988     return ''
1989   endif
1990   if index(domains, 'http://' . matchstr(repo, '^[^:/]*')) >= 0
1991     let root = 'http://' . s:sub(repo,':','/')
1992   else
1993     let root = 'https://' . s:sub(repo,':','/')
1994   endif
1995   if path =~# '^\.git/refs/heads/'
1996     let branch = a:repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
1997     if branch ==# ''
1998       return root . '/commits/' . path[16:-1]
1999     else
2000       return root . '/commits/' . branch
2001     endif
2002   elseif path =~# '^\.git/refs/.'
2003     return root . '/commits/' . matchstr(path,'[^/]\+$')
2004   elseif path =~# '.git/\%(config$\|hooks\>\)'
2005     return root . '/admin'
2006   elseif path =~# '^\.git\>'
2007     return root
2008   endif
2009   if a:rev =~# '^[[:alnum:]._-]\+:'
2010     let commit = matchstr(a:rev,'^[^:]*')
2011   elseif a:commit =~# '^\d\=$'
2012     let local = matchstr(a:repo.head_ref(),'\<refs/heads/\zs.*')
2013     let commit = a:repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
2014     if commit ==# ''
2015       let commit = local
2016     endif
2017   else
2018     let commit = a:commit
2019   endif
2020   if a:type == 'tree'
2021     let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
2022   elseif a:type == 'blob'
2023     let url = root . '/blob/' . commit . '/' . path
2024     if a:line2 > 0 && a:line1 == a:line2
2025       let url .= '#L' . a:line1
2026     elseif a:line2 > 0
2027       let url .= '#L' . a:line1 . '-' . a:line2
2028     endif
2029   elseif a:type == 'tag'
2030     let commit = matchstr(getline(3),'^tag \zs.*')
2031     let url = root . '/tree/' . commit
2032   else
2033     let url = root . '/commit/' . commit
2034   endif
2035   return url
2036 endfunction
2038 function! s:instaweb_url(repo,rev,commit,path,type,...) abort
2039   let output = a:repo.git_chomp('instaweb','-b','unknown')
2040   if output =~# 'http://'
2041     let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:repo.dir(),':t')
2042   else
2043     return ''
2044   endif
2045   if a:path =~# '^\.git/refs/.'
2046     return root . ';a=shortlog;h=' . matchstr(a:path,'^\.git/\zs.*')
2047   elseif a:path =~# '^\.git\>'
2048     return root
2049   endif
2050   let url = root
2051   if a:commit =~# '^\x\{40\}$'
2052     if a:type ==# 'commit'
2053       let url .= ';a=commit'
2054     endif
2055     let url .= ';h=' . a:repo.rev_parse(a:commit . (a:path == '' ? '' : ':' . a:path))
2056   else
2057     if a:type ==# 'blob'
2058       let tmp = tempname()
2059       silent execute 'write !'.a:repo.git_command('hash-object','-w','--stdin').' > '.tmp
2060       let url .= ';h=' . readfile(tmp)[0]
2061     else
2062       try
2063         let url .= ';h=' . a:repo.rev_parse((a:commit == '' ? 'HEAD' : ':' . a:commit) . ':' . a:path)
2064       catch /^fugitive:/
2065         call s:throw('fugitive: cannot browse uncommitted file')
2066       endtry
2067     endif
2068     let root .= ';hb=' . matchstr(a:repo.head_ref(),'[^ ]\+$')
2069   endif
2070   if a:path !=# ''
2071     let url .= ';f=' . a:path
2072   endif
2073   if a:0 && a:1
2074     let url .= '#l' . a:1
2075   endif
2076   return url
2077 endfunction
2079 " Section: File access
2081 function! s:ReplaceCmd(cmd,...) abort
2082   let fn = expand('%:p')
2083   let tmp = tempname()
2084   let prefix = ''
2085   try
2086     if a:0 && a:1 != ''
2087       if s:winshell()
2088         let old_index = $GIT_INDEX_FILE
2089         let $GIT_INDEX_FILE = a:1
2090       else
2091         let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
2092       endif
2093     endif
2094     if s:winshell()
2095       let cmd_escape_char = &shellxquote == '(' ?  '^' : '^^^'
2096       call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').' > '.tmp.'"')
2097     else
2098       call system(' ('.prefix.a:cmd.' > '.tmp.') ')
2099     endif
2100   finally
2101     if exists('old_index')
2102       let $GIT_INDEX_FILE = old_index
2103     endif
2104   endtry
2105   silent exe 'keepalt file '.tmp
2106   try
2107     silent edit!
2108   finally
2109     try
2110       silent exe 'keepalt file '.s:fnameescape(fn)
2111     catch /^Vim\%((\a\+)\)\=:E302/
2112     endtry
2113     call delete(tmp)
2114     if fnamemodify(bufname('$'), ':p') ==# tmp
2115       silent execute 'bwipeout '.bufnr('$')
2116     endif
2117     silent exe 'doau BufReadPost '.s:fnameescape(fn)
2118   endtry
2119 endfunction
2121 function! s:BufReadIndex() abort
2122   if !exists('b:fugitive_display_format')
2123     let b:fugitive_display_format = filereadable(expand('%').'.lock')
2124   endif
2125   let b:fugitive_display_format = b:fugitive_display_format % 2
2126   let b:fugitive_type = 'index'
2127   try
2128     let b:git_dir = s:repo().dir()
2129     setlocal noro ma nomodeline
2130     if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
2131       let index = ''
2132     else
2133       let index = expand('%:p')
2134     endif
2135     if b:fugitive_display_format
2136       call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
2137       set ft=git nospell
2138     else
2139       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
2140       let dir = getcwd()
2141       if fugitive#git_version() =~# '^0\|^1\.[1-7]\.'
2142         let cmd = s:repo().git_command('status')
2143       else
2144         let cmd = s:repo().git_command(
2145               \ '-c', 'status.displayCommentPrefix=true',
2146               \ '-c', 'color.status=false',
2147               \ '-c', 'status.short=false',
2148               \ 'status')
2149       endif
2150       try
2151         execute cd.'`=s:repo().tree()`'
2152         call s:ReplaceCmd(cmd, index)
2153       finally
2154         execute cd.'`=dir`'
2155       endtry
2156       set ft=gitcommit
2157       set foldtext=fugitive#foldtext()
2158     endif
2159     setlocal ro noma nomod noswapfile
2160     if &bufhidden ==# ''
2161       setlocal bufhidden=delete
2162     endif
2163     call s:JumpInit()
2164     nunmap   <buffer>          P
2165     nunmap   <buffer>          ~
2166     nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
2167     nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
2168     nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
2169     xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
2170     nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
2171     nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
2172     nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
2173     nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>
2174     nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
2175     nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
2176     nnoremap <buffer> <silent> cva :<C-U>Gcommit --amend --verbose<CR>
2177     nnoremap <buffer> <silent> cvc :<C-U>Gcommit --verbose<CR>
2178     nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2179     nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2180     nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2181     nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2182     nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
2183     nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2184     nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2185     xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2186     nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
2187     nnoremap <buffer> <silent> r :<C-U>edit<CR>
2188     nnoremap <buffer> <silent> R :<C-U>edit<CR>
2189     nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
2190     nnoremap <buffer> <silent> g?   :help fugitive-:Gstatus<CR>
2191     nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
2192   catch /^fugitive:/
2193     return 'echoerr v:errmsg'
2194   endtry
2195 endfunction
2197 function! s:FileRead() abort
2198   try
2199     let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
2200     let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
2201     let hash = repo.rev_parse(path)
2202     if path =~ '^:'
2203       let type = 'blob'
2204     else
2205       let type = repo.git_chomp('cat-file','-t',hash)
2206     endif
2207     " TODO: use count, if possible
2208     return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
2209   catch /^fugitive:/
2210     return 'echoerr v:errmsg'
2211   endtry
2212 endfunction
2214 function! s:BufReadIndexFile() abort
2215   try
2216     let b:fugitive_type = 'blob'
2217     let b:git_dir = s:repo().dir()
2218     try
2219       call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
2220     finally
2221       if &bufhidden ==# ''
2222         setlocal bufhidden=delete
2223       endif
2224       setlocal noswapfile
2225     endtry
2226     return ''
2227   catch /^fugitive: rev-parse/
2228     silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
2229     return ''
2230   catch /^fugitive:/
2231     return 'echoerr v:errmsg'
2232   endtry
2233 endfunction
2235 function! s:BufWriteIndexFile() abort
2236   let tmp = tempname()
2237   try
2238     let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
2239     let stage = matchstr(expand('<amatch>'),'//\zs\d')
2240     silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
2241     let sha1 = readfile(tmp)[0]
2242     let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
2243     if old_mode == ''
2244       let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
2245     endif
2246     let info = old_mode.' '.sha1.' '.stage."\t".path
2247     call writefile([info],tmp)
2248     if s:winshell()
2249       let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
2250     else
2251       let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
2252     endif
2253     if v:shell_error == 0
2254       setlocal nomodified
2255       silent execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
2256       call fugitive#reload_status()
2257       return ''
2258     else
2259       return 'echoerr '.string('fugitive: '.error)
2260     endif
2261   finally
2262     call delete(tmp)
2263   endtry
2264 endfunction
2266 function! s:BufReadObject() abort
2267   try
2268     setlocal noro ma
2269     let b:git_dir = s:repo().dir()
2270     let hash = s:buffer().sha1()
2271     if !exists("b:fugitive_type")
2272       let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
2273     endif
2274     if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
2275       return "echoerr 'fugitive: unrecognized git type'"
2276     endif
2277     let firstline = getline('.')
2278     if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
2279       let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
2280     endif
2282     if b:fugitive_type !=# 'blob'
2283       setlocal nomodeline
2284     endif
2286     let pos = getpos('.')
2287     silent keepjumps %delete_
2288     setlocal endofline
2290     try
2291       if b:fugitive_type ==# 'tree'
2292         let b:fugitive_display_format = b:fugitive_display_format % 2
2293         if b:fugitive_display_format
2294           call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
2295         else
2296           call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
2297         endif
2298       elseif b:fugitive_type ==# 'tag'
2299         let b:fugitive_display_format = b:fugitive_display_format % 2
2300         if b:fugitive_display_format
2301           call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2302         else
2303           call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
2304         endif
2305       elseif b:fugitive_type ==# 'commit'
2306         let b:fugitive_display_format = b:fugitive_display_format % 2
2307         if b:fugitive_display_format
2308           call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2309         else
2310           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))
2311           keepjumps call search('^parent ')
2312           if getline('.') ==# 'parent '
2313             silent keepjumps delete_
2314           else
2315             silent keepjumps s/\%(^parent\)\@<! /\rparent /ge
2316           endif
2317           keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2318           if lnum
2319             silent keepjumps delete_
2320           end
2321           keepjumps 1
2322         endif
2323       elseif b:fugitive_type ==# 'blob'
2324         call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2325         setlocal nomodeline
2326       endif
2327     finally
2328       keepjumps call setpos('.',pos)
2329       setlocal ro noma nomod noswapfile
2330       if &bufhidden ==# ''
2331         setlocal bufhidden=delete
2332       endif
2333       if b:fugitive_type !=# 'blob'
2334         setlocal filetype=git foldmethod=syntax
2335         nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
2336         nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
2337       else
2338         call s:JumpInit()
2339       endif
2340     endtry
2342     return ''
2343   catch /^fugitive:/
2344     return 'echoerr v:errmsg'
2345   endtry
2346 endfunction
2348 augroup fugitive_files
2349   autocmd!
2350   autocmd BufReadCmd  index{,.lock}
2351         \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
2352         \   exe s:BufReadIndex() |
2353         \ elseif filereadable(expand('<amatch>')) |
2354         \   read <amatch> |
2355         \   1delete |
2356         \ endif
2357   autocmd FileReadCmd fugitive://**//[0-3]/**          exe s:FileRead()
2358   autocmd BufReadCmd  fugitive://**//[0-3]/**          exe s:BufReadIndexFile()
2359   autocmd BufWriteCmd fugitive://**//[0-3]/**          exe s:BufWriteIndexFile()
2360   autocmd BufReadCmd  fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
2361   autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
2362   autocmd FileType git
2363         \ if exists('b:git_dir') |
2364         \  call s:JumpInit() |
2365         \ endif
2366 augroup END
2368 " Section: Temp files
2370 if !exists('s:temp_files')
2371   let s:temp_files = {}
2372 endif
2374 augroup fugitive_temp
2375   autocmd!
2376   autocmd BufNewFile,BufReadPost *
2377         \ if has_key(s:temp_files,tolower(expand('<afile>:p'))) |
2378         \   let b:git_dir = s:temp_files[tolower(expand('<afile>:p'))].dir |
2379         \   let b:git_type = 'temp' |
2380         \   let b:git_args = s:temp_files[tolower(expand('<afile>:p'))].args |
2381         \   call fugitive#detect(expand('<afile>:p')) |
2382         \   setlocal bufhidden=delete |
2383         \   nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>|
2384         \ endif
2385 augroup END
2387 " Section: Go to file
2389 function! s:JumpInit() abort
2390   nnoremap <buffer> <silent> <CR>    :<C-U>exe <SID>GF("edit")<CR>
2391   if !&modifiable
2392     nnoremap <buffer> <silent> o     :<C-U>exe <SID>GF("split")<CR>
2393     nnoremap <buffer> <silent> S     :<C-U>exe <SID>GF("vsplit")<CR>
2394     nnoremap <buffer> <silent> O     :<C-U>exe <SID>GF("tabedit")<CR>
2395     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>
2396     nnoremap <buffer> <silent> P     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
2397     nnoremap <buffer> <silent> ~     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
2398     nnoremap <buffer> <silent> C     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2399     nnoremap <buffer> <silent> cc    :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2400     nnoremap <buffer> <silent> co    :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
2401     nnoremap <buffer> <silent> cS    :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
2402     nnoremap <buffer> <silent> cO    :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
2403     nnoremap <buffer> <silent> cP    :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
2404     nnoremap <buffer>          .     : <C-R>=fnameescape(<SID>recall())<CR><Home>
2405   endif
2406 endfunction
2408 function! s:GF(mode) abort
2409   try
2410     let buffer = s:buffer()
2411     let myhash = buffer.sha1()
2412     if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
2413       let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
2414     endif
2416     if buffer.type('tree')
2417       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2418       if showtree && line('.') == 1
2419         return ""
2420       elseif showtree && line('.') > 2
2421         return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$',''))
2422       elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
2423         return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
2424       endif
2426     elseif buffer.type('blob')
2427       let ref = expand("<cfile>")
2428       try
2429         let sha1 = buffer.repo().rev_parse(ref)
2430       catch /^fugitive:/
2431       endtry
2432       if exists('sha1')
2433         return s:Edit(a:mode,0,ref)
2434       endif
2436     else
2438       " Index
2439       if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
2440         let ref = matchstr(getline('.'),'\x\{40\}')
2441         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
2442         return s:Edit(a:mode,0,file)
2444       elseif getline('.') =~# '^#\trenamed:.* -> '
2445         let file = '/'.matchstr(getline('.'),' -> \zs.*')
2446         return s:Edit(a:mode,0,file)
2447       elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
2448         let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
2449         return s:Edit(a:mode,0,file)
2450       elseif getline('.') =~# '^#\t.'
2451         let file = '/'.matchstr(getline('.'),'#\t\zs.*')
2452         return s:Edit(a:mode,0,file)
2453       elseif getline('.') =~# ': needs merge$'
2454         let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
2455         return s:Edit(a:mode,0,file).'|Gdiff'
2457       elseif getline('.') ==# '# Not currently on any branch.'
2458         return s:Edit(a:mode,0,'HEAD')
2459       elseif getline('.') =~# '^# On branch '
2460         let file = 'refs/heads/'.getline('.')[12:]
2461         return s:Edit(a:mode,0,file)
2462       elseif getline('.') =~# "^# Your branch .*'"
2463         let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
2464         return s:Edit(a:mode,0,file)
2465       endif
2467       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2469       if getline('.') =~# '^ref: '
2470         let ref = strpart(getline('.'),5)
2472       elseif getline('.') =~# '^commit \x\{40\}\>'
2473         let ref = matchstr(getline('.'),'\x\{40\}')
2474         return s:Edit(a:mode,0,ref)
2476       elseif getline('.') =~# '^parent \x\{40\}\>'
2477         let ref = matchstr(getline('.'),'\x\{40\}')
2478         let line = line('.')
2479         let parent = 0
2480         while getline(line) =~# '^parent '
2481           let parent += 1
2482           let line -= 1
2483         endwhile
2484         return s:Edit(a:mode,0,ref)
2486       elseif getline('.') =~ '^tree \x\{40\}$'
2487         let ref = matchstr(getline('.'),'\x\{40\}')
2488         if s:repo().rev_parse(myhash.':') == ref
2489           let ref = myhash.':'
2490         endif
2491         return s:Edit(a:mode,0,ref)
2493       elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
2494         let ref = matchstr(getline('.'),'\x\{40\}')
2495         let type = matchstr(getline(line('.')+1),'type \zs.*')
2497       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
2498         return ''
2500       elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
2501         let ref = matchstr(getline('.'),'\x\{40\}')
2502         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
2504       elseif getline('.') =~# '^[+-]\{3\} [ab/]'
2505         let ref = getline('.')[4:]
2507       elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
2508         let type = getline('.')[0]
2509         let lnum = line('.') - 1
2510         let offset = -1
2511         while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
2512           if getline(lnum) =~# '^[ '.type.']'
2513             let offset += 1
2514           endif
2515           let lnum -= 1
2516         endwhile
2517         let offset += matchstr(getline(lnum), type.'\zs\d\+')
2518         let ref = getline(search('^'.type.'\{3\} [ab]/','bnW'))[4:-1]
2519         let dcmd = '+'.offset.'|normal! zv'
2520         let dref = ''
2522       elseif getline('.') =~# '^rename from '
2523         let ref = 'a/'.getline('.')[12:]
2524       elseif getline('.') =~# '^rename to '
2525         let ref = 'b/'.getline('.')[10:]
2527       elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2528         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2529         let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2530         let dcmd = 'Gdiff'
2532       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2533         let line = getline(line('.')-1)
2534         let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2535         let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2536         let dcmd = 'Gdiff!'
2538       elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
2539         let ref = getline('.')
2540       else
2541         let ref = ''
2542       endif
2544       if myhash ==# ''
2545         let ref = s:sub(ref,'^a/','HEAD:')
2546         let ref = s:sub(ref,'^b/',':0:')
2547         if exists('dref')
2548           let dref = s:sub(dref,'^a/','HEAD:')
2549         endif
2550       else
2551         let ref = s:sub(ref,'^a/',myhash.'^:')
2552         let ref = s:sub(ref,'^b/',myhash.':')
2553         if exists('dref')
2554           let dref = s:sub(dref,'^a/',myhash.'^:')
2555         endif
2556       endif
2558       if ref ==# '/dev/null'
2559         " Empty blob
2560         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
2561       endif
2563       if exists('dref')
2564         return s:Edit(a:mode,0,ref) . '|'.dcmd.' '.s:fnameescape(dref)
2565       elseif ref != ""
2566         return s:Edit(a:mode,0,ref)
2567       endif
2569     endif
2570     return ''
2571   catch /^fugitive:/
2572     return 'echoerr v:errmsg'
2573   endtry
2574 endfunction
2576 " Section: Statusline
2578 function! s:repo_head_ref() dict abort
2579   if !filereadable(self.dir('HEAD'))
2580     return ''
2581   endif
2582   return readfile(self.dir('HEAD'))[0]
2583 endfunction
2585 call s:add_methods('repo',['head_ref'])
2587 function! fugitive#statusline(...) abort
2588   if !exists('b:git_dir')
2589     return ''
2590   endif
2591   let status = ''
2592   if s:buffer().commit() != ''
2593     let status .= ':' . s:buffer().commit()[0:7]
2594   endif
2595   let status .= '('.fugitive#head(7).')'
2596   if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2597     return ',GIT'.status
2598   else
2599     return '[Git'.status.']'
2600   endif
2601 endfunction
2603 function! fugitive#head(...) abort
2604   if !exists('b:git_dir')
2605     return ''
2606   endif
2608   return s:repo().head(a:0 ? a:1 : 0)
2609 endfunction
2611 " Section: Folding
2613 function! fugitive#foldtext() abort
2614   if &foldmethod !=# 'syntax'
2615     return foldtext()
2616   elseif getline(v:foldstart) =~# '^diff '
2617     let [add, remove] = [-1, -1]
2618     let filename = ''
2619     for lnum in range(v:foldstart, v:foldend)
2620       if filename ==# '' && getline(lnum) =~# '^[+-]\{3\} [abciow12]/'
2621         let filename = getline(lnum)[6:-1]
2622       endif
2623       if getline(lnum) =~# '^+'
2624         let add += 1
2625       elseif getline(lnum) =~# '^-'
2626         let remove += 1
2627       elseif getline(lnum) =~# '^Binary '
2628         let binary = 1
2629       endif
2630     endfor
2631     if filename ==# ''
2632       let filename = matchstr(getline(v:foldstart), '^diff .\{-\} a/\zs.*\ze b/')
2633     endif
2634     if filename ==# ''
2635       let filename = getline(v:foldstart)[5:-1]
2636     endif
2637     if exists('binary')
2638       return 'Binary: '.filename
2639     else
2640       return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
2641     endif
2642   elseif getline(v:foldstart) =~# '^# .*:$'
2643     let lines = getline(v:foldstart, v:foldend)
2644     call filter(lines, 'v:val =~# "^#\t"')
2645     cal map(lines,'s:sub(v:val, "^#\t%(modified: +|renamed: +)=", "")')
2646     cal map(lines,'s:sub(v:val, "^([[:alpha:] ]+): +(.*)", "\\2 (\\1)")')
2647     return getline(v:foldstart).' '.join(lines, ', ')
2648   endif
2649   return foldtext()
2650 endfunction
2652 augroup fugitive_foldtext
2653   autocmd!
2654   autocmd User Fugitive
2655         \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
2656         \    set foldtext=fugitive#foldtext() |
2657         \ endif
2658 augroup END