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