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