Make remaining buffer commands global
[vim-fugitive.git] / autoload / fugitive.vim
blob7dfb91750fab231df8fcf1b9932973a5a48b01bd
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
1620     let cmd += ['status', '--porcelain', '-bz']
1621     let [output, message, exec_error] = s:NullError(cmd)
1622     if exec_error
1623       throw 'fugitive: ' . message
1624     endif
1626     let head = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
1627     let pull = ''
1628     if head =~# '\.\.\.'
1629       let [head, pull] = split(head, '\.\.\.')
1630       let branch = head
1631     elseif head ==# 'HEAD' || empty(head)
1632       let head = FugitiveHead(11)
1633       let branch = ''
1634     else
1635       let branch = head
1636     endif
1638     let b:fugitive_files = {'Staged': {}, 'Unstaged': {}}
1639     let [staged, unstaged, untracked] = [[], [], []]
1640     let i = 0
1641     while i < len(output)
1642       let line = output[i]
1643       let file = line[3:-1]
1644       let files = file
1645       let i += 1
1646       if line[2] !=# ' '
1647         continue
1648       endif
1649       if line[0:1] =~# '[RC]'
1650         let files = output[i] . ' -> ' . file
1651         let i += 1
1652       endif
1653       if line[0] !~# '[ ?!#]'
1654         call add(staged, {'type': 'File', 'status': line[0], 'filename': files})
1655       endif
1656       if line[0:1] ==# '??'
1657         call add(untracked, {'type': 'File', 'status': line[1], 'filename': files})
1658       elseif line[1] !~# '[ !#]'
1659         call add(unstaged, {'type': 'File', 'status': line[1], 'filename': files})
1660       endif
1661     endwhile
1663     for dict in staged
1664       let b:fugitive_files['Staged'][dict.filename] = dict
1665     endfor
1666     for dict in unstaged
1667       let b:fugitive_files['Unstaged'][dict.filename] = dict
1668     endfor
1670     let config = fugitive#Config()
1672     let pull_type = 'Pull'
1673     if len(pull)
1674       let rebase = fugitive#Config('branch.' . branch . '.rebase', config)
1675       if empty(rebase)
1676         let rebase = fugitive#Config('pull.rebase', config)
1677       endif
1678       if rebase =~# '^\%(true\|yes\|on\|1\|interactive\)$'
1679         let pull_type = 'Rebase'
1680       elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
1681         let pull_type = 'Merge'
1682       endif
1683     endif
1685     let push_remote = fugitive#Config('branch.' . branch . '.pushRemote', config)
1686     if empty(push_remote)
1687       let push_remote = fugitive#Config('remote.pushDefault', config)
1688     endif
1689     let push = len(push_remote) && len(branch) ? push_remote . '/' . branch : ''
1690     if empty(push)
1691       let push = pull
1692     endif
1694     if len(pull)
1695       let unpulled = s:QueryLog(head . '..' . pull)
1696     else
1697       let unpulled = []
1698     endif
1699     if len(push)
1700       let unpushed = s:QueryLog(push . '..' . head)
1701     else
1702       let unpushed = []
1703     endif
1705     if isdirectory(fugitive#Find('.git/rebase-merge/'))
1706       let rebasing_dir = fugitive#Find('.git/rebase-merge/')
1707     elseif isdirectory(fugitive#Find('.git/rebase-apply/'))
1708       let rebasing_dir = fugitive#Find('.git/rebase-apply/')
1709     endif
1711     let rebasing = []
1712     let rebasing_head = 'detached HEAD'
1713     if exists('rebasing_dir') && filereadable(rebasing_dir . 'git-rebase-todo')
1714       let rebasing_head = substitute(readfile(rebasing_dir . 'head-name')[0], '\C^refs/heads/', '', '')
1715       let len = 11
1716       let lines = readfile(rebasing_dir . 'git-rebase-todo')
1717       for line in lines
1718         let hash = matchstr(line, '^[^a-z].*\s\zs[0-9a-f]\{4,\}\ze\.\.')
1719         if len(hash)
1720           let len = len(hash)
1721           break
1722         endif
1723       endfor
1724       if getfsize(rebasing_dir . 'done') > 0
1725         let done = readfile(rebasing_dir . 'done')
1726         call map(done, 'substitute(v:val, ''^\l\+\>'', "done", "")')
1727         let done[-1] = substitute(done[-1], '^\l\+\>', 'stop', '')
1728         let lines = done + lines
1729       endif
1730       call reverse(lines)
1731       for line in lines
1732         let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
1733         if len(match) && match[1] !~# 'exec\|merge\|label'
1734           call add(rebasing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': strpart(match[2], 0, len), 'subject': match[3]})
1735         endif
1736       endfor
1737     endif
1739     let diff = {'Staged': [], 'Unstaged': []}
1740     if len(staged)
1741       let diff['Staged'] =
1742           \ s:LinesError(['diff', '--color=never', '--no-ext-diff', '--no-prefix', '--cached'])[0]
1743     endif
1744     if len(unstaged)
1745       let diff['Unstaged'] =
1746           \ s:LinesError(['diff', '--color=never', '--no-ext-diff', '--no-prefix'])[0]
1747     endif
1748     let b:fugitive_diff = diff
1749     let expanded = get(b:, 'fugitive_expanded', {'Staged': {}, 'Unstaged': {}})
1750     let b:fugitive_expanded = {'Staged': {}, 'Unstaged': {}}
1752     silent keepjumps %delete_
1754     call s:AddHeader('Head', head)
1755     call s:AddHeader(pull_type, pull)
1756     if push !=# pull
1757       call s:AddHeader('Push', push)
1758     endif
1759     call s:AddSection('Rebasing ' . rebasing_head, rebasing)
1760     call s:AddSection('Untracked', untracked)
1761     call s:AddSection('Unstaged', unstaged)
1762     let unstaged_end = len(unstaged) ? line('$') : 0
1763     call s:AddSection('Staged', staged)
1764     let staged_end = len(staged) ? line('$') : 0
1765     call s:AddSection('Unpushed to ' . push, unpushed)
1766     call s:AddSection('Unpulled from ' . pull, unpulled)
1768     setlocal nomodified readonly noswapfile
1769     silent doautocmd BufReadPost
1770     setlocal nomodifiable
1771     if &bufhidden ==# ''
1772       setlocal bufhidden=delete
1773     endif
1774     let b:dispatch = ':Gfetch --all'
1775     call fugitive#MapJumps()
1776     call s:Map('n', '-', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
1777     call s:Map('x', '-', ":<C-U>execute <SID>Do('Toggle',1)<CR>", '<silent>')
1778     call s:Map('n', 's', ":<C-U>execute <SID>Do('Stage',0)<CR>", '<silent>')
1779     call s:Map('x', 's', ":<C-U>execute <SID>Do('Stage',1)<CR>", '<silent>')
1780     call s:Map('n', 'u', ":<C-U>execute <SID>Do('Unstage',0)<CR>", '<silent>')
1781     call s:Map('x', 'u', ":<C-U>execute <SID>Do('Unstage',1)<CR>", '<silent>')
1782     call s:Map('n', 'U', ":exe <SID>EchoExec('reset', '-q')<CR>", '<silent>')
1783     call s:MapMotion('gu', "exe <SID>StageJump(v:count, 'Untracked', 'Unstaged')")
1784     call s:MapMotion('gU', "exe <SID>StageJump(v:count, 'Unstaged', 'Untracked')")
1785     call s:MapMotion('gs', "exe <SID>StageJump(v:count, 'Staged')")
1786     call s:MapMotion('gp', "exe <SID>StageJump(v:count, 'Unpushed')")
1787     call s:MapMotion('gP', "exe <SID>StageJump(v:count, 'Unpulled')")
1788     call s:MapMotion('gr', "exe <SID>StageJump(v:count, 'Rebasing')")
1789     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>')
1790     call s:Map('n', 'a', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
1791     call s:Map('n', 'i', ":<C-U>execute <SID>NextExpandedHunk(v:count1)<CR>", '<silent>')
1792     call s:Map('n', "=", ":<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>", '<silent>')
1793     call s:Map('n', "<", ":<C-U>execute <SID>StageInline('hide',  line('.'),v:count)<CR>", '<silent>')
1794     call s:Map('n', ">", ":<C-U>execute <SID>StageInline('show',  line('.'),v:count)<CR>", '<silent>')
1795     call s:Map('x', "=", ":<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1796     call s:Map('x', "<", ":<C-U>execute <SID>StageInline('hide',  line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1797     call s:Map('x', ">", ":<C-U>execute <SID>StageInline('show',  line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1798     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>')
1799     call s:Map('n', 'dd', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>", '<silent>')
1800     call s:Map('n', 'dh', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
1801     call s:Map('n', 'ds', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
1802     call s:Map('n', 'dp', ":<C-U>execute <SID>StageDiffEdit()<CR>", '<silent>')
1803     call s:Map('n', 'dv', ":<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>", '<silent>')
1804     call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
1805     call s:Map('n', 'P', ":<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>", '<silent>')
1806     call s:Map('x', 'P', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1807     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>')
1808     call s:Map('x', 'p', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1809     call s:Map('n', 'I', ":<C-U>execute <SID>StagePatch(line('.'),line('.'))<CR>", '<silent>')
1810     call s:Map('x', 'I', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1811     if empty(mapcheck('q', 'n'))
1812       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>
1813     endif
1814     call s:Map('n', 'gq', ":<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>", '<silent>')
1815     call s:Map('n', 'R', ":echohl WarningMsg<Bar>echo 'Reloading is automatic.  Use :e to force'<Bar>echohl NONE<CR>", '<silent>')
1816     call s:Map('n', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent>')
1817     call s:Map('x', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent>')
1818     call s:Map('n', 'X', ":<C-U>execute <SID>StageDelete(line('.'), 0, v:count)<CR>", '<silent>')
1819     call s:Map('x', 'X', ":<C-U>execute <SID>StageDelete(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
1820     call s:Map('n', 'gI', ":<C-U>execute <SID>StageIgnore(line('.'), line('.'), v:count)<CR>", '<silent>')
1821     call s:Map('x', 'gI', ":<C-U>execute <SID>StageIgnore(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
1822     call s:Map('n', '.', ':<C-U> <C-R>=<SID>StageArgs(0)<CR><Home>')
1823     call s:Map('x', '.', ':<C-U> <C-R>=<SID>StageArgs(1)<CR><Home>')
1824     setlocal filetype=fugitive
1826     for [lnum, section] in [[staged_end, 'Staged'], [unstaged_end, 'Unstaged']]
1827       while len(getline(lnum))
1828         let filename = matchstr(getline(lnum), '^[A-Z?] \zs.*')
1829         if has_key(expanded[section], filename)
1830           call s:StageInline('show', lnum)
1831         endif
1832         let lnum -= 1
1833       endwhile
1834     endfor
1836     let b:fugitive_reltime = reltime()
1837     return ''
1838   catch /^fugitive:/
1839     return 'echoerr ' . string(v:exception)
1840   endtry
1841 endfunction
1843 function! fugitive#FileReadCmd(...) abort
1844   let amatch = a:0 ? a:1 : expand('<amatch>')
1845   let [dir, rev] = s:DirRev(amatch)
1846   let line = a:0 > 1 ? a:2 : line("'[")
1847   if empty(dir)
1848     return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1849   endif
1850   if rev !~# ':' && s:ChompDefault('', [dir, 'cat-file', '-t', rev]) =~# '^\%(commit\|tag\)$'
1851     let cmd = fugitive#Prepare(dir, 'log', '--pretty=format:%B', '-1', rev, '--')
1852   else
1853     let cmd = fugitive#Prepare(dir, 'cat-file', '-p', rev)
1854   endif
1855   return line . 'read !' . escape(cmd, '!#%')
1856 endfunction
1858 function! fugitive#FileWriteCmd(...) abort
1859   let tmp = tempname()
1860   let amatch = a:0 ? a:1 : expand('<amatch>')
1861   let autype = a:0 > 1 ? 'Buf' : 'File'
1862   if exists('#' . autype . 'WritePre')
1863     execute s:DoAutocmd(autype . 'WritePre ' . s:fnameescape(amatch))
1864   endif
1865   try
1866     let [dir, commit, file] = s:DirCommitFile(amatch)
1867     if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1868       return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1869     endif
1870     silent execute "'[,']write !".fugitive#Prepare(dir, 'hash-object', '-w', '--stdin', '--').' > '.tmp
1871     let sha1 = readfile(tmp)[0]
1872     let old_mode = matchstr(s:SystemError([dir, 'ls-files', '--stage', '.' . file])[0], '^\d\+')
1873     if empty(old_mode)
1874       let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1875     endif
1876     let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1877     let [error, exec_error] = s:SystemError([dir, 'update-index', '--index-info'], info . "\n")
1878     if !exec_error
1879       setlocal nomodified
1880       if exists('#' . autype . 'WritePost')
1881         execute s:DoAutocmd(autype . 'WritePost ' . s:fnameescape(amatch))
1882       endif
1883       return ''
1884     else
1885       return 'echoerr '.string('fugitive: '.error)
1886     endif
1887   finally
1888     call delete(tmp)
1889   endtry
1890 endfunction
1892 function! fugitive#BufReadCmd(...) abort
1893   let amatch = a:0 ? a:1 : expand('<amatch>')
1894   try
1895     let [dir, rev] = s:DirRev(amatch)
1896     if empty(dir)
1897       return 'echo "Invalid Fugitive URL"'
1898     endif
1899     if rev =~# '^:\d$'
1900       let b:fugitive_type = 'stage'
1901     else
1902       let [b:fugitive_type, exec_error] = s:ChompError([dir, 'cat-file', '-t', rev])
1903       if exec_error && rev =~# '^:0'
1904         let sha = s:ChompDefault('', dir, 'write-tree', '--prefix=' . rev[3:-1])
1905         let exec_error = empty(sha)
1906         let b:fugitive_type = exec_error ? '' : 'tree'
1907       endif
1908       if exec_error
1909         let error = b:fugitive_type
1910         unlet b:fugitive_type
1911         setlocal noswapfile
1912         if empty(&bufhidden)
1913           setlocal bufhidden=delete
1914         endif
1915         if rev =~# '^:\d:'
1916           let &l:readonly = !filewritable(fugitive#Find('.git/index', dir))
1917           return 'silent doautocmd BufNewFile'
1918         else
1919           setlocal readonly nomodifiable
1920           return 'silent doautocmd BufNewFile|echo ' . string(error)
1921         endif
1922       elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1923         return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1924       endif
1925       if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1926         let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1927       endif
1928     endif
1930     if b:fugitive_type !=# 'blob'
1931       setlocal nomodeline
1932     endif
1934     setlocal noreadonly modifiable
1935     let pos = getpos('.')
1936     silent keepjumps %delete_
1937     setlocal endofline
1939     try
1940       silent doautocmd BufReadPre
1941       if b:fugitive_type ==# 'tree'
1942         let b:fugitive_display_format = b:fugitive_display_format % 2
1943         if b:fugitive_display_format
1944           call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1945         else
1946           if !exists('sha')
1947             let sha = s:TreeChomp(dir, 'rev-parse', '--verify', rev, '--')
1948           endif
1949           call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1950         endif
1951       elseif b:fugitive_type ==# 'tag'
1952         let b:fugitive_display_format = b:fugitive_display_format % 2
1953         if b:fugitive_display_format
1954           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1955         else
1956           call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1957         endif
1958       elseif b:fugitive_type ==# 'commit'
1959         let b:fugitive_display_format = b:fugitive_display_format % 2
1960         if b:fugitive_display_format
1961           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1962         else
1963           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])
1964           keepjumps call search('^parent ')
1965           if getline('.') ==# 'parent '
1966             silent keepjumps delete_
1967           else
1968             silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
1969           endif
1970           keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1971           if lnum
1972             silent keepjumps delete_
1973           end
1974           silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/s/\r$//e'
1975           keepjumps 1
1976         endif
1977       elseif b:fugitive_type ==# 'stage'
1978         call s:ReplaceCmd([dir, 'ls-files', '--stage'])
1979       elseif b:fugitive_type ==# 'blob'
1980         call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1981       endif
1982     finally
1983       keepjumps call setpos('.',pos)
1984       setlocal nomodified noswapfile
1985       let modifiable = rev =~# '^:.:' && b:fugitive_type !=# 'tree'
1986       let &l:readonly = !modifiable || !filewritable(fugitive#Find('.git/index', dir))
1987       if empty(&bufhidden)
1988         setlocal bufhidden=delete
1989       endif
1990       let &l:modifiable = modifiable
1991       if b:fugitive_type !=# 'blob'
1992         setlocal filetype=git foldmethod=syntax
1993         call s:Map('n', 'a', ":<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
1994         call s:Map('n', 'i', ":<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
1995       endif
1996       call fugitive#MapJumps()
1997     endtry
1999     setlocal modifiable
2000     return 'silent ' . s:DoAutocmd('BufReadPost') .
2001           \ (modifiable ? '' : '|setl nomodifiable')
2002   catch /^fugitive:/
2003     return 'echoerr ' . string(v:exception)
2004   endtry
2005 endfunction
2007 function! fugitive#BufWriteCmd(...) abort
2008   return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
2009 endfunction
2011 function! fugitive#SourceCmd(...) abort
2012   let amatch = a:0 ? a:1 : expand('<amatch>')
2013   let temp = s:BlobTemp(amatch)
2014   if empty(temp)
2015     return 'noautocmd source ' . s:fnameescape(amatch)
2016   endif
2017   if !exists('g:virtual_scriptnames')
2018     let g:virtual_scriptnames = {}
2019   endif
2020   let g:virtual_scriptnames[temp] = amatch
2021   return 'source ' . s:fnameescape(temp)
2022 endfunction
2024 " Section: Temp files
2026 if !exists('s:temp_files')
2027   let s:temp_files = {}
2028 endif
2030 function! s:TempState(...) abort
2031   return get(s:temp_files, s:cpath(fnamemodify(a:0 ? a:1 : @%, ':p')), {})
2032 endfunction
2034 function! s:TempReadPre(file) abort
2035   if has_key(s:temp_files, s:cpath(a:file))
2036     let dict = s:temp_files[s:cpath(a:file)]
2037     setlocal nomodeline
2038     setlocal bufhidden=delete nobuflisted
2039     setlocal buftype=nowrite
2040     if has_key(dict, 'modifiable')
2041       let &l:modifiable = dict.modifiable
2042     endif
2043     if len(dict.dir)
2044       let b:git_dir = dict.dir
2045       call extend(b:, {'fugitive_type': 'temp'}, 'keep')
2046     endif
2047   endif
2048 endfunction
2050 function! s:TempReadPost(file) abort
2051   if has_key(s:temp_files, s:cpath(a:file))
2052     let dict = s:temp_files[s:cpath(a:file)]
2053     if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
2054       let &l:filetype = dict.filetype
2055     endif
2056     setlocal foldmarker=<<<<<<<,>>>>>>>
2057     if empty(mapcheck('q', 'n'))
2058       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>
2059     endif
2060     if !&modifiable
2061       call s:Map('n', 'gq', ":<C-U>bdelete<CR>", '<silent> <unique>')
2062     endif
2063   endif
2064   return ''
2065 endfunction
2067 augroup fugitive_temp
2068   autocmd!
2069   autocmd BufReadPre  * exe s:TempReadPre( expand('<amatch>:p'))
2070   autocmd BufReadPost * exe s:TempReadPost(expand('<amatch>:p'))
2071 augroup END
2073 " Section: :Git
2075 function! fugitive#Command(line1, line2, range, bang, mods, arg) abort
2076   let dir = s:Dir()
2077   let [args, after] = s:SplitExpandChain(a:arg, s:Tree(dir))
2078   if empty(args)
2079     let cmd = s:StatusCommand(a:line1, a:line2, a:range, a:line2, a:bang, a:mods, '', '', [])
2080     return (empty(cmd) ? 'exe' : cmd) . after
2081   endif
2082   let alias = get(s:Aliases(dir), args[0], '!')
2083   if get(args, 1, '') !=# '--help' && alias !~# '^!\|[\"'']' && !filereadable(s:ExecPath() . '/git-' . args[0])
2084         \ && !(has('win32') && filereadable(s:ExecPath() . '/git-' . args[0] . '.exe'))
2085     call remove(args, 0)
2086     call extend(args, split(alias, '\s\+'), 'keep')
2087   endif
2088   let name = substitute(args[0], '\%(^\|-\)\(\l\)', '\u\1', 'g')
2089   if exists('*s:' . name . 'Subcommand') && get(args, 1, '') !=# '--help'
2090     try
2091       exe s:DirCheck(dir)
2092       return 'exe ' . string(s:{name}Subcommand(a:line1, a:line2, a:range, a:bang, a:mods, args[1:-1])) . after
2093     catch /^fugitive:/
2094       return 'echoerr ' . string(v:exception)
2095     endtry
2096   endif
2097   if a:bang || args[0] =~# '^-P$\|^--no-pager$\|diff\%(tool\)\@!\|log\|^show$' ||
2098         \ (args[0] ==# 'stash' && get(args, 1, '') ==# 'show') ||
2099         \ (args[0] ==# 'help' || get(args, 1, '') ==# '--help') && !s:HasOpt(args, '--web')
2100     return s:OpenExec((a:line2 > 0 ? a:line2 : '') . (a:line2 ? 'split' : 'edit'), a:mods, args, dir) . after
2101   endif
2102   if s:HasOpt(args, ['add', 'checkout', 'commit', 'stage', 'stash', 'reset'], '-p', '--patch') ||
2103         \ s:HasOpt(args, ['add', 'clean', 'stage'], '-i', '--interactive') ||
2104         \ index(['--paginate', '-p'], args[0]) >= 0
2105     let mods = substitute(s:Mods(a:mods), '\<tab\>', '-tab', 'g')
2106     let assign = len(dir) ? '|let b:git_dir = ' . string(dir) : ''
2107     if has('nvim')
2108       if &autowrite || &autowriteall | silent! wall | endif
2109       return mods . (a:line2 ? 'split' : 'edit') . ' term://' . s:fnameescape(s:UserCommand(dir, args)) . assign . '|startinsert' . after
2110     elseif has('terminal')
2111       if &autowrite || &autowriteall | silent! wall | endif
2112       return 'exe ' . string(mods . 'terminal ' . (a:line2 ? '' : '++curwin ') . join(map(s:UserCommandList(dir) + args, 's:fnameescape(v:val)'))) . assign . after
2113     endif
2114   endif
2115   if has('gui_running') && !has('win32')
2116     call insert(args, '--no-pager')
2117   endif
2118   let pre = ''
2119   if has('nvim') && executable('env')
2120     let pre .= 'env GIT_TERMINAL_PROMPT=0 '
2121   endif
2122   return 'exe ' . string('noautocmd !' . escape(pre . s:UserCommand(dir, args), '!#%')) .
2123         \ '|call fugitive#ReloadStatus(' . string(dir) . ', 1)' .
2124         \ after
2125 endfunction
2127 let s:exec_paths = {}
2128 function! s:ExecPath() abort
2129   if !has_key(s:exec_paths, g:fugitive_git_executable)
2130     let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
2131   endif
2132   return s:exec_paths[g:fugitive_git_executable]
2133 endfunction
2135 function! s:Subcommands() abort
2136   let exec_path = s:ExecPath()
2137   return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
2138 endfunction
2140 let s:aliases = {}
2141 function! s:Aliases(dir) abort
2142   if !has_key(s:aliases, a:dir)
2143     let s:aliases[a:dir] = {}
2144     let lines = s:NullError([a:dir, 'config', '-z', '--get-regexp', '^alias[.]'])[0]
2145     for line in lines
2146       let s:aliases[a:dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
2147     endfor
2148   endif
2149   return s:aliases[a:dir]
2150 endfunction
2152 function! fugitive#Complete(lead, ...) abort
2153   let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
2154   let pre = a:0 > 1 ? strpart(a:1, 0, a:2) : ''
2155   let subcmd = matchstr(pre, '\u\w*[! ] *\zs[[:alnum:]-]\+\ze ')
2156   if empty(subcmd)
2157     let results = sort(s:Subcommands() + keys(s:Aliases(dir)))
2158   elseif pre =~# ' -- '
2159     return fugitive#CompletePath(a:lead, dir)
2160   elseif a:lead =~# '^-'
2161     let results = split(s:ChompDefault('', dir, subcmd, '--git-completion-helper'), ' ')
2162   else
2163     return fugitive#CompleteObject(a:lead, dir)
2164   endif
2165   return filter(results, 'strpart(v:val, 0, strlen(a:lead)) ==# a:lead')
2166 endfunction
2168 " Section: :Gcd, :Glcd
2170 function! fugitive#CdComplete(A, L, P) abort
2171   return filter(fugitive#CompletePath(a:A), 'v:val =~# "/$"')
2172 endfunction
2174 function! fugitive#Cd(path, ...) abort
2175   let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
2176   if path !~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
2177     let dir = s:Dir()
2178     exe s:DirCheck(dir)
2179     let path = (empty(s:Tree(dir)) ? dir : s:Tree(dir)) . '/' . path
2180   endif
2181   return (a:0 && a:1 ? 'lcd ' : 'cd ') . s:fnameescape(FugitiveVimPath(path))
2182 endfunction
2184 " Section: :Gstatus
2186 function! s:StatusCommand(line1, line2, range, count, bang, mods, reg, arg, args, ...) abort
2187   let dir = a:0 ? a:1 : s:Dir()
2188   exe s:DirCheck(dir)
2189   try
2190     let mods = s:Mods(a:mods, &splitbelow ? 'botright' : 'topleft')
2191     let file = fugitive#Find(':', dir)
2192     let arg = ' +setl\ foldmethod=syntax\ foldlevel=1\|let\ w:fugitive_status=FugitiveGitDir() ' .
2193           \ s:fnameescape(file)
2194     for winnr in range(1, winnr('$'))
2195       if s:cpath(file, fnamemodify(bufname(winbufnr(winnr)), ':p'))
2196         if winnr == winnr()
2197           call s:ReloadStatus()
2198         else
2199           call s:ExpireStatus(dir)
2200           exe winnr . 'wincmd w'
2201         endif
2202         let w:fugitive_status = dir
2203         1
2204         return ''
2205       endif
2206     endfor
2207     if a:count ==# 0
2208       return mods . 'edit' . (a:bang ? '!' : '') . arg
2209     elseif a:bang
2210       return mods . 'pedit' . arg . '|wincmd P'
2211     else
2212       return mods . (a:count > 0 ? a:count : '') . 'split' . arg
2213     endif
2214   catch /^fugitive:/
2215     return 'echoerr ' . string(v:exception)
2216   endtry
2217   return ''
2218 endfunction
2220 function! s:StageJump(offset, section, ...) abort
2221   let line = search('^\%(' . a:section . '\)', 'nw')
2222   if !line && a:0
2223     let line = search('^\%(' . a:1 . '\)', 'nw')
2224   endif
2225   if line
2226     exe line
2227     if a:offset
2228       for i in range(a:offset)
2229         call search(s:file_commit_pattern . '\|^$', 'W')
2230         if empty(getline('.')) && a:0 && getline(line('.') + 1) =~# '^\%(' . a:1 . '\)'
2231           call search(s:file_commit_pattern . '\|^$', 'W')
2232         endif
2233         if empty(getline('.'))
2234           return ''
2235         endif
2236       endfor
2237       call s:StageReveal()
2238     else
2239       call s:StageReveal()
2240       +
2241     endif
2242   endif
2243   return ''
2244 endfunction
2246 function! s:StageSeek(info, fallback) abort
2247   let info = a:info
2248   if empty(info.section)
2249     return a:fallback
2250   endif
2251   let line = search('^' . info.section, 'wn')
2252   if !line
2253     for section in get({'Staged': ['Unstaged', 'Untracked'], 'Unstaged': ['Untracked', 'Staged'], 'Untracked': ['Unstaged', 'Staged']}, info.section, [])
2254       let line = search('^' . section, 'wn')
2255       if line
2256         return line + (info.index > 0 ? 1 : 0)
2257       endif
2258     endfor
2259     return 1
2260   endif
2261   let i = 0
2262   while len(getline(line))
2263     let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
2264     if len(filename) &&
2265           \ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
2266           \ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
2267           \ filename ==# info.filename)
2268       if info.offset < 0
2269         return line
2270       else
2271         if getline(line+1) !~# '^@'
2272           exe s:StageInline('show', line)
2273         endif
2274         if getline(line+1) !~# '^@'
2275           return line
2276         endif
2277         let type = info.sigil ==# '-' ? '-' : '+'
2278         let offset = -1
2279         while offset < info.offset
2280           let line += 1
2281           if getline(line) =~# '^@'
2282             let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
2283           elseif getline(line) =~# '^[ ' . type . ']'
2284             let offset += 1
2285           elseif getline(line) !~# '^[ @\+-]'
2286             return line - 1
2287           endif
2288         endwhile
2289         return line
2290       endif
2291     endif
2292     let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
2293     if len(commit) && commit ==# info.commit
2294       return line
2295     endif
2296     if i ==# info.index
2297       let backup = line
2298     endif
2299     let i += getline(line) !~# '^[ @\+-]'
2300     let line += 1
2301   endwhile
2302   return exists('backup') ? backup : line - 1
2303 endfunction
2305 function! s:DoAutocmdChanged(dir) abort
2306   let dir = a:dir is# -2 ? '' : FugitiveGitDir(a:dir)
2307   if empty(dir) || !exists('#User#FugitiveChanged') || exists('g:fugitive_event')
2308     return ''
2309   endif
2310   try
2311     let g:fugitive_event = dir
2312     exe s:DoAutocmd('User FugitiveChanged')
2313   finally
2314     unlet! g:fugitive_event
2315     " Force statusline reload with the buffer's Git dir
2316     let &ro = &ro
2317   endtry
2318   return ''
2319 endfunction
2321 function! s:ReloadStatusBuffer(...) abort
2322   if get(b:, 'fugitive_type', '') !=# 'index'
2323     return ''
2324   endif
2325   let original_lnum = a:0 ? a:1 : line('.')
2326   let info = s:StageInfo(original_lnum)
2327   call fugitive#BufReadStatus()
2328   exe s:StageSeek(info, original_lnum)
2329   normal! 0
2330   return ''
2331 endfunction
2333 function! s:ReloadStatus(...) abort
2334   call s:ExpireStatus(-1)
2335   call s:ReloadStatusBuffer(a:0 ? a:1 : line('.'))
2336   exe s:DoAutocmdChanged(-1)
2337   return ''
2338 endfunction
2340 let s:last_time = reltime()
2341 if !exists('s:last_times')
2342   let s:last_times = {}
2343 endif
2345 function! s:ExpireStatus(bufnr) abort
2346   if a:bufnr == -2
2347     let s:last_time = reltime()
2348     return ''
2349   endif
2350   let dir = s:Dir(a:bufnr)
2351   if len(dir)
2352     let s:last_times[s:cpath(dir)] = reltime()
2353   endif
2354   return ''
2355 endfunction
2357 function! FugitiveReloadCheck() abort
2358   let t = b:fugitive_reltime
2359   return [t, reltimestr(reltime(s:last_time, t)),
2360         \ reltimestr(reltime(get(s:last_times, s:cpath(s:Dir()), t), t))]
2361 endfunction
2363 function! s:ReloadWinStatus(...) abort
2364   if get(b:, 'fugitive_type', '') !=# 'index' || &modified
2365     return
2366   endif
2367   if !exists('b:fugitive_reltime')
2368     exe s:ReloadStatusBuffer()
2369     return
2370   endif
2371   let t = b:fugitive_reltime
2372   if reltimestr(reltime(s:last_time, t)) =~# '-\|\d\{10\}\.' ||
2373         \ reltimestr(reltime(get(s:last_times, s:cpath(s:Dir()), t), t)) =~# '-\|\d\{10\}\.'
2374     exe s:ReloadStatusBuffer()
2375   endif
2376 endfunction
2378 function! s:ReloadTabStatus(...) abort
2379   let mytab = tabpagenr()
2380   let tab = a:0 ? a:1 : mytab
2381   for winnr in range(1, tabpagewinnr(tab, '$'))
2382     if getbufvar(tabpagebuflist(tab)[winnr-1], 'fugitive_type') ==# 'index'
2383       execute 'tabnext '.tab
2384       if winnr != winnr()
2385         execute winnr.'wincmd w'
2386         let restorewinnr = 1
2387       endif
2388       try
2389         call s:ReloadWinStatus()
2390       finally
2391         if exists('restorewinnr')
2392           unlet restorewinnr
2393           wincmd p
2394         endif
2395         execute 'tabnext '.mytab
2396       endtry
2397     endif
2398   endfor
2399   unlet! t:fugitive_reload_status
2400 endfunction
2402 function! fugitive#ReloadStatus(...) abort
2403   call s:ExpireStatus(a:0 ? a:1 : -1)
2404   if a:0 > 1 ? a:2 : 1
2405     let t = reltime()
2406     let t:fugitive_reload_status = t
2407     for tabnr in exists('*settabvar') ? range(1, tabpagenr('$')) : []
2408       call settabvar(tabnr, 'fugitive_reload_status', t)
2409     endfor
2410     call s:ReloadTabStatus()
2411     exe s:DoAutocmdChanged(a:0 ? a:1 : -1)
2412   else
2413     call s:ReloadWinStatus()
2414   endif
2415   return ''
2416 endfunction
2418 function! fugitive#EfmDir(...) abort
2419   let dir = matchstr(a:0 ? a:1 : &errorformat, '\c,%\\&\%(git\|fugitive\)_\=dir=\zs\%(\\.\|[^,]\)*')
2420   let dir = substitute(dir, '%%', '%', 'g')
2421   let dir = substitute(dir, '\\\ze[\,]', '', 'g')
2422   return dir
2423 endfunction
2425 augroup fugitive_status
2426   autocmd!
2427   autocmd BufWritePost         * call fugitive#ReloadStatus(-1, 0)
2428   autocmd ShellCmdPost,ShellFilterPost * nested call fugitive#ReloadStatus(-2, 0)
2429   autocmd BufDelete * nested
2430         \ if getbufvar(+expand('<abuf>'), 'buftype') ==# 'terminal' |
2431         \   if !empty(FugitiveGitDir(+expand('<abuf>'))) |
2432         \     call fugitive#ReloadStatus(+expand('<abuf>'), 1) |
2433         \   else |
2434         \     call fugitive#ReloadStatus(-2, 0) |
2435         \  endif |
2436         \ endif
2437   autocmd QuickFixCmdPost make,lmake,[cl]file,[cl]getfile nested
2438         \ call fugitive#ReloadStatus(fugitive#EfmDir(), 1)
2439   if !has('win32')
2440     autocmd FocusGained        * call fugitive#ReloadStatus(-2, 0)
2441   endif
2442   autocmd BufEnter index,index.lock
2443         \ call s:ReloadWinStatus()
2444   autocmd TabEnter *
2445         \ if exists('t:fugitive_reload_status') |
2446         \    call s:ReloadTabStatus() |
2447         \ endif
2448 augroup END
2450 function! s:StageInfo(...) abort
2451   let lnum = a:0 ? a:1 : line('.')
2452   let sigil = matchstr(getline(lnum), '^[ @\+-]')
2453   let offset = -1
2454   if len(sigil)
2455     let type = sigil ==# '-' ? '-' : '+'
2456     while lnum > 0 && getline(lnum) !~# '^@'
2457       if getline(lnum) =~# '^[ '.type.']'
2458         let offset += 1
2459       endif
2460       let lnum -= 1
2461     endwhile
2462     let offset += matchstr(getline(lnum), type.'\zs\d\+')
2463     while getline(lnum) =~# '^[ @\+-]'
2464       let lnum -= 1
2465     endwhile
2466   endif
2467   let slnum = lnum + 1
2468   let section = ''
2469   let index = 0
2470   while len(getline(slnum - 1)) && empty(section)
2471     let slnum -= 1
2472     let section = matchstr(getline(slnum), '^\u\l\+\ze.* (\d\+)$')
2473     if empty(section) && getline(slnum) !~# '^[ @\+-]'
2474       let index += 1
2475     endif
2476   endwhile
2477   let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
2478   return {'section': section,
2479         \ 'heading': getline(slnum),
2480         \ 'sigil': sigil,
2481         \ 'offset': offset,
2482         \ 'filename': text,
2483         \ 'relative': reverse(split(text, ' -> ')),
2484         \ 'paths': map(reverse(split(text, ' -> ')), 's:Tree() . "/" . v:val'),
2485         \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
2486         \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
2487         \ 'index': index}
2488 endfunction
2490 function! s:Selection(arg1, ...) abort
2491   if a:arg1 ==# 'n'
2492     let arg1 = line('.')
2493     let arg2 = -v:count
2494   elseif a:arg1 ==# 'v'
2495     let arg1 = line("'<")
2496     let arg2 = line("'>")
2497   else
2498     let arg1 = a:arg1
2499     let arg2 = a:0 ? a:1 : 0
2500   endif
2501   let first = arg1
2502   if arg2 < 0
2503     let last = first - arg2 - 1
2504   elseif arg2 > 0
2505     let last = arg2
2506   else
2507     let last = first
2508   endif
2509   while getline(first) =~# '^$\|^[A-Z][a-z]'
2510     let first += 1
2511   endwhile
2512   if first > last || &filetype !=# 'fugitive'
2513     return []
2514   endif
2515   let flnum = first
2516   while getline(flnum) =~# '^[ @\+-]'
2517     let flnum -= 1
2518   endwhile
2519   let slnum = flnum + 1
2520   let section = ''
2521   let index = 0
2522   while len(getline(slnum - 1)) && empty(section)
2523     let slnum -= 1
2524     let heading = matchstr(getline(slnum), '^\u\l\+.* (\d\+)$')
2525     if empty(heading) && getline(slnum) !~# '^[ @\+-]'
2526       let index += 1
2527     endif
2528   endwhile
2529   let results = []
2530   let template = {
2531         \ 'heading': heading,
2532         \ 'section': matchstr(heading, '^\u\l\+\ze.* (\d\+)$'),
2533         \ 'filename': '',
2534         \ 'relative': [],
2535         \ 'paths': [],
2536         \ 'commit': '',
2537         \ 'status': '',
2538         \ 'patch': 0,
2539         \ 'index': index}
2540   let line = getline(flnum)
2541   let lnum = first - (arg1 == flnum ? 0 : 1)
2542   let root = s:Tree() . '/'
2543   while lnum <= last
2544     if line =~# '^\u\l\+\ze.* (\d\+)$'
2545       let template.heading = getline(lnum)
2546       let template.section = matchstr(template.heading, '^\u\l\+\ze.* (\d\+)$')
2547       let template.index = 0
2548     elseif line =~# '^[ @\+-]'
2549       let template.index -= 1
2550       if !results[-1].patch
2551         let results[-1].patch = lnum
2552       endif
2553       let results[-1].lnum = lnum
2554     elseif line =~# '^[A-Z?] '
2555       let filename = matchstr(line, '^[A-Z?] \zs.*')
2556       call add(results, extend(deepcopy(template), {
2557             \ 'lnum': lnum,
2558             \ 'filename': filename,
2559             \ 'relative': reverse(split(filename, ' -> ')),
2560             \ 'paths': map(reverse(split(filename, ' -> ')), 'root . v:val'),
2561             \ 'status': matchstr(line, '^[A-Z?]'),
2562             \ }))
2563     elseif line =~# '^\x\x\x\+ '
2564       call add(results, extend({
2565             \ 'lnum': lnum,
2566             \ 'commit': matchstr(line, '^\x\x\x\+'),
2567             \ }, template, 'keep'))
2568     elseif line =~# '^\l\+ \x\x\x\+ '
2569       call add(results, extend({
2570             \ 'lnum': lnum,
2571             \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
2572             \ 'status': matchstr(line, '^\l\+'),
2573             \ }, template, 'keep'))
2574     endif
2575     let lnum += 1
2576     let template.index += 1
2577     let line = getline(lnum)
2578   endwhile
2579   if len(results) && results[0].patch && arg2 == 0
2580     while getline(results[0].patch) =~# '^[ \+-]'
2581       let results[0].patch -= 1
2582     endwhile
2583     while getline(results[0].lnum + 1) =~# '^[ \+-]'
2584       let results[0].lnum += 1
2585     endwhile
2586   endif
2587   return results
2588 endfunction
2590 function! s:StageArgs(visual) abort
2591   let commits = []
2592   let paths = []
2593   for record in s:Selection(a:visual ? 'v' : 'n')
2594     if len(record.commit)
2595       call add(commits, record.commit)
2596     endif
2597     call extend(paths, record.paths)
2598   endfor
2599   if s:cpath(s:Tree(), getcwd())
2600     call map(paths, 'fugitive#Path(v:val, "./")')
2601   endif
2602   return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
2603 endfunction
2605 function! s:Do(action, visual) abort
2606   let line = getline('.')
2607   let reload = 0
2608   if !a:visual && !v:count && line =~# '^[A-Z][a-z]'
2609     let header = matchstr(line, '^\S\+\ze:')
2610     if len(header) && exists('*s:Do' . a:action . header . 'Header')
2611       let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
2612     else
2613       let section = matchstr(line, '^\S\+')
2614       if exists('*s:Do' . a:action . section . 'Heading')
2615         let reload = s:Do{a:action}{section}Heading(line) > 0
2616       endif
2617     endif
2618     return reload ? s:ReloadStatus() : ''
2619   endif
2620   let selection = s:Selection(a:visual ? 'v' : 'n')
2621   if empty(selection)
2622     return ''
2623   endif
2624   call filter(selection, 'v:val.section ==# selection[0].section')
2625   let status = 0
2626   let err = ''
2627   try
2628     for record in selection
2629       if exists('*s:Do' . a:action . record.section)
2630         let status = s:Do{a:action}{record.section}(record)
2631       else
2632         continue
2633       endif
2634       if !status
2635         return ''
2636       endif
2637       let reload = reload || (status > 0)
2638     endfor
2639     if status < 0
2640       execute record.lnum + 1
2641     endif
2642     let success = 1
2643   catch /^fugitive:/
2644     return 'echoerr ' . string(v:exception)
2645   finally
2646     if reload
2647       execute s:ReloadStatus()
2648     endif
2649     if exists('success')
2650       call s:StageReveal()
2651     endif
2652   endtry
2653   return ''
2654 endfunction
2656 function! s:StageReveal() abort
2657   exe 'normal! zv'
2658   let begin = line('.')
2659   if getline(begin) =~# '^@'
2660     let end = begin + 1
2661     while getline(end) =~# '^[ \+-]'
2662       let end += 1
2663     endwhile
2664   elseif getline(begin) =~# '^commit '
2665     let end = begin
2666     while end < line('$') && getline(end + 1) !~# '^commit '
2667       let end += 1
2668     endwhile
2669   elseif getline(begin) =~# s:section_pattern
2670     let end = begin
2671     while len(getline(end + 1))
2672       let end += 1
2673     endwhile
2674   endif
2675   if exists('end')
2676     while line('.') > line('w0') + &scrolloff && end > line('w$')
2677       execute "normal! \<C-E>"
2678     endwhile
2679   endif
2680 endfunction
2682 let s:file_pattern = '^[A-Z?] .\|^diff --'
2683 let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
2684 let s:item_pattern = s:file_commit_pattern . '\|^@@'
2686 function! s:NextHunk(count) abort
2687   if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
2688     exe s:StageInline('show')
2689   endif
2690   for i in range(a:count)
2691     if &filetype ==# 'fugitive'
2692       call search(s:file_pattern . '\|^@', 'W')
2693       if getline('.') =~# s:file_pattern
2694         exe s:StageInline('show')
2695         if getline(line('.') + 1) =~# '^@'
2696           +
2697         endif
2698       endif
2699     else
2700       call search('^@@', 'W')
2701     endif
2702   endfor
2703   call s:StageReveal()
2704   return '.'
2705 endfunction
2707 function! s:PreviousHunk(count) abort
2708   for i in range(a:count)
2709     if &filetype ==# 'fugitive'
2710       let lnum = search(s:file_pattern . '\|^@','Wbn')
2711       call s:StageInline('show', lnum)
2712       call search('^? .\|^@','Wb')
2713     else
2714       call search('^@@', 'Wb')
2715     endif
2716   endfor
2717   call s:StageReveal()
2718   return '.'
2719 endfunction
2721 function! s:NextFile(count) abort
2722   for i in range(a:count)
2723     exe s:StageInline('hide')
2724     if !search(s:file_pattern, 'W')
2725       break
2726     endif
2727   endfor
2728   exe s:StageInline('hide')
2729   return '.'
2730 endfunction
2732 function! s:PreviousFile(count) abort
2733   exe s:StageInline('hide')
2734   for i in range(a:count)
2735     if !search(s:file_pattern, 'Wb')
2736       break
2737     endif
2738     exe s:StageInline('hide')
2739   endfor
2740   return '.'
2741 endfunction
2743 function! s:NextItem(count) abort
2744   for i in range(a:count)
2745     if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
2746       call search('^commit ', 'W')
2747     endif
2748   endfor
2749   call s:StageReveal()
2750   return '.'
2751 endfunction
2753 function! s:PreviousItem(count) abort
2754   for i in range(a:count)
2755     if !search(s:item_pattern, 'Wbe') && getline('.') !~# s:item_pattern
2756       call search('^commit ', 'Wbe')
2757     endif
2758   endfor
2759   call s:StageReveal()
2760   return '.'
2761 endfunction
2763 let s:section_pattern = '^[A-Z][a-z][^:]*$'
2764 let s:section_commit_pattern = s:section_pattern . '\|^commit '
2766 function! s:NextSection(count) abort
2767   let orig = line('.')
2768   if getline('.') !~# '^commit '
2769     -
2770   endif
2771   for i in range(a:count)
2772     if !search(s:section_commit_pattern, 'W')
2773       break
2774     endif
2775   endfor
2776   if getline('.') =~# s:section_commit_pattern
2777     call s:StageReveal()
2778     return getline('.') =~# s:section_pattern ? '+' : ':'
2779   else
2780     return orig
2781   endif
2782 endfunction
2784 function! s:PreviousSection(count) abort
2785   let orig = line('.')
2786   if getline('.') !~# '^commit '
2787     -
2788   endif
2789   for i in range(a:count)
2790     if !search(s:section_commit_pattern . '\|\%^', 'bW')
2791       break
2792     endif
2793   endfor
2794   if getline('.') =~# s:section_commit_pattern || line('.') == 1
2795     call s:StageReveal()
2796     return getline('.') =~# s:section_pattern ? '+' : ':'
2797   else
2798     return orig
2799   endif
2800 endfunction
2802 function! s:NextSectionEnd(count) abort
2803   +
2804   if empty(getline('.'))
2805     +
2806   endif
2807   for i in range(a:count)
2808     if !search(s:section_commit_pattern, 'W')
2809       return '$'
2810     endif
2811   endfor
2812   return search('^.', 'Wb')
2813 endfunction
2815 function! s:PreviousSectionEnd(count) abort
2816   let old = line('.')
2817   for i in range(a:count)
2818     if search(s:section_commit_pattern, 'Wb') <= 1
2819       exe old
2820       if i
2821         break
2822       else
2823         return ''
2824       endif
2825     endif
2826     let old = line('.')
2827   endfor
2828   return search('^.', 'Wb')
2829 endfunction
2831 function! s:PatchSearchExpr(reverse) abort
2832   let line = getline('.')
2833   if col('.') ==# 1 && line =~# '^[+-]'
2834     if line =~# '^[+-]\{3\} '
2835       let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
2836     else
2837       let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
2838     endif
2839     if a:reverse
2840       return '?' . escape(pattern, '/') . "\<CR>"
2841     else
2842       return '/' . escape(pattern, '/?') . "\<CR>"
2843     endif
2844   endif
2845   return a:reverse ? '#' : '*'
2846 endfunction
2848 function! s:StageInline(mode, ...) abort
2849   if &filetype !=# 'fugitive'
2850     return ''
2851   endif
2852   let lnum1 = a:0 ? a:1 : line('.')
2853   let lnum = lnum1 + 1
2854   if a:0 > 1 && a:2 == 0
2855     let info = s:StageInfo(lnum - 1)
2856     if empty(info.paths) && len(info.section)
2857       while len(getline(lnum))
2858         let lnum += 1
2859       endwhile
2860     endif
2861   elseif a:0 > 1
2862     let lnum += a:2 - 1
2863   endif
2864   while lnum > lnum1
2865     let lnum -= 1
2866     while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
2867       let lnum -= 1
2868     endwhile
2869     let info = s:StageInfo(lnum)
2870     if !has_key(b:fugitive_diff, info.section)
2871       continue
2872     endif
2873     if getline(lnum + 1) =~# '^[ @\+-]'
2874       let lnum2 = lnum + 1
2875       while getline(lnum2 + 1) =~# '^[ @\+-]'
2876         let lnum2 += 1
2877       endwhile
2878       if a:mode !=# 'show'
2879         setlocal modifiable noreadonly
2880         exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
2881         call remove(b:fugitive_expanded[info.section], info.filename)
2882         setlocal nomodifiable readonly nomodified
2883       endif
2884       continue
2885     endif
2886     if !has_key(b:fugitive_diff, info.section) || info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
2887       continue
2888     endif
2889     let mode = ''
2890     let diff = []
2891     let index = 0
2892     let start = -1
2893     for line in b:fugitive_diff[info.section]
2894       if mode ==# 'await' && line[0] ==# '@'
2895         let mode = 'capture'
2896       endif
2897       if mode !=# 'head' && line !~# '^[ @\+-]'
2898         if len(diff)
2899           break
2900         endif
2901         let start = index
2902         let mode = 'head'
2903       elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '--- ' . info.relative[-1]
2904         let mode = 'await'
2905       elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '+++ ' . info.relative[0]
2906         let mode = 'await'
2907       elseif mode ==# 'capture'
2908         call add(diff, line)
2909       elseif line[0] ==# '@'
2910         let mode = ''
2911       endif
2912       let index += 1
2913     endfor
2914     if len(diff)
2915       setlocal modifiable noreadonly
2916       silent call append(lnum, diff)
2917       let b:fugitive_expanded[info.section][info.filename] = [start, len(diff)]
2918       setlocal nomodifiable readonly nomodified
2919     endif
2920   endwhile
2921   return lnum
2922 endfunction
2924 function! s:NextExpandedHunk(count) abort
2925   for i in range(a:count)
2926     call s:StageInline('show', line('.'), 1)
2927     call search(s:file_pattern . '\|^@','W')
2928   endfor
2929   return '.'
2930 endfunction
2932 function! s:StageDiff(diff) abort
2933   let lnum = line('.')
2934   let info = s:StageInfo(lnum)
2935   let prefix = info.offset > 0 ? '+' . info.offset : ''
2936   if empty(info.paths) && info.section ==# 'Staged'
2937     return 'Git! diff --no-ext-diff --cached'
2938   elseif empty(info.paths)
2939     return 'Git! diff --no-ext-diff'
2940   elseif len(info.paths) > 1
2941     execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
2942     return a:diff . '! HEAD:'.s:fnameescape(info.paths[1])
2943   elseif info.section ==# 'Staged' && info.sigil ==# '-'
2944     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2945     return a:diff . '! :0:%'
2946   elseif info.section ==# 'Staged'
2947     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2948     return a:diff . '! @:%'
2949   elseif info.sigil ==# '-'
2950     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2951     return a:diff . '! :(top)%'
2952   else
2953     execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
2954     return a:diff . '!'
2955   endif
2956 endfunction
2958 function! s:StageDiffEdit() abort
2959   let info = s:StageInfo(line('.'))
2960   let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
2961   if info.section ==# 'Staged'
2962     return 'Git! diff --no-ext-diff --cached '.s:fnameescape(arg)
2963   elseif info.status ==# '?'
2964     call s:TreeChomp('add', '--intent-to-add', '--', arg)
2965     return s:ReloadStatus()
2966   else
2967     return 'Git! diff --no-ext-diff '.s:fnameescape(arg)
2968   endif
2969 endfunction
2971 function! s:StageApply(info, reverse, extra) abort
2972   if a:info.status ==# 'R'
2973     call s:throw('fugitive: patching renamed file not yet supported')
2974   endif
2975   let cmd = ['apply', '-p0', '--recount'] + a:extra
2976   let info = a:info
2977   let start = info.patch
2978   let end = info.lnum
2979   let lines = getline(start, end)
2980   if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
2981     return -1
2982   endif
2983   while getline(end) =~# '^[-+ ]'
2984     let end += 1
2985     if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
2986       call add(lines, ' ' . getline(end)[1:-1])
2987     endif
2988   endwhile
2989   while start > 0 && getline(start) !~# '^@'
2990     let start -= 1
2991     if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
2992       call insert(lines, ' ' . getline(start)[1:-1])
2993     elseif getline(start) =~# '^@'
2994       call insert(lines, getline(start))
2995     endif
2996   endwhile
2997   if start == 0
2998     throw 'fugitive: cold not find hunk'
2999   elseif getline(start) !~# '^@@ '
3000     throw 'fugitive: cannot apply conflict hunk'
3001   endif
3002   let i = b:fugitive_expanded[info.section][info.filename][0]
3003   let head = []
3004   while get(b:fugitive_diff[info.section], i, '@') !~# '^@'
3005     call add(head, b:fugitive_diff[info.section][i])
3006     let i += 1
3007   endwhile
3008   call extend(lines, head, 'keep')
3009   let temp = tempname()
3010   call writefile(lines, temp)
3011   if a:reverse
3012     call add(cmd, '--reverse')
3013   endif
3014   call extend(cmd, ['--', temp])
3015   let [output, exec_error] = s:ChompError(cmd)
3016   if !exec_error
3017     return 1
3018   endif
3019   call s:throw(output)
3020 endfunction
3022 function! s:StageDelete(lnum1, lnum2, count) abort
3023   let restore = []
3024   let err = ''
3025   try
3026     for info in s:Selection(a:lnum1, a:lnum2)
3027       if empty(info.paths)
3028         continue
3029       endif
3030       if info.status ==# 'D'
3031         let undo = 'Gremove'
3032       elseif info.paths[0] =~# '/$'
3033         let err .= '|echoerr ' . string('fugitive: will not delete directory ' . string(info.relative[0]))
3034         break
3035       else
3036         let undo = 'Gread ' . s:TreeChomp('hash-object', '-w', '--', info.paths[0])[0:10]
3037       endif
3038       if info.patch
3039         call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
3040       elseif info.status ==# '?'
3041         call s:TreeChomp('clean', '-f', '--', info.paths[0])
3042       elseif a:count == 2
3043         call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
3044       elseif a:count == 3
3045         call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
3046       elseif info.status =~# '[ADU]' &&
3047             \ get(b:fugitive_files[info.section ==# 'Staged' ? 'Unstaged' : 'Staged'], info.filename, {'status': ''}).status =~# '[AU]'
3048         call s:TreeChomp('checkout', info.section ==# 'Staged' ? '--ours' : '--theirs', '--', info.paths[0])
3049       elseif info.status ==# 'U'
3050         call s:TreeChomp('rm', '--', info.paths[0])
3051       elseif info.status ==# 'A'
3052         call s:TreeChomp('rm', '-f', '--', info.paths[0])
3053       elseif info.section ==# 'Unstaged'
3054         call s:TreeChomp('checkout', '--', info.paths[0])
3055       else
3056         call s:TreeChomp('checkout', 'HEAD^{}', '--', info.paths[0])
3057       endif
3058       call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|' . undo)
3059     endfor
3060   catch /^fugitive:/
3061     let err .= '|echoerr ' . string(v:exception)
3062   endtry
3063   if empty(restore)
3064     return err[1:-1]
3065   endif
3066   exe s:ReloadStatus()
3067   call s:StageReveal()
3068   if len(restore)
3069     return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
3070   else
3071     return 'checktime|redraw' . err
3072   endif
3073 endfunction
3075 function! s:StageIgnore(lnum1, lnum2, count) abort
3076   let paths = []
3077   for info in s:Selection(a:lnum1, a:lnum2)
3078     call extend(paths, info.relative)
3079   endfor
3080   call map(paths, '"/" . v:val')
3081   exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
3082   let last = line('$')
3083   if last == 1 && empty(getline(1))
3084     call setline(last, paths)
3085   else
3086     call append(last, paths)
3087     exe last + 1
3088   endif
3089   return ''
3090 endfunction
3092 function! s:DoToggleHeadHeader(value) abort
3093   exe 'edit' s:fnameescape(s:Dir())
3094   call search('\C^index$', 'wc')
3095 endfunction
3097 function! s:DoStageUnpushedHeading(heading) abort
3098   let remote = matchstr(a:heading, 'to \zs[^/]\+\ze/')
3099   if empty(remote)
3100     let remote = '.'
3101   endif
3102   let branch = matchstr(a:heading, 'to \%([^/]\+/\)\=\zs\S\+')
3103   call feedkeys(':Gpush ' . remote . ' ' . 'HEAD:' . branch)
3104 endfunction
3106 function! s:DoToggleUnpushedHeading(heading) abort
3107   return s:DoStageUnpushedHeading(a:heading)
3108 endfunction
3110 function! s:DoStageUnpushed(record) abort
3111   let remote = matchstr(a:record.heading, 'to \zs[^/]\+\ze/')
3112   if empty(remote)
3113     let remote = '.'
3114   endif
3115   let branch = matchstr(a:record.heading, 'to \%([^/]\+/\)\=\zs\S\+')
3116   call feedkeys(':Gpush ' . remote . ' ' . a:record.commit . ':' . branch)
3117 endfunction
3119 function! s:DoToggleUnpushed(record) abort
3120   return s:DoStageUnpushed(a:record)
3121 endfunction
3123 function! s:DoUnstageUnpulledHeading(heading) abort
3124   call feedkeys(':Grebase')
3125 endfunction
3127 function! s:DoToggleUnpulledHeading(heading) abort
3128   call s:DoUnstageUnpulledHeading(a:heading)
3129 endfunction
3131 function! s:DoUnstageUnpulled(record) abort
3132   call feedkeys(':Grebase ' . a:record.commit)
3133 endfunction
3135 function! s:DoToggleUnpulled(record) abort
3136   call s:DoUnstageUnpulled(a:record)
3137 endfunction
3139 function! s:DoUnstageUnpushed(record) abort
3140   call feedkeys(':Grebase --autosquash ' . a:record.commit . '^')
3141 endfunction
3143 function! s:DoToggleStagedHeading(...) abort
3144   call s:TreeChomp('reset', '-q')
3145   return 1
3146 endfunction
3148 function! s:DoUnstageStagedHeading(heading) abort
3149   return s:DoToggleStagedHeading(a:heading)
3150 endfunction
3152 function! s:DoToggleUnstagedHeading(...) abort
3153   call s:TreeChomp('add', '-u')
3154   return 1
3155 endfunction
3157 function! s:DoStageUnstagedHeading(heading) abort
3158   return s:DoToggleUnstagedHeading(a:heading)
3159 endfunction
3161 function! s:DoToggleUntrackedHeading(...) abort
3162   call s:TreeChomp('add', '.')
3163   return 1
3164 endfunction
3166 function! s:DoStageUntrackedHeading(heading) abort
3167   return s:DoToggleUntrackedHeading(a:heading)
3168 endfunction
3170 function! s:DoToggleStaged(record) abort
3171   if a:record.patch
3172     return s:StageApply(a:record, 1, ['--cached'])
3173   else
3174     call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3175     return 1
3176   endif
3177 endfunction
3179 function! s:DoUnstageStaged(record) abort
3180   return s:DoToggleStaged(a:record)
3181 endfunction
3183 function! s:DoToggleUnstaged(record) abort
3184   if a:record.patch && a:record.status !=# 'A'
3185     return s:StageApply(a:record, 0, ['--cached'])
3186   else
3187     call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
3188     return 1
3189   endif
3190 endfunction
3192 function! s:DoStageUnstaged(record) abort
3193   return s:DoToggleUnstaged(a:record)
3194 endfunction
3196 function! s:DoUnstageUnstaged(record) abort
3197   if a:record.status ==# 'A'
3198     call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3199     return 1
3200   else
3201     return -1
3202   endif
3203 endfunction
3205 function! s:DoToggleUntracked(record) abort
3206   call s:TreeChomp(['add', '--'] + a:record.paths)
3207   return 1
3208 endfunction
3210 function! s:DoStageUntracked(record) abort
3211   return s:DoToggleUntracked(a:record)
3212 endfunction
3214 function! s:StagePatch(lnum1,lnum2) abort
3215   let add = []
3216   let reset = []
3217   let intend = []
3219   for lnum in range(a:lnum1,a:lnum2)
3220     let info = s:StageInfo(lnum)
3221     if empty(info.paths) && info.section ==# 'Staged'
3222       return 'Git reset --patch'
3223     elseif empty(info.paths) && info.section ==# 'Unstaged'
3224       return 'Git add --patch'
3225     elseif empty(info.paths) && info.section ==# 'Untracked'
3226       return 'Git add --interactive'
3227     elseif empty(info.paths)
3228       continue
3229     endif
3230     execute lnum
3231     if info.section ==# 'Staged'
3232       let reset += info.relative
3233     elseif info.section ==# 'Untracked'
3234       let intend += info.paths
3235     elseif info.status !~# '^D'
3236       let add += info.relative
3237     endif
3238   endfor
3239   try
3240     if !empty(intend)
3241       call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
3242     endif
3243     if !empty(add)
3244       execute "Git add --patch -- ".join(map(add,'s:fnameescape(v:val)'))
3245     endif
3246     if !empty(reset)
3247       execute "Git reset --patch -- ".join(map(reset,'s:fnameescape(v:val)'))
3248     endif
3249   catch /^fugitive:/
3250     return 'echoerr ' . string(v:exception)
3251   endtry
3252   return s:ReloadStatus()
3253 endfunction
3255 " Section: :Gcommit, :Grevert
3257 function! s:CommitInteractive(line1, line2, range, bang, mods, args, patch) abort
3258   let status = s:StatusCommand(a:line1, a:line2, a:range, a:line2, a:bang, a:mods, '', '', [])
3259   let status = len(status) ? status . '|' : ''
3260   if a:patch
3261     return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
3262   else
3263     return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
3264   endif
3265 endfunction
3267 function! s:CommitSubcommand(line1, line2, range, bang, mods, args, ...) abort
3268   let mods = substitute(s:Mods(a:mods), '\C\<tab\>', '-tab', 'g')
3269   let dir = a:0 ? a:1 : s:Dir()
3270   let tree = s:Tree(dir)
3271   let msgfile = fugitive#Find('.git/COMMIT_EDITMSG', dir)
3272   let outfile = tempname()
3273   try
3274     if s:winshell()
3275       let command = 'set GIT_EDITOR=false& '
3276     else
3277       let command = 'env GIT_EDITOR=false '
3278     endif
3279     let argv = a:args
3280     let i = 0
3281     while get(argv, i, '--') !=# '--'
3282       if argv[i] =~# '^-[apzsneiovq].'
3283         call insert(argv, argv[i][0:1])
3284         let argv[i+1] = '-' . argv[i+1][2:-1]
3285       else
3286         let i += 1
3287       endif
3288     endwhile
3289     let command .= s:UserCommand(dir, ['commit'] + argv)
3290     if (&autowrite || &autowriteall) && !a:0
3291       silent! wall
3292     endif
3293     if s:HasOpt(argv, '-i', '--interactive')
3294       return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 0)
3295     elseif s:HasOpt(argv, '-p', '--patch')
3296       return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 1)
3297     else
3298       let [error_string, exec_error] = s:TempCmd(outfile, command)
3299       let errors = split(error_string, "\n")
3300     endif
3301     if !has('gui_running')
3302       redraw!
3303     endif
3304     if !exec_error
3305       echo join(errors, "\n")
3306       if filereadable(outfile)
3307         echo join(readfile(outfile), "\n")
3308       endif
3309       call fugitive#ReloadStatus(dir, 1)
3310       return ''
3311     else
3312       let error = get(errors,-2,get(errors,-1,'!'))
3313       if error =~# 'false''\=\.$'
3314         let i = 0
3315         while get(argv, i, '--') !=# '--'
3316           if argv[i] =~# '^\%(-[eips]\|-[CcFm].\+\|--edit\|--interactive\|--patch\|--signoff\|--reedit-message=.*\|--reuse-message=.*\|--file=.*\|--message=.*\)$'
3317             call remove(argv, i)
3318           elseif argv[i] =~# '^\%(-[CcFm]\|--reedit-message\|--reuse-message\|--file\|--message\)$'
3319             call remove(argv, i, i + 1)
3320           else
3321             if argv[i] =~# '^--cleanup\>'
3322               let cleanup = 1
3323             endif
3324             let i += 1
3325           endif
3326         endwhile
3327         call insert(argv, '--no-signoff', i)
3328         call insert(argv, '--no-interactive', i)
3329         call insert(argv, '--no-edit', i)
3330         if !exists('cleanup')
3331           call insert(argv, '--cleanup=strip')
3332         endif
3333         call extend(argv, ['-F', msgfile], 'keep')
3334         if (bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&modified) || a:line2 == 0
3335           execute mods . 'keepalt edit' s:fnameescape(msgfile)
3336         elseif s:HasOpt(argv, '-v') || mods =~# '\<tab\>'
3337           execute mods . 'keepalt -tabedit' s:fnameescape(msgfile)
3338         else
3339           execute mods . 'keepalt split' s:fnameescape(msgfile)
3340         endif
3341         let b:fugitive_commit_arguments = argv
3342         setlocal bufhidden=wipe filetype=gitcommit
3343         return '1'
3344       elseif empty(errors)
3345         let out = readfile(outfile)
3346         echo get(out, -1, '') =~# 'stash\|\d' ? get(out, -2, '') : get(out, -1, '')
3347         return ''
3348       else
3349         echo join(errors, "\n")
3350         return ''
3351       endif
3352     endif
3353   catch /^fugitive:/
3354     return 'echoerr ' . string(v:exception)
3355   finally
3356     call delete(outfile)
3357   endtry
3358 endfunction
3360 function! s:RevertSubcommand(line1, line2, range, bang, mods, args) abort
3361   let dir = s:Dir()
3362   let no_commit = s:HasOpt(a:args, '-n', '--no-commit', '--no-edit', '--abort', '--continue', '--quit')
3363   let cmd = s:UserCommand(dir, ['revert'] + (no_commit ? [] : ['-n']) + a:args)
3364   let [out, exec_error] = s:SystemError(cmd)
3365   call fugitive#ReloadStatus(dir, 1)
3366   if no_commit || exec_error
3367     return 'echo ' . string(substitute(out, "\n$", '', ''))
3368   endif
3369   return s:CommitSubcommand(a:line1, a:line2, a:range, a:bang, a:mods, [], dir)
3370 endfunction
3372 function! fugitive#CommitComplete(A, L, P) abort
3373   if a:A =~# '^--fixup=\|^--squash='
3374     let commits = s:LinesError(['log', '--pretty=format:%s', '@{upstream}..'])[0]
3375     let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
3376     if pre =~# "'"
3377       call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
3378       call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
3379       return commits
3380     else
3381       return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
3382     endif
3383   else
3384     return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'))
3385   endif
3386   return []
3387 endfunction
3389 function! fugitive#RevertComplete(A, L, P) abort
3390   return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'))
3391 endfunction
3393 function! s:FinishCommit() abort
3394   let buf = +expand('<abuf>')
3395   let args = getbufvar(buf, 'fugitive_commit_arguments')
3396   if !empty(args)
3397     call setbufvar(buf, 'fugitive_commit_arguments', [])
3398     if getbufvar(buf, 'fugitive_commit_rebase')
3399       call setbufvar(buf, 'fugitive_commit_rebase', 0)
3400       let s:rebase_continue = s:Dir(buf)
3401     endif
3402     return s:CommitSubcommand(-1, -1, 0, 0, '', args, s:Dir(buf))
3403   endif
3404   return ''
3405 endfunction
3407 " Section: :Gmerge, :Grebase, :Gpull
3409 function! fugitive#MergeComplete(A, L, P) abort
3410   return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'))
3411 endfunction
3413 function! fugitive#RebaseComplete(A, L, P) abort
3414   return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'))
3415 endfunction
3417 function! fugitive#PullComplete(A, L, P) abort
3418   return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'))
3419 endfunction
3421 function! s:RebaseSequenceAborter() abort
3422   if !exists('s:rebase_sequence_aborter')
3423     let temp = tempname() . '.sh'
3424     call writefile(
3425           \ ['#!/bin/sh',
3426           \ 'echo exec false | cat - "$1" > "$1.fugitive"',
3427           \ 'mv "$1.fugitive" "$1"'],
3428           \ temp)
3429     let s:rebase_sequence_aborter = temp
3430   endif
3431   return s:rebase_sequence_aborter
3432 endfunction
3434 function! fugitive#Cwindow() abort
3435   if &buftype == 'quickfix'
3436     cwindow
3437   else
3438     botright cwindow
3439     if &buftype == 'quickfix'
3440       wincmd p
3441     endif
3442   endif
3443 endfunction
3445 let s:common_efm = ''
3446       \ . '%+Egit:%.%#,'
3447       \ . '%+Eusage:%.%#,'
3448       \ . '%+Eerror:%.%#,'
3449       \ . '%+Efatal:%.%#,'
3450       \ . '%-G%.%#%\e[K%.%#,'
3451       \ . '%-G%.%#%\r%.%\+'
3453 let s:rebase_abbrevs = {
3454       \ 'p': 'pick',
3455       \ 'r': 'reword',
3456       \ 'e': 'edit',
3457       \ 's': 'squash',
3458       \ 'f': 'fixup',
3459       \ 'x': 'exec',
3460       \ 'd': 'drop',
3461       \ 'l': 'label',
3462       \ 't': 'reset',
3463       \ 'm': 'merge',
3464       \ 'b': 'break',
3465       \ }
3467 function! s:RebaseEdit(cmd, dir) abort
3468   let rebase_todo = s:fnameescape(fugitive#Find('.git/rebase-merge/git-rebase-todo', a:dir))
3470   if filereadable(rebase_todo)
3471     let new = readfile(rebase_todo)
3472     let sha_length = 0
3473     let shas = {}
3475     for i in range(len(new))
3476       if new[i] =~# '^\l\+\s\+[0-9a-f]\{5,\}\>'
3477         let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3478         if !sha_length
3479           let sha_length = len(s:TreeChomp(a:dir, 'rev-parse', '--short', sha))
3480         endif
3481         let shortened_sha = strpart(sha, 0, sha_length)
3482         let shas[shortened_sha] = sha
3483         let new[i] = substitute(new[i], sha, shortened_sha, '')
3484       endif
3485     endfor
3486     call writefile(new, rebase_todo)
3487   endif
3488   return a:cmd . ' +setlocal\ bufhidden=wipe\|' . escape('let b:fugitive_rebase_shas = ' . string(shas), ' ') . ' ' . rebase_todo
3489 endfunction
3491 function! s:MergeRebase(cmd, bang, mods, args, ...) abort
3492   let dir = a:0 ? a:1 : s:Dir()
3493   let args = a:args
3494   let mods = s:Mods(a:mods)
3495   if a:cmd =~# '^rebase' && s:HasOpt(args, '-i', '--interactive')
3496     let cmd = fugitive#Prepare(dir, '-c', 'sequence.editor=sh ' . s:RebaseSequenceAborter(), 'rebase') . ' ' . s:shellesc(args)
3497     let out = system(cmd)[0:-2]
3498     for file in ['end', 'msgnum']
3499       let file = fugitive#Find('.git/rebase-merge/' . file, dir)
3500       if !filereadable(file)
3501         return 'echoerr ' . string("fugitive: " . out)
3502       endif
3503       call writefile([readfile(file)[0] - 1], file)
3504     endfor
3505     call writefile([], fugitive#Find('.git/rebase-merge/done', dir))
3506     if a:bang
3507       return 'exe'
3508     endif
3509     return s:RebaseEdit(mods . 'split', dir)
3510   elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--edit-todo') && filereadable(fugitive#Find('.git/rebase-merge/git-rebase-todo', dir))
3511     return s:RebaseEdit(mods . 'split', dir)
3512   elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--continue') && !a:0
3513     let rdir = fugitive#Find('.git/rebase-merge', dir)
3514     let exec_error = s:ChompError([dir, 'diff-index', '--cached', '--quiet', 'HEAD', '--'])[1]
3515     if exec_error && isdirectory(rdir)
3516       if getfsize(rdir . '/amend') <= 0
3517         return 'exe ' . string(mods . 'Gcommit -n -F ' . s:fnameescape(rdir .'/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3518       elseif readfile(rdir . '/amend')[0] ==# fugitive#Head(-1, dir)
3519         return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(rdir . '/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3520       endif
3521     endif
3522   endif
3523   let had_merge_msg = filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3524   let argv = []
3525   if a:cmd ==# 'pull'
3526     let argv += s:AskPassArgs(dir) + ['pull', '--progress']
3527   else
3528     call add(argv, a:cmd)
3529   endif
3530   if !s:HasOpt(args, '--no-edit', '--abort', '-m') && a:cmd !=# 'rebase'
3531     call add(argv, '--edit')
3532   endif
3533   if a:cmd ==# 'rebase' && s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '--interactive', '-i')
3534     call add(argv, '--interactive')
3535   endif
3536   call extend(argv, args)
3538   let [mp, efm] = [&l:mp, &l:efm]
3539   try
3540     let cdback = s:Cd(s:Tree(dir))
3541     let &l:errorformat = ''
3542           \ . '%-Gerror:%.%#false''.,'
3543           \ . '%-G%.%# ''git commit'' %.%#,'
3544           \ . '%+Emerge:%.%#,'
3545           \ . s:common_efm . ','
3546           \ . '%+ECannot %.%#: You have unstaged changes.,'
3547           \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
3548           \ . '%+EThere is no tracking information for the current branch.,'
3549           \ . '%+EYou are not currently on a branch. Please specify which,'
3550           \ . '%+I %#git rebase --continue,'
3551           \ . 'CONFLICT (%m): %f deleted in %.%#,'
3552           \ . 'CONFLICT (%m): Merge conflict in %f,'
3553           \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
3554           \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
3555           \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
3556           \ . '%+ECONFLICT %.%#,'
3557           \ . '%+EKONFLIKT %.%#,'
3558           \ . '%+ECONFLIT %.%#,'
3559           \ . "%+EXUNG \u0110\u1ed8T %.%#,"
3560           \ . "%+E\u51b2\u7a81 %.%#,"
3561           \ . 'U%\t%f'
3562     if a:cmd =~# '^merge' && empty(args) &&
3563           \ (had_merge_msg || isdirectory(fugitive#Find('.git/rebase-apply', dir)) ||
3564           \  !empty(s:TreeChomp(dir, 'diff-files', '--diff-filter=U')))
3565       let cmd = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
3566     else
3567       let cmd = s:UserCommand(dir, argv)
3568     endif
3569     if !empty($GIT_SEQUENCE_EDITOR) || has('win32')
3570       let old_sequence_editor = $GIT_SEQUENCE_EDITOR
3571       let $GIT_SEQUENCE_EDITOR = 'true'
3572     else
3573       let cmd = 'env GIT_SEQUENCE_EDITOR=true ' . cmd
3574     endif
3575     if !empty($GIT_EDITOR) || has('win32')
3576       let old_editor = $GIT_EDITOR
3577       let $GIT_EDITOR = 'false'
3578     else
3579       let cmd = 'env GIT_EDITOR=false ' . substitute(cmd, '^env ', '', '')
3580     endif
3581     if !has('patch-8.1.0334') && has('terminal') && &autowrite
3582       let autowrite_was_set = 1
3583       set noautowrite
3584       silent! wall
3585     endif
3586     let &l:makeprg = cmd
3587     silent noautocmd make!
3588   catch /^Vim\%((\a\+)\)\=:E211/
3589     let err = v:exception
3590   finally
3591     if exists('autowrite_was_set')
3592       set autowrite
3593     endif
3594     redraw!
3595     let [&l:mp, &l:efm] = [mp, efm]
3596     if exists('old_editor')
3597       let $GIT_EDITOR = old_editor
3598     endif
3599     if exists('old_sequence_editor')
3600       let $GIT_SEQUENCE_EDITOR = old_sequence_editor
3601     endif
3602     execute cdback
3603   endtry
3604   call fugitive#ReloadStatus(dir, 1)
3605   if empty(filter(getqflist(),'v:val.valid && v:val.type !=# "I"'))
3606     if a:cmd =~# '^rebase' &&
3607           \ filereadable(fugitive#Find('.git/rebase-merge/amend', dir)) &&
3608           \ filereadable(fugitive#Find('.git/rebase-merge/done', dir)) &&
3609           \ get(readfile(fugitive#Find('.git/rebase-merge/done', dir)), -1, '') =~# '^[^e]'
3610       cclose
3611       return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(fugitive#Find('.git/rebase-merge/message', dir)) . ' -e') . '|let b:fugitive_commit_rebase = 1'
3612     elseif !had_merge_msg && filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3613       cclose
3614       return mods . 'Gcommit --no-status -n -t '.s:fnameescape(fugitive#Find('.git/MERGE_MSG', dir))
3615     endif
3616   endif
3617   let qflist = getqflist()
3618   let found = 0
3619   for e in qflist
3620     if !empty(e.bufnr)
3621       let found = 1
3622       let e.pattern = '^<<<<<<<'
3623     endif
3624   endfor
3625   call fugitive#Cwindow()
3626   if found
3627     call setqflist(qflist, 'r')
3628     if !a:bang
3629       call s:BlurStatus()
3630       return 'cfirst'
3631     endif
3632   endif
3633   return exists('err') ? 'echoerr '.string(err) : 'exe'
3634 endfunction
3636 function! s:RebaseClean(file) abort
3637   if !filereadable(a:file)
3638     return ''
3639   endif
3640   let old = readfile(a:file)
3641   let new = copy(old)
3642   for i in range(len(new))
3643     let new[i] = substitute(new[i], '^\l\>', '\=get(s:rebase_abbrevs,submatch(0),submatch(0))', '')
3645     let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3646     let rebase_shas = getbufvar(a:file, 'fugitive_rebase_shas')
3647     if len(sha) && type(rebase_shas) == type({}) && has_key(rebase_shas, sha)
3648       let new[i] = substitute(new[i], '\C\<' . sha . '\>', rebase_shas[sha], '')
3649     endif
3650   endfor
3651   if new !=# old
3652     call writefile(new, a:file)
3653   endif
3654   return ''
3655 endfunction
3657 function! s:MergeSubcommand(line1, line2, range, bang, mods, args) abort
3658   return s:MergeRebase('merge', a:bang, a:mods, a:args)
3659 endfunction
3661 function! s:RebaseSubcommand(line1, line2, range, bang, mods, args) abort
3662   return s:MergeRebase('rebase', a:bang, a:mods, a:args)
3663 endfunction
3665 function! s:PullSubcommand(line1, line2, range, bang, mods, args) abort
3666   return s:MergeRebase('pull', a:bang, a:mods, a:args)
3667 endfunction
3669 augroup fugitive_merge
3670   autocmd!
3671   autocmd VimLeavePre,BufDelete git-rebase-todo
3672         \ if getbufvar(+expand('<abuf>'), '&bufhidden') ==# 'wipe' |
3673         \   call s:RebaseClean(expand('<afile>')) |
3674         \   if getfsize(FugitiveFind('.git/rebase-merge/done', +expand('<abuf>'))) == 0 |
3675         \     let s:rebase_continue = FugitiveGitDir(+expand('<abuf>')) |
3676         \   endif |
3677         \ endif
3678   autocmd BufEnter * nested
3679         \ if exists('s:rebase_continue') |
3680         \   exe s:MergeRebase('rebase', 0, '', [getfsize(fugitive#Find('.git/rebase-merge/git-rebase-todo', s:rebase_continue)) > 0 ? '--continue' : '--abort'], remove(s:, 'rebase_continue')) |
3681         \ endif
3682 augroup END
3684 " Section: :Ggrep, :Glog
3686 if !exists('g:fugitive_summary_format')
3687   let g:fugitive_summary_format = '%s'
3688 endif
3690 function! fugitive#GrepComplete(A, L, P) abort
3691   return s:CompleteSub('grep', a:A, a:L, a:P)
3692 endfunction
3694 function! fugitive#LogComplete(A, L, P) abort
3695   return s:CompleteSub('log', a:A, a:L, a:P)
3696 endfunction
3698 function! s:GrepParseLine(prefix, name_only, dir, line) abort
3699   let entry = {'valid': 1}
3700   let match = matchlist(a:line, '^\(.\{-\}\):\(\d\+\):\(\d\+:\)\=\(.*\)$')
3701   if len(match)
3702     let entry.module = match[1]
3703     let entry.lnum = +match[2]
3704     let entry.col = +match[3]
3705     let entry.text = match[4]
3706   elseif a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
3707     return {'text': a:line}
3708   else
3709     let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
3710     if len(entry.module)
3711       let entry.text = 'Binary file'
3712       let entry.valid = 0
3713     endif
3714   endif
3715   if empty(entry.module) && a:name_only
3716     let entry.module = a:line
3717   endif
3718   if empty(entry.module)
3719     return {'text': a:line}
3720   endif
3721   if entry.module !~# ':'
3722     let entry.filename = a:prefix . entry.module
3723   else
3724     let entry.filename = fugitive#Find(entry.module, a:dir)
3725   endif
3726   return entry
3727 endfunction
3729 function! s:GrepSubcommand(line1, line2, range, bang, mods, args) abort
3730   let dir = s:Dir()
3731   exe s:DirCheck(dir)
3732   let listnr = a:line1 == 0 ? a:line1 : a:line2
3733   let cmd = ['--no-pager', 'grep', '-n', '--no-color', '--full-name']
3734   if fugitive#GitVersion(2, 19)
3735     call add(cmd, '--column')
3736   endif
3737   let tree = s:Tree(dir)
3738   if type(a:args) == type([])
3739     let [args, after] = [a:args, '']
3740   else
3741     let [args, after] = s:SplitExpandChain(a:args, tree)
3742   endif
3743   let prefix = FugitiveVimPath(s:HasOpt(args, '--cached') || empty(tree) ? 'fugitive://' . dir . '//0/' : tree . '/')
3744   let name_only = s:HasOpt(args, '-l', '--files-with-matches', '--name-only', '-L', '--files-without-match')
3745   let title = [listnr < 0 ? ':Ggrep' : ':Glgrep'] + args
3746   if listnr > 0
3747     exe listnr 'wincmd w'
3748   else
3749     call s:BlurStatus()
3750   endif
3751   redraw
3752   call s:QuickfixCreate(listnr, {'title': (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)})
3753   let tempfile = tempname()
3754   let event = listnr < 0 ? 'grep-fugitive' : 'lgrep-fugitive'
3755   silent exe s:DoAutocmd('QuickFixCmdPre ' . event)
3756   exe '!' . escape(s:UserCommand(dir, cmd + args), '%#!')
3757         \ printf(&shellpipe . (&shellpipe =~# '%s' ? '' : ' %s'), s:shellesc(tempfile))
3758   let list = map(readfile(tempfile), 's:GrepParseLine(prefix, name_only, dir, v:val)')
3759   call s:QuickfixSet(listnr, list, 'a')
3760   silent exe s:DoAutocmd('QuickFixCmdPost ' . event)
3761   if !has('gui_running')
3762     redraw
3763   endif
3764   if !a:bang && !empty(list)
3765     return (listnr < 0 ? 'c' : 'l').'first' . after
3766   else
3767     return after[1:-1]
3768   endif
3769 endfunction
3771 function! s:LogFlushQueue(state) abort
3772   let queue = remove(a:state, 'queue')
3773   if a:state.child_found
3774     call remove(queue, 0)
3775   endif
3776   if len(queue) && queue[-1] ==# {'text': ''}
3777     call remove(queue, -1)
3778   endif
3779   return queue
3780 endfunction
3782 function! s:LogParse(state, dir, line) abort
3783   if a:state.context ==# 'hunk' && a:line =~# '^[-+ ]'
3784     return []
3785   endif
3786   let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
3787   if len(list)
3788     let a:state.context = 'commit'
3789     let a:state.base = 'fugitive://' . a:dir . '//' . list[2]
3790     let a:state.base_module = len(list[1]) ? list[1] : list[2]
3791     let a:state.message = list[3]
3792     if has_key(a:state, 'diffing')
3793       call remove(a:state, 'diffing')
3794     endif
3795     let queue = s:LogFlushQueue(a:state)
3796     let a:state.queue = [{
3797           \ 'valid': 1,
3798           \ 'filename': a:state.base . a:state.target,
3799           \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
3800           \ 'text': a:state.message}]
3801     let a:state.child_found = 0
3802     return queue
3803   elseif type(a:line) == type(0)
3804     return s:LogFlushQueue(a:state)
3805   elseif a:line =~# '^diff'
3806     let a:state.context = 'diffhead'
3807   elseif a:line =~# '^[+-]\{3\} \w/' && a:state.context ==# 'diffhead'
3808     let a:state.diffing = a:line[5:-1]
3809   elseif a:line =~# '^@@[^@]*+\d' && has_key(a:state, 'diffing') && has_key(a:state, 'base')
3810     let a:state.context = 'hunk'
3811     if empty(a:state.target) || a:state.target ==# a:state.diffing
3812       let a:state.child_found = 1
3813       call add(a:state.queue, {
3814             \ 'valid': 1,
3815             \ 'filename': a:state.base . a:state.diffing,
3816             \ 'module': a:state.base_module . substitute(a:state.diffing, '^/', ':', ''),
3817             \ 'lnum': +matchstr(a:line, '+\zs\d\+'),
3818             \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
3819     endif
3820   elseif a:state.follow &&
3821         \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
3822     let rename = matchstr(a:line, '^ rename \zs.* => .*\ze (\d\+%)$')
3823     if len(rename)
3824       let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
3825       if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
3826         let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
3827       endif
3828     endif
3829     if !get(a:state, 'ignore_summary')
3830       call add(a:state.queue, {'text': a:line})
3831     endif
3832   elseif a:state.context ==# 'commit' || a:state.context ==# 'init'
3833     call add(a:state.queue, {'text': a:line})
3834   endif
3835   return []
3836 endfunction
3838 function! fugitive#LogCommand(line1, count, range, bang, mods, args, type) abort
3839   let dir = s:Dir()
3840   exe s:DirCheck(dir)
3841   let listnr = a:type =~# '^l' ? 0 : -1
3842   let [args, after] = s:SplitExpandChain(a:args, s:Tree(dir))
3843   let split = index(args, '--')
3844   if split > 0
3845     let paths = args[split : -1]
3846     let args = args[0 : split - 1]
3847   elseif split == 0
3848     let paths = args
3849     let args = []
3850   else
3851     let paths = []
3852   endif
3853   if a:line1 == 0 && a:count
3854     let path = fugitive#Path(bufname(a:count), '/', dir)
3855   elseif a:count >= 0
3856     let path = fugitive#Path(@%, '/', dir)
3857   else
3858      let path = ''
3859   endif
3860   let range = ''
3861   let extra = []
3862   let state = {'context': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
3863   if path =~# '^/\.git\%(/\|$\)\|^$'
3864     let path = ''
3865   elseif a:line1 == 0
3866     let range = "0," . (a:count ? a:count : bufnr(''))
3867     let extra = ['.' . path]
3868     if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
3869       let state.follow = 1
3870       if !s:HasOpt(args, '--follow')
3871         call insert(args, '--follow')
3872       endif
3873       if !s:HasOpt(args, '--summary')
3874         call insert(args, '--summary')
3875         let state.ignore_summary = 1
3876       endif
3877     endif
3878   elseif a:count > 0
3879     if !s:HasOpt(args, '--merges', '--no-merges')
3880       call insert(args, '--no-merges')
3881     endif
3882     call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
3883   endif
3884   if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
3885     let owner = s:Owner(@%, dir)
3886     if len(owner)
3887       call add(args, owner)
3888     endif
3889   endif
3890   if empty(extra)
3891     let path = ''
3892   endif
3893   if s:HasOpt(args, '-g', '--walk-reflogs')
3894     let format = "%gd\t%H %gs"
3895   else
3896     let format = "%h\t%H " . g:fugitive_summary_format
3897   endif
3898   let cmd = ['--no-pager']
3899   if fugitive#GitVersion(1, 9)
3900     call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'])
3901   else
3902     call extend(cmd, ['log', '-U0', '--no-patch'])
3903   endif
3904   call extend(cmd,
3905         \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
3906         \ args + paths + extra)
3907   let state.target = path
3908   let title = (listnr < 0 ? ':Gclog ' : ':Gllog ') . s:fnameescape(args + paths)
3909   if empty(paths + extra) && empty(a:type) && len(s:Relative('/'))
3910     let after = '|echohl WarningMsg|echo ' . string('Use :0Glog or :0Gclog for old behavior of targeting current file') . '|echohl NONE' . after
3911   endif
3912   return s:QuickfixStream(listnr, title, s:UserCommandList(dir) + cmd, !a:bang, s:function('s:LogParse'), state, dir) . after
3913 endfunction
3915 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
3917 function! s:UsableWin(nr) abort
3918   return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
3919         \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
3920         \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
3921         \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
3922 endfunction
3924 function! s:OpenParse(args, wants_cmd) abort
3925   let opts = []
3926   let cmds = []
3927   let args = copy(a:args)
3928   while !empty(args)
3929     if args[0] =~# '^++'
3930       call add(opts, ' ' . escape(remove(args, 0), ' |"'))
3931     elseif a:wants_cmd && args[0] =~# '^+'
3932       call add(cmds, remove(args, 0)[1:-1])
3933     else
3934       break
3935     endif
3936   endwhile
3937   if len(args)
3938     let file = join(args)
3939   elseif empty(expand('%'))
3940     let file = ''
3941   elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
3942     let file = '>:0'
3943   else
3944     let file = '>'
3945   endif
3946   let dir = s:Dir()
3947   let efile = s:Expand(file)
3948   let url = fugitive#Find(efile, dir)
3950   if a:wants_cmd && file[0] ==# '>' && efile[0] !=# '>' && get(b:, 'fugitive_type', '') isnot# 'tree' && &filetype !=# 'netrw'
3951     let line = line('.')
3952     if expand('%:p') !=# url
3953       let diffcmd = 'diff'
3954       let from = s:DirRev(@%)[1]
3955       let to = s:DirRev(url)[1]
3956       if empty(from) && empty(to)
3957         let diffcmd = 'diff-files'
3958         let args = ['--', expand('%:p'), url]
3959       elseif empty(to)
3960         let args = [from, '--', url]
3961       elseif empty(from)
3962         let args = [to, '--', expand('%:p')]
3963         let reverse = 1
3964       else
3965         let args = [from, to]
3966       endif
3967       let [res, exec_error] = s:LinesError([dir, diffcmd, '-U0'] + args)
3968       if !exec_error
3969         call filter(res, 'v:val =~# "^@@ "')
3970         call map(res, 'substitute(v:val, ''[-+]\d\+\zs '', ",1 ", "g")')
3971         call map(res, 'matchlist(v:val, ''^@@ -\(\d\+\),\(\d\+\) +\(\d\+\),\(\d\+\) @@'')[1:4]')
3972         if exists('reverse')
3973           call map(res, 'v:val[2:3] + v:val[0:1]')
3974         endif
3975         call filter(res, 'v:val[0] < '.line('.'))
3976         let hunk = get(res, -1, [0,0,0,0])
3977         if hunk[0] + hunk[1] > line('.')
3978           let line = hunk[2] + max([1 - hunk[3], 0])
3979         else
3980           let line = hunk[2] + max([hunk[3], 1]) + line('.') - hunk[0] - max([hunk[1], 1])
3981         endif
3982       endif
3983     endif
3984     call insert(cmds, line)
3985   endif
3987   let pre = join(opts, '')
3988   if len(cmds) > 1
3989     let pre .= ' +' . escape(join(map(cmds, '"exe ".string(v:val)'), '|'), ' |"')
3990   elseif len(cmds)
3991     let pre .= ' +' . escape(cmds[0], ' |"')
3992   endif
3993   return [url, pre]
3994 endfunction
3996 function! s:DiffClose() abort
3997   let mywinnr = winnr()
3998   for winnr in [winnr('#')] + range(winnr('$'),1,-1)
3999     if winnr != mywinnr && getwinvar(winnr,'&diff')
4000       execute winnr.'wincmd w'
4001       close
4002       if winnr('$') > 1
4003         wincmd p
4004       endif
4005     endif
4006   endfor
4007   diffoff!
4008 endfunction
4010 function! s:BlurStatus() abort
4011   if (&previewwindow || exists('w:fugitive_status')) && get(b:,'fugitive_type', '') ==# 'index'
4012     let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
4013     if len(winnrs)
4014       exe winnrs[0].'wincmd w'
4015     else
4016       belowright new
4017     endif
4018     if &diff
4019       call s:DiffClose()
4020     endif
4021   endif
4022 endfunction
4024 function! s:OpenExec(cmd, mods, args, ...) abort
4025   let dir = a:0 ? s:Dir(a:1) : s:Dir()
4026   let temp = tempname()
4027   let columns = get(g:, 'fugitive_columns', 80)
4028   if columns <= 0
4029     let env = ''
4030   elseif s:winshell()
4031     let env = 'set COLUMNS=' . columns . '& '
4032   else
4033     let env = 'env COLUMNS=' . columns . ' '
4034   endif
4035   silent! execute '!' . escape(env . s:UserCommand(dir, ['--no-pager'] + a:args), '!#%') .
4036         \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
4037   redraw!
4038   let temp = s:Resolve(temp)
4039   let first = join(readfile(temp, '', 2), "\n")
4040   if first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
4041     let filetype = 'man'
4042   else
4043     let filetype = 'git'
4044   endif
4045   let s:temp_files[s:cpath(temp)] = { 'dir': dir, 'filetype': filetype, 'modifiable': first =~# '^diff ' }
4046   if a:cmd ==# 'edit'
4047     call s:BlurStatus()
4048   endif
4049   silent execute s:Mods(a:mods) . a:cmd temp
4050   call fugitive#ReloadStatus(dir, 1)
4051   return 'echo ' . string(':!' . s:UserCommand(dir, a:args))
4052 endfunction
4054 function! fugitive#Open(cmd, bang, mods, arg, args) abort
4055   if a:bang
4056     return s:OpenExec(a:cmd, a:mods, s:SplitExpand(a:arg, s:Tree()))
4057   endif
4059   let mods = s:Mods(a:mods)
4060   try
4061     let [file, pre] = s:OpenParse(a:args, 1)
4062   catch /^fugitive:/
4063     return 'echoerr ' . string(v:exception)
4064   endtry
4065   if file !~# '^\a\a\+:'
4066     let file = s:sub(file, '/$', '')
4067   endif
4068   if a:cmd ==# 'edit'
4069     call s:BlurStatus()
4070   endif
4071   return mods . a:cmd . pre . ' ' . s:fnameescape(file)
4072 endfunction
4074 function! fugitive#ReadCommand(line1, count, range, bang, mods, arg, args) abort
4075   let mods = s:Mods(a:mods)
4076   let after = a:count
4077   if a:count < 0
4078     let delete = 'silent 1,' . line('$') . 'delete_|'
4079     let after = line('$')
4080   elseif a:range == 2
4081     let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
4082   else
4083     let delete = ''
4084   endif
4085   if a:bang
4086     let dir = s:Dir()
4087     let args = s:SplitExpand(a:arg, s:Tree(dir))
4088     silent execute mods . after . 'read!' escape(s:UserCommand(dir, ['--no-pager'] + args), '!#%')
4089     execute delete . 'diffupdate'
4090     call fugitive#ReloadStatus(dir, 1)
4091     return 'redraw|echo '.string(':!'.s:UserCommand(dir, args))
4092   endif
4093   try
4094     let [file, pre] = s:OpenParse(a:args, 0)
4095   catch /^fugitive:/
4096     return 'echoerr ' . string(v:exception)
4097   endtry
4098   if file =~# '^fugitive:' && after is# 0
4099     return 'exe ' .string(mods . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
4100   endif
4101   if foldlevel(after)
4102     exe after . 'foldopen!'
4103   endif
4104   return mods . after . 'read' . pre . ' ' . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
4105 endfunction
4107 function! fugitive#ReadComplete(A, L, P) abort
4108   if a:L =~# '^\w\+!'
4109     return fugitive#Complete(a:A, a:L, a:P)
4110   else
4111     return fugitive#CompleteObject(a:A, a:L, a:P)
4112   endif
4113 endfunction
4115 " Section: :Gwrite, :Gwq
4117 function! fugitive#WriteCommand(line1, line2, range, bang, mods, arg, args) abort
4118   if exists('b:fugitive_commit_arguments')
4119     return 'write|bdelete'
4120   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
4121     return 'wq'
4122   elseif get(b:, 'fugitive_type', '') ==# 'index'
4123     return 'Gcommit'
4124   elseif &buftype ==# 'nowrite' && getline(4) =~# '^+++ '
4125     let filename = getline(4)[6:-1]
4126     setlocal buftype=
4127     silent write
4128     setlocal buftype=nowrite
4129     if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
4130       let [message, exec_error] = s:ChompError(['apply', '--cached', '--reverse', '--', expand('%:p')])
4131     else
4132       let [message, exec_error] = s:ChompError(['apply', '--cached', '--', expand('%:p')])
4133     endif
4134     if exec_error
4135       echohl ErrorMsg
4136       echo message
4137       echohl NONE
4138       return ''
4139     elseif a:bang
4140       return 'bdelete'
4141     else
4142       return 'Gedit '.fnameescape(filename)
4143     endif
4144   endif
4145   let mytab = tabpagenr()
4146   let mybufnr = bufnr('')
4147   try
4148     let file = len(a:args) ? s:Generate(s:Expand(join(a:args, ' '))) : fugitive#Real(@%)
4149   catch /^fugitive:/
4150     return 'echoerr ' . string(v:exception)
4151   endtry
4152   if empty(file)
4153     return 'echoerr '.string('fugitive: cannot determine file path')
4154   endif
4155   if file =~# '^fugitive:'
4156     return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
4157   endif
4158   exe s:DirCheck()
4159   let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
4160   if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
4161     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
4162     return 'echoerr v:errmsg'
4163   endif
4164   let treebufnr = 0
4165   for nr in range(1,bufnr('$'))
4166     if fnamemodify(bufname(nr),':p') ==# file
4167       let treebufnr = nr
4168     endif
4169   endfor
4171   if treebufnr > 0 && treebufnr != bufnr('')
4172     let temp = tempname()
4173     silent execute 'keepalt %write '.temp
4174     for tab in [mytab] + range(1,tabpagenr('$'))
4175       for winnr in range(1,tabpagewinnr(tab,'$'))
4176         if tabpagebuflist(tab)[winnr-1] == treebufnr
4177           execute 'tabnext '.tab
4178           if winnr != winnr()
4179             execute winnr.'wincmd w'
4180             let restorewinnr = 1
4181           endif
4182           try
4183             let lnum = line('.')
4184             let last = line('$')
4185             silent execute '$read '.temp
4186             silent execute '1,'.last.'delete_'
4187             silent write!
4188             silent execute lnum
4189             diffupdate
4190             let did = 1
4191           finally
4192             if exists('restorewinnr')
4193               wincmd p
4194             endif
4195             execute 'tabnext '.mytab
4196           endtry
4197           break
4198         endif
4199       endfor
4200     endfor
4201     if !exists('did')
4202       call writefile(readfile(temp,'b'),file,'b')
4203     endif
4204   else
4205     execute 'write! '.s:fnameescape(file)
4206   endif
4208   if a:bang
4209     let [error, exec_error] = s:ChompError(['add', '--force', '--', file])
4210   else
4211     let [error, exec_error] = s:ChompError(['add', '--', file])
4212   endif
4213   if exec_error
4214     let v:errmsg = 'fugitive: '.error
4215     return 'echoerr v:errmsg'
4216   endif
4217   if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
4218     setlocal nomodified
4219   endif
4221   let one = s:Generate(':1:'.file)
4222   let two = s:Generate(':2:'.file)
4223   let three = s:Generate(':3:'.file)
4224   for nr in range(1,bufnr('$'))
4225     let name = fnamemodify(bufname(nr), ':p')
4226     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
4227       execute nr.'bdelete'
4228     endif
4229   endfor
4231   unlet! restorewinnr
4232   let zero = s:Generate(':0:'.file)
4233   silent exe s:DoAutocmd('BufWritePost ' . s:fnameescape(zero))
4234   for tab in range(1,tabpagenr('$'))
4235     for winnr in range(1,tabpagewinnr(tab,'$'))
4236       let bufnr = tabpagebuflist(tab)[winnr-1]
4237       let bufname = fnamemodify(bufname(bufnr), ':p')
4238       if bufname ==# zero && bufnr != mybufnr
4239         execute 'tabnext '.tab
4240         if winnr != winnr()
4241           execute winnr.'wincmd w'
4242           let restorewinnr = 1
4243         endif
4244         try
4245           let lnum = line('.')
4246           let last = line('$')
4247           silent execute '$read '.s:fnameescape(file)
4248           silent execute '1,'.last.'delete_'
4249           silent execute lnum
4250           setlocal nomodified
4251           diffupdate
4252         finally
4253           if exists('restorewinnr')
4254             wincmd p
4255           endif
4256           execute 'tabnext '.mytab
4257         endtry
4258         break
4259       endif
4260     endfor
4261   endfor
4262   call fugitive#ReloadStatus(-1, 1)
4263   return 'checktime'
4264 endfunction
4266 function! fugitive#WqCommand(...) abort
4267   let bang = a:4 ? '!' : ''
4268   if exists('b:fugitive_commit_arguments')
4269     return 'wq'.bang
4270   endif
4271   let result = call('fugitive#WriteCommand', a:000)
4272   if result =~# '^\%(write\|wq\|echoerr\)'
4273     return s:sub(result,'^write','wq')
4274   else
4275     return result.'|quit'.bang
4276   endif
4277 endfunction
4279 augroup fugitive_commit
4280   autocmd!
4281   autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute substitute(s:FinishCommit(), '\C^echoerr \(''[^'']*''\)*', 'redraw|echohl ErrorMsg|echo \1|echohl NONE', '')
4282 augroup END
4284 " Section: :Gpush, :Gfetch
4286 function! fugitive#PushComplete(A, L, P) abort
4287   return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompleteRemote'))
4288 endfunction
4290 function! fugitive#FetchComplete(A, L, P) abort
4291   return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'))
4292 endfunction
4294 function! s:AskPassArgs(dir) abort
4295   if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) && fugitive#GitVersion(1, 8) &&
4296         \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#Config('core.askPass', a:dir))
4297     if s:executable(s:ExecPath() . '/git-gui--askpass')
4298       return ['-c', 'core.askPass=' . s:ExecPath() . '/git-gui--askpass']
4299     elseif s:executable('ssh-askpass')
4300       return ['-c', 'core.askPass=ssh-askpass']
4301     endif
4302   endif
4303   return []
4304 endfunction
4306 function! s:Dispatch(bang, cmd, args) abort
4307   let dir = s:Dir()
4308   let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
4309   try
4310     let b:current_compiler = 'git'
4311     let &l:errorformat = s:common_efm .
4312           \ ',%\&git_dir=' . escape(substitute(dir, '%', '%%', 'g'), '\,')
4313     let &l:makeprg = s:UserCommand(dir, s:AskPassArgs(dir) + [a:cmd] + a:args)
4314     if exists(':Make') == 2
4315       Make
4316       return ''
4317     else
4318       if !has('patch-8.1.0334') && has('terminal') && &autowrite
4319         let autowrite_was_set = 1
4320         set noautowrite
4321         silent! wall
4322       endif
4323       silent noautocmd make!
4324       redraw!
4325       return 'call fugitive#Cwindow()|silent ' . s:DoAutocmd('ShellCmdPost')
4326     endif
4327   finally
4328     let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
4329     if empty(cc) | unlet! b:current_compiler | endif
4330     if exists('autowrite_was_set')
4331       set autowrite
4332     endif
4333   endtry
4334 endfunction
4336 function! s:PushSubcommand(line1, line2, range, bang, mods, args) abort
4337   return s:Dispatch(a:bang ? '!' : '', 'push', a:args)
4338 endfunction
4340 function! s:FetchSubcommand(line1, line2, range, bang, mods, args) abort
4341   return s:Dispatch(a:bang ? '!' : '', 'fetch', a:args)
4342 endfunction
4344 " Section: :Gdiff
4346 augroup fugitive_diff
4347   autocmd!
4348   autocmd BufWinLeave *
4349         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
4350         \   call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
4351         \ endif
4352   autocmd BufWinEnter *
4353         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
4354         \   call s:diffoff() |
4355         \ endif
4356 augroup END
4358 function! s:can_diffoff(buf) abort
4359   return getwinvar(bufwinnr(a:buf), '&diff') &&
4360         \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
4361 endfunction
4363 function! fugitive#CanDiffoff(buf) abort
4364   return s:can_diffoff(bufnr(a:buf))
4365 endfunction
4367 function! s:diff_modifier(count) abort
4368   let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
4369   if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
4370     return ''
4371   elseif &diffopt =~# 'vertical'
4372     return 'vertical '
4373   elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
4374     return ''
4375   else
4376     return 'vertical '
4377   endif
4378 endfunction
4380 function! s:diff_window_count() abort
4381   let c = 0
4382   for nr in range(1,winnr('$'))
4383     let c += getwinvar(nr,'&diff')
4384   endfor
4385   return c
4386 endfunction
4388 function! s:diff_restore() abort
4389   let restore = 'setlocal nodiff noscrollbind'
4390         \ . ' scrollopt=' . &l:scrollopt
4391         \ . (&l:wrap ? ' wrap' : ' nowrap')
4392         \ . ' foldlevel=999'
4393         \ . ' foldmethod=' . &l:foldmethod
4394         \ . ' foldcolumn=' . &l:foldcolumn
4395         \ . ' foldlevel=' . &l:foldlevel
4396         \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
4397   if has('cursorbind')
4398     let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
4399   endif
4400   return restore
4401 endfunction
4403 function! s:diffthis() abort
4404   if !&diff
4405     let w:fugitive_diff_restore = s:diff_restore()
4406     diffthis
4407   endif
4408 endfunction
4410 function! s:diffoff() abort
4411   if exists('w:fugitive_diff_restore')
4412     execute w:fugitive_diff_restore
4413     unlet w:fugitive_diff_restore
4414   else
4415     diffoff
4416   endif
4417 endfunction
4419 function! s:diffoff_all(dir) abort
4420   let curwin = winnr()
4421   for nr in range(1,winnr('$'))
4422     if getwinvar(nr,'&diff')
4423       if nr != winnr()
4424         execute nr.'wincmd w'
4425         let restorewinnr = 1
4426       endif
4427       if s:Dir() ==# a:dir
4428         call s:diffoff()
4429       endif
4430     endif
4431   endfor
4432   execute curwin.'wincmd w'
4433 endfunction
4435 function! s:CompareAge(mine, theirs) abort
4436   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
4437   let mine = substitute(a:mine, '^:', '', '')
4438   let theirs = substitute(a:theirs, '^:', '', '')
4439   let my_score    = get(scores, ':'.mine, 0)
4440   let their_score = get(scores, ':'.theirs, 0)
4441   if my_score || their_score
4442     return my_score < their_score ? -1 : my_score != their_score
4443   elseif mine ==# theirs
4444     return 0
4445   endif
4446   let base = s:TreeChomp('merge-base', mine, theirs)
4447   if base ==# mine
4448     return -1
4449   elseif base ==# theirs
4450     return 1
4451   endif
4452   let my_time    = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:mine, '--')
4453   let their_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:theirs, '--')
4454   return my_time < their_time ? -1 : my_time != their_time
4455 endfunction
4457 function! s:IsConflicted() abort
4458   return len(@%) && !empty(s:ChompDefault('', 'ls-files', '--unmerged', '--', expand('%:p')))
4459 endfunction
4461 function! fugitive#Diffsplit(autodir, keepfocus, mods, arg, args) abort
4462   let args = copy(a:args)
4463   let post = ''
4464   if get(args, 0) =~# '^+'
4465     let post = remove(args, 0)[1:-1]
4466   endif
4467   if exists(':DiffGitCached') && empty(args)
4468     return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
4469   endif
4470   let commit = s:DirCommitFile(@%)[1]
4471   if a:mods =~# '\<tab\>'
4472     let mods = substitute(a:mods, '\<tab\>', '', 'g')
4473     let pre = 'tab split'
4474   else
4475     let mods = 'keepalt ' . a:mods
4476     let pre = ''
4477   endif
4478   let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
4479   if (empty(args) || args[0] ==# ':') && a:keepfocus
4480     exe s:DirCheck()
4481     if empty(commit) && s:IsConflicted()
4482       let parents = [s:Relative(':2:'), s:Relative(':3:')]
4483     elseif empty(commit)
4484       let parents = [s:Relative(':0:')]
4485     elseif commit =~# '^\d\=$'
4486       let parents = [s:Relative('HEAD:')]
4487     elseif commit =~# '^\x\x\+$'
4488       let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
4489       call map(parents, 's:Relative(v:val . ":")')
4490     endif
4491   endif
4492   try
4493     if exists('parents') && len(parents) > 1
4494       exe pre
4495       let mods = (a:autodir ? s:diff_modifier(len(parents) + 1) : '') . s:Mods(mods, 'leftabove')
4496       let nr = bufnr('')
4497       execute mods 'split' s:fnameescape(s:Generate(parents[0]))
4498       call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4499       let nr2 = bufnr('')
4500       call s:diffthis()
4501       exe back
4502       call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
4503       let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
4504       for i in range(len(parents)-1, 1, -1)
4505         execute mods 'split' s:fnameescape(s:Generate(parents[i]))
4506         call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4507         let nrx = bufnr('')
4508         call s:diffthis()
4509         exe back
4510         call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
4511       endfor
4512       call s:diffthis()
4513       if len(parents) > 1
4514         wincmd =
4515       endif
4516       return post
4517     elseif len(args)
4518       let arg = join(args, ' ')
4519       if arg ==# ''
4520         return post
4521       elseif arg ==# ':/'
4522         exe s:DirCheck()
4523         let file = s:Relative()
4524       elseif arg ==# ':'
4525         exe s:DirCheck()
4526         let file = s:Relative(':0:')
4527       elseif arg =~# '^:\d$'
4528         exe s:DirCheck()
4529         let file = s:Relative(arg . ':')
4530       else
4531         try
4532           let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
4533         catch /^fugitive:/
4534           return 'echoerr ' . string(v:exception)
4535         endtry
4536       endif
4537     elseif exists('parents') && len(parents)
4538       let file = parents[-1]
4539     elseif len(commit)
4540       let file = s:Relative()
4541     elseif s:IsConflicted()
4542       let file = s:Relative(':1:')
4543       let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
4544     else
4545       exe s:DirCheck()
4546       let file = s:Relative(':0:')
4547     endif
4548     let spec = s:Generate(file)
4549     if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
4550       let spec = FugitiveVimPath(spec . s:Relative('/'))
4551     endif
4552     exe pre
4553     let restore = s:diff_restore()
4554     let w:fugitive_diff_restore = restore
4555     if len(spec) && s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
4556       let mods = s:Mods(mods, 'rightbelow')
4557     else
4558       let mods = s:Mods(mods, 'leftabove')
4559     endif
4560     let mods = (a:autodir ? s:diff_modifier(2) : '') . mods
4561     if &diffopt =~# 'vertical'
4562       let diffopt = &diffopt
4563       set diffopt-=vertical
4564     endif
4565     execute mods 'diffsplit' s:fnameescape(spec)
4566     let &l:readonly = &l:readonly
4567     redraw
4568     let w:fugitive_diff_restore = restore
4569     let winnr = winnr()
4570     if getwinvar('#', '&diff')
4571       if a:keepfocus
4572         exe back
4573       endif
4574     endif
4575     return post
4576   catch /^fugitive:/
4577     return 'echoerr ' . string(v:exception)
4578   finally
4579     if exists('diffopt')
4580       let &diffopt = diffopt
4581     endif
4582   endtry
4583 endfunction
4585 " Section: :Gmove, :Gremove
4587 function! s:Move(force, rename, destination) abort
4588   let dir = s:Dir()
4589   exe s:DirCheck(dir)
4590   if s:DirCommitFile(@%)[1] !~# '^0\=$' || empty(@%)
4591     return 'echoerr ' . string('fugitive: mv not supported for this buffer')
4592   endif
4593   if a:destination =~# '^\.\.\=\%(/\|$\)'
4594     let destination = simplify(getcwd() . '/' . a:destination)
4595   elseif a:destination =~# '^\a\+:\|^/'
4596     let destination = a:destination
4597   elseif a:destination =~# '^:/:\='
4598     let destination = s:Tree(dir) . substitute(a:destination, '^:/:\=', '', '')
4599   elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\))'
4600     let destination = s:Tree(dir) . matchstr(a:destination, ')\zs.*')
4601   elseif a:destination =~# '^:(literal)'
4602     let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
4603   elseif a:rename
4604     let destination = expand('%:p:s?[\/]$??:h') . '/' . a:destination
4605   else
4606     let destination = s:Tree(dir) . '/' . a:destination
4607   endif
4608   let destination = s:Slash(destination)
4609   if isdirectory(@%)
4610     setlocal noswapfile
4611   endif
4612   let [message, exec_error] = s:ChompError(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination], dir)
4613   if exec_error
4614     let v:errmsg = 'fugitive: '.message
4615     return 'echoerr v:errmsg'
4616   endif
4617   if isdirectory(destination)
4618     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
4619   endif
4620   let reload = '|call fugitive#ReloadStatus(' . string(dir) . ', 1)'
4621   if empty(s:DirCommitFile(@%)[1])
4622     if isdirectory(destination)
4623       return 'keepalt edit '.s:fnameescape(destination) . reload
4624     else
4625       return 'keepalt saveas! '.s:fnameescape(destination) . reload
4626     endif
4627   else
4628     return 'file '.s:fnameescape(fugitive#Find(':0:'.destination, dir)) . reload
4629   endif
4630 endfunction
4632 function! fugitive#RenameComplete(A,L,P) abort
4633   if a:A =~# '^[.:]\=/'
4634     return fugitive#CompletePath(a:A)
4635   else
4636     let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
4637     return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
4638   endif
4639 endfunction
4641 function! fugitive#MoveCommand(line1, line2, range, bang, mods, arg, args) abort
4642   return s:Move(a:bang, 0, a:arg)
4643 endfunction
4645 function! fugitive#RenameCommand(line1, line2, range, bang, mods, arg, args) abort
4646   return s:Move(a:bang, 1, a:arg)
4647 endfunction
4649 function! s:Remove(after, force) abort
4650   let dir = s:Dir()
4651   exe s:DirCheck(dir)
4652   if len(@%) && s:DirCommitFile(@%)[1] ==# ''
4653     let cmd = ['rm']
4654   elseif s:DirCommitFile(@%)[1] ==# '0'
4655     let cmd = ['rm','--cached']
4656   else
4657     return 'echoerr ' . string('fugitive: rm not supported for this buffer')
4658   endif
4659   if a:force
4660     let cmd += ['--force']
4661   endif
4662   let [message, exec_error] = s:ChompError(cmd + ['--', expand('%:p')], dir)
4663   if exec_error
4664     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
4665     return 'echoerr '.string(v:errmsg)
4666   else
4667     return a:after . (a:force ? '!' : ''). '|call fugitive#ReloadStatus(' . string(dir) . ', 1)'
4668   endif
4669 endfunction
4671 function! fugitive#RemoveCommand(line1, line2, range, bang, mods, arg, args) abort
4672   return s:Remove('edit', a:bang)
4673 endfunction
4675 function! fugitive#DeleteCommand(line1, line2, range, bang, mods, arg, args) abort
4676   return s:Remove('bdelete', a:bang)
4677 endfunction
4679 " Section: :Gblame
4681 function! s:Keywordprg() abort
4682   let args = ' --git-dir='.escape(s:Dir(),"\\\"' ")
4683   if has('gui_running') && !has('win32')
4684     return s:UserCommand() . ' --no-pager' . args . ' log -1'
4685   else
4686     return s:UserCommand() . args . ' show'
4687   endif
4688 endfunction
4690 function! s:linechars(pattern) abort
4691   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
4692   if exists('*synconcealed') && &conceallevel > 1
4693     for col in range(1, chars)
4694       let chars -= synconcealed(line('.'), col)[0]
4695     endfor
4696   endif
4697   return chars
4698 endfunction
4700 function! s:BlameBufnr(...) abort
4701   let state = s:TempState(bufname(a:0 ? a:1 : ''))
4702   if get(state, 'filetype', '') ==# 'fugitiveblame'
4703     return get(state, 'bufnr', -1)
4704   else
4705     return -1
4706   endif
4707 endfunction
4709 function! s:BlameCommitFileLnum(...) abort
4710   let line = a:0 ? a:1 : getline('.')
4711   let state = a:0 ? a:2 : s:TempState()
4712   let commit = matchstr(line, '^\^\=\zs\x\+')
4713   if commit =~# '^0\+$'
4714     let commit = ''
4715   elseif line !~# '^\^' && has_key(state, 'blame_reverse_end')
4716     let commit = get(s:LinesError('rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end)[0], 0, '')
4717   endif
4718   let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
4719   let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s\+\%(\%( \d\+ \)\@<!([^()]*\w \d\+)\|\d\+ \)')
4720   if empty(path) && lnum
4721     let path = get(state, 'blame_file', '')
4722   endif
4723   return [commit, path, lnum]
4724 endfunction
4726 function! s:BlameLeave() abort
4727   let bufwinnr = bufwinnr(s:BlameBufnr())
4728   if bufwinnr > 0
4729     let bufnr = bufnr('')
4730     exe bufwinnr . 'wincmd w'
4731     return bufnr . 'bdelete'
4732   endif
4733   return ''
4734 endfunction
4736 function! s:BlameQuit() abort
4737   let cmd = s:BlameLeave()
4738   if empty(cmd)
4739     return 'bdelete'
4740   elseif len(s:DirCommitFile(@%)[1])
4741     return cmd . '|Gedit'
4742   else
4743     return cmd
4744   endif
4745 endfunction
4747 function! fugitive#BlameComplete(A, L, P) abort
4748   return s:CompleteSub('blame', a:A, a:L, a:P)
4749 endfunction
4751 function! s:BlameSubcommand(line1, count, range, bang, mods, args) abort
4752   exe s:DirCheck()
4753   let flags = copy(a:args)
4754   let i = 0
4755   let raw = 0
4756   let commits = []
4757   let files = []
4758   let ranges = []
4759   if a:line1 > 0 && a:count > 0 && a:range != 1
4760     call extend(ranges, ['-L', a:line1 . ',' . a:count])
4761   endif
4762   while i < len(flags)
4763     let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
4764     if len(match) && len(match[2])
4765       call insert(flags, match[1])
4766       let flags[i+1] = '-' . match[2]
4767       continue
4768     endif
4769     let arg = flags[i]
4770     if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
4771       let raw = 1
4772     elseif arg ==# '--contents' && i + 1 < len(flags)
4773       call extend(commits, remove(flags, i, i+1))
4774       continue
4775     elseif arg ==# '-L' && i + 1 < len(flags)
4776       call extend(ranges, remove(flags, i, i+1))
4777       continue
4778     elseif arg =~# '^--contents='
4779       call add(commits, remove(flags, i))
4780       continue
4781     elseif arg =~# '^-L.'
4782       call add(ranges, remove(flags, i))
4783       continue
4784     elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
4785       let i += 1
4786       if i == len(flags)
4787         echohl ErrorMsg
4788         echo s:ChompError(['blame', arg])[0]
4789         echohl NONE
4790         return ''
4791       endif
4792     elseif arg ==# '--'
4793       if i + 1 < len(flags)
4794         call extend(files, remove(flags, i + 1, -1))
4795       endif
4796       call remove(flags, i)
4797       break
4798     elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
4799       if index(flags, '--') >= 0
4800         call add(commits, remove(flags, i))
4801         continue
4802       endif
4803       if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
4804         call add(commits, remove(flags, i))
4805         continue
4806       endif
4807       try
4808         let dcf = s:DirCommitFile(fugitive#Find(arg))
4809         if len(dcf[1]) && empty(dcf[2])
4810           call add(commits, remove(flags, i))
4811           continue
4812         endif
4813       catch /^fugitive:/
4814       endtry
4815       call add(files, remove(flags, i))
4816       continue
4817     endif
4818     let i += 1
4819   endwhile
4820   let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./'))), '^\.\%(/\|$\)', '', '')
4821   if empty(commits) && len(files) > 1
4822     call add(commits, remove(files, 1))
4823   endif
4824   exe s:BlameLeave()
4825   try
4826     let cmd = ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', 'blame', '--show-number']
4827     call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
4828     if a:count > 0 && empty(ranges)
4829       let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
4830     endif
4831     call extend(cmd, ranges)
4832     if len(commits)
4833       let cmd += commits
4834     elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
4835       let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
4836     elseif empty(files) && !s:HasOpt(flags, '--reverse')
4837       let cmd += ['--contents', '-']
4838     endif
4839     let basecmd = escape(fugitive#Prepare(cmd) . ' -- ' . s:shellesc(len(files) ? files : file), '!#%')
4840     let tempname = tempname()
4841     let error = tempname . '.err'
4842     let temp = tempname . (raw ? '' : '.fugitiveblame')
4843     if &shell =~# 'csh'
4844       silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
4845     else
4846       silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
4847     endif
4848     let l:shell_error = v:shell_error
4849     redraw
4850     try
4851       if l:shell_error
4852         let lines = readfile(error)
4853         if empty(lines)
4854           let lines = readfile(temp)
4855         endif
4856         for i in range(len(lines))
4857           if lines[i] =~# '^error: \|^fatal: '
4858             echohl ErrorMsg
4859             echon lines[i]
4860             echohl NONE
4861             break
4862           else
4863             echon lines[i]
4864           endif
4865           if i != len(lines) - 1
4866             echon "\n"
4867           endif
4868         endfor
4869         return ''
4870       endif
4871       let temp_state = {'dir': s:Dir(), 'filetype': (raw ? '' : 'fugitiveblame'), 'blame_flags': flags, 'blame_file': file, 'modifiable': 0}
4872       if s:HasOpt(flags, '--reverse')
4873         let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
4874       endif
4875       if (a:line1 == 0 || a:range == 1) && a:count > 0
4876         let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit'], a:count - (a:line1 ? a:line1 : 1), 'split')
4877         return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
4878       else
4879         let temp = s:Resolve(temp)
4880         let s:temp_files[s:cpath(temp)] = temp_state
4881         if len(ranges + commits + files) || raw
4882           let mods = s:Mods(a:mods)
4883           if a:count != 0
4884             exe 'silent keepalt' mods 'split' s:fnameescape(temp)
4885           elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
4886             exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
4887           else
4888             return mods . 'edit ' . s:fnameescape(temp)
4889           endif
4890           return ''
4891         endif
4892         if a:mods =~# '\<tab\>'
4893           silent tabedit %
4894         endif
4895         let mods = substitute(a:mods, '\<tab\>', '', 'g')
4896         for winnr in range(winnr('$'),1,-1)
4897           if getwinvar(winnr, '&scrollbind')
4898             call setwinvar(winnr, '&scrollbind', 0)
4899           endif
4900           if exists('+cursorbind') && getwinvar(winnr, '&cursorbind')
4901             call setwinvar(winnr, '&cursorbind', 0)
4902           endif
4903           if s:BlameBufnr(winbufnr(winnr)) > 0
4904             execute winbufnr(winnr).'bdelete'
4905           endif
4906         endfor
4907         let bufnr = bufnr('')
4908         let temp_state.bufnr = bufnr
4909         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
4910         if exists('+cursorbind')
4911           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
4912         endif
4913         if &l:wrap
4914           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
4915         endif
4916         if &l:foldenable
4917           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
4918         endif
4919         setlocal scrollbind nowrap nofoldenable
4920         if exists('+cursorbind')
4921           setlocal cursorbind
4922         endif
4923         let top = line('w0') + &scrolloff
4924         let current = line('.')
4925         exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
4926         let w:fugitive_leave = restore
4927         execute top
4928         normal! zt
4929         execute current
4930         if exists('+cursorbind')
4931           setlocal cursorbind
4932         endif
4933         setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
4934         if exists('+relativenumber')
4935           setlocal norelativenumber
4936         endif
4937         execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
4938         call s:Map('n', 'A', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>')
4939         call s:Map('n', 'C', ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>')
4940         call s:Map('n', 'D', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>')
4941         redraw
4942         syncbind
4943       endif
4944     endtry
4945     return ''
4946   catch /^fugitive:/
4947     return 'echoerr ' . string(v:exception)
4948   endtry
4949 endfunction
4951 function! s:BlameCommit(cmd, ...) abort
4952   let line = a:0 ? a:1 : getline('.')
4953   let state = a:0 ? a:2 : s:TempState()
4954   let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
4955   let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
4956   let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
4957   if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
4958     let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
4959     return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
4960   endif
4961   if commit =~# '^0*$'
4962     return 'echoerr ' . string('fugitive: no commit')
4963   endif
4964   if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
4965     let path = commit . ':' . path
4966     return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
4967   endif
4968   let cmd = fugitive#Open(mods . a:cmd, 0, '', commit, [commit])
4969   if cmd =~# '^echoerr'
4970     return cmd
4971   endif
4972   execute cmd
4973   if a:cmd ==# 'pedit' || empty(path)
4974     return ''
4975   endif
4976   if search('^diff .* b/\M'.escape(path,'\').'$','W')
4977     call search('^+++')
4978     let head = line('.')
4979     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
4980       let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
4981       let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
4982       if lnum >= top && lnum <= top + len
4983         let offset = lnum - top
4984         if &scrolloff
4985           +
4986           normal! zt
4987         else
4988           normal! zt
4989           +
4990         endif
4991         while offset > 0 && line('.') < line('$')
4992           +
4993           if getline('.') =~# '^[ ' . sigil . ']'
4994             let offset -= 1
4995           endif
4996         endwhile
4997         return 'normal! zv'
4998       endif
4999     endwhile
5000     execute head
5001     normal! zt
5002   endif
5003   return ''
5004 endfunction
5006 function! s:BlameJump(suffix, ...) abort
5007   let suffix = a:suffix
5008   let [commit, path, lnum] = s:BlameCommitFileLnum()
5009   if empty(path)
5010     return 'echoerr ' . string('fugitive: could not determine filename for blame')
5011   endif
5012   if commit =~# '^0*$'
5013     let commit = 'HEAD'
5014     let suffix = ''
5015   endif
5016   let offset = line('.') - line('w0')
5017   let flags = get(s:TempState(), 'blame_flags', [])
5018   if a:0 && a:1
5019     if s:HasOpt(flags, '--reverse')
5020       call remove(flags, '--reverse')
5021     else
5022       call add(flags, '--reverse')
5023     endif
5024   endif
5025   let blame_bufnr = s:BlameBufnr()
5026   if blame_bufnr > 0
5027     let bufnr = bufnr('')
5028     let winnr = bufwinnr(blame_bufnr)
5029     if winnr > 0
5030       exe winnr.'wincmd w'
5031     endif
5032     execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
5033     execute lnum
5034     if winnr > 0
5035       exe bufnr.'bdelete'
5036     endif
5037   endif
5038   if exists(':Gblame')
5039     let my_bufnr = bufnr('')
5040     if blame_bufnr < 0
5041       let blame_args = flags + [commit . suffix, '--', path]
5042       let result = s:BlameSubcommand(0, 0, 0, 0, '', blame_args)
5043     else
5044       let blame_args = flags
5045       let result = s:BlameSubcommand(-1, -1, 0, 0, '', blame_args)
5046     endif
5047     if bufnr('') == my_bufnr
5048       return result
5049     endif
5050     execute result
5051     execute lnum
5052     let delta = line('.') - line('w0') - offset
5053     if delta > 0
5054       execute 'normal! '.delta."\<C-E>"
5055     elseif delta < 0
5056       execute 'normal! '.(-delta)."\<C-Y>"
5057     endif
5058     keepjumps syncbind
5059     redraw
5060     echo ':Gblame' s:fnameescape(blame_args)
5061   endif
5062   return ''
5063 endfunction
5065 let s:hash_colors = {}
5067 function! fugitive#BlameSyntax() abort
5068   let conceal = has('conceal') ? ' conceal' : ''
5069   let config = fugitive#Config()
5070   let flags = get(s:TempState(), 'blame_flags', [])
5071   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
5072   syn match FugitiveblameHash       "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5073   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5074   if get(get(config, 'blame.blankboundary', ['x']), 0, 'x') =~# '^$\|^true$' || s:HasOpt(flags, '-b')
5075     syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5076   else
5077     syn match FugitiveblameBoundary "^\^"
5078   endif
5079   syn match FugitiveblameScoreDebug        " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
5080   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
5081   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
5082   exec 'syn match FugitiveblameLineNumber         "\s*\d\+)\@=" contained containedin=FugitiveblameAnnotation' conceal
5083   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)
5084   exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5085   exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5086   syn match FugitiveblameShort              " \d\+)" contained contains=FugitiveblameLineNumber
5087   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
5088   hi def link FugitiveblameBoundary           Keyword
5089   hi def link FugitiveblameHash               Identifier
5090   hi def link FugitiveblameBoundaryIgnore     Ignore
5091   hi def link FugitiveblameUncommitted        Ignore
5092   hi def link FugitiveblameScoreDebug         Debug
5093   hi def link FugitiveblameTime               PreProc
5094   hi def link FugitiveblameLineNumber         Number
5095   hi def link FugitiveblameOriginalFile       String
5096   hi def link FugitiveblameOriginalLineNumber Float
5097   hi def link FugitiveblameShort              FugitiveblameDelimiter
5098   hi def link FugitiveblameDelimiter          Delimiter
5099   hi def link FugitiveblameNotCommittedYet    Comment
5100   if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
5101     return
5102   endif
5103   let seen = {}
5104   for lnum in range(1, line('$'))
5105     let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
5106     if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
5107       continue
5108     endif
5109     let seen[hash] = 1
5110     if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
5111           \ && empty(get(s:hash_colors, hash))
5112       let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
5113       let color = csapprox#per_component#Approximate(r, g, b)
5114       if color == 16 && &background ==# 'dark'
5115         let color = 8
5116       endif
5117       let s:hash_colors[hash] = ' ctermfg='.color
5118     else
5119       let s:hash_colors[hash] = ''
5120     endif
5121     exe 'syn match FugitiveblameHash'.hash.'       "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
5122   endfor
5123   call s:BlameRehighlight()
5124 endfunction
5126 function! s:BlameRehighlight() abort
5127   for [hash, cterm] in items(s:hash_colors)
5128     if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
5129       exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
5130     else
5131       exe 'hi link FugitiveblameHash'.hash.' Identifier'
5132     endif
5133   endfor
5134 endfunction
5136 function! s:BlameFileType() abort
5137   setlocal nomodeline
5138   setlocal foldmethod=manual
5139   if len(s:Dir())
5140     let &l:keywordprg = s:Keywordprg()
5141   endif
5142   let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
5143   if exists('+concealcursor')
5144     setlocal concealcursor=nc conceallevel=2
5145     let b:undo_ftplugin .= ' concealcursor< conceallevel<'
5146   endif
5147   if &modifiable
5148     return ''
5149   endif
5150   call s:Map('n', '<F1>', ':help :Gblame<CR>', '<silent>')
5151   call s:Map('n', 'g?',   ':help :Gblame<CR>', '<silent>')
5152   if mapcheck('q', 'n') =~# '^$\|bdelete'
5153     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>')
5154   endif
5155   call s:Map('n', 'gq',   ':exe <SID>BlameQuit()<CR>', '<silent>')
5156   call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5157   call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5158   call s:Map('n', '-',    ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>')
5159   call s:Map('n', 'P',    ':<C-U>exe <SID>BlameJump("^".v:count1)<CR>', '<silent>')
5160   call s:Map('n', '~',    ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>')
5161   call s:Map('n', 'i',    ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5162   call s:Map('n', 'o',    ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>')
5163   call s:Map('n', 'O',    ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>')
5164   call s:Map('n', 'p',    ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>')
5165 endfunction
5167 augroup fugitive_blame
5168   autocmd!
5169   autocmd FileType fugitiveblame call s:BlameFileType()
5170   autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
5171   autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
5172 augroup END
5174 " Section: :Gbrowse
5176 let s:redirects = {}
5178 function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, args) abort
5179   let dir = s:Dir()
5180   exe s:DirCheck(dir)
5181   try
5182     let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
5183     if a:args ==# ['-']
5184       if a:count >= 0
5185         return 'echoerr ' . string('fugitive: ''-'' no longer required to get persistent URL if range given')
5186       else
5187         return 'echoerr ' . string('fugitive: use :0Gbrowse instead of :Gbrowse -')
5188       endif
5189     elseif len(a:args)
5190       let remote = matchstr(join(a:args, ' '),'@\zs\%('.validremote.'\)$')
5191       let rev = substitute(join(a:args, ' '),'@\%('.validremote.'\)$','','')
5192     else
5193       let remote = ''
5194       let rev = ''
5195     endif
5196     if rev ==# ''
5197       let rev = s:DirRev(@%)[1]
5198     endif
5199     if rev =~# '^:\=$'
5200       let expanded = s:Relative()
5201     else
5202       let expanded = s:Expand(rev)
5203     endif
5204     let cdir = FugitiveVimPath(fugitive#CommonDir(dir))
5205     for subdir in ['tags/', 'heads/', 'remotes/']
5206       if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . subdir . expanded)
5207         let expanded = '.git/refs/' . subdir . expanded
5208       endif
5209     endfor
5210     let full = fugitive#Find(expanded, dir)
5211     let commit = ''
5212     if full =~? '^fugitive:'
5213       let [pathdir, commit, path] = s:DirCommitFile(full)
5214       if commit =~# '^:\=\d$'
5215         let commit = ''
5216       endif
5217       if commit =~ '..'
5218         let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
5219         let branch = matchstr(expanded, '^[^:]*')
5220       else
5221         let type = 'blob'
5222       endif
5223       let path = path[1:-1]
5224     elseif empty(s:Tree(dir))
5225       let path = '.git/' . full[strlen(dir)+1:-1]
5226       let type = ''
5227     else
5228       let path = fugitive#Path(full, '/')[1:-1]
5229       if path =~# '^\.git/'
5230         let type = ''
5231       elseif isdirectory(full) || empty(path)
5232         let type = 'tree'
5233       else
5234         let type = 'blob'
5235       endif
5236     endif
5237     if type ==# 'tree' && !empty(path)
5238       let path = s:sub(path, '/\=$', '/')
5239     endif
5240     if path =~# '^\.git/.*HEAD$' && filereadable(dir . '/' . path[5:-1])
5241       let body = readfile(dir . '/' . path[5:-1])[0]
5242       if body =~# '^\x\{40,\}$'
5243         let commit = body
5244         let type = 'commit'
5245         let path = ''
5246       elseif body =~# '^ref: refs/'
5247         let path = '.git/' . matchstr(body,'ref: \zs.*')
5248       endif
5249     endif
5251     let merge = ''
5252     if path =~# '^\.git/refs/remotes/.'
5253       if empty(remote)
5254         let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
5255         let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5256       else
5257         let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5258         let path = '.git/refs/heads/'.merge
5259       endif
5260     elseif path =~# '^\.git/refs/heads/.'
5261       let branch = path[16:-1]
5262     elseif !exists('branch')
5263       let branch = FugitiveHead()
5264     endif
5265     if !empty(branch)
5266       let r = fugitive#Config('branch.'.branch.'.remote')
5267       let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
5268       if r ==# '.' && !empty(m)
5269         let r2 = fugitive#Config('branch.'.m.'.remote')
5270         if r2 !~# '^\.\=$'
5271           let r = r2
5272           let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
5273         endif
5274       endif
5275       if empty(remote)
5276         let remote = r
5277       endif
5278       if r ==# '.' || r ==# remote
5279         let merge = m
5280         if path =~# '^\.git/refs/heads/.'
5281           let path = '.git/refs/heads/'.merge
5282         endif
5283       endif
5284     endif
5286     let line1 = a:count > 0 ? a:line1 : 0
5287     let line2 = a:count > 0 ? a:count : 0
5288     if empty(commit) && path !~# '^\.git/'
5289       if a:count < 0 && !empty(merge)
5290         let commit = merge
5291       else
5292         let commit = ''
5293         if len(merge)
5294           let owner = s:Owner(@%)
5295           let [commit, exec_error] = s:ChompError(['merge-base', 'refs/remotes/' . remote . '/' . merge, empty(owner) ? 'HEAD' : owner, '--'])
5296           if exec_error
5297             let commit = ''
5298           endif
5299           if a:count > 0 && empty(a:args) && commit =~# '^\x\{40,\}$'
5300             let blame_list = tempname()
5301             call writefile([commit, ''], blame_list, 'b')
5302             let blame_in = tempname()
5303             silent exe '%write' blame_in
5304             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])
5305             if !exec_error
5306               let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
5307               if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
5308                 let line1 = +matchstr(blame[0], blame_regex)
5309                 let line2 = +matchstr(blame[-1], blame_regex)
5310               else
5311                 call s:throw("Can't browse to uncommitted change")
5312               endif
5313             endif
5314           endif
5315         endif
5316       endif
5317       if empty(commit)
5318         let commit = readfile(fugitive#Find('.git/HEAD', dir), '', 1)[0]
5319       endif
5320       let i = 0
5321       while commit =~# '^ref: ' && i < 10
5322         let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
5323         let i -= 1
5324       endwhile
5325     endif
5327     if empty(remote)
5328       let remote = '.'
5329     endif
5330     let raw = fugitive#RemoteUrl(remote)
5331     if empty(raw)
5332       let raw = remote
5333     endif
5335     if raw =~# '^https\=://' && s:executable('curl')
5336       if !has_key(s:redirects, raw)
5337         let s:redirects[raw] = matchstr(system('curl -I ' .
5338               \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
5339               \ 'Location: \zs\S\+\ze/info/refs?')
5340       endif
5341       if len(s:redirects[raw])
5342         let raw = s:redirects[raw]
5343       endif
5344     endif
5346     let opts = {
5347           \ 'dir': dir,
5348           \ 'repo': fugitive#repo(dir),
5349           \ 'remote': raw,
5350           \ 'revision': 'No longer provided',
5351           \ 'commit': commit,
5352           \ 'path': path,
5353           \ 'type': type,
5354           \ 'line1': line1,
5355           \ 'line2': line2}
5357     let url = ''
5358     for Handler in get(g:, 'fugitive_browse_handlers', [])
5359       let url = call(Handler, [copy(opts)])
5360       if !empty(url)
5361         break
5362       endif
5363     endfor
5365     if empty(url)
5366       call s:throw("No Gbrowse handler installed for '".raw."'")
5367     endif
5369     let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
5370     if a:bang
5371       if has('clipboard')
5372         let @+ = url
5373       endif
5374       return 'echomsg '.string(url)
5375     elseif exists(':Browse') == 2
5376       return 'echomsg '.string(url).'|Browse '.url
5377     else
5378       if !exists('g:loaded_netrw')
5379         runtime! autoload/netrw.vim
5380       endif
5381       if exists('*netrw#BrowseX')
5382         return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
5383       else
5384         return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
5385       endif
5386     endif
5387   catch /^fugitive:/
5388     return 'echoerr ' . string(v:exception)
5389   endtry
5390 endfunction
5392 " Section: Go to file
5394 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
5395 function! fugitive#MapCfile(...) abort
5396   exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
5397   let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
5398   if !exists('g:fugitive_no_maps')
5399     call s:Map('n', 'gf',          '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
5400     call s:Map('n', '<C-W>f',     '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5401     call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5402     call s:Map('n', '<C-W>gf',  '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
5403     call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
5404   endif
5405 endfunction
5407 function! s:ContainingCommit() abort
5408   let commit = s:Owner(@%)
5409   return empty(commit) ? 'HEAD' : commit
5410 endfunction
5412 function! s:SquashArgument(...) abort
5413   if &filetype == 'fugitive'
5414     let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze ')
5415   elseif has_key(s:temp_files, s:cpath(expand('%:p')))
5416     let commit = matchstr(getline('.'), '\<\x\{4,\}\>')
5417   else
5418     let commit = s:Owner(@%)
5419   endif
5420   return len(commit) && a:0 ? printf(a:1, commit) : commit
5421 endfunction
5423 function! s:RebaseArgument() abort
5424   return s:SquashArgument(' %s^')
5425 endfunction
5427 function! s:NavigateUp(count) abort
5428   let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
5429   let c = a:count
5430   while c
5431     if rev =~# ':.*/.'
5432       let rev = matchstr(rev, '.*\ze/.\+', '')
5433     elseif rev =~# '.:.'
5434       let rev = matchstr(rev, '^.[^:]*:')
5435     elseif rev =~# '^:'
5436       let rev = 'HEAD^{}'
5437     elseif rev =~# ':$'
5438       let rev = rev[0:-2]
5439     else
5440       return rev.'~'.c
5441     endif
5442     let c -= 1
5443   endwhile
5444   return rev
5445 endfunction
5447 function! s:MapMotion(lhs, rhs) abort
5448   call s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5449   call s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5450   call s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")
5451 endfunction
5453 function! fugitive#MapJumps(...) abort
5454   if !&modifiable
5455     if get(b:, 'fugitive_type', '') ==# 'blob'
5456       let blame_map = 'Gblame<C-R>=v:count ? " --reverse" : ""<CR><CR>'
5457       call s:Map('n', '<2-LeftMouse>', ':<C-U>0,1' . blame_map, '<silent>')
5458       call s:Map('n', '<CR>', ':<C-U>0,1' . blame_map, '<silent>')
5459       call s:Map('n', 'o',    ':<C-U>0,2' . blame_map, '<silent>')
5460       call s:Map('n', 'p',    ':<C-U>0,3' . blame_map, '<silent>')
5461       call s:Map('n', 'gO',   ':<C-U>0,4' . blame_map, '<silent>')
5462       call s:Map('n', 'O',    ':<C-U>0,5' . blame_map, '<silent>')
5464       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>')
5465       call s:Map('n', 'dd', ":<C-U>call <SID>DiffClose()<Bar>Gdiffsplit!<CR>", '<silent>')
5466       call s:Map('n', 'dh', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5467       call s:Map('n', 'ds', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5468       call s:Map('n', 'dv', ":<C-U>call <SID>DiffClose()<Bar>Gvdiffsplit!<CR>", '<silent>')
5469       call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
5471     else
5472       call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5473       call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5474       call s:Map('n', 'o',    ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
5475       call s:Map('n', 'gO',   ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
5476       call s:Map('n', 'O',    ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
5477       call s:Map('n', 'p',    ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
5479       if !exists('g:fugitive_no_maps')
5480         if exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
5481           nnoremap <buffer> <silent> <C-P> :<C-U>execute line('.') == 1 ? 'CtrlP ' . fnameescape(<SID>Tree()) : <SID>PreviousItem(v:count1)<CR>
5482         else
5483           nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>PreviousItem(v:count1)<CR>
5484         endif
5485         nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>NextItem(v:count1)<CR>
5486       endif
5487       call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
5488       call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
5489       call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
5490       call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
5491       call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
5492       call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
5493       call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
5494       call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
5495       call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
5496       call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
5497       call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
5498       call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
5499       call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
5500       call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
5501       call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
5502       call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
5503     endif
5504     call s:Map('n', 'S',    ':<C-U>echoerr "Use gO"<CR>', '<silent>')
5505     call s:Map('n', 'dq', ":<C-U>call <SID>DiffClose()<CR>", '<silent>')
5506     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>')
5507     call s:Map('n', 'P',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5508     call s:Map('n', '~',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5509     call s:Map('n', 'C',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5510     call s:Map('n', 'cp',    ":<C-U>echoerr 'Use gC'<CR>", '<silent>')
5511     call s:Map('n', 'gC',    ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5512     call s:Map('n', 'gc',    ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5513     call s:Map('n', 'gi',    ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5514     call s:Map('x', 'gi',    ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5516     nnoremap <buffer>       c<Space> :Git commit<Space>
5517     nnoremap <buffer>          c<CR> :Git commit<CR>
5518     nnoremap <buffer>      cv<Space> :Git commit -v<Space>
5519     nnoremap <buffer>         cv<CR> :Git commit -v<CR>
5520     nnoremap <buffer> <silent> ca    :<C-U>Gcommit --amend<CR>
5521     nnoremap <buffer> <silent> cc    :<C-U>Gcommit<CR>
5522     nnoremap <buffer> <silent> ce    :<C-U>Gcommit --amend --no-edit<CR>
5523     nnoremap <buffer> <silent> cw    :<C-U>Gcommit --amend --only<CR>
5524     nnoremap <buffer> <silent> cva   :<C-U>Gcommit -v --amend<CR>
5525     nnoremap <buffer> <silent> cvc   :<C-U>Gcommit -v<CR>
5526     nnoremap <buffer> <silent> cRa   :<C-U>Gcommit --reset-author --amend<CR>
5527     nnoremap <buffer> <silent> cRe   :<C-U>Gcommit --reset-author --amend --no-edit<CR>
5528     nnoremap <buffer> <silent> cRw   :<C-U>Gcommit --reset-author --amend --only<CR>
5529     nnoremap <buffer>          cf    :<C-U>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5530     nnoremap <buffer>          cF    :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5531     nnoremap <buffer>          cs    :<C-U>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5532     nnoremap <buffer>          cS    :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5533     nnoremap <buffer>          cA    :<C-U>Gcommit --edit --squash=<C-R>=<SID>SquashArgument()<CR>
5534     nnoremap <buffer> <silent> c?    :<C-U>help fugitive_c<CR>
5536     nnoremap <buffer>      cr<Space> :Git revert<Space>
5537     nnoremap <buffer>         cr<CR> :Git revert<CR>
5538     nnoremap <buffer> <silent> crc   :<C-U>Grevert <C-R>=<SID>SquashArgument()<CR><CR>
5539     nnoremap <buffer> <silent> crn   :<C-U>Grevert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>
5540     nnoremap <buffer> <silent> cr?   :help fugitive_cr<CR>
5542     nnoremap <buffer>      cm<Space> :Git merge<Space>
5543     nnoremap <buffer>         cm<CR> :Git merge<CR>
5544     nnoremap <buffer> <silent> cm?   :help fugitive_cm<CR>
5546     nnoremap <buffer>      cz<Space> :Git stash<Space>
5547     nnoremap <buffer>         cz<CR> :Git stash<CR>
5548     nnoremap <buffer> <silent> cza   :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5549     nnoremap <buffer> <silent> czA   :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', 'stash@{' . v:count . '}'])<CR>
5550     nnoremap <buffer> <silent> czp   :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5551     nnoremap <buffer> <silent> czP   :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', 'stash@{' . v:count . '}'])<CR>
5552     nnoremap <buffer> <silent> czv   :<C-U>exe 'Gedit' fugitive#RevParse('stash@{' . v:count . '}')<CR>
5553     nnoremap <buffer> <silent> czw   :<C-U>exe <SID>EchoExec(['stash', '--keep-index'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5554     nnoremap <buffer> <silent> czz   :<C-U>exe <SID>EchoExec(['stash'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5555     nnoremap <buffer> <silent> cz?   :<C-U>help fugitive_cz<CR>
5557     nnoremap <buffer>      co<Space> :Git checkout<Space>
5558     nnoremap <buffer>         co<CR> :Git checkout<CR>
5559     nnoremap <buffer>          coo   :exe <SID>EchoExec(['checkout'] + split(<SID>SquashArgument()) + ['--'])<CR>
5560     nnoremap <buffer>          co?   :<C-U>help fugitive_co<CR>
5562     nnoremap <buffer>      cb<Space> :Git branch<Space>
5563     nnoremap <buffer>         cb<CR> :Git branch<CR>
5564     nnoremap <buffer>         cb?    :<C-U>help fugitive_cb<CR>
5566     nnoremap <buffer>       r<Space> :Git rebase<Space>
5567     nnoremap <buffer>          r<CR> :Git rebase<CR>
5568     nnoremap <buffer> <silent> ri    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>
5569     nnoremap <buffer> <silent> rf    :<C-U>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>
5570     nnoremap <buffer> <silent> ru    :<C-U>Grebase --interactive @{upstream}<CR>
5571     nnoremap <buffer> <silent> rp    :<C-U>Grebase --interactive @{push}<CR>
5572     nnoremap <buffer> <silent> rw    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>
5573     nnoremap <buffer> <silent> rm    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>
5574     nnoremap <buffer> <silent> rd    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5575     nnoremap <buffer> <silent> rk    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5576     nnoremap <buffer> <silent> rx    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5577     nnoremap <buffer> <silent> rr    :<C-U>Grebase --continue<CR>
5578     nnoremap <buffer> <silent> rs    :<C-U>Grebase --skip<CR>
5579     nnoremap <buffer> <silent> re    :<C-U>Grebase --edit-todo<CR>
5580     nnoremap <buffer> <silent> ra    :<C-U>Grebase --abort<CR>
5581     nnoremap <buffer> <silent> r?    :<C-U>help fugitive_r<CR>
5583     call s:Map('n', '.',     ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5584     call s:Map('x', '.',     ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5585     call s:Map('n', 'g?',    ":<C-U>help fugitive-map<CR>", '<silent>')
5586     call s:Map('n', '<F1>',  ":<C-U>help fugitive-map<CR>", '<silent>')
5587   endif
5588 endfunction
5590 function! s:StatusCfile(...) abort
5591   let tree = s:Tree()
5592   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5593   let info = s:StageInfo()
5594   let line = getline('.')
5595   if len(info.sigil) && len(info.section) && len(info.paths)
5596     if info.section ==# 'Unstaged' && info.sigil !=# '-'
5597       return [lead . info.relative[0], info.offset, 'normal!zv']
5598     elseif info.section ==# 'Staged' && info.sigil ==# '-'
5599       return ['@:' . info.relative[0], info.offset, 'normal!zv']
5600     else
5601       return [':0:' . info.relative[0], info.offset, 'normal!zv']
5602     endif
5603   elseif len(info.paths)
5604     return [lead . info.relative[0]]
5605   elseif len(info.commit)
5606     return [info.commit]
5607   elseif line =~# '^\%(Head\|Merge\|Rebase\|Upstream\|Pull\|Push\): '
5608     return [matchstr(line, ' \zs.*')]
5609   else
5610     return ['']
5611   endif
5612 endfunction
5614 function! fugitive#StatusCfile() abort
5615   let file = s:Generate(s:StatusCfile()[0])
5616   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5617 endfunction
5619 function! s:MessageCfile(...) abort
5620   let tree = s:Tree()
5621   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5622   if getline('.') =~# '^.\=\trenamed:.* -> '
5623     return lead . matchstr(getline('.'),' -> \zs.*')
5624   elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
5625     return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
5626   elseif getline('.') =~# '^.\=\t.'
5627     return lead . matchstr(getline('.'),'\t\zs.*')
5628   elseif getline('.') =~# ': needs merge$'
5629     return lead . matchstr(getline('.'),'.*\ze: needs merge$')
5630   elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
5631     return 'HEAD'
5632   elseif getline('.') =~# '^\%(. \)\=On branch '
5633     return 'refs/heads/'.getline('.')[12:]
5634   elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
5635     return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
5636   else
5637     return ''
5638   endif
5639 endfunction
5641 function! fugitive#MessageCfile() abort
5642   let file = s:Generate(s:MessageCfile())
5643   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5644 endfunction
5646 function! s:cfile() abort
5647   try
5648     let myhash = s:DirRev(@%)[1]
5649     if len(myhash)
5650       try
5651         let myhash = fugitive#RevParse(myhash)
5652       catch /^fugitive:/
5653         let myhash = ''
5654       endtry
5655     endif
5656     if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
5657       let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
5658     endif
5660     let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
5662     let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
5663           \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
5665     if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
5666       return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
5667     elseif showtree
5668       return [treebase . s:sub(getline('.'),'/$','')]
5670     else
5672       let dcmds = []
5674       " Index
5675       if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
5676         let ref = matchstr(getline('.'),'\x\{40,\}')
5677         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
5678         return [file]
5679       endif
5681       if getline('.') =~# '^ref: '
5682         let ref = strpart(getline('.'),5)
5684       elseif getline('.') =~# '^commit \x\{40,\}\>'
5685         let ref = matchstr(getline('.'),'\x\{40,\}')
5686         return [ref]
5688       elseif getline('.') =~# '^parent \x\{40,\}\>'
5689         let ref = matchstr(getline('.'),'\x\{40,\}')
5690         let line = line('.')
5691         let parent = 0
5692         while getline(line) =~# '^parent '
5693           let parent += 1
5694           let line -= 1
5695         endwhile
5696         return [ref]
5698       elseif getline('.') =~# '^tree \x\{40,\}$'
5699         let ref = matchstr(getline('.'),'\x\{40,\}')
5700         if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
5701           let ref = myhash.':'
5702         endif
5703         return [ref]
5705       elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
5706         let ref = matchstr(getline('.'),'\x\{40,\}')
5707         let type = matchstr(getline(line('.')+1),'type \zs.*')
5709       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
5710         let ref = s:DirRev(@%)[1]
5712       elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
5713         let ref = matchstr(getline('.'),'\x\{40,\}')
5714         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
5716       elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
5717         let ref = getline('.')[4:]
5719       elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
5720         let type = getline('.')[0]
5721         let lnum = line('.') - 1
5722         let offset = 0
5723         while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5724           if getline(lnum) =~# '^[ '.type.']'
5725             let offset += 1
5726           endif
5727           let lnum -= 1
5728         endwhile
5729         let offset += matchstr(getline(lnum), type.'\zs\d\+')
5730         let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
5731         let dcmds = [offset, 'normal!zv']
5733       elseif getline('.') =~# '^rename from '
5734         let ref = 'a/'.getline('.')[12:]
5735       elseif getline('.') =~# '^rename to '
5736         let ref = 'b/'.getline('.')[10:]
5738       elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5739         let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
5740         let offset = matchstr(getline('.'), '+\zs\d\+')
5742         let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5743         let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5744         let dcmd = 'Gdiffsplit! +'.offset
5746       elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5747         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5748         let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5749         let dcmd = 'Gdiffsplit!'
5751       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5752         let line = getline(line('.')-1)
5753         let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5754         let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5755         let dcmd = 'Gdiffsplit!'
5757       elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
5758         let ref = getline('.')
5760       elseif expand('<cword>') =~# '^\x\{7,\}\>'
5761         return [expand('<cword>')]
5763       else
5764         let ref = ''
5765       endif
5767       let prefixes = {
5768             \ '1': '',
5769             \ '2': '',
5770             \ 'b': ':0:',
5771             \ 'i': ':0:',
5772             \ 'o': '',
5773             \ 'w': ''}
5775       if len(myhash)
5776         let prefixes.a = myhash.'^:'
5777         let prefixes.b = myhash.':'
5778       endif
5779       let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5780       if exists('dref')
5781         let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5782       endif
5784       if ref ==# '/dev/null'
5785         " Empty blob
5786         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
5787       endif
5789       if exists('dref')
5790         return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
5791       elseif ref != ""
5792         return [ref] + dcmds
5793       endif
5795     endif
5796     return []
5797   endtry
5798 endfunction
5800 function! s:GF(mode) abort
5801   try
5802     let results = &filetype ==# 'fugitive' ? s:StatusCfile() : &filetype ==# 'gitcommit' ? [s:MessageCfile()] : s:cfile()
5803   catch /^fugitive:/
5804     return 'echoerr ' . string(v:exception)
5805   endtry
5806   if len(results) > 1
5807     return 'G' . a:mode .
5808           \ ' +' . escape(results[1], ' ') . ' ' .
5809           \ s:fnameescape(results[0]) . join(map(results[2:-1], '"|" . v:val'), '')
5810   elseif len(results) && len(results[0])
5811     return 'G' . a:mode . ' ' . s:fnameescape(results[0])
5812   else
5813     return ''
5814   endif
5815 endfunction
5817 function! fugitive#Cfile() abort
5818   let pre = ''
5819   let results = s:cfile()
5820   if empty(results)
5821     let cfile = expand('<cfile>')
5822     if &includeexpr =~# '\<v:fname\>'
5823       sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
5824     endif
5825     return cfile
5826   elseif len(results) > 1
5827     let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
5828   endif
5829   return pre . s:fnameescape(s:Generate(results[0]))
5830 endfunction
5832 " Section: Statusline
5834 function! fugitive#Statusline(...) abort
5835   let dir = s:Dir(bufnr(''))
5836   if empty(dir)
5837     return ''
5838   endif
5839   let status = ''
5840   let commit = s:DirCommitFile(@%)[1]
5841   if len(commit)
5842     let status .= ':' . commit[0:6]
5843   endif
5844   let status .= '('.FugitiveHead(7, dir).')'
5845   return '[Git'.status.']'
5846 endfunction
5848 function! fugitive#statusline(...) abort
5849   return fugitive#Statusline()
5850 endfunction
5852 function! fugitive#head(...) abort
5853   if empty(s:Dir())
5854     return ''
5855   endif
5857   return fugitive#Head(a:0 ? a:1 : 0)
5858 endfunction
5860 " Section: Folding
5862 function! fugitive#Foldtext() abort
5863   if &foldmethod !=# 'syntax'
5864     return foldtext()
5865   endif
5867   let line_foldstart = getline(v:foldstart)
5868   if line_foldstart =~# '^diff '
5869     let [add, remove] = [-1, -1]
5870     let filename = ''
5871     for lnum in range(v:foldstart, v:foldend)
5872       let line = getline(lnum)
5873       if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
5874         let filename = line[6:-1]
5875       endif
5876       if line =~# '^+'
5877         let add += 1
5878       elseif line =~# '^-'
5879         let remove += 1
5880       elseif line =~# '^Binary '
5881         let binary = 1
5882       endif
5883     endfor
5884     if filename ==# ''
5885       let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
5886     endif
5887     if filename ==# ''
5888       let filename = line_foldstart[5:-1]
5889     endif
5890     if exists('binary')
5891       return 'Binary: '.filename
5892     else
5893       return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
5894     endif
5895   elseif line_foldstart =~# '^# .*:$'
5896     let lines = getline(v:foldstart, v:foldend)
5897     call filter(lines, 'v:val =~# "^#\t"')
5898     cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
5899     cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
5900     return line_foldstart.' '.join(lines, ', ')
5901   endif
5902   return foldtext()
5903 endfunction
5905 function! fugitive#foldtext() abort
5906   return fugitive#Foldtext()
5907 endfunction
5909 augroup fugitive_folding
5910   autocmd!
5911   autocmd User Fugitive
5912         \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
5913         \    set foldtext=fugitive#Foldtext() |
5914         \ endif
5915 augroup END
5917 " Section: Initialization
5919 function! fugitive#Init() abort
5920   if exists('#User#FugitiveBoot')
5921     exe s:DoAutocmd('User FugitiveBoot')
5922   endif
5923   let dir = s:Dir()
5924   if &tags !~# '\.git' && @% !~# '\.git' && !exists('s:tags_warning')
5925     let actualdir = fugitive#Find('.git/', dir)
5926     if filereadable(actualdir . 'tags')
5927       let s:tags_warning = 1
5928       echohl WarningMsg
5929       echo "Fugitive .git/tags support removed in favor of `:set tags^=./.git/tags;`"
5930       echohl NONE
5931     endif
5932   endif
5933   exe s:DoAutocmd('User Fugitive')
5934 endfunction
5936 function! fugitive#is_git_dir(path) abort
5937   return FugitiveIsGitDir(a:path)
5938 endfunction
5940 function! fugitive#extract_git_dir(path) abort
5941   return FugitiveExtractGitDir(a:path)
5942 endfunction
5944 function! fugitive#detect(path) abort
5945   return FugitiveDetect(a:path)
5946 endfunction
5948 " Section: End