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