Extract function for getting current commit/ref
[vim-fugitive.git] / autoload / fugitive.vim
blob0c99f384d28d6bbfa970061aaf5aca55cfaa093e
1 " Location:     autoload/fugitive.vim
2 " Maintainer:   Tim Pope <http://tpo.pe/>
4 if exists('g:autoloaded_fugitive')
5   finish
6 endif
7 let g:autoloaded_fugitive = 1
9 if !exists('g:fugitive_git_executable')
10   let g:fugitive_git_executable = 'git'
11 endif
13 " Section: Utility
15 function! s:function(name) abort
16   return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
17 endfunction
19 function! s:sub(str,pat,rep) abort
20   return substitute(a:str,'\v\C'.a:pat,a:rep,'')
21 endfunction
23 function! s:gsub(str,pat,rep) abort
24   return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
25 endfunction
27 function! s:Uniq(list) abort
28   let i = 0
29   let seen = {}
30   while i < len(a:list)
31     let str = string(a:list[i])
32     if has_key(seen, str)
33       call remove(a:list, i)
34     else
35       let seen[str] = 1
36       let i += 1
37     endif
38   endwhile
39   return a:list
40 endfunction
42 function! s:winshell() abort
43   return &shell =~? 'cmd' || exists('+shellslash') && !&shellslash
44 endfunction
46 function! s:shellesc(arg) abort
47   if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
48     return a:arg
49   elseif s:winshell()
50     return '"'.s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"').'"'
51   else
52     return shellescape(a:arg)
53   endif
54 endfunction
56 function! s:fnameescape(file) abort
57   if exists('*fnameescape')
58     return fnameescape(a:file)
59   else
60     return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
61   endif
62 endfunction
64 function! s:tempname() abort
65   let temp = resolve(tempname())
66   if has('win32')
67     let temp = fnamemodify(fnamemodify(temp, ':h'), ':p').fnamemodify(temp, ':t')
68   endif
69   return temp
70 endfunction
72 function! s:throw(string) abort
73   let v:errmsg = 'fugitive: '.a:string
74   throw v:errmsg
75 endfunction
77 function! s:warn(str) abort
78   echohl WarningMsg
79   echomsg a:str
80   echohl None
81   let v:warningmsg = a:str
82 endfunction
84 function! s:Slash(path) abort
85   if s:winshell()
86     return tr(a:path, '\', '/')
87   else
88     return a:path
89   endif
90 endfunction
92 function! s:PlatformSlash(path) abort
93   if exists('+shellslash') && !&shellslash
94     return tr(a:path, '/', '\')
95   else
96     return a:path
97   endif
98 endfunction
100 function! s:cpath(path, ...) abort
101   if exists('+fileignorecase') && &fileignorecase
102     let path = s:PlatformSlash(tolower(a:path))
103   else
104     let path = s:PlatformSlash(a:path)
105   endif
106   return a:0 ? path ==# s:cpath(a:1) : path
107 endfunction
109 let s:executables = {}
111 function! s:executable(binary) abort
112   if !has_key(s:executables, a:binary)
113     let s:executables[a:binary] = executable(a:binary)
114   endif
115   return s:executables[a:binary]
116 endfunction
118 function! s:UserCommand() abort
119   return get(g:, 'fugitive_git_command', g:fugitive_git_executable)
120 endfunction
122 function! s:System(cmd) abort
123   try
124     return system(a:cmd)
125   catch /^Vim\%((\a\+)\)\=:E484:/
126     let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
127     call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
128     call map(opts, 'v:val."=".eval("&".v:val)')
129     call s:throw('failed to run `' . a:cmd . '` with ' . join(opts, ' '))
130   endtry
131 endfunction
133 function! s:Prepare(dir, ...) abort
134   if type(a:dir) == type([])
135     let args = ['--git-dir=' . (a:0 ? a:1 : get(b:, 'git_dir', ''))] + a:dir
136   else
137     let args = ['--git-dir=' . a:dir] + (a:000)
138   endif
139   return g:fugitive_git_executable . ' ' . join(map(args, 's:shellesc(v:val)'))
140 endfunction
142 let s:git_versions = {}
143 function! fugitive#GitVersion(...) abort
144   if !has_key(s:git_versions, g:fugitive_git_executable)
145     let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), "\\S\\+\\ze\n")
146   endif
147   return s:git_versions[g:fugitive_git_executable]
148 endfunction
150 let s:commondirs = {}
151 function! fugitive#CommonDir(dir) abort
152   if empty(a:dir)
153     return ''
154   endif
155   if !has_key(s:commondirs, a:dir)
156     if getfsize(a:dir . '/HEAD') < 10
157       let s:commondirs[a:dir] = ''
158     elseif filereadable(a:dir . '/commondir')
159       let dir = get(readfile(a:dir . '/commondir', 1), 0, '')
160       if dir =~# '^/\|^\a:/'
161         let s:commondirs[a:dir] = dir
162       else
163         let s:commondirs[a:dir] = simplify(a:dir . '/' . dir)
164       endif
165     else
166       let s:commondirs[a:dir] = a:dir
167     endif
168   endif
169   return s:commondirs[a:dir]
170 endfunction
172 function! s:Tree(...) abort
173   return FugitiveTreeForGitDir(a:0 ? a:1 : get(b:, 'git_dir', ''))
174 endfunction
176 function! s:TreeChomp(...) abort
177   let args = copy(type(a:1) == type([]) ? a:1 : a:000)
178   let dir = a:0 > 1 && type(a:1) == type([]) ? a:2 : b:git_dir
179   let tree = s:Tree(dir)
180   let pre = ''
181   if empty(tree)
182     let args = ['--git-dir=' . dir] + args
183   elseif s:cpath(tree) !=# s:cpath(getcwd())
184     if fugitive#GitVersion() =~# '^[01]\.'
185       let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? ' & ' : '; ')
186     else
187       let args = ['-C', tree] + args
188     endif
189   endif
190   return s:sub(s:System(pre . g:fugitive_git_executable . ' ' .
191         \ join(map(args, 's:shellesc(v:val)'))), '\n$', '')
192 endfunction
194 function! fugitive#Prepare(cmd, ...) abort
195   let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
196   let tree = s:Tree(dir)
197   let args = type(a:cmd) == type([]) ? join(map(copy(a:cmd), 's:shellesc(v:val)')) : a:cmd
198   let pre = ''
199   if empty(tree)
200     let args = s:shellesc('--git-dir=' . dir) . ' ' . args
201   elseif fugitive#GitVersion() =~# '^[01]\.'
202     let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? ' & ' : '; ')
203   else
204     let args = '-C ' . s:shellesc(tree) . ' ' . args
205   endif
206   return pre . g:fugitive_git_executable . ' ' . args
207 endfunction
209 function! fugitive#Head(...) abort
210   let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
211   if empty(dir) || !filereadable(dir . '/HEAD')
212     return ''
213   endif
214   let head = readfile(dir . '/HEAD')[0]
215   if head =~# '^ref: '
216     return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
217   elseif head =~# '^\x\{40\}$'
218     let len = a:0 ? a:1 : 0
219     return len < 0 ? head : len ? head[0:len-1] : ''
220   else
221     return ''
222   endif
223 endfunction
225 function! fugitive#RevParse(rev, ...) abort
226   let hash = system(s:Prepare(a:0 ? a:1 : b:git_dir, 'rev-parse', '--verify', a:rev))[0:-2]
227   if !v:shell_error && hash =~# '^\x\{40\}$'
228     return hash
229   endif
230   call s:throw('rev-parse '.a:rev.': '.hash)
231 endfunction
233 function! fugitive#Config(name, ...) abort
234   let cmd = s:Prepare(a:0 ? a:1 : get(b:, 'git_dir', ''), 'config', '--get', a:name)
235   let out = matchstr(system(cmd), "[^\r\n]*")
236   return v:shell_error ? '' : out
237 endfunction
239 function! s:Remote(dir) abort
240   let head = FugitiveHead(0, a:dir)
241   let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
242   let i = 10
243   while remote ==# '.' && i > 0
244     let head = matchstr(fugitive#Config('branch.' . head . '.merge'), 'refs/heads/\zs.*')
245     let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
246     let i -= 1
247   endwhile
248   return remote =~# '^\.\=$' ? 'origin' : remote
249 endfunction
251 function! fugitive#RemoteUrl(...) abort
252   let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
253   let remote = !a:0 || a:1 =~# '^\.\=$' ? s:Remote(dir) : a:1
254   if fugitive#GitVersion() =~# '^[01]\.\|^2\.[0-6]\.'
255     return fugitive#Config('remote.' . remote . '.url')
256   endif
257   let cmd = s:Prepare(dir, 'remote', 'get-url', remote)
258   let out = substitute(system(cmd), "\n$", '', '')
259   return v:shell_error ? '' : out
260 endfunction
262 function! s:map(mode, lhs, rhs, ...) abort
263   let flags = (a:0 ? a:1 : '') . (a:rhs =~# '<Plug>' ? '' : '<script>')
264   let head = a:lhs
265   let tail = ''
266   let keys = get(g:, a:mode.'remap', {})
267   if type(keys) == type([])
268     return
269   endif
270   while !empty(head)
271     if has_key(keys, head)
272       let head = keys[head]
273       if empty(head)
274         return
275       endif
276       break
277     endif
278     let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
279     let head = substitute(head, '<[^<>]*>$\|.$', '', '')
280   endwhile
281   if flags !~# '<unique>' || empty(mapcheck(head.tail, a:mode))
282     exe a:mode.'map <buffer>' flags head.tail a:rhs
283     if a:0 > 1
284       let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
285             \ '|sil! exe "' . a:mode . 'unmap <buffer> ' . head.tail . '"'
286     endif
287   endif
288 endfunction
290 function! s:add_methods(namespace, method_names) abort
291   for name in a:method_names
292     let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
293   endfor
294 endfunction
296 let s:commands = []
297 function! s:command(definition) abort
298   let s:commands += [a:definition]
299 endfunction
301 function! s:define_commands() abort
302   for command in s:commands
303     exe 'command! -buffer '.command
304   endfor
305 endfunction
307 let s:abstract_prototype = {}
309 " Section: Repository
311 let s:repo_prototype = {}
312 let s:repos = {}
314 function! fugitive#repo(...) abort
315   let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : FugitiveExtractGitDir(expand('%:p')))
316   if dir !=# ''
317     if has_key(s:repos, dir)
318       let repo = get(s:repos, dir)
319     else
320       let repo = {'git_dir': dir}
321       let s:repos[dir] = repo
322     endif
323     return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
324   endif
325   call s:throw('not a git repository: '.expand('%:p'))
326 endfunction
328 function! s:repo_dir(...) dict abort
329   return join([self.git_dir]+a:000,'/')
330 endfunction
332 function! s:repo_tree(...) dict abort
333   let dir = s:Tree(self.git_dir)
334   if dir ==# ''
335     call s:throw('no work tree')
336   else
337     return join([dir]+a:000,'/')
338   endif
339 endfunction
341 function! s:repo_bare() dict abort
342   if self.dir() =~# '/\.git$'
343     return 0
344   else
345     return s:Tree(self.git_dir) ==# ''
346   endif
347 endfunction
349 function! s:repo_route(object) dict abort
350   return fugitive#Route(a:object, self.git_dir)
351 endfunction
353 function! s:repo_translate(rev) dict abort
354   return s:Slash(s:Generate(a:rev, self.git_dir))
355 endfunction
357 function! s:repo_head(...) dict abort
358   return fugitive#Head(a:0 ? a:1 : 0, self.git_dir)
359 endfunction
361 call s:add_methods('repo',['dir','tree','bare','route','translate','head'])
363 function! s:repo_git_command(...) dict abort
364   let git = s:UserCommand() . ' --git-dir='.s:shellesc(self.git_dir)
365   return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
366 endfunction
368 function! s:repo_git_chomp(...) dict abort
369   return s:sub(s:System(s:Prepare(a:000, self.git_dir)), '\n$', '')
370 endfunction
372 function! s:repo_git_chomp_in_tree(...) dict abort
373   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
374   let dir = getcwd()
375   try
376     execute cd s:fnameescape(self.tree())
377     return call(self.git_chomp, a:000, self)
378   finally
379     execute cd s:fnameescape(dir)
380   endtry
381 endfunction
383 function! s:repo_rev_parse(rev) dict abort
384   return fugitive#RevParse(a:rev, self.git_dir)
385 endfunction
387 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
389 function! s:repo_superglob(base) dict abort
390   return map(fugitive#Complete(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
391 endfunction
393 call s:add_methods('repo',['superglob'])
395 function! s:repo_config(name) dict abort
396   return fugitive#Config(a:name, self.git_dir)
397 endfunction
399 function! s:repo_user() dict abort
400   let username = self.config('user.name')
401   let useremail = self.config('user.email')
402   return username.' <'.useremail.'>'
403 endfunction
405 call s:add_methods('repo',['config', 'user'])
407 " Section: Buffer
409 function! s:DirCommitFile(path) abort
410   let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40\}\|[0-3]\)\(/.*\)\=$')
411   if empty(vals)
412     return ['', '', '']
413   endif
414   return vals[1:3]
415 endfunction
417 function! s:DirRev(url) abort
418   let [dir, commit, file] = s:DirCommitFile(a:url)
419   return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
420 endfunction
422 function! s:Owner(path, ...) abort
423   let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
424   if empty(dir)
425     return ''
426   endif
427   let [pdir, commit, file] = s:DirCommitFile(a:path)
428   if s:cpath(dir, pdir) && commit =~# '^\x\{40\}$'
429     return commit
430   endif
431   let path = fnamemodify(a:path, ':p')
432   if s:cpath(dir . '/', path[0 : len(dir)]) && a:path =~# 'HEAD$'
433     return strpart(path, len(dir) + 1)
434   endif
435   let refs = fugitive#CommonDir(dir) . '/refs'
436   if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
437     return strpart(path, len(refs) - 4)
438   endif
439   return ''
440 endfunction
442 function! fugitive#Real(url) abort
443   if empty(a:url)
444     return ''
445   endif
446   let [dir, commit, file] = s:DirCommitFile(a:url)
447   if len(dir)
448     let tree = s:Tree(dir)
449     return s:PlatformSlash((len(tree) ? tree : dir) . file)
450   endif
451   let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
452   if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
453     let url = {pre}Real(a:url)
454   else
455     let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
456   endif
457   return s:PlatformSlash(empty(url) ? a:url : url)
458 endfunction
460 function! fugitive#Path(url, ...) abort
461   if !a:0 || empty(a:url)
462     return fugitive#Real(a:url)
463   endif
464   let url = s:Slash(fnamemodify(a:url, ':p'))
465   if url =~# '/$' && s:Slash(a:url) !~# '/$'
466     let url = url[0:-2]
467   endif
468   let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
469   let tree = s:Tree(dir)
470   let [argdir, commit, file] = s:DirCommitFile(a:url)
471   if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
472     let file = ''
473   elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
474     let file = '/.git'.url[strlen(dir) : -1]
475   elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
476     let file = url[len(tree) : -1]
477   elseif s:cpath(url) ==# s:cpath(tree) || len(argdir) && empty(file)
478     let file = '/'
479   endif
480   if empty(file) && a:1 =~# '^$\|^[.:]/$'
481     return s:Slash(fugitive#Real(a:url))
482   endif
483   return substitute(file, '^/', a:1, '')
484 endfunction
486 function! fugitive#Route(object, ...) abort
487   if a:object =~# '^[~$]'
488     let prefix = matchstr(a:object, '^[~$]\i*')
489     let owner = expand(prefix)
490     return s:PlatformSlash((len(owner) ? owner : prefix) . strpart(a:object, len(prefix)))
491   elseif s:Slash(a:object) =~# '^\%(\a\a\+:\)\=\%(a:\)\=/'
492     return s:PlatformSlash(a:object)
493   endif
494   let rev = substitute(a:object, '[:/]\zs\.\%(/\+\|$\)', '', 'g')
495   let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
496   let tree = s:Tree(dir)
497   let base = len(tree) ? tree : 'fugitive://' . dir . '//0'
498   if rev =~# '^\%(\./\)\=\.git$' && empty(tree)
499     let f = dir
500   elseif rev =~# '^\%(\./\)\=\.git/'
501     let f = substitute(rev, '\C^\%(\./\)\=\.git', '', '')
502     let cdir = fugitive#CommonDir(dir)
503     if cdir !=# dir && (f =~# '^/\%(config\|info\|hooks\|objects\|refs\|worktrees\)' || !filereadable(f) && filereadable(cdir . f))
504       let f = cdir . f
505     else
506       let f = dir . f
507     endif
508   elseif rev =~# '^$\|^:/$'
509     let f = base
510   elseif rev =~# '^\.\%(/\|$\)'
511     let f = base . rev[1:-1]
512   elseif rev =~# '^:[0-3]:/\@!'
513     let f = 'fugitive://' . dir . '//' . rev[1] . '/' . rev[3:-1]
514   elseif rev ==# ':'
515     if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && s:cpath(fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(dir)]) ==# s:cpath(dir . '/') && filereadable($GIT_INDEX_FILE)
516       let f = fnamemodify($GIT_INDEX_FILE, ':p')
517     else
518       let f = dir . '/index'
519     endif
520   elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
521     let f = base . '/' . matchstr(rev, ')\zs.*')
522   elseif rev =~# '^:/\@!'
523     let f = 'fugitive://' . dir . '//0/' . rev[1:-1]
524   else
525     if rev =~# 'HEAD$\|^refs/' && rev !~# ':'
526       let cdir = rev =~# '^refs/' ? fugitive#CommonDir(dir) : dir
527       if filereadable(cdir . '/' . rev)
528         let f = simplify(cdir . '/' . rev)
529       endif
530     endif
531     if !exists('f')
532       let commit = substitute(matchstr(rev,'^[^:]\+\|^:.*'), '^@\%($|[^~]\)\@=', 'HEAD', '')
533       let file = substitute(matchstr(rev, '^[^:]\+\zs:.*'), '^:', '/', '')
534       if commit !~# '^[0-9a-f]\{40\}$'
535         let commit = system(s:Prepare(dir, 'rev-parse', '--verify', commit))[0:-2]
536         let commit = v:shell_error ? '' : commit
537       endif
538       if len(commit)
539         let f = 'fugitive://' . dir . '//' . commit . file
540       else
541         let f = base . '/' . rev
542       endif
543     endif
544   endif
545   return s:PlatformSlash(f)
546 endfunction
548 function! s:Generate(rev, ...) abort
549   let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
550   let tree = s:Tree(dir)
551   let object = a:rev
552   if a:rev =~# '^/' && len(tree) && getftime(tree . a:rev) >= 0 && getftime(a:rev) < 0 || a:rev =~# '^/\.git\%(/\|$\)'
553     let object = '.' . object
554   endif
555   return fugitive#Route(object, dir)
556 endfunction
558 function! s:RemoveDot(path, ...) abort
559   if a:path !~# '^\./'
560     return a:path
561   endif
562   let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
563   let cdir = fugitive#CommonDir(dir)
564   if len(filter(['', '/tags', '/heads', '/remotes'], 'getftime(cdir . "/refs" . v:val . a:path[1:-1]) >= 0')) ||
565         \ a:path =~# 'HEAD$' && filereadable(dir . a:path[1:-1]) ||
566         \ a:path =~# '^\./refs/' && filereadable(cdir . a:path[1:-1])
567     return a:path
568   endif
569   return a:path[2:-1]
570 endfunction
572 function! fugitive#Object(...) abort
573   let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
574   let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
575   if s:cpath(dir) !=# s:cpath(fdir)
576     let rev = ''
577   endif
578   let tree = s:Tree(dir)
579   if empty(rev) && empty(tree)
580   elseif empty(rev)
581     let rev = fugitive#Path(a:0 ? a:1 : @%, './', dir)
582     let cdir = fugitive#CommonDir(dir)
583     if rev =~# '^\./\.git/refs/\%(tags\|heads\|remotes\)/.\|^\./\.git/\w*HEAD$'
584       let rev = rev[7:-1]
585     elseif s:cpath(cdir . '/refs/', rev[0 : len(cdir)])
586       let rev = strpart(rev, len(cdir)+1)
587     elseif rev =~# '^\./.git\%(/\|$\)'
588       return fnamemodify(a:0 ? a:1 : @%, ':p')
589     endif
590   endif
591   if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
592     return rev
593   else
594     return tree . rev[1:-1]
595   endif
596 endfunction
598 function! s:Expand(rev) abort
599   if a:rev =~# '^:[0-3]$'
600     let file = a:rev . s:Relative(':')
601   elseif a:rev =~# '^-'
602     let file = 'HEAD^{}' . a:rev[1:-1] . s:Relative(':')
603   elseif a:rev =~# '^@{'
604     let file = 'HEAD' . a:rev. s:Relative(':')
605   elseif a:rev =~# '^[~^]/\@!'
606     let commit = substitute(s:DirCommitFile(@%)[1], '^\d\=$', 'HEAD', '')
607     let file = commit . a:rev . s:Relative(':')
608   else
609     let file = a:rev
610   endif
611   return s:sub(substitute(file,
612         \ '\([%#]\)$\|\\\([[:punct:]]\)','\=len(submatch(2)) ? submatch(2) : fugitive#Path(expand(submatch(1)), "./", dir)','g'),
613         \ '\.\@<=/$','')
614 endfunction
616 function! s:ShellExpand(cmd) abort
617   return substitute(a:cmd, '\\\@<![%#]:\@!', '\=s:RemoveDot(fugitive#Path(expand(submatch(0)), "./"))', 'g')
618 endfunction
620 let s:trees = {}
621 let s:indexes = {}
622 function! s:TreeInfo(dir, commit) abort
623   let git = s:Prepare(a:dir)
624   if a:commit =~# '^:\=[0-3]$'
625     let index = get(s:indexes, a:dir, [])
626     let newftime = getftime(a:dir . '/index')
627     if get(index, 0, -1) < newftime
628       let out = system(git . ' ls-files --stage')
629       let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
630       if v:shell_error
631         return [{}, -1]
632       endif
633       for line in split(out, "\n")
634         let [info, filename] = split(line, "\t")
635         let [mode, sha, stage] = split(info, '\s\+')
636         let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
637         while filename =~# '/'
638           let filename = substitute(filename, '/[^/]*$', '', '')
639           let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
640         endwhile
641       endfor
642     endif
643     return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
644   elseif a:commit =~# '^\x\{40\}$'
645     if !has_key(s:trees, a:dir)
646       let s:trees[a:dir] = {}
647     endif
648     if !has_key(s:trees[a:dir], a:commit)
649       let ftime = +system(git . ' log -1 --pretty=format:%ct ' . a:commit)
650       if v:shell_error
651         let s:trees[a:dir][a:commit] = [{}, -1]
652         return s:trees[a:dir][a:commit]
653       endif
654       let s:trees[a:dir][a:commit] = [{}, +ftime]
655       let out = system(git . ' ls-tree -rtl --full-name ' . a:commit)
656       if v:shell_error
657         return s:trees[a:dir][a:commit]
658       endif
659       for line in split(out, "\n")
660         let [info, filename] = split(line, "\t")
661         let [mode, type, sha, size] = split(info, '\s\+')
662         let s:trees[a:dir][a:commit][0][filename] = [ftime, mode, type, sha, +size, filename]
663       endfor
664     endif
665     return s:trees[a:dir][a:commit]
666   endif
667   return [{}, -1]
668 endfunction
670 function! s:PathInfo(url) abort
671   let [dir, commit, file] = s:DirCommitFile(a:url)
672   if empty(dir) || !get(g:, 'fugitive_file_api', 1)
673     return [-1, '000000', '', '', -1]
674   endif
675   let path = substitute(file[1:-1], '/*$', '', '')
676   let [tree, ftime] = s:TreeInfo(dir, commit)
677   let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
678   if empty(entry) || file =~# '/$' && entry[1] !=# 'tree'
679     return [-1, '000000', '', '', -1]
680   else
681     return entry
682   endif
683 endfunction
685 function! fugitive#simplify(url) abort
686   let [dir, commit, file] = s:DirCommitFile(a:url)
687   if empty(dir)
688     return ''
689   endif
690   if file =~# '/\.\.\%(/\|$\)'
691     let tree = s:Tree(dir)
692     if len(tree)
693       let path = simplify(tree . file)
694       if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
695         return s:PlatformSlash(path)
696       endif
697     endif
698   endif
699   return s:PlatformSlash('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
700 endfunction
702 function! fugitive#resolve(url) abort
703   let url = fugitive#simplify(a:url)
704   if url =~? '^fugitive:'
705     return url
706   else
707     return resolve(url)
708   endif
709 endfunction
711 function! fugitive#getftime(url) abort
712   return s:PathInfo(a:url)[0]
713 endfunction
715 function! fugitive#getfsize(url) abort
716   let entry = s:PathInfo(a:url)
717   if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
718     let dir = s:DirCommitFile(a:url)[0]
719     let size = +system(s:Prepare(dir, 'cat-file', '-s', entry[3]))
720     let entry[4] = v:shell_error ? -1 : size
721   endif
722   return entry[4]
723 endfunction
725 function! fugitive#getftype(url) abort
726   return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
727 endfunction
729 function! fugitive#filereadable(url) abort
730   return s:PathInfo(a:url)[2] ==# 'blob'
731 endfunction
733 function! fugitive#filewritable(url) abort
734   let [dir, commit, file] = s:DirCommitFile(a:url)
735   if commit !~# '^\d$' || !filewritable(dir . '/index')
736     return 0
737   endif
738   return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
739 endfunction
741 function! fugitive#isdirectory(url) abort
742   return s:PathInfo(a:url)[2] ==# 'tree'
743 endfunction
745 function! fugitive#getfperm(url) abort
746   let [dir, commit, file] = s:DirCommitFile(a:url)
747   let perm = getfperm(dir)
748   let fperm = s:PathInfo(a:url)[1]
749   if fperm ==# '040000'
750     let fperm = '000755'
751   endif
752   if fperm !~# '[15]'
753     let perm = tr(perm, 'x', '-')
754   endif
755   if fperm !~# '[45]$'
756     let perm = tr(perm, 'rw', '--')
757   endif
758   if commit !~# '^\d$'
759     let perm = tr(perm, 'w', '-')
760   endif
761   return perm ==# '---------' ? '' : perm
762 endfunction
764 function! fugitive#setfperm(url, perm) abort
765   let [dir, commit, file] = s:DirCommitFile(a:url)
766   let entry = s:PathInfo(a:url)
767   let perm = fugitive#getfperm(a:url)
768   if commit !~# '^\d$' || entry[2] !=# 'blob' ||
769       \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
770     return -2
771   endif
772   call system(s:Prepare(dir, 'update-index', '--index-info'),
773         \ (a:perm =~# 'x' ? '000755 ' : '000644 ') . entry[3] . ' ' . commit . "\t" . file[1:-1])
774   return v:shell_error ? -1 : 0
775 endfunction
777 function! s:TempCmd(out, cmd) abort
778   let prefix = ''
779   try
780     let cmd = (type(a:cmd) == type([]) ? call('s:Prepare', a:cmd) : a:cmd)
781     let redir = ' > ' . a:out
782     if s:winshell()
783       let cmd_escape_char = &shellxquote == '(' ?  '^' : '^^^'
784       return s:System('cmd /c "' . prefix . s:gsub(cmd, '[<>]', cmd_escape_char . '&') . redir . '"')
785     elseif &shell =~# 'fish'
786       return s:System(' begin;' . prefix . cmd . redir . ';end ')
787     else
788       return s:System(' (' . prefix . cmd . redir . ') ')
789     endif
790   endtry
791 endfunction
793 if !exists('s:blobdirs')
794   let s:blobdirs = {}
795 endif
796 function! s:BlobTemp(url) abort
797   let [dir, commit, file] = s:DirCommitFile(a:url)
798   if empty(file)
799     return ''
800   endif
801   if !has_key(s:blobdirs, dir)
802     let s:blobdirs[dir] = s:tempname()
803   endif
804   let tempfile = s:PlatformSlash(s:blobdirs[dir] . '/' . commit . file)
805   let tempparent = fnamemodify(tempfile, ':h')
806   if !isdirectory(tempparent)
807     call mkdir(tempparent, 'p')
808   endif
809   if commit =~# '^\d$' || !filereadable(tempfile)
810     let rev = s:DirRev(a:url)[1]
811     let command = s:Prepare(dir, 'cat-file', 'blob', rev)
812     call s:TempCmd(tempfile, command)
813     if v:shell_error
814       call delete(tempfile)
815       return ''
816     endif
817   endif
818   return tempfile
819 endfunction
821 function! fugitive#readfile(url, ...) abort
822   let entry = s:PathInfo(a:url)
823   if entry[2] !=# 'blob'
824     return []
825   endif
826   let temp = s:BlobTemp(a:url)
827   if empty(temp)
828     return []
829   endif
830   return call('readfile', [temp] + a:000)
831 endfunction
833 function! fugitive#writefile(lines, url, ...) abort
834   let url = type(a:url) ==# type('') ? a:url : ''
835   let [dir, commit, file] = s:DirCommitFile(url)
836   let entry = s:PathInfo(url)
837   if commit =~# '^\d$' && entry[2] !=# 'tree'
838     let temp = s:tempname()
839     if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
840       call writefile(fugitive#readfile(url, 'b'), temp, 'b')
841     endif
842     call call('writefile', [a:lines, temp] + a:000)
843     let hash = system(s:Prepare(dir, 'hash-object', '-w', temp))[0:-2]
844     let mode = len(entry[1]) ? entry[1] : '100644'
845     if !v:shell_error && hash =~# '^\x\{40\}$'
846       call system(s:Prepare(dir, 'update-index', '--index-info'),
847             \ mode . ' ' . hash . ' ' . commit . "\t" . file[1:-1])
848       if !v:shell_error
849         return 0
850       endif
851     endif
852   endif
853   return call('writefile', [a:lines, a:url] + a:000)
854 endfunction
856 let s:globsubs = {
857       \ '/**/': '/\%([^./][^/]*/\)*',
858       \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
859       \ '**/': '[^/]*\%(/[^./][^/]*\)*',
860       \ '**': '.*',
861       \ '/*': '/[^/.][^/]*',
862       \ '*': '[^/]*',
863       \ '?': '[^/]'}
864 function! fugitive#glob(url, ...) abort
865   let [dirglob, commit, glob] = s:DirCommitFile(a:url)
866   let append = matchstr(glob, '/*$')
867   let glob = substitute(glob, '/*$', '', '')
868   let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\^$]', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
869   let results = []
870   for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
871     if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(dir . '/HEAD')
872       continue
873     endif
874     let files = items(s:TreeInfo(dir, commit)[0])
875     if len(append)
876       call filter(files, 'v:val[1][2] ==# "tree"')
877     endif
878     call map(files, 'v:val[0]')
879     call filter(files, 'v:val =~# pattern')
880     let prepend = 'fugitive://' . dir . '//' . substitute(commit, '^:', '', '') . '/'
881     call sort(files)
882     call map(files, 's:PlatformSlash(prepend . v:val . append)')
883     call extend(results, files)
884   endfor
885   if a:0 > 1 && a:2
886     return results
887   else
888     return join(results, "\n")
889   endif
890 endfunction
892 function! fugitive#delete(url, ...) abort
893   let [dir, commit, file] = s:DirCommitFile(a:url)
894   if a:0 && len(a:1) || commit !~# '^\d$'
895     return -1
896   endif
897   let entry = s:PathInfo(a:url)
898   if entry[2] !=# 'blob'
899     return -1
900   endif
901   call system(s:Prepare(dir, 'update-index', '--index-info'),
902         \ '000000 0000000000000000000000000000000000000000 ' . commit . "\t" . file[1:-1])
903   return v:shell_error ? -1 : 0
904 endfunction
906 let s:buffer_prototype = {}
908 function! fugitive#buffer(...) abort
909   let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
910   call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
911   if buffer.getvar('git_dir') !=# ''
912     return buffer
913   endif
914   call s:throw('not a git repository: '.bufname(buffer['#']))
915 endfunction
917 function! s:buffer_getvar(var) dict abort
918   return getbufvar(self['#'],a:var)
919 endfunction
921 function! s:buffer_getline(lnum) dict abort
922   return get(getbufline(self['#'], a:lnum), 0, '')
923 endfunction
925 function! s:buffer_repo() dict abort
926   return fugitive#repo(self.getvar('git_dir'))
927 endfunction
929 function! s:buffer_type(...) dict abort
930   if !empty(self.getvar('fugitive_type'))
931     let type = self.getvar('fugitive_type')
932   elseif fnamemodify(self.spec(),':p') =~# '\.git/refs/\|\.git/\w*HEAD$'
933     let type = 'head'
934   elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
935     let type = 'tree'
936   elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
937     let type = 'tree'
938   elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
939     let type = 'index'
940   elseif isdirectory(self.spec())
941     let type = 'directory'
942   elseif self.spec() == ''
943     let type = 'null'
944   else
945     let type = 'file'
946   endif
947   if a:0
948     return !empty(filter(copy(a:000),'v:val ==# type'))
949   else
950     return type
951   endif
952 endfunction
954 if has('win32')
956   function! s:buffer_spec() dict abort
957     let bufname = bufname(self['#'])
958     let retval = ''
959     for i in split(bufname,'[^:]\zs\\')
960       let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
961     endfor
962     return s:Slash(fnamemodify(retval,':p'))
963   endfunction
965 else
967   function! s:buffer_spec() dict abort
968     let bufname = bufname(self['#'])
969     return s:Slash(bufname == '' ? '' : fnamemodify(bufname,':p'))
970   endfunction
972 endif
974 function! s:buffer_name() dict abort
975   return self.spec()
976 endfunction
978 function! s:buffer_commit() dict abort
979   return matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs\w*')
980 endfunction
982 function! s:buffer_relative(...) dict abort
983   let rev = matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs.*')
984   if rev != ''
985     let rev = s:sub(rev,'\w*','')
986   elseif s:cpath(self.spec()[0 : len(self.repo().dir())]) ==#
987         \ s:cpath(self.repo().dir() . '/')
988     let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
989   elseif !self.repo().bare() &&
990         \ s:cpath(self.spec()[0 : len(self.repo().tree())]) ==#
991         \ s:cpath(self.repo().tree() . '/')
992     let rev = self.spec()[strlen(self.repo().tree()) : -1]
993   endif
994   return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
995 endfunction
997 function! s:Relative(...) abort
998   return fugitive#Path(@%, a:0 ? a:1 : './')
999 endfunction
1001 function! s:buffer_path(...) dict abort
1002   if a:0
1003     return self.relative(a:1)
1004   endif
1005   return self.relative()
1006 endfunction
1008 call s:add_methods('buffer',['getvar','getline','repo','type','spec','name','commit','path','relative'])
1010 " Section: Completion
1012 function! s:GlobComplete(lead, pattern) abort
1013   if v:version >= 704
1014     let results = glob(a:lead . a:pattern, 0, 1)
1015   else
1016     let results = split(glob(a:lead . a:pattern), "\n")
1017   endif
1018   call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1019   call map(results, 'v:val[ strlen(a:lead) : -1 ]')
1020   return results
1021 endfunction
1023 function! fugitive#PathComplete(base, ...) abort
1024   let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
1025   let tree = FugitiveTreeForGitDir(dir) . '/'
1026   let strip = '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\|:(literal)\)\%(\./\)\='
1027   let base = substitute((a:base =~# '^/' ? '.' : '') . a:base, strip, '', '')
1028   if base =~# '^\.git/'
1029     let pattern = s:gsub(base[5:-1], '/', '*&').'*'
1030     let matches = s:GlobComplete(dir . '/', pattern)
1031     let cdir = fugitive#CommonDir(dir)
1032     if len(cdir) && s:cpath(dir) !=# s:cpath(cdir)
1033       call extend(matches, s:GlobComplete(cdir . '/', pattern))
1034     endif
1035     call s:Uniq(matches)
1036     call map(matches, "'.git/' . v:val")
1037   elseif base =~# '^\~/'
1038     let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
1039   elseif len(tree) > 1
1040     let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*')
1041   else
1042     let matches = []
1043   endif
1044   if empty(matches) && a:base =~# '^/\|^\a\+:'
1045     let matches = s:GlobComplete('', a:base . '*')
1046   endif
1047   call map(matches, 's:fnameescape(s:Slash(matchstr(a:base, strip) . v:val))')
1048   return matches
1049 endfunction
1051 function! fugitive#Complete(base, ...) abort
1052   let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
1053   let tree = s:Tree(dir) . '/'
1054   if a:base =~# '^\.\=/\|^:(' || a:base !~# ':'
1055     let results = []
1056     if a:base =~# '^refs/'
1057       let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:Slash(v:val)')
1058     elseif a:base !~# '^\.\=/\|^:('
1059       let heads = ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'MERGE_HEAD', 'refs/']
1060       let heads += sort(split(s:TreeChomp(["rev-parse","--symbolic","--branches","--tags","--remotes"], dir),"\n"))
1061       if filereadable(fugitive#CommonDir(dir) . '/refs/stash')
1062         let heads += ["stash"]
1063         let heads += sort(split(s:TreeChomp(["stash","list","--pretty=format:%gd"], dir),"\n"))
1064       endif
1065       call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1066       let results += heads
1067     endif
1068     call map(results, 's:fnameescape(v:val)')
1069     if !empty(tree)
1070       let results += fugitive#PathComplete(a:base, dir)
1071     endif
1072     return results
1074   elseif a:base =~# '^:'
1075     let entries = split(s:TreeChomp(['ls-files','--stage'], dir),"\n")
1076     call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
1077     if a:base !~# '^:[0-3]\%(:\|$\)'
1078       call filter(entries,'v:val[1] == "0"')
1079       call map(entries,'v:val[2:-1]')
1080     endif
1081     call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1082     return map(entries, 's:fnameescape(v:val)')
1084   else
1085     let tree = matchstr(a:base,'.*[:/]')
1086     let entries = split(s:TreeChomp(['ls-tree',tree], dir),"\n")
1087     call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
1088     call map(entries,'tree.s:sub(v:val,".*\t","")')
1089     call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1090     return map(entries, 's:fnameescape(v:val)')
1091   endif
1092 endfunction
1094 " Section: File access
1096 function! s:ReplaceCmd(cmd) abort
1097   let tmp = tempname()
1098   let err = s:TempCmd(tmp, a:cmd)
1099   if v:shell_error
1100     call s:throw((len(err) ? err : filereadable(tmp) ? join(readfile(tmp), ' ') : 'unknown error running ' . a:cmd))
1101   endif
1102   let fn = expand('%:p')
1103   silent exe 'doau BufReadPre '.s:fnameescape(fn)
1104   silent exe 'keepalt file '.tmp
1105   try
1106     silent noautocmd edit!
1107   finally
1108     try
1109       silent exe 'keepalt file '.s:fnameescape(fn)
1110     catch /^Vim\%((\a\+)\)\=:E302:/
1111     endtry
1112     call delete(tmp)
1113     if fnamemodify(bufname('$'), ':p') ==# tmp
1114       silent execute 'bwipeout '.bufnr('$')
1115     endif
1116     silent exe 'doau BufReadPost '.s:fnameescape(fn)
1117   endtry
1118 endfunction
1120 function! fugitive#BufReadStatus() abort
1121   let amatch = s:Slash(expand('%:p'))
1122   if !exists('b:fugitive_display_format')
1123     let b:fugitive_display_format = filereadable(expand('%').'.lock')
1124   endif
1125   let b:fugitive_display_format = b:fugitive_display_format % 2
1126   let b:fugitive_type = 'index'
1127   try
1128     let dir = fnamemodify(amatch, ':h')
1129     setlocal noro ma nomodeline
1130     let prefix = ''
1131     if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p')) !=# s:cpath(amatch)
1132       if s:winshell()
1133         let old_index = $GIT_INDEX_FILE
1134       else
1135         let prefix = 'env GIT_INDEX_FILE='.s:shellesc(amatch).' '
1136       endif
1137     endif
1138     if b:fugitive_display_format
1139       let cmd = ['ls-files', '--stage']
1140     elseif fugitive#GitVersion() =~# '^0\|^1\.[1-7]\.'
1141       let cmd = ['status']
1142     else
1143       let cmd = [
1144             \ '-c', 'status.displayCommentPrefix=true',
1145             \ '-c', 'color.status=false',
1146             \ '-c', 'status.short=false',
1147             \ 'status']
1148     endif
1149     let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1150     let cwd = getcwd()
1151     let cmd_str = prefix . s:Prepare(cmd, dir)
1152     try
1153       if exists('old_index')
1154         let $GIT_INDEX_FILE = amatch
1155       endif
1156       execute cd s:fnameescape(s:Tree(dir))
1157       call s:ReplaceCmd(cmd_str)
1158     finally
1159       if exists('old_index')
1160         let $GIT_INDEX_FILE = old_index
1161       endif
1162       execute cd s:fnameescape(cwd)
1163     endtry
1164     if b:fugitive_display_format
1165       if &filetype !=# 'git'
1166         set filetype=git
1167       endif
1168       set nospell
1169     else
1170       if &filetype !=# 'gitcommit'
1171         set filetype=gitcommit
1172       endif
1173       set foldtext=fugitive#Foldtext()
1174     endif
1175     setlocal readonly nomodifiable nomodified noswapfile
1176     if &bufhidden ==# ''
1177       setlocal bufhidden=delete
1178     endif
1179     call fugitive#MapJumps()
1180     let nowait = v:version >= 704 ? '<nowait>' : ''
1181     nunmap   <buffer>          P
1182     nunmap   <buffer>          ~
1183     nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
1184     nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
1185     exe "nnoremap <buffer> <silent>" nowait "- :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>"
1186     exe "xnoremap <buffer> <silent>" nowait "- :<C-U>silent execute <SID>StageToggle(line(\"'<\"),line(\"'>\"))<CR>"
1187     nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe fugitive#BufReadStatus()<CR>
1188     nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe fugitive#BufReadStatus()<CR>
1189     nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>
1190     nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>:echohl WarningMsg<Bar>echo ':Gstatus cA is deprecated in favor of ce'<Bar>echohl NONE<CR>
1191     nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
1192     nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
1193     nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
1194     nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
1195     nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
1196     nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
1197     nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1198     nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1199     nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1200     nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1201     nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
1202     nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
1203     nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1204     xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1205     nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1206     xnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1207     nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
1208     nnoremap <buffer> <silent> r :<C-U>edit<CR>
1209     nnoremap <buffer> <silent> R :<C-U>edit<CR>
1210     nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
1211     nnoremap <buffer>          . : <C-R>=<SID>fnameescape(<SID>StatusCfile())<CR><Home>
1212     nnoremap <buffer> <silent> g?   :help fugitive-:Gstatus<CR>
1213     nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
1214   catch /^fugitive:/
1215     return 'echoerr v:errmsg'
1216   endtry
1217 endfunction
1219 function! fugitive#FileReadCmd(...) abort
1220   let amatch = a:0 ? a:1 : expand('<amatch>')
1221   let [dir, rev] = s:DirRev(amatch)
1222   let line = a:0 > 1 ? a:2 : line("'[")
1223   if empty(dir)
1224     return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1225   endif
1226   if rev !~# ':'
1227     let cmd = s:Prepare(dir, 'log', '--pretty=format:%B', '-1', rev)
1228   else
1229     let cmd = s:Prepare(dir, 'cat-file', '-p', rev)
1230   endif
1231   return line . 'read !' . escape(cmd, '!#%')
1232 endfunction
1234 function! fugitive#FileWriteCmd(...) abort
1235   let tmp = tempname()
1236   let amatch = a:0 ? a:1 : expand('<amatch>')
1237   let autype = a:0 > 1 ? 'Buf' : 'File'
1238   if exists('#' . autype . 'WritePre')
1239     execute 'doautocmd ' . autype . 'WritePre ' . s:fnameescape(amatch)
1240   endif
1241   try
1242     let [dir, commit, file] = s:DirCommitFile(amatch)
1243     if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1244       return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1245     endif
1246     silent execute "'[,']write !".s:Prepare(dir, 'hash-object', '-w', '--stdin').' > '.tmp
1247     let sha1 = readfile(tmp)[0]
1248     let old_mode = matchstr(system(s:Prepare(dir, 'ls-files', '--stage', file[1:-1])), '^\d\+')
1249     if old_mode == ''
1250       let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1251     endif
1252     let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1253     call writefile([info],tmp)
1254     if s:winshell()
1255       let error = s:System('type '.s:gsub(tmp,'/','\\').'|'.s:Prepare(dir, 'update-index', '--index-info'))
1256     else
1257       let error = s:System(s:Prepare(dir, 'update-index', '--index-info').' < '.tmp)
1258     endif
1259     if v:shell_error == 0
1260       setlocal nomodified
1261       if exists('#' . autype . 'WritePost')
1262         execute 'doautocmd ' . autype . 'WritePost ' . s:fnameescape(amatch)
1263       endif
1264       call fugitive#ReloadStatus()
1265       return ''
1266     else
1267       return 'echoerr '.string('fugitive: '.error)
1268     endif
1269   finally
1270     call delete(tmp)
1271   endtry
1272 endfunction
1274 function! fugitive#BufReadCmd(...) abort
1275   let amatch = a:0 ? a:1 : expand('<amatch>')
1276   try
1277     let [dir, rev] = s:DirRev(amatch)
1278     if empty(dir)
1279       return 'echo "Invalid Fugitive URL"'
1280     endif
1281     if rev =~# '^:\d$'
1282       let b:fugitive_type = 'stage'
1283     else
1284       let b:fugitive_type = system(s:Prepare(dir, 'cat-file', '-t', rev))[0:-2]
1285       if v:shell_error && rev =~# '^:0'
1286         let sha = system(s:Prepare(dir, 'write-tree', '--prefix=' . rev[3:-1]))[0:-2]
1287         let b:fugitive_type = 'tree'
1288       endif
1289       if v:shell_error
1290         unlet b:fugitive_type
1291         if rev =~# '^:\d:'
1292           let &readonly = !filewritable(dir . '/index')
1293           return 'silent doautocmd BufNewFile '.s:fnameescape(amatch)
1294         else
1295           setlocal readonly nomodifiable
1296           return ''
1297         endif
1298       elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1299         return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1300       endif
1301       if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1302         let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1303       endif
1304     endif
1306     if b:fugitive_type !=# 'blob'
1307       setlocal nomodeline
1308     endif
1310     setlocal noreadonly modifiable
1311     let pos = getpos('.')
1312     silent keepjumps %delete_
1313     setlocal endofline
1315     try
1316       if b:fugitive_type ==# 'tree'
1317         let b:fugitive_display_format = b:fugitive_display_format % 2
1318         if b:fugitive_display_format
1319           call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1320         else
1321           if !exists('sha')
1322             let sha = system(s:Prepare(dir, 'rev-parse', '--verify', rev))[0:-2]
1323           endif
1324           call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1325         endif
1326       elseif b:fugitive_type ==# 'tag'
1327         let b:fugitive_display_format = b:fugitive_display_format % 2
1328         if b:fugitive_display_format
1329           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1330         else
1331           call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1332         endif
1333       elseif b:fugitive_type ==# 'commit'
1334         let b:fugitive_display_format = b:fugitive_display_format % 2
1335         if b:fugitive_display_format
1336           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1337         else
1338           call s:ReplaceCmd([dir, 'show', '--no-color', '--pretty=format:tree%x20%T%nparent%x20%P%nauthor%x20%an%x20<%ae>%x20%ad%ncommitter%x20%cn%x20<%ce>%x20%cd%nencoding%x20%e%n%n%s%n%n%b', rev])
1339           keepjumps call search('^parent ')
1340           if getline('.') ==# 'parent '
1341             silent keepjumps delete_
1342           else
1343             silent exe 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
1344           endif
1345           keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1346           if lnum
1347             silent keepjumps delete_
1348           end
1349           silent keepjumps 1,/^diff --git\|\%$/g/\r$/s///
1350           keepjumps 1
1351         endif
1352       elseif b:fugitive_type ==# 'stage'
1353         call s:ReplaceCmd([dir, 'ls-files', '--stage'])
1354       elseif b:fugitive_type ==# 'blob'
1355         call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1356         setlocal nomodeline
1357       endif
1358     finally
1359       keepjumps call setpos('.',pos)
1360       setlocal nomodified noswapfile
1361       if rev !~# '^:.:'
1362         setlocal nomodifiable
1363       else
1364         let &modifiable = b:fugitive_type !=# 'tree'
1365       endif
1366       let &readonly = !&modifiable || !filewritable(dir . '/index')
1367       if &bufhidden ==# ''
1368         setlocal bufhidden=delete
1369       endif
1370       if b:fugitive_type !=# 'blob'
1371         setlocal filetype=git foldmethod=syntax
1372         nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1373         nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1374       else
1375         call fugitive#MapJumps()
1376       endif
1377     endtry
1379     return ''
1380   catch /^fugitive:/
1381     return 'echoerr v:errmsg'
1382   endtry
1383 endfunction
1385 function! fugitive#BufWriteCmd(...) abort
1386   return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
1387 endfunction
1389 function! fugitive#SourceCmd(...) abort
1390   let amatch = a:0 ? a:1 : expand('<amatch>')
1391   let temp = s:BlobTemp(amatch)
1392   if empty(temp)
1393     return 'noautocmd source ' . s:fnameescape(amatch)
1394   endif
1395   if !exists('g:virtual_scriptnames')
1396     let g:virtual_scriptnames = {}
1397   endif
1398   let g:virtual_scriptnames[temp] = amatch
1399   return 'source ' . s:fnameescape(temp)
1400 endfunction
1402 " Section: Temp files
1404 if !exists('s:temp_files')
1405   let s:temp_files = {}
1406 endif
1408 function! s:SetupTemp(file) abort
1409   if has_key(s:temp_files, s:cpath(a:file))
1410     let dict = s:temp_files[s:cpath(a:file)]
1411     let b:git_dir = dict.dir
1412     call extend(b:, {'fugitive_type': 'temp'}, 'keep')
1413     if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
1414       let &l:filetype = dict.filetype
1415     endif
1416     setlocal foldmarker=<<<<<<<,>>>>>>>
1417     setlocal bufhidden=delete nobuflisted
1418     setlocal buftype=nowrite
1419     nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
1420     if getline(1) !~# '^diff '
1421       setlocal nomodifiable
1422     endif
1423     call FugitiveDetect(a:file)
1424   endif
1425   return ''
1426 endfunction
1428 augroup fugitive_temp
1429   autocmd!
1430   autocmd BufNewFile,BufReadPost * exe s:SetupTemp(expand('<amatch>:p'))
1431 augroup END
1433 " Section: Git
1435 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,'<mods>',<q-args>)")
1437 function! s:Git(bang, mods, args) abort
1438   if a:bang
1439     return s:Edit('edit', 1, a:mods, a:args)
1440   endif
1441   let git = s:UserCommand()
1442   if has('gui_running') && !has('win32')
1443     let git .= ' --no-pager'
1444   endif
1445   let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
1446   let after = matchstr(a:args, '\v\C\\@<!%(\\\\)*\zs\|.*')
1447   let tree = s:Tree()
1448   if has('win32')
1449     let after = '|call fugitive#ReloadStatus()' . after
1450   endif
1451   if exists(':terminal') && has('nvim') && !get(g:, 'fugitive_force_bang_command')
1452     if len(@%)
1453       -tabedit %
1454     else
1455       -tabnew
1456     endif
1457     execute 'lcd' fnameescape(tree)
1458     let exec = escape(git . ' ' . s:ShellExpand(args), '#%')
1459     return 'exe ' . string('terminal ' . exec) . after
1460   else
1461     let cmd = "exe '!'.escape(" . string(git) . " . ' ' . s:ShellExpand(" . string(args) . "),'!#%')"
1462     if s:cpath(tree) !=# s:cpath(getcwd())
1463       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1464       let cmd = 'try|' . cd . ' ' . tree . '|' . cmd . '|finally|' . cd . ' ' . s:fnameescape(getcwd()) . '|endtry'
1465     endif
1466     return cmd . after
1467   endif
1468 endfunction
1470 let s:exec_paths = {}
1471 function! s:Subcommands() abort
1472   if !has_key(s:exec_paths, g:fugitive_git_executable)
1473     let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
1474   endif
1475   let exec_path = s:exec_paths[g:fugitive_git_executable]
1476   return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
1477 endfunction
1479 let s:aliases = {}
1480 function! s:Aliases() abort
1481   if !has_key(s:aliases, b:git_dir)
1482     let s:aliases[b:git_dir] = {}
1483     let lines = split(s:TreeChomp('config','-z','--get-regexp','^alias[.]'),"\1")
1484     for line in v:shell_error ? [] : lines
1485       let s:aliases[b:git_dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
1486     endfor
1487   endif
1488   return s:aliases[b:git_dir]
1489 endfunction
1491 function! s:GitComplete(A, L, P) abort
1492   let pre = strpart(a:L, 0, a:P)
1493   if pre !~# ' [[:alnum:]-]\+ '
1494     let cmds = s:Subcommands()
1495     return filter(sort(cmds+keys(s:Aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
1496   elseif pre =~# ' -- '
1497     return fugitive#PathComplete(a:A)
1498   else
1499     return fugitive#Complete(a:A, a:L, a:P)
1500   endif
1501 endfunction
1503 " Section: Gcd, Glcd
1505 function! s:DirComplete(A, L, P) abort
1506   return filter(fugitive#PathComplete(a:A), 'v:val =~# "/$"')
1507 endfunction
1509 function! s:DirArg(path) abort
1510   let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
1511   if path =~# '^/\|^\a\+:'
1512     return path
1513   else
1514     return (empty(s:Tree()) ? b:git_dir : s:Tree()) . '/' . path
1515   endif
1516 endfunction
1518 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd  :exe 'cd<bang>'  s:fnameescape(s:DirArg(<q-args>))")
1519 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe 'lcd<bang>' s:fnameescape(s:DirArg(<q-args>))")
1521 " Section: Gstatus
1523 call s:command("-bar -bang -range=-1 Gstatus :execute s:Status(<bang>0, <count>, '<mods>')")
1524 augroup fugitive_status
1525   autocmd!
1526   if !has('win32')
1527     autocmd FocusGained,ShellCmdPost * call fugitive#ReloadStatus()
1528     autocmd BufDelete term://* call fugitive#ReloadStatus()
1529   endif
1530 augroup END
1532 function! s:Status(bang, count, mods) abort
1533   try
1534     exe (a:mods ==# '<mods>' ? '' : a:mods) 'Gpedit :'
1535     wincmd P
1536     setlocal foldmethod=syntax foldlevel=1 buftype=nowrite
1537     nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
1538   catch /^fugitive:/
1539     return 'echoerr v:errmsg'
1540   endtry
1541   return ''
1542 endfunction
1544 function! fugitive#ReloadStatus() abort
1545   if exists('s:reloading_status')
1546     return
1547   endif
1548   try
1549     let s:reloading_status = 1
1550     let mytab = tabpagenr()
1551     for tab in [mytab] + range(1,tabpagenr('$'))
1552       for winnr in range(1,tabpagewinnr(tab,'$'))
1553         if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
1554           execute 'tabnext '.tab
1555           if winnr != winnr()
1556             execute winnr.'wincmd w'
1557             let restorewinnr = 1
1558           endif
1559           try
1560             if !&modified
1561               call fugitive#BufReadStatus()
1562             endif
1563           finally
1564             if exists('restorewinnr')
1565               wincmd p
1566             endif
1567             execute 'tabnext '.mytab
1568           endtry
1569         endif
1570       endfor
1571     endfor
1572   finally
1573     unlet! s:reloading_status
1574   endtry
1575 endfunction
1577 function! fugitive#reload_status() abort
1578   return fugitive#ReloadStatus()
1579 endfunction
1581 function! s:stage_info(lnum) abort
1582   let filename = matchstr(getline(a:lnum),'^.\=\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
1583   let lnum = a:lnum
1584   if has('multi_byte_encoding')
1585     let colon = '\%(:\|\%uff1a\)'
1586   else
1587     let colon = ':'
1588   endif
1589   while lnum && getline(lnum) !~# colon.'$'
1590     let lnum -= 1
1591   endwhile
1592   if !lnum
1593     return ['', '']
1594   elseif (getline(lnum+1) =~# '^.\= .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) =~# '^\%(. \)\=Changes to be committed:$'
1595     return [matchstr(filename, colon.' *\zs.*'), 'staged']
1596   elseif (getline(lnum+1) =~# '^.\= .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.'  ') || getline(lnum) =~# '^\(. \)\=Untracked files:$'
1597     return [filename, 'untracked']
1598   elseif getline(lnum+2) =~# '^.\= .*\<git checkout ' || getline(lnum) =~# '\%(. \)\=Changes not staged for commit:$'
1599     return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
1600   elseif getline(lnum+2) =~# '^.\= .*\<git \%(add\|rm\)' || getline(lnum) =~# '\%(. \)\=Unmerged paths:$'
1601     return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
1602   else
1603     return ['', 'unknown']
1604   endif
1605 endfunction
1607 function! s:StageNext(count) abort
1608   for i in range(a:count)
1609     call search('^.\=\t.*','W')
1610   endfor
1611   return '.'
1612 endfunction
1614 function! s:StagePrevious(count) abort
1615   if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
1616     return 'CtrlP '.fnameescape(s:Tree())
1617   else
1618     for i in range(a:count)
1619       call search('^.\=\t.*','Wbe')
1620     endfor
1621     return '.'
1622   endif
1623 endfunction
1625 function! s:StageReloadSeek(target,lnum1,lnum2) abort
1626   let jump = a:target
1627   let f = matchstr(getline(a:lnum1-1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1628   if f !=# '' | let jump = f | endif
1629   let f = matchstr(getline(a:lnum2+1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1630   if f !=# '' | let jump = f | endif
1631   silent! edit!
1632   1
1633   redraw
1634   call search('^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1635 endfunction
1637 function! s:StageUndo() abort
1638   let [filename, section] = s:stage_info(line('.'))
1639   if empty(filename)
1640     return ''
1641   endif
1642   let hash = s:TreeChomp('hash-object', '-w', filename)
1643   if !empty(hash)
1644     if section ==# 'untracked'
1645       call s:TreeChomp('clean', '-f', '--', filename)
1646     elseif section ==# 'unmerged'
1647       call s:TreeChomp('rm', '--', filename)
1648     elseif section ==# 'unstaged'
1649       call s:TreeChomp('checkout', '--', filename)
1650     else
1651       call s:TreeChomp('checkout', 'HEAD', '--', filename)
1652     endif
1653     call s:StageReloadSeek(filename, line('.'), line('.'))
1654     let @" = hash
1655     return 'checktime|redraw|echomsg ' .
1656           \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
1657   endif
1658 endfunction
1660 function! s:StageDiff(diff) abort
1661   let [filename, section] = s:stage_info(line('.'))
1662   if filename ==# '' && section ==# 'staged'
1663     return 'Git! diff --no-ext-diff --cached'
1664   elseif filename ==# ''
1665     return 'Git! diff --no-ext-diff'
1666   elseif filename =~# ' -> '
1667     let [old, new] = split(filename,' -> ')
1668     execute 'Gedit '.s:fnameescape(':0:'.new)
1669     return a:diff.' HEAD:'.s:fnameescape(old)
1670   elseif section ==# 'staged'
1671     execute 'Gedit '.s:fnameescape(':0:'.filename)
1672     return a:diff.' -'
1673   else
1674     execute 'Gedit '.s:fnameescape('/'.filename)
1675     return a:diff
1676   endif
1677 endfunction
1679 function! s:StageDiffEdit() abort
1680   let [filename, section] = s:stage_info(line('.'))
1681   let arg = (filename ==# '' ? '.' : filename)
1682   if section ==# 'staged'
1683     return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
1684   elseif section ==# 'untracked'
1685     call s:TreeChomp('add','--intent-to-add',arg)
1686     if arg ==# '.'
1687       silent! edit!
1688       1
1689       if !search('^.*:\n.*\n.\= .*"git checkout \|^\%(# \)=Changes not staged for commit:$','W')
1690         call search(':$','W')
1691       endif
1692     else
1693       call s:StageReloadSeek(arg,line('.'),line('.'))
1694     endif
1695     return ''
1696   else
1697     return 'Git! diff --no-ext-diff '.s:shellesc(arg)
1698   endif
1699 endfunction
1701 function! s:StageToggle(lnum1,lnum2) abort
1702   if a:lnum1 == 1 && a:lnum2 == 1
1703     return 'Gedit /.git|call search("^index$", "wc")'
1704   endif
1705   try
1706     let output = ''
1707     for lnum in range(a:lnum1,a:lnum2)
1708       let [filename, section] = s:stage_info(lnum)
1709       if getline('.') =~# ':$'
1710         if section ==# 'staged'
1711           call s:TreeChomp('reset','-q')
1712           silent! edit!
1713           1
1714           if !search('^.*:\n.\= .*"git add .*\n#\n\|^\%(. \)\=Untracked files:$','W')
1715             call search(':$','W')
1716           endif
1717           return ''
1718         elseif section ==# 'unstaged'
1719           call s:TreeChomp('add','-u')
1720           silent! edit!
1721           1
1722           if !search('^.*:\n\.\= .*"git add .*\n#\n\|^\%( \)=Untracked files:$','W')
1723             call search(':$','W')
1724           endif
1725           return ''
1726         else
1727           call s:TreeChomp('add','.')
1728           silent! edit!
1729           1
1730           call search(':$','W')
1731           return ''
1732         endif
1733       endif
1734       if filename ==# ''
1735         continue
1736       endif
1737       execute lnum
1738       if section ==# 'staged'
1739         if filename =~ ' -> '
1740           let files_to_unstage = split(filename,' -> ')
1741         else
1742           let files_to_unstage = [filename]
1743         endif
1744         let filename = files_to_unstage[-1]
1745         let cmd = ['reset','-q','--'] + files_to_unstage
1746       elseif getline(lnum) =~# '^.\=\tdeleted:'
1747         let cmd = ['rm','--',filename]
1748       elseif getline(lnum) =~# '^.\=\tmodified:'
1749         let cmd = ['add','--',filename]
1750       else
1751         let cmd = ['add','-A','--',filename]
1752       endif
1753       if !exists('first_filename')
1754         let first_filename = filename
1755       endif
1756       let output .= call('s:TreeChomp', cmd)."\n"
1757     endfor
1758     if exists('first_filename')
1759       call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
1760     endif
1761     echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
1762   catch /^fugitive:/
1763     return 'echoerr v:errmsg'
1764   endtry
1765   return 'checktime'
1766 endfunction
1768 function! s:StagePatch(lnum1,lnum2) abort
1769   let add = []
1770   let reset = []
1772   for lnum in range(a:lnum1,a:lnum2)
1773     let [filename, section] = s:stage_info(lnum)
1774     if getline('.') =~# ':$' && section ==# 'staged'
1775       return 'Git reset --patch'
1776     elseif getline('.') =~# ':$' && section ==# 'unstaged'
1777       return 'Git add --patch'
1778     elseif getline('.') =~# ':$' && section ==# 'untracked'
1779       return 'Git add -N .'
1780     elseif filename ==# ''
1781       continue
1782     endif
1783     if !exists('first_filename')
1784       let first_filename = filename
1785     endif
1786     execute lnum
1787     if filename =~ ' -> '
1788       let reset += [split(filename,' -> ')[1]]
1789     elseif section ==# 'staged'
1790       let reset += [filename]
1791     elseif getline(lnum) !~# '^.\=\tdeleted:'
1792       let add += [filename]
1793     endif
1794   endfor
1795   try
1796     if !empty(add)
1797       execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1798     endif
1799     if !empty(reset)
1800       execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1801     endif
1802     if exists('first_filename')
1803       silent! edit!
1804       1
1805       redraw
1806       call search('^.\=\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1807     endif
1808   catch /^fugitive:/
1809     return 'echoerr v:errmsg'
1810   endtry
1811   return 'checktime'
1812 endfunction
1814 " Section: Gcommit
1816 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit('<mods>', <q-args>)")
1818 function! s:Commit(mods, args, ...) abort
1819   let mods = s:gsub(a:mods ==# '<mods>' ? '' : a:mods, '<tab>', '-tab')
1820   let dir = a:0 ? a:1 : b:git_dir
1821   let tree = s:Tree(dir)
1822   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1823   let cwd = getcwd()
1824   let msgfile = dir . '/COMMIT_EDITMSG'
1825   let outfile = tempname()
1826   let errorfile = tempname()
1827   try
1828     let guioptions = &guioptions
1829     try
1830       if &guioptions =~# '!'
1831         setglobal guioptions-=!
1832       endif
1833       execute cd s:fnameescape(tree)
1834       if s:winshell()
1835         let command = ''
1836         let old_editor = $GIT_EDITOR
1837         let $GIT_EDITOR = 'false'
1838       else
1839         let command = 'env GIT_EDITOR=false '
1840       endif
1841       let args = s:ShellExpand(a:args)
1842       let command .= s:UserCommand() . ' commit ' . args
1843       if &shell =~# 'csh'
1844         noautocmd silent execute '!('.escape(command, '!#%').' > '.outfile.') >& '.errorfile
1845       elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1846         noautocmd execute '!'.command.' 2> '.errorfile
1847       else
1848         noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1849       endif
1850       let error = v:shell_error
1851     finally
1852       execute cd s:fnameescape(cwd)
1853       let &guioptions = guioptions
1854     endtry
1855     if !has('gui_running')
1856       redraw!
1857     endif
1858     if !error
1859       if filereadable(outfile)
1860         for line in readfile(outfile)
1861           echo line
1862         endfor
1863       endif
1864       return ''
1865     else
1866       let errors = readfile(errorfile)
1867       let error = get(errors,-2,get(errors,-1,'!'))
1868       if error =~# 'false''\=\.$'
1869         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
1870         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1871         let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1872         let cwd = getcwd()
1873         let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
1874         let args = '-F '.s:shellesc(msgfile).' '.args
1875         if args !~# '\%(^\| \)--cleanup\>'
1876           let args = '--cleanup=strip '.args
1877         endif
1878         if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
1879           execute mods 'keepalt edit' s:fnameescape(msgfile)
1880         elseif a:args =~# '\%(^\| \)-\w*v' || mods =~# '\<tab\>'
1881           execute mods 'keepalt -tabedit' s:fnameescape(msgfile)
1882         elseif get(b:, 'fugitive_type', '') ==# 'index'
1883           execute mods 'keepalt edit' s:fnameescape(msgfile)
1884           execute (search('^#','n')+1).'wincmd+'
1885           setlocal nopreviewwindow
1886         else
1887           execute mods 'keepalt split' s:fnameescape(msgfile)
1888         endif
1889         let b:fugitive_commit_arguments = args
1890         setlocal bufhidden=wipe filetype=gitcommit
1891         return '1'
1892       elseif error ==# '!'
1893         return 'Gstatus'
1894       else
1895         call s:throw(empty(error)?join(errors, ' '):error)
1896       endif
1897     endif
1898   catch /^fugitive:/
1899     return 'echoerr v:errmsg'
1900   finally
1901     if exists('old_editor')
1902       let $GIT_EDITOR = old_editor
1903     endif
1904     call delete(outfile)
1905     call delete(errorfile)
1906     call fugitive#ReloadStatus()
1907   endtry
1908 endfunction
1910 function! s:CommitComplete(A,L,P) abort
1911   if a:A =~# '^--fixup=\|^--squash='
1912     let commits = split(s:TreeChomp('log', '--pretty=format:%s', '@{upstream}..'), "\n")
1913     if !v:shell_error
1914       let pre = matchstr(a:A, '^--\w*=') . ':/^'
1915       return map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")')
1916     endif
1917   elseif a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
1918     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=', '--fixup=', '--include', '--interactive', '--message=', '--no-verify', '--only', '--quiet', '--reedit-message=', '--reuse-message=', '--signoff', '--squash=', '--template=', '--untracked-files', '--verbose']
1919     return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
1920   else
1921     return fugitive#PathComplete(a:A)
1922   endif
1923   return []
1924 endfunction
1926 function! s:FinishCommit() abort
1927   let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
1928   if !empty(args)
1929     call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
1930     return s:Commit('', args, getbufvar(+expand('<abuf>'),'git_dir'))
1931   endif
1932   return ''
1933 endfunction
1935 " Section: Gmerge, Gpull
1937 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
1938       \ "execute s:Merge('merge', <bang>0, <q-args>)")
1939 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Grebase " .
1940       \ "execute s:Merge('rebase', <bang>0, <q-args>)")
1941 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
1942       \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
1944 function! s:RevisionComplete(A, L, P) abort
1945   return s:TreeChomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
1946         \ . "\nHEAD\nFETCH_HEAD\nMERGE_HEAD\nORIG_HEAD"
1947 endfunction
1949 function! s:RemoteComplete(A, L, P) abort
1950   let remote = matchstr(a:L, ' \zs\S\+\ze ')
1951   if !empty(remote)
1952     let matches = split(s:TreeChomp('ls-remote', remote), "\n")
1953     call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1954     call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1955   else
1956     let matches = split(s:TreeChomp('remote'), "\n")
1957   endif
1958   return join(matches, "\n")
1959 endfunction
1961 function! fugitive#Cwindow() abort
1962   if &buftype == 'quickfix'
1963     cwindow
1964   else
1965     botright cwindow
1966     if &buftype == 'quickfix'
1967       wincmd p
1968     endif
1969   endif
1970 endfunction
1972 let s:common_efm = ''
1973       \ . '%+Egit:%.%#,'
1974       \ . '%+Eusage:%.%#,'
1975       \ . '%+Eerror:%.%#,'
1976       \ . '%+Efatal:%.%#,'
1977       \ . '%-G%.%#%\e[K%.%#,'
1978       \ . '%-G%.%#%\r%.%\+'
1980 function! s:Merge(cmd, bang, args) abort
1981   if a:cmd =~# '^rebase' && ' '.a:args =~# ' -i\| --interactive\| --edit-todo'
1982     return 'echoerr "git rebase --interactive not supported"'
1983   endif
1984   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1985   let cwd = getcwd()
1986   let [mp, efm] = [&l:mp, &l:efm]
1987   let had_merge_msg = filereadable(b:git_dir . '/MERGE_MSG')
1988   try
1989     let &l:errorformat = ''
1990           \ . '%-Gerror:%.%#false''.,'
1991           \ . '%-G%.%# ''git commit'' %.%#,'
1992           \ . '%+Emerge:%.%#,'
1993           \ . s:common_efm . ','
1994           \ . '%+ECannot %.%#: You have unstaged changes.,'
1995           \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
1996           \ . '%+EThere is no tracking information for the current branch.,'
1997           \ . '%+EYou are not currently on a branch. Please specify which,'
1998           \ . 'CONFLICT (%m): %f deleted in %.%#,'
1999           \ . 'CONFLICT (%m): Merge conflict in %f,'
2000           \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
2001           \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
2002           \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
2003           \ . '%+ECONFLICT %.%#,'
2004           \ . '%+EKONFLIKT %.%#,'
2005           \ . '%+ECONFLIT %.%#,'
2006           \ . "%+EXUNG \u0110\u1ed8T %.%#,"
2007           \ . "%+E\u51b2\u7a81 %.%#,"
2008           \ . 'U%\t%f'
2009     if a:cmd =~# '^merge' && empty(a:args) &&
2010           \ (had_merge_msg || isdirectory(b:git_dir . '/rebase-apply') ||
2011           \  !empty(s:TreeChomp('diff-files', '--diff-filter=U')))
2012       let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
2013     else
2014       let &l:makeprg = s:sub(s:UserCommand() . ' ' . a:cmd .
2015             \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' || a:cmd =~# '^rebase' ? '' : ' --edit') .
2016             \ ' ' . a:args, ' *$', '')
2017     endif
2018     if !empty($GIT_EDITOR) || has('win32')
2019       let old_editor = $GIT_EDITOR
2020       let $GIT_EDITOR = 'false'
2021     else
2022       let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
2023     endif
2024     execute cd fnameescape(s:Tree())
2025     silent noautocmd make!
2026   catch /^Vim\%((\a\+)\)\=:E211/
2027     let err = v:exception
2028   finally
2029     redraw!
2030     let [&l:mp, &l:efm] = [mp, efm]
2031     if exists('old_editor')
2032       let $GIT_EDITOR = old_editor
2033     endif
2034     execute cd fnameescape(cwd)
2035   endtry
2036   call fugitive#ReloadStatus()
2037   if empty(filter(getqflist(),'v:val.valid'))
2038     if !had_merge_msg && filereadable(b:git_dir . '/MERGE_MSG')
2039       cclose
2040       return 'Gcommit --no-status -n -t '.s:shellesc(b:git_dir . '/MERGE_MSG')
2041     endif
2042   endif
2043   let qflist = getqflist()
2044   let found = 0
2045   for e in qflist
2046     if !empty(e.bufnr)
2047       let found = 1
2048       let e.pattern = '^<<<<<<<'
2049     endif
2050   endfor
2051   call fugitive#Cwindow()
2052   if found
2053     call setqflist(qflist, 'r')
2054     if !a:bang
2055       return 'cfirst'
2056     endif
2057   endif
2058   return exists('err') ? 'echoerr '.string(err) : ''
2059 endfunction
2061 " Section: Ggrep, Glog
2063 if !exists('g:fugitive_summary_format')
2064   let g:fugitive_summary_format = '%s'
2065 endif
2067 function! s:GrepComplete(A, L, P) abort
2068   if strpart(a:L, 0, a:P) =~# ' -- '
2069     return fugitive#PathComplete(a:A)
2070   else
2071     return fugitive#Complete(a:A)
2072   endif
2073 endfunction
2075 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
2076 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
2077 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Glog :call s:Log('grep',<bang>0,<line1>,<count>,<q-args>)")
2078 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Gllog :call s:Log('lgrep',<bang>0,<line1>,<count>,<q-args>)")
2080 function! s:Grep(cmd,bang,arg) abort
2081   let grepprg = &grepprg
2082   let grepformat = &grepformat
2083   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2084   let dir = getcwd()
2085   try
2086     execute cd s:fnameescape(s:Tree())
2087     let &grepprg = s:UserCommand() . ' --no-pager grep -n --no-color'
2088     let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
2089     exe a:cmd.'! '.escape(s:ShellExpand(matchstr(a:arg, '\v\C.{-}%($|[''" ]\@=\|)@=')), '|#%')
2090     let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
2091     for entry in list
2092       if bufname(entry.bufnr) =~ ':'
2093         let entry.filename = s:Generate(bufname(entry.bufnr))
2094         unlet! entry.bufnr
2095         let changed = 1
2096       elseif a:arg =~# '\%(^\| \)--cached\>'
2097         let entry.filename = s:Generate(':0:'.bufname(entry.bufnr))
2098         unlet! entry.bufnr
2099         let changed = 1
2100       endif
2101     endfor
2102     if a:cmd =~# '^l' && exists('changed')
2103       call setloclist(0, list, 'r')
2104     elseif exists('changed')
2105       call setqflist(list, 'r')
2106     endif
2107     if !a:bang && !empty(list)
2108       return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
2109     else
2110       return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
2111     endif
2112   finally
2113     let &grepprg = grepprg
2114     let &grepformat = grepformat
2115     execute cd s:fnameescape(dir)
2116   endtry
2117 endfunction
2119 function! s:Log(cmd, bang, line1, line2, ...) abort
2120   let args = ' ' . join(a:000, ' ')
2121   let before = substitute(args, ' --\S\@!.*', '', '')
2122   let after = strpart(args, len(before))
2123   let path = s:Relative('/')
2124   if path =~# '^/\.git\%(/\|$\)' || len(after)
2125     let path = ''
2126   endif
2127   let relative = s:Relative('')
2128   if before !~# '\s[^[:space:]-]'
2129     let owner = s:Owner(@%)
2130     if len(owner)
2131       let before .= ' ' . s:shellesc(owner)
2132     endif
2133   endif
2134   if relative =~# '^\.git\%(/\|$\)'
2135     let relative = ''
2136   endif
2137   if len(relative) && a:line2 > 0
2138     let before .= ' -L ' . s:shellesc(a:line1 . ',' . a:line2 . ':' . relative)
2139   elseif len(relative) && (empty(after) || a:line2 == 0)
2140     let after = (len(after) > 3 ? after : ' -- ') . relative
2141   endif
2142   let grepformat = &grepformat
2143   let grepprg = &grepprg
2144   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2145   let dir = getcwd()
2146   try
2147     execute cd s:fnameescape(s:Tree())
2148     let &grepprg = escape(s:UserCommand() . ' --no-pager log --no-color ' .
2149           \ s:shellesc('--pretty=format:fugitive://'.b:git_dir.'//%H'.path.'::'.g:fugitive_summary_format), '%#')
2150     let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
2151     exe a:cmd . (a:bang ? '! ' : ' ') . s:ShellExpand(before . after)
2152   finally
2153     let &grepformat = grepformat
2154     let &grepprg = grepprg
2155     execute cd s:fnameescape(dir)
2156   endtry
2157 endfunction
2159 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
2161 function! s:UsableWin(nr) abort
2162   return a:nr && !getwinvar(a:nr, '&previewwindow') &&
2163         \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
2164 endfunction
2166 function! s:EditParse(args) abort
2167   let pre = []
2168   let args = copy(a:args)
2169   while !empty(args) && args[0] =~# '^+'
2170     call add(pre, escape(remove(args, 0), ' |"') . ' ')
2171   endwhile
2172   if len(args)
2173     let file = join(args)
2174   elseif empty(expand('%'))
2175     let file = ':'
2176   elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
2177     let file = s:Relative(':0:')
2178   else
2179     let file = s:Relative('./')
2180   endif
2181   return [s:Expand(file), join(pre)]
2182 endfunction
2184 function! s:BlurStatus() abort
2185   if &previewwindow && get(b:,'fugitive_type', '') ==# 'index'
2186     let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
2187     if len(winnrs)
2188       exe winnrs[0].'wincmd w'
2189     elseif winnr('$') == 1
2190       let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
2191       execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
2192     else
2193       rightbelow new
2194     endif
2195     if &diff
2196       let mywinnr = winnr()
2197       for winnr in range(winnr('$'),1,-1)
2198         if winnr != mywinnr && getwinvar(winnr,'&diff')
2199           execute winnr.'wincmd w'
2200           close
2201           if winnr('$') > 1
2202             wincmd p
2203           endif
2204         endif
2205       endfor
2206       diffoff!
2207     endif
2208   endif
2209 endfunction
2211 function! s:Edit(cmd, bang, mods, args, ...) abort
2212   let mods = a:mods ==# '<mods>' ? '' : a:mods
2214   if a:bang
2215     let temp = s:tempname()
2216     let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2217     let cwd = getcwd()
2218     try
2219       execute cd s:fnameescape(s:Tree())
2220       let git = s:UserCommand()
2221       let args = s:ShellExpand(a:args)
2222       silent! execute '!' . escape(git . ' --no-pager ' . args, '!#%') .
2223             \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
2224     finally
2225       execute cd s:fnameescape(cwd)
2226     endtry
2227     let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'git' }
2228     if a:cmd ==# 'edit'
2229       call s:BlurStatus()
2230     endif
2231     silent execute mods a:cmd temp
2232     call fugitive#ReloadStatus()
2233     return 'redraw|echo ' . string(':!' . git . ' ' . args)
2234   endif
2236   let [file, pre] = s:EditParse(a:000)
2237   try
2238     let file = s:Generate(file)
2239   catch /^fugitive:/
2240     return 'echoerr v:errmsg'
2241   endtry
2242   if file !~# '^\a\a\+:'
2243     let file = s:sub(file, '/$', '')
2244   endif
2245   if a:cmd ==# 'edit'
2246     call s:BlurStatus()
2247   endif
2248   return mods . ' ' . a:cmd . ' ' . pre . s:fnameescape(file)
2249 endfunction
2251 function! s:Read(count, line1, line2, range, bang, mods, args, ...) abort
2252   let mods = a:mods ==# '<mods>' ? '' : a:mods
2253   let after = a:line2
2254   if a:count < 0
2255     let delete = 'silent 1,' . line('$') . 'delete_|'
2256     let after = line('$')
2257   elseif a:range == 2
2258     let delete = 'silent ' . a:line1 . ',' . a:line2 . 'delete_|'
2259   else
2260     let delete = ''
2261   endif
2262   if a:bang
2263     let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2264     let cwd = getcwd()
2265     try
2266       execute cd s:fnameescape(s:Tree())
2267       let git = s:UserCommand()
2268       let args = s:ShellExpand(a:args)
2269       silent execute mods after.'read!' escape(git . ' --no-pager ' . args, '!#%')
2270     finally
2271       execute cd s:fnameescape(cwd)
2272     endtry
2273     execute delete . 'diffupdate'
2274     call fugitive#ReloadStatus()
2275     return 'redraw|echo '.string(':!'.git.' '.args)
2276   endif
2277   let [file, pre] = s:EditParse(a:000)
2278   try
2279     let file = s:Generate(file)
2280   catch /^fugitive:/
2281     return 'echoerr v:errmsg'
2282   endtry
2283   return mods . ' ' . after . 'read ' . pre . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
2284 endfunction
2286 function! s:EditRunComplete(A,L,P) abort
2287   if a:L =~# '^\w\+!'
2288     return s:GitComplete(a:A, a:L, a:P)
2289   else
2290     return fugitive#Complete(a:A, a:L, a:P)
2291   endif
2292 endfunction
2294 call s:command("-bar -bang -nargs=*           -complete=customlist,fugitive#Complete Ge       execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2295 call s:command("-bar -bang -nargs=*           -complete=customlist,fugitive#Complete Gedit    execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2296 call s:command("-bar -bang -nargs=*           -complete=customlist,s:EditRunComplete Gpedit   execute s:Edit('pedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2297 call s:command("-bar -bang -nargs=* -range=0  -complete=customlist,s:EditRunComplete Gsplit   execute s:Edit((<count> ? <count> : '').'split', <bang>0, '<mods>', <q-args>, <f-args>)")
2298 call s:command("-bar -bang -nargs=* -range=0  -complete=customlist,s:EditRunComplete Gvsplit  execute s:Edit((<count> ? <count> : '').'vsplit', <bang>0, '<mods>', <q-args>, <f-args>)")
2299 call s:command("-bar -bang -nargs=* -range=0  -complete=customlist,s:EditRunComplete" . (has('patch-7.4.542') ? ' -addr=tabs' : '') . " Gtabedit execute s:Edit((<count> ? <count> : '').'tabedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2300 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:EditRunComplete Gread execute s:Read(<count>, <line1>, <line2>, +'<range>', <bang>0, '<mods>', <q-args>, <f-args>)")
2302 " Section: Gwrite, Gwq
2304 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwrite :execute s:Write(<bang>0,<f-args>)")
2305 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gw :execute s:Write(<bang>0,<f-args>)")
2306 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwq :execute s:Wq(<bang>0,<f-args>)")
2308 function! s:Write(force,...) abort
2309   if exists('b:fugitive_commit_arguments')
2310     return 'write|bdelete'
2311   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
2312     return 'wq'
2313   elseif get(b:, 'fugitive_type', '') ==# 'index'
2314     return 'Gcommit'
2315   elseif s:Relative('') ==# '' && getline(4) =~# '^+++ '
2316     let filename = getline(4)[6:-1]
2317     setlocal buftype=
2318     silent write
2319     setlocal buftype=nowrite
2320     if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
2321       let err = s:TreeChomp('apply', '--cached', '--reverse', expand('%:p'))
2322     else
2323       let err = s:TreeChomp('apply', '--cached', expand('%:p'))
2324     endif
2325     if err !=# ''
2326       let v:errmsg = split(err,"\n")[0]
2327       return 'echoerr v:errmsg'
2328     elseif a:force
2329       return 'bdelete'
2330     else
2331       return 'Gedit '.fnameescape(filename)
2332     endif
2333   endif
2334   let mytab = tabpagenr()
2335   let mybufnr = bufnr('')
2336   let path = a:0 ? s:Expand(join(a:000, ' ')) : s:Relative()
2337   if empty(path)
2338     return 'echoerr '.string('fugitive: cannot determine file path')
2339   endif
2340   if path =~# '^:\d\>'
2341     return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:Generate(path))
2342   endif
2343   let always_permitted = ((s:Relative() ==# path || s:Relative('') ==# path) && s:DirCommitFile(@%)[1] =~# '^0\=$')
2344   if !always_permitted && !a:force && (len(s:TreeChomp('diff','--name-status','HEAD','--',path)) || len(s:TreeChomp('ls-files','--others','--',path)))
2345     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
2346     return 'echoerr v:errmsg'
2347   endif
2348   let file = s:Generate(path)
2349   let treebufnr = 0
2350   for nr in range(1,bufnr('$'))
2351     if fnamemodify(bufname(nr),':p') ==# file
2352       let treebufnr = nr
2353     endif
2354   endfor
2356   if treebufnr > 0 && treebufnr != bufnr('')
2357     let temp = tempname()
2358     silent execute '%write '.temp
2359     for tab in [mytab] + range(1,tabpagenr('$'))
2360       for winnr in range(1,tabpagewinnr(tab,'$'))
2361         if tabpagebuflist(tab)[winnr-1] == treebufnr
2362           execute 'tabnext '.tab
2363           if winnr != winnr()
2364             execute winnr.'wincmd w'
2365             let restorewinnr = 1
2366           endif
2367           try
2368             let lnum = line('.')
2369             let last = line('$')
2370             silent execute '$read '.temp
2371             silent execute '1,'.last.'delete_'
2372             silent write!
2373             silent execute lnum
2374             let did = 1
2375           finally
2376             if exists('restorewinnr')
2377               wincmd p
2378             endif
2379             execute 'tabnext '.mytab
2380           endtry
2381         endif
2382       endfor
2383     endfor
2384     if !exists('did')
2385       call writefile(readfile(temp,'b'),file,'b')
2386     endif
2387   else
2388     execute 'write! '.s:fnameescape(s:Generate(path))
2389   endif
2391   if a:force
2392     let error = s:TreeChomp('add', '--force', '--', path)
2393   else
2394     let error = s:TreeChomp('add', '--', path)
2395   endif
2396   if v:shell_error
2397     let v:errmsg = 'fugitive: '.error
2398     return 'echoerr v:errmsg'
2399   endif
2400   if s:Relative('') ==# path && s:DirCommitFile(@%)[1] =~# '^\d$'
2401     set nomodified
2402   endif
2404   let one = s:Generate(':1:'.path)
2405   let two = s:Generate(':2:'.path)
2406   let three = s:Generate(':3:'.path)
2407   for nr in range(1,bufnr('$'))
2408     let name = fnamemodify(bufname(nr), ':p')
2409     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
2410       execute nr.'bdelete'
2411     endif
2412   endfor
2414   unlet! restorewinnr
2415   let zero = s:Generate(':0:'.path)
2416   silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
2417   for tab in range(1,tabpagenr('$'))
2418     for winnr in range(1,tabpagewinnr(tab,'$'))
2419       let bufnr = tabpagebuflist(tab)[winnr-1]
2420       let bufname = fnamemodify(bufname(bufnr), ':p')
2421       if bufname ==# zero && bufnr != mybufnr
2422         execute 'tabnext '.tab
2423         if winnr != winnr()
2424           execute winnr.'wincmd w'
2425           let restorewinnr = 1
2426         endif
2427         try
2428           let lnum = line('.')
2429           let last = line('$')
2430           silent execute '$read '.s:fnameescape(file)
2431           silent execute '1,'.last.'delete_'
2432           silent execute lnum
2433           set nomodified
2434           diffupdate
2435         finally
2436           if exists('restorewinnr')
2437             wincmd p
2438           endif
2439           execute 'tabnext '.mytab
2440         endtry
2441         break
2442       endif
2443     endfor
2444   endfor
2445   call fugitive#ReloadStatus()
2446   return 'checktime'
2447 endfunction
2449 function! s:Wq(force,...) abort
2450   let bang = a:force ? '!' : ''
2451   if exists('b:fugitive_commit_arguments')
2452     return 'wq'.bang
2453   endif
2454   let result = call(s:function('s:Write'),[a:force]+a:000)
2455   if result =~# '^\%(write\|wq\|echoerr\)'
2456     return s:sub(result,'^write','wq')
2457   else
2458     return result.'|quit'.bang
2459   endif
2460 endfunction
2462 augroup fugitive_commit
2463   autocmd!
2464   autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
2465 augroup END
2467 " Section: Gpush, Gfetch
2469 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush  execute s:Dispatch('<bang>', 'push '.<q-args>)")
2470 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
2472 function! s:Dispatch(bang, args)
2473   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2474   let cwd = getcwd()
2475   let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
2476   try
2477     let b:current_compiler = 'git'
2478     let &l:errorformat = s:common_efm
2479     execute cd fnameescape(s:Tree())
2480     let &l:makeprg = substitute(s:UserCommand() . ' ' . a:args, '\s\+$', '', '')
2481     if exists(':Make') == 2
2482       noautocmd Make
2483     else
2484       silent noautocmd make!
2485       redraw!
2486       return 'call fugitive#Cwindow()'
2487     endif
2488     return ''
2489   finally
2490     let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
2491     if empty(cc) | unlet! b:current_compiler | endif
2492     execute cd fnameescape(cwd)
2493   endtry
2494 endfunction
2496 " Section: Gdiff
2498 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
2499 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
2500 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
2502 augroup fugitive_diff
2503   autocmd!
2504   autocmd BufWinLeave *
2505         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
2506         \   call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
2507         \ endif
2508   autocmd BufWinEnter *
2509         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
2510         \   call s:diffoff() |
2511         \ endif
2512 augroup END
2514 function! s:can_diffoff(buf) abort
2515   return getwinvar(bufwinnr(a:buf), '&diff') &&
2516         \ !empty(getbufvar(a:buf, 'git_dir')) &&
2517         \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
2518 endfunction
2520 function! fugitive#CanDiffoff(buf) abort
2521   return s:can_diffoff(a:buf)
2522 endfunction
2524 function! s:diff_modifier(count) abort
2525   let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
2526   if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
2527     return 'keepalt '
2528   elseif &diffopt =~# 'vertical'
2529     return 'keepalt vert '
2530   elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
2531     return 'keepalt '
2532   else
2533     return 'keepalt vert '
2534   endif
2535 endfunction
2537 function! s:diff_window_count() abort
2538   let c = 0
2539   for nr in range(1,winnr('$'))
2540     let c += getwinvar(nr,'&diff')
2541   endfor
2542   return c
2543 endfunction
2545 function! s:diff_restore() abort
2546   let restore = 'setlocal nodiff noscrollbind'
2547         \ . ' scrollopt=' . &l:scrollopt
2548         \ . (&l:wrap ? ' wrap' : ' nowrap')
2549         \ . ' foldlevel=999'
2550         \ . ' foldmethod=' . &l:foldmethod
2551         \ . ' foldcolumn=' . &l:foldcolumn
2552         \ . ' foldlevel=' . &l:foldlevel
2553         \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
2554   if has('cursorbind')
2555     let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
2556   endif
2557   return restore
2558 endfunction
2560 function! s:diffthis() abort
2561   if !&diff
2562     let w:fugitive_diff_restore = s:diff_restore()
2563     diffthis
2564   endif
2565 endfunction
2567 function! s:diffoff() abort
2568   if exists('w:fugitive_diff_restore')
2569     execute w:fugitive_diff_restore
2570     unlet w:fugitive_diff_restore
2571   else
2572     diffoff
2573   endif
2574 endfunction
2576 function! s:diffoff_all(dir) abort
2577   let curwin = winnr()
2578   for nr in range(1,winnr('$'))
2579     if getwinvar(nr,'&diff')
2580       if nr != winnr()
2581         execute nr.'wincmd w'
2582         let restorewinnr = 1
2583       endif
2584       if exists('b:git_dir') && b:git_dir ==# a:dir
2585         call s:diffoff()
2586       endif
2587     endif
2588   endfor
2589   execute curwin.'wincmd w'
2590 endfunction
2592 function! s:CompareAge(mine, theirs) abort
2593   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
2594   let mine = substitute(a:mine, '^:', '', '')
2595   let theirs = substitute(a:theirs, '^:', '', '')
2596   let my_score    = get(scores, ':'.mine, 0)
2597   let their_score = get(scores, ':'.theirs, 0)
2598   if my_score || their_score
2599     return my_score < their_score ? -1 : my_score != their_score
2600   elseif mine ==# theirs
2601     return 0
2602   endif
2603   let base = s:TreeChomp('merge-base', mine, theirs)
2604   if base ==# mine
2605     return -1
2606   elseif base ==# theirs
2607     return 1
2608   endif
2609   let my_time    = +s:TreeChomp('log','--max-count=1','--pretty=format:%at',a:mine)
2610   let their_time = +s:TreeChomp('log','--max-count=1','--pretty=format:%at',a:theirs)
2611   return my_time < their_time ? -1 : my_time != their_time
2612 endfunction
2614 function! s:Diff(vert,keepfocus,...) abort
2615   let args = copy(a:000)
2616   let post = ''
2617   if get(args, 0) =~# '^+'
2618     let post = remove(args, 0)[1:-1]
2619   endif
2620   let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
2621   let commit = s:DirCommitFile(@%)[1]
2622   let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
2623   if exists(':DiffGitCached')
2624     return 'DiffGitCached'
2625   elseif (empty(args) || args[0] ==# ':') && commit =~# '^[0-1]\=$' && !empty(s:TreeChomp('ls-files', '--unmerged', '--', s:Relative('')))
2626     if v:shell_error
2627       return 'echoerr ' . string("fugitive: error determining merge status of " . s:Relative(''))
2628     endif
2629     let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
2630     let nr = bufnr('')
2631     execute 'leftabove '.vert.'split' s:fnameescape(s:Generate(s:Relative(':2:')))
2632     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2633     let nr2 = bufnr('')
2634     call s:diffthis()
2635     exe back
2636     execute 'rightbelow '.vert.'split' s:fnameescape(s:Generate(s:Relative(':3:')))
2637     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2638     let nr3 = bufnr('')
2639     call s:diffthis()
2640     exe back
2641     call s:diffthis()
2642     execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
2643     execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
2644     return post
2645   elseif len(args)
2646     let arg = join(args, ' ')
2647     if arg ==# ''
2648       return post
2649     elseif arg ==# '/'
2650       let file = s:Relative('/')
2651     elseif arg ==# ':'
2652       let file = s:Relative(':0:')
2653     elseif arg =~# '^:/.'
2654       try
2655         let file = fugitive#RevParse(arg).s:Relative(':')
2656       catch /^fugitive:/
2657         return 'echoerr v:errmsg'
2658       endtry
2659     else
2660       let file = s:Expand(arg)
2661     endif
2662     if file !~# ':' && file !~# '^/' && s:TreeChomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
2663       let file = file.s:Relative(':')
2664     endif
2665   else
2666     let file = s:Relative(empty(commit) ? ':0:' : '/')
2667   endif
2668   try
2669     let spec = s:Generate(file)
2670     let restore = s:diff_restore()
2671     if exists('+cursorbind')
2672       setlocal cursorbind
2673     endif
2674     let w:fugitive_diff_restore = restore
2675     if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
2676       execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
2677     else
2678       execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
2679     endif
2680     let &l:readonly = &l:readonly
2681     redraw
2682     let w:fugitive_diff_restore = restore
2683     let winnr = winnr()
2684     if getwinvar('#', '&diff')
2685       exe back
2686       if !a:keepfocus
2687         call feedkeys(winnr."\<C-W>w", 'n')
2688       endif
2689     endif
2690     return post
2691   catch /^fugitive:/
2692     return 'echoerr v:errmsg'
2693   endtry
2694 endfunction
2696 " Section: Gmove, Gremove
2698 function! s:Move(force, rename, destination) abort
2699   if a:destination =~# '^\./.'
2700     let destination = a:destination[2:-1]
2701   elseif a:destination =~# '^:/:\='
2702     let destination = substitute(a:destination, '^:/:\=', '', '')
2703   elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
2704     let destination = matchstr(a:destination, ')\zs.*')
2705   elseif a:rename && a:destination !~# '^\a\+:\|^/'
2706     let destination = fnamemodify(s:Relative(''), ':h') . '/' . a:destination
2707   else
2708     let destination = a:destination
2709   endif
2710   if isdirectory(@%)
2711     setlocal noswapfile
2712   endif
2713   let message = call('s:TreeChomp', ['mv'] + (a:force ? ['-f'] : []) + ['--', s:Relative(''), destination])
2714   if v:shell_error
2715     let v:errmsg = 'fugitive: '.message
2716     return 'echoerr v:errmsg'
2717   endif
2718   let destination = s:Tree() . '/' . destination
2719   if isdirectory(destination)
2720     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
2721   endif
2722   call fugitive#ReloadStatus()
2723   if empty(s:DirCommitFile(@%)[1])
2724     if isdirectory(destination)
2725       return 'keepalt edit '.s:fnameescape(destination)
2726     else
2727       return 'keepalt saveas! '.s:fnameescape(destination)
2728     endif
2729   else
2730     return 'file '.s:fnameescape(s:Generate(':0:'.destination))
2731   endif
2732 endfunction
2734 function! s:RenameComplete(A,L,P) abort
2735   if a:A =~# '^[.:]\=/'
2736     return fugitive#PathComplete(a:A)
2737   else
2738     let pre = '/' . fnamemodify(s:Relative(''), ':h') . '/'
2739     return map(fugitive#PathComplete(pre.a:A), 'strpart(v:val, len(pre))')
2740   endif
2741 endfunction
2743 function! s:Remove(after, force) abort
2744   if s:DirCommitFile(@%)[1] ==# ''
2745     let cmd = ['rm']
2746   elseif s:DirCommitFile(@%)[1] ==# '0'
2747     let cmd = ['rm','--cached']
2748   else
2749     let v:errmsg = 'fugitive: rm not supported here'
2750     return 'echoerr v:errmsg'
2751   endif
2752   if a:force
2753     let cmd += ['--force']
2754   endif
2755   let message = call('s:TreeChomp', cmd+['--',s:Relative('')])
2756   if v:shell_error
2757     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
2758     return 'echoerr '.string(v:errmsg)
2759   else
2760     call fugitive#ReloadStatus()
2761     return a:after . (a:force ? '!' : '')
2762   endif
2763 endfunction
2765 augroup fugitive_remove
2766   autocmd!
2767   autocmd User Fugitive if s:DirCommitFile(@%)[1] =~# '^0\=$' |
2768         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,fugitive#PathComplete Gmove :execute s:Move(<bang>0,0,<q-args>)" |
2769         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
2770         \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
2771         \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
2772         \ endif
2773 augroup END
2775 " Section: Gblame
2777 function! s:Keywordprg() abort
2778   let args = ' --git-dir='.escape(b:git_dir,"\\\"' ")
2779   if has('gui_running') && !has('win32')
2780     return s:UserCommand() . ' --no-pager' . args . ' log -1'
2781   else
2782     return s:UserCommand() . args . ' show'
2783   endif
2784 endfunction
2786 augroup fugitive_blame
2787   autocmd!
2788   autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:Keywordprg() | endif
2789   autocmd Syntax fugitiveblame call s:BlameSyntax()
2790   autocmd User Fugitive
2791         \ if get(b:, 'fugitive_type') =~# '^\%(file\|blob\|blame\)$' || filereadable(@%) |
2792         \   exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,'<mods>',[<f-args>])" |
2793         \ endif
2794   autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
2795   autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
2796 augroup END
2798 function! s:linechars(pattern) abort
2799   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
2800   if exists('*synconcealed') && &conceallevel > 1
2801     for col in range(1, chars)
2802       let chars -= synconcealed(line('.'), col)[0]
2803     endfor
2804   endif
2805   return chars
2806 endfunction
2808 function! s:Blame(bang, line1, line2, count, mods, args) abort
2809   if exists('b:fugitive_blamed_bufnr')
2810     return 'bdelete'
2811   endif
2812   try
2813     if empty(s:Relative(''))
2814       call s:throw('file or blob required')
2815     endif
2816     if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
2817       call s:throw('unsupported option')
2818     endif
2819     call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
2820     let cmd = ['--no-pager', 'blame', '--show-number']
2821     if a:count
2822       let cmd += ['-L', a:line1 . ',' . a:line1]
2823     endif
2824     let cmd += a:args
2825     if s:DirCommitFile(@%)[1] =~# '\D\|..'
2826       let cmd += [s:DirCommitFile(@%)[1]]
2827     else
2828       let cmd += ['--contents', '-']
2829     endif
2830     let cmd += ['--', s:Relative('')]
2831     let basecmd = escape(s:Prepare(cmd), '!#%')
2832     try
2833       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2834       let tree = s:Tree()
2835       if len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
2836         let cwd = getcwd()
2837         execute cd s:fnameescape(tree)
2838       endif
2839       let error = s:tempname()
2840       let temp = error.'.fugitiveblame'
2841       if &shell =~# 'csh'
2842         silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
2843       else
2844         silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
2845       endif
2846       if exists('l:cwd')
2847         execute cd s:fnameescape(cwd)
2848         unlet cwd
2849       endif
2850       if v:shell_error
2851         call s:throw(join(readfile(error),"\n"))
2852       endif
2853       if a:count
2854         let edit = substitute(a:mods, '^<mods>$', '', '') . get(['edit', 'split', 'pedit'], a:line2 - a:line1, ' split')
2855         return s:BlameCommit(edit, get(readfile(temp), 0, ''))
2856       else
2857         for winnr in range(winnr('$'),1,-1)
2858           call setwinvar(winnr, '&scrollbind', 0)
2859           if exists('+cursorbind')
2860             call setwinvar(winnr, '&cursorbind', 0)
2861           endif
2862           if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
2863             execute winbufnr(winnr).'bdelete'
2864           endif
2865         endfor
2866         let bufnr = bufnr('')
2867         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
2868         if exists('+cursorbind')
2869           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
2870         endif
2871         if &l:wrap
2872           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
2873         endif
2874         if &l:foldenable
2875           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
2876         endif
2877         setlocal scrollbind nowrap nofoldenable
2878         if exists('+cursorbind')
2879           setlocal cursorbind
2880         endif
2881         let top = line('w0') + &scrolloff
2882         let current = line('.')
2883         let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'fugitiveblame', 'args': cmd, 'bufnr': bufnr }
2884         exe 'keepalt leftabove vsplit '.temp
2885         let b:fugitive_blamed_bufnr = bufnr
2886         let b:fugitive_type = 'blame'
2887         let w:fugitive_leave = restore
2888         let b:fugitive_blame_arguments = join(a:args,' ')
2889         execute top
2890         normal! zt
2891         execute current
2892         if exists('+cursorbind')
2893           setlocal cursorbind
2894         endif
2895         setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame buftype=nowrite
2896         if exists('+concealcursor')
2897           setlocal concealcursor=nc conceallevel=2
2898         endif
2899         if exists('+relativenumber')
2900           setlocal norelativenumber
2901         endif
2902         execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
2903         nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
2904         nnoremap <buffer> <silent> g?   :help fugitive-:Gblame<CR>
2905         nnoremap <buffer> <silent> q    :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
2906         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>
2907         nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2908         nnoremap <buffer> <silent> -    :<C-U>exe <SID>BlameJump('')<CR>
2909         nnoremap <buffer> <silent> P    :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
2910         nnoremap <buffer> <silent> ~    :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
2911         nnoremap <buffer> <silent> i    :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2912         nnoremap <buffer> <silent> o    :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
2913         nnoremap <buffer> <silent> O    :<C-U>exe <SID>BlameCommit("tabedit")<CR>
2914         nnoremap <buffer> <silent> p    :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft").' pedit', 0, '', matchstr(getline('.'), '\x\+'), matchstr(getline('.'), '\x\+'))<CR>
2915         nnoremap <buffer> <silent> A    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
2916         nnoremap <buffer> <silent> C    :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
2917         nnoremap <buffer> <silent> D    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
2918         redraw
2919         syncbind
2920       endif
2921     finally
2922       if exists('l:cwd')
2923         execute cd s:fnameescape(cwd)
2924       endif
2925     endtry
2926     return ''
2927   catch /^fugitive:/
2928     return 'echoerr v:errmsg'
2929   endtry
2930 endfunction
2932 function! s:BlameCommit(cmd, ...) abort
2933   let line = a:0 ? a:1 : getline('.')
2934   if line =~# '^0\{4,40\} '
2935     return 'echoerr ' . string('Not Committed Yet')
2936   endif
2937   let cmd = s:Edit(a:cmd, 0, '', matchstr(line, '\x\+'), matchstr(line, '\x\+'))
2938   if cmd =~# '^echoerr'
2939     return cmd
2940   endif
2941   let lnum = matchstr(line, ' \zs\d\+\ze\s\+[([:digit:]]')
2942   let path = matchstr(line, '^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2943   if path ==# ''
2944     let path = fugitive#Path(a:0 ? @% : bufname(b:fugitive_blamed_bufnr), '')
2945   endif
2946   execute cmd
2947   if a:cmd ==# 'pedit'
2948     return ''
2949   endif
2950   if search('^diff .* b/\M'.escape(path,'\').'$','W')
2951     call search('^+++')
2952     let head = line('.')
2953     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
2954       let top = +matchstr(getline('.'),' +\zs\d\+')
2955       let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
2956       if lnum >= top && lnum <= top + len
2957         let offset = lnum - top
2958         if &scrolloff
2959           +
2960           normal! zt
2961         else
2962           normal! zt
2963           +
2964         endif
2965         while offset > 0 && line('.') < line('$')
2966           +
2967           if getline('.') =~# '^[ +]'
2968             let offset -= 1
2969           endif
2970         endwhile
2971         return 'normal! zv'
2972       endif
2973     endwhile
2974     execute head
2975     normal! zt
2976   endif
2977   return ''
2978 endfunction
2980 function! s:BlameJump(suffix) abort
2981   let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
2982   if commit =~# '^0\+$'
2983     let commit = ':0'
2984   endif
2985   let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2986   let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2987   if path ==# ''
2988     let path = fugitive#Path(bufname(b:fugitive_blamed_bufnr), '')
2989   endif
2990   let args = b:fugitive_blame_arguments
2991   let offset = line('.') - line('w0')
2992   let bufnr = bufnr('%')
2993   let winnr = bufwinnr(b:fugitive_blamed_bufnr)
2994   if winnr > 0
2995     exe winnr.'wincmd w'
2996   endif
2997   execute 'Gedit' s:fnameescape(commit . a:suffix . ':' . path)
2998   execute lnum
2999   if winnr > 0
3000     exe bufnr.'bdelete'
3001   endif
3002   if exists(':Gblame')
3003     execute 'Gblame '.args
3004     execute lnum
3005     let delta = line('.') - line('w0') - offset
3006     if delta > 0
3007       execute 'normal! '.delta."\<C-E>"
3008     elseif delta < 0
3009       execute 'normal! '.(-delta)."\<C-Y>"
3010     endif
3011     syncbind
3012   endif
3013   return ''
3014 endfunction
3016 let s:hash_colors = {}
3018 function! s:BlameSyntax() abort
3019   let b:current_syntax = 'fugitiveblame'
3020   let conceal = has('conceal') ? ' conceal' : ''
3021   let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
3022   syn match FugitiveblameBoundary "^\^"
3023   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
3024   syn match FugitiveblameHash       "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3025   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3026   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
3027   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
3028   exec 'syn match FugitiveblameLineNumber         " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
3029   exec 'syn match FugitiveblameOriginalFile       " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
3030   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
3031   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
3032   syn match FugitiveblameShort              " \d\+)" contained contains=FugitiveblameLineNumber
3033   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
3034   hi def link FugitiveblameBoundary           Keyword
3035   hi def link FugitiveblameHash               Identifier
3036   hi def link FugitiveblameUncommitted        Ignore
3037   hi def link FugitiveblameTime               PreProc
3038   hi def link FugitiveblameLineNumber         Number
3039   hi def link FugitiveblameOriginalFile       String
3040   hi def link FugitiveblameOriginalLineNumber Float
3041   hi def link FugitiveblameShort              FugitiveblameDelimiter
3042   hi def link FugitiveblameDelimiter          Delimiter
3043   hi def link FugitiveblameNotCommittedYet    Comment
3044   let seen = {}
3045   for lnum in range(1, line('$'))
3046     let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
3047     if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
3048       continue
3049     endif
3050     let seen[hash] = 1
3051     if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
3052           \ && empty(get(s:hash_colors, hash))
3053       let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
3054       let color = csapprox#per_component#Approximate(r, g, b)
3055       if color == 16 && &background ==# 'dark'
3056         let color = 8
3057       endif
3058       let s:hash_colors[hash] = ' ctermfg='.color
3059     else
3060       let s:hash_colors[hash] = ''
3061     endif
3062     exe 'syn match FugitiveblameHash'.hash.'       "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
3063   endfor
3064   call s:RehighlightBlame()
3065 endfunction
3067 function! s:RehighlightBlame() abort
3068   for [hash, cterm] in items(s:hash_colors)
3069     if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
3070       exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
3071     else
3072       exe 'hi link FugitiveblameHash'.hash.' Identifier'
3073     endif
3074   endfor
3075 endfunction
3077 " Section: Gbrowse
3079 call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,fugitive#Complete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
3081 let s:redirects = {}
3083 function! s:Browse(bang,line1,count,...) abort
3084   try
3085     let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
3086     if a:0
3087       let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
3088       let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
3089     else
3090       let remote = ''
3091       let rev = ''
3092     endif
3093     if rev ==# ''
3094       let rev = s:DirRev(@%)[1]
3095     endif
3096     if rev =~# '^:\=$'
3097       let expanded = s:Relative('/')
3098     else
3099       let expanded = s:Expand(rev)
3100     endif
3101     let cdir = fugitive#CommonDir(b:git_dir)
3102     for dir in ['tags/', 'heads/', 'remotes/']
3103       if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . dir . expanded)
3104         let expanded = '/.git/refs/' . dir . expanded
3105       endif
3106     endfor
3107     let full = s:Generate(expanded)
3108     let commit = ''
3109     if full =~? '^fugitive:'
3110       let [dir, commit, path] = s:DirCommitFile(full)
3111       if commit =~# '^:\=\d$'
3112         let commit = ''
3113       endif
3114       if commit =~ '..'
3115         let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
3116         let branch = matchstr(expanded, '^[^:]*')
3117       else
3118         let type = 'blob'
3119       endif
3120       let path = path[1:-1]
3121     elseif empty(s:Tree())
3122       let path = '.git/' . full[strlen(b:git_dir)+1:-1]
3123       let type = ''
3124     else
3125       let path = full[strlen(s:Tree())+1:-1]
3126       if path =~# '^\.git/'
3127         let type = ''
3128       elseif isdirectory(full)
3129         let type = 'tree'
3130       else
3131         let type = 'blob'
3132       endif
3133     endif
3134     if type ==# 'tree' && !empty(path)
3135       let path = s:sub(path, '/\=$', '/')
3136     endif
3137     if path =~# '^\.git/.*HEAD$' && filereadable(b:git_dir . '/' . path[5:-1])
3138       let body = readfile(b:git_dir . '/' . path[5:-1])[0]
3139       if body =~# '^\x\{40\}$'
3140         let commit = body
3141         let type = 'commit'
3142         let path = ''
3143       elseif body =~# '^ref: refs/'
3144         let path = '.git/' . matchstr(body,'ref: \zs.*')
3145       endif
3146     endif
3148     let merge = ''
3149     if path =~# '^\.git/refs/remotes/.'
3150       if empty(remote)
3151         let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
3152         let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3153       else
3154         let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3155         let path = '.git/refs/heads/'.merge
3156       endif
3157     elseif path =~# '^\.git/refs/heads/.'
3158       let branch = path[16:-1]
3159     elseif !exists('branch')
3160       let branch = FugitiveHead()
3161     endif
3162     if !empty(branch)
3163       let r = fugitive#Config('branch.'.branch.'.remote')
3164       let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
3165       if r ==# '.' && !empty(m)
3166         let r2 = fugitive#Config('branch.'.m.'.remote')
3167         if r2 !~# '^\.\=$'
3168           let r = r2
3169           let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
3170         endif
3171       endif
3172       if empty(remote)
3173         let remote = r
3174       endif
3175       if r ==# '.' || r ==# remote
3176         let merge = m
3177         if path =~# '^\.git/refs/heads/.'
3178           let path = '.git/refs/heads/'.merge
3179         endif
3180       endif
3181     endif
3183     let line1 = a:count > 0 ? a:line1 : 0
3184     let line2 = a:count > 0 ? a:count : 0
3185     if empty(commit) && path !~# '^\.git/'
3186       if a:line1 && !a:count && !empty(merge)
3187         let commit = merge
3188       else
3189         let commit = ''
3190         if len(merge)
3191           let remotehead = cdir . '/refs/remotes/' . remote . '/' . merge
3192           let commit = filereadable(remotehead) ? get(readfile(remotehead), 0, '') : ''
3193           if a:count && !a:0 && commit =~# '^\x\{40\}$'
3194             let blame_list = s:tempname()
3195             call writefile([commit, ''], blame_list, 'b')
3196             let blame_in = s:tempname()
3197             silent exe '%write' blame_in
3198             let blame = split(s:TreeChomp('blame', '--contents', blame_in, '-L', a:line1.','.a:count, '-S', blame_list, '-s', '--show-number', '--', path), "\n")
3199             if !v:shell_error
3200               let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
3201               if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
3202                 let line1 = +matchstr(blame[0], blame_regex)
3203                 let line2 = +matchstr(blame[-1], blame_regex)
3204               else
3205                 call s:throw("Can't browse to uncommitted change")
3206               endif
3207             endif
3208           endif
3209         endif
3210       endif
3211       if empty(commit)
3212         let commit = readfile(b:git_dir . '/HEAD', '', 1)[0]
3213       endif
3214       let i = 0
3215       while commit =~# '^ref: ' && i < 10
3216         let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
3217         let i -= 1
3218       endwhile
3219     endif
3221     if empty(remote)
3222       let remote = '.'
3223     endif
3224     let raw = fugitive#RemoteUrl(remote)
3225     if empty(raw)
3226       let raw = remote
3227     endif
3229     if raw =~# '^https\=://' && s:executable('curl')
3230       if !has_key(s:redirects, raw)
3231         let s:redirects[raw] = matchstr(system('curl -I ' .
3232               \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
3233               \ 'Location: \zs\S\+\ze/info/refs?')
3234       endif
3235       if len(s:redirects[raw])
3236         let raw = s:redirects[raw]
3237       endif
3238     endif
3240     let opts = {
3241           \ 'dir': b:git_dir,
3242           \ 'repo': fugitive#repo(),
3243           \ 'remote': raw,
3244           \ 'revision': 'No longer provided',
3245           \ 'commit': commit,
3246           \ 'path': path,
3247           \ 'type': type,
3248           \ 'line1': line1,
3249           \ 'line2': line2}
3251     for Handler in get(g:, 'fugitive_browse_handlers', [])
3252       let url = call(Handler, [copy(opts)])
3253       if !empty(url)
3254         break
3255       endif
3256     endfor
3258     if empty(url)
3259       call s:throw("No Gbrowse handler found for '".raw."'")
3260     endif
3262     let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
3263     if a:bang
3264       if has('clipboard')
3265         let @+ = url
3266       endif
3267       return 'echomsg '.string(url)
3268     elseif exists(':Browse') == 2
3269       return 'echomsg '.string(url).'|Browse '.url
3270     else
3271       if !exists('g:loaded_netrw')
3272         runtime! autoload/netrw.vim
3273       endif
3274       if exists('*netrw#BrowseX')
3275         return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
3276       else
3277         return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
3278       endif
3279     endif
3280   catch /^fugitive:/
3281     return 'echoerr v:errmsg'
3282   endtry
3283 endfunction
3285 " Section: Go to file
3287 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
3288 function! fugitive#MapCfile(...) abort
3289   exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
3290   let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
3291   if !exists('g:fugitive_no_maps')
3292     call s:map('n', 'gf',          '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
3293     call s:map('n', '<C-W>f',     '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3294     call s:map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3295     call s:map('n', '<C-W>gf',  '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
3296     call s:map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
3297   endif
3298 endfunction
3300 function! s:ContainingCommit() abort
3301   let commit = s:Owner(@%)
3302   return empty(commit) ? 'HEAD' : commit
3303 endfunction
3305 function! s:NavigateUp(count) abort
3306   let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
3307   let c = a:count
3308   while c
3309     if rev =~# ':.*/.'
3310       let rev = matchstr(rev, '.*\ze/.\+', '')
3311     elseif rev =~# '.:.'
3312       let rev = matchstr(rev, '^.[^:]*:')
3313     elseif rev =~# '^:'
3314       let rev = 'HEAD^{}'
3315     elseif rev =~# ':$'
3316       let rev = rev[0:-2]
3317     else
3318       return rev.'~'.c
3319     endif
3320     let c -= 1
3321   endwhile
3322   return rev
3323 endfunction
3325 function! fugitive#MapJumps(...) abort
3326   if get(b:, 'fugitive_type', '') ==# 'blob'
3327     nnoremap <buffer> <silent> <CR>    :<C-U>.Gblame<CR>
3328   else
3329     nnoremap <buffer> <silent> <CR>    :<C-U>exe <SID>GF("edit")<CR>
3330   endif
3331   if !&modifiable
3332     let nowait = v:version >= 704 ? '<nowait>' : ''
3333     if get(b:, 'fugitive_type', '') ==# 'blob'
3334       nnoremap <buffer> <silent> o     :<C-U>.,.+1Gblame<CR>
3335       nnoremap <buffer> <silent> S     :<C-U>vertical .,.+1Gblame<CR>
3336       nnoremap <buffer> <silent> O     :<C-U>tab .,.+1Gblame<CR>
3337       nnoremap <buffer> <silent> p     :<C-U>.,.+2Gblame<CR>
3338     else
3339       nnoremap <buffer> <silent> o     :<C-U>exe <SID>GF("split")<CR>
3340       nnoremap <buffer> <silent> S     :<C-U>exe <SID>GF("vsplit")<CR>
3341       nnoremap <buffer> <silent> O     :<C-U>exe <SID>GF("tabedit")<CR>
3342       nnoremap <buffer> <silent> p     :<C-U>exe <SID>GF("pedit")<CR>
3343     endif
3344     exe "nnoremap <buffer> <silent>" nowait  "-     :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>NavigateUp(v:count1))<Bar> if getline(1) =~# '^tree \x\{40\}$' && empty(getline(2))<Bar>call search('^'.escape(expand('#:t'),'.*[]~\').'/\=$','wc')<Bar>endif<CR>"
3345     nnoremap <buffer> <silent> P     :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>
3346     nnoremap <buffer> <silent> ~     :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>
3347     nnoremap <buffer> <silent> C     :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3348     nnoremap <buffer> <silent> cc    :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3349     nnoremap <buffer> <silent> co    :<C-U>exe 'Gsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3350     nnoremap <buffer> <silent> cS    :<C-U>exe 'Gvsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3351     nnoremap <buffer> <silent> cO    :<C-U>exe 'Gtabedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3352     nnoremap <buffer> <silent> cp    :<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3353     nmap     <buffer>          .     <SID>: <Plug><cfile><Home>
3354   endif
3355 endfunction
3357 function! s:StatusCfile(...) abort
3358   let pre = ''
3359   let tree = FugitiveTreeForGitDir(b:git_dir)
3360   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
3361   if getline('.') =~# '^.\=\trenamed:.* -> '
3362     return lead . matchstr(getline('.'),' -> \zs.*')
3363   elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
3364     return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
3365   elseif getline('.') =~# '^.\=\t.'
3366     return lead . matchstr(getline('.'),'\t\zs.*')
3367   elseif getline('.') =~# ': needs merge$'
3368     return lead . matchstr(getline('.'),'.*\ze: needs merge$')
3369   elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
3370     return 'HEAD'
3371   elseif getline('.') =~# '^\%(. \)\=On branch '
3372     return 'refs/heads/'.getline('.')[12:]
3373   elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
3374     return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
3375   else
3376     return ''
3377   endif
3378 endfunction
3380 function! fugitive#StatusCfile() abort
3381   let file = s:Generate(s:StatusCfile())
3382   return empty(file) ? "\<C-R>\<C-F>" : s:fnameescape(file)
3383 endfunction
3385 function! s:cfile() abort
3386   try
3387     let myhash = s:DirRev(@%)[1]
3388     if len(myhash)
3389       try
3390         let myhash = fugitive#RevParse(myhash)
3391       catch /^fugitive:/
3392         let myhash = ''
3393       endtry
3394     endif
3395     if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
3396       let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
3397     endif
3399     let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
3401     let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
3402           \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
3404     if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
3405       return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
3406     elseif showtree
3407       return [treebase . s:sub(getline('.'),'/$','')]
3409     else
3411       let dcmds = []
3413       " Index
3414       if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
3415         let ref = matchstr(getline('.'),'\x\{40\}')
3416         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
3417         return [file]
3418       endif
3420       if getline('.') =~# '^ref: '
3421         let ref = strpart(getline('.'),5)
3423       elseif getline('.') =~# '^commit \x\{40\}\>'
3424         let ref = matchstr(getline('.'),'\x\{40\}')
3425         return [ref]
3427       elseif getline('.') =~# '^parent \x\{40\}\>'
3428         let ref = matchstr(getline('.'),'\x\{40\}')
3429         let line = line('.')
3430         let parent = 0
3431         while getline(line) =~# '^parent '
3432           let parent += 1
3433           let line -= 1
3434         endwhile
3435         return [ref]
3437       elseif getline('.') =~# '^tree \x\{40\}$'
3438         let ref = matchstr(getline('.'),'\x\{40\}')
3439         if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
3440           let ref = myhash.':'
3441         endif
3442         return [ref]
3444       elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
3445         let ref = matchstr(getline('.'),'\x\{40\}')
3446         let type = matchstr(getline(line('.')+1),'type \zs.*')
3448       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
3449         let ref = s:DirRev(@%)[1]
3451       elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
3452         let ref = matchstr(getline('.'),'\x\{40\}')
3453         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
3455       elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
3456         let ref = getline('.')[4:]
3458       elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
3459         let type = getline('.')[0]
3460         let lnum = line('.') - 1
3461         let offset = 0
3462         while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3463           if getline(lnum) =~# '^[ '.type.']'
3464             let offset += 1
3465           endif
3466           let lnum -= 1
3467         endwhile
3468         let offset += matchstr(getline(lnum), type.'\zs\d\+')
3469         let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
3470         let dcmds = [offset, 'normal!zv']
3472       elseif getline('.') =~# '^rename from '
3473         let ref = 'a/'.getline('.')[12:]
3474       elseif getline('.') =~# '^rename to '
3475         let ref = 'b/'.getline('.')[10:]
3477       elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3478         let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
3479         let offset = matchstr(getline('.'), '+\zs\d\+')
3481         let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3482         let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3483         let dcmd = 'Gdiff! +'.offset
3485       elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3486         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3487         let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3488         let dcmd = 'Gdiff!'
3490       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3491         let line = getline(line('.')-1)
3492         let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3493         let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3494         let dcmd = 'Gdiff!'
3496       elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
3497         let ref = getline('.')
3499       elseif expand('<cword>') =~# '^\x\{7,40\}\>'
3500         return [expand('<cword>')]
3502       else
3503         let ref = ''
3504       endif
3506       let prefixes = {
3507             \ '1': '',
3508             \ '2': '',
3509             \ 'b': ':0:',
3510             \ 'i': ':0:',
3511             \ 'o': '',
3512             \ 'w': ''}
3514       if len(myhash)
3515         let prefixes.a = myhash.'^:'
3516         let prefixes.b = myhash.':'
3517       endif
3518       let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3519       if exists('dref')
3520         let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3521       endif
3523       if ref ==# '/dev/null'
3524         " Empty blob
3525         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
3526       endif
3528       if exists('dref')
3529         return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
3530       elseif ref != ""
3531         return [ref] + dcmds
3532       endif
3534     endif
3535     return []
3536   endtry
3537 endfunction
3539 function! s:GF(mode) abort
3540   try
3541     let results = &filetype ==# 'gitcommit' ? [s:StatusCfile()] : s:cfile()
3542   catch /^fugitive:/
3543     return 'echoerr v:errmsg'
3544   endtry
3545   if len(results) > 1
3546     return 'G' . a:mode .
3547           \ ' +' . escape(join(results[1:-1], '|'), '| ') . ' ' .
3548           \ s:fnameescape(results[0])
3549   elseif len(results)
3550     return 'G' . a:mode . ' ' . s:fnameescape(results[0])
3551   else
3552     return ''
3553   endif
3554 endfunction
3556 function! fugitive#Cfile() abort
3557   let pre = ''
3558   let results = s:cfile()
3559   if empty(results)
3560     let cfile = expand('<cfile>')
3561     if &includeexpr =~# '\<v:fname\>'
3562       sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
3563     endif
3564     return cfile
3565   elseif len(results) > 1
3566     let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
3567   endif
3568   return pre . s:fnameescape(s:Generate(results[0]))
3569 endfunction
3571 " Section: Statusline
3573 function! fugitive#Statusline(...) abort
3574   if !exists('b:git_dir')
3575     return ''
3576   endif
3577   let status = ''
3578   let commit = s:DirCommitFile(@%)[1]
3579   if len(commit)
3580     let status .= ':' . commit[0:7]
3581   endif
3582   let status .= '('.FugitiveHead(7).')'
3583   return '[Git'.status.']'
3584 endfunction
3586 function! fugitive#statusline(...) abort
3587   return fugitive#Statusline()
3588 endfunction
3590 function! fugitive#head(...) abort
3591   if !exists('b:git_dir')
3592     return ''
3593   endif
3595   return fugitive#Head(a:0 ? a:1 : 0)
3596 endfunction
3598 " Section: Folding
3600 function! fugitive#Foldtext() abort
3601   if &foldmethod !=# 'syntax'
3602     return foldtext()
3603   endif
3605   let line_foldstart = getline(v:foldstart)
3606   if line_foldstart =~# '^diff '
3607     let [add, remove] = [-1, -1]
3608     let filename = ''
3609     for lnum in range(v:foldstart, v:foldend)
3610       let line = getline(lnum)
3611       if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
3612         let filename = line[6:-1]
3613       endif
3614       if line =~# '^+'
3615         let add += 1
3616       elseif line =~# '^-'
3617         let remove += 1
3618       elseif line =~# '^Binary '
3619         let binary = 1
3620       endif
3621     endfor
3622     if filename ==# ''
3623       let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
3624     endif
3625     if filename ==# ''
3626       let filename = line_foldstart[5:-1]
3627     endif
3628     if exists('binary')
3629       return 'Binary: '.filename
3630     else
3631       return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3632     endif
3633   elseif line_foldstart =~# '^# .*:$'
3634     let lines = getline(v:foldstart, v:foldend)
3635     call filter(lines, 'v:val =~# "^#\t"')
3636     cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
3637     cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
3638     return line_foldstart.' '.join(lines, ', ')
3639   endif
3640   return foldtext()
3641 endfunction
3643 function! fugitive#foldtext() abort
3644   return fugitive#Foldtext()
3645 endfunction
3647 augroup fugitive_folding
3648   autocmd!
3649   autocmd User Fugitive
3650         \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3651         \    set foldtext=fugitive#Foldtext() |
3652         \ endif
3653 augroup END
3655 " Section: Initialization
3657 function! fugitive#Init() abort
3658   if exists('#User#FugitiveBoot')
3659     try
3660       let [save_mls, &modelines] = [&mls, 0]
3661       doautocmd User FugitiveBoot
3662     finally
3663       let &mls = save_mls
3664     endtry
3665   endif
3666   if !exists('g:fugitive_no_maps')
3667     call s:map('c', '<C-R><C-G>', '<SID>fnameescape(fugitive#Object(@%))', '<expr>')
3668     call s:map('n', 'y<C-G>', ':<C-U>call setreg(v:register, fugitive#Object(@%))<CR>', '<silent>')
3669   endif
3670   if expand('%:p') =~# ':[\/][\/]'
3671     let &l:path = s:sub(&path, '^\.%(,|$)', '')
3672   endif
3673   if stridx(&tags, escape(b:git_dir, ', ')) == -1
3674     if filereadable(b:git_dir.'/tags')
3675       let &l:tags = escape(b:git_dir.'/tags', ', ').','.&tags
3676     endif
3677     if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
3678       let &l:tags = escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.&tags
3679     endif
3680   endif
3681   try
3682     let [save_mls, &modelines] = [&mls, 0]
3683     call s:define_commands()
3684     doautocmd User Fugitive
3685   finally
3686     let &mls = save_mls
3687   endtry
3688 endfunction
3690 function! fugitive#is_git_dir(path) abort
3691   return FugitiveIsGitDir(a:path)
3692 endfunction
3694 function! fugitive#extract_git_dir(path) abort
3695   return FugitiveExtractGitDir(a:path)
3696 endfunction
3698 function! fugitive#detect(path) abort
3699   return FugitiveDetect(a:path)
3700 endfunction