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