Show first parent diff for merge commits
[vim-fugitive.git] / autoload / fugitive.vim
blob32d7a7cd93b3884f68ae643378af7b94d8cbfdda
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 keepjumps 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', '-m', '--first-parent', '--pretty=format:tree%x20%T%nparent%x20%P%nauthor%x20%an%x20<%ae>%x20%ad%ncommitter%x20%cn%x20<%ce>%x20%cd%nencoding%x20%e%n%n%s%n%n%b', rev])
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 * nested
2414         \ if getbufvar(+expand('<abuf>'), 'buftype') == 'terminal' |
2415         \   call fugitive#ReloadStatus() |
2416         \ endif
2417   if !has('win32')
2418     autocmd FocusGained        * call fugitive#ReloadStatus(-2, 0)
2419   endif
2420   autocmd BufEnter index,index.lock
2421         \ call s:ReloadWinStatus()
2422   autocmd TabEnter *
2423         \ if exists('t:fugitive_reload_status') |
2424         \    call s:ReloadTabStatus() |
2425         \ endif
2426 augroup END
2428 function! s:StageInfo(...) abort
2429   let lnum = a:0 ? a:1 : line('.')
2430   let sigil = matchstr(getline(lnum), '^[ @\+-]')
2431   let offset = -1
2432   if len(sigil)
2433     let type = sigil ==# '-' ? '-' : '+'
2434     while lnum > 0 && getline(lnum) !~# '^@'
2435       if getline(lnum) =~# '^[ '.type.']'
2436         let offset += 1
2437       endif
2438       let lnum -= 1
2439     endwhile
2440     let offset += matchstr(getline(lnum), type.'\zs\d\+')
2441     while getline(lnum) =~# '^[ @\+-]'
2442       let lnum -= 1
2443     endwhile
2444   endif
2445   let slnum = lnum + 1
2446   let section = ''
2447   let index = 0
2448   while len(getline(slnum - 1)) && empty(section)
2449     let slnum -= 1
2450     let section = matchstr(getline(slnum), '^\u\l\+\ze.* (\d\+)$')
2451     if empty(section) && getline(slnum) !~# '^[ @\+-]'
2452       let index += 1
2453     endif
2454   endwhile
2455   let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
2456   return {'section': section,
2457         \ 'heading': getline(slnum),
2458         \ 'sigil': sigil,
2459         \ 'offset': offset,
2460         \ 'filename': text,
2461         \ 'relative': reverse(split(text, ' -> ')),
2462         \ 'paths': map(reverse(split(text, ' -> ')), 's:Tree() . "/" . v:val'),
2463         \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
2464         \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
2465         \ 'index': index}
2466 endfunction
2468 function! s:Selection(arg1, ...) abort
2469   if a:arg1 ==# 'n'
2470     let arg1 = line('.')
2471     let arg2 = -v:count
2472   elseif a:arg1 ==# 'v'
2473     let arg1 = line("'<")
2474     let arg2 = line("'>")
2475   else
2476     let arg1 = a:arg1
2477     let arg2 = a:0 ? a:1 : 0
2478   endif
2479   let first = arg1
2480   if arg2 < 0
2481     let last = first - arg2 + 1
2482   elseif arg2 > 0
2483     let last = arg2
2484   else
2485     let last = first
2486   endif
2487   while getline(first) =~# '^$\|^[A-Z][a-z]'
2488     let first += 1
2489   endwhile
2490   if first > last || &filetype !=# 'fugitive'
2491     return []
2492   endif
2493   let flnum = first
2494   while getline(flnum) =~# '^[ @\+-]'
2495     let flnum -= 1
2496   endwhile
2497   let slnum = flnum + 1
2498   let section = ''
2499   let index = 0
2500   while len(getline(slnum - 1)) && empty(section)
2501     let slnum -= 1
2502     let heading = matchstr(getline(slnum), '^\u\l\+.* (\d\+)$')
2503     if empty(heading) && getline(slnum) !~# '^[ @\+-]'
2504       let index += 1
2505     endif
2506   endwhile
2507   let results = []
2508   let template = {
2509         \ 'heading': heading,
2510         \ 'section': matchstr(heading, '^\u\l\+\ze.* (\d\+)$'),
2511         \ 'filename': '',
2512         \ 'relative': [],
2513         \ 'paths': [],
2514         \ 'commit': '',
2515         \ 'status': '',
2516         \ 'patch': 0,
2517         \ 'index': index}
2518   let line = getline(flnum)
2519   let lnum = first - (arg1 == flnum ? 0 : 1)
2520   let root = s:Tree() . '/'
2521   while lnum <= last
2522     if line =~# '^\u\l\+\ze.* (\d\+)$'
2523       let template.heading = getline(lnum)
2524       let template.section = matchstr(template.heading, '^\u\l\+\ze.* (\d\+)$')
2525       let template.index = 0
2526     elseif line =~# '^[ @\+-]'
2527       let template.index -= 1
2528       if !results[-1].patch
2529         let results[-1].patch = lnum
2530       endif
2531       let results[-1].lnum = lnum
2532     elseif line =~# '^[A-Z?] '
2533       let filename = matchstr(line, '^[A-Z?] \zs.*')
2534       call add(results, extend(deepcopy(template), {
2535             \ 'lnum': lnum,
2536             \ 'filename': filename,
2537             \ 'relative': reverse(split(filename, ' -> ')),
2538             \ 'paths': map(reverse(split(filename, ' -> ')), 'root . v:val'),
2539             \ 'status': matchstr(line, '^[A-Z?]'),
2540             \ }))
2541     elseif line =~# '^\x\x\x\+ '
2542       call add(results, extend({
2543             \ 'lnum': lnum,
2544             \ 'commit': matchstr(line, '^\x\x\x\+'),
2545             \ }, template, 'keep'))
2546     elseif line =~# '^\l\+ \x\x\x\+ '
2547       call add(results, extend({
2548             \ 'lnum': lnum,
2549             \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
2550             \ 'status': matchstr(line, '^\l\+'),
2551             \ }, template, 'keep'))
2552     endif
2553     let lnum += 1
2554     let template.index += 1
2555     let line = getline(lnum)
2556   endwhile
2557   if len(results) && results[0].patch && arg2 == 0
2558     while getline(results[0].patch) =~# '^[ \+-]'
2559       let results[0].patch -= 1
2560     endwhile
2561     while getline(results[0].lnum + 1) =~# '^[ \+-]'
2562       let results[0].lnum += 1
2563     endwhile
2564   endif
2565   return results
2566 endfunction
2568 function! s:StageArgs(visual) abort
2569   let commits = []
2570   let paths = []
2571   for record in s:Selection(a:visual ? 'v' : 'n')
2572     if len(record.commit)
2573       call add(commits, record.commit)
2574     endif
2575     call extend(paths, record.paths)
2576   endfor
2577   if s:cpath(s:Tree(), getcwd())
2578     call map(paths, 'fugitive#Path(v:val, "./")')
2579   endif
2580   return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
2581 endfunction
2583 function! s:Do(action, visual) abort
2584   let line = getline('.')
2585   let reload = 0
2586   if !a:0 && !v:count && line =~# '^[A-Z][a-z]'
2587     let header = matchstr(line, '^\S\+\ze:')
2588     if len(header) && exists('*s:Do' . a:action . header . 'Header')
2589       let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
2590     else
2591       let section = matchstr(line, '^\S\+')
2592       if exists('*s:Do' . a:action . section . 'Heading')
2593         let reload = s:Do{a:action}{section}Heading(line) > 0
2594       endif
2595     endif
2596     return reload ? s:ReloadStatus() : ''
2597   endif
2598   let selection = s:Selection(a:visual ? 'v' : 'n')
2599   if empty(selection)
2600     return ''
2601   endif
2602   call filter(selection, 'v:val.section ==# selection[0].section')
2603   let status = 0
2604   let err = ''
2605   try
2606     for record in selection
2607       if exists('*s:Do' . a:action . record.section)
2608         let status = s:Do{a:action}{record.section}(record)
2609       else
2610         continue
2611       endif
2612       if !status
2613         return ''
2614       endif
2615       let reload = reload || (status > 0)
2616     endfor
2617     if status < 0
2618       execute record.lnum + 1
2619     endif
2620     let success = 1
2621   catch /^fugitive:/
2622     return 'echoerr ' . string(v:exception)
2623   finally
2624     if reload
2625       execute s:ReloadStatus()
2626     endif
2627     if exists('success')
2628       call s:StageReveal()
2629     endif
2630   endtry
2631   return ''
2632 endfunction
2634 function! s:StageReveal() abort
2635   exe 'normal! zv'
2636   let begin = line('.')
2637   if getline(begin) =~# '^@'
2638     let end = begin + 1
2639     while getline(end) =~# '^[ \+-]'
2640       let end += 1
2641     endwhile
2642   elseif getline(begin) =~# '^commit '
2643     let end = begin
2644     while end < line('$') && getline(end + 1) !~# '^commit '
2645       let end += 1
2646     endwhile
2647   elseif getline(begin) =~# s:section_pattern
2648     let end = begin
2649     while len(getline(end + 1))
2650       let end += 1
2651     endwhile
2652   endif
2653   if exists('end')
2654     while line('.') > line('w0') + &scrolloff && end > line('w$')
2655       execute "normal! \<C-E>"
2656     endwhile
2657   endif
2658 endfunction
2660 let s:file_pattern = '^[A-Z?] .\|^diff --'
2661 let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
2662 let s:item_pattern = s:file_commit_pattern . '\|^@@'
2664 function! s:NextHunk(count) abort
2665   if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
2666     exe s:StageInline('show')
2667   endif
2668   for i in range(a:count)
2669     if &filetype ==# 'fugitive'
2670       call search(s:file_pattern . '\|^@', 'W')
2671       if getline('.') =~# s:file_pattern
2672         exe s:StageInline('show')
2673         if getline(line('.') + 1) =~# '^@'
2674           +
2675         endif
2676       endif
2677     else
2678       call search('^@@', 'W')
2679     endif
2680   endfor
2681   call s:StageReveal()
2682   return '.'
2683 endfunction
2685 function! s:PreviousHunk(count) abort
2686   for i in range(a:count)
2687     if &filetype ==# 'fugitive'
2688       let lnum = search(s:file_pattern . '\|^@','Wbn')
2689       call s:StageInline('show', lnum)
2690       call search('^? .\|^@','Wb')
2691     else
2692       call search('^@@', 'Wb')
2693     endif
2694   endfor
2695   call s:StageReveal()
2696   return '.'
2697 endfunction
2699 function! s:NextFile(count) abort
2700   for i in range(a:count)
2701     exe s:StageInline('hide')
2702     if !search(s:file_pattern, 'W')
2703       break
2704     endif
2705   endfor
2706   exe s:StageInline('hide')
2707   return '.'
2708 endfunction
2710 function! s:PreviousFile(count) abort
2711   exe s:StageInline('hide')
2712   for i in range(a:count)
2713     if !search(s:file_pattern, 'Wb')
2714       break
2715     endif
2716     exe s:StageInline('hide')
2717   endfor
2718   return '.'
2719 endfunction
2721 function! s:NextItem(count) abort
2722   for i in range(a:count)
2723     if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
2724       call search('^commit ', 'W')
2725     endif
2726   endfor
2727   call s:StageReveal()
2728   return '.'
2729 endfunction
2731 function! s:PreviousItem(count) abort
2732   for i in range(a:count)
2733     if !search(s:item_pattern, 'Wbe') && getline('.') !~# s:item_pattern
2734       call search('^commit ', 'Wbe')
2735     endif
2736   endfor
2737   call s:StageReveal()
2738   return '.'
2739 endfunction
2741 let s:section_pattern = '^[A-Z][a-z][^:]*$'
2742 let s:section_commit_pattern = s:section_pattern . '\|^commit '
2744 function! s:NextSection(count) abort
2745   let orig = line('.')
2746   if getline('.') !~# '^commit '
2747     -
2748   endif
2749   for i in range(a:count)
2750     if !search(s:section_commit_pattern, 'W')
2751       break
2752     endif
2753   endfor
2754   if getline('.') =~# s:section_commit_pattern
2755     call s:StageReveal()
2756     return getline('.') =~# s:section_pattern ? '+' : ':'
2757   else
2758     return orig
2759   endif
2760 endfunction
2762 function! s:PreviousSection(count) abort
2763   let orig = line('.')
2764   if getline('.') !~# '^commit '
2765     -
2766   endif
2767   for i in range(a:count)
2768     if !search(s:section_commit_pattern . '\|\%^', 'bW')
2769       break
2770     endif
2771   endfor
2772   if getline('.') =~# s:section_commit_pattern || line('.') == 1
2773     call s:StageReveal()
2774     return getline('.') =~# s:section_pattern ? '+' : ':'
2775   else
2776     return orig
2777   endif
2778 endfunction
2780 function! s:NextSectionEnd(count) abort
2781   +
2782   if empty(getline('.'))
2783     +
2784   endif
2785   for i in range(a:count)
2786     if !search(s:section_commit_pattern, 'W')
2787       return '$'
2788     endif
2789   endfor
2790   return search('^.', 'Wb')
2791 endfunction
2793 function! s:PreviousSectionEnd(count) abort
2794   let old = line('.')
2795   for i in range(a:count)
2796     if search(s:section_commit_pattern, 'Wb') <= 1
2797       exe old
2798       if i
2799         break
2800       else
2801         return ''
2802       endif
2803     endif
2804     let old = line('.')
2805   endfor
2806   return search('^.', 'Wb')
2807 endfunction
2809 function! s:PatchSearchExpr(reverse) abort
2810   let line = getline('.')
2811   if col('.') ==# 1 && line =~# '^[+-]'
2812     if line =~# '^[+-]\{3\} '
2813       let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
2814     else
2815       let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
2816     endif
2817     if a:reverse
2818       return '?' . escape(pattern, '/') . "\<CR>"
2819     else
2820       return '/' . escape(pattern, '/?') . "\<CR>"
2821     endif
2822   endif
2823   return a:reverse ? '#' : '*'
2824 endfunction
2826 function! s:StageInline(mode, ...) abort
2827   if &filetype !=# 'fugitive'
2828     return ''
2829   endif
2830   let lnum1 = a:0 ? a:1 : line('.')
2831   let lnum = lnum1 + 1
2832   if a:0 > 1 && a:2 == 0
2833     let info = s:StageInfo(lnum - 1)
2834     if empty(info.paths) && len(info.section)
2835       while len(getline(lnum))
2836         let lnum += 1
2837       endwhile
2838     endif
2839   elseif a:0 > 1
2840     let lnum += a:2 - 1
2841   endif
2842   while lnum > lnum1
2843     let lnum -= 1
2844     while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
2845       let lnum -= 1
2846     endwhile
2847     let info = s:StageInfo(lnum)
2848     if !has_key(b:fugitive_diff, info.section)
2849       continue
2850     endif
2851     if getline(lnum + 1) =~# '^[ @\+-]'
2852       let lnum2 = lnum + 1
2853       while getline(lnum2 + 1) =~# '^[ @\+-]'
2854         let lnum2 += 1
2855       endwhile
2856       if a:mode !=# 'show'
2857         setlocal modifiable noreadonly
2858         exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
2859         call remove(b:fugitive_expanded[info.section], info.filename)
2860         setlocal nomodifiable readonly nomodified
2861       endif
2862       continue
2863     endif
2864     if !has_key(b:fugitive_diff, info.section) || info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
2865       continue
2866     endif
2867     let mode = ''
2868     let diff = []
2869     let index = 0
2870     let start = -1
2871     for line in b:fugitive_diff[info.section]
2872       if mode ==# 'await' && line[0] ==# '@'
2873         let mode = 'capture'
2874       endif
2875       if mode !=# 'head' && line !~# '^[ @\+-]'
2876         if len(diff)
2877           break
2878         endif
2879         let start = index
2880         let mode = 'head'
2881       elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '--- ' . info.relative[-1]
2882         let mode = 'await'
2883       elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '+++ ' . info.relative[0]
2884         let mode = 'await'
2885       elseif mode ==# 'capture'
2886         call add(diff, line)
2887       elseif line[0] ==# '@'
2888         let mode = ''
2889       endif
2890       let index += 1
2891     endfor
2892     if len(diff)
2893       setlocal modifiable noreadonly
2894       silent call append(lnum, diff)
2895       let b:fugitive_expanded[info.section][info.filename] = [start, len(diff)]
2896       setlocal nomodifiable readonly nomodified
2897     endif
2898   endwhile
2899   return lnum
2900 endfunction
2902 function! s:NextExpandedHunk(count) abort
2903   for i in range(a:count)
2904     call s:StageInline('show', line('.'), 1)
2905     call search(s:file_pattern . '\|^@','W')
2906   endfor
2907   return '.'
2908 endfunction
2910 function! s:StageDiff(diff) abort
2911   let lnum = line('.')
2912   let info = s:StageInfo(lnum)
2913   let prefix = info.offset > 0 ? '+' . info.offset : ''
2914   if empty(info.paths) && info.section ==# 'Staged'
2915     return 'Git! diff --no-ext-diff --cached'
2916   elseif empty(info.paths)
2917     return 'Git! diff --no-ext-diff'
2918   elseif len(info.paths) > 1
2919     execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
2920     return a:diff . '! HEAD:'.s:fnameescape(info.paths[1])
2921   elseif info.section ==# 'Staged' && info.sigil ==# '-'
2922     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2923     return a:diff . '! :0:%'
2924   elseif info.section ==# 'Staged'
2925     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2926     return a:diff . '! @:%'
2927   elseif info.sigil ==# '-'
2928     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2929     return a:diff . '! :(top)%'
2930   else
2931     execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
2932     return a:diff . '!'
2933   endif
2934 endfunction
2936 function! s:StageDiffEdit() abort
2937   let info = s:StageInfo(line('.'))
2938   let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
2939   if info.section ==# 'Staged'
2940     return 'Git! diff --no-ext-diff --cached '.s:fnameescape(arg)
2941   elseif info.status ==# '?'
2942     call s:TreeChomp('add', '--intent-to-add', '--', arg)
2943     return s:ReloadStatus()
2944   else
2945     return 'Git! diff --no-ext-diff '.s:fnameescape(arg)
2946   endif
2947 endfunction
2949 function! s:StageApply(info, reverse, extra) abort
2950   if a:info.status ==# 'R'
2951     call s:throw('fugitive: patching renamed file not yet supported')
2952   endif
2953   let cmd = ['apply', '-p0', '--recount'] + a:extra
2954   let info = a:info
2955   let start = info.patch
2956   let end = info.lnum
2957   let lines = getline(start, end)
2958   if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
2959     return -1
2960   endif
2961   while getline(end) =~# '^[-+ ]'
2962     let end += 1
2963     if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
2964       call add(lines, ' ' . getline(end)[1:-1])
2965     endif
2966   endwhile
2967   while start > 0 && getline(start) !~# '^@'
2968     let start -= 1
2969     if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
2970       call insert(lines, ' ' . getline(start)[1:-1])
2971     elseif getline(start) =~# '^@'
2972       call insert(lines, getline(start))
2973     endif
2974   endwhile
2975   if start == 0
2976     throw 'fugitive: cold not find hunk'
2977   elseif getline(start) !~# '^@@ '
2978     throw 'fugitive: cannot apply conflict hunk'
2979   endif
2980   let i = b:fugitive_expanded[info.section][info.filename][0]
2981   let head = []
2982   while get(b:fugitive_diff[info.section], i, '@') !~# '^@'
2983     call add(head, b:fugitive_diff[info.section][i])
2984     let i += 1
2985   endwhile
2986   call extend(lines, head, 'keep')
2987   let temp = tempname()
2988   call writefile(lines, temp)
2989   if a:reverse
2990     call add(cmd, '--reverse')
2991   endif
2992   call extend(cmd, ['--', temp])
2993   let [output, exec_error] = s:ChompError(cmd)
2994   if !exec_error
2995     return 1
2996   endif
2997   call s:throw(output)
2998 endfunction
3000 function! s:StageDelete(lnum1, lnum2, count) abort
3001   let restore = []
3002   let err = ''
3003   try
3004     for info in s:Selection(a:lnum1, a:lnum2)
3005       if empty(info.paths)
3006         continue
3007       endif
3008       let hash = s:TreeChomp('hash-object', '-w', '--', info.paths[0])
3009       if empty(hash)
3010         continue
3011       endif
3012       if info.patch
3013         call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
3014       elseif info.status ==# '?'
3015         call s:TreeChomp('clean', '-f', '--', info.paths[0])
3016       elseif a:count == 2
3017         call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
3018       elseif a:count == 3
3019         call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
3020       elseif info.status =~# '[ADU]' &&
3021             \ get(b:fugitive_status[info.section ==# 'Staged' ? 'Unstaged' : 'Staged'], info.filename, '') =~# '[AU]'
3022         call s:TreeChomp('checkout', info.section ==# 'Staged' ? '--ours' : '--theirs', '--', info.paths[0])
3023       elseif info.status ==# 'U'
3024         call s:TreeChomp('rm', '--', info.paths[0])
3025       elseif info.status ==# 'A'
3026         call s:TreeChomp('rm', '-f', '--', info.paths[0])
3027       elseif info.section ==# 'Unstaged'
3028         call s:TreeChomp('checkout', '--', info.paths[0])
3029       else
3030         call s:TreeChomp('checkout', 'HEAD^{}', '--', info.paths[0])
3031       endif
3032       call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|Gread ' . hash[0:6])
3033     endfor
3034   catch /^fugitive:/
3035     let err = '|echoerr ' . string(v:exception)
3036   endtry
3037   if empty(restore)
3038     return err[1:-1]
3039   endif
3040   exe s:ReloadStatus()
3041   call s:StageReveal()
3042   return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
3043 endfunction
3045 function! s:StageIgnore(lnum1, lnum2, count) abort
3046   let paths = []
3047   for info in s:Selection(a:lnum1, a:lnum2)
3048     call extend(paths, info.relative)
3049   endfor
3050   call map(paths, '"/" . v:val')
3051   exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
3052   let last = line('$')
3053   if last == 1 && empty(getline(1))
3054     call setline(last, paths)
3055   else
3056     call append(last, paths)
3057     exe last + 1
3058   endif
3059   return ''
3060 endfunction
3062 function! s:DoToggleHeadHeader(value) abort
3063   exe 'edit' s:fnameescape(s:Dir())
3064   call search('\C^index$', 'wc')
3065 endfunction
3067 function! s:DoStageUnpushedHeading(heading) abort
3068   let remote = matchstr(a:heading, 'to \zs[^/]\+\ze/')
3069   if empty(remote)
3070     let remote = '.'
3071   endif
3072   let branch = matchstr(a:heading, 'to \%([^/]\+/\)\=\zs\S\+')
3073   call feedkeys(':Gpush ' . remote . ' ' . 'HEAD:' . branch)
3074 endfunction
3076 function! s:DoToggleUnpushedHeading(heading) abort
3077   return s:DoStageUnpushedHeading(a:heading)
3078 endfunction
3080 function! s:DoStageUnpushed(record) abort
3081   let remote = matchstr(a:record.heading, 'to \zs[^/]\+\ze/')
3082   if empty(remote)
3083     let remote = '.'
3084   endif
3085   let branch = matchstr(a:record.heading, 'to \%([^/]\+/\)\=\zs\S\+')
3086   call feedkeys(':Gpush ' . remote . ' ' . a:record.commit . ':' . branch)
3087 endfunction
3089 function! s:DoToggleUnpushed(record) abort
3090   return s:DoStageUnpushed(a:record)
3091 endfunction
3093 function! s:DoUnstageUnpulledHeading(heading) abort
3094   call feedkeys(':Grebase')
3095 endfunction
3097 function! s:DoToggleUnpulledHeading(heading) abort
3098   call s:DoUnstageUnpulledHeading(a:heading)
3099 endfunction
3101 function! s:DoUnstageUnpulled(record) abort
3102   call feedkeys(':Grebase ' . a:record.commit)
3103 endfunction
3105 function! s:DoToggleUnpulled(record) abort
3106   call s:DoUnstageUnpulled(a:record)
3107 endfunction
3109 function! s:DoUnstageUnpushed(record) abort
3110   call feedkeys(':Grebase --autosquash ' . a:record.commit . '^')
3111 endfunction
3113 function! s:DoToggleStagedHeading(...) abort
3114   call s:TreeChomp('reset', '-q')
3115   return 1
3116 endfunction
3118 function! s:DoUnstageStagedHeading(heading) abort
3119   return s:DoToggleStagedHeading(a:heading)
3120 endfunction
3122 function! s:DoToggleUnstagedHeading(...) abort
3123   call s:TreeChomp('add', '-u')
3124   return 1
3125 endfunction
3127 function! s:DoStageUnstagedHeading(heading) abort
3128   return s:DoToggleUnstagedHeading(a:heading)
3129 endfunction
3131 function! s:DoToggleUntrackedHeading(...) abort
3132   call s:TreeChomp('add', '.')
3133   return 1
3134 endfunction
3136 function! s:DoStageUntrackedHeading(heading) abort
3137   return s:DoToggleUntrackedHeading(a:heading)
3138 endfunction
3140 function! s:DoToggleStaged(record) abort
3141   if a:record.patch
3142     return s:StageApply(a:record, 1, ['--cached'])
3143   else
3144     call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3145     return 1
3146   endif
3147 endfunction
3149 function! s:DoUnstageStaged(record) abort
3150   return s:DoToggleStaged(a:record)
3151 endfunction
3153 function! s:DoToggleUnstaged(record) abort
3154   if a:record.patch && a:record.status !=# 'A'
3155     return s:StageApply(a:record, 0, ['--cached'])
3156   else
3157     call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
3158     return 1
3159   endif
3160 endfunction
3162 function! s:DoStageUnstaged(record) abort
3163   return s:DoToggleUnstaged(a:record)
3164 endfunction
3166 function! s:DoUnstageUnstaged(record) abort
3167   if a:record.status ==# 'A'
3168     call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3169     return 1
3170   else
3171     return -1
3172   endif
3173 endfunction
3175 function! s:DoToggleUntracked(record) abort
3176   call s:TreeChomp(['add', '--'] + a:record.paths)
3177   return 1
3178 endfunction
3180 function! s:DoStageUntracked(record) abort
3181   return s:DoToggleUntracked(a:record)
3182 endfunction
3184 function! s:StagePatch(lnum1,lnum2) abort
3185   let add = []
3186   let reset = []
3187   let intend = []
3189   for lnum in range(a:lnum1,a:lnum2)
3190     let info = s:StageInfo(lnum)
3191     if empty(info.paths) && info.section ==# 'Staged'
3192       return 'Git reset --patch'
3193     elseif empty(info.paths) && info.section ==# 'Unstaged'
3194       return 'Git add --patch'
3195     elseif empty(info.paths) && info.section ==# 'Untracked'
3196       return 'Git add --interactive'
3197     elseif empty(info.paths)
3198       continue
3199     endif
3200     execute lnum
3201     if info.section ==# 'Staged'
3202       let reset += info.relative
3203     elseif info.section ==# 'Untracked'
3204       let intend += info.paths
3205     elseif info.status !~# '^D'
3206       let add += info.relative
3207     endif
3208   endfor
3209   try
3210     if !empty(intend)
3211       call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
3212     endif
3213     if !empty(add)
3214       execute "Git add --patch -- ".join(map(add,'s:fnameescape(v:val)'))
3215     endif
3216     if !empty(reset)
3217       execute "Git reset --patch -- ".join(map(reset,'s:fnameescape(v:val)'))
3218     endif
3219   catch /^fugitive:/
3220     return 'echoerr ' . string(v:exception)
3221   endtry
3222   return s:ReloadStatus()
3223 endfunction
3225 " Section: :Gcommit, :Grevert
3227 function! s:CommitInteractive(line1, line2, range, bang, mods, args, patch) abort
3228   let status = s:StatusCommand(a:line1, a:line2, a:range, a:line2, a:bang, a:mods, '', '', [])
3229   let status = len(status) ? status . '|' : ''
3230   if a:patch
3231     return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
3232   else
3233     return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
3234   endif
3235 endfunction
3237 function! s:CommitSubcommand(line1, line2, range, bang, mods, args, ...) abort
3238   let mods = substitute(s:Mods(a:mods), '\C\<tab\>', '-tab', 'g')
3239   let dir = a:0 ? a:1 : s:Dir()
3240   let tree = s:Tree(dir)
3241   let msgfile = fugitive#Find('.git/COMMIT_EDITMSG', dir)
3242   let outfile = tempname()
3243   try
3244     if s:winshell()
3245       let command = 'set GIT_EDITOR=false& '
3246     else
3247       let command = 'env GIT_EDITOR=false '
3248     endif
3249     let argv = a:args
3250     let i = 0
3251     while get(argv, i, '--') !=# '--'
3252       if argv[i] =~# '^-[apzsneiovq].'
3253         call insert(argv, argv[i][0:1])
3254         let argv[i+1] = '-' . argv[i+1][2:-1]
3255       else
3256         let i += 1
3257       endif
3258     endwhile
3259     let command .= s:UserCommand(dir, ['commit'] + argv)
3260     if (&autowrite || &autowriteall) && !a:0
3261       silent! wall
3262     endif
3263     if s:HasOpt(argv, '-i', '--interactive')
3264       return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 0)
3265     elseif s:HasOpt(argv, '-p', '--patch')
3266       return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 1)
3267     else
3268       let [error_string, exec_error] = s:TempCmd(outfile, command)
3269       let errors = split(error_string, "\n")
3270     endif
3271     if !has('gui_running')
3272       redraw!
3273     endif
3274     if !exec_error
3275       echo join(errors, "\n")
3276       if filereadable(outfile)
3277         echo join(readfile(outfile), "\n")
3278       endif
3279       call fugitive#ReloadStatus(dir, 1)
3280       return ''
3281     else
3282       let error = get(errors,-2,get(errors,-1,'!'))
3283       if error =~# 'false''\=\.$'
3284         let i = 0
3285         while get(argv, i, '--') !=# '--'
3286           if argv[i] =~# '^\%(-[eips]\|-[CcFm].\+\|--edit\|--interactive\|--patch\|--signoff\|--reedit-message=.*\|--reuse-message=.*\|--file=.*\|--message=.*\)$'
3287             call remove(argv, i)
3288           elseif argv[i] =~# '^\%(-[CcFm]\|--reedit-message\|--reuse-message\|--file\|--message\)$'
3289             call remove(argv, i, i + 1)
3290           else
3291             if argv[i] =~# '^--cleanup\>'
3292               let cleanup = 1
3293             endif
3294             let i += 1
3295           endif
3296         endwhile
3297         call insert(argv, '--no-signoff', i)
3298         call insert(argv, '--no-interactive', i)
3299         call insert(argv, '--no-edit', i)
3300         if !exists('cleanup')
3301           call insert(argv, '--cleanup=strip')
3302         endif
3303         call extend(argv, ['-F', msgfile], 'keep')
3304         if (bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&modified) || a:line2 == 0
3305           execute mods . 'keepalt edit' s:fnameescape(msgfile)
3306         elseif s:HasOpt(argv, '-v') || mods =~# '\<tab\>'
3307           execute mods . 'keepalt -tabedit' s:fnameescape(msgfile)
3308         else
3309           execute mods . 'keepalt split' s:fnameescape(msgfile)
3310         endif
3311         let b:fugitive_commit_arguments = argv
3312         setlocal bufhidden=wipe filetype=gitcommit
3313         return '1'
3314       elseif empty(errors)
3315         let out = readfile(outfile)
3316         echo get(out, -1, '') =~# 'stash\|\d' ? get(out, -2, '') : get(out, -1, '')
3317         return ''
3318       else
3319         echo join(errors, "\n")
3320         return ''
3321       endif
3322     endif
3323   catch /^fugitive:/
3324     return 'echoerr ' . string(v:exception)
3325   finally
3326     call delete(outfile)
3327   endtry
3328 endfunction
3330 function! s:RevertSubcommand(line1, line2, range, bang, mods, args) abort
3331   let dir = s:Dir()
3332   let no_commit = s:HasOpt(a:args, '-n', '--no-commit', '--no-edit', '--abort', '--continue', '--quit')
3333   let cmd = s:UserCommand(dir, ['revert'] + (no_commit ? [] : ['-n']) + a:args)
3334   let [out, exec_error] = s:SystemError(cmd)
3335   call fugitive#ReloadStatus(-1, 1)
3336   if no_commit || exec_error
3337     return 'echo ' . string(substitute(out, "\n$", '', ''))
3338   endif
3339   return s:CommitSubcommand(a:line1, a:line2, a:range, a:bang, a:mods, [], dir)
3340 endfunction
3342 function! fugitive#CommitComplete(A, L, P) abort
3343   if a:A =~# '^--fixup=\|^--squash='
3344     let commits = s:LinesError(['log', '--pretty=format:%s', '@{upstream}..'])[0]
3345     let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
3346     if pre =~# "'"
3347       call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
3348       call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
3349       return commits
3350     else
3351       return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
3352     endif
3353   else
3354     return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'))
3355   endif
3356   return []
3357 endfunction
3359 function! fugitive#RevertComplete(A, L, P) abort
3360   return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'))
3361 endfunction
3363 function! s:FinishCommit() abort
3364   let buf = +expand('<abuf>')
3365   let args = getbufvar(buf, 'fugitive_commit_arguments')
3366   if !empty(args)
3367     call setbufvar(buf, 'fugitive_commit_arguments', [])
3368     if getbufvar(buf, 'fugitive_commit_rebase')
3369       call setbufvar(buf, 'fugitive_commit_rebase', 0)
3370       let s:rebase_continue = s:Dir(buf)
3371     endif
3372     return s:CommitSubcommand(-1, -1, 0, 0, '', args, s:Dir(buf))
3373   endif
3374   return ''
3375 endfunction
3377 call s:command("-nargs=? -range=-1 -complete=customlist,fugitive#CommitComplete Gcommit", "commit")
3378 call s:command("-nargs=? -range=-1 -complete=customlist,fugitive#RevertComplete Grevert", "revert")
3380 " Section: :Gmerge, :Grebase, :Gpull
3382 function! fugitive#MergeComplete(A, L, P) abort
3383   return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'))
3384 endfunction
3386 function! fugitive#RebaseComplete(A, L, P) abort
3387   return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'))
3388 endfunction
3390 function! fugitive#PullComplete(A, L, P) abort
3391   return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'))
3392 endfunction
3394 function! s:RebaseSequenceAborter() abort
3395   if !exists('s:rebase_sequence_aborter')
3396     let temp = tempname() . '.sh'
3397     call writefile(
3398           \ ['#!/bin/sh',
3399           \ 'echo exec false | cat - "$1" > "$1.fugitive"',
3400           \ 'mv "$1.fugitive" "$1"'],
3401           \ temp)
3402     let s:rebase_sequence_aborter = temp
3403   endif
3404   return s:rebase_sequence_aborter
3405 endfunction
3407 function! fugitive#Cwindow() abort
3408   if &buftype == 'quickfix'
3409     cwindow
3410   else
3411     botright cwindow
3412     if &buftype == 'quickfix'
3413       wincmd p
3414     endif
3415   endif
3416 endfunction
3418 let s:common_efm = ''
3419       \ . '%+Egit:%.%#,'
3420       \ . '%+Eusage:%.%#,'
3421       \ . '%+Eerror:%.%#,'
3422       \ . '%+Efatal:%.%#,'
3423       \ . '%-G%.%#%\e[K%.%#,'
3424       \ . '%-G%.%#%\r%.%\+'
3426 let s:rebase_abbrevs = {
3427       \ 'p': 'pick',
3428       \ 'r': 'reword',
3429       \ 'e': 'edit',
3430       \ 's': 'squash',
3431       \ 'f': 'fixup',
3432       \ 'x': 'exec',
3433       \ 'd': 'drop',
3434       \ 'l': 'label',
3435       \ 't': 'reset',
3436       \ 'm': 'merge',
3437       \ 'b': 'break',
3438       \ }
3440 function! s:RebaseEdit(cmd, dir) abort
3441   let rebase_todo = s:fnameescape(fugitive#Find('.git/rebase-merge/git-rebase-todo', a:dir))
3443   if filereadable(rebase_todo)
3444     let new = readfile(rebase_todo)
3445     let sha_length = 0
3446     let shas = {}
3448     for i in range(len(new))
3449       if new[i] =~# '^\l\+\s\+[0-9a-f]\{5,\}\>'
3450         let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3451         if !sha_length
3452           let sha_length = len(s:TreeChomp(a:dir, 'rev-parse', '--short', sha))
3453         endif
3454         let shortened_sha = strpart(sha, 0, sha_length)
3455         let shas[shortened_sha] = sha
3456         let new[i] = substitute(new[i], sha, shortened_sha, '')
3457       endif
3458     endfor
3459     call writefile(new, rebase_todo)
3460   endif
3461   return a:cmd . ' +setlocal\ bufhidden=wipe\|' . escape('let b:fugitive_rebase_shas = ' . string(shas), ' ') . ' ' . rebase_todo
3462 endfunction
3464 function! s:MergeRebase(cmd, bang, mods, args, ...) abort
3465   let dir = a:0 ? a:1 : s:Dir()
3466   let args = a:args
3467   let mods = s:Mods(a:mods)
3468   if a:cmd =~# '^rebase' && s:HasOpt(args, '-i', '--interactive')
3469     let cmd = fugitive#Prepare(dir, '-c', 'sequence.editor=sh ' . s:RebaseSequenceAborter(), 'rebase') . ' ' . s:shellesc(args)
3470     let out = system(cmd)[0:-2]
3471     for file in ['end', 'msgnum']
3472       let file = fugitive#Find('.git/rebase-merge/' . file, dir)
3473       if !filereadable(file)
3474         return 'echoerr ' . string("fugitive: " . out)
3475       endif
3476       call writefile([readfile(file)[0] - 1], file)
3477     endfor
3478     call writefile([], fugitive#Find('.git/rebase-merge/done', dir))
3479     if a:bang
3480       return 'exe'
3481     endif
3482     return s:RebaseEdit(mods . 'split', dir)
3483   elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--edit-todo') && filereadable(fugitive#Find('.git/rebase-merge/git-rebase-todo', dir))
3484     return s:RebaseEdit(mods . 'split', dir)
3485   elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--continue') && !a:0
3486     let rdir = fugitive#Find('.git/rebase-merge', dir)
3487     let exec_error = s:ChompError([dir, 'diff-index', '--cached', '--quiet', 'HEAD', '--'])[1]
3488     if exec_error && isdirectory(rdir)
3489       if getfsize(rdir . '/amend') <= 0
3490         return 'exe ' . string(mods . 'Gcommit -n -F ' . s:fnameescape(rdir .'/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3491       elseif readfile(rdir . '/amend')[0] ==# fugitive#Head(-1, dir)
3492         return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(rdir . '/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3493       endif
3494     endif
3495   endif
3496   let had_merge_msg = filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3497   let argv = []
3498   if a:cmd ==# 'pull'
3499     let argv += s:AskPassArgs(dir) + ['pull', '--progress']
3500   else
3501     call add(argv, a:cmd)
3502   endif
3503   if !s:HasOpt(args, '--no-edit', '--abort', '-m') && a:cmd !=# 'rebase'
3504     call add(argv, '--edit')
3505   endif
3506   if a:cmd ==# 'rebase' && s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '--interactive', '-i')
3507     call add(argv, '--interactive')
3508   endif
3509   call extend(argv, args)
3511   let [mp, efm] = [&l:mp, &l:efm]
3512   try
3513     let cdback = s:Cd(s:Tree(dir))
3514     let &l:errorformat = ''
3515           \ . '%-Gerror:%.%#false''.,'
3516           \ . '%-G%.%# ''git commit'' %.%#,'
3517           \ . '%+Emerge:%.%#,'
3518           \ . s:common_efm . ','
3519           \ . '%+ECannot %.%#: You have unstaged changes.,'
3520           \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
3521           \ . '%+EThere is no tracking information for the current branch.,'
3522           \ . '%+EYou are not currently on a branch. Please specify which,'
3523           \ . '%+I %#git rebase --continue,'
3524           \ . 'CONFLICT (%m): %f deleted in %.%#,'
3525           \ . 'CONFLICT (%m): Merge conflict in %f,'
3526           \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
3527           \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
3528           \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
3529           \ . '%+ECONFLICT %.%#,'
3530           \ . '%+EKONFLIKT %.%#,'
3531           \ . '%+ECONFLIT %.%#,'
3532           \ . "%+EXUNG \u0110\u1ed8T %.%#,"
3533           \ . "%+E\u51b2\u7a81 %.%#,"
3534           \ . 'U%\t%f'
3535     if a:cmd =~# '^merge' && empty(args) &&
3536           \ (had_merge_msg || isdirectory(fugitive#Find('.git/rebase-apply', dir)) ||
3537           \  !empty(s:TreeChomp(dir, 'diff-files', '--diff-filter=U')))
3538       let cmd = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
3539     else
3540       let cmd = s:UserCommand(dir, argv)
3541     endif
3542     if !empty($GIT_SEQUENCE_EDITOR) || has('win32')
3543       let old_sequence_editor = $GIT_SEQUENCE_EDITOR
3544       let $GIT_SEQUENCE_EDITOR = 'true'
3545     else
3546       let cmd = 'env GIT_SEQUENCE_EDITOR=true ' . cmd
3547     endif
3548     if !empty($GIT_EDITOR) || has('win32')
3549       let old_editor = $GIT_EDITOR
3550       let $GIT_EDITOR = 'false'
3551     else
3552       let cmd = 'env GIT_EDITOR=false ' . substitute(cmd, '^env ', '', '')
3553     endif
3554     if !has('patch-8.1.0334') && has('terminal') && &autowrite
3555       let autowrite_was_set = 1
3556       set noautowrite
3557       silent! wall
3558     endif
3559     let &l:makeprg = cmd
3560     silent noautocmd make!
3561   catch /^Vim\%((\a\+)\)\=:E211/
3562     let err = v:exception
3563   finally
3564     if exists('autowrite_was_set')
3565       set autowrite
3566     endif
3567     redraw!
3568     let [&l:mp, &l:efm] = [mp, efm]
3569     if exists('old_editor')
3570       let $GIT_EDITOR = old_editor
3571     endif
3572     if exists('old_sequence_editor')
3573       let $GIT_SEQUENCE_EDITOR = old_sequence_editor
3574     endif
3575     execute cdback
3576   endtry
3577   call fugitive#ReloadStatus(dir, 1)
3578   if empty(filter(getqflist(),'v:val.valid && v:val.type !=# "I"'))
3579     if a:cmd =~# '^rebase' &&
3580           \ filereadable(fugitive#Find('.git/rebase-merge/amend', dir)) &&
3581           \ filereadable(fugitive#Find('.git/rebase-merge/done', dir)) &&
3582           \ get(readfile(fugitive#Find('.git/rebase-merge/done', dir)), -1, '') =~# '^[^e]'
3583       cclose
3584       return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(fugitive#Find('.git/rebase-merge/message', dir)) . ' -e') . '|let b:fugitive_commit_rebase = 1'
3585     elseif !had_merge_msg && filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3586       cclose
3587       return mods . 'Gcommit --no-status -n -t '.s:fnameescape(fugitive#Find('.git/MERGE_MSG', dir))
3588     endif
3589   endif
3590   let qflist = getqflist()
3591   let found = 0
3592   for e in qflist
3593     if !empty(e.bufnr)
3594       let found = 1
3595       let e.pattern = '^<<<<<<<'
3596     endif
3597   endfor
3598   call fugitive#Cwindow()
3599   if found
3600     call setqflist(qflist, 'r')
3601     if !a:bang
3602       call s:BlurStatus()
3603       return 'cfirst'
3604     endif
3605   endif
3606   return exists('err') ? 'echoerr '.string(err) : 'exe'
3607 endfunction
3609 function! s:RebaseClean(file) abort
3610   if !filereadable(a:file)
3611     return ''
3612   endif
3613   let old = readfile(a:file)
3614   let new = copy(old)
3615   for i in range(len(new))
3616     let new[i] = substitute(new[i], '^\l\>', '\=get(s:rebase_abbrevs,submatch(0),submatch(0))', '')
3618     let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3619     let rebase_shas = getbufvar(a:file, 'fugitive_rebase_shas')
3620     if len(sha) && type(rebase_shas) == type({}) && has_key(rebase_shas, sha)
3621       let new[i] = substitute(new[i], '\C\<' . sha . '\>', rebase_shas[sha], '')
3622     endif
3623   endfor
3624   if new !=# old
3625     call writefile(new, a:file)
3626   endif
3627   return ''
3628 endfunction
3630 function! s:MergeSubcommand(line1, line2, range, bang, mods, args) abort
3631   return s:MergeRebase('merge', a:bang, a:mods, a:args)
3632 endfunction
3634 function! s:RebaseSubcommand(line1, line2, range, bang, mods, args) abort
3635   return s:MergeRebase('rebase', a:bang, a:mods, a:args)
3636 endfunction
3638 function! s:PullSubcommand(line1, line2, range, bang, mods, args) abort
3639   return s:MergeRebase('pull', a:bang, a:mods, a:args)
3640 endfunction
3642 augroup fugitive_merge
3643   autocmd!
3644   autocmd VimLeavePre,BufDelete git-rebase-todo
3645         \ if getbufvar(+expand('<abuf>'), '&bufhidden') ==# 'wipe' |
3646         \   call s:RebaseClean(expand('<afile>')) |
3647         \   if getfsize(FugitiveFind('.git/rebase-merge/done', +expand('<abuf>'))) == 0 |
3648         \     let s:rebase_continue = FugitiveGitDir(+expand('<abuf>')) |
3649         \   endif |
3650         \ endif
3651   autocmd BufEnter * nested
3652         \ if exists('s:rebase_continue') |
3653         \   exe s:MergeRebase('rebase', 0, '', [getfsize(fugitive#Find('.git/rebase-merge/git-rebase-todo', s:rebase_continue)) > 0 ? '--continue' : '--abort'], remove(s:, 'rebase_continue')) |
3654         \ endif
3655 augroup END
3657 call s:command("-nargs=? -bang -complete=customlist,fugitive#MergeComplete Gmerge", "merge")
3658 call s:command("-nargs=? -bang -complete=customlist,fugitive#RebaseComplete Grebase", "rebase")
3659 call s:command("-nargs=? -bang -complete=customlist,fugitive#PullComplete Gpull", "pull")
3661 " Section: :Ggrep, :Glog
3663 if !exists('g:fugitive_summary_format')
3664   let g:fugitive_summary_format = '%s'
3665 endif
3667 function! fugitive#GrepComplete(A, L, P) abort
3668   return s:CompleteSub('grep', a:A, a:L, a:P)
3669 endfunction
3671 function! fugitive#LogComplete(A, L, P) abort
3672   return s:CompleteSub('log', a:A, a:L, a:P)
3673 endfunction
3675 function! s:GrepParseLine(prefix, name_only, dir, line) abort
3676   let entry = {'valid': 1}
3677   let match = matchlist(a:line, '^\(.\{-\}\):\(\d\+\):\(\d\+:\)\=\(.*\)$')
3678   if len(match)
3679     let entry.module = match[1]
3680     let entry.lnum = +match[2]
3681     let entry.col = +match[3]
3682     let entry.text = match[4]
3683   elseif a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
3684     return {'text': a:line}
3685   else
3686     let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
3687     if len(entry.module)
3688       let entry.text = 'Binary file'
3689       let entry.valid = 0
3690     endif
3691   endif
3692   if empty(entry.module) && a:name_only
3693     let entry.module = a:line
3694   endif
3695   if empty(entry.module)
3696     return {'text': a:line}
3697   endif
3698   if entry.module !~# ':'
3699     let entry.filename = a:prefix . entry.module
3700   else
3701     let entry.filename = fugitive#Find(entry.module, a:dir)
3702   endif
3703   return entry
3704 endfunction
3706 function! s:GrepSubcommand(line1, line2, range, bang, mods, args) abort
3707   let dir = s:Dir()
3708   exe s:DirCheck(dir)
3709   let listnr = a:line1 == 0 ? a:line1 : a:line2
3710   let cmd = ['--no-pager', 'grep', '-n', '--no-color', '--full-name']
3711   if fugitive#GitVersion(2, 19)
3712     call add(cmd, '--column')
3713   endif
3714   let tree = s:Tree(dir)
3715   if type(a:args) == type([])
3716     let [args, after] = [a:args, '']
3717   else
3718     let [args, after] = s:SplitExpandChain(a:args, tree)
3719   endif
3720   let prefix = FugitiveVimPath(s:HasOpt(args, '--cached') || empty(tree) ? 'fugitive://' . dir . '//0/' : tree . '/')
3721   let name_only = s:HasOpt(args, '-l', '--files-with-matches', '--name-only', '-L', '--files-without-match')
3722   let title = [listnr < 0 ? ':Ggrep' : ':Glgrep'] + args
3723   if listnr > 0
3724     exe listnr 'wincmd w'
3725   else
3726     call s:BlurStatus()
3727   endif
3728   redraw
3729   call s:QuickfixCreate(listnr, {'title': (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)})
3730   let tempfile = tempname()
3731   if v:version >= 704 | exe 'silent doautocmd <nomodeline> QuickFixCmdPre ' (listnr < 0 ? 'Ggrep' : 'Glgrep') | endif
3732   exe '!' . escape(s:UserCommand(dir, cmd + args), '%#!')
3733         \ printf(&shellpipe . (&shellpipe =~# '%s' ? '' : ' %s'), s:shellesc(tempfile))
3734   let list = map(readfile(tempfile), 's:GrepParseLine(prefix, name_only, dir, v:val)')
3735   call s:QuickfixSet(listnr, list, 'a')
3736   if v:version >= 704 | exe 'silent doautocmd <nomodeline> QuickFixCmdPost ' (listnr < 0 ? 'Ggrep' : 'Glgrep') | endif
3737   if !has('gui_running')
3738     redraw
3739   endif
3740   if !a:bang && !empty(list)
3741     return (listnr < 0 ? 'c' : 'l').'first' . after
3742   else
3743     return after[1:-1]
3744   endif
3745 endfunction
3747 function! s:LogFlushQueue(state) abort
3748   let queue = remove(a:state, 'queue')
3749   if a:state.child_found
3750     call remove(queue, 0)
3751   endif
3752   if len(queue) && queue[-1] ==# {'text': ''}
3753     call remove(queue, -1)
3754   endif
3755   return queue
3756 endfunction
3758 function! s:LogParse(state, dir, line) abort
3759   if a:state.context ==# 'hunk' && a:line =~# '^[-+ ]'
3760     return []
3761   endif
3762   let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
3763   if len(list)
3764     let a:state.context = 'commit'
3765     let a:state.base = 'fugitive://' . a:dir . '//' . list[2]
3766     let a:state.base_module = len(list[1]) ? list[1] : list[2]
3767     let a:state.message = list[3]
3768     if has_key(a:state, 'diffing')
3769       call remove(a:state, 'diffing')
3770     endif
3771     let queue = s:LogFlushQueue(a:state)
3772     let a:state.queue = [{
3773           \ 'valid': 1,
3774           \ 'filename': a:state.base . a:state.target,
3775           \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
3776           \ 'text': a:state.message}]
3777     let a:state.child_found = 0
3778     return queue
3779   elseif type(a:line) == type(0)
3780     return s:LogFlushQueue(a:state)
3781   elseif a:line =~# '^diff'
3782     let a:state.context = 'diffhead'
3783   elseif a:line =~# '^[+-]\{3\} \w/' && a:state.context ==# 'diffhead'
3784     let a:state.diffing = a:line[5:-1]
3785   elseif a:line =~# '^@@[^@]*+\d' && has_key(a:state, 'diffing') && has_key(a:state, 'base')
3786     let a:state.context = 'hunk'
3787     if empty(a:state.target) || a:state.target ==# a:state.diffing
3788       let a:state.child_found = 1
3789       call add(a:state.queue, {
3790             \ 'valid': 1,
3791             \ 'filename': a:state.base . a:state.diffing,
3792             \ 'module': a:state.base_module . substitute(a:state.diffing, '^/', ':', ''),
3793             \ 'lnum': +matchstr(a:line, '+\zs\d\+'),
3794             \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
3795     endif
3796   elseif a:state.follow &&
3797         \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
3798     let rename = matchstr(a:line, '^ rename \zs.* => .*\ze (\d\+%)$')
3799     if len(rename)
3800       let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
3801       if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
3802         let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
3803       endif
3804     endif
3805     if !get(a:state, 'ignore_summary')
3806       call add(a:state.queue, {'text': a:line})
3807     endif
3808   elseif a:state.context ==# 'commit' || a:state.context ==# 'init'
3809     call add(a:state.queue, {'text': a:line})
3810   endif
3811   return []
3812 endfunction
3814 function! fugitive#LogCommand(line1, count, range, bang, mods, args, type) abort
3815   let dir = s:Dir()
3816   exe s:DirCheck(dir)
3817   let listnr = a:type =~# '^l' ? 0 : -1
3818   let [args, after] = s:SplitExpandChain(a:args, s:Tree(dir))
3819   let split = index(args, '--')
3820   if split > 0
3821     let paths = args[split : -1]
3822     let args = args[0 : split - 1]
3823   elseif split == 0
3824     let paths = args
3825     let args = []
3826   else
3827     let paths = []
3828   endif
3829   if a:line1 == 0 && a:count
3830     let path = fugitive#Path(bufname(a:count), '/', dir)
3831   elseif a:count >= 0
3832     let path = fugitive#Path(@%, '/', dir)
3833   else
3834      let path = ''
3835   endif
3836   let range = ''
3837   let extra = []
3838   let state = {'context': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
3839   if path =~# '^/\.git\%(/\|$\)\|^$'
3840     let path = ''
3841   elseif a:line1 == 0
3842     let range = "0," . (a:count ? a:count : bufnr(''))
3843     let extra = ['.' . path]
3844     if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
3845       let state.follow = 1
3846       if !s:HasOpt(args, '--follow')
3847         call insert(args, '--follow')
3848       endif
3849       if !s:HasOpt(args, '--summary')
3850         call insert(args, '--summary')
3851         let state.ignore_summary = 1
3852       endif
3853     endif
3854   elseif a:count > 0
3855     if !s:HasOpt(args, '--merges', '--no-merges')
3856       call insert(args, '--no-merges')
3857     endif
3858     call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
3859   endif
3860   if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
3861     let owner = s:Owner(@%, dir)
3862     if len(owner)
3863       call add(args, owner)
3864     endif
3865   endif
3866   if empty(extra)
3867     let path = ''
3868   endif
3869   if s:HasOpt(args, '-g', '--walk-reflogs')
3870     let format = "%gd\t%H %gs"
3871   else
3872     let format = "%h\t%H " . g:fugitive_summary_format
3873   endif
3874   let cmd = ['--no-pager']
3875   if fugitive#GitVersion(1, 9)
3876     call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'])
3877   else
3878     call extend(cmd, ['log', '-U0', '--no-patch'])
3879   endif
3880   call extend(cmd,
3881         \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
3882         \ args + paths + extra)
3883   let state.target = path
3884   let title = (listnr < 0 ? ':Gclog ' : ':Gllog ') . s:fnameescape(args + paths)
3885   if empty(paths + extra) && empty(a:type) && len(s:Relative('/'))
3886     let after = '|echohl WarningMsg|echo ' . string('Use :0Glog or :0Gclog for old behavior of targeting current file') . '|echohl NONE' . after
3887   endif
3888   return s:QuickfixStream(listnr, title, s:UserCommandList(dir) + cmd, !a:bang, s:function('s:LogParse'), state, dir) . after
3889 endfunction
3891 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
3893 function! s:UsableWin(nr) abort
3894   return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
3895         \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
3896         \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
3897         \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
3898 endfunction
3900 function! s:OpenParse(args) abort
3901   let pre = []
3902   let args = copy(a:args)
3903   while !empty(args) && args[0] =~# '^+'
3904     call add(pre, ' ' . escape(remove(args, 0), ' |"'))
3905   endwhile
3906   if len(args)
3907     let file = join(args)
3908   elseif empty(expand('%'))
3909     let file = ''
3910   elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
3911     let file = '>:0'
3912   else
3913     let file = '>'
3914   endif
3915   return [s:Expand(file), join(pre)]
3916 endfunction
3918 function! s:DiffClose() abort
3919   let mywinnr = winnr()
3920   for winnr in [winnr('#')] + range(winnr('$'),1,-1)
3921     if winnr != mywinnr && getwinvar(winnr,'&diff')
3922       execute winnr.'wincmd w'
3923       close
3924       if winnr('$') > 1
3925         wincmd p
3926       endif
3927     endif
3928   endfor
3929   diffoff!
3930 endfunction
3932 function! s:BlurStatus() abort
3933   if (&previewwindow || exists('w:fugitive_status')) && get(b:,'fugitive_type', '') ==# 'index'
3934     let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
3935     if len(winnrs)
3936       exe winnrs[0].'wincmd w'
3937     else
3938       belowright new
3939     endif
3940     if &diff
3941       call s:DiffClose()
3942     endif
3943   endif
3944 endfunction
3946 function! s:OpenExec(cmd, mods, args, ...) abort
3947   let dir = a:0 ? s:Dir(a:1) : s:Dir()
3948   let temp = tempname()
3949   let columns = get(g:, 'fugitive_columns', 80)
3950   if columns <= 0
3951     let env = ''
3952   elseif s:winshell()
3953     let env = 'set COLUMNS=' . columns . '& '
3954   else
3955     let env = 'env COLUMNS=' . columns . ' '
3956   endif
3957   silent! execute '!' . escape(env . s:UserCommand(dir, ['--no-pager'] + a:args), '!#%') .
3958         \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
3959   redraw!
3960   let temp = s:Resolve(temp)
3961   let first = join(readfile(temp, '', 2), "\n")
3962   if first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
3963     let filetype = 'man'
3964   else
3965     let filetype = 'git'
3966   endif
3967   let s:temp_files[s:cpath(temp)] = { 'dir': dir, 'filetype': filetype, 'modifiable': first =~# '^diff ' }
3968   if a:cmd ==# 'edit'
3969     call s:BlurStatus()
3970   endif
3971   silent execute s:Mods(a:mods) . a:cmd temp
3972   call fugitive#ReloadStatus(dir, 1)
3973   return 'echo ' . string(':!' . s:UserCommand(dir, a:args))
3974 endfunction
3976 function! fugitive#Open(cmd, bang, mods, arg, args) abort
3977   if a:bang
3978     return s:OpenExec(a:cmd, a:mods, s:SplitExpand(a:arg, s:Tree()))
3979   endif
3981   let mods = s:Mods(a:mods)
3982   try
3983     let [file, pre] = s:OpenParse(a:args)
3984     let file = s:Generate(file)
3985   catch /^fugitive:/
3986     return 'echoerr ' . string(v:exception)
3987   endtry
3988   if file !~# '^\a\a\+:'
3989     let file = s:sub(file, '/$', '')
3990   endif
3991   if a:cmd ==# 'edit'
3992     call s:BlurStatus()
3993   endif
3994   return mods . a:cmd . pre . ' ' . s:fnameescape(file)
3995 endfunction
3997 function! fugitive#ReadCommand(line1, count, range, bang, mods, arg, args) abort
3998   let mods = s:Mods(a:mods)
3999   let after = a:count
4000   if a:count < 0
4001     let delete = 'silent 1,' . line('$') . 'delete_|'
4002     let after = line('$')
4003   elseif a:range == 2
4004     let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
4005   else
4006     let delete = ''
4007   endif
4008   if a:bang
4009     let dir = s:Dir()
4010     let args = s:SplitExpand(a:arg, s:Tree(dir))
4011     silent execute mods . after . 'read!' escape(s:UserCommand(dir, ['--no-pager'] + args), '!#%')
4012     execute delete . 'diffupdate'
4013     call fugitive#ReloadStatus()
4014     return 'redraw|echo '.string(':!'.s:UserCommand(dir, args))
4015   endif
4016   try
4017     let [file, pre] = s:OpenParse(a:args)
4018     let file = s:Generate(file)
4019   catch /^fugitive:/
4020     return 'echoerr ' . string(v:exception)
4021   endtry
4022   if file =~# '^fugitive:' && after is# 0
4023     return 'exe ' .string(mods . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
4024   endif
4025   if foldlevel(after)
4026     exe after . 'foldopen!'
4027   endif
4028   return mods . after . 'read' . pre . ' ' . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
4029 endfunction
4031 function! fugitive#ReadComplete(A, L, P) abort
4032   if a:L =~# '^\w\+!'
4033     return fugitive#Complete(a:A, a:L, a:P)
4034   else
4035     return fugitive#CompleteObject(a:A, a:L, a:P)
4036   endif
4037 endfunction
4039 " Section: :Gwrite, :Gwq
4041 function! fugitive#WriteCommand(line1, line2, range, bang, mods, arg, args) abort
4042   if exists('b:fugitive_commit_arguments')
4043     return 'write|bdelete'
4044   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
4045     return 'wq'
4046   elseif get(b:, 'fugitive_type', '') ==# 'index'
4047     return 'Gcommit'
4048   elseif &buftype ==# 'nowrite' && getline(4) =~# '^+++ '
4049     let filename = getline(4)[6:-1]
4050     setlocal buftype=
4051     silent write
4052     setlocal buftype=nowrite
4053     if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
4054       let [message, exec_error] = s:ChompError(['apply', '--cached', '--reverse', '--', expand('%:p')])
4055     else
4056       let [message, exec_error] = s:ChompError(['apply', '--cached', '--', expand('%:p')])
4057     endif
4058     if exec_error
4059       echohl ErrorMsg
4060       echo message
4061       echohl NONE
4062       return ''
4063     elseif a:bang
4064       return 'bdelete'
4065     else
4066       return 'Gedit '.fnameescape(filename)
4067     endif
4068   endif
4069   let mytab = tabpagenr()
4070   let mybufnr = bufnr('')
4071   try
4072     let file = len(a:args) ? s:Generate(s:Expand(join(a:args, ' '))) : fugitive#Real(@%)
4073   catch /^fugitive:/
4074     return 'echoerr ' . string(v:exception)
4075   endtry
4076   if empty(file)
4077     return 'echoerr '.string('fugitive: cannot determine file path')
4078   endif
4079   if file =~# '^fugitive:'
4080     return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
4081   endif
4082   exe s:DirCheck()
4083   let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
4084   if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
4085     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
4086     return 'echoerr v:errmsg'
4087   endif
4088   let treebufnr = 0
4089   for nr in range(1,bufnr('$'))
4090     if fnamemodify(bufname(nr),':p') ==# file
4091       let treebufnr = nr
4092     endif
4093   endfor
4095   if treebufnr > 0 && treebufnr != bufnr('')
4096     let temp = tempname()
4097     silent execute 'keepalt %write '.temp
4098     for tab in [mytab] + range(1,tabpagenr('$'))
4099       for winnr in range(1,tabpagewinnr(tab,'$'))
4100         if tabpagebuflist(tab)[winnr-1] == treebufnr
4101           execute 'tabnext '.tab
4102           if winnr != winnr()
4103             execute winnr.'wincmd w'
4104             let restorewinnr = 1
4105           endif
4106           try
4107             let lnum = line('.')
4108             let last = line('$')
4109             silent execute '$read '.temp
4110             silent execute '1,'.last.'delete_'
4111             silent write!
4112             silent execute lnum
4113             diffupdate
4114             let did = 1
4115           finally
4116             if exists('restorewinnr')
4117               wincmd p
4118             endif
4119             execute 'tabnext '.mytab
4120           endtry
4121           break
4122         endif
4123       endfor
4124     endfor
4125     if !exists('did')
4126       call writefile(readfile(temp,'b'),file,'b')
4127     endif
4128   else
4129     execute 'write! '.s:fnameescape(file)
4130   endif
4132   if a:bang
4133     let [error, exec_error] = s:ChompError(['add', '--force', '--', file])
4134   else
4135     let [error, exec_error] = s:ChompError(['add', '--', file])
4136   endif
4137   if exec_error
4138     let v:errmsg = 'fugitive: '.error
4139     return 'echoerr v:errmsg'
4140   endif
4141   if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
4142     setlocal nomodified
4143   endif
4145   let one = s:Generate(':1:'.file)
4146   let two = s:Generate(':2:'.file)
4147   let three = s:Generate(':3:'.file)
4148   for nr in range(1,bufnr('$'))
4149     let name = fnamemodify(bufname(nr), ':p')
4150     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
4151       execute nr.'bdelete'
4152     endif
4153   endfor
4155   unlet! restorewinnr
4156   let zero = s:Generate(':0:'.file)
4157   silent execute 'doautocmd' s:nomodeline 'BufWritePost' s:fnameescape(zero)
4158   for tab in range(1,tabpagenr('$'))
4159     for winnr in range(1,tabpagewinnr(tab,'$'))
4160       let bufnr = tabpagebuflist(tab)[winnr-1]
4161       let bufname = fnamemodify(bufname(bufnr), ':p')
4162       if bufname ==# zero && bufnr != mybufnr
4163         execute 'tabnext '.tab
4164         if winnr != winnr()
4165           execute winnr.'wincmd w'
4166           let restorewinnr = 1
4167         endif
4168         try
4169           let lnum = line('.')
4170           let last = line('$')
4171           silent execute '$read '.s:fnameescape(file)
4172           silent execute '1,'.last.'delete_'
4173           silent execute lnum
4174           setlocal nomodified
4175           diffupdate
4176         finally
4177           if exists('restorewinnr')
4178             wincmd p
4179           endif
4180           execute 'tabnext '.mytab
4181         endtry
4182         break
4183       endif
4184     endfor
4185   endfor
4186   call fugitive#ReloadStatus()
4187   return 'checktime'
4188 endfunction
4190 function! fugitive#WqCommand(...) abort
4191   let bang = a:4 ? '!' : ''
4192   if exists('b:fugitive_commit_arguments')
4193     return 'wq'.bang
4194   endif
4195   let result = call('fugitive#WriteCommand', a:000)
4196   if result =~# '^\%(write\|wq\|echoerr\)'
4197     return s:sub(result,'^write','wq')
4198   else
4199     return result.'|quit'.bang
4200   endif
4201 endfunction
4203 augroup fugitive_commit
4204   autocmd!
4205   autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute substitute(s:FinishCommit(), '\C^echoerr \(''[^'']*''\)*', 'redraw|echohl ErrorMsg|echo \1|echohl NONE', '')
4206 augroup END
4208 " Section: :Gpush, :Gfetch
4210 function! fugitive#PushComplete(A, L, P) abort
4211   return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompleteRemote'))
4212 endfunction
4214 function! fugitive#FetchComplete(A, L, P) abort
4215   return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'))
4216 endfunction
4218 function! s:AskPassArgs(dir) abort
4219   if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) && fugitive#GitVersion(1, 8) &&
4220         \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#Config('core.askPass', a:dir))
4221     if s:executable(s:ExecPath() . '/git-gui--askpass')
4222       return ['-c', 'core.askPass=' . s:ExecPath() . '/git-gui--askpass']
4223     elseif s:executable('ssh-askpass')
4224       return ['-c', 'core.askPass=ssh-askpass']
4225     endif
4226   endif
4227   return []
4228 endfunction
4230 function! s:Dispatch(bang, cmd, args) abort
4231   let dir = s:Dir()
4232   let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
4233   try
4234     let b:current_compiler = 'git'
4235     let &l:errorformat = s:common_efm
4236     let &l:makeprg = s:UserCommand(dir, s:AskPassArgs(dir) + [a:cmd] + a:args)
4237     if exists(':Make') == 2
4238       Make
4239       return ''
4240     else
4241       if !has('patch-8.1.0334') && has('terminal') && &autowrite
4242         let autowrite_was_set = 1
4243         set noautowrite
4244         silent! wall
4245       endif
4246       silent noautocmd make!
4247       redraw!
4248       return 'call fugitive#Cwindow()|silent doautocmd ShellCmdPost'
4249     endif
4250   finally
4251     let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
4252     if empty(cc) | unlet! b:current_compiler | endif
4253     if exists('autowrite_was_set')
4254       set autowrite
4255     endif
4256   endtry
4257 endfunction
4259 function! s:PushSubcommand(line1, line2, range, bang, mods, args) abort
4260   return s:Dispatch(a:bang ? '!' : '', 'push', a:args)
4261 endfunction
4263 function! s:FetchSubcommand(line1, line2, range, bang, mods, args) abort
4264   return s:Dispatch(a:bang ? '!' : '', 'fetch', a:args)
4265 endfunction
4267 call s:command("-nargs=? -bang -complete=customlist,fugitive#PushComplete Gpush", "push")
4268 call s:command("-nargs=? -bang -complete=customlist,fugitive#FetchComplete Gfetch", "fetch")
4270 " Section: :Gdiff
4272 augroup fugitive_diff
4273   autocmd!
4274   autocmd BufWinLeave *
4275         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
4276         \   call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
4277         \ endif
4278   autocmd BufWinEnter *
4279         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
4280         \   call s:diffoff() |
4281         \ endif
4282 augroup END
4284 function! s:can_diffoff(buf) abort
4285   return getwinvar(bufwinnr(a:buf), '&diff') &&
4286         \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
4287 endfunction
4289 function! fugitive#CanDiffoff(buf) abort
4290   return s:can_diffoff(bufnr(a:buf))
4291 endfunction
4293 function! s:diff_modifier(count) abort
4294   let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
4295   if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
4296     return ''
4297   elseif &diffopt =~# 'vertical'
4298     return 'vertical '
4299   elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
4300     return ''
4301   else
4302     return 'vertical '
4303   endif
4304 endfunction
4306 function! s:diff_window_count() abort
4307   let c = 0
4308   for nr in range(1,winnr('$'))
4309     let c += getwinvar(nr,'&diff')
4310   endfor
4311   return c
4312 endfunction
4314 function! s:diff_restore() abort
4315   let restore = 'setlocal nodiff noscrollbind'
4316         \ . ' scrollopt=' . &l:scrollopt
4317         \ . (&l:wrap ? ' wrap' : ' nowrap')
4318         \ . ' foldlevel=999'
4319         \ . ' foldmethod=' . &l:foldmethod
4320         \ . ' foldcolumn=' . &l:foldcolumn
4321         \ . ' foldlevel=' . &l:foldlevel
4322         \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
4323   if has('cursorbind')
4324     let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
4325   endif
4326   return restore
4327 endfunction
4329 function! s:diffthis() abort
4330   if !&diff
4331     let w:fugitive_diff_restore = s:diff_restore()
4332     diffthis
4333   endif
4334 endfunction
4336 function! s:diffoff() abort
4337   if exists('w:fugitive_diff_restore')
4338     execute w:fugitive_diff_restore
4339     unlet w:fugitive_diff_restore
4340   else
4341     diffoff
4342   endif
4343 endfunction
4345 function! s:diffoff_all(dir) abort
4346   let curwin = winnr()
4347   for nr in range(1,winnr('$'))
4348     if getwinvar(nr,'&diff')
4349       if nr != winnr()
4350         execute nr.'wincmd w'
4351         let restorewinnr = 1
4352       endif
4353       if s:Dir() ==# a:dir
4354         call s:diffoff()
4355       endif
4356     endif
4357   endfor
4358   execute curwin.'wincmd w'
4359 endfunction
4361 function! s:CompareAge(mine, theirs) abort
4362   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
4363   let mine = substitute(a:mine, '^:', '', '')
4364   let theirs = substitute(a:theirs, '^:', '', '')
4365   let my_score    = get(scores, ':'.mine, 0)
4366   let their_score = get(scores, ':'.theirs, 0)
4367   if my_score || their_score
4368     return my_score < their_score ? -1 : my_score != their_score
4369   elseif mine ==# theirs
4370     return 0
4371   endif
4372   let base = s:TreeChomp('merge-base', mine, theirs)
4373   if base ==# mine
4374     return -1
4375   elseif base ==# theirs
4376     return 1
4377   endif
4378   let my_time    = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:mine, '--')
4379   let their_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:theirs, '--')
4380   return my_time < their_time ? -1 : my_time != their_time
4381 endfunction
4383 function! s:IsConflicted() abort
4384   return len(@%) && !empty(s:ChompDefault('', 'ls-files', '--unmerged', '--', expand('%:p')))
4385 endfunction
4387 function! fugitive#Diffsplit(autodir, keepfocus, mods, arg, args) abort
4388   let args = copy(a:args)
4389   let post = ''
4390   if get(args, 0) =~# '^+'
4391     let post = remove(args, 0)[1:-1]
4392   endif
4393   if exists(':DiffGitCached') && empty(args)
4394     return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
4395   endif
4396   let commit = s:DirCommitFile(@%)[1]
4397   if a:mods =~# '\<tab\>'
4398     let mods = substitute(a:mods, '\<tab\>', '', 'g')
4399     let pre = 'tab split'
4400   else
4401     let mods = 'keepalt ' . a:mods
4402     let pre = ''
4403   endif
4404   let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
4405   if (empty(args) || args[0] ==# ':') && a:keepfocus
4406     exe s:DirCheck()
4407     if empty(commit) && s:IsConflicted()
4408       let parents = [s:Relative(':2:'), s:Relative(':3:')]
4409     elseif empty(commit)
4410       let parents = [s:Relative(':0:')]
4411     elseif commit =~# '^\d\=$'
4412       let parents = [s:Relative('HEAD:')]
4413     elseif commit =~# '^\x\x\+$'
4414       let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
4415       call map(parents, 's:Relative(v:val . ":")')
4416     endif
4417   endif
4418   try
4419     if exists('parents') && len(parents) > 1
4420       exe pre
4421       let mods = (a:autodir ? s:diff_modifier(len(parents) + 1) : '') . s:Mods(mods, 'leftabove')
4422       let nr = bufnr('')
4423       execute mods 'split' s:fnameescape(s:Generate(parents[0]))
4424       call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4425       let nr2 = bufnr('')
4426       call s:diffthis()
4427       exe back
4428       call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
4429       let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
4430       for i in range(len(parents)-1, 1, -1)
4431         execute mods 'split' s:fnameescape(s:Generate(parents[i]))
4432         call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4433         let nrx = bufnr('')
4434         call s:diffthis()
4435         exe back
4436         call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
4437       endfor
4438       call s:diffthis()
4439       if len(parents) > 1
4440         wincmd =
4441       endif
4442       return post
4443     elseif len(args)
4444       let arg = join(args, ' ')
4445       if arg ==# ''
4446         return post
4447       elseif arg ==# ':/'
4448         exe s:DirCheck()
4449         let file = s:Relative()
4450       elseif arg ==# ':'
4451         exe s:DirCheck()
4452         let file = s:Relative(':0:')
4453       elseif arg =~# '^:\d$'
4454         exe s:DirCheck()
4455         let file = s:Relative(arg . ':')
4456       else
4457         try
4458           let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
4459         catch /^fugitive:/
4460           return 'echoerr ' . string(v:exception)
4461         endtry
4462       endif
4463     elseif exists('parents') && len(parents)
4464       let file = parents[-1]
4465     elseif len(commit)
4466       let file = s:Relative()
4467     elseif s:IsConflicted()
4468       let file = s:Relative(':1:')
4469       let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
4470     else
4471       exe s:DirCheck()
4472       let file = s:Relative(':0:')
4473     endif
4474     let spec = s:Generate(file)
4475     if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
4476       let spec = FugitiveVimPath(spec . s:Relative('/'))
4477     endif
4478     exe pre
4479     let restore = s:diff_restore()
4480     let w:fugitive_diff_restore = restore
4481     if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
4482       let mods = s:Mods(mods, 'rightbelow')
4483     else
4484       let mods = s:Mods(mods, 'leftabove')
4485     endif
4486     let mods = (a:autodir ? s:diff_modifier(2) : '') . mods
4487     if &diffopt =~# 'vertical'
4488       let diffopt = &diffopt
4489       set diffopt-=vertical
4490     endif
4491     execute mods 'diffsplit' s:fnameescape(spec)
4492     let &l:readonly = &l:readonly
4493     redraw
4494     let w:fugitive_diff_restore = restore
4495     let winnr = winnr()
4496     if getwinvar('#', '&diff')
4497       if a:keepfocus
4498         exe back
4499       endif
4500     endif
4501     return post
4502   catch /^fugitive:/
4503     return 'echoerr ' . string(v:exception)
4504   finally
4505     if exists('diffopt')
4506       let &diffopt = diffopt
4507     endif
4508   endtry
4509 endfunction
4511 " Section: :Gmove, :Gremove
4513 function! s:Move(force, rename, destination) abort
4514   let dir = s:Dir()
4515   exe s:DirCheck(dir)
4516   if s:DirCommitFile(@%)[1] !~# '^0\=$' || empty(@%)
4517     return 'echoerr ' . string('fugitive: mv not supported for this buffer')
4518   endif
4519   if a:destination =~# '^\.\.\=\%(/\|$\)'
4520     let destination = simplify(getcwd() . '/' . a:destination)
4521   elseif a:destination =~# '^\a\+:\|^/'
4522     let destination = a:destination
4523   elseif a:destination =~# '^:/:\='
4524     let destination = s:Tree(dir) . substitute(a:destination, '^:/:\=', '', '')
4525   elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\))'
4526     let destination = s:Tree(dir) . matchstr(a:destination, ')\zs.*')
4527   elseif a:destination =~# '^:(literal)'
4528     let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
4529   elseif a:rename
4530     let destination = expand('%:p:s?[\/]$??:h') . '/' . a:destination
4531   else
4532     let destination = s:Tree(dir) . '/' . a:destination
4533   endif
4534   let destination = s:Slash(destination)
4535   if isdirectory(@%)
4536     setlocal noswapfile
4537   endif
4538   let [message, exec_error] = s:ChompError(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination], dir)
4539   if exec_error
4540     let v:errmsg = 'fugitive: '.message
4541     return 'echoerr v:errmsg'
4542   endif
4543   if isdirectory(destination)
4544     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
4545   endif
4546   call fugitive#ReloadStatus(dir)
4547   if empty(s:DirCommitFile(@%)[1])
4548     if isdirectory(destination)
4549       return 'keepalt edit '.s:fnameescape(destination)
4550     else
4551       return 'keepalt saveas! '.s:fnameescape(destination)
4552     endif
4553   else
4554     return 'file '.s:fnameescape(fugitive#Find(':0:'.destination, dir))
4555   endif
4556 endfunction
4558 function! fugitive#RenameComplete(A,L,P) abort
4559   if a:A =~# '^[.:]\=/'
4560     return fugitive#CompletePath(a:A)
4561   else
4562     let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
4563     return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
4564   endif
4565 endfunction
4567 function! fugitive#MoveCommand(line1, line2, range, bang, mods, arg, args) abort
4568   return s:Move(a:bang, 0, a:arg)
4569 endfunction
4571 function! fugitive#RenameCommand(line1, line2, range, bang, mods, arg, args) abort
4572   return s:Move(a:bang, 1, a:arg)
4573 endfunction
4575 function! s:Remove(after, force) abort
4576   let dir = s:Dir()
4577   exe s:DirCheck(dir)
4578   if len(@%) && s:DirCommitFile(@%)[1] ==# ''
4579     let cmd = ['rm']
4580   elseif s:DirCommitFile(@%)[1] ==# '0'
4581     let cmd = ['rm','--cached']
4582   else
4583     return 'echoerr ' . string('fugitive: rm not supported for this buffer')
4584   endif
4585   if a:force
4586     let cmd += ['--force']
4587   endif
4588   let [message, exec_error] = s:ChompError(cmd + ['--', expand('%:p')], dir)
4589   if exec_error
4590     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
4591     return 'echoerr '.string(v:errmsg)
4592   else
4593     call fugitive#ReloadStatus(dir)
4594     return a:after . (a:force ? '!' : '')
4595   endif
4596 endfunction
4598 function! fugitive#RemoveCommand(line1, line2, range, bang, mods, arg, args) abort
4599   return s:Remove('edit', a:bang)
4600 endfunction
4602 function! fugitive#DeleteCommand(line1, line2, range, bang, mods, arg, args) abort
4603   return s:Remove('bdelete', a:bang)
4604 endfunction
4606 " Section: :Gblame
4608 function! s:Keywordprg() abort
4609   let args = ' --git-dir='.escape(s:Dir(),"\\\"' ")
4610   if has('gui_running') && !has('win32')
4611     return s:UserCommand() . ' --no-pager' . args . ' log -1'
4612   else
4613     return s:UserCommand() . args . ' show'
4614   endif
4615 endfunction
4617 function! s:linechars(pattern) abort
4618   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
4619   if exists('*synconcealed') && &conceallevel > 1
4620     for col in range(1, chars)
4621       let chars -= synconcealed(line('.'), col)[0]
4622     endfor
4623   endif
4624   return chars
4625 endfunction
4627 function! s:BlameBufnr(...) abort
4628   let state = s:TempState(bufname(a:0 ? a:1 : ''))
4629   if get(state, 'filetype', '') ==# 'fugitiveblame'
4630     return get(state, 'bufnr', -1)
4631   else
4632     return -1
4633   endif
4634 endfunction
4636 function! s:BlameCommitFileLnum(...) abort
4637   let line = a:0 ? a:1 : getline('.')
4638   let state = a:0 ? a:2 : s:TempState()
4639   let commit = matchstr(line, '^\^\=\zs\x\+')
4640   if commit =~# '^0\+$'
4641     let commit = ''
4642   elseif line !~# '^\^' && has_key(state, 'blame_reverse_end')
4643     let commit = get(s:LinesError('rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end)[0], 0, '')
4644   endif
4645   let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
4646   let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s\+\%(\%( \d\+ \)\@<!([^()]*\w \d\+)\|\d\+ \)')
4647   if empty(path) && lnum
4648     let path = get(state, 'blame_file', '')
4649   endif
4650   return [commit, path, lnum]
4651 endfunction
4653 function! s:BlameLeave() abort
4654   let bufwinnr = bufwinnr(s:BlameBufnr())
4655   if bufwinnr > 0
4656     let bufnr = bufnr('')
4657     exe bufwinnr . 'wincmd w'
4658     return bufnr . 'bdelete'
4659   endif
4660   return ''
4661 endfunction
4663 function! s:BlameQuit() abort
4664   let cmd = s:BlameLeave()
4665   if empty(cmd)
4666     return 'bdelete'
4667   elseif len(s:DirCommitFile(@%)[1])
4668     return cmd . '|Gedit'
4669   else
4670     return cmd
4671   endif
4672 endfunction
4674 function! fugitive#BlameComplete(A, L, P) abort
4675   return s:CompleteSub('blame', a:A, a:L, a:P)
4676 endfunction
4678 function! s:BlameSubcommand(line1, count, range, bang, mods, args) abort
4679   exe s:DirCheck()
4680   let flags = copy(a:args)
4681   let i = 0
4682   let raw = 0
4683   let commits = []
4684   let files = []
4685   let ranges = []
4686   if a:line1 > 0 && a:count > 0 && a:range != 1
4687     call extend(ranges, ['-L', a:line1 . ',' . a:count])
4688   endif
4689   while i < len(flags)
4690     let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
4691     if len(match) && len(match[2])
4692       call insert(flags, match[1])
4693       let flags[i+1] = '-' . match[2]
4694       continue
4695     endif
4696     let arg = flags[i]
4697     if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
4698       let raw = 1
4699     elseif arg ==# '--contents' && i + 1 < len(flags)
4700       call extend(commits, remove(flags, i, i+1))
4701       continue
4702     elseif arg ==# '-L' && i + 1 < len(flags)
4703       call extend(ranges, remove(flags, i, i+1))
4704       continue
4705     elseif arg =~# '^--contents='
4706       call add(commits, remove(flags, i))
4707       continue
4708     elseif arg =~# '^-L.'
4709       call add(ranges, remove(flags, i))
4710       continue
4711     elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
4712       let i += 1
4713       if i == len(flags)
4714         echohl ErrorMsg
4715         echo s:ChompError(['blame', arg])[0]
4716         echohl NONE
4717         return ''
4718       endif
4719     elseif arg ==# '--'
4720       if i + 1 < len(flags)
4721         call extend(files, remove(flags, i + 1, -1))
4722       endif
4723       call remove(flags, i)
4724       break
4725     elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
4726       if index(flags, '--') >= 0
4727         call add(commits, remove(flags, i))
4728         continue
4729       endif
4730       if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
4731         call add(commits, remove(flags, i))
4732         continue
4733       endif
4734       try
4735         let dcf = s:DirCommitFile(fugitive#Find(arg))
4736         if len(dcf[1]) && empty(dcf[2])
4737           call add(commits, remove(flags, i))
4738           continue
4739         endif
4740       catch /^fugitive:/
4741       endtry
4742       call add(files, remove(flags, i))
4743       continue
4744     endif
4745     let i += 1
4746   endwhile
4747   let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./'))), '^\.\%(/\|$\)', '', '')
4748   if empty(commits) && len(files) > 1
4749     call add(commits, remove(files, 1))
4750   endif
4751   exe s:BlameLeave()
4752   try
4753     let cmd = ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', 'blame', '--show-number']
4754     call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
4755     if a:count > 0 && empty(ranges)
4756       let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
4757     endif
4758     call extend(cmd, ranges)
4759     if len(commits)
4760       let cmd += commits
4761     elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
4762       let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
4763     elseif empty(files) && !s:HasOpt(flags, '--reverse')
4764       let cmd += ['--contents', '-']
4765     endif
4766     let basecmd = escape(fugitive#Prepare(cmd) . ' -- ' . s:shellesc(len(files) ? files : file), '!#%')
4767     let tempname = tempname()
4768     let error = tempname . '.err'
4769     let temp = tempname . (raw ? '' : '.fugitiveblame')
4770     if &shell =~# 'csh'
4771       silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
4772     else
4773       silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
4774     endif
4775     redraw
4776     try
4777       if v:shell_error
4778         let lines = readfile(error)
4779         if empty(lines)
4780           let lines = readfile(temp)
4781         endif
4782         for i in range(len(lines))
4783           if lines[i] =~# '^error: \|^fatal: '
4784             echohl ErrorMsg
4785             echon lines[i]
4786             echohl NONE
4787             break
4788           else
4789             echon lines[i]
4790           endif
4791           if i != len(lines) - 1
4792             echon "\n"
4793           endif
4794         endfor
4795         return ''
4796       endif
4797       let temp_state = {'dir': s:Dir(), 'filetype': (raw ? '' : 'fugitiveblame'), 'blame_flags': flags, 'blame_file': file, 'modifiable': 0}
4798       if s:HasOpt(flags, '--reverse')
4799         let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
4800       endif
4801       if (a:line1 == 0 || a:range == 1) && a:count > 0
4802         let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit'], a:count - (a:line1 ? a:line1 : 1), 'split')
4803         return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
4804       else
4805         let temp = s:Resolve(temp)
4806         let s:temp_files[s:cpath(temp)] = temp_state
4807         if len(ranges + commits + files) || raw
4808           let mods = s:Mods(a:mods)
4809           if a:count != 0
4810             exe 'silent keepalt' mods 'split' s:fnameescape(temp)
4811           elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
4812             exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
4813           else
4814             return mods . 'edit ' . s:fnameescape(temp)
4815           endif
4816           return ''
4817         endif
4818         if a:mods =~# '\<tab\>'
4819           silent tabedit %
4820         endif
4821         let mods = substitute(a:mods, '\<tab\>', '', 'g')
4822         for winnr in range(winnr('$'),1,-1)
4823           if getwinvar(winnr, '&scrollbind')
4824             call setwinvar(winnr, '&scrollbind', 0)
4825           endif
4826           if exists('+cursorbind') && getwinvar(winnr, '&cursorbind')
4827             call setwinvar(winnr, '&cursorbind', 0)
4828           endif
4829           if s:BlameBufnr(winbufnr(winnr)) > 0
4830             execute winbufnr(winnr).'bdelete'
4831           endif
4832         endfor
4833         let bufnr = bufnr('')
4834         let temp_state.bufnr = bufnr
4835         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
4836         if exists('+cursorbind')
4837           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
4838         endif
4839         if &l:wrap
4840           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
4841         endif
4842         if &l:foldenable
4843           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
4844         endif
4845         setlocal scrollbind nowrap nofoldenable
4846         if exists('+cursorbind')
4847           setlocal cursorbind
4848         endif
4849         let top = line('w0') + &scrolloff
4850         let current = line('.')
4851         exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
4852         let w:fugitive_leave = restore
4853         execute top
4854         normal! zt
4855         execute current
4856         if exists('+cursorbind')
4857           setlocal cursorbind
4858         endif
4859         setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
4860         if exists('+relativenumber')
4861           setlocal norelativenumber
4862         endif
4863         execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
4864         call s:Map('n', 'A', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>')
4865         call s:Map('n', 'C', ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>')
4866         call s:Map('n', 'D', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>')
4867         redraw
4868         syncbind
4869       endif
4870     endtry
4871     return ''
4872   catch /^fugitive:/
4873     return 'echoerr ' . string(v:exception)
4874   endtry
4875 endfunction
4877 function! s:BlameCommit(cmd, ...) abort
4878   let line = a:0 ? a:1 : getline('.')
4879   let state = a:0 ? a:2 : s:TempState()
4880   let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
4881   let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
4882   let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
4883   if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
4884     let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
4885     return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
4886   endif
4887   if commit =~# '^0*$'
4888     return 'echoerr ' . string('fugitive: no commit')
4889   endif
4890   if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
4891     let path = commit . ':' . path
4892     return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
4893   endif
4894   let cmd = fugitive#Open(mods . a:cmd, 0, '', commit, [commit])
4895   if cmd =~# '^echoerr'
4896     return cmd
4897   endif
4898   execute cmd
4899   if a:cmd ==# 'pedit' || empty(path)
4900     return ''
4901   endif
4902   if search('^diff .* b/\M'.escape(path,'\').'$','W')
4903     call search('^+++')
4904     let head = line('.')
4905     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
4906       let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
4907       let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
4908       if lnum >= top && lnum <= top + len
4909         let offset = lnum - top
4910         if &scrolloff
4911           +
4912           normal! zt
4913         else
4914           normal! zt
4915           +
4916         endif
4917         while offset > 0 && line('.') < line('$')
4918           +
4919           if getline('.') =~# '^[ ' . sigil . ']'
4920             let offset -= 1
4921           endif
4922         endwhile
4923         return 'normal! zv'
4924       endif
4925     endwhile
4926     execute head
4927     normal! zt
4928   endif
4929   return ''
4930 endfunction
4932 function! s:BlameJump(suffix, ...) abort
4933   let suffix = a:suffix
4934   let [commit, path, lnum] = s:BlameCommitFileLnum()
4935   if empty(path)
4936     return 'echoerr ' . string('fugitive: could not determine filename for blame')
4937   endif
4938   if commit =~# '^0*$'
4939     let commit = 'HEAD'
4940     let suffix = ''
4941   endif
4942   let offset = line('.') - line('w0')
4943   let flags = get(s:TempState(), 'blame_flags', [])
4944   if a:0 && a:1
4945     if s:HasOpt(flags, '--reverse')
4946       call remove(flags, '--reverse')
4947     else
4948       call add(flags, '--reverse')
4949     endif
4950   endif
4951   let blame_bufnr = s:BlameBufnr()
4952   if blame_bufnr > 0
4953     let bufnr = bufnr('')
4954     let winnr = bufwinnr(blame_bufnr)
4955     if winnr > 0
4956       exe winnr.'wincmd w'
4957     endif
4958     execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
4959     execute lnum
4960     if winnr > 0
4961       exe bufnr.'bdelete'
4962     endif
4963   endif
4964   if exists(':Gblame')
4965     let my_bufnr = bufnr('')
4966     if blame_bufnr < 0
4967       let blame_args = flags + [commit . suffix, '--', path]
4968       let result = s:BlameSubcommand(0, 0, 0, 0, '', blame_args)
4969     else
4970       let blame_args = flags
4971       let result = s:BlameSubcommand(-1, -1, 0, 0, '', blame_args)
4972     endif
4973     if bufnr('') == my_bufnr
4974       return result
4975     endif
4976     execute result
4977     execute lnum
4978     let delta = line('.') - line('w0') - offset
4979     if delta > 0
4980       execute 'normal! '.delta."\<C-E>"
4981     elseif delta < 0
4982       execute 'normal! '.(-delta)."\<C-Y>"
4983     endif
4984     keepjumps syncbind
4985     redraw
4986     echo ':Gblame' s:fnameescape(blame_args)
4987   endif
4988   return ''
4989 endfunction
4991 let s:hash_colors = {}
4993 function! fugitive#BlameSyntax() abort
4994   let conceal = has('conceal') ? ' conceal' : ''
4995   let config = fugitive#Config()
4996   let flags = get(s:TempState(), 'blame_flags', [])
4997   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
4998   syn match FugitiveblameHash       "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
4999   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5000   if get(get(config, 'blame.blankboundary', ['x']), 0, 'x') =~# '^$\|^true$' || s:HasOpt(flags, '-b')
5001     syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5002   else
5003     syn match FugitiveblameBoundary "^\^"
5004   endif
5005   syn match FugitiveblameScoreDebug        " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
5006   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
5007   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
5008   exec 'syn match FugitiveblameLineNumber         "\s*\d\+)\@=" contained containedin=FugitiveblameAnnotation' conceal
5009   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)
5010   exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5011   exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5012   syn match FugitiveblameShort              " \d\+)" contained contains=FugitiveblameLineNumber
5013   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
5014   hi def link FugitiveblameBoundary           Keyword
5015   hi def link FugitiveblameHash               Identifier
5016   hi def link FugitiveblameBoundaryIgnore     Ignore
5017   hi def link FugitiveblameUncommitted        Ignore
5018   hi def link FugitiveblameScoreDebug         Debug
5019   hi def link FugitiveblameTime               PreProc
5020   hi def link FugitiveblameLineNumber         Number
5021   hi def link FugitiveblameOriginalFile       String
5022   hi def link FugitiveblameOriginalLineNumber Float
5023   hi def link FugitiveblameShort              FugitiveblameDelimiter
5024   hi def link FugitiveblameDelimiter          Delimiter
5025   hi def link FugitiveblameNotCommittedYet    Comment
5026   if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
5027     return
5028   endif
5029   let seen = {}
5030   for lnum in range(1, line('$'))
5031     let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
5032     if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
5033       continue
5034     endif
5035     let seen[hash] = 1
5036     if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
5037           \ && empty(get(s:hash_colors, hash))
5038       let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
5039       let color = csapprox#per_component#Approximate(r, g, b)
5040       if color == 16 && &background ==# 'dark'
5041         let color = 8
5042       endif
5043       let s:hash_colors[hash] = ' ctermfg='.color
5044     else
5045       let s:hash_colors[hash] = ''
5046     endif
5047     exe 'syn match FugitiveblameHash'.hash.'       "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
5048   endfor
5049   call s:BlameRehighlight()
5050 endfunction
5052 function! s:BlameRehighlight() abort
5053   for [hash, cterm] in items(s:hash_colors)
5054     if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
5055       exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
5056     else
5057       exe 'hi link FugitiveblameHash'.hash.' Identifier'
5058     endif
5059   endfor
5060 endfunction
5062 function! s:BlameFileType() abort
5063   setlocal nomodeline
5064   setlocal foldmethod=manual
5065   if len(s:Dir())
5066     let &l:keywordprg = s:Keywordprg()
5067   endif
5068   let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
5069   if exists('+concealcursor')
5070     setlocal concealcursor=nc conceallevel=2
5071     let b:undo_ftplugin .= ' concealcursor< conceallevel<'
5072   endif
5073   if &modifiable
5074     return ''
5075   endif
5076   call s:Map('n', '<F1>', ':help fugitive-:Gblame<CR>', '<silent>')
5077   call s:Map('n', 'g?',   ':help fugitive-:Gblame<CR>', '<silent>')
5078   if mapcheck('q', 'n') =~# '^$\|bdelete'
5079     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>')
5080   endif
5081   call s:Map('n', 'gq',   ':exe <SID>BlameQuit()<CR>', '<silent>')
5082   call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5083   call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5084   call s:Map('n', '-',    ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>')
5085   call s:Map('n', 'P',    ':<C-U>exe <SID>BlameJump("^".v:count1)<CR>', '<silent>')
5086   call s:Map('n', '~',    ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>')
5087   call s:Map('n', 'i',    ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5088   call s:Map('n', 'o',    ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>')
5089   call s:Map('n', 'O',    ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>')
5090   call s:Map('n', 'p',    ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>')
5091 endfunction
5093 augroup fugitive_blame
5094   autocmd!
5095   autocmd FileType fugitiveblame call s:BlameFileType()
5096   autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
5097   autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
5098 augroup END
5100 call s:command('-buffer -bang -range=-1 -nargs=? -complete=customlist,fugitive#BlameComplete Gblame', 'blame')
5102 " Section: :Gbrowse
5104 let s:redirects = {}
5106 function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, args) abort
5107   let dir = s:Dir()
5108   exe s:DirCheck(dir)
5109   try
5110     let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
5111     if a:args ==# ['-']
5112       if a:count >= 0
5113         return 'echoerr ' . string('fugitive: ''-'' no longer required to get persistent URL if range given')
5114       else
5115         return 'echoerr ' . string('fugitive: use :0Gbrowse instead of :Gbrowse -')
5116       endif
5117     elseif len(a:args)
5118       let remote = matchstr(join(a:args, ' '),'@\zs\%('.validremote.'\)$')
5119       let rev = substitute(join(a:args, ' '),'@\%('.validremote.'\)$','','')
5120     else
5121       let remote = ''
5122       let rev = ''
5123     endif
5124     if rev ==# ''
5125       let rev = s:DirRev(@%)[1]
5126     endif
5127     if rev =~# '^:\=$'
5128       let expanded = s:Relative()
5129     else
5130       let expanded = s:Expand(rev)
5131     endif
5132     let cdir = FugitiveVimPath(fugitive#CommonDir(dir))
5133     for subdir in ['tags/', 'heads/', 'remotes/']
5134       if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . subdir . expanded)
5135         let expanded = '.git/refs/' . subdir . expanded
5136       endif
5137     endfor
5138     let full = fugitive#Find(expanded, dir)
5139     let commit = ''
5140     if full =~? '^fugitive:'
5141       let [pathdir, commit, path] = s:DirCommitFile(full)
5142       if commit =~# '^:\=\d$'
5143         let commit = ''
5144       endif
5145       if commit =~ '..'
5146         let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
5147         let branch = matchstr(expanded, '^[^:]*')
5148       else
5149         let type = 'blob'
5150       endif
5151       let path = path[1:-1]
5152     elseif empty(s:Tree(dir))
5153       let path = '.git/' . full[strlen(dir)+1:-1]
5154       let type = ''
5155     else
5156       let path = fugitive#Path(full, '/')[1:-1]
5157       if path =~# '^\.git/'
5158         let type = ''
5159       elseif isdirectory(full) || empty(path)
5160         let type = 'tree'
5161       else
5162         let type = 'blob'
5163       endif
5164     endif
5165     if type ==# 'tree' && !empty(path)
5166       let path = s:sub(path, '/\=$', '/')
5167     endif
5168     if path =~# '^\.git/.*HEAD$' && filereadable(dir . '/' . path[5:-1])
5169       let body = readfile(dir . '/' . path[5:-1])[0]
5170       if body =~# '^\x\{40,\}$'
5171         let commit = body
5172         let type = 'commit'
5173         let path = ''
5174       elseif body =~# '^ref: refs/'
5175         let path = '.git/' . matchstr(body,'ref: \zs.*')
5176       endif
5177     endif
5179     let merge = ''
5180     if path =~# '^\.git/refs/remotes/.'
5181       if empty(remote)
5182         let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
5183         let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5184       else
5185         let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5186         let path = '.git/refs/heads/'.merge
5187       endif
5188     elseif path =~# '^\.git/refs/heads/.'
5189       let branch = path[16:-1]
5190     elseif !exists('branch')
5191       let branch = FugitiveHead()
5192     endif
5193     if !empty(branch)
5194       let r = fugitive#Config('branch.'.branch.'.remote')
5195       let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
5196       if r ==# '.' && !empty(m)
5197         let r2 = fugitive#Config('branch.'.m.'.remote')
5198         if r2 !~# '^\.\=$'
5199           let r = r2
5200           let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
5201         endif
5202       endif
5203       if empty(remote)
5204         let remote = r
5205       endif
5206       if r ==# '.' || r ==# remote
5207         let merge = m
5208         if path =~# '^\.git/refs/heads/.'
5209           let path = '.git/refs/heads/'.merge
5210         endif
5211       endif
5212     endif
5214     let line1 = a:count > 0 ? a:line1 : 0
5215     let line2 = a:count > 0 ? a:count : 0
5216     if empty(commit) && path !~# '^\.git/'
5217       if a:count < 0 && !empty(merge)
5218         let commit = merge
5219       else
5220         let commit = ''
5221         if len(merge)
5222           let owner = s:Owner(@%)
5223           let [commit, exec_error] = s:ChompError(['merge-base', 'refs/remotes/' . remote . '/' . merge, empty(owner) ? 'HEAD' : owner, '--'])
5224           if exec_error
5225             let commit = ''
5226           endif
5227           if a:count > 0 && empty(a:args) && commit =~# '^\x\{40,\}$'
5228             let blame_list = tempname()
5229             call writefile([commit, ''], blame_list, 'b')
5230             let blame_in = tempname()
5231             silent exe '%write' blame_in
5232             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])
5233             if !exec_error
5234               let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
5235               if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
5236                 let line1 = +matchstr(blame[0], blame_regex)
5237                 let line2 = +matchstr(blame[-1], blame_regex)
5238               else
5239                 call s:throw("Can't browse to uncommitted change")
5240               endif
5241             endif
5242           endif
5243         endif
5244       endif
5245       if empty(commit)
5246         let commit = readfile(fugitive#Find('.git/HEAD', dir), '', 1)[0]
5247       endif
5248       let i = 0
5249       while commit =~# '^ref: ' && i < 10
5250         let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
5251         let i -= 1
5252       endwhile
5253     endif
5255     if empty(remote)
5256       let remote = '.'
5257     endif
5258     let raw = fugitive#RemoteUrl(remote)
5259     if empty(raw)
5260       let raw = remote
5261     endif
5263     if raw =~# '^https\=://' && s:executable('curl')
5264       if !has_key(s:redirects, raw)
5265         let s:redirects[raw] = matchstr(system('curl -I ' .
5266               \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
5267               \ 'Location: \zs\S\+\ze/info/refs?')
5268       endif
5269       if len(s:redirects[raw])
5270         let raw = s:redirects[raw]
5271       endif
5272     endif
5274     let opts = {
5275           \ 'dir': dir,
5276           \ 'repo': fugitive#repo(dir),
5277           \ 'remote': raw,
5278           \ 'revision': 'No longer provided',
5279           \ 'commit': commit,
5280           \ 'path': path,
5281           \ 'type': type,
5282           \ 'line1': line1,
5283           \ 'line2': line2}
5285     let url = ''
5286     for Handler in get(g:, 'fugitive_browse_handlers', [])
5287       let url = call(Handler, [copy(opts)])
5288       if !empty(url)
5289         break
5290       endif
5291     endfor
5293     if empty(url)
5294       call s:throw("No Gbrowse handler installed for '".raw."'")
5295     endif
5297     let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
5298     if a:bang
5299       if has('clipboard')
5300         let @+ = url
5301       endif
5302       return 'echomsg '.string(url)
5303     elseif exists(':Browse') == 2
5304       return 'echomsg '.string(url).'|Browse '.url
5305     else
5306       if !exists('g:loaded_netrw')
5307         runtime! autoload/netrw.vim
5308       endif
5309       if exists('*netrw#BrowseX')
5310         return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
5311       else
5312         return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
5313       endif
5314     endif
5315   catch /^fugitive:/
5316     return 'echoerr ' . string(v:exception)
5317   endtry
5318 endfunction
5320 " Section: Go to file
5322 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
5323 function! fugitive#MapCfile(...) abort
5324   exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
5325   let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
5326   if !exists('g:fugitive_no_maps')
5327     call s:Map('n', 'gf',          '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
5328     call s:Map('n', '<C-W>f',     '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5329     call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5330     call s:Map('n', '<C-W>gf',  '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
5331     call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
5332   endif
5333 endfunction
5335 function! s:ContainingCommit() abort
5336   let commit = s:Owner(@%)
5337   return empty(commit) ? 'HEAD' : commit
5338 endfunction
5340 function! s:SquashArgument(...) abort
5341   if &filetype == 'fugitive'
5342     let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze ')
5343   elseif has_key(s:temp_files, s:cpath(expand('%:p')))
5344     let commit = matchstr(getline('.'), '\<\x\{4,\}\>')
5345   else
5346     let commit = s:Owner(@%)
5347   endif
5348   return len(commit) && a:0 ? printf(a:1, commit) : commit
5349 endfunction
5351 function! s:RebaseArgument() abort
5352   return s:SquashArgument(' %s^')
5353 endfunction
5355 function! s:NavigateUp(count) abort
5356   let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
5357   let c = a:count
5358   while c
5359     if rev =~# ':.*/.'
5360       let rev = matchstr(rev, '.*\ze/.\+', '')
5361     elseif rev =~# '.:.'
5362       let rev = matchstr(rev, '^.[^:]*:')
5363     elseif rev =~# '^:'
5364       let rev = 'HEAD^{}'
5365     elseif rev =~# ':$'
5366       let rev = rev[0:-2]
5367     else
5368       return rev.'~'.c
5369     endif
5370     let c -= 1
5371   endwhile
5372   return rev
5373 endfunction
5375 function! s:MapMotion(lhs, rhs) abort
5376   call s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5377   call s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5378   call s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")
5379 endfunction
5381 function! fugitive#MapJumps(...) abort
5382   if !&modifiable
5383     if get(b:, 'fugitive_type', '') ==# 'blob'
5384       let blame_map = 'Gblame<C-R>=v:count ? " --reverse" : ""<CR><CR>'
5385       call s:Map('n', '<2-LeftMouse>', ':<C-U>0,1' . blame_map, '<silent>')
5386       call s:Map('n', '<CR>', ':<C-U>0,1' . blame_map, '<silent>')
5387       call s:Map('n', 'o',    ':<C-U>0,2' . blame_map, '<silent>')
5388       call s:Map('n', 'p',    ':<C-U>0,3' . blame_map, '<silent>')
5389       call s:Map('n', 'gO',   ':<C-U>0,4' . blame_map, '<silent>')
5390       call s:Map('n', 'O',    ':<C-U>0,5' . blame_map, '<silent>')
5392       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>')
5393       call s:Map('n', 'dd', ":<C-U>call <SID>DiffClose()<Bar>Gdiffsplit!<CR>", '<silent>')
5394       call s:Map('n', 'dh', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5395       call s:Map('n', 'ds', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5396       call s:Map('n', 'dv', ":<C-U>call <SID>DiffClose()<Bar>Gvdiffsplit!<CR>", '<silent>')
5397       call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
5399     else
5400       call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5401       call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5402       call s:Map('n', 'o',    ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
5403       call s:Map('n', 'gO',   ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
5404       call s:Map('n', 'O',    ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
5405       call s:Map('n', 'p',    ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
5407       if !exists('g:fugitive_no_maps')
5408         if exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
5409           nnoremap <buffer> <silent> <C-P> :<C-U>execute line('.') == 1 ? 'CtrlP ' . fnameescape(<SID>Tree()) : <SID>PreviousItem(v:count1)<CR>
5410         else
5411           nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>PreviousItem(v:count1)<CR>
5412         endif
5413         nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>NextItem(v:count1)<CR>
5414       endif
5415       call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
5416       call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
5417       call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
5418       call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
5419       call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
5420       call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
5421       call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
5422       call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
5423       call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
5424       call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
5425       call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
5426       call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
5427       call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
5428       call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
5429       call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
5430       call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
5431     endif
5432     call s:Map('n', 'S',    ':<C-U>echoerr "Use gO"<CR>', '<silent>')
5433     call s:Map('n', 'dq', ":<C-U>call <SID>DiffClose()<CR>", '<silent>')
5434     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>')
5435     call s:Map('n', 'P',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5436     call s:Map('n', '~',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5437     call s:Map('n', 'C',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5438     call s:Map('n', 'cp',    ":<C-U>echoerr 'Use gC'<CR>", '<silent>')
5439     call s:Map('n', 'gC',    ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5440     call s:Map('n', 'gc',    ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5441     call s:Map('n', 'gi',    ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5442     call s:Map('x', 'gi',    ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5444     nnoremap <buffer>       c<Space> :Git commit<Space>
5445     nnoremap <buffer>          c<CR> :Git commit<CR>
5446     nnoremap <buffer>      cv<Space> :Git commit -v<Space>
5447     nnoremap <buffer>         cv<CR> :Git commit -v<CR>
5448     nnoremap <buffer> <silent> ca    :<C-U>Gcommit --amend<CR>
5449     nnoremap <buffer> <silent> cc    :<C-U>Gcommit<CR>
5450     nnoremap <buffer> <silent> ce    :<C-U>Gcommit --amend --no-edit<CR>
5451     nnoremap <buffer> <silent> cw    :<C-U>Gcommit --amend --only<CR>
5452     nnoremap <buffer> <silent> cva   :<C-U>Gcommit -v --amend<CR>
5453     nnoremap <buffer> <silent> cvc   :<C-U>Gcommit -v<CR>
5454     nnoremap <buffer> <silent> cRa   :<C-U>Gcommit --reset-author --amend<CR>
5455     nnoremap <buffer> <silent> cRe   :<C-U>Gcommit --reset-author --amend --no-edit<CR>
5456     nnoremap <buffer> <silent> cRw   :<C-U>Gcommit --reset-author --amend --only<CR>
5457     nnoremap <buffer>          cf    :<C-U>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5458     nnoremap <buffer>          cF    :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5459     nnoremap <buffer>          cs    :<C-U>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5460     nnoremap <buffer>          cS    :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5461     nnoremap <buffer>          cA    :<C-U>Gcommit --edit --squash=<C-R>=<SID>SquashArgument()<CR>
5462     nnoremap <buffer> <silent> c?    :<C-U>help fugitive_c<CR>
5464     nnoremap <buffer>      cr<Space> :Git revert<Space>
5465     nnoremap <buffer>         cr<CR> :Git revert<CR>
5466     nnoremap <buffer> <silent> crc   :<C-U>Grevert <C-R>=<SID>SquashArgument()<CR><CR>
5467     nnoremap <buffer> <silent> crn   :<C-U>Grevert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>
5468     nnoremap <buffer> <silent> cr?   :help fugitive_cr<CR>
5470     nnoremap <buffer>      cm<Space> :Git merge<Space>
5471     nnoremap <buffer>         cm<CR> :Git merge<CR>
5472     nnoremap <buffer> <silent> cm?   :help fugitive_cm<CR>
5474     nnoremap <buffer>      cz<Space> :Git stash<Space>
5475     nnoremap <buffer>         cz<CR> :Git stash<CR>
5476     nnoremap <buffer> <silent> cza   :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5477     nnoremap <buffer> <silent> czA   :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', 'stash@{' . v:count . '}'])<CR>
5478     nnoremap <buffer> <silent> czp   :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5479     nnoremap <buffer> <silent> czP   :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', 'stash@{' . v:count . '}'])<CR>
5480     nnoremap <buffer> <silent> czv   :<C-U>exe 'Gedit' fugitive#RevParse('stash@{' . v:count . '}')<CR>
5481     nnoremap <buffer> <silent> czw   :<C-U>exe <SID>EchoExec(['stash', '--keep-index'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5482     nnoremap <buffer> <silent> czz   :<C-U>exe <SID>EchoExec(['stash'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5483     nnoremap <buffer> <silent> cz?   :<C-U>help fugitive_cz<CR>
5485     nnoremap <buffer>      co<Space> :Git checkout<Space>
5486     nnoremap <buffer>         co<CR> :Git checkout<CR>
5487     nnoremap <buffer>          coo   :exe <SID>EchoExec(['checkout'] + split(<SID>SquashArgument()) + ['--'])<CR>
5488     nnoremap <buffer>          co?   :<C-U>help fugitive_co<CR>
5490     nnoremap <buffer>      cb<Space> :Git branch<Space>
5491     nnoremap <buffer>         cb<CR> :Git branch<CR>
5492     nnoremap <buffer>         cb?    :<C-U>help fugitive_cb<CR>
5494     nnoremap <buffer>       r<Space> :Git rebase<Space>
5495     nnoremap <buffer>          r<CR> :Git rebase<CR>
5496     nnoremap <buffer> <silent> ri    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>
5497     nnoremap <buffer> <silent> rf    :<C-U>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>
5498     nnoremap <buffer> <silent> ru    :<C-U>Grebase --interactive @{upstream}<CR>
5499     nnoremap <buffer> <silent> rp    :<C-U>Grebase --interactive @{push}<CR>
5500     nnoremap <buffer> <silent> rw    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>
5501     nnoremap <buffer> <silent> rm    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>
5502     nnoremap <buffer> <silent> rd    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5503     nnoremap <buffer> <silent> rk    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5504     nnoremap <buffer> <silent> rx    :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5505     nnoremap <buffer> <silent> rr    :<C-U>Grebase --continue<CR>
5506     nnoremap <buffer> <silent> rs    :<C-U>Grebase --skip<CR>
5507     nnoremap <buffer> <silent> re    :<C-U>Grebase --edit-todo<CR>
5508     nnoremap <buffer> <silent> ra    :<C-U>Grebase --abort<CR>
5509     nnoremap <buffer> <silent> r?    :<C-U>help fugitive_r<CR>
5511     call s:Map('n', '.',     ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5512     call s:Map('x', '.',     ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5513     call s:Map('n', 'g?',    ":<C-U>help fugitive-map<CR>", '<silent>')
5514     call s:Map('n', '<F1>',  ":<C-U>help fugitive-map<CR>", '<silent>')
5515   endif
5516 endfunction
5518 function! s:StatusCfile(...) abort
5519   let tree = s:Tree()
5520   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5521   let info = s:StageInfo()
5522   let line = getline('.')
5523   if len(info.sigil) && len(info.section) && len(info.paths)
5524     if info.section ==# 'Unstaged' && info.sigil !=# '-'
5525       return [lead . info.relative[0], info.offset, 'normal!zv']
5526     elseif info.section ==# 'Staged' && info.sigil ==# '-'
5527       return ['@:' . info.relative[0], info.offset, 'normal!zv']
5528     else
5529       return [':0:' . info.relative[0], info.offset, 'normal!zv']
5530     endif
5531   elseif len(info.paths)
5532     return [lead . info.relative[0]]
5533   elseif len(info.commit)
5534     return [info.commit]
5535   elseif line =~# '^\%(Head\|Merge\|Rebase\|Upstream\|Pull\|Push\): '
5536     return [matchstr(line, ' \zs.*')]
5537   else
5538     return ['']
5539   endif
5540 endfunction
5542 function! fugitive#StatusCfile() abort
5543   let file = s:Generate(s:StatusCfile()[0])
5544   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5545 endfunction
5547 function! s:MessageCfile(...) abort
5548   let tree = s:Tree()
5549   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5550   if getline('.') =~# '^.\=\trenamed:.* -> '
5551     return lead . matchstr(getline('.'),' -> \zs.*')
5552   elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
5553     return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
5554   elseif getline('.') =~# '^.\=\t.'
5555     return lead . matchstr(getline('.'),'\t\zs.*')
5556   elseif getline('.') =~# ': needs merge$'
5557     return lead . matchstr(getline('.'),'.*\ze: needs merge$')
5558   elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
5559     return 'HEAD'
5560   elseif getline('.') =~# '^\%(. \)\=On branch '
5561     return 'refs/heads/'.getline('.')[12:]
5562   elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
5563     return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
5564   else
5565     return ''
5566   endif
5567 endfunction
5569 function! fugitive#MessageCfile() abort
5570   let file = s:Generate(s:MessageCfile())
5571   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5572 endfunction
5574 function! s:cfile() abort
5575   try
5576     let myhash = s:DirRev(@%)[1]
5577     if len(myhash)
5578       try
5579         let myhash = fugitive#RevParse(myhash)
5580       catch /^fugitive:/
5581         let myhash = ''
5582       endtry
5583     endif
5584     if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
5585       let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
5586     endif
5588     let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
5590     let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
5591           \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
5593     if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
5594       return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
5595     elseif showtree
5596       return [treebase . s:sub(getline('.'),'/$','')]
5598     else
5600       let dcmds = []
5602       " Index
5603       if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
5604         let ref = matchstr(getline('.'),'\x\{40,\}')
5605         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
5606         return [file]
5607       endif
5609       if getline('.') =~# '^ref: '
5610         let ref = strpart(getline('.'),5)
5612       elseif getline('.') =~# '^commit \x\{40,\}\>'
5613         let ref = matchstr(getline('.'),'\x\{40,\}')
5614         return [ref]
5616       elseif getline('.') =~# '^parent \x\{40,\}\>'
5617         let ref = matchstr(getline('.'),'\x\{40,\}')
5618         let line = line('.')
5619         let parent = 0
5620         while getline(line) =~# '^parent '
5621           let parent += 1
5622           let line -= 1
5623         endwhile
5624         return [ref]
5626       elseif getline('.') =~# '^tree \x\{40,\}$'
5627         let ref = matchstr(getline('.'),'\x\{40,\}')
5628         if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
5629           let ref = myhash.':'
5630         endif
5631         return [ref]
5633       elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
5634         let ref = matchstr(getline('.'),'\x\{40,\}')
5635         let type = matchstr(getline(line('.')+1),'type \zs.*')
5637       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
5638         let ref = s:DirRev(@%)[1]
5640       elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
5641         let ref = matchstr(getline('.'),'\x\{40,\}')
5642         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
5644       elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
5645         let ref = getline('.')[4:]
5647       elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
5648         let type = getline('.')[0]
5649         let lnum = line('.') - 1
5650         let offset = 0
5651         while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5652           if getline(lnum) =~# '^[ '.type.']'
5653             let offset += 1
5654           endif
5655           let lnum -= 1
5656         endwhile
5657         let offset += matchstr(getline(lnum), type.'\zs\d\+')
5658         let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
5659         let dcmds = [offset, 'normal!zv']
5661       elseif getline('.') =~# '^rename from '
5662         let ref = 'a/'.getline('.')[12:]
5663       elseif getline('.') =~# '^rename to '
5664         let ref = 'b/'.getline('.')[10:]
5666       elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5667         let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
5668         let offset = matchstr(getline('.'), '+\zs\d\+')
5670         let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5671         let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5672         let dcmd = 'Gdiffsplit! +'.offset
5674       elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5675         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5676         let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5677         let dcmd = 'Gdiffsplit!'
5679       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5680         let line = getline(line('.')-1)
5681         let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5682         let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5683         let dcmd = 'Gdiffsplit!'
5685       elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
5686         let ref = getline('.')
5688       elseif expand('<cword>') =~# '^\x\{7,\}\>'
5689         return [expand('<cword>')]
5691       else
5692         let ref = ''
5693       endif
5695       let prefixes = {
5696             \ '1': '',
5697             \ '2': '',
5698             \ 'b': ':0:',
5699             \ 'i': ':0:',
5700             \ 'o': '',
5701             \ 'w': ''}
5703       if len(myhash)
5704         let prefixes.a = myhash.'^:'
5705         let prefixes.b = myhash.':'
5706       endif
5707       let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5708       if exists('dref')
5709         let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5710       endif
5712       if ref ==# '/dev/null'
5713         " Empty blob
5714         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
5715       endif
5717       if exists('dref')
5718         return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
5719       elseif ref != ""
5720         return [ref] + dcmds
5721       endif
5723     endif
5724     return []
5725   endtry
5726 endfunction
5728 function! s:GF(mode) abort
5729   try
5730     let results = &filetype ==# 'fugitive' ? s:StatusCfile() : &filetype ==# 'gitcommit' ? [s:MessageCfile()] : s:cfile()
5731   catch /^fugitive:/
5732     return 'echoerr ' . string(v:exception)
5733   endtry
5734   if len(results) > 1
5735     return 'G' . a:mode .
5736           \ ' +' . escape(results[1], ' ') . ' ' .
5737           \ s:fnameescape(results[0]) . join(map(results[2:-1], '"|" . v:val'), '')
5738   elseif len(results) && len(results[0])
5739     return 'G' . a:mode . ' ' . s:fnameescape(results[0])
5740   else
5741     return ''
5742   endif
5743 endfunction
5745 function! fugitive#Cfile() abort
5746   let pre = ''
5747   let results = s:cfile()
5748   if empty(results)
5749     let cfile = expand('<cfile>')
5750     if &includeexpr =~# '\<v:fname\>'
5751       sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
5752     endif
5753     return cfile
5754   elseif len(results) > 1
5755     let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
5756   endif
5757   return pre . s:fnameescape(s:Generate(results[0]))
5758 endfunction
5760 " Section: Statusline
5762 function! fugitive#Statusline(...) abort
5763   let dir = s:Dir(bufnr(''))
5764   if empty(dir)
5765     return ''
5766   endif
5767   let status = ''
5768   let commit = s:DirCommitFile(@%)[1]
5769   if len(commit)
5770     let status .= ':' . commit[0:6]
5771   endif
5772   let status .= '('.FugitiveHead(7, dir).')'
5773   return '[Git'.status.']'
5774 endfunction
5776 function! fugitive#statusline(...) abort
5777   return fugitive#Statusline()
5778 endfunction
5780 function! fugitive#head(...) abort
5781   if empty(s:Dir())
5782     return ''
5783   endif
5785   return fugitive#Head(a:0 ? a:1 : 0)
5786 endfunction
5788 " Section: Folding
5790 function! fugitive#Foldtext() abort
5791   if &foldmethod !=# 'syntax'
5792     return foldtext()
5793   endif
5795   let line_foldstart = getline(v:foldstart)
5796   if line_foldstart =~# '^diff '
5797     let [add, remove] = [-1, -1]
5798     let filename = ''
5799     for lnum in range(v:foldstart, v:foldend)
5800       let line = getline(lnum)
5801       if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
5802         let filename = line[6:-1]
5803       endif
5804       if line =~# '^+'
5805         let add += 1
5806       elseif line =~# '^-'
5807         let remove += 1
5808       elseif line =~# '^Binary '
5809         let binary = 1
5810       endif
5811     endfor
5812     if filename ==# ''
5813       let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
5814     endif
5815     if filename ==# ''
5816       let filename = line_foldstart[5:-1]
5817     endif
5818     if exists('binary')
5819       return 'Binary: '.filename
5820     else
5821       return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
5822     endif
5823   elseif line_foldstart =~# '^# .*:$'
5824     let lines = getline(v:foldstart, v:foldend)
5825     call filter(lines, 'v:val =~# "^#\t"')
5826     cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
5827     cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
5828     return line_foldstart.' '.join(lines, ', ')
5829   endif
5830   return foldtext()
5831 endfunction
5833 function! fugitive#foldtext() abort
5834   return fugitive#Foldtext()
5835 endfunction
5837 augroup fugitive_folding
5838   autocmd!
5839   autocmd User Fugitive
5840         \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
5841         \    set foldtext=fugitive#Foldtext() |
5842         \ endif
5843 augroup END
5845 " Section: Initialization
5847 function! fugitive#Init() abort
5848   if exists('#User#FugitiveBoot')
5849     try
5850       let [save_mls, &modelines] = [&mls, 0]
5851       doautocmd User FugitiveBoot
5852     finally
5853       let &mls = save_mls
5854     endtry
5855   endif
5856   let dir = s:Dir()
5857   if stridx(&tags, escape(dir, ', ')) == -1 && &tags !~# '\.git' && !exists('s:tags_warning')
5858     let actualdir = fugitive#Find('.git/', dir)
5859     if filereadable(actualdir . 'tags')
5860       let s:tags_warning = 1
5861       echohl WarningMsg
5862       echo "Fugitive .git/tags support removed in favor of `:set tags^=./.git/tags;`"
5863       echohl NONE
5864     endif
5865   endif
5866   try
5867     let [save_mls, &modelines] = [&mls, 0]
5868     call s:define_commands()
5869     doautocmd User Fugitive
5870   finally
5871     let &mls = save_mls
5872   endtry
5873 endfunction
5875 function! fugitive#is_git_dir(path) abort
5876   return FugitiveIsGitDir(a:path)
5877 endfunction
5879 function! fugitive#extract_git_dir(path) abort
5880   return FugitiveExtractGitDir(a:path)
5881 endfunction
5883 function! fugitive#detect(path) abort
5884   return FugitiveDetect(a:path)
5885 endfunction
5887 " Section: End