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