Tab complete > arguments
[vim-fugitive.git] / autoload / fugitive.vim
blobc06341438ad5fc45b2e4328a77e76c23062b92fd
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 elseif g:fugitive_git_executable =~# '^\w\+='
12   let g:fugitive_git_executable = 'env ' . g:fugitive_git_executable
13 endif
15 " Section: Utility
17 function! s:function(name) abort
18   return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '.*\zs<SNR>\d\+_'),''))
19 endfunction
21 function! s:sub(str,pat,rep) abort
22   return substitute(a:str,'\v\C'.a:pat,a:rep,'')
23 endfunction
25 function! s:gsub(str,pat,rep) abort
26   return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
27 endfunction
29 function! s:Uniq(list) abort
30   let i = 0
31   let seen = {}
32   while i < len(a:list)
33     let str = string(a:list[i])
34     if has_key(seen, str)
35       call remove(a:list, i)
36     else
37       let seen[str] = 1
38       let i += 1
39     endif
40   endwhile
41   return a:list
42 endfunction
44 function! s:winshell() abort
45   return has('win32') && &shellcmdflag =~# '^/\|^-Command$'
46 endfunction
48 function! s:shellesc(arg) abort
49   if type(a:arg) == type([])
50     return join(map(copy(a:arg), 's:shellesc(v:val)'))
51   elseif a:arg =~ '^[A-Za-z0-9_/:.-]\+$'
52     return a:arg
53   elseif s:winshell()
54     return '"'.s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"').'"'
55   else
56     return shellescape(a:arg)
57   endif
58 endfunction
60 let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
61 function! s:fnameescape(file) abort
62   if type(a:file) == type([])
63     return join(map(copy(a:file), 's:fnameescape(v:val)'))
64   elseif exists('*fnameescape')
65     return fnameescape(a:file)
66   else
67     return escape(a:file, s:fnameescape)
68   endif
69 endfunction
71 function! s:throw(string) abort
72   throw 'fugitive: '.a:string
73 endfunction
75 function! s:DirCheck(...) abort
76   if !empty(a:0 ? s:Dir(a:1) : s:Dir())
77     return ''
78   elseif empty(bufname(''))
79     return 'return ' . string('echoerr "fugitive: blank buffer unsupported (edit a file from a repository)"')
80   else
81     return 'return ' . string('echoerr "fugitive: file does not belong to a Git repository"')
82   endif
83 endfunction
85 function! s:Mods(mods, ...) abort
86   let mods = substitute(a:mods, '\C<mods>', '', '')
87   let mods = mods =~# '\S$' ? mods . ' ' : mods
88   if a:0 && mods !~# '\<\%(aboveleft\|belowright\|leftabove\|rightbelow\|topleft\|botright\|tab\)\>'
89     let mods = a:1 . ' ' . mods
90   endif
91   return substitute(mods, '\s\+', ' ', 'g')
92 endfunction
94 function! s:Slash(path) abort
95   if exists('+shellslash')
96     return tr(a:path, '\', '/')
97   else
98     return a:path
99   endif
100 endfunction
102 function! s:Resolve(path) abort
103   let path = resolve(a:path)
104   if has('win32')
105     let path = FugitiveVimPath(fnamemodify(fnamemodify(path, ':h'), ':p') . fnamemodify(path, ':t'))
106   endif
107   return path
108 endfunction
110 function! s:cpath(path, ...) abort
111   if exists('+fileignorecase') && &fileignorecase
112     let path = FugitiveVimPath(tolower(a:path))
113   else
114     let path = FugitiveVimPath(a:path)
115   endif
116   return a:0 ? path ==# s:cpath(a:1) : path
117 endfunction
119 function! s:Cd(...) abort
120   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'
121   if !a:0
122     return cd
123   endif
124   let cwd = getcwd()
125   if s:cpath(cwd, a:1)
126     return ''
127   endif
128   exe cd s:fnameescape(a:1)
129   return cd . ' ' . s:fnameescape(cwd)
130 endfunction
132 let s:executables = {}
134 function! s:executable(binary) abort
135   if !has_key(s:executables, a:binary)
136     let s:executables[a:binary] = executable(a:binary)
137   endif
138   return s:executables[a:binary]
139 endfunction
141 function! s:DoAutocmd(cmd) abort
142   if v:version >= 704 || (v:version == 703 && has('patch442'))
143     return 'doautocmd <nomodeline>' . a:cmd
144   elseif &modelines > 0
145     return 'try|set modelines=0|doautocmd ' . a:cmd . '|finally|set modelines=' . &modelines . '|endtry'
146   else
147     return 'doautocmd ' . a:cmd
148   endif
149 endfunction
151 let s:nowait = v:version >= 704 ? '<nowait>' : ''
153 function! s:Map(mode, lhs, rhs, ...) abort
154   for mode in split(a:mode, '\zs')
155     let flags = (a:0 ? a:1 : '') . (a:rhs =~# '<Plug>' ? '' : '<script>')
156     let head = a:lhs
157     let tail = ''
158     let keys = get(g:, mode.'remap', {})
159     if type(keys) == type([])
160       return
161     endif
162     while !empty(head)
163       if has_key(keys, head)
164         let head = keys[head]
165         if empty(head)
166           return
167         endif
168         break
169       endif
170       let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
171       let head = substitute(head, '<[^<>]*>$\|.$', '', '')
172     endwhile
173     if flags !~# '<unique>' || empty(mapcheck(head.tail, mode))
174       exe mode.'map <buffer>' s:nowait flags head.tail a:rhs
175       if a:0 > 1
176         let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
177               \ '|sil! exe "' . mode . 'unmap <buffer> ' . head.tail . '"'
178       endif
179     endif
180   endfor
181 endfunction
183 " Section: Quickfix
185 function! s:QuickfixGet(nr, ...) abort
186   if a:nr < 0
187     return call('getqflist', a:000)
188   else
189     return call('getloclist', [a:nr] + a:000)
190   endif
191 endfunction
193 function! s:QuickfixSet(nr, ...) abort
194   if a:nr < 0
195     return call('setqflist', a:000)
196   else
197     return call('setloclist', [a:nr] + a:000)
198   endif
199 endfunction
201 function! s:QuickfixCreate(nr, opts) abort
202   if has('patch-7.4.2200')
203     call s:QuickfixSet(a:nr, [], ' ', a:opts)
204   else
205     call s:QuickfixSet(a:nr, [], ' ')
206   endif
207 endfunction
209 function! s:QuickfixStream(nr, title, cmd, first, callback, ...) abort
210   call s:QuickfixCreate(a:nr, {'title': a:title})
211   let winnr = winnr()
212   exe a:nr < 0 ? 'copen' : 'lopen'
213   if winnr != winnr()
214     wincmd p
215   endif
217   let buffer = []
218   let lines = split(s:SystemError(s:shellesc(a:cmd))[0], "\n")
219   for line in lines
220     call extend(buffer, call(a:callback, a:000 + [line]))
221     if len(buffer) >= 20
222       call s:QuickfixSet(a:nr, remove(buffer, 0, -1), 'a')
223       redraw
224     endif
225   endfor
226   call s:QuickfixSet(a:nr, extend(buffer, call(a:callback, a:000 + [0])), 'a')
228   if a:first && len(s:QuickfixGet(a:nr))
229     call s:BlurStatus()
230     return a:nr < 0 ? 'cfirst' : 'lfirst'
231   else
232     return 'exe'
233   endif
234 endfunction
236 " Section: Git
238 function! s:UserCommandList(...) abort
239   let git = split(get(g:, 'fugitive_git_command', g:fugitive_git_executable), '\s\+')
240   let dir = a:0 ? s:Dir(a:1) : ''
241   if len(dir)
242     let tree = s:Tree(dir)
243     if empty(tree)
244       call add(git, '--git-dir=' . FugitiveGitPath(dir))
245     elseif len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
246       if fugitive#GitVersion(1, 8, 5)
247         call extend(git, ['-C', FugitiveGitPath(tree)])
248       else
249         throw 'fugitive: Git 1.8.5 or higher required to change directory'
250       endif
251     endif
252   endif
253   return git
254 endfunction
256 function! s:UserCommand(...) abort
257   return s:shellesc(call('s:UserCommandList', a:0 ? [a:1] : []) + (a:0 ? a:2 : []))
258 endfunction
260 let s:git_versions = {}
261 function! fugitive#GitVersion(...) abort
262   if !has_key(s:git_versions, g:fugitive_git_executable)
263     let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), '\d[^[:space:]]\+')
264   endif
265   if !a:0
266     return s:git_versions[g:fugitive_git_executable]
267   endif
268   let components = split(s:git_versions[g:fugitive_git_executable], '\D\+')
269   if empty(components)
270     return -1
271   endif
272   for i in range(len(a:000))
273     if a:000[i] > +get(components, i)
274       return 0
275     elseif a:000[i] < +get(components, i)
276       return 1
277     endif
278   endfor
279   return a:000[i] ==# get(components, i)
280 endfunction
282 let s:commondirs = {}
283 function! fugitive#CommonDir(dir) abort
284   if empty(a:dir)
285     return ''
286   endif
287   if !has_key(s:commondirs, a:dir)
288     if getfsize(a:dir . '/HEAD') < 10
289       let s:commondirs[a:dir] = ''
290     elseif filereadable(a:dir . '/commondir')
291       let cdir = get(readfile(a:dir . '/commondir', 1), 0, '')
292       if cdir =~# '^/\|^\a:/'
293         let s:commondirs[a:dir] = s:Slash(FugitiveVimPath(cdir))
294       else
295         let s:commondirs[a:dir] = simplify(a:dir . '/' . cdir)
296       endif
297     else
298       let s:commondirs[a:dir] = a:dir
299     endif
300   endif
301   return s:commondirs[a:dir]
302 endfunction
304 function! s:Dir(...) abort
305   return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
306 endfunction
308 function! s:Tree(...) abort
309   return a:0 ? FugitiveWorkTree(a:1) : FugitiveWorkTree()
310 endfunction
312 function! s:HasOpt(args, ...) abort
313   let args = a:args[0 : index(a:args, '--')]
314   let opts = copy(a:000)
315   if type(opts[0]) == type([])
316     if empty(args) || index(opts[0], args[0]) == -1
317       return 0
318     endif
319     call remove(opts, 0)
320   endif
321   for opt in opts
322     if index(args, opt) != -1
323       return 1
324     endif
325   endfor
326 endfunction
328 function! s:PreparePathArgs(cmd, dir, literal) abort
329   let literal_supported = fugitive#GitVersion(1, 9)
330   if a:literal && literal_supported
331     call insert(a:cmd, '--literal-pathspecs')
332   endif
333   let split = index(a:cmd, '--')
334   for i in range(split < 0 ? len(a:cmd) : split)
335     if type(a:cmd[i]) == type(0)
336       let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
337     endif
338   endfor
339   if split < 0
340     return a:cmd
341   endif
342   for i in range(split + 1, len(a:cmd) - 1)
343     if type(a:cmd[i]) == type(0)
344       let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
345     elseif a:literal
346       let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
347     elseif !literal_supported
348       let a:cmd[i] = substitute(a:cmd[i], '^:\%(/\|([^)]*)\)\=:\=', './', '')
349     endif
350   endfor
351   return a:cmd
352 endfunction
354 let s:prepare_env = {
355       \ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
356       \ 'core.editor': 'GIT_EDITOR',
357       \ 'core.askpass': 'GIT_ASKPASS',
358       \ }
359 function! fugitive#PrepareDirEnvArgv(...) abort
360   if a:0 && type(a:1) ==# type([])
361     let cmd = a:000[1:-1] + a:1
362   else
363     let cmd = copy(a:000)
364   endif
365   let env = {}
366   let i = 0
367   while i < len(cmd)
368     if cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
369       let dir = remove(cmd, 0)
370     elseif cmd[i] =~# '^--git-dir='
371       let dir = remove(cmd, 0)[10:-1]
372     elseif type(cmd[i]) ==# type(0)
373       let dir = s:Dir(remove(cmd, i))
374     elseif cmd[i] ==# '-c' && len(cmd) > i + 1
375       let key = matchstr(cmd[i+1], '^[^=]*')
376       if has_key(s:prepare_env, tolower(key)) || key !~# '\.'
377         let var = get(s:prepare_env, tolower(key), key)
378         let val = matchstr(cmd[i+1], '=\zs.*')
379         let env[var] = val
380       endif
381       if fugitive#GitVersion(1, 8) && cmd[i+1] =~# '\.'
382         let i += 2
383       else
384         call remove(cmd, i, i + 1)
385       endif
386     elseif cmd[i] =~# '^--.*pathspecs$'
387       let explicit_pathspec_option = 1
388       if fugitive#GitVersion(1, 9)
389         let i += 1
390       else
391         call remove(cmd, i)
392       endif
393     elseif cmd[i] !~# '^-'
394       break
395     else
396       let i += 1
397     endif
398   endwhile
399   if !exists('dir')
400     let dir = s:Dir()
401   endif
402   call s:PreparePathArgs(cmd, dir, !exists('explicit_pathspec_option'))
403   return [dir, env, cmd]
404 endfunction
406 function! s:BuildShell(dir, env, args) abort
407   let cmd = copy(a:args)
408   let tree = s:Tree(a:dir)
409   let pre = ''
410   for [var, val] in items(a:env)
411     if s:winshell()
412       let pre .= 'set ' . var . '=' . s:shellesc(val) . '& '
413     else
414       let pre = (len(pre) ? pre : 'env ') . var . '=' . s:shellesc(val) . ' '
415     endif
416   endfor
417   if empty(tree) || index(cmd, '--') == len(cmd) - 1
418     call insert(cmd, '--git-dir=' . FugitiveGitPath(a:dir))
419   elseif fugitive#GitVersion(1, 8, 5)
420     call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
421   else
422     let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? '& ' : '; ') . pre
423   endif
424   return pre . g:fugitive_git_executable . ' ' . join(map(cmd, 's:shellesc(v:val)'))
425 endfunction
427 function! fugitive#Prepare(...) abort
428   let [dir, env, argv] = call('fugitive#PrepareDirEnvArgv', a:000)
429   return s:BuildShell(dir, env, argv)
430 endfunction
432 function! s:SystemError(cmd, ...) abort
433   try
434     if &shellredir ==# '>' && &shell =~# 'sh\|cmd'
435       let shellredir = &shellredir
436       if &shell =~# 'csh'
437         set shellredir=>&
438       else
439         set shellredir=>%s\ 2>&1
440       endif
441     endif
442     let out = call('system', [type(a:cmd) ==# type([]) ? fugitive#Prepare(a:cmd) : a:cmd] + a:000)
443     return [out, v:shell_error]
444   catch /^Vim\%((\a\+)\)\=:E484:/
445     let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
446     call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
447     call map(opts, 'v:val."=".eval("&".v:val)')
448     call s:throw('failed to run `' . a:cmd . '` with ' . join(opts, ' '))
449   finally
450     if exists('shellredir')
451       let &shellredir = shellredir
452     endif
453   endtry
454 endfunction
456 function! s:ChompError(...) abort
457   let [out, exec_error] = s:SystemError(call('fugitive#Prepare', a:000))
458   return [s:sub(out, '\n$', ''), exec_error]
459 endfunction
461 function! s:ChompDefault(default, ...) abort
462   let [out, exec_error] = call('s:ChompError', a:000)
463   return exec_error ? a:default : out
464 endfunction
466 function! s:LinesError(...) abort
467   let [out, exec_error] = call('s:ChompError', a:000)
468   return [len(out) && !exec_error ? split(out, "\n", 1) : [], exec_error]
469 endfunction
471 function! s:NullError(...) abort
472   let [out, exec_error] = s:SystemError(call('fugitive#Prepare', a:000))
473   return [exec_error ? [] : split(out, "\1"), exec_error ? substitute(out, "\n$", "", "") : '', exec_error]
474 endfunction
476 function! s:TreeChomp(...) abort
477   let cmd = call('fugitive#Prepare', a:000)
478   let [out, exec_error] = s:SystemError(cmd)
479   let out = s:sub(out, '\n$', '')
480   if !exec_error
481     return out
482   endif
483   throw 'fugitive: error running `' . cmd . '`: ' . out
484 endfunction
486 function! s:EchoExec(...) abort
487   echo call('s:ChompError', a:000)[0]
488   call fugitive#ReloadStatus(-1, 1)
489   return 'checktime'
490 endfunction
492 function! fugitive#Head(...) abort
493   let dir = a:0 > 1 ? a:2 : s:Dir()
494   if empty(dir) || !filereadable(fugitive#Find('.git/HEAD', dir))
495     return ''
496   endif
497   let head = readfile(fugitive#Find('.git/HEAD', dir))[0]
498   if head =~# '^ref: '
499     return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
500   elseif head =~# '^\x\{40,\}$'
501     let len = a:0 ? a:1 : 0
502     return len < 0 ? head : len ? head[0:len-1] : ''
503   else
504     return ''
505   endif
506 endfunction
508 function! fugitive#RevParse(rev, ...) abort
509   let [hash, exec_error] = s:ChompError([a:0 ? a:1 : s:Dir(), 'rev-parse', '--verify', a:rev, '--'])
510   if !exec_error && hash =~# '^\x\{40,\}$'
511     return hash
512   endif
513   throw 'fugitive: rev-parse '.a:rev.': '.hash
514 endfunction
516 function! s:ConfigTimestamps(dir, dict) abort
517   let files = ['/etc/gitconfig', '~/.gitconfig',
518         \ len($XDG_CONFIG_HOME) ? $XDG_CONFIG_HOME . '/git/config' : '~/.config/git/config']
519   if len(a:dir)
520     call add(files, fugitive#Find('.git/config', a:dir))
521   endif
522   call extend(files, get(a:dict, 'include.path', []))
523   return join(map(files, 'getftime(expand(v:val))'), ',')
524 endfunction
526 let s:config = {}
527 function! fugitive#Config(...) abort
528   let dir = s:Dir()
529   let name = ''
530   if a:0 >= 2 && type(a:2) == type({})
531     let name = substitute(a:1, '^[^.]\+\|[^.]\+$', '\L&', 'g')
532     return len(a:1) ? get(get(a:2, name, []), 0, '') : a:2
533   elseif a:0 >= 2
534     let dir = a:2
535     let name = a:1
536   elseif a:0 == 1 && type(a:1) == type({})
537     return a:1
538   elseif a:0 == 1 && a:1 =~# '^[[:alnum:]-]\+\.'
539     let name = a:1
540   elseif a:0 == 1
541     let dir = a:1
542   endif
543   let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
544   let key = len(dir) ? dir : '_'
545   if has_key(s:config, key) && s:config[key][0] ==# s:ConfigTimestamps(dir, s:config[key][1])
546     let dict = s:config[key][1]
547   else
548     let dict = {}
549     let [lines, message, exec_error] = s:NullError([dir, 'config', '--list', '-z'])
550     if exec_error
551       return {}
552     endif
553     for line in lines
554       let key = matchstr(line, "^[^\n]*")
555       if !has_key(dict, key)
556         let dict[key] = []
557       endif
558       call add(dict[key], strpart(line, len(key) + 1))
559     endfor
560     let s:config[dir] = [s:ConfigTimestamps(dir, dict), dict]
561     lockvar! dict
562   endif
563   return len(name) ? get(get(dict, name, []), 0, '') : dict
564 endfunction
566 function! s:Remote(dir) abort
567   let head = FugitiveHead(0, a:dir)
568   let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
569   let i = 10
570   while remote ==# '.' && i > 0
571     let head = matchstr(fugitive#Config('branch.' . head . '.merge'), 'refs/heads/\zs.*')
572     let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
573     let i -= 1
574   endwhile
575   return remote =~# '^\.\=$' ? 'origin' : remote
576 endfunction
578 function! fugitive#RemoteUrl(...) abort
579   let dir = a:0 > 1 ? a:2 : s:Dir()
580   let remote = !a:0 || a:1 =~# '^\.\=$' ? s:Remote(dir) : a:1
581   if !fugitive#GitVersion(2, 7)
582     return fugitive#Config('remote.' . remote . '.url')
583   endif
584   return s:ChompDefault('', [dir, 'remote', 'get-url', remote, '--'])
585 endfunction
587 " Section: Repository Object
589 function! s:add_methods(namespace, method_names) abort
590   for name in a:method_names
591     let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
592   endfor
593 endfunction
595 let s:repo_prototype = {}
596 let s:repos = {}
598 function! fugitive#repo(...) abort
599   let dir = a:0 ? s:Dir(a:1) : (len(s:Dir()) ? s:Dir() : FugitiveExtractGitDir(expand('%:p')))
600   if dir !=# ''
601     if has_key(s:repos, dir)
602       let repo = get(s:repos, dir)
603     else
604       let repo = {'git_dir': dir}
605       let s:repos[dir] = repo
606     endif
607     return extend(repo, s:repo_prototype, 'keep')
608   endif
609   call s:throw('not a Git repository')
610 endfunction
612 function! s:repo_dir(...) dict abort
613   return join([self.git_dir]+a:000,'/')
614 endfunction
616 function! s:repo_tree(...) dict abort
617   let dir = s:Tree(self.git_dir)
618   if dir ==# ''
619     call s:throw('no work tree')
620   else
621     return join([dir]+a:000,'/')
622   endif
623 endfunction
625 function! s:repo_bare() dict abort
626   if self.dir() =~# '/\.git$'
627     return 0
628   else
629     return s:Tree(self.git_dir) ==# ''
630   endif
631 endfunction
633 function! s:repo_find(object) dict abort
634   return fugitive#Find(a:object, self.git_dir)
635 endfunction
637 function! s:repo_translate(rev) dict abort
638   return s:Slash(fugitive#Find(substitute(a:rev, '^/', ':(top)', ''), self.git_dir))
639 endfunction
641 function! s:repo_head(...) dict abort
642   return fugitive#Head(a:0 ? a:1 : 0, self.git_dir)
643 endfunction
645 call s:add_methods('repo',['dir','tree','bare','find','translate','head'])
647 function! s:repo_prepare(...) dict abort
648   return call('fugitive#Prepare', [self.git_dir] + a:000)
649 endfunction
651 function! s:repo_git_command(...) dict abort
652   let git = s:UserCommand() . ' --git-dir='.s:shellesc(self.git_dir)
653   return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
654 endfunction
656 function! s:repo_git_chomp(...) dict abort
657   let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
658   let output = git . join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
659   return s:sub(system(output), '\n$', '')
660 endfunction
662 function! s:repo_git_chomp_in_tree(...) dict abort
663   let cdback = s:Cd(self.tree())
664   try
665     return call(self.git_chomp, a:000, self)
666   finally
667     execute cdback
668   endtry
669 endfunction
671 function! s:repo_rev_parse(rev) dict abort
672   return fugitive#RevParse(a:rev, self.git_dir)
673 endfunction
675 call s:add_methods('repo',['prepare','git_command','git_chomp','git_chomp_in_tree','rev_parse'])
677 function! s:repo_superglob(base) dict abort
678   return map(fugitive#CompleteObject(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
679 endfunction
681 call s:add_methods('repo',['superglob'])
683 function! s:repo_config(name) dict abort
684   return fugitive#Config(a:name, self.git_dir)
685 endfunction
687 function! s:repo_user() dict abort
688   let username = self.config('user.name')
689   let useremail = self.config('user.email')
690   return username.' <'.useremail.'>'
691 endfunction
693 call s:add_methods('repo',['config', 'user'])
695 " Section: File API
697 function! s:DirCommitFile(path) abort
698   let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40,\}\|[0-3]\)\(/.*\)\=$')
699   if empty(vals)
700     return ['', '', '']
701   endif
702   return vals[1:3]
703 endfunction
705 function! s:DirRev(url) abort
706   let [dir, commit, file] = s:DirCommitFile(a:url)
707   return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
708 endfunction
710 let s:merge_heads = ['MERGE_HEAD', 'REBASE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD']
711 function! s:MergeHead(...) abort
712   let dir = fugitive#Find('.git/', a:0 ? a:1 : s:Dir())
713   for head in s:merge_heads
714     if filereadable(dir . head)
715       return head
716     endif
717   endfor
718   return ''
719 endfunction
721 function! s:Owner(path, ...) abort
722   let dir = a:0 ? a:1 : s:Dir()
723   if empty(dir)
724     return ''
725   endif
726   let actualdir = fugitive#Find('.git/', dir)
727   let [pdir, commit, file] = s:DirCommitFile(a:path)
728   if s:cpath(dir, pdir)
729     if commit =~# '^\x\{40,\}$'
730       return commit
731     elseif commit ==# '2'
732       return 'HEAD^{}'
733     elseif commit ==# '0'
734       return ''
735     endif
736     let merge_head = s:MergeHead()
737     if empty(merge_head)
738       return ''
739     endif
740     if commit ==# '3'
741       return merge_head . '^{}'
742     elseif commit ==# '1'
743       return s:TreeChomp('merge-base', 'HEAD', merge_head, '--')
744     endif
745   endif
746   let path = fnamemodify(a:path, ':p')
747   if s:cpath(actualdir, strpart(path, 0, len(actualdir))) && a:path =~# 'HEAD$'
748     return strpart(path, len(actualdir))
749   endif
750   let refs = fugitive#Find('.git/refs', dir)
751   if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
752     return strpart(path, len(refs) - 4)
753   endif
754   return ''
755 endfunction
757 function! fugitive#Real(url) abort
758   if empty(a:url)
759     return ''
760   endif
761   let [dir, commit, file] = s:DirCommitFile(a:url)
762   if len(dir)
763     let tree = s:Tree(dir)
764     return FugitiveVimPath((len(tree) ? tree : dir) . file)
765   endif
766   let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
767   if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
768     let url = {pre}Real(a:url)
769   else
770     let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
771   endif
772   return FugitiveVimPath(empty(url) ? a:url : url)
773 endfunction
775 function! fugitive#Path(url, ...) abort
776   if empty(a:url)
777     return ''
778   endif
779   let dir = a:0 > 1 ? a:2 : s:Dir()
780   let tree = s:Tree(dir)
781   if !a:0
782     return fugitive#Real(a:url)
783   elseif a:1 =~# '\.$'
784     let path = s:Slash(fugitive#Real(a:url))
785     let cwd = getcwd()
786     let lead = ''
787     while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
788       if s:cpath(cwd . '/', path[0 : len(cwd)])
789         if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
790           break
791         endif
792         return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
793       endif
794       let cwd = fnamemodify(cwd, ':h')
795       let lead .= '../'
796     endwhile
797     return a:1[0:-2] . path
798   endif
799   let url = a:url
800   let temp_state = s:TempState(url)
801   if has_key(temp_state, 'bufnr')
802     let url = bufname(temp_state.bufnr)
803   endif
804   let url = s:Slash(fnamemodify(url, ':p'))
805   if url =~# '/$' && s:Slash(a:url) !~# '/$'
806     let url = url[0:-2]
807   endif
808   let [argdir, commit, file] = s:DirCommitFile(a:url)
809   if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
810     let file = ''
811   elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
812     let file = '/.git'.url[strlen(dir) : -1]
813   elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
814     let file = url[len(tree) : -1]
815   elseif s:cpath(url) ==# s:cpath(tree)
816     let file = '/'
817   endif
818   if empty(file) && a:1 =~# '^$\|^[.:]/$'
819     return FugitiveGitPath(fugitive#Real(a:url))
820   endif
821   return substitute(file, '^/', a:1, '')
822 endfunction
824 function! s:Relative(...) abort
825   return fugitive#Path(@%, a:0 ? a:1 : ':(top)', a:0 > 1 ? a:2 : s:Dir())
826 endfunction
828 function! fugitive#Find(object, ...) abort
829   if type(a:object) == type(0)
830     let name = bufname(a:object)
831     return FugitiveVimPath(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
832   elseif a:object =~# '^[~$]'
833     let prefix = matchstr(a:object, '^[~$]\i*')
834     let owner = expand(prefix)
835     return FugitiveVimPath((len(owner) ? owner : prefix) . strpart(a:object, len(prefix)))
836   elseif s:Slash(a:object) =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
837     return FugitiveVimPath(a:object)
838   elseif s:Slash(a:object) =~# '^\.\.\=\%(/\|$\)'
839     return FugitiveVimPath(simplify(getcwd() . '/' . a:object))
840   endif
841   let dir = a:0 ? a:1 : s:Dir()
842   if empty(dir)
843     let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs.*', '', '')
844     let dir = FugitiveExtractGitDir(file)
845     if empty(dir)
846       return fnamemodify(FugitiveVimPath(len(file) ? file : a:object), ':p')
847     endif
848   endif
849   let rev = s:Slash(a:object)
850   let tree = s:Tree(dir)
851   let base = len(tree) ? tree : 'fugitive://' . dir . '//0'
852   if rev ==# '.git'
853     let f = len(tree) ? tree . '/.git' : dir
854   elseif rev =~# '^\.git/'
855     let f = substitute(rev, '^\.git', '', '')
856     let cdir = fugitive#CommonDir(dir)
857     if f =~# '^/\.\./\.\.\%(/\|$\)'
858       let f = simplify(len(tree) ? tree . f[3:-1] : dir . f)
859     elseif f =~# '^/\.\.\%(/\|$\)'
860       let f = base . f[3:-1]
861     elseif cdir !=# dir && (
862           \ f =~# '^/\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
863           \ f !~# '^/\%(index$\|index\.lock$\|\w*MSG$\|\w*HEAD$\|logs/\w*HEAD$\|logs$\|rebase-\w\+\)\%(/\|$\)' &&
864           \ getftime(FugitiveVimPath(dir . f)) < 0 && getftime(FugitiveVimPath(cdir . f)) >= 0)
865       let f = simplify(cdir . f)
866     else
867       let f = simplify(dir . f)
868     endif
869   elseif rev ==# ':/'
870     let f = base
871   elseif rev =~# '^\.\%(/\|$\)'
872     let f = base . rev[1:-1]
873   elseif rev =~# '^::\%(/\|\a\+\:\)'
874     let f = rev[2:-1]
875   elseif rev =~# '^::\.\.\=\%(/\|$\)'
876     let f = simplify(getcwd() . '/' . rev[2:-1])
877   elseif rev =~# '^::'
878     let f = base . '/' . rev[2:-1]
879   elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
880     let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
881     if s:cpath(base . '/', (f . '/')[0 : len(base)])
882       let f = 'fugitive://' . dir . '//' . +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1)
883     else
884       let altdir = FugitiveExtractGitDir(f)
885       if len(altdir) && !s:cpath(dir, altdir)
886         return fugitive#Find(a:object, altdir)
887       endif
888     endif
889   elseif rev =~# '^:[0-3]:'
890     let f = 'fugitive://' . dir . '//' . rev[1] . '/' . rev[3:-1]
891   elseif rev ==# ':'
892     if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && s:cpath(fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(dir)]) ==# s:cpath(dir . '/') && filereadable($GIT_INDEX_FILE)
893       let f = fnamemodify($GIT_INDEX_FILE, ':p')
894     else
895       let f = fugitive#Find('.git/index', dir)
896     endif
897   elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
898     let f = matchstr(rev, ')\zs.*')
899     if f=~# '^\.\.\=\%(/\|$\)'
900       let f = simplify(getcwd() . '/' . f)
901     elseif f !~# '^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
902       let f = base . '/' . f
903     endif
904   elseif rev =~# '^:/\@!'
905     let f = 'fugitive://' . dir . '//0/' . rev[1:-1]
906   else
907     if !exists('f')
908       let commit = substitute(matchstr(rev, '^[^:.-][^:]*\|^:.*'), '^@\%($\|[~^]\|@{\)\@=', 'HEAD', '')
909       let file = substitute(matchstr(rev, '^[^:.-][^:]*\zs:.*'), '^:', '/', '')
910       if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
911         let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
912         if s:cpath(base . '/', (file . '/')[0 : len(base)])
913           let file = '/' . strpart(file, len(base) + 1)
914         else
915           let altdir = FugitiveExtractGitDir(file)
916           if len(altdir) && !s:cpath(dir, altdir)
917             return fugitive#Find(a:object, altdir)
918           endif
919           return file
920         endif
921       endif
922       let commits = split(commit, '\.\.\.-\@!', 1)
923       if len(commits) == 2
924         call map(commits, 'empty(v:val) || v:val ==# "@" ? "HEAD" : v:val')
925         let commit = matchstr(s:ChompDefault('', [dir, 'merge-base'] + commits + ['--']), '\<[0-9a-f]\{40,\}\>')
926       endif
927       if commit !~# '^[0-9a-f]\{40,\}$'
928         let commit = matchstr(s:ChompDefault('', [dir, 'rev-parse', '--verify', commit, '--']), '\<[0-9a-f]\{40,\}\>')
929       endif
930       if len(commit)
931         let f = 'fugitive://' . dir . '//' . commit . file
932       else
933         let f = base . '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', '')
934       endif
935     endif
936   endif
937   return FugitiveVimPath(f)
938 endfunction
940 function! s:Generate(rev, ...) abort
941   return fugitive#Find(a:rev, a:0 ? a:1 : s:Dir())
942 endfunction
944 function! s:DotRelative(path, ...) abort
945   let cwd = a:0 ? a:1 : getcwd()
946   let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
947   if len(cwd) && s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
948     return '.' . strpart(path, len(cwd))
949   endif
950   return a:path
951 endfunction
953 function! fugitive#Object(...) abort
954   let dir = a:0 > 1 ? a:2 : s:Dir()
955   let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
956   if s:cpath(dir) !=# s:cpath(fdir)
957     let rev = ''
958   endif
959   let tree = s:Tree(dir)
960   let full = a:0 ? a:1 : @%
961   let full = fnamemodify(full, ':p' . (s:Slash(full) =~# '/$' ? '' : ':s?/$??'))
962   if empty(rev) && empty(tree)
963     return FugitiveGitPath(full)
964   elseif empty(rev)
965     let rev = fugitive#Path(full, './', dir)
966     if rev =~# '^\./.git\%(/\|$\)'
967       return FugitiveGitPath(full)
968     endif
969   endif
970   if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
971     return rev
972   else
973     return FugitiveGitPath(tree . rev[1:-1])
974   endif
975 endfunction
977 let s:var = '\%(%\|#<\=\d\+\|##\=\)'
978 let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
979 let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
981 function! s:BufName(var) abort
982   if a:var ==# '%'
983     return bufname(get(s:TempState(), 'bufnr', ''))
984   elseif a:var =~# '^#\d*$'
985     let nr = get(s:TempState(bufname(+a:var[1:-1])), 'bufnr', '')
986     return bufname(nr ? nr : +a:var[1:-1])
987   else
988     return expand(a:var)
989   endif
990 endfunction
992 function! s:ExpandVarLegacy(str) abort
993   if get(g:, 'fugitive_legacy_quoting', 1)
994     return substitute(a:str, '\\\ze[%#!]', '', 'g')
995   else
996     return a:str
997   endif
998 endfunction
1000 function! s:ExpandVar(other, var, flags, esc, ...) abort
1001   let cwd = a:0 ? a:1 : getcwd()
1002   if a:other =~# '^\'
1003     return a:other[1:-1]
1004   elseif a:other =~# '^'''
1005     return s:ExpandVarLegacy(substitute(a:other[1:-2], "''", "'", "g"))
1006   elseif a:other =~# '^"'
1007     return s:ExpandVarLegacy(substitute(a:other[1:-2], '""', '"', "g"))
1008   elseif a:other =~# '^!'
1009     let buffer = s:BufName(len(a:other) > 1 ? '#'. a:other[1:-1] : '%')
1010     let owner = s:Owner(buffer)
1011     return len(owner) ? owner : '@'
1012   endif
1013   let flags = a:flags
1014   let file = s:DotRelative(fugitive#Real(s:BufName(a:var)), cwd)
1015   while len(flags)
1016     let flag = matchstr(flags, s:flag)
1017     let flags = strpart(flags, len(flag))
1018     if flag ==# ':.'
1019       let file = s:DotRelative(file, cwd)
1020     else
1021       let file = fnamemodify(file, flag)
1022     endif
1023   endwhile
1024   let file = s:Slash(file)
1025   return (len(a:esc) ? shellescape(file) : file)
1026 endfunction
1028 function! s:Expand(rev, ...) abort
1029   if a:rev =~# '^:[0-3]$'
1030     let file = len(expand('%')) ? a:rev . ':%' : '%'
1031   elseif a:rev ==# '>'
1032     let file = '%'
1033   elseif a:rev =~# '^>[~^]'
1034     let file = len(expand('%')) ? '!' . a:rev[1:-1] . ':%' : '%'
1035   elseif a:rev =~# '^>[> ]\@!'
1036     let file = len(expand('%')) ? a:rev[1:-1] . ':%' : '%'
1037   else
1038     let file = a:rev
1039   endif
1040   return substitute(file,
1041         \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1042         \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),"", a:0 ? a:1 : getcwd())', 'g')
1043 endfunction
1045 function! fugitive#Expand(object) abort
1046   return substitute(a:object,
1047         \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1048         \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
1049 endfunction
1051 function! s:ExpandSplit(string, ...) abort
1052   let list = []
1053   let string = a:string
1054   let handle_bar = a:0 && a:1
1055   let dquote = handle_bar ? '"\%([^"]\|""\|\\"\)*"\|' : ''
1056   let cwd = a:0 > 1 ? a:2 : getcwd()
1057   while string =~# '\S'
1058     if handle_bar && string =~# '^\s*|'
1059       return [list, substitute(string, '^\s*', '', '')]
1060     endif
1061     let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^[:space:] ' . (handle_bar ? '|' : '') . ']\)\+')
1062     let string = strpart(string, len(arg))
1063     let arg = substitute(arg, '^\s\+', '', '')
1064     if !exists('seen_separator')
1065       let arg = substitute(arg, '^\%([^:.][^:]*:\|^:\|^:[0-3]:\)\=\zs\.\.\=\%(/.*\)\=$',
1066             \ '\=s:DotRelative(s:Slash(simplify(getcwd() . "/" . submatch(0))), cwd)', '')
1067     endif
1068     let arg = substitute(arg,
1069           \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1070           \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5), cwd)', 'g')
1071     call add(list, arg)
1072     if arg ==# '--'
1073       let seen_separator = 1
1074     endif
1075   endwhile
1076   return handle_bar ? [list, ''] : list
1077 endfunction
1079 function! s:SplitExpand(string, ...) abort
1080   return s:ExpandSplit(a:string, 0, a:0 ? a:1 : getcwd())
1081 endfunction
1083 function! s:SplitExpandChain(string, ...) abort
1084   return s:ExpandSplit(a:string, 1, a:0 ? a:1 : getcwd())
1085 endfunction
1087 let s:trees = {}
1088 let s:indexes = {}
1089 function! s:TreeInfo(dir, commit) abort
1090   if a:commit =~# '^:\=[0-3]$'
1091     let index = get(s:indexes, a:dir, [])
1092     let newftime = getftime(fugitive#Find('.git/index', a:dir))
1093     if get(index, 0, -1) < newftime
1094       let [lines, exec_error] = s:LinesError([a:dir, 'ls-files', '--stage', '--'])
1095       let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
1096       if exec_error
1097         return [{}, -1]
1098       endif
1099       for line in lines
1100         let [info, filename] = split(line, "\t")
1101         let [mode, sha, stage] = split(info, '\s\+')
1102         let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
1103         while filename =~# '/'
1104           let filename = substitute(filename, '/[^/]*$', '', '')
1105           let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
1106         endwhile
1107       endfor
1108     endif
1109     return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
1110   elseif a:commit =~# '^\x\{40,\}$'
1111     if !has_key(s:trees, a:dir)
1112       let s:trees[a:dir] = {}
1113     endif
1114     if !has_key(s:trees[a:dir], a:commit)
1115       let [ftime, exec_error] = s:ChompError([a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'])
1116       if exec_error
1117         let s:trees[a:dir][a:commit] = [{}, -1]
1118         return s:trees[a:dir][a:commit]
1119       endif
1120       let s:trees[a:dir][a:commit] = [{}, +ftime]
1121       let [lines, exec_error] = s:LinesError([a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'])
1122       if exec_error
1123         return s:trees[a:dir][a:commit]
1124       endif
1125       for line in lines
1126         let [info, filename] = split(line, "\t")
1127         let [mode, type, sha, size] = split(info, '\s\+')
1128         let s:trees[a:dir][a:commit][0][filename] = [+ftime, mode, type, sha, +size, filename]
1129       endfor
1130     endif
1131     return s:trees[a:dir][a:commit]
1132   endif
1133   return [{}, -1]
1134 endfunction
1136 function! s:PathInfo(url) abort
1137   let [dir, commit, file] = s:DirCommitFile(a:url)
1138   if empty(dir) || !get(g:, 'fugitive_file_api', 1)
1139     return [-1, '000000', '', '', -1]
1140   endif
1141   let path = substitute(file[1:-1], '/*$', '', '')
1142   let [tree, ftime] = s:TreeInfo(dir, commit)
1143   let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
1144   if empty(entry) || file =~# '/$' && entry[2] !=# 'tree'
1145     return [-1, '000000', '', '', -1]
1146   else
1147     return entry
1148   endif
1149 endfunction
1151 function! fugitive#simplify(url) abort
1152   let [dir, commit, file] = s:DirCommitFile(a:url)
1153   if empty(dir)
1154     return ''
1155   endif
1156   if file =~# '/\.\.\%(/\|$\)'
1157     let tree = s:Tree(dir)
1158     if len(tree)
1159       let path = simplify(tree . file)
1160       if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
1161         return FugitiveVimPath(path)
1162       endif
1163     endif
1164   endif
1165   return FugitiveVimPath('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
1166 endfunction
1168 function! fugitive#resolve(url) abort
1169   let url = fugitive#simplify(a:url)
1170   if url =~? '^fugitive:'
1171     return url
1172   else
1173     return resolve(url)
1174   endif
1175 endfunction
1177 function! fugitive#getftime(url) abort
1178   return s:PathInfo(a:url)[0]
1179 endfunction
1181 function! fugitive#getfsize(url) abort
1182   let entry = s:PathInfo(a:url)
1183   if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
1184     let dir = s:DirCommitFile(a:url)[0]
1185     let entry[4] = +s:ChompDefault(-1, [dir, 'cat-file', '-s', entry[3]])
1186   endif
1187   return entry[4]
1188 endfunction
1190 function! fugitive#getftype(url) abort
1191   return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
1192 endfunction
1194 function! fugitive#filereadable(url) abort
1195   return s:PathInfo(a:url)[2] ==# 'blob'
1196 endfunction
1198 function! fugitive#filewritable(url) abort
1199   let [dir, commit, file] = s:DirCommitFile(a:url)
1200   if commit !~# '^\d$' || !filewritable(fugitive#Find('.git/index', dir))
1201     return 0
1202   endif
1203   return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
1204 endfunction
1206 function! fugitive#isdirectory(url) abort
1207   return s:PathInfo(a:url)[2] ==# 'tree'
1208 endfunction
1210 function! fugitive#getfperm(url) abort
1211   let [dir, commit, file] = s:DirCommitFile(a:url)
1212   let perm = getfperm(dir)
1213   let fperm = s:PathInfo(a:url)[1]
1214   if fperm ==# '040000'
1215     let fperm = '000755'
1216   endif
1217   if fperm !~# '[15]'
1218     let perm = tr(perm, 'x', '-')
1219   endif
1220   if fperm !~# '[45]$'
1221     let perm = tr(perm, 'rw', '--')
1222   endif
1223   if commit !~# '^\d$'
1224     let perm = tr(perm, 'w', '-')
1225   endif
1226   return perm ==# '---------' ? '' : perm
1227 endfunction
1229 function! fugitive#setfperm(url, perm) abort
1230   let [dir, commit, file] = s:DirCommitFile(a:url)
1231   let entry = s:PathInfo(a:url)
1232   let perm = fugitive#getfperm(a:url)
1233   if commit !~# '^\d$' || entry[2] !=# 'blob' ||
1234       \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
1235     return -2
1236   endif
1237   let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1238         \ (a:perm =~# 'x' ? '000755 ' : '000644 ') . entry[3] . ' ' . commit . "\t" . file[1:-1])[1]
1239   return exec_error ? -1 : 0
1240 endfunction
1242 function! s:TempCmd(out, cmd) abort
1243   let prefix = ''
1244   try
1245     let cmd = (type(a:cmd) == type([]) ? fugitive#Prepare(a:cmd) : a:cmd)
1246     let redir = ' > ' . a:out
1247     if s:winshell()
1248       let cmd_escape_char = &shellxquote == '(' ?  '^' : '^^^'
1249       return s:SystemError('cmd /c "' . prefix . s:gsub(cmd, '[<>]', cmd_escape_char . '&') . redir . '"')
1250     elseif &shell =~# 'fish'
1251       return s:SystemError(' begin;' . prefix . cmd . redir . ';end ')
1252     else
1253       return s:SystemError(' (' . prefix . cmd . redir . ') ')
1254     endif
1255   endtry
1256 endfunction
1258 if !exists('s:blobdirs')
1259   let s:blobdirs = {}
1260 endif
1261 function! s:BlobTemp(url) abort
1262   let [dir, commit, file] = s:DirCommitFile(a:url)
1263   if empty(file)
1264     return ''
1265   endif
1266   if !has_key(s:blobdirs, dir)
1267     let s:blobdirs[dir] = tempname()
1268   endif
1269   let tempfile = s:blobdirs[dir] . '/' . commit . file
1270   let tempparent = fnamemodify(tempfile, ':h')
1271   if !isdirectory(tempparent)
1272     call mkdir(tempparent, 'p')
1273   endif
1274   if commit =~# '^\d$' || !filereadable(tempfile)
1275     let rev = s:DirRev(a:url)[1]
1276     let exec_error = s:TempCmd(tempfile, [dir, 'cat-file', 'blob', rev])[1]
1277     if exec_error
1278       call delete(tempfile)
1279       return ''
1280     endif
1281   endif
1282   return s:Resolve(tempfile)
1283 endfunction
1285 function! fugitive#readfile(url, ...) abort
1286   let entry = s:PathInfo(a:url)
1287   if entry[2] !=# 'blob'
1288     return []
1289   endif
1290   let temp = s:BlobTemp(a:url)
1291   if empty(temp)
1292     return []
1293   endif
1294   return call('readfile', [temp] + a:000)
1295 endfunction
1297 function! fugitive#writefile(lines, url, ...) abort
1298   let url = type(a:url) ==# type('') ? a:url : ''
1299   let [dir, commit, file] = s:DirCommitFile(url)
1300   let entry = s:PathInfo(url)
1301   if commit =~# '^\d$' && entry[2] !=# 'tree'
1302     let temp = tempname()
1303     if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
1304       call writefile(fugitive#readfile(url, 'b'), temp, 'b')
1305     endif
1306     call call('writefile', [a:lines, temp] + a:000)
1307     let [hash, exec_error] = s:ChompError([dir, 'hash-object', '-w', temp])
1308     let mode = len(entry[1]) ? entry[1] : '100644'
1309     if !exec_error && hash =~# '^\x\{40,\}$'
1310       let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1311             \ mode . ' ' . hash . ' ' . commit . "\t" . file[1:-1])[1]
1312       if !exec_error
1313         return 0
1314       endif
1315     endif
1316   endif
1317   return call('writefile', [a:lines, a:url] + a:000)
1318 endfunction
1320 let s:globsubs = {
1321       \ '/**/': '/\%([^./][^/]*/\)*',
1322       \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
1323       \ '**/': '[^/]*\%(/[^./][^/]*\)*',
1324       \ '**': '.*',
1325       \ '/*': '/[^/.][^/]*',
1326       \ '*': '[^/]*',
1327       \ '?': '[^/]'}
1328 function! fugitive#glob(url, ...) abort
1329   let [dirglob, commit, glob] = s:DirCommitFile(a:url)
1330   let append = matchstr(glob, '/*$')
1331   let glob = substitute(glob, '/*$', '', '')
1332   let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\$]\|^^', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
1333   let results = []
1334   for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
1335     if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(fugitive#Find('.git/HEAD', dir))
1336       continue
1337     endif
1338     let files = items(s:TreeInfo(dir, commit)[0])
1339     if len(append)
1340       call filter(files, 'v:val[1][2] ==# "tree"')
1341     endif
1342     call map(files, 'v:val[0]')
1343     call filter(files, 'v:val =~# pattern')
1344     let prepend = 'fugitive://' . dir . '//' . substitute(commit, '^:', '', '') . '/'
1345     call sort(files)
1346     call map(files, 'FugitiveVimPath(prepend . v:val . append)')
1347     call extend(results, files)
1348   endfor
1349   if a:0 > 1 && a:2
1350     return results
1351   else
1352     return join(results, "\n")
1353   endif
1354 endfunction
1356 function! fugitive#delete(url, ...) abort
1357   let [dir, commit, file] = s:DirCommitFile(a:url)
1358   if a:0 && len(a:1) || commit !~# '^\d$'
1359     return -1
1360   endif
1361   let entry = s:PathInfo(a:url)
1362   if entry[2] !=# 'blob'
1363     return -1
1364   endif
1365   let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1366         \ '000000 0000000000000000000000000000000000000000 ' . commit . "\t" . file[1:-1])[1]
1367   return exec_error ? -1 : 0
1368 endfunction
1370 " Section: Buffer Object
1372 let s:buffer_prototype = {}
1374 function! fugitive#buffer(...) abort
1375   let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
1376   call extend(buffer, s:buffer_prototype, 'keep')
1377   return buffer
1378 endfunction
1380 function! s:buffer_repo() dict abort
1381   return fugitive#repo(self['#'])
1382 endfunction
1384 function! s:buffer_type(...) dict abort
1385   return 'see b:fugitive_type'
1386 endfunction
1388 call s:add_methods('buffer', ['repo', 'type'])
1390 " Section: Completion
1392 function! s:FilterEscape(items, ...) abort
1393   let items = copy(a:items)
1394   if a:0 && type(a:1) == type('')
1395     call filter(items, 'strpart(v:val, 0, strlen(a:1)) ==# a:1')
1396   endif
1397   return map(items, 's:fnameescape(v:val)')
1398 endfunction
1400 function! s:GlobComplete(lead, pattern) abort
1401   if a:lead ==# '/'
1402     return []
1403   elseif v:version >= 704
1404     let results = glob(a:lead . a:pattern, 0, 1)
1405   else
1406     let results = split(glob(a:lead . a:pattern), "\n")
1407   endif
1408   call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1409   call map(results, 'v:val[ strlen(a:lead) : -1 ]')
1410   return results
1411 endfunction
1413 function! fugitive#CompletePath(base, ...) abort
1414   let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
1415   let tree = s:Tree(dir) . '/'
1416   let strip = '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\|:(literal)\)'
1417   let base = substitute(a:base, strip, '', '')
1418   if base =~# '^\.git/'
1419     let pattern = s:gsub(base[5:-1], '/', '*&').'*'
1420     let matches = s:GlobComplete(dir . '/', pattern)
1421     let cdir = fugitive#CommonDir(dir)
1422     if len(cdir) && s:cpath(dir) !=# s:cpath(cdir)
1423       call extend(matches, s:GlobComplete(cdir . '/', pattern))
1424     endif
1425     call s:Uniq(matches)
1426     call map(matches, "'.git/' . v:val")
1427   elseif base =~# '^\~/'
1428     let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
1429   elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/\|^:(literal)'
1430     let matches = s:GlobComplete('', base . '*')
1431   elseif len(tree) > 1
1432     let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*')
1433   else
1434     let matches = []
1435   endif
1436   call map(matches, 's:fnameescape(s:Slash(matchstr(a:base, strip) . v:val))')
1437   return matches
1438 endfunction
1440 function! fugitive#PathComplete(...) abort
1441   return call('fugitive#CompletePath', a:000)
1442 endfunction
1444 function! s:CompleteHeads(dir) abort
1445   let dir = fugitive#Find('.git/', a:dir)
1446   return sort(filter(['HEAD', 'FETCH_HEAD', 'ORIG_HEAD'] + s:merge_heads, 'filereadable(dir . v:val)')) +
1447         \ sort(s:LinesError('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')[0])
1448 endfunction
1450 function! fugitive#CompleteObject(base, ...) abort
1451   let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
1452   let cwd = getcwd()
1453   let tree = s:Tree(dir) . '/'
1454   let subdir = ''
1455   if len(tree) > 1 && s:cpath(tree, cwd[0 : len(tree) - 1])
1456     let subdir = strpart(cwd, len(tree)) . '/'
1457   endif
1459   if a:base =~# '^\.\=/\|^:(' || a:base !~# ':'
1460     let results = []
1461     if a:base =~# '^refs/'
1462       let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:Slash(v:val)')
1463     elseif a:base !~# '^\.\=/\|^:('
1464       let heads = s:CompleteHeads(dir)
1465       if filereadable(fugitive#Find('.git/refs/stash', dir))
1466         let heads += ["stash"]
1467         let heads += sort(s:LinesError(["stash","list","--pretty=format:%gd"], dir)[0])
1468       endif
1469       call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1470       let results += heads
1471     endif
1472     call map(results, 's:fnameescape(v:val)')
1473     if !empty(tree)
1474       let results += a:0 == 1 ? fugitive#CompletePath(a:base, dir) : fugitive#CompletePath(a:base)
1475     endif
1476     return results
1478   elseif a:base =~# '^:'
1479     let entries = s:LinesError(['ls-files','--stage'], dir)[0]
1480     if a:base =~# ':\./'
1481       call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
1482     endif
1483     call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
1484     if a:base !~# '^:[0-3]\%(:\|$\)'
1485       call filter(entries,'v:val[1] == "0"')
1486       call map(entries,'v:val[2:-1]')
1487     endif
1489   else
1490     let tree = matchstr(a:base, '.*[:/]')
1491     let entries = s:LinesError(['ls-tree', substitute(tree,  ':\zs\./', '\=subdir', '')], dir)[0]
1492     call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
1493     call map(entries,'tree.s:sub(v:val,".*\t","")')
1495   endif
1496   return s:FilterEscape(entries, a:base)
1497 endfunction
1499 function! s:CompleteSub(subcommand, A, L, P, ...) abort
1500   let pre = strpart(a:L, 0, a:P)
1501   if pre =~# ' -- '
1502     return fugitive#CompletePath(a:A)
1503   elseif a:A =~# '^-' || a:A is# 0
1504     return s:FilterEscape(split(s:ChompDefault('', a:subcommand, '--git-completion-helper'), ' '), a:A)
1505   elseif !a:0
1506     return fugitive#CompleteObject(a:A, s:Dir())
1507   elseif type(a:1) == type(function('tr'))
1508     return call(a:1, [a:A, a:L, a:P])
1509   else
1510     return s:FilterEscape(a:1, a:A)
1511   endif
1512 endfunction
1514 function! s:CompleteRevision(A, L, P, ...) abort
1515   return s:FilterEscape(s:CompleteHeads(s:Dir()), a:A)
1516 endfunction
1518 function! s:CompleteRemote(A, L, P) abort
1519   let remote = matchstr(a:L, '\u\w*[! ] *\zs\S\+\ze ')
1520   if !empty(remote)
1521     let matches = s:LinesError('ls-remote', remote)[0]
1522     call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1523     call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1524   else
1525     let matches = s:LinesError('remote')[0]
1526   endif
1527   return s:FilterEscape(matches, a:A)
1528 endfunction
1530 " Section: Buffer auto-commands
1532 function! s:ReplaceCmd(cmd) abort
1533   let temp = tempname()
1534   let [err, exec_error] = s:TempCmd(temp, a:cmd)
1535   if exec_error
1536     call s:throw((len(err) ? err : filereadable(temp) ? join(readfile(temp), ' ') : 'unknown error running ' . a:cmd))
1537   endif
1538   let temp = s:Resolve(temp)
1539   let fn = expand('%:p')
1540   silent exe 'keepalt file '.temp
1541   let modelines = &modelines
1542   try
1543     set modelines=0
1544     silent keepjumps noautocmd edit!
1545   finally
1546     let &modelines = modelines
1547     try
1548       silent exe 'keepalt file '.s:fnameescape(fn)
1549     catch /^Vim\%((\a\+)\)\=:E302:/
1550     endtry
1551     call delete(temp)
1552     if s:cpath(fnamemodify(bufname('$'), ':p'), temp)
1553       silent execute 'bwipeout '.bufnr('$')
1554     endif
1555   endtry
1556 endfunction
1558 function! s:QueryLog(refspec) abort
1559   let lines = s:LinesError(['log', '-n', '256', '--format=%h%x09%s', a:refspec, '--'])[0]
1560   call map(lines, 'split(v:val, "\t")')
1561   call map(lines, '{"type": "Log", "commit": v:val[0], "subject": v:val[-1]}')
1562   return lines
1563 endfunction
1565 function! s:FormatLog(dict) abort
1566   return a:dict.commit . ' ' . a:dict.subject
1567 endfunction
1569 function! s:FormatRebase(dict) abort
1570   return a:dict.status . ' ' . a:dict.commit . ' ' . a:dict.subject
1571 endfunction
1573 function! s:FormatFile(dict) abort
1574   return a:dict.status . ' ' . a:dict.filename
1575 endfunction
1577 function! s:Format(val) abort
1578   if type(a:val) == type({})
1579     return s:Format{a:val.type}(a:val)
1580   elseif type(a:val) == type([])
1581     return map(copy(a:val), 's:Format(v:val)')
1582   else
1583     return '' . a:val
1584   endif
1585 endfunction
1587 function! s:AddHeader(key, value) abort
1588   if empty(a:value)
1589     return
1590   endif
1591   let before = 1
1592   while !empty(getline(before))
1593     let before += 1
1594   endwhile
1595   call append(before - 1, [a:key . ':' . (len(a:value) ? ' ' . a:value : '')])
1596   if before == 1 && line('$') == 2
1597     silent keepjumps 2delete _
1598   endif
1599 endfunction
1601 function! s:AddSection(label, lines, ...) abort
1602   let note = a:0 ? a:1 : ''
1603   if empty(a:lines) && empty(note)
1604     return
1605   endif
1606   call append(line('$'), ['', a:label . (len(note) ? ': ' . note : ' (' . len(a:lines) . ')')] + s:Format(a:lines))
1607 endfunction
1609 function! fugitive#BufReadStatus() abort
1610   let amatch = s:Slash(expand('%:p'))
1611   let b:fugitive_type = 'index'
1612   unlet! b:fugitive_reltime
1613   try
1614     silent doautocmd BufReadPre
1615     let cmd = [fnamemodify(amatch, ':h')]
1616     setlocal noro ma nomodeline buftype=nowrite
1617     if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : fugitive#Find('.git/index'), ':p')) !=# s:cpath(amatch)
1618       let cmd += ['-c', 'GIT_INDEX_FILE=' . amatch]
1619     endif
1621     let b:fugitive_files = {'Staged': {}, 'Unstaged': {}}
1622     let [staged, unstaged, untracked] = [[], [], []]
1624     if fugitive#GitVersion(2, 11)
1625       let cmd += ['status', '--porcelain=v2', '-bz']
1626       let [output, message, exec_error] = s:NullError(cmd)
1627       if exec_error
1628         throw 'fugitive: ' . message
1629       endif
1631       let i = match(output, '^[^#]')
1632       let head = matchlist(output[:i], '^# branch\.head \zs.*$')[0]
1633       let pull = get(matchlist(output[:i], '^# branch\.upstream \zs.*$'), 0, '')
1634       if len(pull)
1635         let branch = head
1636       elseif head ==# '(detached)'
1637         let head = matchlist(output[:i], '^# branch\.oid \zs.*$')[0][:10]
1638         let branch = ''
1639       else
1640         let branch = head
1641       endif
1643       while i < len(output)
1644         let line = output[i]
1645         if line[0] ==# '?'
1646           call add(untracked, {'type': 'File', 'status': line[0], 'filename': line[2:-1]})
1647         elseif line[0] !=# '#'
1648           if line[0] ==# 'u'
1649             let file = matchstr(line, '^.\{37\} \x\{40,\} \x\{40,\} \x\{40,\} \zs.*$')
1650           else
1651             let file = matchstr(line, '^.\{30\} \x\{40,\} \x\{40,\} \zs.*$')
1652           endif
1653           if line[0] ==# '2'
1654             let i += 1
1655             let file = output[i] . ' -> ' . matchstr(file, ' \zs.*')
1656           endif
1657           let sub = matchstr(line, '^[12u] .. \zs....')
1658           if line[2] !=# '.'
1659             call add(staged, {'type': 'File', 'status': line[2], 'filename': file, 'sub': sub})
1660           endif
1661           if line[3] !=# '.'
1662             call add(unstaged, {'type': 'File', 'status': get({'C':'M','M':'?','U':'?'}, matchstr(sub, 'S\.*\zs[CMU]'), line[3]), 'filename': file, 'sub': sub})
1663           endif
1664         endif
1665         let i += 1
1666       endwhile
1667     else " git < 2.11
1668       let cmd += ['status', '--porcelain', '-bz']
1669       let [output, message, exec_error] = s:NullError(cmd)
1670       if exec_error
1671         throw 'fugitive: ' . message
1672       endif
1674       let head = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
1675       let pull = ''
1676       if head =~# '\.\.\.'
1677         let [head, pull] = split(head, '\.\.\.')
1678         let branch = head
1679       elseif head ==# 'HEAD' || empty(head)
1680         let head = FugitiveHead(11)
1681         let branch = ''
1682       else
1683         let branch = head
1684       endif
1686       let i = 0
1687       while i < len(output)
1688         let line = output[i]
1689         let file = line[3:-1]
1690         let files = file
1691         let i += 1
1692         if line[2] !=# ' '
1693           continue
1694         endif
1695         if line[0:1] =~# '[RC]'
1696           let files = output[i] . ' -> ' . file
1697           let i += 1
1698         endif
1699         if line[0] !~# '[ ?!#]'
1700           call add(staged, {'type': 'File', 'status': line[0], 'filename': files, 'sub': ''})
1701         endif
1702         if line[0:1] ==# '??'
1703           call add(untracked, {'type': 'File', 'status': line[1], 'filename': files})
1704         elseif line[1] !~# '[ !#]'
1705           call add(unstaged, {'type': 'File', 'status': line[1], 'filename': files, 'sub': ''})
1706         endif
1707       endwhile
1708     endif
1710     for dict in staged
1711       let b:fugitive_files['Staged'][dict.filename] = dict
1712     endfor
1713     for dict in unstaged
1714       let b:fugitive_files['Unstaged'][dict.filename] = dict
1715     endfor
1717     let config = fugitive#Config()
1719     let pull_type = 'Pull'
1720     if len(pull)
1721       let rebase = fugitive#Config('branch.' . branch . '.rebase', config)
1722       if empty(rebase)
1723         let rebase = fugitive#Config('pull.rebase', config)
1724       endif
1725       if rebase =~# '^\%(true\|yes\|on\|1\|interactive\)$'
1726         let pull_type = 'Rebase'
1727       elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
1728         let pull_type = 'Merge'
1729       endif
1730     endif
1732     let push_remote = fugitive#Config('branch.' . branch . '.pushRemote', config)
1733     if empty(push_remote)
1734       let push_remote = fugitive#Config('remote.pushDefault', config)
1735     endif
1736     let push = len(push_remote) && len(branch) ? push_remote . '/' . branch : ''
1737     if empty(push)
1738       let push = pull
1739     endif
1741     if len(pull)
1742       let unpulled = s:QueryLog(head . '..' . pull)
1743     else
1744       let unpulled = []
1745     endif
1746     if len(push)
1747       let unpushed = s:QueryLog(push . '..' . head)
1748     else
1749       let unpushed = []
1750     endif
1752     if isdirectory(fugitive#Find('.git/rebase-merge/'))
1753       let rebasing_dir = fugitive#Find('.git/rebase-merge/')
1754     elseif isdirectory(fugitive#Find('.git/rebase-apply/'))
1755       let rebasing_dir = fugitive#Find('.git/rebase-apply/')
1756     endif
1758     let rebasing = []
1759     let rebasing_head = 'detached HEAD'
1760     if exists('rebasing_dir') && filereadable(rebasing_dir . 'git-rebase-todo')
1761       let rebasing_head = substitute(readfile(rebasing_dir . 'head-name')[0], '\C^refs/heads/', '', '')
1762       let len = 11
1763       let lines = readfile(rebasing_dir . 'git-rebase-todo')
1764       for line in lines
1765         let hash = matchstr(line, '^[^a-z].*\s\zs[0-9a-f]\{4,\}\ze\.\.')
1766         if len(hash)
1767           let len = len(hash)
1768           break
1769         endif
1770       endfor
1771       if getfsize(rebasing_dir . 'done') > 0
1772         let done = readfile(rebasing_dir . 'done')
1773         call map(done, 'substitute(v:val, ''^\l\+\>'', "done", "")')
1774         let done[-1] = substitute(done[-1], '^\l\+\>', 'stop', '')
1775         let lines = done + lines
1776       endif
1777       call reverse(lines)
1778       for line in lines
1779         let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
1780         if len(match) && match[1] !~# 'exec\|merge\|label'
1781           call add(rebasing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': strpart(match[2], 0, len), 'subject': match[3]})
1782         endif
1783       endfor
1784     endif
1786     let diff = {'Staged': [], 'Unstaged': []}
1787     if len(staged)
1788       let diff['Staged'] =
1789           \ s:LinesError(['diff', '--color=never', '--no-ext-diff', '--no-prefix', '--cached'])[0]
1790     endif
1791     if len(unstaged)
1792       let diff['Unstaged'] =
1793           \ s:LinesError(['diff', '--color=never', '--no-ext-diff', '--no-prefix'])[0]
1794     endif
1795     let b:fugitive_diff = diff
1796     let expanded = get(b:, 'fugitive_expanded', {'Staged': {}, 'Unstaged': {}})
1797     let b:fugitive_expanded = {'Staged': {}, 'Unstaged': {}}
1799     silent keepjumps %delete_
1801     call s:AddHeader('Head', head)
1802     call s:AddHeader(pull_type, pull)
1803     if push !=# pull
1804       call s:AddHeader('Push', push)
1805     endif
1806     call s:AddSection('Rebasing ' . rebasing_head, rebasing)
1807     call s:AddSection('Untracked', untracked)
1808     call s:AddSection('Unstaged', unstaged)
1809     let unstaged_end = len(unstaged) ? line('$') : 0
1810     call s:AddSection('Staged', staged)
1811     let staged_end = len(staged) ? line('$') : 0
1812     call s:AddSection('Unpushed to ' . push, unpushed)
1813     call s:AddSection('Unpulled from ' . pull, unpulled)
1815     setlocal nomodified readonly noswapfile
1816     silent doautocmd BufReadPost
1817     setlocal nomodifiable
1818     if &bufhidden ==# ''
1819       setlocal bufhidden=delete
1820     endif
1821     let b:dispatch = ':Gfetch --all'
1822     call fugitive#MapJumps()
1823     call s:Map('n', '-', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
1824     call s:Map('x', '-', ":<C-U>execute <SID>Do('Toggle',1)<CR>", '<silent>')
1825     call s:Map('n', 's', ":<C-U>execute <SID>Do('Stage',0)<CR>", '<silent>')
1826     call s:Map('x', 's', ":<C-U>execute <SID>Do('Stage',1)<CR>", '<silent>')
1827     call s:Map('n', 'u', ":<C-U>execute <SID>Do('Unstage',0)<CR>", '<silent>')
1828     call s:Map('x', 'u', ":<C-U>execute <SID>Do('Unstage',1)<CR>", '<silent>')
1829     call s:Map('n', 'U', ":exe <SID>EchoExec('reset', '-q')<CR>", '<silent>')
1830     call s:MapMotion('gu', "exe <SID>StageJump(v:count, 'Untracked', 'Unstaged')")
1831     call s:MapMotion('gU', "exe <SID>StageJump(v:count, 'Unstaged', 'Untracked')")
1832     call s:MapMotion('gs', "exe <SID>StageJump(v:count, 'Staged')")
1833     call s:MapMotion('gp', "exe <SID>StageJump(v:count, 'Unpushed')")
1834     call s:MapMotion('gP', "exe <SID>StageJump(v:count, 'Unpulled')")
1835     call s:MapMotion('gr', "exe <SID>StageJump(v:count, 'Rebasing')")
1836     call s:Map('n', 'C', ":<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>", '<silent>')
1837     call s:Map('n', 'a', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
1838     call s:Map('n', 'i', ":<C-U>execute <SID>NextExpandedHunk(v:count1)<CR>", '<silent>')
1839     call s:Map('n', "=", ":<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>", '<silent>')
1840     call s:Map('n', "<", ":<C-U>execute <SID>StageInline('hide',  line('.'),v:count)<CR>", '<silent>')
1841     call s:Map('n', ">", ":<C-U>execute <SID>StageInline('show',  line('.'),v:count)<CR>", '<silent>')
1842     call s:Map('x', "=", ":<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1843     call s:Map('x', "<", ":<C-U>execute <SID>StageInline('hide',  line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1844     call s:Map('x', ">", ":<C-U>execute <SID>StageInline('show',  line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1845     call s:Map('n', 'D', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<Bar>redraw<Bar>echohl WarningMsg<Bar> echo ':Gstatus D is deprecated in favor of dd'<Bar>echohl NONE<CR>", '<silent>')
1846     call s:Map('n', 'dd', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>", '<silent>')
1847     call s:Map('n', 'dh', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
1848     call s:Map('n', 'ds', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
1849     call s:Map('n', 'dp', ":<C-U>execute <SID>StageDiffEdit()<CR>", '<silent>')
1850     call s:Map('n', 'dv', ":<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>", '<silent>')
1851     call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
1852     call s:Map('n', 'P', ":<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>", '<silent>')
1853     call s:Map('x', 'P', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1854     call s:Map('n', 'p', ":<C-U>if v:count<Bar>silent exe <SID>GF('pedit')<Bar>else<Bar>echoerr 'Use = for inline diff, P for :Git add/reset --patch, 1p for :pedit'<Bar>endif<CR>", '<silent>')
1855     call s:Map('x', 'p', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1856     call s:Map('n', 'I', ":<C-U>execute <SID>StagePatch(line('.'),line('.'))<CR>", '<silent>')
1857     call s:Map('x', 'I', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1858     if empty(mapcheck('q', 'n'))
1859       nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<Bar>echohl WarningMsg<Bar>echo ':Gstatus q is deprecated in favor of gq or the built-in <Lt>C-W>q'<Bar>echohl NONE<CR>
1860     endif
1861     call s:Map('n', 'gq', ":<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>", '<silent>')
1862     call s:Map('n', 'R', ":echohl WarningMsg<Bar>echo 'Reloading is automatic.  Use :e to force'<Bar>echohl NONE<CR>", '<silent>')
1863     call s:Map('n', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent>')
1864     call s:Map('x', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent>')
1865     call s:Map('n', 'X', ":<C-U>execute <SID>StageDelete(line('.'), 0, v:count)<CR>", '<silent>')
1866     call s:Map('x', 'X', ":<C-U>execute <SID>StageDelete(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
1867     call s:Map('n', 'gI', ":<C-U>execute <SID>StageIgnore(line('.'), line('.'), v:count)<CR>", '<silent>')
1868     call s:Map('x', 'gI', ":<C-U>execute <SID>StageIgnore(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
1869     call s:Map('n', '.', ':<C-U> <C-R>=<SID>StageArgs(0)<CR><Home>')
1870     call s:Map('x', '.', ':<C-U> <C-R>=<SID>StageArgs(1)<CR><Home>')
1871     setlocal filetype=fugitive
1873     for [lnum, section] in [[staged_end, 'Staged'], [unstaged_end, 'Unstaged']]
1874       while len(getline(lnum))
1875         let filename = matchstr(getline(lnum), '^[A-Z?] \zs.*')
1876         if has_key(expanded[section], filename)
1877           call s:StageInline('show', lnum)
1878         endif
1879         let lnum -= 1
1880       endwhile
1881     endfor
1883     let b:fugitive_reltime = reltime()
1884     return ''
1885   catch /^fugitive:/
1886     return 'echoerr ' . string(v:exception)
1887   endtry
1888 endfunction
1890 function! fugitive#FileReadCmd(...) abort
1891   let amatch = a:0 ? a:1 : expand('<amatch>')
1892   let [dir, rev] = s:DirRev(amatch)
1893   let line = a:0 > 1 ? a:2 : line("'[")
1894   if empty(dir)
1895     return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1896   endif
1897   if rev !~# ':' && s:ChompDefault('', [dir, 'cat-file', '-t', rev]) =~# '^\%(commit\|tag\)$'
1898     let cmd = fugitive#Prepare(dir, 'log', '--pretty=format:%B', '-1', rev, '--')
1899   else
1900     let cmd = fugitive#Prepare(dir, 'cat-file', '-p', rev)
1901   endif
1902   return line . 'read !' . escape(cmd, '!#%')
1903 endfunction
1905 function! fugitive#FileWriteCmd(...) abort
1906   let tmp = tempname()
1907   let amatch = a:0 ? a:1 : expand('<amatch>')
1908   let autype = a:0 > 1 ? 'Buf' : 'File'
1909   if exists('#' . autype . 'WritePre')
1910     execute s:DoAutocmd(autype . 'WritePre ' . s:fnameescape(amatch))
1911   endif
1912   try
1913     let [dir, commit, file] = s:DirCommitFile(amatch)
1914     if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1915       return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1916     endif
1917     silent execute "'[,']write !".fugitive#Prepare(dir, 'hash-object', '-w', '--stdin', '--').' > '.tmp
1918     let sha1 = readfile(tmp)[0]
1919     let old_mode = matchstr(s:SystemError([dir, 'ls-files', '--stage', '.' . file])[0], '^\d\+')
1920     if empty(old_mode)
1921       let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1922     endif
1923     let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1924     let [error, exec_error] = s:SystemError([dir, 'update-index', '--index-info'], info . "\n")
1925     if !exec_error
1926       setlocal nomodified
1927       if exists('#' . autype . 'WritePost')
1928         execute s:DoAutocmd(autype . 'WritePost ' . s:fnameescape(amatch))
1929       endif
1930       return ''
1931     else
1932       return 'echoerr '.string('fugitive: '.error)
1933     endif
1934   finally
1935     call delete(tmp)
1936   endtry
1937 endfunction
1939 function! fugitive#BufReadCmd(...) abort
1940   let amatch = a:0 ? a:1 : expand('<amatch>')
1941   try
1942     let [dir, rev] = s:DirRev(amatch)
1943     if empty(dir)
1944       return 'echo "Invalid Fugitive URL"'
1945     endif
1946     if rev =~# '^:\d$'
1947       let b:fugitive_type = 'stage'
1948     else
1949       let [b:fugitive_type, exec_error] = s:ChompError([dir, 'cat-file', '-t', rev])
1950       if exec_error && rev =~# '^:0'
1951         let sha = s:ChompDefault('', dir, 'write-tree', '--prefix=' . rev[3:-1])
1952         let exec_error = empty(sha)
1953         let b:fugitive_type = exec_error ? '' : 'tree'
1954       endif
1955       if exec_error
1956         let error = b:fugitive_type
1957         unlet b:fugitive_type
1958         setlocal noswapfile
1959         if empty(&bufhidden)
1960           setlocal bufhidden=delete
1961         endif
1962         if rev =~# '^:\d:'
1963           let &l:readonly = !filewritable(fugitive#Find('.git/index', dir))
1964           return 'silent doautocmd BufNewFile'
1965         else
1966           setlocal readonly nomodifiable
1967           return 'silent doautocmd BufNewFile|echo ' . string(error)
1968         endif
1969       elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1970         return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1971       endif
1972       if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1973         let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1974       endif
1975     endif
1977     if b:fugitive_type !=# 'blob'
1978       setlocal nomodeline
1979     endif
1981     setlocal noreadonly modifiable
1982     let pos = getpos('.')
1983     silent keepjumps %delete_
1984     setlocal endofline
1986     try
1987       silent doautocmd BufReadPre
1988       if b:fugitive_type ==# 'tree'
1989         let b:fugitive_display_format = b:fugitive_display_format % 2
1990         if b:fugitive_display_format
1991           call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1992         else
1993           if !exists('sha')
1994             let sha = s:TreeChomp(dir, 'rev-parse', '--verify', rev, '--')
1995           endif
1996           call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1997         endif
1998       elseif b:fugitive_type ==# 'tag'
1999         let b:fugitive_display_format = b:fugitive_display_format % 2
2000         if b:fugitive_display_format
2001           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
2002         else
2003           call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
2004         endif
2005       elseif b:fugitive_type ==# 'commit'
2006         let b:fugitive_display_format = b:fugitive_display_format % 2
2007         if b:fugitive_display_format
2008           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
2009         else
2010           call s:ReplaceCmd([dir, 'show', '--no-color', '-m', '--first-parent', '--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])
2011           keepjumps call search('^parent ')
2012           if getline('.') ==# 'parent '
2013             silent keepjumps delete_
2014           else
2015             silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
2016           endif
2017           keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2018           if lnum
2019             silent keepjumps delete_
2020           end
2021           silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/s/\r$//e'
2022           keepjumps 1
2023         endif
2024       elseif b:fugitive_type ==# 'stage'
2025         call s:ReplaceCmd([dir, 'ls-files', '--stage'])
2026       elseif b:fugitive_type ==# 'blob'
2027         call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
2028       endif
2029     finally
2030       keepjumps call setpos('.',pos)
2031       setlocal nomodified noswapfile
2032       let modifiable = rev =~# '^:.:' && b:fugitive_type !=# 'tree'
2033       let &l:readonly = !modifiable || !filewritable(fugitive#Find('.git/index', dir))
2034       if empty(&bufhidden)
2035         setlocal bufhidden=delete
2036       endif
2037       let &l:modifiable = modifiable
2038       if b:fugitive_type !=# 'blob'
2039         setlocal filetype=git foldmethod=syntax
2040         call s:Map('n', 'a', ":<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
2041         call s:Map('n', 'i', ":<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
2042       endif
2043       call fugitive#MapJumps()
2044     endtry
2046     setlocal modifiable
2047     return 'silent ' . s:DoAutocmd('BufReadPost') .
2048           \ (modifiable ? '' : '|setl nomodifiable')
2049   catch /^fugitive:/
2050     return 'echoerr ' . string(v:exception)
2051   endtry
2052 endfunction
2054 function! fugitive#BufWriteCmd(...) abort
2055   return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
2056 endfunction
2058 function! fugitive#SourceCmd(...) abort
2059   let amatch = a:0 ? a:1 : expand('<amatch>')
2060   let temp = s:BlobTemp(amatch)
2061   if empty(temp)
2062     return 'noautocmd source ' . s:fnameescape(amatch)
2063   endif
2064   if !exists('g:virtual_scriptnames')
2065     let g:virtual_scriptnames = {}
2066   endif
2067   let g:virtual_scriptnames[temp] = amatch
2068   return 'source ' . s:fnameescape(temp)
2069 endfunction
2071 " Section: Temp files
2073 if !exists('s:temp_files')
2074   let s:temp_files = {}
2075 endif
2077 function! s:TempState(...) abort
2078   return get(s:temp_files, s:cpath(fnamemodify(a:0 ? a:1 : @%, ':p')), {})
2079 endfunction
2081 function! s:TempReadPre(file) abort
2082   if has_key(s:temp_files, s:cpath(a:file))
2083     let dict = s:temp_files[s:cpath(a:file)]
2084     setlocal nomodeline
2085     setlocal bufhidden=delete nobuflisted
2086     setlocal buftype=nowrite
2087     if has_key(dict, 'modifiable')
2088       let &l:modifiable = dict.modifiable
2089     endif
2090     if len(dict.dir)
2091       let b:git_dir = dict.dir
2092       call extend(b:, {'fugitive_type': 'temp'}, 'keep')
2093     endif
2094   endif
2095 endfunction
2097 function! s:TempReadPost(file) abort
2098   if has_key(s:temp_files, s:cpath(a:file))
2099     let dict = s:temp_files[s:cpath(a:file)]
2100     if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
2101       let &l:filetype = dict.filetype
2102     endif
2103     setlocal foldmarker=<<<<<<<,>>>>>>>
2104     if empty(mapcheck('q', 'n'))
2105       nnoremap <buffer> <silent> q    :<C-U>bdelete<Bar>echohl WarningMsg<Bar>echo "Temp file q is deprecated in favor of the built-in <Lt>C-W>q"<Bar>echohl NONE<CR>
2106     endif
2107     if !&modifiable
2108       call s:Map('n', 'gq', ":<C-U>bdelete<CR>", '<silent> <unique>')
2109     endif
2110   endif
2111   return ''
2112 endfunction
2114 augroup fugitive_temp
2115   autocmd!
2116   autocmd BufReadPre  * exe s:TempReadPre( expand('<amatch>:p'))
2117   autocmd BufReadPost * exe s:TempReadPost(expand('<amatch>:p'))
2118 augroup END
2120 " Section: :Git
2122 function! fugitive#Command(line1, line2, range, bang, mods, arg) abort
2123   let dir = s:Dir()
2124   let [args, after] = s:SplitExpandChain(a:arg, s:Tree(dir))
2125   if empty(args)
2126     let cmd = s:StatusCommand(a:line1, a:line2, a:range, a:line2, a:bang, a:mods, '', '', [])
2127     return (empty(cmd) ? 'exe' : cmd) . after
2128   endif
2129   let alias = get(s:Aliases(dir), args[0], '!')
2130   if get(args, 1, '') !=# '--help' && alias !~# '^!\|[\"'']' && !filereadable(s:ExecPath() . '/git-' . args[0])
2131         \ && !(has('win32') && filereadable(s:ExecPath() . '/git-' . args[0] . '.exe'))
2132     call remove(args, 0)
2133     call extend(args, split(alias, '\s\+'), 'keep')
2134   endif
2135   let name = substitute(args[0], '\%(^\|-\)\(\l\)', '\u\1', 'g')
2136   if exists('*s:' . name . 'Subcommand') && get(args, 1, '') !=# '--help'
2137     try
2138       exe s:DirCheck(dir)
2139       return 'exe ' . string(s:{name}Subcommand(a:line1, a:line2, a:range, a:bang, a:mods, args[1:-1])) . after
2140     catch /^fugitive:/
2141       return 'echoerr ' . string(v:exception)
2142     endtry
2143   endif
2144   if a:bang || args[0] =~# '^-P$\|^--no-pager$\|diff\%(tool\)\@!\|log\|^show$' ||
2145         \ (args[0] ==# 'stash' && get(args, 1, '') ==# 'show') ||
2146         \ (args[0] ==# 'help' || get(args, 1, '') ==# '--help') && !s:HasOpt(args, '--web')
2147     return s:OpenExec((a:line2 > 0 ? a:line2 : '') . (a:line2 ? 'split' : 'edit'), a:mods, args, dir) . after
2148   endif
2149   if s:HasOpt(args, ['add', 'checkout', 'commit', 'stage', 'stash', 'reset'], '-p', '--patch') ||
2150         \ s:HasOpt(args, ['add', 'clean', 'stage'], '-i', '--interactive') ||
2151         \ index(['--paginate', '-p'], args[0]) >= 0
2152     let mods = substitute(s:Mods(a:mods), '\<tab\>', '-tab', 'g')
2153     let assign = len(dir) ? '|let b:git_dir = ' . string(dir) : ''
2154     if has('nvim')
2155       if &autowrite || &autowriteall | silent! wall | endif
2156       return mods . (a:line2 ? 'split' : 'edit') . ' term://' . s:fnameescape(s:UserCommand(dir, args)) . assign . '|startinsert' . after
2157     elseif has('terminal')
2158       if &autowrite || &autowriteall | silent! wall | endif
2159       return 'exe ' . string(mods . 'terminal ' . (a:line2 ? '' : '++curwin ') . join(map(s:UserCommandList(dir) + args, 's:fnameescape(v:val)'))) . assign . after
2160     endif
2161   endif
2162   if has('gui_running') && !has('win32')
2163     call insert(args, '--no-pager')
2164   endif
2165   let pre = ''
2166   if has('nvim') && executable('env')
2167     let pre .= 'env GIT_TERMINAL_PROMPT=0 '
2168   endif
2169   return 'exe ' . string('noautocmd !' . escape(pre . s:UserCommand(dir, args), '!#%')) .
2170         \ '|call fugitive#ReloadStatus(' . string(dir) . ', 1)' .
2171         \ after
2172 endfunction
2174 let s:exec_paths = {}
2175 function! s:ExecPath() abort
2176   if !has_key(s:exec_paths, g:fugitive_git_executable)
2177     let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
2178   endif
2179   return s:exec_paths[g:fugitive_git_executable]
2180 endfunction
2182 function! s:Subcommands() abort
2183   let exec_path = s:ExecPath()
2184   return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
2185 endfunction
2187 let s:aliases = {}
2188 function! s:Aliases(dir) abort
2189   if !has_key(s:aliases, a:dir)
2190     let s:aliases[a:dir] = {}
2191     let lines = s:NullError([a:dir, 'config', '-z', '--get-regexp', '^alias[.]'])[0]
2192     for line in lines
2193       let s:aliases[a:dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
2194     endfor
2195   endif
2196   return s:aliases[a:dir]
2197 endfunction
2199 function! fugitive#Complete(lead, ...) abort
2200   let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
2201   let pre = a:0 > 1 ? strpart(a:1, 0, a:2) : ''
2202   let subcmd = matchstr(pre, '\u\w*[! ] *\zs[[:alnum:]-]\+\ze ')
2203   if empty(subcmd)
2204     let results = sort(s:Subcommands() + keys(s:Aliases(dir)))
2205   elseif pre =~# ' -- '
2206     return fugitive#CompletePath(a:lead, dir)
2207   elseif a:lead =~# '^-'
2208     let results = split(s:ChompDefault('', dir, subcmd, '--git-completion-helper'), ' ')
2209   else
2210     return fugitive#CompleteObject(a:lead, dir)
2211   endif
2212   return filter(results, 'strpart(v:val, 0, strlen(a:lead)) ==# a:lead')
2213 endfunction
2215 " Section: :Gcd, :Glcd
2217 function! fugitive#CdComplete(A, L, P) abort
2218   return filter(fugitive#CompletePath(a:A), 'v:val =~# "/$"')
2219 endfunction
2221 function! fugitive#Cd(path, ...) abort
2222   let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
2223   if path !~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
2224     let dir = s:Dir()
2225     exe s:DirCheck(dir)
2226     let path = (empty(s:Tree(dir)) ? dir : s:Tree(dir)) . '/' . path
2227   endif
2228   return (a:0 && a:1 ? 'lcd ' : 'cd ') . s:fnameescape(FugitiveVimPath(path))
2229 endfunction
2231 " Section: :Gstatus
2233 function! s:StatusCommand(line1, line2, range, count, bang, mods, reg, arg, args, ...) abort
2234   let dir = a:0 ? a:1 : s:Dir()
2235   exe s:DirCheck(dir)
2236   try
2237     let mods = s:Mods(a:mods, &splitbelow ? 'botright' : 'topleft')
2238     let file = fugitive#Find(':', dir)
2239     let arg = ' +setl\ foldmethod=syntax\ foldlevel=1\|let\ w:fugitive_status=FugitiveGitDir() ' .
2240           \ s:fnameescape(file)
2241     for winnr in range(1, winnr('$'))
2242       if s:cpath(file, fnamemodify(bufname(winbufnr(winnr)), ':p'))
2243         if winnr == winnr()
2244           call s:ReloadStatus()
2245         else
2246           call s:ExpireStatus(dir)
2247           exe winnr . 'wincmd w'
2248         endif
2249         let w:fugitive_status = dir
2250         1
2251         return ''
2252       endif
2253     endfor
2254     if a:count ==# 0
2255       return mods . 'edit' . (a:bang ? '!' : '') . arg
2256     elseif a:bang
2257       return mods . 'pedit' . arg . '|wincmd P'
2258     else
2259       return mods . (a:count > 0 ? a:count : '') . 'split' . arg
2260     endif
2261   catch /^fugitive:/
2262     return 'echoerr ' . string(v:exception)
2263   endtry
2264   return ''
2265 endfunction
2267 function! s:StageJump(offset, section, ...) abort
2268   let line = search('^\%(' . a:section . '\)', 'nw')
2269   if !line && a:0
2270     let line = search('^\%(' . a:1 . '\)', 'nw')
2271   endif
2272   if line
2273     exe line
2274     if a:offset
2275       for i in range(a:offset)
2276         call search(s:file_commit_pattern . '\|^$', 'W')
2277         if empty(getline('.')) && a:0 && getline(line('.') + 1) =~# '^\%(' . a:1 . '\)'
2278           call search(s:file_commit_pattern . '\|^$', 'W')
2279         endif
2280         if empty(getline('.'))
2281           return ''
2282         endif
2283       endfor
2284       call s:StageReveal()
2285     else
2286       call s:StageReveal()
2287       +
2288     endif
2289   endif
2290   return ''
2291 endfunction
2293 function! s:StageSeek(info, fallback) abort
2294   let info = a:info
2295   if empty(info.section)
2296     return a:fallback
2297   endif
2298   let line = search('^' . info.section, 'wn')
2299   if !line
2300     for section in get({'Staged': ['Unstaged', 'Untracked'], 'Unstaged': ['Untracked', 'Staged'], 'Untracked': ['Unstaged', 'Staged']}, info.section, [])
2301       let line = search('^' . section, 'wn')
2302       if line
2303         return line + (info.index > 0 ? 1 : 0)
2304       endif
2305     endfor
2306     return 1
2307   endif
2308   let i = 0
2309   while len(getline(line))
2310     let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
2311     if len(filename) &&
2312           \ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
2313           \ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
2314           \ filename ==# info.filename)
2315       if info.offset < 0
2316         return line
2317       else
2318         if getline(line+1) !~# '^@'
2319           exe s:StageInline('show', line)
2320         endif
2321         if getline(line+1) !~# '^@'
2322           return line
2323         endif
2324         let type = info.sigil ==# '-' ? '-' : '+'
2325         let offset = -1
2326         while offset < info.offset
2327           let line += 1
2328           if getline(line) =~# '^@'
2329             let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
2330           elseif getline(line) =~# '^[ ' . type . ']'
2331             let offset += 1
2332           elseif getline(line) !~# '^[ @\+-]'
2333             return line - 1
2334           endif
2335         endwhile
2336         return line
2337       endif
2338     endif
2339     let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
2340     if len(commit) && commit ==# info.commit
2341       return line
2342     endif
2343     if i ==# info.index
2344       let backup = line
2345     endif
2346     let i += getline(line) !~# '^[ @\+-]'
2347     let line += 1
2348   endwhile
2349   return exists('backup') ? backup : line - 1
2350 endfunction
2352 function! s:DoAutocmdChanged(dir) abort
2353   let dir = a:dir is# -2 ? '' : FugitiveGitDir(a:dir)
2354   if empty(dir) || !exists('#User#FugitiveChanged') || exists('g:fugitive_event')
2355     return ''
2356   endif
2357   try
2358     let g:fugitive_event = dir
2359     exe s:DoAutocmd('User FugitiveChanged')
2360   finally
2361     unlet! g:fugitive_event
2362     " Force statusline reload with the buffer's Git dir
2363     let &ro = &ro
2364   endtry
2365   return ''
2366 endfunction
2368 function! s:ReloadStatusBuffer(...) abort
2369   if get(b:, 'fugitive_type', '') !=# 'index'
2370     return ''
2371   endif
2372   let original_lnum = a:0 ? a:1 : line('.')
2373   let info = s:StageInfo(original_lnum)
2374   call fugitive#BufReadStatus()
2375   exe s:StageSeek(info, original_lnum)
2376   normal! 0
2377   return ''
2378 endfunction
2380 function! s:ReloadStatus(...) abort
2381   call s:ExpireStatus(-1)
2382   call s:ReloadStatusBuffer(a:0 ? a:1 : line('.'))
2383   exe s:DoAutocmdChanged(-1)
2384   return ''
2385 endfunction
2387 let s:last_time = reltime()
2388 if !exists('s:last_times')
2389   let s:last_times = {}
2390 endif
2392 function! s:ExpireStatus(bufnr) abort
2393   if a:bufnr == -2
2394     let s:last_time = reltime()
2395     return ''
2396   endif
2397   let dir = s:Dir(a:bufnr)
2398   if len(dir)
2399     let s:last_times[s:cpath(dir)] = reltime()
2400   endif
2401   return ''
2402 endfunction
2404 function! FugitiveReloadCheck() abort
2405   let t = b:fugitive_reltime
2406   return [t, reltimestr(reltime(s:last_time, t)),
2407         \ reltimestr(reltime(get(s:last_times, s:cpath(s:Dir()), t), t))]
2408 endfunction
2410 function! s:ReloadWinStatus(...) abort
2411   if get(b:, 'fugitive_type', '') !=# 'index' || &modified
2412     return
2413   endif
2414   if !exists('b:fugitive_reltime')
2415     exe s:ReloadStatusBuffer()
2416     return
2417   endif
2418   let t = b:fugitive_reltime
2419   if reltimestr(reltime(s:last_time, t)) =~# '-\|\d\{10\}\.' ||
2420         \ reltimestr(reltime(get(s:last_times, s:cpath(s:Dir()), t), t)) =~# '-\|\d\{10\}\.'
2421     exe s:ReloadStatusBuffer()
2422   endif
2423 endfunction
2425 function! s:ReloadTabStatus(...) abort
2426   let mytab = tabpagenr()
2427   let tab = a:0 ? a:1 : mytab
2428   for winnr in range(1, tabpagewinnr(tab, '$'))
2429     if getbufvar(tabpagebuflist(tab)[winnr-1], 'fugitive_type') ==# 'index'
2430       execute 'tabnext '.tab
2431       if winnr != winnr()
2432         execute winnr.'wincmd w'
2433         let restorewinnr = 1
2434       endif
2435       try
2436         call s:ReloadWinStatus()
2437       finally
2438         if exists('restorewinnr')
2439           unlet restorewinnr
2440           wincmd p
2441         endif
2442         execute 'tabnext '.mytab
2443       endtry
2444     endif
2445   endfor
2446   unlet! t:fugitive_reload_status
2447 endfunction
2449 function! fugitive#ReloadStatus(...) abort
2450   call s:ExpireStatus(a:0 ? a:1 : -1)
2451   if a:0 > 1 ? a:2 : 1
2452     let t = reltime()
2453     let t:fugitive_reload_status = t
2454     for tabnr in exists('*settabvar') ? range(1, tabpagenr('$')) : []
2455       call settabvar(tabnr, 'fugitive_reload_status', t)
2456     endfor
2457     call s:ReloadTabStatus()
2458     exe s:DoAutocmdChanged(a:0 ? a:1 : -1)
2459   else
2460     call s:ReloadWinStatus()
2461   endif
2462   return ''
2463 endfunction
2465 function! fugitive#EfmDir(...) abort
2466   let dir = matchstr(a:0 ? a:1 : &errorformat, '\c,%\\&\%(git\|fugitive\)_\=dir=\zs\%(\\.\|[^,]\)*')
2467   let dir = substitute(dir, '%%', '%', 'g')
2468   let dir = substitute(dir, '\\\ze[\,]', '', 'g')
2469   return dir
2470 endfunction
2472 augroup fugitive_status
2473   autocmd!
2474   autocmd BufWritePost         * call fugitive#ReloadStatus(-1, 0)
2475   autocmd ShellCmdPost,ShellFilterPost * nested call fugitive#ReloadStatus(-2, 0)
2476   autocmd BufDelete * nested
2477         \ if getbufvar(+expand('<abuf>'), 'buftype') ==# 'terminal' |
2478         \   if !empty(FugitiveGitDir(+expand('<abuf>'))) |
2479         \     call fugitive#ReloadStatus(+expand('<abuf>'), 1) |
2480         \   else |
2481         \     call fugitive#ReloadStatus(-2, 0) |
2482         \  endif |
2483         \ endif
2484   autocmd QuickFixCmdPost make,lmake,[cl]file,[cl]getfile nested
2485         \ call fugitive#ReloadStatus(fugitive#EfmDir(), 1)
2486   if !has('win32')
2487     autocmd FocusGained        * call fugitive#ReloadStatus(-2, 0)
2488   endif
2489   autocmd BufEnter index,index.lock
2490         \ call s:ReloadWinStatus()
2491   autocmd TabEnter *
2492         \ if exists('t:fugitive_reload_status') |
2493         \    call s:ReloadTabStatus() |
2494         \ endif
2495 augroup END
2497 function! s:StageInfo(...) abort
2498   let lnum = a:0 ? a:1 : line('.')
2499   let sigil = matchstr(getline(lnum), '^[ @\+-]')
2500   let offset = -1
2501   if len(sigil)
2502     let type = sigil ==# '-' ? '-' : '+'
2503     while lnum > 0 && getline(lnum) !~# '^@'
2504       if getline(lnum) =~# '^[ '.type.']'
2505         let offset += 1
2506       endif
2507       let lnum -= 1
2508     endwhile
2509     let offset += matchstr(getline(lnum), type.'\zs\d\+')
2510     while getline(lnum) =~# '^[ @\+-]'
2511       let lnum -= 1
2512     endwhile
2513   endif
2514   let slnum = lnum + 1
2515   let section = ''
2516   let index = 0
2517   while len(getline(slnum - 1)) && empty(section)
2518     let slnum -= 1
2519     let section = matchstr(getline(slnum), '^\u\l\+\ze.* (\d\+)$')
2520     if empty(section) && getline(slnum) !~# '^[ @\+-]'
2521       let index += 1
2522     endif
2523   endwhile
2524   let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
2525   return {'section': section,
2526         \ 'heading': getline(slnum),
2527         \ 'sigil': sigil,
2528         \ 'offset': offset,
2529         \ 'filename': text,
2530         \ 'relative': reverse(split(text, ' -> ')),
2531         \ 'paths': map(reverse(split(text, ' -> ')), 's:Tree() . "/" . v:val'),
2532         \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
2533         \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
2534         \ 'sub': get(get(get(b:fugitive_files, section, {}), text, {}), 'sub', ''),
2535         \ 'index': index}
2536 endfunction
2538 function! s:Selection(arg1, ...) abort
2539   if a:arg1 ==# 'n'
2540     let arg1 = line('.')
2541     let arg2 = -v:count
2542   elseif a:arg1 ==# 'v'
2543     let arg1 = line("'<")
2544     let arg2 = line("'>")
2545   else
2546     let arg1 = a:arg1
2547     let arg2 = a:0 ? a:1 : 0
2548   endif
2549   let first = arg1
2550   if arg2 < 0
2551     let last = first - arg2 - 1
2552   elseif arg2 > 0
2553     let last = arg2
2554   else
2555     let last = first
2556   endif
2557   while getline(first) =~# '^$\|^[A-Z][a-z]'
2558     let first += 1
2559   endwhile
2560   if first > last || &filetype !=# 'fugitive'
2561     return []
2562   endif
2563   let flnum = first
2564   while getline(flnum) =~# '^[ @\+-]'
2565     let flnum -= 1
2566   endwhile
2567   let slnum = flnum + 1
2568   let section = ''
2569   let index = 0
2570   while len(getline(slnum - 1)) && empty(section)
2571     let slnum -= 1
2572     let heading = matchstr(getline(slnum), '^\u\l\+.* (\d\+)$')
2573     if empty(heading) && getline(slnum) !~# '^[ @\+-]'
2574       let index += 1
2575     endif
2576   endwhile
2577   let results = []
2578   let template = {
2579         \ 'heading': heading,
2580         \ 'section': matchstr(heading, '^\u\l\+\ze.* (\d\+)$'),
2581         \ 'filename': '',
2582         \ 'relative': [],
2583         \ 'paths': [],
2584         \ 'commit': '',
2585         \ 'status': '',
2586         \ 'patch': 0,
2587         \ 'index': index}
2588   let line = getline(flnum)
2589   let lnum = first - (arg1 == flnum ? 0 : 1)
2590   let root = s:Tree() . '/'
2591   while lnum <= last
2592     if line =~# '^\u\l\+\ze.* (\d\+)$'
2593       let template.heading = getline(lnum)
2594       let template.section = matchstr(template.heading, '^\u\l\+\ze.* (\d\+)$')
2595       let template.index = 0
2596     elseif line =~# '^[ @\+-]'
2597       let template.index -= 1
2598       if !results[-1].patch
2599         let results[-1].patch = lnum
2600       endif
2601       let results[-1].lnum = lnum
2602     elseif line =~# '^[A-Z?] '
2603       let filename = matchstr(line, '^[A-Z?] \zs.*')
2604       call add(results, extend(deepcopy(template), {
2605             \ 'lnum': lnum,
2606             \ 'filename': filename,
2607             \ 'relative': reverse(split(filename, ' -> ')),
2608             \ 'paths': map(reverse(split(filename, ' -> ')), 'root . v:val'),
2609             \ 'status': matchstr(line, '^[A-Z?]'),
2610             \ }))
2611     elseif line =~# '^\x\x\x\+ '
2612       call add(results, extend({
2613             \ 'lnum': lnum,
2614             \ 'commit': matchstr(line, '^\x\x\x\+'),
2615             \ }, template, 'keep'))
2616     elseif line =~# '^\l\+ \x\x\x\+ '
2617       call add(results, extend({
2618             \ 'lnum': lnum,
2619             \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
2620             \ 'status': matchstr(line, '^\l\+'),
2621             \ }, template, 'keep'))
2622     endif
2623     let lnum += 1
2624     let template.index += 1
2625     let line = getline(lnum)
2626   endwhile
2627   if len(results) && results[0].patch && arg2 == 0
2628     while getline(results[0].patch) =~# '^[ \+-]'
2629       let results[0].patch -= 1
2630     endwhile
2631     while getline(results[0].lnum + 1) =~# '^[ \+-]'
2632       let results[0].lnum += 1
2633     endwhile
2634   endif
2635   return results
2636 endfunction
2638 function! s:StageArgs(visual) abort
2639   let commits = []
2640   let paths = []
2641   for record in s:Selection(a:visual ? 'v' : 'n')
2642     if len(record.commit)
2643       call add(commits, record.commit)
2644     endif
2645     call extend(paths, record.paths)
2646   endfor
2647   if s:cpath(s:Tree(), getcwd())
2648     call map(paths, 'fugitive#Path(v:val, "./")')
2649   endif
2650   return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
2651 endfunction
2653 function! s:Do(action, visual) abort
2654   let line = getline('.')
2655   let reload = 0
2656   if !a:visual && !v:count && line =~# '^[A-Z][a-z]'
2657     let header = matchstr(line, '^\S\+\ze:')
2658     if len(header) && exists('*s:Do' . a:action . header . 'Header')
2659       let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
2660     else
2661       let section = matchstr(line, '^\S\+')
2662       if exists('*s:Do' . a:action . section . 'Heading')
2663         let reload = s:Do{a:action}{section}Heading(line) > 0
2664       endif
2665     endif
2666     return reload ? s:ReloadStatus() : ''
2667   endif
2668   let selection = s:Selection(a:visual ? 'v' : 'n')
2669   if empty(selection)
2670     return ''
2671   endif
2672   call filter(selection, 'v:val.section ==# selection[0].section')
2673   let status = 0
2674   let err = ''
2675   try
2676     for record in selection
2677       if exists('*s:Do' . a:action . record.section)
2678         let status = s:Do{a:action}{record.section}(record)
2679       else
2680         continue
2681       endif
2682       if !status
2683         return ''
2684       endif
2685       let reload = reload || (status > 0)
2686     endfor
2687     if status < 0
2688       execute record.lnum + 1
2689     endif
2690     let success = 1
2691   catch /^fugitive:/
2692     return 'echoerr ' . string(v:exception)
2693   finally
2694     if reload
2695       execute s:ReloadStatus()
2696     endif
2697     if exists('success')
2698       call s:StageReveal()
2699     endif
2700   endtry
2701   return ''
2702 endfunction
2704 function! s:StageReveal() abort
2705   exe 'normal! zv'
2706   let begin = line('.')
2707   if getline(begin) =~# '^@'
2708     let end = begin + 1
2709     while getline(end) =~# '^[ \+-]'
2710       let end += 1
2711     endwhile
2712   elseif getline(begin) =~# '^commit '
2713     let end = begin
2714     while end < line('$') && getline(end + 1) !~# '^commit '
2715       let end += 1
2716     endwhile
2717   elseif getline(begin) =~# s:section_pattern
2718     let end = begin
2719     while len(getline(end + 1))
2720       let end += 1
2721     endwhile
2722   endif
2723   if exists('end')
2724     while line('.') > line('w0') + &scrolloff && end > line('w$')
2725       execute "normal! \<C-E>"
2726     endwhile
2727   endif
2728 endfunction
2730 let s:file_pattern = '^[A-Z?] .\|^diff --'
2731 let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
2732 let s:item_pattern = s:file_commit_pattern . '\|^@@'
2734 function! s:NextHunk(count) abort
2735   if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
2736     exe s:StageInline('show')
2737   endif
2738   for i in range(a:count)
2739     if &filetype ==# 'fugitive'
2740       call search(s:file_pattern . '\|^@', 'W')
2741       if getline('.') =~# s:file_pattern
2742         exe s:StageInline('show')
2743         if getline(line('.') + 1) =~# '^@'
2744           +
2745         endif
2746       endif
2747     else
2748       call search('^@@', 'W')
2749     endif
2750   endfor
2751   call s:StageReveal()
2752   return '.'
2753 endfunction
2755 function! s:PreviousHunk(count) abort
2756   for i in range(a:count)
2757     if &filetype ==# 'fugitive'
2758       let lnum = search(s:file_pattern . '\|^@','Wbn')
2759       call s:StageInline('show', lnum)
2760       call search('^? .\|^@','Wb')
2761     else
2762       call search('^@@', 'Wb')
2763     endif
2764   endfor
2765   call s:StageReveal()
2766   return '.'
2767 endfunction
2769 function! s:NextFile(count) abort
2770   for i in range(a:count)
2771     exe s:StageInline('hide')
2772     if !search(s:file_pattern, 'W')
2773       break
2774     endif
2775   endfor
2776   exe s:StageInline('hide')
2777   return '.'
2778 endfunction
2780 function! s:PreviousFile(count) abort
2781   exe s:StageInline('hide')
2782   for i in range(a:count)
2783     if !search(s:file_pattern, 'Wb')
2784       break
2785     endif
2786     exe s:StageInline('hide')
2787   endfor
2788   return '.'
2789 endfunction
2791 function! s:NextItem(count) abort
2792   for i in range(a:count)
2793     if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
2794       call search('^commit ', 'W')
2795     endif
2796   endfor
2797   call s:StageReveal()
2798   return '.'
2799 endfunction
2801 function! s:PreviousItem(count) abort
2802   for i in range(a:count)
2803     if !search(s:item_pattern, 'Wbe') && getline('.') !~# s:item_pattern
2804       call search('^commit ', 'Wbe')
2805     endif
2806   endfor
2807   call s:StageReveal()
2808   return '.'
2809 endfunction
2811 let s:section_pattern = '^[A-Z][a-z][^:]*$'
2812 let s:section_commit_pattern = s:section_pattern . '\|^commit '
2814 function! s:NextSection(count) abort
2815   let orig = line('.')
2816   if getline('.') !~# '^commit '
2817     -
2818   endif
2819   for i in range(a:count)
2820     if !search(s:section_commit_pattern, 'W')
2821       break
2822     endif
2823   endfor
2824   if getline('.') =~# s:section_commit_pattern
2825     call s:StageReveal()
2826     return getline('.') =~# s:section_pattern ? '+' : ':'
2827   else
2828     return orig
2829   endif
2830 endfunction
2832 function! s:PreviousSection(count) abort
2833   let orig = line('.')
2834   if getline('.') !~# '^commit '
2835     -
2836   endif
2837   for i in range(a:count)
2838     if !search(s:section_commit_pattern . '\|\%^', 'bW')
2839       break
2840     endif
2841   endfor
2842   if getline('.') =~# s:section_commit_pattern || line('.') == 1
2843     call s:StageReveal()
2844     return getline('.') =~# s:section_pattern ? '+' : ':'
2845   else
2846     return orig
2847   endif
2848 endfunction
2850 function! s:NextSectionEnd(count) abort
2851   +
2852   if empty(getline('.'))
2853     +
2854   endif
2855   for i in range(a:count)
2856     if !search(s:section_commit_pattern, 'W')
2857       return '$'
2858     endif
2859   endfor
2860   return search('^.', 'Wb')
2861 endfunction
2863 function! s:PreviousSectionEnd(count) abort
2864   let old = line('.')
2865   for i in range(a:count)
2866     if search(s:section_commit_pattern, 'Wb') <= 1
2867       exe old
2868       if i
2869         break
2870       else
2871         return ''
2872       endif
2873     endif
2874     let old = line('.')
2875   endfor
2876   return search('^.', 'Wb')
2877 endfunction
2879 function! s:PatchSearchExpr(reverse) abort
2880   let line = getline('.')
2881   if col('.') ==# 1 && line =~# '^[+-]'
2882     if line =~# '^[+-]\{3\} '
2883       let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
2884     else
2885       let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
2886     endif
2887     if a:reverse
2888       return '?' . escape(pattern, '/') . "\<CR>"
2889     else
2890       return '/' . escape(pattern, '/?') . "\<CR>"
2891     endif
2892   endif
2893   return a:reverse ? '#' : '*'
2894 endfunction
2896 function! s:StageInline(mode, ...) abort
2897   if &filetype !=# 'fugitive'
2898     return ''
2899   endif
2900   let lnum1 = a:0 ? a:1 : line('.')
2901   let lnum = lnum1 + 1
2902   if a:0 > 1 && a:2 == 0
2903     let info = s:StageInfo(lnum - 1)
2904     if empty(info.paths) && len(info.section)
2905       while len(getline(lnum))
2906         let lnum += 1
2907       endwhile
2908     endif
2909   elseif a:0 > 1
2910     let lnum += a:2 - 1
2911   endif
2912   while lnum > lnum1
2913     let lnum -= 1
2914     while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
2915       let lnum -= 1
2916     endwhile
2917     let info = s:StageInfo(lnum)
2918     if !has_key(b:fugitive_diff, info.section)
2919       continue
2920     endif
2921     if getline(lnum + 1) =~# '^[ @\+-]'
2922       let lnum2 = lnum + 1
2923       while getline(lnum2 + 1) =~# '^[ @\+-]'
2924         let lnum2 += 1
2925       endwhile
2926       if a:mode !=# 'show'
2927         setlocal modifiable noreadonly
2928         exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
2929         call remove(b:fugitive_expanded[info.section], info.filename)
2930         setlocal nomodifiable readonly nomodified
2931       endif
2932       continue
2933     endif
2934     if !has_key(b:fugitive_diff, info.section) || info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
2935       continue
2936     endif
2937     let mode = ''
2938     let diff = []
2939     let index = 0
2940     let start = -1
2941     for line in b:fugitive_diff[info.section]
2942       if mode ==# 'await' && line[0] ==# '@'
2943         let mode = 'capture'
2944       endif
2945       if mode !=# 'head' && line !~# '^[ @\+-]'
2946         if len(diff)
2947           break
2948         endif
2949         let start = index
2950         let mode = 'head'
2951       elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '--- ' . info.relative[-1]
2952         let mode = 'await'
2953       elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '+++ ' . info.relative[0]
2954         let mode = 'await'
2955       elseif mode ==# 'capture'
2956         call add(diff, line)
2957       elseif line[0] ==# '@'
2958         let mode = ''
2959       endif
2960       let index += 1
2961     endfor
2962     if len(diff)
2963       setlocal modifiable noreadonly
2964       silent call append(lnum, diff)
2965       let b:fugitive_expanded[info.section][info.filename] = [start, len(diff)]
2966       setlocal nomodifiable readonly nomodified
2967     endif
2968   endwhile
2969   return lnum
2970 endfunction
2972 function! s:NextExpandedHunk(count) abort
2973   for i in range(a:count)
2974     call s:StageInline('show', line('.'), 1)
2975     call search(s:file_pattern . '\|^@','W')
2976   endfor
2977   return '.'
2978 endfunction
2980 function! s:StageDiff(diff) abort
2981   let lnum = line('.')
2982   let info = s:StageInfo(lnum)
2983   let prefix = info.offset > 0 ? '+' . info.offset : ''
2984   if info.sub =~# '^S'
2985     if info.section ==# 'Staged'
2986       return 'Git! diff --no-ext-diff --submodule=log --cached -- ' . info.paths[0]
2987     elseif info.sub =~# '^SC'
2988       return 'Git! diff --no-ext-diff --submodule=log -- ' . info.paths[0]
2989     else
2990       return 'Git! diff --no-ext-diff --submodule=diff -- ' . info.paths[0]
2991     endif
2992   elseif empty(info.paths) && info.section ==# 'Staged'
2993     return 'Git! diff --no-ext-diff --cached'
2994   elseif empty(info.paths)
2995     return 'Git! diff --no-ext-diff'
2996   elseif len(info.paths) > 1
2997     execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
2998     return a:diff . '! HEAD:'.s:fnameescape(info.paths[1])
2999   elseif info.section ==# 'Staged' && info.sigil ==# '-'
3000     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
3001     return a:diff . '! :0:%'
3002   elseif info.section ==# 'Staged'
3003     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
3004     return a:diff . '! @:%'
3005   elseif info.sigil ==# '-'
3006     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
3007     return a:diff . '! :(top)%'
3008   else
3009     execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
3010     return a:diff . '!'
3011   endif
3012 endfunction
3014 function! s:StageDiffEdit() abort
3015   let info = s:StageInfo(line('.'))
3016   let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
3017   if info.section ==# 'Staged'
3018     return 'Git! diff --no-ext-diff --cached '.s:fnameescape(arg)
3019   elseif info.status ==# '?'
3020     call s:TreeChomp('add', '--intent-to-add', '--', arg)
3021     return s:ReloadStatus()
3022   else
3023     return 'Git! diff --no-ext-diff '.s:fnameescape(arg)
3024   endif
3025 endfunction
3027 function! s:StageApply(info, reverse, extra) abort
3028   if a:info.status ==# 'R'
3029     call s:throw('fugitive: patching renamed file not yet supported')
3030   endif
3031   let cmd = ['apply', '-p0', '--recount'] + a:extra
3032   let info = a:info
3033   let start = info.patch
3034   let end = info.lnum
3035   let lines = getline(start, end)
3036   if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
3037     return -1
3038   endif
3039   while getline(end) =~# '^[-+ ]'
3040     let end += 1
3041     if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
3042       call add(lines, ' ' . getline(end)[1:-1])
3043     endif
3044   endwhile
3045   while start > 0 && getline(start) !~# '^@'
3046     let start -= 1
3047     if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
3048       call insert(lines, ' ' . getline(start)[1:-1])
3049     elseif getline(start) =~# '^@'
3050       call insert(lines, getline(start))
3051     endif
3052   endwhile
3053   if start == 0
3054     throw 'fugitive: cold not find hunk'
3055   elseif getline(start) !~# '^@@ '
3056     throw 'fugitive: cannot apply conflict hunk'
3057   endif
3058   let i = b:fugitive_expanded[info.section][info.filename][0]
3059   let head = []
3060   while get(b:fugitive_diff[info.section], i, '@') !~# '^@'
3061     call add(head, b:fugitive_diff[info.section][i])
3062     let i += 1
3063   endwhile
3064   call extend(lines, head, 'keep')
3065   let temp = tempname()
3066   call writefile(lines, temp)
3067   if a:reverse
3068     call add(cmd, '--reverse')
3069   endif
3070   call extend(cmd, ['--', temp])
3071   let [output, exec_error] = s:ChompError(cmd)
3072   if !exec_error
3073     return 1
3074   endif
3075   call s:throw(output)
3076 endfunction
3078 function! s:StageDelete(lnum1, lnum2, count) abort
3079   let restore = []
3080   let err = ''
3081   try
3082     for info in s:Selection(a:lnum1, a:lnum2)
3083       if empty(info.paths)
3084         continue
3085       endif
3086       let sub = get(get(get(b:fugitive_files, info.section, {}), info.filename, {}), 'sub')
3087       if sub =~# '^S'
3088         if info.status ==# 'A'
3089           continue
3090         endif
3091         if info.section ==# 'Staged'
3092           call s:TreeChomp('reset', '--', info.paths[0])
3093         endif
3094         if info.status =~# '[MD]'
3095           call s:TreeChomp('submodule', 'update', '--', info.paths[0])
3096           call add(restore, ':Git -C ' . info.relative[0] . ' checkout -')
3097         endif
3098         continue
3099       endif
3100       if info.status ==# 'D'
3101         let undo = 'Gremove'
3102       elseif info.paths[0] =~# '/$'
3103         let err .= '|echoerr ' . string('fugitive: will not delete directory ' . string(info.relative[0]))
3104         break
3105       else
3106         let undo = 'Gread ' . s:TreeChomp('hash-object', '-w', '--', info.paths[0])[0:10]
3107       endif
3108       if info.patch
3109         call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
3110       elseif info.status ==# '?'
3111         call s:TreeChomp('clean', '-f', '--', info.paths[0])
3112       elseif a:count == 2
3113         call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
3114       elseif a:count == 3
3115         call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
3116       elseif info.status =~# '[ADU]' &&
3117             \ get(b:fugitive_files[info.section ==# 'Staged' ? 'Unstaged' : 'Staged'], info.filename, {'status': ''}).status =~# '[AU]'
3118         call s:TreeChomp('checkout', info.section ==# 'Staged' ? '--ours' : '--theirs', '--', info.paths[0])
3119       elseif info.status ==# 'U'
3120         call s:TreeChomp('rm', '--', info.paths[0])
3121       elseif info.status ==# 'A'
3122         call s:TreeChomp('rm', '-f', '--', info.paths[0])
3123       elseif info.section ==# 'Unstaged'
3124         call s:TreeChomp('checkout', '--', info.paths[0])
3125       else
3126         call s:TreeChomp('checkout', 'HEAD^{}', '--', info.paths[0])
3127       endif
3128       call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|' . undo)
3129     endfor
3130   catch /^fugitive:/
3131     let err .= '|echoerr ' . string(v:exception)
3132   endtry
3133   if empty(restore)
3134     return err[1:-1]
3135   endif
3136   exe s:ReloadStatus()
3137   call s:StageReveal()
3138   if len(restore)
3139     return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
3140   else
3141     return 'checktime|redraw' . err
3142   endif
3143 endfunction
3145 function! s:StageIgnore(lnum1, lnum2, count) abort
3146   let paths = []
3147   for info in s:Selection(a:lnum1, a:lnum2)
3148     call extend(paths, info.relative)
3149   endfor
3150   call map(paths, '"/" . v:val')
3151   exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
3152   let last = line('$')
3153   if last == 1 && empty(getline(1))
3154     call setline(last, paths)
3155   else
3156     call append(last, paths)
3157     exe last + 1
3158   endif
3159   return ''
3160 endfunction
3162 function! s:DoToggleHeadHeader(value) abort
3163   exe 'edit' s:fnameescape(s:Dir())
3164   call search('\C^index$', 'wc')
3165 endfunction
3167 function! s:DoStageUnpushedHeading(heading) abort
3168   let remote = matchstr(a:heading, 'to \zs[^/]\+\ze/')
3169   if empty(remote)
3170     let remote = '.'
3171   endif
3172   let branch = matchstr(a:heading, 'to \%([^/]\+/\)\=\zs\S\+')
3173   call feedkeys(':Gpush ' . remote . ' ' . 'HEAD:' . branch)
3174 endfunction
3176 function! s:DoToggleUnpushedHeading(heading) abort
3177   return s:DoStageUnpushedHeading(a:heading)
3178 endfunction
3180 function! s:DoStageUnpushed(record) abort
3181   let remote = matchstr(a:record.heading, 'to \zs[^/]\+\ze/')
3182   if empty(remote)
3183     let remote = '.'
3184   endif
3185   let branch = matchstr(a:record.heading, 'to \%([^/]\+/\)\=\zs\S\+')
3186   call feedkeys(':Gpush ' . remote . ' ' . a:record.commit . ':' . branch)
3187 endfunction
3189 function! s:DoToggleUnpushed(record) abort
3190   return s:DoStageUnpushed(a:record)
3191 endfunction
3193 function! s:DoUnstageUnpulledHeading(heading) abort
3194   call feedkeys(':Grebase')
3195 endfunction
3197 function! s:DoToggleUnpulledHeading(heading) abort
3198   call s:DoUnstageUnpulledHeading(a:heading)
3199 endfunction
3201 function! s:DoUnstageUnpulled(record) abort
3202   call feedkeys(':Grebase ' . a:record.commit)
3203 endfunction
3205 function! s:DoToggleUnpulled(record) abort
3206   call s:DoUnstageUnpulled(a:record)
3207 endfunction
3209 function! s:DoUnstageUnpushed(record) abort
3210   call feedkeys(':Grebase --autosquash ' . a:record.commit . '^')
3211 endfunction
3213 function! s:DoToggleStagedHeading(...) abort
3214   call s:TreeChomp('reset', '-q')
3215   return 1
3216 endfunction
3218 function! s:DoUnstageStagedHeading(heading) abort
3219   return s:DoToggleStagedHeading(a:heading)
3220 endfunction
3222 function! s:DoToggleUnstagedHeading(...) abort
3223   call s:TreeChomp('add', '-u')
3224   return 1
3225 endfunction
3227 function! s:DoStageUnstagedHeading(heading) abort
3228   return s:DoToggleUnstagedHeading(a:heading)
3229 endfunction
3231 function! s:DoToggleUntrackedHeading(...) abort
3232   call s:TreeChomp('add', '.')
3233   return 1
3234 endfunction
3236 function! s:DoStageUntrackedHeading(heading) abort
3237   return s:DoToggleUntrackedHeading(a:heading)
3238 endfunction
3240 function! s:DoToggleStaged(record) abort
3241   if a:record.patch
3242     return s:StageApply(a:record, 1, ['--cached'])
3243   else
3244     call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3245     return 1
3246   endif
3247 endfunction
3249 function! s:DoUnstageStaged(record) abort
3250   return s:DoToggleStaged(a:record)
3251 endfunction
3253 function! s:DoToggleUnstaged(record) abort
3254   if a:record.patch && a:record.status !=# 'A'
3255     return s:StageApply(a:record, 0, ['--cached'])
3256   else
3257     call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
3258     return 1
3259   endif
3260 endfunction
3262 function! s:DoStageUnstaged(record) abort
3263   return s:DoToggleUnstaged(a:record)
3264 endfunction
3266 function! s:DoUnstageUnstaged(record) abort
3267   if a:record.status ==# 'A'
3268     call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3269     return 1
3270   else
3271     return -1
3272   endif
3273 endfunction
3275 function! s:DoToggleUntracked(record) abort
3276   call s:TreeChomp(['add', '--'] + a:record.paths)
3277   return 1
3278 endfunction
3280 function! s:DoStageUntracked(record) abort
3281   return s:DoToggleUntracked(a:record)
3282 endfunction
3284 function! s:StagePatch(lnum1,lnum2) abort
3285   let add = []
3286   let reset = []
3287   let intend = []
3289   for lnum in range(a:lnum1,a:lnum2)
3290     let info = s:StageInfo(lnum)
3291     if empty(info.paths) && info.section ==# 'Staged'
3292       return 'Git reset --patch'
3293     elseif empty(info.paths) && info.section ==# 'Unstaged'
3294       return 'Git add --patch'
3295     elseif empty(info.paths) && info.section ==# 'Untracked'
3296       return 'Git add --interactive'
3297     elseif empty(info.paths)
3298       continue
3299     endif
3300     execute lnum
3301     if info.section ==# 'Staged'
3302       let reset += info.relative
3303     elseif info.section ==# 'Untracked'
3304       let intend += info.paths
3305     elseif info.status !~# '^D'
3306       let add += info.relative
3307     endif
3308   endfor
3309   try
3310     if !empty(intend)
3311       call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
3312     endif
3313     if !empty(add)
3314       execute "Git add --patch -- ".join(map(add,'s:fnameescape(v:val)'))
3315     endif
3316     if !empty(reset)
3317       execute "Git reset --patch -- ".join(map(reset,'s:fnameescape(v:val)'))
3318     endif
3319   catch /^fugitive:/
3320     return 'echoerr ' . string(v:exception)
3321   endtry
3322   return s:ReloadStatus()
3323 endfunction
3325 " Section: :Gcommit, :Grevert
3327 function! s:CommitInteractive(line1, line2, range, bang, mods, args, patch) abort
3328   let status = s:StatusCommand(a:line1, a:line2, a:range, a:line2, a:bang, a:mods, '', '', [])
3329   let status = len(status) ? status . '|' : ''
3330   if a:patch
3331     return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
3332   else
3333     return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
3334   endif
3335 endfunction
3337 function! s:CommitSubcommand(line1, line2, range, bang, mods, args, ...) abort
3338   let mods = substitute(s:Mods(a:mods), '\C\<tab\>', '-tab', 'g')
3339   let dir = a:0 ? a:1 : s:Dir()
3340   let tree = s:Tree(dir)
3341   let msgfile = fugitive#Find('.git/COMMIT_EDITMSG', dir)
3342   let outfile = tempname()
3343   try
3344     if s:winshell()
3345       let command = 'set GIT_EDITOR=false& '
3346     else
3347       let command = 'env GIT_EDITOR=false '
3348     endif
3349     let argv = a:args
3350     let i = 0
3351     while get(argv, i, '--') !=# '--'
3352       if argv[i] =~# '^-[apzsneiovq].'
3353         call insert(argv, argv[i][0:1])
3354         let argv[i+1] = '-' . argv[i+1][2:-1]
3355       else
3356         let i += 1
3357       endif
3358     endwhile
3359     let command .= s:UserCommand(dir, ['commit'] + argv)
3360     if (&autowrite || &autowriteall) && !a:0
3361       silent! wall
3362     endif
3363     if s:HasOpt(argv, '-i', '--interactive')
3364       return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 0)
3365     elseif s:HasOpt(argv, '-p', '--patch')
3366       return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 1)
3367     else
3368       let [error_string, exec_error] = s:TempCmd(outfile, command)
3369       let errors = split(error_string, "\n")
3370     endif
3371     if !has('gui_running')
3372       redraw!
3373     endif
3374     if !exec_error
3375       echo join(errors, "\n")
3376       if filereadable(outfile)
3377         echo join(readfile(outfile), "\n")
3378       endif
3379       call fugitive#ReloadStatus(dir, 1)
3380       return ''
3381     else
3382       let error = get(errors,-2,get(errors,-1,'!'))
3383       if error =~# 'false''\=\.$'
3384         let i = 0
3385         while get(argv, i, '--') !=# '--'
3386           if argv[i] =~# '^\%(-[eips]\|-[CcFm].\+\|--edit\|--interactive\|--patch\|--signoff\|--reedit-message=.*\|--reuse-message=.*\|--file=.*\|--message=.*\)$'
3387             call remove(argv, i)
3388           elseif argv[i] =~# '^\%(-[CcFm]\|--reedit-message\|--reuse-message\|--file\|--message\)$'
3389             call remove(argv, i, i + 1)
3390           else
3391             if argv[i] =~# '^--cleanup\>'
3392               let cleanup = 1
3393             endif
3394             let i += 1
3395           endif
3396         endwhile
3397         call insert(argv, '--no-signoff', i)
3398         call insert(argv, '--no-interactive', i)
3399         call insert(argv, '--no-edit', i)
3400         if !exists('cleanup')
3401           call insert(argv, '--cleanup=strip')
3402         endif
3403         call extend(argv, ['-F', msgfile], 'keep')
3404         if (bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&modified) || a:line2 == 0
3405           execute mods . 'keepalt edit' s:fnameescape(msgfile)
3406         elseif s:HasOpt(argv, '-v') || mods =~# '\<tab\>'
3407           execute mods . 'keepalt -tabedit' s:fnameescape(msgfile)
3408         else
3409           execute mods . 'keepalt split' s:fnameescape(msgfile)
3410         endif
3411         let b:fugitive_commit_arguments = argv
3412         setlocal bufhidden=wipe filetype=gitcommit
3413         return '1'
3414       elseif empty(errors)
3415         let out = readfile(outfile)
3416         echo get(out, -1, '') =~# 'stash\|\d' ? get(out, -2, '') : get(out, -1, '')
3417         return ''
3418       else
3419         echo join(errors, "\n")
3420         return ''
3421       endif
3422     endif
3423   catch /^fugitive:/
3424     return 'echoerr ' . string(v:exception)
3425   finally
3426     call delete(outfile)
3427   endtry
3428 endfunction
3430 function! s:RevertSubcommand(line1, line2, range, bang, mods, args) abort
3431   let dir = s:Dir()
3432   let no_commit = s:HasOpt(a:args, '-n', '--no-commit', '--no-edit', '--abort', '--continue', '--quit')
3433   let cmd = s:UserCommand(dir, ['revert'] + (no_commit ? [] : ['-n']) + a:args)
3434   let [out, exec_error] = s:SystemError(cmd)
3435   call fugitive#ReloadStatus(dir, 1)
3436   if no_commit || exec_error
3437     return 'echo ' . string(substitute(out, "\n$", '', ''))
3438   endif
3439   return s:CommitSubcommand(a:line1, a:line2, a:range, a:bang, a:mods, [], dir)
3440 endfunction
3442 function! fugitive#CommitComplete(A, L, P) abort
3443   if a:A =~# '^--fixup=\|^--squash='
3444     let commits = s:LinesError(['log', '--pretty=format:%s', '@{upstream}..'])[0]
3445     let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
3446     if pre =~# "'"
3447       call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
3448       call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
3449       return commits
3450     else
3451       return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
3452     endif
3453   else
3454     return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'))
3455   endif
3456   return []
3457 endfunction
3459 function! fugitive#RevertComplete(A, L, P) abort
3460   return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'))
3461 endfunction
3463 function! s:FinishCommit() abort
3464   let buf = +expand('<abuf>')
3465   let args = getbufvar(buf, 'fugitive_commit_arguments')
3466   if !empty(args)
3467     call setbufvar(buf, 'fugitive_commit_arguments', [])
3468     if getbufvar(buf, 'fugitive_commit_rebase')
3469       call setbufvar(buf, 'fugitive_commit_rebase', 0)
3470       let s:rebase_continue = s:Dir(buf)
3471     endif
3472     return s:CommitSubcommand(-1, -1, 0, 0, '', args, s:Dir(buf))
3473   endif
3474   return ''
3475 endfunction
3477 " Section: :Gmerge, :Grebase, :Gpull
3479 function! fugitive#MergeComplete(A, L, P) abort
3480   return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'))
3481 endfunction
3483 function! fugitive#RebaseComplete(A, L, P) abort
3484   return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'))
3485 endfunction
3487 function! fugitive#PullComplete(A, L, P) abort
3488   return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'))
3489 endfunction
3491 function! s:RebaseSequenceAborter() abort
3492   if !exists('s:rebase_sequence_aborter')
3493     let temp = tempname() . '.sh'
3494     call writefile(
3495           \ ['#!/bin/sh',
3496           \ 'echo exec false | cat - "$1" > "$1.fugitive"',
3497           \ 'mv "$1.fugitive" "$1"'],
3498           \ temp)
3499     let s:rebase_sequence_aborter = temp
3500   endif
3501   return s:rebase_sequence_aborter
3502 endfunction
3504 function! fugitive#Cwindow() abort
3505   if &buftype == 'quickfix'
3506     cwindow
3507   else
3508     botright cwindow
3509     if &buftype == 'quickfix'
3510       wincmd p
3511     endif
3512   endif
3513 endfunction
3515 let s:common_efm = ''
3516       \ . '%+Egit:%.%#,'
3517       \ . '%+Eusage:%.%#,'
3518       \ . '%+Eerror:%.%#,'
3519       \ . '%+Efatal:%.%#,'
3520       \ . '%-G%.%#%\e[K%.%#,'
3521       \ . '%-G%.%#%\r%.%\+'
3523 let s:rebase_abbrevs = {
3524       \ 'p': 'pick',
3525       \ 'r': 'reword',
3526       \ 'e': 'edit',
3527       \ 's': 'squash',
3528       \ 'f': 'fixup',
3529       \ 'x': 'exec',
3530       \ 'd': 'drop',
3531       \ 'l': 'label',
3532       \ 't': 'reset',
3533       \ 'm': 'merge',
3534       \ 'b': 'break',
3535       \ }
3537 function! s:RebaseEdit(cmd, dir) abort
3538   let rebase_todo = s:fnameescape(fugitive#Find('.git/rebase-merge/git-rebase-todo', a:dir))
3540   if filereadable(rebase_todo)
3541     let new = readfile(rebase_todo)
3542     let sha_length = 0
3543     let shas = {}
3545     for i in range(len(new))
3546       if new[i] =~# '^\l\+\s\+[0-9a-f]\{5,\}\>'
3547         let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3548         if !sha_length
3549           let sha_length = len(s:TreeChomp(a:dir, 'rev-parse', '--short', sha))
3550         endif
3551         let shortened_sha = strpart(sha, 0, sha_length)
3552         let shas[shortened_sha] = sha
3553         let new[i] = substitute(new[i], sha, shortened_sha, '')
3554       endif
3555     endfor
3556     call writefile(new, rebase_todo)
3557   endif
3558   return a:cmd . ' +setlocal\ bufhidden=wipe\|' . escape('let b:fugitive_rebase_shas = ' . string(shas), ' ') . ' ' . rebase_todo
3559 endfunction
3561 function! s:MergeRebase(cmd, bang, mods, args, ...) abort
3562   let dir = a:0 ? a:1 : s:Dir()
3563   let args = a:args
3564   let mods = s:Mods(a:mods)
3565   if a:cmd =~# '^rebase' && s:HasOpt(args, '-i', '--interactive')
3566     let cmd = fugitive#Prepare(dir, '-c', 'sequence.editor=sh ' . s:RebaseSequenceAborter(), 'rebase') . ' ' . s:shellesc(args)
3567     let out = system(cmd)[0:-2]
3568     for file in ['end', 'msgnum']
3569       let file = fugitive#Find('.git/rebase-merge/' . file, dir)
3570       if !filereadable(file)
3571         return 'echoerr ' . string("fugitive: " . out)
3572       endif
3573       call writefile([readfile(file)[0] - 1], file)
3574     endfor
3575     call writefile([], fugitive#Find('.git/rebase-merge/done', dir))
3576     if a:bang
3577       return 'exe'
3578     endif
3579     return s:RebaseEdit(mods . 'split', dir)
3580   elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--edit-todo') && filereadable(fugitive#Find('.git/rebase-merge/git-rebase-todo', dir))
3581     return s:RebaseEdit(mods . 'split', dir)
3582   elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--continue') && !a:0
3583     let rdir = fugitive#Find('.git/rebase-merge', dir)
3584     let exec_error = s:ChompError([dir, 'diff-index', '--cached', '--quiet', 'HEAD', '--'])[1]
3585     if exec_error && isdirectory(rdir)
3586       if getfsize(rdir . '/amend') <= 0
3587         return 'exe ' . string(mods . 'Gcommit -n -F ' . s:fnameescape(rdir .'/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3588       elseif readfile(rdir . '/amend')[0] ==# fugitive#Head(-1, dir)
3589         return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(rdir . '/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3590       endif
3591     endif
3592   endif
3593   let had_merge_msg = filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3594   let argv = []
3595   if a:cmd ==# 'pull'
3596     let argv += s:AskPassArgs(dir) + ['pull', '--progress']
3597   else
3598     call add(argv, a:cmd)
3599   endif
3600   if !s:HasOpt(args, '--no-edit', '--abort', '-m') && a:cmd !=# 'rebase'
3601     call add(argv, '--edit')
3602   endif
3603   if a:cmd ==# 'rebase' && s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '--interactive', '-i')
3604     call add(argv, '--interactive')
3605   endif
3606   call extend(argv, args)
3608   let [mp, efm] = [&l:mp, &l:efm]
3609   try
3610     let cdback = s:Cd(s:Tree(dir))
3611     let &l:errorformat = ''
3612           \ . '%-Gerror:%.%#false''.,'
3613           \ . '%-G%.%# ''git commit'' %.%#,'
3614           \ . '%+Emerge:%.%#,'
3615           \ . s:common_efm . ','
3616           \ . '%+ECannot %.%#: You have unstaged changes.,'
3617           \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
3618           \ . '%+EThere is no tracking information for the current branch.,'
3619           \ . '%+EYou are not currently on a branch. Please specify which,'
3620           \ . '%+I %#git rebase --continue,'
3621           \ . 'CONFLICT (%m): %f deleted in %.%#,'
3622           \ . 'CONFLICT (%m): Merge conflict in %f,'
3623           \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
3624           \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
3625           \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
3626           \ . '%+ECONFLICT %.%#,'
3627           \ . '%+EKONFLIKT %.%#,'
3628           \ . '%+ECONFLIT %.%#,'
3629           \ . "%+EXUNG \u0110\u1ed8T %.%#,"
3630           \ . "%+E\u51b2\u7a81 %.%#,"
3631           \ . 'U%\t%f'
3632     if a:cmd =~# '^merge' && empty(args) &&
3633           \ (had_merge_msg || isdirectory(fugitive#Find('.git/rebase-apply', dir)) ||
3634           \  !empty(s:TreeChomp(dir, 'diff-files', '--diff-filter=U')))
3635       let cmd = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
3636     else
3637       let cmd = s:UserCommand(dir, argv)
3638     endif
3639     if !empty($GIT_SEQUENCE_EDITOR) || has('win32')
3640       let old_sequence_editor = $GIT_SEQUENCE_EDITOR
3641       let $GIT_SEQUENCE_EDITOR = 'true'
3642     else
3643       let cmd = 'env GIT_SEQUENCE_EDITOR=true ' . cmd
3644     endif
3645     if !empty($GIT_EDITOR) || has('win32')
3646       let old_editor = $GIT_EDITOR
3647       let $GIT_EDITOR = 'false'
3648     else
3649       let cmd = 'env GIT_EDITOR=false ' . substitute(cmd, '^env ', '', '')
3650     endif
3651     if !has('patch-8.1.0334') && has('terminal') && &autowrite
3652       let autowrite_was_set = 1
3653       set noautowrite
3654       silent! wall
3655     endif
3656     let &l:makeprg = cmd
3657     silent noautocmd make!
3658   catch /^Vim\%((\a\+)\)\=:E211/
3659     let err = v:exception
3660   finally
3661     if exists('autowrite_was_set')
3662       set autowrite
3663     endif
3664     redraw!
3665     let [&l:mp, &l:efm] = [mp, efm]
3666     if exists('old_editor')
3667       let $GIT_EDITOR = old_editor
3668     endif
3669     if exists('old_sequence_editor')
3670       let $GIT_SEQUENCE_EDITOR = old_sequence_editor
3671     endif
3672     execute cdback
3673   endtry
3674   call fugitive#ReloadStatus(dir, 1)
3675   if empty(filter(getqflist(),'v:val.valid && v:val.type !=# "I"'))
3676     if a:cmd =~# '^rebase' &&
3677           \ filereadable(fugitive#Find('.git/rebase-merge/amend', dir)) &&
3678           \ filereadable(fugitive#Find('.git/rebase-merge/done', dir)) &&
3679           \ get(readfile(fugitive#Find('.git/rebase-merge/done', dir)), -1, '') =~# '^[^e]'
3680       cclose
3681       return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(fugitive#Find('.git/rebase-merge/message', dir)) . ' -e') . '|let b:fugitive_commit_rebase = 1'
3682     elseif !had_merge_msg && filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3683       cclose
3684       return mods . 'Gcommit --no-status -n -t '.s:fnameescape(fugitive#Find('.git/MERGE_MSG', dir))
3685     endif
3686   endif
3687   let qflist = getqflist()
3688   let found = 0
3689   for e in qflist
3690     if !empty(e.bufnr)
3691       let found = 1
3692       let e.pattern = '^<<<<<<<'
3693     endif
3694   endfor
3695   call fugitive#Cwindow()
3696   if found
3697     call setqflist(qflist, 'r')
3698     if !a:bang
3699       call s:BlurStatus()
3700       return 'cfirst'
3701     endif
3702   endif
3703   return exists('err') ? 'echoerr '.string(err) : 'exe'
3704 endfunction
3706 function! s:RebaseClean(file) abort
3707   if !filereadable(a:file)
3708     return ''
3709   endif
3710   let old = readfile(a:file)
3711   let new = copy(old)
3712   for i in range(len(new))
3713     let new[i] = substitute(new[i], '^\l\>', '\=get(s:rebase_abbrevs,submatch(0),submatch(0))', '')
3715     let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3716     let rebase_shas = getbufvar(a:file, 'fugitive_rebase_shas')
3717     if len(sha) && type(rebase_shas) == type({}) && has_key(rebase_shas, sha)
3718       let new[i] = substitute(new[i], '\C\<' . sha . '\>', rebase_shas[sha], '')
3719     endif
3720   endfor
3721   if new !=# old
3722     call writefile(new, a:file)
3723   endif
3724   return ''
3725 endfunction
3727 function! s:MergeSubcommand(line1, line2, range, bang, mods, args) abort
3728   return s:MergeRebase('merge', a:bang, a:mods, a:args)
3729 endfunction
3731 function! s:RebaseSubcommand(line1, line2, range, bang, mods, args) abort
3732   return s:MergeRebase('rebase', a:bang, a:mods, a:args)
3733 endfunction
3735 function! s:PullSubcommand(line1, line2, range, bang, mods, args) abort
3736   return s:MergeRebase('pull', a:bang, a:mods, a:args)
3737 endfunction
3739 augroup fugitive_merge
3740   autocmd!
3741   autocmd VimLeavePre,BufDelete git-rebase-todo
3742         \ if getbufvar(+expand('<abuf>'), '&bufhidden') ==# 'wipe' |
3743         \   call s:RebaseClean(expand('<afile>')) |
3744         \   if getfsize(FugitiveFind('.git/rebase-merge/done', +expand('<abuf>'))) == 0 |
3745         \     let s:rebase_continue = FugitiveGitDir(+expand('<abuf>')) |
3746         \   endif |
3747         \ endif
3748   autocmd BufEnter * nested
3749         \ if exists('s:rebase_continue') |
3750         \   exe s:MergeRebase('rebase', 0, '', [getfsize(fugitive#Find('.git/rebase-merge/git-rebase-todo', s:rebase_continue)) > 0 ? '--continue' : '--abort'], remove(s:, 'rebase_continue')) |
3751         \ endif
3752 augroup END
3754 " Section: :Ggrep, :Glog
3756 if !exists('g:fugitive_summary_format')
3757   let g:fugitive_summary_format = '%s'
3758 endif
3760 function! fugitive#GrepComplete(A, L, P) abort
3761   return s:CompleteSub('grep', a:A, a:L, a:P)
3762 endfunction
3764 function! fugitive#LogComplete(A, L, P) abort
3765   return s:CompleteSub('log', a:A, a:L, a:P)
3766 endfunction
3768 function! s:GrepParseLine(prefix, name_only, dir, line) abort
3769   let entry = {'valid': 1}
3770   let match = matchlist(a:line, '^\(.\{-\}\):\(\d\+\):\(\d\+:\)\=\(.*\)$')
3771   if len(match)
3772     let entry.module = match[1]
3773     let entry.lnum = +match[2]
3774     let entry.col = +match[3]
3775     let entry.text = match[4]
3776   elseif a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
3777     return {'text': a:line}
3778   else
3779     let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
3780     if len(entry.module)
3781       let entry.text = 'Binary file'
3782       let entry.valid = 0
3783     endif
3784   endif
3785   if empty(entry.module) && a:name_only
3786     let entry.module = a:line
3787   endif
3788   if empty(entry.module)
3789     return {'text': a:line}
3790   endif
3791   if entry.module !~# ':'
3792     let entry.filename = a:prefix . entry.module
3793   else
3794     let entry.filename = fugitive#Find(entry.module, a:dir)
3795   endif
3796   return entry
3797 endfunction
3799 function! s:GrepSubcommand(line1, line2, range, bang, mods, args) abort
3800   let dir = s:Dir()
3801   exe s:DirCheck(dir)
3802   let listnr = a:line1 == 0 ? a:line1 : a:line2
3803   let cmd = ['--no-pager', 'grep', '-n', '--no-color', '--full-name']
3804   if fugitive#GitVersion(2, 19)
3805     call add(cmd, '--column')
3806   endif
3807   let tree = s:Tree(dir)
3808   if type(a:args) == type([])
3809     let [args, after] = [a:args, '']
3810   else
3811     let [args, after] = s:SplitExpandChain(a:args, tree)
3812   endif
3813   let prefix = FugitiveVimPath(s:HasOpt(args, '--cached') || empty(tree) ? 'fugitive://' . dir . '//0/' : tree . '/')
3814   let name_only = s:HasOpt(args, '-l', '--files-with-matches', '--name-only', '-L', '--files-without-match')
3815   let title = [listnr < 0 ? ':Ggrep' : ':Glgrep'] + args
3816   if listnr > 0
3817     exe listnr 'wincmd w'
3818   else
3819     call s:BlurStatus()
3820   endif
3821   redraw
3822   call s:QuickfixCreate(listnr, {'title': (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)})
3823   let tempfile = tempname()
3824   let event = listnr < 0 ? 'grep-fugitive' : 'lgrep-fugitive'
3825   silent exe s:DoAutocmd('QuickFixCmdPre ' . event)
3826   exe '!' . escape(s:UserCommand(dir, cmd + args), '%#!')
3827         \ printf(&shellpipe . (&shellpipe =~# '%s' ? '' : ' %s'), s:shellesc(tempfile))
3828   let list = map(readfile(tempfile), 's:GrepParseLine(prefix, name_only, dir, v:val)')
3829   call s:QuickfixSet(listnr, list, 'a')
3830   silent exe s:DoAutocmd('QuickFixCmdPost ' . event)
3831   if !has('gui_running')
3832     redraw
3833   endif
3834   if !a:bang && !empty(list)
3835     return (listnr < 0 ? 'c' : 'l').'first' . after
3836   else
3837     return after[1:-1]
3838   endif
3839 endfunction
3841 function! s:LogFlushQueue(state) abort
3842   let queue = remove(a:state, 'queue')
3843   if a:state.child_found
3844     call remove(queue, 0)
3845   endif
3846   if len(queue) && queue[-1] ==# {'text': ''}
3847     call remove(queue, -1)
3848   endif
3849   return queue
3850 endfunction
3852 function! s:LogParse(state, dir, line) abort
3853   if a:state.context ==# 'hunk' && a:line =~# '^[-+ ]'
3854     return []
3855   endif
3856   let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
3857   if len(list)
3858     let a:state.context = 'commit'
3859     let a:state.base = 'fugitive://' . a:dir . '//' . list[2]
3860     let a:state.base_module = len(list[1]) ? list[1] : list[2]
3861     let a:state.message = list[3]
3862     if has_key(a:state, 'diffing')
3863       call remove(a:state, 'diffing')
3864     endif
3865     let queue = s:LogFlushQueue(a:state)
3866     let a:state.queue = [{
3867           \ 'valid': 1,
3868           \ 'filename': a:state.base . a:state.target,
3869           \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
3870           \ 'text': a:state.message}]
3871     let a:state.child_found = 0
3872     return queue
3873   elseif type(a:line) == type(0)
3874     return s:LogFlushQueue(a:state)
3875   elseif a:line =~# '^diff'
3876     let a:state.context = 'diffhead'
3877   elseif a:line =~# '^[+-]\{3\} \w/' && a:state.context ==# 'diffhead'
3878     let a:state.diffing = a:line[5:-1]
3879   elseif a:line =~# '^@@[^@]*+\d' && has_key(a:state, 'diffing') && has_key(a:state, 'base')
3880     let a:state.context = 'hunk'
3881     if empty(a:state.target) || a:state.target ==# a:state.diffing
3882       let a:state.child_found = 1
3883       call add(a:state.queue, {
3884             \ 'valid': 1,
3885             \ 'filename': a:state.base . a:state.diffing,
3886             \ 'module': a:state.base_module . substitute(a:state.diffing, '^/', ':', ''),
3887             \ 'lnum': +matchstr(a:line, '+\zs\d\+'),
3888             \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
3889     endif
3890   elseif a:state.follow &&
3891         \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
3892     let rename = matchstr(a:line, '^ rename \zs.* => .*\ze (\d\+%)$')
3893     if len(rename)
3894       let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
3895       if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
3896         let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
3897       endif
3898     endif
3899     if !get(a:state, 'ignore_summary')
3900       call add(a:state.queue, {'text': a:line})
3901     endif
3902   elseif a:state.context ==# 'commit' || a:state.context ==# 'init'
3903     call add(a:state.queue, {'text': a:line})
3904   endif
3905   return []
3906 endfunction
3908 function! fugitive#LogCommand(line1, count, range, bang, mods, args, type) abort
3909   let dir = s:Dir()
3910   exe s:DirCheck(dir)
3911   let listnr = a:type =~# '^l' ? 0 : -1
3912   let [args, after] = s:SplitExpandChain(a:args, s:Tree(dir))
3913   let split = index(args, '--')
3914   if split > 0
3915     let paths = args[split : -1]
3916     let args = args[0 : split - 1]
3917   elseif split == 0
3918     let paths = args
3919     let args = []
3920   else
3921     let paths = []
3922   endif
3923   if a:line1 == 0 && a:count
3924     let path = fugitive#Path(bufname(a:count), '/', dir)
3925   elseif a:count >= 0
3926     let path = fugitive#Path(@%, '/', dir)
3927   else
3928      let path = ''
3929   endif
3930   let range = ''
3931   let extra = []
3932   let state = {'context': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
3933   if path =~# '^/\.git\%(/\|$\)\|^$'
3934     let path = ''
3935   elseif a:line1 == 0
3936     let range = "0," . (a:count ? a:count : bufnr(''))
3937     let extra = ['.' . path]
3938     if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
3939       let state.follow = 1
3940       if !s:HasOpt(args, '--follow')
3941         call insert(args, '--follow')
3942       endif
3943       if !s:HasOpt(args, '--summary')
3944         call insert(args, '--summary')
3945         let state.ignore_summary = 1
3946       endif
3947     endif
3948   elseif a:count > 0
3949     if !s:HasOpt(args, '--merges', '--no-merges')
3950       call insert(args, '--no-merges')
3951     endif
3952     call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
3953   endif
3954   if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
3955     let owner = s:Owner(@%, dir)
3956     if len(owner)
3957       call add(args, owner)
3958     endif
3959   endif
3960   if empty(extra)
3961     let path = ''
3962   endif
3963   if s:HasOpt(args, '-g', '--walk-reflogs')
3964     let format = "%gd\t%H %gs"
3965   else
3966     let format = "%h\t%H " . g:fugitive_summary_format
3967   endif
3968   let cmd = ['--no-pager']
3969   if fugitive#GitVersion(1, 9)
3970     call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'])
3971   else
3972     call extend(cmd, ['log', '-U0', '--no-patch'])
3973   endif
3974   call extend(cmd,
3975         \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
3976         \ args + paths + extra)
3977   let state.target = path
3978   let title = (listnr < 0 ? ':Gclog ' : ':Gllog ') . s:fnameescape(args + paths)
3979   if empty(paths + extra) && empty(a:type) && len(s:Relative('/'))
3980     let after = '|echohl WarningMsg|echo ' . string('Use :0Glog or :0Gclog for old behavior of targeting current file') . '|echohl NONE' . after
3981   endif
3982   return s:QuickfixStream(listnr, title, s:UserCommandList(dir) + cmd, !a:bang, s:function('s:LogParse'), state, dir) . after
3983 endfunction
3985 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
3987 function! s:UsableWin(nr) abort
3988   return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
3989         \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
3990         \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
3991         \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
3992 endfunction
3994 function! s:OpenParse(args, wants_cmd) abort
3995   let opts = []
3996   let cmds = []
3997   let args = copy(a:args)
3998   while !empty(args)
3999     if args[0] =~# '^++'
4000       call add(opts, ' ' . escape(remove(args, 0), ' |"'))
4001     elseif a:wants_cmd && args[0] =~# '^+'
4002       call add(cmds, remove(args, 0)[1:-1])
4003     else
4004       break
4005     endif
4006   endwhile
4007   if len(args)
4008     let file = join(args)
4009   elseif empty(expand('%'))
4010     let file = ''
4011   elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
4012     let file = '>:0'
4013   else
4014     let file = '>'
4015   endif
4016   let dir = s:Dir()
4017   let efile = s:Expand(file)
4018   let url = fugitive#Find(efile, dir)
4020   if a:wants_cmd && file[0] ==# '>' && efile[0] !=# '>' && get(b:, 'fugitive_type', '') isnot# 'tree' && &filetype !=# 'netrw'
4021     let line = line('.')
4022     if expand('%:p') !=# url
4023       let diffcmd = 'diff'
4024       let from = s:DirRev(@%)[1]
4025       let to = s:DirRev(url)[1]
4026       if empty(from) && empty(to)
4027         let diffcmd = 'diff-files'
4028         let args = ['--', expand('%:p'), url]
4029       elseif empty(to)
4030         let args = [from, '--', url]
4031       elseif empty(from)
4032         let args = [to, '--', expand('%:p')]
4033         let reverse = 1
4034       else
4035         let args = [from, to]
4036       endif
4037       let [res, exec_error] = s:LinesError([dir, diffcmd, '-U0'] + args)
4038       if !exec_error
4039         call filter(res, 'v:val =~# "^@@ "')
4040         call map(res, 'substitute(v:val, ''[-+]\d\+\zs '', ",1 ", "g")')
4041         call map(res, 'matchlist(v:val, ''^@@ -\(\d\+\),\(\d\+\) +\(\d\+\),\(\d\+\) @@'')[1:4]')
4042         if exists('reverse')
4043           call map(res, 'v:val[2:3] + v:val[0:1]')
4044         endif
4045         call filter(res, 'v:val[0] < '.line('.'))
4046         let hunk = get(res, -1, [0,0,0,0])
4047         if hunk[0] + hunk[1] > line('.')
4048           let line = hunk[2] + max([1 - hunk[3], 0])
4049         else
4050           let line = hunk[2] + max([hunk[3], 1]) + line('.') - hunk[0] - max([hunk[1], 1])
4051         endif
4052       endif
4053     endif
4054     call insert(cmds, line)
4055   endif
4057   let pre = join(opts, '')
4058   if len(cmds) > 1
4059     let pre .= ' +' . escape(join(map(cmds, '"exe ".string(v:val)'), '|'), ' |"')
4060   elseif len(cmds)
4061     let pre .= ' +' . escape(cmds[0], ' |"')
4062   endif
4063   return [url, pre]
4064 endfunction
4066 function! s:DiffClose() abort
4067   let mywinnr = winnr()
4068   for winnr in [winnr('#')] + range(winnr('$'),1,-1)
4069     if winnr != mywinnr && getwinvar(winnr,'&diff')
4070       execute winnr.'wincmd w'
4071       close
4072       if winnr('$') > 1
4073         wincmd p
4074       endif
4075     endif
4076   endfor
4077   diffoff!
4078 endfunction
4080 function! s:BlurStatus() abort
4081   if (&previewwindow || exists('w:fugitive_status')) && get(b:,'fugitive_type', '') ==# 'index'
4082     let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
4083     if len(winnrs)
4084       exe winnrs[0].'wincmd w'
4085     else
4086       belowright new
4087     endif
4088     if &diff
4089       call s:DiffClose()
4090     endif
4091   endif
4092 endfunction
4094 function! s:OpenExec(cmd, mods, args, ...) abort
4095   let dir = a:0 ? s:Dir(a:1) : s:Dir()
4096   let temp = tempname()
4097   let columns = get(g:, 'fugitive_columns', 80)
4098   if columns <= 0
4099     let env = ''
4100   elseif s:winshell()
4101     let env = 'set COLUMNS=' . columns . '& '
4102   else
4103     let env = 'env COLUMNS=' . columns . ' '
4104   endif
4105   silent! execute '!' . escape(env . s:UserCommand(dir, ['--no-pager'] + a:args), '!#%') .
4106         \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
4107   redraw!
4108   let temp = s:Resolve(temp)
4109   let first = join(readfile(temp, '', 2), "\n")
4110   if first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
4111     let filetype = 'man'
4112   else
4113     let filetype = 'git'
4114   endif
4115   let s:temp_files[s:cpath(temp)] = { 'dir': dir, 'filetype': filetype, 'modifiable': first =~# '^diff ' }
4116   if a:cmd ==# 'edit'
4117     call s:BlurStatus()
4118   endif
4119   silent execute s:Mods(a:mods) . a:cmd temp
4120   call fugitive#ReloadStatus(dir, 1)
4121   return 'echo ' . string(':!' . s:UserCommand(dir, a:args))
4122 endfunction
4124 function! fugitive#Open(cmd, bang, mods, arg, args) abort
4125   if a:bang
4126     return s:OpenExec(a:cmd, a:mods, s:SplitExpand(a:arg, s:Tree()))
4127   endif
4129   let mods = s:Mods(a:mods)
4130   try
4131     let [file, pre] = s:OpenParse(a:args, 1)
4132   catch /^fugitive:/
4133     return 'echoerr ' . string(v:exception)
4134   endtry
4135   if file !~# '^\a\a\+:'
4136     let file = s:sub(file, '/$', '')
4137   endif
4138   if a:cmd ==# 'edit'
4139     call s:BlurStatus()
4140   endif
4141   return mods . a:cmd . pre . ' ' . s:fnameescape(file)
4142 endfunction
4144 function! fugitive#ReadCommand(line1, count, range, bang, mods, arg, args) abort
4145   let mods = s:Mods(a:mods)
4146   let after = a:count
4147   if a:count < 0
4148     let delete = 'silent 1,' . line('$') . 'delete_|'
4149     let after = line('$')
4150   elseif a:range == 2
4151     let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
4152   else
4153     let delete = ''
4154   endif
4155   if a:bang
4156     let dir = s:Dir()
4157     let args = s:SplitExpand(a:arg, s:Tree(dir))
4158     silent execute mods . after . 'read!' escape(s:UserCommand(dir, ['--no-pager'] + args), '!#%')
4159     execute delete . 'diffupdate'
4160     call fugitive#ReloadStatus(dir, 1)
4161     return 'redraw|echo '.string(':!'.s:UserCommand(dir, args))
4162   endif
4163   try
4164     let [file, pre] = s:OpenParse(a:args, 0)
4165   catch /^fugitive:/
4166     return 'echoerr ' . string(v:exception)
4167   endtry
4168   if file =~# '^fugitive:' && after is# 0
4169     return 'exe ' .string(mods . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
4170   endif
4171   if foldlevel(after)
4172     exe after . 'foldopen!'
4173   endif
4174   return mods . after . 'read' . pre . ' ' . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
4175 endfunction
4177 function! fugitive#EditComplete(A, L, P) abort
4178   if a:A =~# '^>'
4179     return map(s:FilterEscape(s:CompleteHeads(s:Dir()), a:A[1:-1]), "'>' . v:val")
4180   else
4181     return fugitive#CompleteObject(a:A, a:L, a:P)
4182   endif
4183 endfunction
4185 function! fugitive#ReadComplete(A, L, P) abort
4186   if a:L =~# '^\w\+!'
4187     return fugitive#Complete(a:A, a:L, a:P)
4188   else
4189     return fugitive#EditComplete(a:A, a:L, a:P)
4190   endif
4191 endfunction
4193 " Section: :Gwrite, :Gwq
4195 function! fugitive#WriteCommand(line1, line2, range, bang, mods, arg, args) abort
4196   if exists('b:fugitive_commit_arguments')
4197     return 'write|bdelete'
4198   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
4199     return 'wq'
4200   elseif get(b:, 'fugitive_type', '') ==# 'index'
4201     return 'Gcommit'
4202   elseif &buftype ==# 'nowrite' && getline(4) =~# '^+++ '
4203     let filename = getline(4)[6:-1]
4204     setlocal buftype=
4205     silent write
4206     setlocal buftype=nowrite
4207     if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
4208       let [message, exec_error] = s:ChompError(['apply', '--cached', '--reverse', '--', expand('%:p')])
4209     else
4210       let [message, exec_error] = s:ChompError(['apply', '--cached', '--', expand('%:p')])
4211     endif
4212     if exec_error
4213       echohl ErrorMsg
4214       echo message
4215       echohl NONE
4216       return ''
4217     elseif a:bang
4218       return 'bdelete'
4219     else
4220       return 'Gedit '.fnameescape(filename)
4221     endif
4222   endif
4223   let mytab = tabpagenr()
4224   let mybufnr = bufnr('')
4225   try
4226     let file = len(a:args) ? s:Generate(s:Expand(join(a:args, ' '))) : fugitive#Real(@%)
4227   catch /^fugitive:/
4228     return 'echoerr ' . string(v:exception)
4229   endtry
4230   if empty(file)
4231     return 'echoerr '.string('fugitive: cannot determine file path')
4232   endif
4233   if file =~# '^fugitive:'
4234     return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
4235   endif
4236   exe s:DirCheck()
4237   let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
4238   if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
4239     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
4240     return 'echoerr v:errmsg'
4241   endif
4242   let treebufnr = 0
4243   for nr in range(1,bufnr('$'))
4244     if fnamemodify(bufname(nr),':p') ==# file
4245       let treebufnr = nr
4246     endif
4247   endfor
4249   if treebufnr > 0 && treebufnr != bufnr('')
4250     let temp = tempname()
4251     silent execute 'keepalt %write '.temp
4252     for tab in [mytab] + range(1,tabpagenr('$'))
4253       for winnr in range(1,tabpagewinnr(tab,'$'))
4254         if tabpagebuflist(tab)[winnr-1] == treebufnr
4255           execute 'tabnext '.tab
4256           if winnr != winnr()
4257             execute winnr.'wincmd w'
4258             let restorewinnr = 1
4259           endif
4260           try
4261             let lnum = line('.')
4262             let last = line('$')
4263             silent execute '$read '.temp
4264             silent execute '1,'.last.'delete_'
4265             silent write!
4266             silent execute lnum
4267             diffupdate
4268             let did = 1
4269           finally
4270             if exists('restorewinnr')
4271               wincmd p
4272             endif
4273             execute 'tabnext '.mytab
4274           endtry
4275           break
4276         endif
4277       endfor
4278     endfor
4279     if !exists('did')
4280       call writefile(readfile(temp,'b'),file,'b')
4281     endif
4282   else
4283     execute 'write! '.s:fnameescape(file)
4284   endif
4286   if a:bang
4287     let [error, exec_error] = s:ChompError(['add', '--force', '--', file])
4288   else
4289     let [error, exec_error] = s:ChompError(['add', '--', file])
4290   endif
4291   if exec_error
4292     let v:errmsg = 'fugitive: '.error
4293     return 'echoerr v:errmsg'
4294   endif
4295   if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
4296     setlocal nomodified
4297   endif
4299   let one = s:Generate(':1:'.file)
4300   let two = s:Generate(':2:'.file)
4301   let three = s:Generate(':3:'.file)
4302   for nr in range(1,bufnr('$'))
4303     let name = fnamemodify(bufname(nr), ':p')
4304     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
4305       execute nr.'bdelete'
4306     endif
4307   endfor
4309   unlet! restorewinnr
4310   let zero = s:Generate(':0:'.file)
4311   silent exe s:DoAutocmd('BufWritePost ' . s:fnameescape(zero))
4312   for tab in range(1,tabpagenr('$'))
4313     for winnr in range(1,tabpagewinnr(tab,'$'))
4314       let bufnr = tabpagebuflist(tab)[winnr-1]
4315       let bufname = fnamemodify(bufname(bufnr), ':p')
4316       if bufname ==# zero && bufnr != mybufnr
4317         execute 'tabnext '.tab
4318         if winnr != winnr()
4319           execute winnr.'wincmd w'
4320           let restorewinnr = 1
4321         endif
4322         try
4323           let lnum = line('.')
4324           let last = line('$')
4325           silent execute '$read '.s:fnameescape(file)
4326           silent execute '1,'.last.'delete_'
4327           silent execute lnum
4328           setlocal nomodified
4329           diffupdate
4330         finally
4331           if exists('restorewinnr')
4332             wincmd p
4333           endif
4334           execute 'tabnext '.mytab
4335         endtry
4336         break
4337       endif
4338     endfor
4339   endfor
4340   call fugitive#ReloadStatus(-1, 1)
4341   return 'checktime'
4342 endfunction
4344 function! fugitive#WqCommand(...) abort
4345   let bang = a:4 ? '!' : ''
4346   if exists('b:fugitive_commit_arguments')
4347     return 'wq'.bang
4348   endif
4349   let result = call('fugitive#WriteCommand', a:000)
4350   if result =~# '^\%(write\|wq\|echoerr\)'
4351     return s:sub(result,'^write','wq')
4352   else
4353     return result.'|quit'.bang
4354   endif
4355 endfunction
4357 augroup fugitive_commit
4358   autocmd!
4359   autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute substitute(s:FinishCommit(), '\C^echoerr \(''[^'']*''\)*', 'redraw|echohl ErrorMsg|echo \1|echohl NONE', '')
4360 augroup END
4362 " Section: :Gpush, :Gfetch
4364 function! fugitive#PushComplete(A, L, P) abort
4365   return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompleteRemote'))
4366 endfunction
4368 function! fugitive#FetchComplete(A, L, P) abort
4369   return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'))
4370 endfunction
4372 function! s:AskPassArgs(dir) abort
4373   if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) && fugitive#GitVersion(1, 8) &&
4374         \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#Config('core.askPass', a:dir))
4375     if s:executable(s:ExecPath() . '/git-gui--askpass')
4376       return ['-c', 'core.askPass=' . s:ExecPath() . '/git-gui--askpass']
4377     elseif s:executable('ssh-askpass')
4378       return ['-c', 'core.askPass=ssh-askpass']
4379     endif
4380   endif
4381   return []
4382 endfunction
4384 function! s:Dispatch(bang, cmd, args) abort
4385   let dir = s:Dir()
4386   let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
4387   try
4388     let b:current_compiler = 'git'
4389     let &l:errorformat = s:common_efm .
4390           \ ',%\&git_dir=' . escape(substitute(dir, '%', '%%', 'g'), '\,')
4391     let &l:makeprg = s:UserCommand(dir, s:AskPassArgs(dir) + [a:cmd] + a:args)
4392     if exists(':Make') == 2
4393       Make
4394       return ''
4395     else
4396       if !has('patch-8.1.0334') && has('terminal') && &autowrite
4397         let autowrite_was_set = 1
4398         set noautowrite
4399         silent! wall
4400       endif
4401       silent noautocmd make!
4402       redraw!
4403       return 'call fugitive#Cwindow()|silent ' . s:DoAutocmd('ShellCmdPost')
4404     endif
4405   finally
4406     let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
4407     if empty(cc) | unlet! b:current_compiler | endif
4408     if exists('autowrite_was_set')
4409       set autowrite
4410     endif
4411   endtry
4412 endfunction
4414 function! s:PushSubcommand(line1, line2, range, bang, mods, args) abort
4415   return s:Dispatch(a:bang ? '!' : '', 'push', a:args)
4416 endfunction
4418 function! s:FetchSubcommand(line1, line2, range, bang, mods, args) abort
4419   return s:Dispatch(a:bang ? '!' : '', 'fetch', a:args)
4420 endfunction
4422 " Section: :Gdiff
4424 augroup fugitive_diff
4425   autocmd!
4426   autocmd BufWinLeave *
4427         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
4428         \   call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
4429         \ endif
4430   autocmd BufWinEnter *
4431         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
4432         \   call s:diffoff() |
4433         \ endif
4434 augroup END
4436 function! s:can_diffoff(buf) abort
4437   return getwinvar(bufwinnr(a:buf), '&diff') &&
4438         \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
4439 endfunction
4441 function! fugitive#CanDiffoff(buf) abort
4442   return s:can_diffoff(bufnr(a:buf))
4443 endfunction
4445 function! s:diff_modifier(count) abort
4446   let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
4447   if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
4448     return ''
4449   elseif &diffopt =~# 'vertical'
4450     return 'vertical '
4451   elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
4452     return ''
4453   else
4454     return 'vertical '
4455   endif
4456 endfunction
4458 function! s:diff_window_count() abort
4459   let c = 0
4460   for nr in range(1,winnr('$'))
4461     let c += getwinvar(nr,'&diff')
4462   endfor
4463   return c
4464 endfunction
4466 function! s:diff_restore() abort
4467   let restore = 'setlocal nodiff noscrollbind'
4468         \ . ' scrollopt=' . &l:scrollopt
4469         \ . (&l:wrap ? ' wrap' : ' nowrap')
4470         \ . ' foldlevel=999'
4471         \ . ' foldmethod=' . &l:foldmethod
4472         \ . ' foldcolumn=' . &l:foldcolumn
4473         \ . ' foldlevel=' . &l:foldlevel
4474         \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
4475   if has('cursorbind')
4476     let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
4477   endif
4478   return restore
4479 endfunction
4481 function! s:diffthis() abort
4482   if !&diff
4483     let w:fugitive_diff_restore = s:diff_restore()
4484     diffthis
4485   endif
4486 endfunction
4488 function! s:diffoff() abort
4489   if exists('w:fugitive_diff_restore')
4490     execute w:fugitive_diff_restore
4491     unlet w:fugitive_diff_restore
4492   else
4493     diffoff
4494   endif
4495 endfunction
4497 function! s:diffoff_all(dir) abort
4498   let curwin = winnr()
4499   for nr in range(1,winnr('$'))
4500     if getwinvar(nr,'&diff')
4501       if nr != winnr()
4502         execute nr.'wincmd w'
4503         let restorewinnr = 1
4504       endif
4505       if s:Dir() ==# a:dir
4506         call s:diffoff()
4507       endif
4508     endif
4509   endfor
4510   execute curwin.'wincmd w'
4511 endfunction
4513 function! s:CompareAge(mine, theirs) abort
4514   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
4515   let mine = substitute(a:mine, '^:', '', '')
4516   let theirs = substitute(a:theirs, '^:', '', '')
4517   let my_score    = get(scores, ':'.mine, 0)
4518   let their_score = get(scores, ':'.theirs, 0)
4519   if my_score || their_score
4520     return my_score < their_score ? -1 : my_score != their_score
4521   elseif mine ==# theirs
4522     return 0
4523   endif
4524   let base = s:TreeChomp('merge-base', mine, theirs)
4525   if base ==# mine
4526     return -1
4527   elseif base ==# theirs
4528     return 1
4529   endif
4530   let my_time    = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:mine, '--')
4531   let their_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:theirs, '--')
4532   return my_time < their_time ? -1 : my_time != their_time
4533 endfunction
4535 function! s:IsConflicted() abort
4536   return len(@%) && !empty(s:ChompDefault('', 'ls-files', '--unmerged', '--', expand('%:p')))
4537 endfunction
4539 function! fugitive#Diffsplit(autodir, keepfocus, mods, arg, args) abort
4540   let args = copy(a:args)
4541   let post = ''
4542   if get(args, 0) =~# '^+'
4543     let post = remove(args, 0)[1:-1]
4544   endif
4545   if exists(':DiffGitCached') && empty(args)
4546     return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
4547   endif
4548   let commit = s:DirCommitFile(@%)[1]
4549   if a:mods =~# '\<tab\>'
4550     let mods = substitute(a:mods, '\<tab\>', '', 'g')
4551     let pre = 'tab split'
4552   else
4553     let mods = 'keepalt ' . a:mods
4554     let pre = ''
4555   endif
4556   let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
4557   if (empty(args) || args[0] ==# ':') && a:keepfocus
4558     exe s:DirCheck()
4559     if empty(commit) && s:IsConflicted()
4560       let parents = [s:Relative(':2:'), s:Relative(':3:')]
4561     elseif empty(commit)
4562       let parents = [s:Relative(':0:')]
4563     elseif commit =~# '^\d\=$'
4564       let parents = [s:Relative('HEAD:')]
4565     elseif commit =~# '^\x\x\+$'
4566       let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
4567       call map(parents, 's:Relative(v:val . ":")')
4568     endif
4569   endif
4570   try
4571     if exists('parents') && len(parents) > 1
4572       exe pre
4573       let mods = (a:autodir ? s:diff_modifier(len(parents) + 1) : '') . s:Mods(mods, 'leftabove')
4574       let nr = bufnr('')
4575       execute mods 'split' s:fnameescape(s:Generate(parents[0]))
4576       call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4577       let nr2 = bufnr('')
4578       call s:diffthis()
4579       exe back
4580       call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
4581       let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
4582       for i in range(len(parents)-1, 1, -1)
4583         execute mods 'split' s:fnameescape(s:Generate(parents[i]))
4584         call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4585         let nrx = bufnr('')
4586         call s:diffthis()
4587         exe back
4588         call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
4589       endfor
4590       call s:diffthis()
4591       if len(parents) > 1
4592         wincmd =
4593       endif
4594       return post
4595     elseif len(args)
4596       let arg = join(args, ' ')
4597       if arg ==# ''
4598         return post
4599       elseif arg ==# ':/'
4600         exe s:DirCheck()
4601         let file = s:Relative()
4602       elseif arg ==# ':'
4603         exe s:DirCheck()
4604         let file = s:Relative(':0:')
4605       elseif arg =~# '^:\d$'
4606         exe s:DirCheck()
4607         let file = s:Relative(arg . ':')
4608       else
4609         try
4610           let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
4611         catch /^fugitive:/
4612           return 'echoerr ' . string(v:exception)
4613         endtry
4614       endif
4615     elseif exists('parents') && len(parents)
4616       let file = parents[-1]
4617     elseif len(commit)
4618       let file = s:Relative()
4619     elseif s:IsConflicted()
4620       let file = s:Relative(':1:')
4621       let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
4622     else
4623       exe s:DirCheck()
4624       let file = s:Relative(':0:')
4625     endif
4626     let spec = s:Generate(file)
4627     if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
4628       let spec = FugitiveVimPath(spec . s:Relative('/'))
4629     endif
4630     exe pre
4631     let restore = s:diff_restore()
4632     let w:fugitive_diff_restore = restore
4633     if len(spec) && s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
4634       let mods = s:Mods(mods, 'rightbelow')
4635     else
4636       let mods = s:Mods(mods, 'leftabove')
4637     endif
4638     let mods = (a:autodir ? s:diff_modifier(2) : '') . mods
4639     if &diffopt =~# 'vertical'
4640       let diffopt = &diffopt
4641       set diffopt-=vertical
4642     endif
4643     execute mods 'diffsplit' s:fnameescape(spec)
4644     let &l:readonly = &l:readonly
4645     redraw
4646     let w:fugitive_diff_restore = restore
4647     let winnr = winnr()
4648     if getwinvar('#', '&diff')
4649       if a:keepfocus
4650         exe back
4651       endif
4652     endif
4653     return post
4654   catch /^fugitive:/
4655     return 'echoerr ' . string(v:exception)
4656   finally
4657     if exists('diffopt')
4658       let &diffopt = diffopt
4659     endif
4660   endtry
4661 endfunction
4663 " Section: :Gmove, :Gremove
4665 function! s:Move(force, rename, destination) abort
4666   let dir = s:Dir()
4667   exe s:DirCheck(dir)
4668   if s:DirCommitFile(@%)[1] !~# '^0\=$' || empty(@%)
4669     return 'echoerr ' . string('fugitive: mv not supported for this buffer')
4670   endif
4671   if a:destination =~# '^\.\.\=\%(/\|$\)'
4672     let destination = simplify(getcwd() . '/' . a:destination)
4673   elseif a:destination =~# '^\a\+:\|^/'
4674     let destination = a:destination
4675   elseif a:destination =~# '^:/:\='
4676     let destination = s:Tree(dir) . substitute(a:destination, '^:/:\=', '', '')
4677   elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\))'
4678     let destination = s:Tree(dir) . matchstr(a:destination, ')\zs.*')
4679   elseif a:destination =~# '^:(literal)'
4680     let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
4681   elseif a:rename
4682     let destination = expand('%:p:s?[\/]$??:h') . '/' . a:destination
4683   else
4684     let destination = s:Tree(dir) . '/' . a:destination
4685   endif
4686   let destination = s:Slash(destination)
4687   if isdirectory(@%)
4688     setlocal noswapfile
4689   endif
4690   let [message, exec_error] = s:ChompError(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination], dir)
4691   if exec_error
4692     let v:errmsg = 'fugitive: '.message
4693     return 'echoerr v:errmsg'
4694   endif
4695   if isdirectory(destination)
4696     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
4697   endif
4698   let reload = '|call fugitive#ReloadStatus(' . string(dir) . ', 1)'
4699   if empty(s:DirCommitFile(@%)[1])
4700     if isdirectory(destination)
4701       return 'keepalt edit '.s:fnameescape(destination) . reload
4702     else
4703       return 'keepalt saveas! '.s:fnameescape(destination) . reload
4704     endif
4705   else
4706     return 'file '.s:fnameescape(fugitive#Find(':0:'.destination, dir)) . reload
4707   endif
4708 endfunction
4710 function! fugitive#RenameComplete(A,L,P) abort
4711   if a:A =~# '^[.:]\=/'
4712     return fugitive#CompletePath(a:A)
4713   else
4714     let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
4715     return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
4716   endif
4717 endfunction
4719 function! fugitive#MoveCommand(line1, line2, range, bang, mods, arg, args) abort
4720   return s:Move(a:bang, 0, a:arg)
4721 endfunction
4723 function! fugitive#RenameCommand(line1, line2, range, bang, mods, arg, args) abort
4724   return s:Move(a:bang, 1, a:arg)
4725 endfunction
4727 function! s:Remove(after, force) abort
4728   let dir = s:Dir()
4729   exe s:DirCheck(dir)
4730   if len(@%) && s:DirCommitFile(@%)[1] ==# ''
4731     let cmd = ['rm']
4732   elseif s:DirCommitFile(@%)[1] ==# '0'
4733     let cmd = ['rm','--cached']
4734   else
4735     return 'echoerr ' . string('fugitive: rm not supported for this buffer')
4736   endif
4737   if a:force
4738     let cmd += ['--force']
4739   endif
4740   let [message, exec_error] = s:ChompError(cmd + ['--', expand('%:p')], dir)
4741   if exec_error
4742     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
4743     return 'echoerr '.string(v:errmsg)
4744   else
4745     return a:after . (a:force ? '!' : ''). '|call fugitive#ReloadStatus(' . string(dir) . ', 1)'
4746   endif
4747 endfunction
4749 function! fugitive#RemoveCommand(line1, line2, range, bang, mods, arg, args) abort
4750   return s:Remove('edit', a:bang)
4751 endfunction
4753 function! fugitive#DeleteCommand(line1, line2, range, bang, mods, arg, args) abort
4754   return s:Remove('bdelete', a:bang)
4755 endfunction
4757 " Section: :Gblame
4759 function! s:Keywordprg() abort
4760   let args = ' --git-dir='.escape(s:Dir(),"\\\"' ")
4761   if has('gui_running') && !has('win32')
4762     return s:UserCommand() . ' --no-pager' . args . ' log -1'
4763   else
4764     return s:UserCommand() . args . ' show'
4765   endif
4766 endfunction
4768 function! s:linechars(pattern) abort
4769   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
4770   if exists('*synconcealed') && &conceallevel > 1
4771     for col in range(1, chars)
4772       let chars -= synconcealed(line('.'), col)[0]
4773     endfor
4774   endif
4775   return chars
4776 endfunction
4778 function! s:BlameBufnr(...) abort
4779   let state = s:TempState(bufname(a:0 ? a:1 : ''))
4780   if get(state, 'filetype', '') ==# 'fugitiveblame'
4781     return get(state, 'bufnr', -1)
4782   else
4783     return -1
4784   endif
4785 endfunction
4787 function! s:BlameCommitFileLnum(...) abort
4788   let line = a:0 ? a:1 : getline('.')
4789   let state = a:0 ? a:2 : s:TempState()
4790   let commit = matchstr(line, '^\^\=\zs\x\+')
4791   if commit =~# '^0\+$'
4792     let commit = ''
4793   elseif has_key(state, 'blame_reverse_end')
4794     let commit = get(s:LinesError('rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end)[0], 0, '')
4795   endif
4796   let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
4797   let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s\+\%(\%( \d\+ \)\@<!([^()]*\w \d\+)\|\d\+ \)')
4798   if empty(path) && lnum
4799     let path = get(state, 'blame_file', '')
4800   endif
4801   return [commit, path, lnum]
4802 endfunction
4804 function! s:BlameLeave() abort
4805   let bufwinnr = bufwinnr(s:BlameBufnr())
4806   if bufwinnr > 0
4807     let bufnr = bufnr('')
4808     exe bufwinnr . 'wincmd w'
4809     return bufnr . 'bdelete'
4810   endif
4811   return ''
4812 endfunction
4814 function! s:BlameQuit() abort
4815   let cmd = s:BlameLeave()
4816   if empty(cmd)
4817     return 'bdelete'
4818   elseif len(s:DirCommitFile(@%)[1])
4819     return cmd . '|Gedit'
4820   else
4821     return cmd
4822   endif
4823 endfunction
4825 function! fugitive#BlameComplete(A, L, P) abort
4826   return s:CompleteSub('blame', a:A, a:L, a:P)
4827 endfunction
4829 function! s:BlameSubcommand(line1, count, range, bang, mods, args) abort
4830   exe s:DirCheck()
4831   let flags = copy(a:args)
4832   let i = 0
4833   let raw = 0
4834   let commits = []
4835   let files = []
4836   let ranges = []
4837   if a:line1 > 0 && a:count > 0 && a:range != 1
4838     call extend(ranges, ['-L', a:line1 . ',' . a:count])
4839   endif
4840   while i < len(flags)
4841     let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
4842     if len(match) && len(match[2])
4843       call insert(flags, match[1])
4844       let flags[i+1] = '-' . match[2]
4845       continue
4846     endif
4847     let arg = flags[i]
4848     if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
4849       let raw = 1
4850     elseif arg ==# '--contents' && i + 1 < len(flags)
4851       call extend(commits, remove(flags, i, i+1))
4852       continue
4853     elseif arg ==# '-L' && i + 1 < len(flags)
4854       call extend(ranges, remove(flags, i, i+1))
4855       continue
4856     elseif arg =~# '^--contents='
4857       call add(commits, remove(flags, i))
4858       continue
4859     elseif arg =~# '^-L.'
4860       call add(ranges, remove(flags, i))
4861       continue
4862     elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
4863       let i += 1
4864       if i == len(flags)
4865         echohl ErrorMsg
4866         echo s:ChompError(['blame', arg])[0]
4867         echohl NONE
4868         return ''
4869       endif
4870     elseif arg ==# '--'
4871       if i + 1 < len(flags)
4872         call extend(files, remove(flags, i + 1, -1))
4873       endif
4874       call remove(flags, i)
4875       break
4876     elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
4877       if index(flags, '--') >= 0
4878         call add(commits, remove(flags, i))
4879         continue
4880       endif
4881       if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
4882         call add(commits, remove(flags, i))
4883         continue
4884       endif
4885       try
4886         let dcf = s:DirCommitFile(fugitive#Find(arg))
4887         if len(dcf[1]) && empty(dcf[2])
4888           call add(commits, remove(flags, i))
4889           continue
4890         endif
4891       catch /^fugitive:/
4892       endtry
4893       call add(files, remove(flags, i))
4894       continue
4895     endif
4896     let i += 1
4897   endwhile
4898   let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./'))), '^\.\%(/\|$\)', '', '')
4899   if empty(commits) && len(files) > 1
4900     call add(commits, remove(files, 1))
4901   endif
4902   exe s:BlameLeave()
4903   try
4904     let cmd = ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', 'blame', '--show-number']
4905     call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
4906     if a:count > 0 && empty(ranges)
4907       let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
4908     endif
4909     call extend(cmd, ranges)
4910     if len(commits)
4911       let cmd += commits
4912     elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
4913       let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
4914     elseif empty(files) && !s:HasOpt(flags, '--reverse')
4915       let cmd += ['--contents', '-']
4916     endif
4917     let basecmd = escape(fugitive#Prepare(cmd) . ' -- ' . s:shellesc(len(files) ? files : file), '!#%')
4918     let tempname = tempname()
4919     let error = tempname . '.err'
4920     let temp = tempname . (raw ? '' : '.fugitiveblame')
4921     if &shell =~# 'csh'
4922       silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
4923     else
4924       silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
4925     endif
4926     let l:shell_error = v:shell_error
4927     redraw
4928     try
4929       if l:shell_error
4930         let lines = readfile(error)
4931         if empty(lines)
4932           let lines = readfile(temp)
4933         endif
4934         for i in range(len(lines))
4935           if lines[i] =~# '^error: \|^fatal: '
4936             echohl ErrorMsg
4937             echon lines[i]
4938             echohl NONE
4939             break
4940           else
4941             echon lines[i]
4942           endif
4943           if i != len(lines) - 1
4944             echon "\n"
4945           endif
4946         endfor
4947         return ''
4948       endif
4949       let temp_state = {'dir': s:Dir(), 'filetype': (raw ? '' : 'fugitiveblame'), 'blame_flags': flags, 'blame_file': file, 'modifiable': 0}
4950       if s:HasOpt(flags, '--reverse')
4951         let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
4952       endif
4953       if (a:line1 == 0 || a:range == 1) && a:count > 0
4954         let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit'], a:count - (a:line1 ? a:line1 : 1), 'split')
4955         return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
4956       else
4957         let temp = s:Resolve(temp)
4958         let s:temp_files[s:cpath(temp)] = temp_state
4959         if len(ranges + commits + files) || raw
4960           let mods = s:Mods(a:mods)
4961           if a:count != 0
4962             exe 'silent keepalt' mods 'split' s:fnameescape(temp)
4963           elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
4964             exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
4965           else
4966             return mods . 'edit ' . s:fnameescape(temp)
4967           endif
4968           return ''
4969         endif
4970         if a:mods =~# '\<tab\>'
4971           silent tabedit %
4972         endif
4973         let mods = substitute(a:mods, '\<tab\>', '', 'g')
4974         for winnr in range(winnr('$'),1,-1)
4975           if getwinvar(winnr, '&scrollbind')
4976             call setwinvar(winnr, '&scrollbind', 0)
4977           endif
4978           if exists('+cursorbind') && getwinvar(winnr, '&cursorbind')
4979             call setwinvar(winnr, '&cursorbind', 0)
4980           endif
4981           if s:BlameBufnr(winbufnr(winnr)) > 0
4982             execute winbufnr(winnr).'bdelete'
4983           endif
4984         endfor
4985         let bufnr = bufnr('')
4986         let temp_state.bufnr = bufnr
4987         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
4988         if exists('+cursorbind')
4989           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
4990         endif
4991         if &l:wrap
4992           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
4993         endif
4994         if &l:foldenable
4995           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
4996         endif
4997         setlocal scrollbind nowrap nofoldenable
4998         if exists('+cursorbind')
4999           setlocal cursorbind
5000         endif
5001         let top = line('w0') + &scrolloff
5002         let current = line('.')
5003         exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
5004         let w:fugitive_leave = restore
5005         execute top
5006         normal! zt
5007         execute current
5008         if exists('+cursorbind')
5009           setlocal cursorbind
5010         endif
5011         setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
5012         if exists('+relativenumber')
5013           setlocal norelativenumber
5014         endif
5015         execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
5016         call s:Map('n', 'A', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>')
5017         call s:Map('n', 'C', ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>')
5018         call s:Map('n', 'D', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>')
5019         redraw
5020         syncbind
5021       endif
5022     endtry
5023     return ''
5024   catch /^fugitive:/
5025     return 'echoerr ' . string(v:exception)
5026   endtry
5027 endfunction
5029 function! s:BlameCommit(cmd, ...) abort
5030   let line = a:0 ? a:1 : getline('.')
5031   let state = a:0 ? a:2 : s:TempState()
5032   let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
5033   let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
5034   let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
5035   if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
5036     let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
5037     return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
5038   endif
5039   if commit =~# '^0*$'
5040     return 'echoerr ' . string('fugitive: no commit')
5041   endif
5042   if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
5043     let path = commit . ':' . path
5044     return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
5045   endif
5046   let cmd = fugitive#Open(mods . a:cmd, 0, '', commit, [commit])
5047   if cmd =~# '^echoerr'
5048     return cmd
5049   endif
5050   execute cmd
5051   if a:cmd ==# 'pedit' || empty(path)
5052     return ''
5053   endif
5054   if search('^diff .* b/\M'.escape(path,'\').'$','W')
5055     call search('^+++')
5056     let head = line('.')
5057     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
5058       let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
5059       let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
5060       if lnum >= top && lnum <= top + len
5061         let offset = lnum - top
5062         if &scrolloff
5063           +
5064           normal! zt
5065         else
5066           normal! zt
5067           +
5068         endif
5069         while offset > 0 && line('.') < line('$')
5070           +
5071           if getline('.') =~# '^[ ' . sigil . ']'
5072             let offset -= 1
5073           endif
5074         endwhile
5075         return 'normal! zv'
5076       endif
5077     endwhile
5078     execute head
5079     normal! zt
5080   endif
5081   return ''
5082 endfunction
5084 function! s:BlameJump(suffix, ...) abort
5085   let suffix = a:suffix
5086   let [commit, path, lnum] = s:BlameCommitFileLnum()
5087   if empty(path)
5088     return 'echoerr ' . string('fugitive: could not determine filename for blame')
5089   endif
5090   if commit =~# '^0*$'
5091     let commit = 'HEAD'
5092     let suffix = ''
5093   endif
5094   let offset = line('.') - line('w0')
5095   let flags = get(s:TempState(), 'blame_flags', [])
5096   if a:0 && a:1
5097     if s:HasOpt(flags, '--reverse')
5098       call remove(flags, '--reverse')
5099     else
5100       call add(flags, '--reverse')
5101     endif
5102   endif
5103   let blame_bufnr = s:BlameBufnr()
5104   if blame_bufnr > 0
5105     let bufnr = bufnr('')
5106     let winnr = bufwinnr(blame_bufnr)
5107     if winnr > 0
5108       exe winnr.'wincmd w'
5109     endif
5110     execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
5111     execute lnum
5112     if winnr > 0
5113       exe bufnr.'bdelete'
5114     endif
5115   endif
5116   if exists(':Gblame')
5117     let my_bufnr = bufnr('')
5118     if blame_bufnr < 0
5119       let blame_args = flags + [commit . suffix, '--', path]
5120       let result = s:BlameSubcommand(0, 0, 0, 0, '', blame_args)
5121     else
5122       let blame_args = flags
5123       let result = s:BlameSubcommand(-1, -1, 0, 0, '', blame_args)
5124     endif
5125     if bufnr('') == my_bufnr
5126       return result
5127     endif
5128     execute result
5129     execute lnum
5130     let delta = line('.') - line('w0') - offset
5131     if delta > 0
5132       execute 'normal! '.delta."\<C-E>"
5133     elseif delta < 0
5134       execute 'normal! '.(-delta)."\<C-Y>"
5135     endif
5136     keepjumps syncbind
5137     redraw
5138     echo ':Gblame' s:fnameescape(blame_args)
5139   endif
5140   return ''
5141 endfunction
5143 let s:hash_colors = {}
5145 function! fugitive#BlameSyntax() abort
5146   let conceal = has('conceal') ? ' conceal' : ''
5147   let config = fugitive#Config()
5148   let flags = get(s:TempState(), 'blame_flags', [])
5149   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
5150   syn match FugitiveblameHash       "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5151   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5152   if get(get(config, 'blame.blankboundary', ['x']), 0, 'x') =~# '^$\|^true$' || s:HasOpt(flags, '-b')
5153     syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5154   else
5155     syn match FugitiveblameBoundary "^\^"
5156   endif
5157   syn match FugitiveblameScoreDebug        " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
5158   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
5159   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
5160   exec 'syn match FugitiveblameLineNumber         "\s*\d\+)\@=" contained containedin=FugitiveblameAnnotation' conceal
5161   exec 'syn match FugitiveblameOriginalFile       "\s\%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-name', '-f') ? '' : conceal)
5162   exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5163   exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5164   syn match FugitiveblameShort              " \d\+)" contained contains=FugitiveblameLineNumber
5165   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
5166   hi def link FugitiveblameBoundary           Keyword
5167   hi def link FugitiveblameHash               Identifier
5168   hi def link FugitiveblameBoundaryIgnore     Ignore
5169   hi def link FugitiveblameUncommitted        Ignore
5170   hi def link FugitiveblameScoreDebug         Debug
5171   hi def link FugitiveblameTime               PreProc
5172   hi def link FugitiveblameLineNumber         Number
5173   hi def link FugitiveblameOriginalFile       String
5174   hi def link FugitiveblameOriginalLineNumber Float
5175   hi def link FugitiveblameShort              FugitiveblameDelimiter
5176   hi def link FugitiveblameDelimiter          Delimiter
5177   hi def link FugitiveblameNotCommittedYet    Comment
5178   if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
5179     return
5180   endif
5181   let seen = {}
5182   for lnum in range(1, line('$'))
5183     let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
5184     if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
5185       continue
5186     endif
5187     let seen[hash] = 1
5188     if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
5189           \ && empty(get(s:hash_colors, hash))
5190       let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
5191       let color = csapprox#per_component#Approximate(r, g, b)
5192       if color == 16 && &background ==# 'dark'
5193         let color = 8
5194       endif
5195       let s:hash_colors[hash] = ' ctermfg='.color
5196     else
5197       let s:hash_colors[hash] = ''
5198     endif
5199     exe 'syn match FugitiveblameHash'.hash.'       "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
5200   endfor
5201   call s:BlameRehighlight()
5202 endfunction
5204 function! s:BlameRehighlight() abort
5205   for [hash, cterm] in items(s:hash_colors)
5206     if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
5207       exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
5208     else
5209       exe 'hi link FugitiveblameHash'.hash.' Identifier'
5210     endif
5211   endfor
5212 endfunction
5214 function! s:BlameFileType() abort
5215   setlocal nomodeline
5216   setlocal foldmethod=manual
5217   if len(s:Dir())
5218     let &l:keywordprg = s:Keywordprg()
5219   endif
5220   let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
5221   if exists('+concealcursor')
5222     setlocal concealcursor=nc conceallevel=2
5223     let b:undo_ftplugin .= ' concealcursor< conceallevel<'
5224   endif
5225   if &modifiable
5226     return ''
5227   endif
5228   call s:Map('n', '<F1>', ':help :Gblame<CR>', '<silent>')
5229   call s:Map('n', 'g?',   ':help :Gblame<CR>', '<silent>')
5230   if mapcheck('q', 'n') =~# '^$\|bdelete'
5231     call s:Map('n', 'q',  ':exe <SID>BlameQuit()<Bar>echohl WarningMsg<Bar>echo ":Gblame q is deprecated in favor of gq"<Bar>echohl NONE<CR>', '<silent>')
5232   endif
5233   call s:Map('n', 'gq',   ':exe <SID>BlameQuit()<CR>', '<silent>')
5234   call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5235   call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5236   call s:Map('n', '-',    ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>')
5237   call s:Map('n', 'P',    ':<C-U>exe <SID>BlameJump("^".v:count1)<CR>', '<silent>')
5238   call s:Map('n', '~',    ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>')
5239   call s:Map('n', 'i',    ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5240   call s:Map('n', 'o',    ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>')
5241   call s:Map('n', 'O',    ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>')
5242   call s:Map('n', 'p',    ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>')
5243 endfunction
5245 augroup fugitive_blame
5246   autocmd!
5247   autocmd FileType fugitiveblame call s:BlameFileType()
5248   autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
5249   autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
5250 augroup END
5252 " Section: :Gbrowse
5254 let s:redirects = {}
5256 function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, args) abort
5257   let dir = s:Dir()
5258   exe s:DirCheck(dir)
5259   try
5260     let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
5261     if a:args ==# ['-']
5262       if a:count >= 0
5263         return 'echoerr ' . string('fugitive: ''-'' no longer required to get persistent URL if range given')
5264       else
5265         return 'echoerr ' . string('fugitive: use :0Gbrowse instead of :Gbrowse -')
5266       endif
5267     elseif len(a:args)
5268       let remote = matchstr(join(a:args, ' '),'@\zs\%('.validremote.'\)$')
5269       let rev = substitute(join(a:args, ' '),'@\%('.validremote.'\)$','','')
5270     else
5271       let remote = ''
5272       let rev = ''
5273     endif
5274     if rev ==# ''
5275       let rev = s:DirRev(@%)[1]
5276     endif
5277     if rev =~# '^:\=$'
5278       let expanded = s:Relative()
5279     else
5280       let expanded = s:Expand(rev)
5281     endif
5282     let cdir = FugitiveVimPath(fugitive#CommonDir(dir))
5283     for subdir in ['tags/', 'heads/', 'remotes/']
5284       if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . subdir . expanded)
5285         let expanded = '.git/refs/' . subdir . expanded
5286       endif
5287     endfor
5288     let full = fugitive#Find(expanded, dir)
5289     let commit = ''
5290     if full =~? '^fugitive:'
5291       let [pathdir, commit, path] = s:DirCommitFile(full)
5292       if commit =~# '^:\=\d$'
5293         let commit = ''
5294       endif
5295       if commit =~ '..'
5296         let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
5297         let branch = matchstr(expanded, '^[^:]*')
5298       else
5299         let type = 'blob'
5300       endif
5301       let path = path[1:-1]
5302     elseif empty(s:Tree(dir))
5303       let path = '.git/' . full[strlen(dir)+1:-1]
5304       let type = ''
5305     else
5306       let path = fugitive#Path(full, '/')[1:-1]
5307       if path =~# '^\.git/'
5308         let type = ''
5309       elseif isdirectory(full) || empty(path)
5310         let type = 'tree'
5311       else
5312         let type = 'blob'
5313       endif
5314     endif
5315     if type ==# 'tree' && !empty(path)
5316       let path = s:sub(path, '/\=$', '/')
5317     endif
5318     if path =~# '^\.git/.*HEAD$' && filereadable(dir . '/' . path[5:-1])
5319       let body = readfile(dir . '/' . path[5:-1])[0]
5320       if body =~# '^\x\{40,\}$'
5321         let commit = body
5322         let type = 'commit'
5323         let path = ''
5324       elseif body =~# '^ref: refs/'
5325         let path = '.git/' . matchstr(body,'ref: \zs.*')
5326       endif
5327     endif
5329     let merge = ''
5330     if path =~# '^\.git/refs/remotes/.'
5331       if empty(remote)
5332         let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
5333         let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5334       else
5335         let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5336         let path = '.git/refs/heads/'.merge
5337       endif
5338     elseif path =~# '^\.git/refs/heads/.'
5339       let branch = path[16:-1]
5340     elseif !exists('branch')
5341       let branch = FugitiveHead()
5342     endif
5343     if !empty(branch)
5344       let r = fugitive#Config('branch.'.branch.'.remote')
5345       let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
5346       if r ==# '.' && !empty(m)
5347         let r2 = fugitive#Config('branch.'.m.'.remote')
5348         if r2 !~# '^\.\=$'
5349           let r = r2
5350           let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
5351         endif
5352       endif
5353       if empty(remote)
5354         let remote = r
5355       endif
5356       if r ==# '.' || r ==# remote
5357         let merge = m
5358         if path =~# '^\.git/refs/heads/.'
5359           let path = '.git/refs/heads/'.merge
5360         endif
5361       endif
5362     endif
5364     let line1 = a:count > 0 ? a:line1 : 0
5365     let line2 = a:count > 0 ? a:count : 0
5366     if empty(commit) && path !~# '^\.git/'
5367       if a:count < 0 && !empty(merge)
5368         let commit = merge
5369       else
5370         let commit = ''
5371         if len(merge)
5372           let owner = s:Owner(@%)
5373           let [commit, exec_error] = s:ChompError(['merge-base', 'refs/remotes/' . remote . '/' . merge, empty(owner) ? 'HEAD' : owner, '--'])
5374           if exec_error
5375             let commit = ''
5376           endif
5377           if a:count > 0 && empty(a:args) && commit =~# '^\x\{40,\}$'
5378             let blame_list = tempname()
5379             call writefile([commit, ''], blame_list, 'b')
5380             let blame_in = tempname()
5381             silent exe '%write' blame_in
5382             let [blame, exec_error] = s:LinesError(['-c', 'blame.coloring=none', 'blame', '--contents', blame_in, '-L', a:line1.','.a:count, '-S', blame_list, '-s', '--show-number', './' . path])
5383             if !exec_error
5384               let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
5385               if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
5386                 let line1 = +matchstr(blame[0], blame_regex)
5387                 let line2 = +matchstr(blame[-1], blame_regex)
5388               else
5389                 call s:throw("Can't browse to uncommitted change")
5390               endif
5391             endif
5392           endif
5393         endif
5394       endif
5395       if empty(commit)
5396         let commit = readfile(fugitive#Find('.git/HEAD', dir), '', 1)[0]
5397       endif
5398       let i = 0
5399       while commit =~# '^ref: ' && i < 10
5400         let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
5401         let i -= 1
5402       endwhile
5403     endif
5405     if empty(remote)
5406       let remote = '.'
5407     endif
5408     let raw = fugitive#RemoteUrl(remote)
5409     if empty(raw)
5410       let raw = remote
5411     endif
5413     if raw =~# '^https\=://' && s:executable('curl')
5414       if !has_key(s:redirects, raw)
5415         let s:redirects[raw] = matchstr(system('curl -I ' .
5416               \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
5417               \ 'Location: \zs\S\+\ze/info/refs?')
5418       endif
5419       if len(s:redirects[raw])
5420         let raw = s:redirects[raw]
5421       endif
5422     endif
5424     let opts = {
5425           \ 'dir': dir,
5426           \ 'repo': fugitive#repo(dir),
5427           \ 'remote': raw,
5428           \ 'revision': 'No longer provided',
5429           \ 'commit': commit,
5430           \ 'path': path,
5431           \ 'type': type,
5432           \ 'line1': line1,
5433           \ 'line2': line2}
5435     let url = ''
5436     for Handler in get(g:, 'fugitive_browse_handlers', [])
5437       let url = call(Handler, [copy(opts)])
5438       if !empty(url)
5439         break
5440       endif
5441     endfor
5443     if empty(url)
5444       call s:throw("No Gbrowse handler installed for '".raw."'")
5445     endif
5447     let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
5448     if a:bang
5449       if has('clipboard')
5450         let @+ = url
5451       endif
5452       return 'echomsg '.string(url)
5453     elseif exists(':Browse') == 2
5454       return 'echomsg '.string(url).'|Browse '.url
5455     else
5456       if !exists('g:loaded_netrw')
5457         runtime! autoload/netrw.vim
5458       endif
5459       if exists('*netrw#BrowseX')
5460         return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
5461       else
5462         return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
5463       endif
5464     endif
5465   catch /^fugitive:/
5466     return 'echoerr ' . string(v:exception)
5467   endtry
5468 endfunction
5470 " Section: Go to file
5472 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
5473 function! fugitive#MapCfile(...) abort
5474   exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
5475   let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
5476   if !exists('g:fugitive_no_maps')
5477     call s:Map('n', 'gf',          '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
5478     call s:Map('n', '<C-W>f',     '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5479     call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5480     call s:Map('n', '<C-W>gf',  '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
5481     call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
5482   endif
5483 endfunction
5485 function! s:ContainingCommit() abort
5486   let commit = s:Owner(@%)
5487   return empty(commit) ? 'HEAD' : commit
5488 endfunction
5490 function! s:SquashArgument(...) abort
5491   if &filetype == 'fugitive'
5492     let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze ')
5493   elseif has_key(s:temp_files, s:cpath(expand('%:p')))
5494     let commit = matchstr(getline('.'), '\<\x\{4,\}\>')
5495   else
5496     let commit = s:Owner(@%)
5497   endif
5498   return len(commit) && a:0 ? printf(a:1, commit) : commit
5499 endfunction
5501 function! s:RebaseArgument() abort
5502   return s:SquashArgument(' %s^')
5503 endfunction
5505 function! s:NavigateUp(count) abort
5506   let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
5507   let c = a:count
5508   while c
5509     if rev =~# ':.*/.'
5510       let rev = matchstr(rev, '.*\ze/.\+', '')
5511     elseif rev =~# '.:.'
5512       let rev = matchstr(rev, '^.[^:]*:')
5513     elseif rev =~# '^:'
5514       let rev = 'HEAD^{}'
5515     elseif rev =~# ':$'
5516       let rev = rev[0:-2]
5517     else
5518       return rev.'~'.c
5519     endif
5520     let c -= 1
5521   endwhile
5522   return rev
5523 endfunction
5525 function! s:MapMotion(lhs, rhs) abort
5526   call s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5527   call s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5528   call s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")
5529 endfunction
5531 function! fugitive#MapJumps(...) abort
5532   if !&modifiable
5533     if get(b:, 'fugitive_type', '') ==# 'blob'
5534       let blame_map = 'Gblame<C-R>=v:count ? " --reverse" : ""<CR><CR>'
5535       call s:Map('n', '<2-LeftMouse>', ':<C-U>0,1' . blame_map, '<silent>')
5536       call s:Map('n', '<CR>', ':<C-U>0,1' . blame_map, '<silent>')
5537       call s:Map('n', 'o',    ':<C-U>0,2' . blame_map, '<silent>')
5538       call s:Map('n', 'p',    ':<C-U>0,3' . blame_map, '<silent>')
5539       call s:Map('n', 'gO',   ':<C-U>0,4' . blame_map, '<silent>')
5540       call s:Map('n', 'O',    ':<C-U>0,5' . blame_map, '<silent>')
5542       call s:Map('n', 'D',  ":<C-U>call <SID>DiffClose()<Bar>Gdiffsplit!<Bar>redraw<Bar>echohl WarningMsg<Bar> echo ':Gstatus D is deprecated in favor of dd'<Bar>echohl NONE<CR>", '<silent>')
5543       call s:Map('n', 'dd', ":<C-U>call <SID>DiffClose()<Bar>Gdiffsplit!<CR>", '<silent>')
5544       call s:Map('n', 'dh', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5545       call s:Map('n', 'ds', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5546       call s:Map('n', 'dv', ":<C-U>call <SID>DiffClose()<Bar>Gvdiffsplit!<CR>", '<silent>')
5547       call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
5549     else
5550       call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5551       call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5552       call s:Map('n', 'o',    ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
5553       call s:Map('n', 'gO',   ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
5554       call s:Map('n', 'O',    ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
5555       call s:Map('n', 'p',    ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
5557       if !exists('g:fugitive_no_maps')
5558         if exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
5559           nnoremap <buffer> <silent> <C-P> :<C-U>execute line('.') == 1 ? 'CtrlP ' . fnameescape(<SID>Tree()) : <SID>PreviousItem(v:count1)<CR>
5560         else
5561           nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>PreviousItem(v:count1)<CR>
5562         endif
5563         nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>NextItem(v:count1)<CR>
5564       endif
5565       call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
5566       call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
5567       call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
5568       call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
5569       call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
5570       call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
5571       call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
5572       call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
5573       call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
5574       call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
5575       call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
5576       call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
5577       call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
5578       call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
5579       call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
5580       call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
5581     endif
5582     call s:Map('n', 'S',    ':<C-U>echoerr "Use gO"<CR>', '<silent>')
5583     call s:Map('n', 'dq', ":<C-U>call <SID>DiffClose()<CR>", '<silent>')
5584     call s:Map('n', '-', ":<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>", '<silent>')
5585     call s:Map('n', 'P',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5586     call s:Map('n', '~',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5587     call s:Map('n', 'C',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5588     call s:Map('n', 'cp',    ":<C-U>echoerr 'Use gC'<CR>", '<silent>')
5589     call s:Map('n', 'gC',    ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5590     call s:Map('n', 'gc',    ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5591     call s:Map('n', 'gi',    ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5592     call s:Map('x', 'gi',    ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5594     nnoremap <buffer>       c<Space> :Git commit<Space>
5595     nnoremap <buffer>          c<CR> :Git commit<CR>
5596     nnoremap <buffer>      cv<Space> :Git commit -v<Space>
5597     nnoremap <buffer>         cv<CR> :Git commit -v<CR>
5598     nnoremap <buffer> <silent> ca    :<C-U>Gcommit --amend<CR>
5599     nnoremap <buffer> <silent> cc    :<C-U>Gcommit<CR>
5600     nnoremap <buffer> <silent> ce    :<C-U>Gcommit --amend --no-edit<CR>
5601     nnoremap <buffer> <silent> cw    :<C-U>Gcommit --amend --only<CR>
5602     nnoremap <buffer> <silent> cva   :<C-U>Gcommit -v --amend<CR>
5603     nnoremap <buffer> <silent> cvc   :<C-U>Gcommit -v<CR>
5604     nnoremap <buffer> <silent> cRa   :<C-U>Gcommit --reset-author --amend<CR>
5605     nnoremap <buffer> <silent> cRe   :<C-U>Gcommit --reset-author --amend --no-edit<CR>
5606     nnoremap <buffer> <silent> cRw   :<C-U>Gcommit --reset-author --amend --only<CR>
5607     nnoremap <buffer>          cf    :<C-U>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5608     nnoremap <buffer>          cF    :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5609     nnoremap <buffer>          cs    :<C-U>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5610     nnoremap <buffer>          cS    :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5611     nnoremap <buffer>          cA    :<C-U>Gcommit --edit --squash=<C-R>=<SID>SquashArgument()<CR>
5612     nnoremap <buffer> <silent> c?    :<C-U>help fugitive_c<CR>
5614     nnoremap <buffer>      cr<Space> :Git revert<Space>
5615     nnoremap <buffer>         cr<CR> :Git revert<CR>
5616     nnoremap <buffer> <silent> crc   :<C-U>Grevert <C-R>=<SID>SquashArgument()<CR><CR>
5617     nnoremap <buffer> <silent> crn   :<C-U>Grevert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>
5618     nnoremap <buffer> <silent> cr?   :help fugitive_cr<CR>
5620     nnoremap <buffer>      cm<Space> :Git merge<Space>
5621     nnoremap <buffer>         cm<CR> :Git merge<CR>
5622     nnoremap <buffer> <silent> cm?   :help fugitive_cm<CR>
5624     nnoremap <buffer>      cz<Space> :Git stash<Space>
5625     nnoremap <buffer>         cz<CR> :Git stash<CR>
5626     nnoremap <buffer> <silent> cza   :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5627     nnoremap <buffer> <silent> czA   :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', 'stash@{' . v:count . '}'])<CR>
5628     nnoremap <buffer> <silent> czp   :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5629     nnoremap <buffer> <silent> czP   :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', 'stash@{' . v:count . '}'])<CR>
5630     nnoremap <buffer> <silent> czv   :<C-U>exe 'Gedit' fugitive#RevParse('stash@{' . v:count . '}')<CR>
5631     nnoremap <buffer> <silent> czw   :<C-U>exe <SID>EchoExec(['stash', '--keep-index'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5632     nnoremap <buffer> <silent> czz   :<C-U>exe <SID>EchoExec(['stash'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5633     nnoremap <buffer> <silent> cz?   :<C-U>help fugitive_cz<CR>
5635     nnoremap <buffer>      co<Space> :Git checkout<Space>
5636     nnoremap <buffer>         co<CR> :Git checkout<CR>
5637     nnoremap <buffer>          coo   :exe <SID>EchoExec(['checkout'] + split(<SID>SquashArgument()) + ['--'])<CR>
5638     nnoremap <buffer>          co?   :<C-U>help fugitive_co<CR>
5640     nnoremap <buffer>      cb<Space> :Git branch<Space>
5641     nnoremap <buffer>         cb<CR> :Git branch<CR>
5642     nnoremap <buffer>         cb?    :<C-U>help fugitive_cb<CR>
5644     nnoremap <buffer>       r<Space> :Git rebase<Space>
5645     nnoremap <buffer>          r<CR> :Git rebase<CR>
5646     nnoremap <buffer> <silent> ri    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>
5647     nnoremap <buffer> <silent> rf    :<C-U>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>
5648     nnoremap <buffer> <silent> ru    :<C-U>Grebase --interactive @{upstream}<CR>
5649     nnoremap <buffer> <silent> rp    :<C-U>Grebase --interactive @{push}<CR>
5650     nnoremap <buffer> <silent> rw    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>
5651     nnoremap <buffer> <silent> rm    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>
5652     nnoremap <buffer> <silent> rd    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5653     nnoremap <buffer> <silent> rk    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5654     nnoremap <buffer> <silent> rx    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5655     nnoremap <buffer> <silent> rr    :<C-U>Grebase --continue<CR>
5656     nnoremap <buffer> <silent> rs    :<C-U>Grebase --skip<CR>
5657     nnoremap <buffer> <silent> re    :<C-U>Grebase --edit-todo<CR>
5658     nnoremap <buffer> <silent> ra    :<C-U>Grebase --abort<CR>
5659     nnoremap <buffer> <silent> r?    :<C-U>help fugitive_r<CR>
5661     call s:Map('n', '.',     ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5662     call s:Map('x', '.',     ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5663     call s:Map('n', 'g?',    ":<C-U>help fugitive-map<CR>", '<silent>')
5664     call s:Map('n', '<F1>',  ":<C-U>help fugitive-map<CR>", '<silent>')
5665   endif
5666 endfunction
5668 function! s:StatusCfile(...) abort
5669   let tree = s:Tree()
5670   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5671   let info = s:StageInfo()
5672   let line = getline('.')
5673   if len(info.sigil) && len(info.section) && len(info.paths)
5674     if info.section ==# 'Unstaged' && info.sigil !=# '-'
5675       return [lead . info.relative[0], info.offset, 'normal!zv']
5676     elseif info.section ==# 'Staged' && info.sigil ==# '-'
5677       return ['@:' . info.relative[0], info.offset, 'normal!zv']
5678     else
5679       return [':0:' . info.relative[0], info.offset, 'normal!zv']
5680     endif
5681   elseif len(info.paths)
5682     return [lead . info.relative[0]]
5683   elseif len(info.commit)
5684     return [info.commit]
5685   elseif line =~# '^\%(Head\|Merge\|Rebase\|Upstream\|Pull\|Push\): '
5686     return [matchstr(line, ' \zs.*')]
5687   else
5688     return ['']
5689   endif
5690 endfunction
5692 function! fugitive#StatusCfile() abort
5693   let file = s:Generate(s:StatusCfile()[0])
5694   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5695 endfunction
5697 function! s:MessageCfile(...) abort
5698   let tree = s:Tree()
5699   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5700   if getline('.') =~# '^.\=\trenamed:.* -> '
5701     return lead . matchstr(getline('.'),' -> \zs.*')
5702   elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
5703     return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
5704   elseif getline('.') =~# '^.\=\t.'
5705     return lead . matchstr(getline('.'),'\t\zs.*')
5706   elseif getline('.') =~# ': needs merge$'
5707     return lead . matchstr(getline('.'),'.*\ze: needs merge$')
5708   elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
5709     return 'HEAD'
5710   elseif getline('.') =~# '^\%(. \)\=On branch '
5711     return 'refs/heads/'.getline('.')[12:]
5712   elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
5713     return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
5714   else
5715     return ''
5716   endif
5717 endfunction
5719 function! fugitive#MessageCfile() abort
5720   let file = s:Generate(s:MessageCfile())
5721   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5722 endfunction
5724 function! s:cfile() abort
5725   try
5726     let myhash = s:DirRev(@%)[1]
5727     if len(myhash)
5728       try
5729         let myhash = fugitive#RevParse(myhash)
5730       catch /^fugitive:/
5731         let myhash = ''
5732       endtry
5733     endif
5734     if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
5735       let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
5736     endif
5738     let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
5740     let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
5741           \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
5743     if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
5744       return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
5745     elseif showtree
5746       return [treebase . s:sub(getline('.'),'/$','')]
5748     else
5750       let dcmds = []
5752       " Index
5753       if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
5754         let ref = matchstr(getline('.'),'\x\{40,\}')
5755         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
5756         return [file]
5757       endif
5759       if getline('.') =~# '^ref: '
5760         let ref = strpart(getline('.'),5)
5762       elseif getline('.') =~# '^commit \x\{40,\}\>'
5763         let ref = matchstr(getline('.'),'\x\{40,\}')
5764         return [ref]
5766       elseif getline('.') =~# '^parent \x\{40,\}\>'
5767         let ref = matchstr(getline('.'),'\x\{40,\}')
5768         let line = line('.')
5769         let parent = 0
5770         while getline(line) =~# '^parent '
5771           let parent += 1
5772           let line -= 1
5773         endwhile
5774         return [ref]
5776       elseif getline('.') =~# '^tree \x\{40,\}$'
5777         let ref = matchstr(getline('.'),'\x\{40,\}')
5778         if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
5779           let ref = myhash.':'
5780         endif
5781         return [ref]
5783       elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
5784         let ref = matchstr(getline('.'),'\x\{40,\}')
5785         let type = matchstr(getline(line('.')+1),'type \zs.*')
5787       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
5788         let ref = s:DirRev(@%)[1]
5790       elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
5791         let ref = matchstr(getline('.'),'\x\{40,\}')
5792         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
5794       elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
5795         let ref = getline('.')[4:]
5797       elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
5798         let type = getline('.')[0]
5799         let lnum = line('.') - 1
5800         let offset = 0
5801         while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5802           if getline(lnum) =~# '^[ '.type.']'
5803             let offset += 1
5804           endif
5805           let lnum -= 1
5806         endwhile
5807         let offset += matchstr(getline(lnum), type.'\zs\d\+')
5808         let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
5809         let dcmds = [offset, 'normal!zv']
5811       elseif getline('.') =~# '^rename from '
5812         let ref = 'a/'.getline('.')[12:]
5813       elseif getline('.') =~# '^rename to '
5814         let ref = 'b/'.getline('.')[10:]
5816       elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5817         let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
5818         let offset = matchstr(getline('.'), '+\zs\d\+')
5820         let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5821         let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5822         let dcmd = 'Gdiffsplit! +'.offset
5824       elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5825         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5826         let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5827         let dcmd = 'Gdiffsplit!'
5829       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5830         let line = getline(line('.')-1)
5831         let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5832         let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5833         let dcmd = 'Gdiffsplit!'
5835       elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
5836         let ref = getline('.')
5838       elseif expand('<cword>') =~# '^\x\{7,\}\>'
5839         return [expand('<cword>')]
5841       else
5842         let ref = ''
5843       endif
5845       let prefixes = {
5846             \ '1': '',
5847             \ '2': '',
5848             \ 'b': ':0:',
5849             \ 'i': ':0:',
5850             \ 'o': '',
5851             \ 'w': ''}
5853       if len(myhash)
5854         let prefixes.a = myhash.'^:'
5855         let prefixes.b = myhash.':'
5856       endif
5857       let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5858       if exists('dref')
5859         let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5860       endif
5862       if ref ==# '/dev/null'
5863         " Empty blob
5864         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
5865       endif
5867       if exists('dref')
5868         return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
5869       elseif ref != ""
5870         return [ref] + dcmds
5871       endif
5873     endif
5874     return []
5875   endtry
5876 endfunction
5878 function! s:GF(mode) abort
5879   try
5880     let results = &filetype ==# 'fugitive' ? s:StatusCfile() : &filetype ==# 'gitcommit' ? [s:MessageCfile()] : s:cfile()
5881   catch /^fugitive:/
5882     return 'echoerr ' . string(v:exception)
5883   endtry
5884   if len(results) > 1
5885     return 'G' . a:mode .
5886           \ ' +' . escape(results[1], ' ') . ' ' .
5887           \ s:fnameescape(results[0]) . join(map(results[2:-1], '"|" . v:val'), '')
5888   elseif len(results) && len(results[0])
5889     return 'G' . a:mode . ' ' . s:fnameescape(results[0])
5890   else
5891     return ''
5892   endif
5893 endfunction
5895 function! fugitive#Cfile() abort
5896   let pre = ''
5897   let results = s:cfile()
5898   if empty(results)
5899     let cfile = expand('<cfile>')
5900     if &includeexpr =~# '\<v:fname\>'
5901       sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
5902     endif
5903     return cfile
5904   elseif len(results) > 1
5905     let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
5906   endif
5907   return pre . s:fnameescape(s:Generate(results[0]))
5908 endfunction
5910 " Section: Statusline
5912 function! fugitive#Statusline(...) abort
5913   let dir = s:Dir(bufnr(''))
5914   if empty(dir)
5915     return ''
5916   endif
5917   let status = ''
5918   let commit = s:DirCommitFile(@%)[1]
5919   if len(commit)
5920     let status .= ':' . commit[0:6]
5921   endif
5922   let status .= '('.FugitiveHead(7, dir).')'
5923   return '[Git'.status.']'
5924 endfunction
5926 function! fugitive#statusline(...) abort
5927   return fugitive#Statusline()
5928 endfunction
5930 function! fugitive#head(...) abort
5931   if empty(s:Dir())
5932     return ''
5933   endif
5935   return fugitive#Head(a:0 ? a:1 : 0)
5936 endfunction
5938 " Section: Folding
5940 function! fugitive#Foldtext() abort
5941   if &foldmethod !=# 'syntax'
5942     return foldtext()
5943   endif
5945   let line_foldstart = getline(v:foldstart)
5946   if line_foldstart =~# '^diff '
5947     let [add, remove] = [-1, -1]
5948     let filename = ''
5949     for lnum in range(v:foldstart, v:foldend)
5950       let line = getline(lnum)
5951       if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
5952         let filename = line[6:-1]
5953       endif
5954       if line =~# '^+'
5955         let add += 1
5956       elseif line =~# '^-'
5957         let remove += 1
5958       elseif line =~# '^Binary '
5959         let binary = 1
5960       endif
5961     endfor
5962     if filename ==# ''
5963       let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
5964     endif
5965     if filename ==# ''
5966       let filename = line_foldstart[5:-1]
5967     endif
5968     if exists('binary')
5969       return 'Binary: '.filename
5970     else
5971       return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
5972     endif
5973   elseif line_foldstart =~# '^# .*:$'
5974     let lines = getline(v:foldstart, v:foldend)
5975     call filter(lines, 'v:val =~# "^#\t"')
5976     cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
5977     cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
5978     return line_foldstart.' '.join(lines, ', ')
5979   endif
5980   return foldtext()
5981 endfunction
5983 function! fugitive#foldtext() abort
5984   return fugitive#Foldtext()
5985 endfunction
5987 augroup fugitive_folding
5988   autocmd!
5989   autocmd User Fugitive
5990         \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
5991         \    set foldtext=fugitive#Foldtext() |
5992         \ endif
5993 augroup END
5995 " Section: Initialization
5997 function! fugitive#Init() abort
5998   if exists('#User#FugitiveBoot')
5999     exe s:DoAutocmd('User FugitiveBoot')
6000   endif
6001   let dir = s:Dir()
6002   if &tags !~# '\.git' && @% !~# '\.git' && !exists('s:tags_warning')
6003     let actualdir = fugitive#Find('.git/', dir)
6004     if filereadable(actualdir . 'tags')
6005       let s:tags_warning = 1
6006       echohl WarningMsg
6007       echo "Fugitive .git/tags support removed in favor of `:set tags^=./.git/tags;`"
6008       echohl NONE
6009     endif
6010   endif
6011   exe s:DoAutocmd('User Fugitive')
6012 endfunction
6014 function! fugitive#is_git_dir(path) abort
6015   return FugitiveIsGitDir(a:path)
6016 endfunction
6018 function! fugitive#extract_git_dir(path) abort
6019   return FugitiveExtractGitDir(a:path)
6020 endfunction
6022 function! fugitive#detect(path) abort
6023   return FugitiveDetect(a:path)
6024 endfunction
6026 " Section: End