Use nvim vim.ui.open as a fallback
[vim-fugitive.git] / autoload / fugitive.vim
blob005dd3705dc3d40ab557bd6a6ce022a60e94b97c
1 " Location:     autoload/fugitive.vim
2 " Maintainer:   Tim Pope <http://tpo.pe/>
4 " The functions contained within this file are for internal use only.  For the
5 " official API, see the commented functions in plugin/fugitive.vim.
7 if exists('g:autoloaded_fugitive')
8   finish
9 endif
10 let g:autoloaded_fugitive = 1
12 " Section: Utility
14 function! s:function(name) abort
15   return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '.*\zs<SNR>\d\+_'),''))
16 endfunction
18 function! s:sub(str,pat,rep) abort
19   return substitute(a:str,'\v\C'.a:pat,a:rep,'')
20 endfunction
22 function! s:gsub(str,pat,rep) abort
23   return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
24 endfunction
26 function! s:Uniq(list) abort
27   let i = 0
28   let seen = {}
29   while i < len(a:list)
30     let str = string(a:list[i])
31     if has_key(seen, str)
32       call remove(a:list, i)
33     else
34       let seen[str] = 1
35       let i += 1
36     endif
37   endwhile
38   return a:list
39 endfunction
41 function! s:JoinChomp(list) abort
42   if empty(a:list[-1])
43     return join(a:list[0:-2], "\n")
44   else
45     return join(a:list, "\n")
46   endif
47 endfunction
49 function! s:winshell() abort
50   return has('win32') && &shellcmdflag !~# '^-'
51 endfunction
53 function! s:WinShellEsc(arg) abort
54   if type(a:arg) == type([])
55     return join(map(copy(a:arg), 's:WinShellEsc(v:val)'))
56   elseif a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
57     return a:arg
58   else
59     return '"' . s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"') . '"'
60   endif
61 endfunction
63 function! s:shellesc(arg) abort
64   if type(a:arg) == type([])
65     return join(map(copy(a:arg), 's:shellesc(v:val)'))
66   elseif a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
67     return a:arg
68   elseif s:winshell()
69     return '"' . s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"') . '"'
70   else
71     return shellescape(a:arg)
72   endif
73 endfunction
75 function! s:fnameescape(file) abort
76   if type(a:file) == type([])
77     return join(map(copy(a:file), 's:fnameescape(v:val)'))
78   else
79     return fnameescape(a:file)
80   endif
81 endfunction
83 function! fugitive#UrlDecode(str) abort
84   return substitute(a:str, '%\(\x\x\)', '\=iconv(nr2char("0x".submatch(1)), "utf-8", "latin1")', 'g')
85 endfunction
87 function! s:UrlEncode(str) abort
88   return substitute(a:str, '[%#?&;+=\<> [:cntrl:]]', '\=printf("%%%02X", char2nr(submatch(0)))', 'g')
89 endfunction
91 function! s:PathUrlEncode(str) abort
92   return substitute(a:str, '[%#?[:cntrl:]]', '\=printf("%%%02X", char2nr(submatch(0)))', 'g')
93 endfunction
95 function! s:PathJoin(prefix, str) abort
96   if a:prefix =~# '://'
97     return a:prefix . s:PathUrlEncode(a:str)
98   else
99     return a:prefix . a:str
100   endif
101 endfunction
103 function! s:throw(string) abort
104   throw 'fugitive: '.a:string
105 endfunction
107 function! s:VersionCheck() abort
108   if v:version < 704
109     return 'return ' . string('echoerr "fugitive: Vim 7.4 or newer required"')
110   elseif empty(fugitive#GitVersion())
111     let exe = get(s:GitCmd(), 0, '')
112     if len(exe) && !executable(exe)
113       return 'return ' . string('echoerr "fugitive: cannot find ' . string(exe) . ' in PATH"')
114     endif
115     return 'return ' . string('echoerr "fugitive: cannot execute Git"')
116   elseif !fugitive#GitVersion(1, 8, 5)
117     return 'return ' . string('echoerr "fugitive: Git 1.8.5 or newer required"')
118   else
119     if exists('b:git_dir') && empty(b:git_dir)
120       unlet! b:git_dir
121     endif
122     return ''
123   endif
124 endfunction
126 let s:worktree_error = "core.worktree is required when using an external Git dir"
127 function! s:DirCheck(...) abort
128   let dir = call('FugitiveGitDir', a:000)
129   if !empty(dir) && FugitiveWorkTree(dir, 1) is# 0
130     return 'return ' . string('echoerr "fugitive: ' . s:worktree_error . '"')
131   elseif !empty(dir)
132     return ''
133   elseif empty(bufname(''))
134     return 'return ' . string('echoerr "fugitive: working directory does not belong to a Git repository"')
135   else
136     return 'return ' . string('echoerr "fugitive: file does not belong to a Git repository"')
137   endif
138 endfunction
140 function! s:Mods(mods, ...) abort
141   let mods = substitute(a:mods, '\C<mods>', '', '')
142   let mods = mods =~# '\S$' ? mods . ' ' : mods
143   if a:0 && mods !~# '\<\d*\%(aboveleft\|belowright\|leftabove\|rightbelow\|topleft\|botright\|tab\)\>'
144     let default = a:1
145     if default ==# 'SpanOrigin'
146       if s:OriginBufnr() > 0 && (mods =~# '\<vertical\>' ? &winfixheight : &winfixwidth)
147         let default = 'Edge'
148       else
149         let default = ''
150       endif
151     endif
152     if default ==# 'Edge'
153       if mods =~# '\<vertical\>' ? &splitright : &splitbelow
154         let mods = 'botright ' . mods
155       else
156         let mods = 'topleft ' . mods
157       endif
158     else
159       let mods = default . ' ' . mods
160     endif
161   endif
162   return substitute(mods, '\s\+', ' ', 'g')
163 endfunction
165 if exists('+shellslash')
167   let s:dir_commit_file = '\c^fugitive://\%(/[^/]\@=\)\=\([^?#]\{-1,\}\)//\%(\(\x\{40,\}\|[0-3]\)\(/[^?#]*\)\=\)\=$'
169   function! s:Slash(path) abort
170     return tr(a:path, '\', '/')
171   endfunction
173   function! s:VimSlash(path) abort
174     return tr(a:path, '\/', &shellslash ? '//' : '\\')
175   endfunction
177 else
179   let s:dir_commit_file = '\c^fugitive://\([^?#]\{-\}\)//\%(\(\x\{40,\}\|[0-3]\)\(/[^?#]*\)\=\)\=$'
181   function! s:Slash(path) abort
182     return a:path
183   endfunction
185   function! s:VimSlash(path) abort
186     return a:path
187   endfunction
189 endif
191 function! s:AbsoluteVimPath(...) abort
192   if a:0 && type(a:1) == type('')
193     let path = a:1
194   else
195     let path = bufname(a:0 && a:1 > 0 ? a:1 : '')
196     if getbufvar(a:0 && a:1 > 0 ? a:1 : '', '&buftype') !~# '^\%(nowrite\|acwrite\)\=$'
197       return path
198     endif
199   endif
200   if s:Slash(path) =~# '^/\|^\a\+:'
201     return path
202   else
203     return getcwd() . matchstr(getcwd(), '[\\/]') . path
204   endif
205 endfunction
207 function! s:Resolve(path) abort
208   let path = resolve(a:path)
209   if has('win32')
210     let path = s:VimSlash(fnamemodify(fnamemodify(path, ':h'), ':p') . fnamemodify(path, ':t'))
211   endif
212   return path
213 endfunction
215 function! s:FileIgnoreCase(for_completion) abort
216   return (exists('+fileignorecase') && &fileignorecase)
217         \ || (a:for_completion && exists('+wildignorecase') && &wildignorecase)
218 endfunction
220 function! s:cpath(path, ...) abort
221   if s:FileIgnoreCase(0)
222     let path = s:VimSlash(tolower(a:path))
223   else
224     let path = s:VimSlash(a:path)
225   endif
226   return a:0 ? path ==# s:cpath(a:1) : path
227 endfunction
229 let s:quote_chars = {
230       \ "\007": 'a', "\010": 'b', "\011": 't', "\012": 'n', "\013": 'v', "\014": 'f', "\015": 'r',
231       \ '"': '"', '\': '\'}
233 let s:unquote_chars = {
234       \ 'a': "\007", 'b': "\010", 't': "\011", 'n': "\012", 'v': "\013", 'f': "\014", 'r': "\015",
235       \ '"': '"', '\': '\'}
237 function! s:Quote(string) abort
238   let string = substitute(a:string, "[\001-\037\"\\\177]", '\="\\" . get(s:quote_chars, submatch(0), printf("%03o", char2nr(submatch(0))))', 'g')
239   if string !=# a:string
240     return '"' . string . '"'
241   else
242     return string
243   endif
244 endfunction
246 function! fugitive#Unquote(string) abort
247   let string = substitute(a:string, "\t*$", '', '')
248   if string =~# '^".*"$'
249     return substitute(string[1:-2], '\\\(\o\o\o\|.\)', '\=get(s:unquote_chars, submatch(1), iconv(nr2char("0" . submatch(1)), "utf-8", "latin1"))', 'g')
250   else
251     return string
252   endif
253 endfunction
255 let s:executables = {}
257 function! s:executable(binary) abort
258   if !has_key(s:executables, a:binary)
259     let s:executables[a:binary] = executable(a:binary)
260   endif
261   return s:executables[a:binary]
262 endfunction
264 if !exists('s:temp_scripts')
265   let s:temp_scripts = {}
266 endif
267 function! s:TempScript(...) abort
268   let body = join(a:000, "\n")
269   if !has_key(s:temp_scripts, body)
270     let s:temp_scripts[body] = tempname() . '.sh'
271   endif
272   let temp = s:temp_scripts[body]
273   if !filereadable(temp)
274     call writefile(['#!/bin/sh'] + a:000, temp)
275   endif
276   let temp = FugitiveGitPath(temp)
277   if temp =~# '\s'
278     let temp = '"' . temp . '"'
279   endif
280   return temp
281 endfunction
283 function! s:DoAutocmd(...) abort
284   return join(map(copy(a:000), "'doautocmd <nomodeline>' . v:val"), '|')
285 endfunction
287 function! s:Map(mode, lhs, rhs, ...) abort
288   let maps = []
289   let flags = a:0 && type(a:1) == type('') ? a:1 : ''
290   let defer = flags =~# '<unique>'
291   let flags = substitute(flags, '<unique>', '', '') . (a:rhs =~# '<Plug>' ? '' : '<script>') . '<nowait>'
292   for mode in split(a:mode, '\zs')
293     if a:0 <= 1
294       call add(maps, mode.'map <buffer>' . substitute(flags, '<unique>', '', '') . ' <Plug>fugitive:' . a:lhs . ' ' . a:rhs)
295     endif
296     let skip = 0
297     let head = a:lhs
298     let tail = ''
299     let keys = get(g:, mode.'remap', {})
300     if type(keys) == type([])
301       continue
302     endif
303     while !empty(head)
304       if has_key(keys, head)
305         let head = keys[head]
306         let skip = empty(head)
307         break
308       endif
309       let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
310       let head = substitute(head, '<[^<>]*>$\|.$', '', '')
311     endwhile
312     if !skip && (!defer || empty(mapcheck(head.tail, mode)))
313       call add(maps, mode.'map <buffer>' . flags . ' ' . head.tail . ' ' . a:rhs)
314       if a:0 > 1 && a:2
315         let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
316               \ '|sil! exe "' . mode . 'unmap <buffer> ' . head.tail . '"'
317       endif
318     endif
319   endfor
320   exe join(maps, '|')
321   return ''
322 endfunction
324 function! fugitive#Autowrite() abort
325   if &autowrite || &autowriteall
326     try
327       if &confirm
328         let reconfirm = 1
329         setglobal noconfirm
330       endif
331       silent! wall
332     finally
333       if exists('reconfirm')
334         setglobal confirm
335       endif
336     endtry
337   endif
338   return ''
339 endfunction
341 function! fugitive#Wait(job_or_jobs, ...) abort
342   let original = type(a:job_or_jobs) == type([]) ? copy(a:job_or_jobs) : [a:job_or_jobs]
343   let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
344   call filter(jobs, 'type(v:val) !=# type("")')
345   let timeout_ms = a:0 ? a:1 : -1
346   if exists('*jobwait')
347     call map(copy(jobs), 'chanclose(v:val, "stdin")')
348     call jobwait(jobs, timeout_ms)
349     let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
350     call filter(jobs, 'type(v:val) !=# type("")')
351     if len(jobs)
352       sleep 1m
353     endif
354   else
355     for job in jobs
356       if ch_status(job) ==# 'open'
357         call ch_close_in(job)
358       endif
359     endfor
360     let i = 0
361     for job in jobs
362       while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
363         if i == timeout_ms
364           break
365         endif
366         let i += 1
367         sleep 1m
368       endwhile
369     endfor
370   endif
371   return a:job_or_jobs
372 endfunction
374 function! s:JobVimExit(dict, callback, temp, job, status) abort
375   let a:dict.exit_status = a:status
376   let a:dict.stderr = readfile(a:temp . '.err', 'b')
377   call delete(a:temp . '.err')
378   let a:dict.stdout = readfile(a:temp . '.out', 'b')
379   call delete(a:temp . '.out')
380   call delete(a:temp . '.in')
381   call remove(a:dict, 'job')
382   call call(a:callback[0], [a:dict] + a:callback[1:-1])
383 endfunction
385 function! s:JobNvimExit(dict, callback, job, data, type) dict abort
386   let a:dict.stdout = self.stdout
387   let a:dict.stderr = self.stderr
388   let a:dict.exit_status = a:data
389   call remove(a:dict, 'job')
390   call call(a:callback[0], [a:dict] + a:callback[1:-1])
391 endfunction
393 function! s:JobExecute(argv, jopts, stdin, callback, ...) abort
394   let dict = a:0 ? a:1 : {}
395   let cb = len(a:callback) ? a:callback : [function('len')]
396   if exists('*jobstart')
397     call extend(a:jopts, {
398           \ 'stdout_buffered': v:true,
399           \ 'stderr_buffered': v:true,
400           \ 'on_exit': function('s:JobNvimExit', [dict, cb])})
401     try
402       let dict.job = jobstart(a:argv, a:jopts)
403       if !empty(a:stdin)
404         call chansend(dict.job, a:stdin)
405       endif
406       call chanclose(dict.job, 'stdin')
407     catch /^Vim\%((\a\+)\)\=:E475:/
408       let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
409     endtry
410   elseif exists('*ch_close_in')
411     let temp = tempname()
412     call extend(a:jopts, {
413           \ 'out_io': 'file',
414           \ 'out_name': temp . '.out',
415           \ 'err_io': 'file',
416           \ 'err_name': temp . '.err',
417           \ 'exit_cb': function('s:JobVimExit', [dict, cb, temp])})
418     if a:stdin ==# ['']
419       let a:jopts.in_io = 'null'
420     elseif !empty(a:stdin)
421       let a:jopts.in_io = 'file'
422       let a:jopts.in_name = temp . '.in'
423       call writefile(a:stdin, a:jopts.in_name, 'b')
424     endif
425     let dict.job = job_start(a:argv, a:jopts)
426     if job_status(dict.job) ==# 'fail'
427       let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
428       unlet dict.job
429     endif
430   elseif &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
431     throw 'fugitive: Vim 8 or higher required to use ' . &shell
432   else
433     let cmd = s:shellesc(a:argv)
434     let outfile = tempname()
435     try
436       if len(a:stdin)
437         call writefile(a:stdin, outfile . '.in', 'b')
438         let cmd = ' (' . cmd . ' >' . outfile . ' <' . outfile . '.in) '
439       else
440         let cmd = ' (' . cmd . ' >' . outfile . ') '
441       endif
442       let dict.stderr = split(system(cmd), "\n", 1)
443       let dict.exit_status = v:shell_error
444       let dict.stdout = readfile(outfile, 'b')
445       call call(cb[0], [dict] + cb[1:-1])
446     finally
447       call delete(outfile)
448       call delete(outfile . '.in')
449     endtry
450   endif
451   if empty(a:callback)
452     call fugitive#Wait(dict)
453   endif
454   return dict
455 endfunction
457 function! s:add_methods(namespace, method_names) abort
458   for name in a:method_names
459     let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
460   endfor
461 endfunction
463 " Section: Git
465 let s:run_jobs = (exists('*ch_close_in') || exists('*jobstart')) && exists('*bufwinid')
467 function! s:GitCmd() abort
468   if !exists('g:fugitive_git_executable')
469     return ['git']
470   elseif type(g:fugitive_git_executable) == type([])
471     return g:fugitive_git_executable
472   else
473     let dquote = '"\%([^"]\|""\|\\"\)*"\|'
474     let string = g:fugitive_git_executable
475     let list = []
476     if string =~# '^\w\+='
477       call add(list, '/usr/bin/env')
478     endif
479     while string =~# '\S'
480       let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
481       let string = strpart(string, len(arg))
482       let arg = substitute(arg, '^\s\+', '', '')
483       let arg = substitute(arg,
484             \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\)\|' . s:expand,
485             \ '\=submatch(0)[0] ==# "\\" ? submatch(0)[1] : submatch(0)[1:-2]', 'g')
486       call add(list, arg)
487     endwhile
488     return list
489   endif
490 endfunction
492 function! s:GitShellCmd() abort
493   if !exists('g:fugitive_git_executable')
494     return 'git'
495   elseif type(g:fugitive_git_executable) == type([])
496     return s:shellesc(g:fugitive_git_executable)
497   else
498     return g:fugitive_git_executable
499   endif
500 endfunction
502 function! s:UserCommandCwd(dir) abort
503   let tree = s:Tree(a:dir)
504   return len(tree) ? s:VimSlash(tree) : getcwd()
505 endfunction
507 function! s:UserCommandList(...) abort
508   if !fugitive#GitVersion(1, 8, 5)
509     throw 'fugitive: Git 1.8.5 or higher required'
510   endif
511   if !exists('g:fugitive_git_command')
512     let git = s:GitCmd()
513   elseif type(g:fugitive_git_command) == type([])
514     let git = g:fugitive_git_command
515   else
516     let git = split(g:fugitive_git_command, '\s\+')
517   endif
518   let flags = []
519   if a:0 && type(a:1) == type({})
520     let git = copy(get(a:1, 'git', git))
521     let flags = get(a:1, 'flags', flags)
522     let dir = a:1.git_dir
523   elseif a:0
524     let dir = s:GitDir(a:1)
525   else
526     let dir = ''
527   endif
528   if len(dir)
529     let tree = s:Tree(dir)
530     if empty(tree)
531       call add(git, '--git-dir=' . FugitiveGitPath(dir))
532     else
533       if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
534         call add(git, '--git-dir=' . FugitiveGitPath(dir))
535       endif
536       if !s:cpath(tree, getcwd())
537         call extend(git, ['-C', FugitiveGitPath(tree)])
538       endif
539     endif
540   endif
541   return git + flags
542 endfunction
544 let s:git_versions = {}
545 function! fugitive#GitVersion(...) abort
546   let git = s:GitShellCmd()
547   if !has_key(s:git_versions, git)
548     let s:git_versions[git] = matchstr(get(s:JobExecute(s:GitCmd() + ['--version'], {}, [], [], {}).stdout, 0, ''), '\d[^[:space:]]\+')
549   endif
550   if !a:0
551     return s:git_versions[git]
552   endif
553   let components = split(s:git_versions[git], '\D\+')
554   if empty(components)
555     return -1
556   endif
557   for i in range(len(a:000))
558     if a:000[i] > +get(components, i)
559       return 0
560     elseif a:000[i] < +get(components, i)
561       return 1
562     endif
563   endfor
564   return a:000[i] ==# get(components, i)
565 endfunction
567 function! s:Dir(...) abort
568   return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
569 endfunction
571 function! s:GitDir(...) abort
572   return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
573 endfunction
575 function! s:InitializeBuffer(repo) abort
576   let b:git_dir = s:GitDir(a:repo)
577 endfunction
579 function! s:SameRepo(one, two) abort
580   let one = s:GitDir(a:one)
581   return !empty(one) && one ==# s:GitDir(a:two)
582 endfunction
584 if exists('+shellslash')
585   function! s:DirUrlPrefix(dir) abort
586     let gd = s:GitDir(a:dir)
587     return 'fugitive://' . (gd =~# '^[^/]' ? '/' : '') . s:PathUrlEncode(gd) . '//'
588   endfunction
589 else
590   function! s:DirUrlPrefix(dir) abort
591     return 'fugitive://' . s:PathUrlEncode(s:GitDir(a:dir)) . '//'
592   endfunction
593 endif
595 function! s:Tree(...) abort
596   return a:0 ? FugitiveWorkTree(a:1) : FugitiveWorkTree()
597 endfunction
599 function! s:HasOpt(args, ...) abort
600   let args = a:args[0 : index(a:args, '--')]
601   let opts = copy(a:000)
602   if type(opts[0]) == type([])
603     if empty(args) || index(opts[0], args[0]) == -1
604       return 0
605     endif
606     call remove(opts, 0)
607   endif
608   for opt in opts
609     if index(args, opt) != -1
610       return 1
611     endif
612   endfor
613 endfunction
615 function! s:PreparePathArgs(cmd, dir, literal, explicit) abort
616   if !a:explicit
617     call insert(a:cmd, '--literal-pathspecs')
618   endif
619   let split = index(a:cmd, '--')
620   for i in range(split < 0 ? len(a:cmd) : split)
621       if type(a:cmd[i]) == type(0)
622         if a:literal
623           let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
624         else
625           let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
626         endif
627       endif
628   endfor
629   if split < 0
630     return a:cmd
631   endif
632   for i in range(split + 1, len(a:cmd) - 1)
633     if type(a:cmd[i]) == type(0)
634       if a:literal
635         let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
636       else
637         let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
638       endif
639     elseif !a:explicit
640       let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
641     endif
642   endfor
643   return a:cmd
644 endfunction
646 let s:git_index_file_env = {}
647 function! s:GitIndexFileEnv() abort
648   if $GIT_INDEX_FILE =~# '^/\|^\a:' && !has_key(s:git_index_file_env, $GIT_INDEX_FILE)
649     let s:git_index_file_env[$GIT_INDEX_FILE] = s:Slash(FugitiveVimPath($GIT_INDEX_FILE))
650   endif
651   return get(s:git_index_file_env, $GIT_INDEX_FILE, '')
652 endfunction
654 function! s:PrepareEnv(env, dir) abort
655   if len($GIT_INDEX_FILE) && len(s:Tree(a:dir)) && !has_key(a:env, 'GIT_INDEX_FILE')
656     let index_dir = substitute(s:GitIndexFileEnv(), '[^/]\+$', '', '')
657     let our_dir = fugitive#Find('.git/', a:dir)
658     if !s:cpath(index_dir, our_dir) && !s:cpath(resolve(index_dir), our_dir)
659       let a:env['GIT_INDEX_FILE'] = FugitiveGitPath(fugitive#Find('.git/index', a:dir))
660     endif
661   endif
662   if len($GIT_WORK_TREE)
663     let a:env['GIT_WORK_TREE'] = '.'
664   endif
665 endfunction
667 let s:prepare_env = {
668       \ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
669       \ 'core.editor': 'GIT_EDITOR',
670       \ 'core.askpass': 'GIT_ASKPASS',
671       \ }
672 function! fugitive#PrepareDirEnvGitFlagsArgs(...) abort
673   if !fugitive#GitVersion(1, 8, 5)
674     throw 'fugitive: Git 1.8.5 or higher required'
675   endif
676   let git = s:GitCmd()
677   if a:0 == 1 && type(a:1) == type({}) && (has_key(a:1, 'fugitive_dir') || has_key(a:1, 'git_dir')) && has_key(a:1, 'flags') && has_key(a:1, 'args')
678     let cmd = a:1.flags + a:1.args
679     let dir = s:Dir(a:1)
680     if has_key(a:1, 'git')
681       let git = a:1.git
682     endif
683     let env = get(a:1, 'env', {})
684   else
685     let list_args = []
686     let cmd = []
687     for l:.arg in a:000
688       if type(arg) ==# type([])
689         call extend(list_args, arg)
690       else
691         call add(cmd, arg)
692       endif
693     endfor
694     call extend(cmd, list_args)
695     let env = {}
696   endif
697   let autoenv = {}
698   let explicit_pathspec_option = 0
699   let literal_pathspecs = 1
700   let i = 0
701   let arg_count = 0
702   while i < len(cmd)
703     if type(cmd[i]) == type({})
704       if has_key(cmd[i], 'fugitive_dir') || has_key(cmd[i], 'git_dir')
705         let dir = s:Dir(cmd[i])
706       endif
707       if has_key(cmd[i], 'git')
708         let git = cmd[i].git
709       endif
710       if has_key(cmd[i], 'env')
711         call extend(env, cmd[i].env)
712       endif
713       call remove(cmd, i)
714     elseif cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
715       let dir = s:Dir(remove(cmd, i))
716     elseif cmd[i] =~# '^--git-dir='
717       let dir = s:Dir(remove(cmd, i)[10:-1])
718     elseif type(cmd[i]) ==# type(0)
719       let dir = s:Dir(remove(cmd, i))
720     elseif cmd[i] ==# '-c' && len(cmd) > i + 1
721       let key = matchstr(cmd[i+1], '^[^=]*')
722       if has_key(s:prepare_env, tolower(key))
723         let var = s:prepare_env[tolower(key)]
724         let val = matchstr(cmd[i+1], '=\zs.*')
725         let autoenv[var] = val
726       endif
727       let i += 2
728     elseif cmd[i] =~# '^--.*pathspecs$'
729       let literal_pathspecs = (cmd[i] ==# '--literal-pathspecs')
730       let explicit_pathspec_option = 1
731       let i += 1
732     elseif cmd[i] !~# '^-'
733       let arg_count = len(cmd) - i
734       break
735     else
736       let i += 1
737     endif
738   endwhile
739   if !exists('dir')
740     let dir = s:Dir()
741   endif
742   call extend(autoenv, env)
743   call s:PrepareEnv(autoenv, dir)
744   if len($GPG_TTY) && !has_key(autoenv, 'GPG_TTY')
745     let autoenv.GPG_TTY = ''
746   endif
747   call s:PreparePathArgs(cmd, dir, literal_pathspecs, explicit_pathspec_option)
748   return [dir, env, extend(autoenv, env), git, cmd[0 : -arg_count-1], arg_count ? cmd[-arg_count : -1] : []]
749 endfunction
751 function! s:BuildEnvPrefix(env) abort
752   let pre = ''
753   let env = items(a:env)
754   if empty(env)
755     return ''
756   elseif &shell =~# '\%(powershell\|pwsh\)\%(\.exe\)\=$'
757     return join(map(env, '"$Env:" . v:val[0] . " = ''" . substitute(v:val[1], "''", "''''", "g") . "''; "'), '')
758   elseif s:winshell()
759     return join(map(env, '"set " . substitute(join(v:val, "="), "[&|<>^]", "^^^&", "g") . "& "'), '')
760   else
761     return '/usr/bin/env ' . s:shellesc(map(env, 'join(v:val, "=")')) . ' '
762   endif
763 endfunction
765 function! s:JobOpts(cmd, env) abort
766   if empty(a:env)
767     return [a:cmd, {}]
768   elseif has('patch-8.2.0239') ||
769         \ has('nvim') && api_info().version.api_level - api_info().version.api_prerelease >= 7 ||
770         \ has('patch-8.0.0902') && !has('nvim') && (!has('win32') || empty(filter(keys(a:env), 'exists("$" . v:val)')))
771     return [a:cmd, {'env': a:env}]
772   endif
773   let envlist = map(items(a:env), 'join(v:val, "=")')
774   if !has('win32')
775     return [['/usr/bin/env'] + envlist + a:cmd, {}]
776   else
777     let pre = join(map(envlist, '"set " . substitute(v:val, "[&|<>^]", "^^^&", "g") . "& "'), '')
778     if len(a:cmd) == 3 && a:cmd[0] ==# 'cmd.exe' && a:cmd[1] ==# '/c'
779       return [a:cmd[0:1] + [pre . a:cmd[2]], {}]
780     else
781       return [['cmd.exe', '/c', pre . s:WinShellEsc(a:cmd)], {}]
782     endif
783   endif
784 endfunction
786 function! s:PrepareJob(opts) abort
787   let dict = {'argv': a:opts.argv}
788   if has_key(a:opts, 'env')
789     let dict.env = a:opts.env
790   endif
791   let [argv, jopts] = s:JobOpts(a:opts.argv, get(a:opts, 'env', {}))
792   if has_key(a:opts, 'cwd')
793     if has('patch-8.0.0902')
794       let jopts.cwd = a:opts.cwd
795       let dict.cwd = a:opts.cwd
796     else
797       throw 'fugitive: cwd unsupported'
798     endif
799   endif
800   return [argv, jopts, dict]
801 endfunction
803 function! fugitive#PrepareJob(...) abort
804   if a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'argv') && !has_key(a:1, 'args')
805     return s:PrepareJob(a:1)
806   endif
807   let [repo, user_env, exec_env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
808   let dir = s:GitDir(repo)
809   let dict = {'git': git, 'git_dir': dir, 'flags': flags, 'args': args}
810   if len(user_env)
811     let dict.env = user_env
812   endif
813   let cmd = flags + args
814   let tree = s:Tree(repo)
815   if empty(tree) || index(cmd, '--') == len(cmd) - 1
816     let dict.cwd = getcwd()
817     call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
818   else
819     let dict.cwd = s:VimSlash(tree)
820     call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
821     if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
822       call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
823     endif
824   endif
825   call extend(cmd, git, 'keep')
826   return s:JobOpts(cmd, exec_env) + [dict]
827 endfunction
829 function! fugitive#Execute(...) abort
830   let cb = copy(a:000)
831   let cmd = []
832   let stdin = []
833   while len(cb) && type(cb[0]) !=# type(function('tr'))
834     if type(cb[0]) ==# type({}) && has_key(cb[0], 'stdin')
835       if type(cb[0].stdin) == type([])
836         call extend(stdin, cb[0].stdin)
837       elseif type(cb[0].stdin) == type('')
838         call extend(stdin, readfile(cb[0].stdin, 'b'))
839       endif
840       if len(keys(cb[0])) == 1
841         call remove(cb, 0)
842         continue
843       endif
844     endif
845     call add(cmd, remove(cb, 0))
846   endwhile
847   let [argv, jopts, dict] = call('fugitive#PrepareJob', cmd)
848   return s:JobExecute(argv, jopts, stdin, cb, dict)
849 endfunction
851 function! s:BuildShell(dir, env, git, args) abort
852   let cmd = copy(a:args)
853   let tree = s:Tree(a:dir)
854   let pre = s:BuildEnvPrefix(a:env)
855   if empty(tree) || index(cmd, '--') == len(cmd) - 1
856     call insert(cmd, '--git-dir=' . FugitiveGitPath(a:dir))
857   else
858     call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
859     if !s:cpath(tree . '/.git', a:dir) || len($GIT_DIR)
860       call extend(cmd, ['--git-dir=' . FugitiveGitPath(a:dir)], 'keep')
861     endif
862   endif
863   return pre . join(map(a:git + cmd, 's:shellesc(v:val)'))
864 endfunction
866 function! s:JobNvimCallback(lines, job, data, type) abort
867   let a:lines[-1] .= remove(a:data, 0)
868   call extend(a:lines, a:data)
869 endfunction
871 function! s:SystemList(cmd) abort
872   let exit = []
873   if exists('*jobstart')
874     let lines = ['']
875     let jopts = {
876           \ 'on_stdout': function('s:JobNvimCallback', [lines]),
877           \ 'on_stderr': function('s:JobNvimCallback', [lines]),
878           \ 'on_exit': { j, code, _ -> add(exit, code) }}
879     let job = jobstart(a:cmd, jopts)
880     call chanclose(job, 'stdin')
881     call jobwait([job])
882     if empty(lines[-1])
883       call remove(lines, -1)
884     endif
885     return [lines, exit[0]]
886   elseif exists('*ch_close_in')
887     let lines = []
888     let jopts = {
889           \ 'out_cb': { j, str -> add(lines, str) },
890           \ 'err_cb': { j, str -> add(lines, str) },
891           \ 'exit_cb': { j, code -> add(exit, code) }}
892     let job = job_start(a:cmd, jopts)
893     call ch_close_in(job)
894     while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
895       sleep 1m
896     endwhile
897     return [lines, exit[0]]
898   else
899     let [output, exec_error] = s:SystemError(s:shellesc(a:cmd))
900     let lines = split(output, "\n", 1)
901     if empty(lines[-1])
902       call remove(lines, -1)
903     endif
904     return [lines, v:shell_error]
905   endif
906 endfunction
908 function! fugitive#ShellCommand(...) abort
909   let [repo, _, env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
910   return s:BuildShell(s:GitDir(repo), env, git, flags + args)
911 endfunction
913 function! s:SystemError(cmd, ...) abort
914   let cmd = type(a:cmd) == type([]) ? s:shellesc(a:cmd) : a:cmd
915   try
916     if &shellredir ==# '>' && &shell =~# 'sh\|cmd'
917       let shellredir = &shellredir
918       if &shell =~# 'csh'
919         set shellredir=>&
920       else
921         set shellredir=>%s\ 2>&1
922       endif
923     endif
924     if exists('+guioptions') && &guioptions =~# '!'
925       let guioptions = &guioptions
926       set guioptions-=!
927     endif
928     let out = call('system', [cmd] + a:000)
929     return [out, v:shell_error]
930   catch /^Vim\%((\a\+)\)\=:E484:/
931     let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
932     call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
933     call map(opts, 'v:val."=".eval("&".v:val)')
934     call s:throw('failed to run `' . cmd . '` with ' . join(opts, ' '))
935   finally
936     if exists('shellredir')
937       let &shellredir = shellredir
938     endif
939     if exists('guioptions')
940       let &guioptions = guioptions
941     endif
942   endtry
943 endfunction
945 function! s:ChompStderr(...) abort
946   let r = call('fugitive#Execute', a:000)
947   return !r.exit_status ? '' : len(r.stderr) > 1 ? s:JoinChomp(r.stderr) : 'unknown Git error' . string(r)
948 endfunction
950 function! s:ChompDefault(default, ...) abort
951   let r = call('fugitive#Execute', a:000)
952   return r.exit_status ? a:default : s:JoinChomp(r.stdout)
953 endfunction
955 function! s:LinesError(...) abort
956   let r = call('fugitive#Execute', a:000)
957   if empty(r.stdout[-1])
958     call remove(r.stdout, -1)
959   endif
960   return [r.exit_status ? [] : r.stdout, r.exit_status]
961 endfunction
963 function! s:TreeChomp(...) abort
964   let r = call('fugitive#Execute', a:000)
965   if !r.exit_status
966     return s:JoinChomp(r.stdout)
967   endif
968   throw 'fugitive: error running `' . call('fugitive#ShellCommand', a:000) . '`: ' . s:JoinChomp(r.stderr)
969 endfunction
971 function! s:StdoutToFile(out, cmd, ...) abort
972   let [argv, jopts, _] = fugitive#PrepareJob(a:cmd)
973   let exit = []
974   if exists('*jobstart')
975     call extend(jopts, {
976           \ 'stdout_buffered': v:true,
977           \ 'stderr_buffered': v:true,
978           \ 'on_exit': { j, code, _ -> add(exit, code) }})
979     let job = jobstart(argv, jopts)
980     if a:0
981       call chansend(job, a:1)
982     endif
983     call chanclose(job, 'stdin')
984     call jobwait([job])
985     if len(a:out)
986       call writefile(jopts.stdout, a:out, 'b')
987     endif
988     return [join(jopts.stderr, "\n"), exit[0]]
989   elseif exists('*ch_close_in')
990     try
991       let err = tempname()
992       call extend(jopts, {
993             \ 'out_io': len(a:out) ? 'file' : 'null',
994             \ 'out_name': a:out,
995             \ 'err_io': 'file',
996             \ 'err_name': err,
997             \ 'exit_cb': { j, code -> add(exit, code) }})
998       let job = job_start(argv, jopts)
999       if a:0
1000         call ch_sendraw(job, a:1)
1001       endif
1002       call ch_close_in(job)
1003       while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
1004         sleep 1m
1005       endwhile
1006       return [join(readfile(err, 'b'), "\n"), exit[0]]
1007     finally
1008       call delete(err)
1009     endtry
1010   elseif s:winshell() || &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
1011     throw 'fugitive: Vim 8 or higher required to use ' . &shell
1012   else
1013     let cmd = fugitive#ShellCommand(a:cmd)
1014     return call('s:SystemError', [' (' . cmd . ' >' . (len(a:out) ? a:out : '/dev/null') . ') '] + a:000)
1015   endif
1016 endfunction
1018 let s:head_cache = {}
1020 function! fugitive#Head(...) abort
1021   let dir = a:0 > 1 ? a:2 : s:Dir()
1022   if empty(dir)
1023     return ''
1024   endif
1025   let file = FugitiveActualDir(dir) . '/HEAD'
1026   let ftime = getftime(file)
1027   if ftime == -1
1028     return ''
1029   elseif ftime != get(s:head_cache, file, [-1])[0]
1030     let s:head_cache[file] = [ftime, readfile(file)[0]]
1031   endif
1032   let head = s:head_cache[file][1]
1033   let len = a:0 ? a:1 : 0
1034   if head =~# '^ref: '
1035     if len < 0
1036       return strpart(head, 5)
1037     else
1038       return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
1039     endif
1040   elseif head =~# '^\x\{40,\}$'
1041     return len < 0 ? head : strpart(head, 0, len)
1042   else
1043     return ''
1044   endif
1045 endfunction
1047 function! fugitive#RevParse(rev, ...) abort
1048   let hash = s:ChompDefault('', [a:0 ? a:1 : s:Dir(), 'rev-parse', '--verify', a:rev, '--'])
1049   if hash =~# '^\x\{40,\}$'
1050     return hash
1051   endif
1052   throw 'fugitive: failed to parse revision ' . a:rev
1053 endfunction
1055 " Section: Git config
1057 function! s:ConfigTimestamps(dir, dict) abort
1058   let files = ['/etc/gitconfig', '~/.gitconfig',
1059         \ len($XDG_CONFIG_HOME) ? $XDG_CONFIG_HOME . '/git/config' : '~/.config/git/config']
1060   if len(a:dir)
1061     call add(files, fugitive#Find('.git/config', a:dir))
1062   endif
1063   call extend(files, get(a:dict, 'include.path', []))
1064   return join(map(files, 'getftime(expand(v:val))'), ',')
1065 endfunction
1067 function! s:ConfigCallback(r, into) abort
1068   let dict = a:into[1]
1069   if has_key(dict, 'job')
1070     call remove(dict, 'job')
1071   endif
1072   let lines = a:r.exit_status ? [] : split(tr(join(a:r.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
1073   for line in lines
1074     let key = matchstr(line, "^[^\n]*")
1075     if !has_key(dict, key)
1076       let dict[key] = []
1077     endif
1078     if len(key) ==# len(line)
1079       call add(dict[key], 1)
1080     else
1081       call add(dict[key], strpart(line, len(key) + 1))
1082     endif
1083   endfor
1084   let callbacks = remove(dict, 'callbacks')
1085   lockvar! dict
1086   let a:into[0] = s:ConfigTimestamps(dict.git_dir, dict)
1087   for callback in callbacks
1088     call call(callback[0], [dict] + callback[1:-1])
1089   endfor
1090 endfunction
1092 let s:config_prototype = {}
1094 let s:config = {}
1095 function! fugitive#ExpireConfig(...) abort
1096   if !a:0 || a:1 is# 0
1097     let s:config = {}
1098   else
1099     let key = a:1 is# '' ? '_' : s:GitDir(a:0 ? a:1 : -1)
1100     if len(key) && has_key(s:config, key)
1101       call remove(s:config, key)
1102     endif
1103   endif
1104 endfunction
1106 function! fugitive#Config(...) abort
1107   let name = ''
1108   let default = get(a:, 3, '')
1109   if a:0 && type(a:1) == type(function('tr'))
1110     let dir = s:Dir()
1111     let callback = a:000
1112   elseif a:0 > 1 && type(a:2) == type(function('tr'))
1113     if type(a:1) == type({}) && has_key(a:1, 'GetAll')
1114       if has_key(a:1, 'callbacks')
1115         call add(a:1.callbacks, a:000[1:-1])
1116       else
1117         call call(a:2, [a:1] + a:000[2:-1])
1118       endif
1119       return a:1
1120     else
1121       let dir = s:Dir(a:1)
1122       let callback = a:000[1:-1]
1123     endif
1124   elseif a:0 >= 2 && type(a:2) == type({}) && has_key(a:2, 'GetAll')
1125     return get(fugitive#ConfigGetAll(a:1, a:2), -1, default)
1126   elseif a:0 >= 2
1127     let dir = s:Dir(a:2)
1128     let name = a:1
1129   elseif a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'GetAll')
1130     return a:1
1131   elseif a:0 == 1 && type(a:1) == type('') && a:1 =~# '^[[:alnum:]-]\+\.'
1132     let dir = s:Dir()
1133     let name = a:1
1134   elseif a:0 == 1
1135     let dir = s:Dir(a:1)
1136   else
1137     let dir = s:Dir()
1138   endif
1139   let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1140   let git_dir = s:GitDir(dir)
1141   let dir_key = len(git_dir) ? git_dir : '_'
1142   let [ts, dict] = get(s:config, dir_key, ['new', {}])
1143   if !has_key(dict, 'job') && ts !=# s:ConfigTimestamps(git_dir, dict)
1144     let dict = copy(s:config_prototype)
1145     let dict.git_dir = git_dir
1146     let into = ['running', dict]
1147     let dict.callbacks = []
1148     let exec = fugitive#Execute([dir, 'config', '--list', '-z', '--'], function('s:ConfigCallback'), into)
1149     if has_key(exec, 'job')
1150       let dict.job = exec.job
1151     endif
1152     let s:config[dir_key] = into
1153   endif
1154   if !exists('l:callback')
1155     call fugitive#Wait(dict)
1156   elseif has_key(dict, 'callbacks')
1157     call add(dict.callbacks, callback)
1158   else
1159     call call(callback[0], [dict] + callback[1:-1])
1160   endif
1161   return len(name) ? get(fugitive#ConfigGetAll(name, dict), 0, default) : dict
1162 endfunction
1164 function! fugitive#ConfigGetAll(name, ...) abort
1165   if a:0 && (type(a:name) !=# type('') || a:name !~# '^[[:alnum:]-]\+\.' && type(a:1) ==# type('') && a:1 =~# '^[[:alnum:]-]\+\.')
1166     let config = fugitive#Config(a:name)
1167     let name = a:1
1168   else
1169     let config = fugitive#Config(a:0 ? a:1 : s:Dir())
1170     let name = a:name
1171   endif
1172   let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1173   call fugitive#Wait(config)
1174   return name =~# '\.' ? copy(get(config, name, [])) : []
1175 endfunction
1177 function! fugitive#ConfigGetRegexp(pattern, ...) abort
1178   if type(a:pattern) !=# type('')
1179     let config = fugitive#Config(a:name)
1180     let pattern = a:0 ? a:1 : '.*'
1181   else
1182     let config = fugitive#Config(a:0 ? a:1 : s:Dir())
1183     let pattern = a:pattern
1184   endif
1185   call fugitive#Wait(config)
1186   let filtered = map(filter(copy(config), 'v:key =~# "\\." && v:key =~# pattern'), 'copy(v:val)')
1187   if pattern !~# '\\\@<!\%(\\\\\)*\\z[se]'
1188     return filtered
1189   endif
1190   let transformed = {}
1191   for [k, v] in items(filtered)
1192     let k = matchstr(k, pattern)
1193     if len(k)
1194       let transformed[k] = v
1195     endif
1196   endfor
1197   return transformed
1198 endfunction
1200 function! s:config_GetAll(name) dict abort
1201   let name = substitute(a:name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1202   call fugitive#Wait(self)
1203   return name =~# '\.' ? copy(get(self, name, [])) : []
1204 endfunction
1206 function! s:config_Get(name, ...) dict abort
1207   return get(self.GetAll(a:name), -1, a:0 ? a:1 : '')
1208 endfunction
1210 function! s:config_GetRegexp(pattern) dict abort
1211   return fugitive#ConfigGetRegexp(self, a:pattern)
1212 endfunction
1214 call s:add_methods('config', ['GetAll', 'Get', 'GetRegexp'])
1216 function! s:RemoteDefault(dir) abort
1217   let head = FugitiveHead(0, a:dir)
1218   let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
1219   let i = 10
1220   while remote ==# '.' && i > 0
1221     let head = matchstr(FugitiveConfigGet('branch.' . head . '.merge', a:dir), 'refs/heads/\zs.*')
1222     let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
1223     let i -= 1
1224   endwhile
1225   return remote =~# '^\.\=$' ? 'origin' : remote
1226 endfunction
1228 function! s:SshParseHost(value) abort
1229   let patterns = []
1230   let negates = []
1231   for host in split(a:value, '\s\+')
1232     let pattern = substitute(host, '[\\^$.*~?]', '\=submatch(0) == "*" ? ".*" : submatch(0) == "?" ? "." : "\\" . submatch(0)', 'g')
1233     if pattern[0] ==# '!'
1234       call add(negates, '\&\%(^' . pattern[1 : -1] . '$\)\@!')
1235     else
1236       call add(patterns, pattern)
1237     endif
1238   endfor
1239   return '^\%(' . join(patterns, '\|') . '\)$' . join(negates, '')
1240 endfunction
1242 function! s:SshParseConfig(into, root, file) abort
1243   try
1244     let lines = readfile(a:file)
1245   catch
1246     return a:into
1247   endtry
1248   let host = '^\%(.*\)$'
1249   while !empty(lines)
1250     let line = remove(lines, 0)
1251     let key = tolower(matchstr(line, '^\s*\zs\w\+\ze\s'))
1252     let value = matchstr(line, '^\s*\w\+\s\+\zs.*\S')
1253     if key ==# 'match'
1254       let host = value ==# 'all' ? '^\%(.*\)$' : ''
1255     elseif key ==# 'host'
1256       let host = s:SshParseHost(value)
1257     elseif key ==# 'include'
1258       for glob in split(value)
1259         if glob !~# '^[~/]'
1260           let glob = a:root . glob
1261         endif
1262         for included in reverse(split(glob(glob), "\n"))
1263           try
1264             call extend(lines, readfile(included), 'keep')
1265           catch
1266           endtry
1267         endfor
1268       endfor
1269     elseif len(key) && len(host)
1270       call extend(a:into, {key : []}, 'keep')
1271       call add(a:into[key], [host, value])
1272     endif
1273   endwhile
1274   return a:into
1275 endfunction
1277 unlet! s:ssh_config
1278 function! fugitive#SshConfig(host, ...) abort
1279   if !exists('s:ssh_config')
1280     let s:ssh_config = {}
1281     for file in [expand("~/.ssh/config"), "/etc/ssh/ssh_config"]
1282       call s:SshParseConfig(s:ssh_config, substitute(file, '\w*$', '', ''), file)
1283     endfor
1284   endif
1285   let host_config = {}
1286   for key in a:0 ? a:1 : keys(s:ssh_config)
1287     for [host_pattern, value] in get(s:ssh_config, key, [])
1288       if a:host =~# host_pattern
1289         let host_config[key] = value
1290         break
1291       endif
1292     endfor
1293   endfor
1294   return host_config
1295 endfunction
1297 function! fugitive#SshHostAlias(authority) abort
1298   let [_, user, host, port; __] = matchlist(a:authority, '^\%(\([^/@]\+\)@\)\=\(.\{-\}\)\%(:\(\d\+\)\)\=$')
1299   let c = fugitive#SshConfig(host, ['user', 'hostname', 'port'])
1300   if empty(user)
1301     let user = get(c, 'user', '')
1302   endif
1303   if empty(port)
1304     let port = get(c, 'port', '')
1305   endif
1306   return (len(user) ? user . '@' : '') . get(c, 'hostname', host) . (port =~# '^\%(22\)\=$' ? '' : ':' . port)
1307 endfunction
1309 function! s:CurlResponse(result) abort
1310   let a:result.headers = {}
1311   for line in a:result.exit_status ? [] : remove(a:result, 'stdout')
1312     let header = matchlist(line, '^\([[:alnum:]-]\+\):\s\(.\{-\}\)'. "\r\\=$")
1313     if len(header)
1314       let k = tolower(header[1])
1315       if has_key(a:result.headers, k)
1316         let a:result.headers[k] .= ', ' . header[2]
1317       else
1318         let a:result.headers[k] = header[2]
1319       endif
1320     elseif empty(line)
1321       break
1322     endif
1323   endfor
1324 endfunction
1326 let s:remote_headers = {}
1328 function! fugitive#RemoteHttpHeaders(remote) abort
1329   let remote = type(a:remote) ==# type({}) ? get(a:remote, 'remote', '') : a:remote
1330   if type(remote) !=# type('') || remote !~# '^https\=://.' || !s:executable('curl')
1331     return {}
1332   endif
1333   let remote = substitute(remote, '#.*', '', '')
1334   if !has_key(s:remote_headers, remote)
1335     let url = remote . '/info/refs?service=git-upload-pack'
1336     let exec = s:JobExecute(
1337           \ ['curl', '--disable', '--silent', '--max-time', '5', '-X', 'GET', '-I',
1338           \ url], {}, [], [function('s:CurlResponse')], {})
1339     call fugitive#Wait(exec)
1340     let s:remote_headers[remote] = exec.headers
1341   endif
1342   return s:remote_headers[remote]
1343 endfunction
1345 function! s:UrlParse(url) abort
1346   let scp_authority = matchstr(a:url, '^[^:/]\+\ze:\%(//\)\@!')
1347   if len(scp_authority) && !(has('win32') && scp_authority =~# '^\a:[\/]')
1348     let url = {'scheme': 'ssh', 'authority': s:UrlEncode(scp_authority), 'hash': '',
1349           \ 'path': s:UrlEncode(strpart(a:url, len(scp_authority) + 1))}
1350   elseif empty(a:url)
1351     let url = {'scheme': '', 'authority': '', 'path': '', 'hash': ''}
1352   else
1353     let match = matchlist(a:url, '^\([[:alnum:].+-]\+\)://\([^/]*\)\(/[^#]*\)\=\(#.*\)\=$')
1354     if empty(match)
1355       let url = {'scheme': 'file', 'authority': '', 'hash': '',
1356             \ 'path': s:UrlEncode(a:url)}
1357     else
1358       let url = {'scheme': match[1], 'authority': match[2], 'hash': match[4]}
1359       let url.path = empty(match[3]) ? '/' : match[3]
1360     endif
1361   endif
1362   return url
1363 endfunction
1365 function! s:UrlPopulate(string, into) abort
1366   let url = a:into
1367   let url.protocol = substitute(url.scheme, '.\zs$', ':', '')
1368   let url.user = fugitive#UrlDecode(matchstr(url.authority, '.\{-\}\ze@', '', ''))
1369   let url.host = substitute(url.authority, '.\{-\}@', '', '')
1370   let url.hostname = substitute(url.host, ':\d\+$', '', '')
1371   let url.port = matchstr(url.host, ':\zs\d\+$', '', '')
1372   let url.origin = substitute(url.scheme, '.\zs$', '://', '') . url.host
1373   let url.search = matchstr(url.path, '?.*')
1374   let url.pathname = '/' . matchstr(url.path, '^/\=\zs[^?]*')
1375   if (url.scheme ==# 'ssh' || url.scheme ==# 'git') && url.path[0:1] ==# '/~'
1376     let url.path = strpart(url.path, 1)
1377   endif
1378   if url.path =~# '^/'
1379     let url.href = url.scheme . '://' . url.authority . url.path . url.hash
1380   elseif url.path =~# '^\~'
1381     let url.href = url.scheme . '://' . url.authority . '/' . url.path . url.hash
1382   elseif url.scheme ==# 'ssh' && url.authority !~# ':'
1383     let url.href = url.authority . ':' . url.path . url.hash
1384   else
1385     let url.href = a:string
1386   endif
1387   let url.path = fugitive#UrlDecode(matchstr(url.path, '^[^?]*'))
1388   let url.url = matchstr(url.href, '^[^#]*')
1389 endfunction
1391 function! s:RemoteResolve(url, flags) abort
1392   let remote = s:UrlParse(a:url)
1393   if remote.scheme =~# '^https\=$' && index(a:flags, ':nohttp') < 0
1394     let headers = fugitive#RemoteHttpHeaders(a:url)
1395     let loc = matchstr(get(headers, 'location', ''), '^https\=://.\{-\}\ze/info/refs?')
1396     if len(loc)
1397       let remote = s:UrlParse(loc)
1398     else
1399       let remote.headers = headers
1400     endif
1401   elseif remote.scheme ==# 'ssh'
1402     let remote.authority = fugitive#SshHostAlias(remote.authority)
1403   endif
1404   return remote
1405 endfunction
1407 function! s:ConfigLengthSort(i1, i2) abort
1408   return len(a:i2[0]) - len(a:i1[0])
1409 endfunction
1411 function! s:RemoteCallback(config, into, flags, cb) abort
1412   if a:into.remote_name =~# '^\.\=$'
1413     let a:into.remote_name = s:RemoteDefault(a:config)
1414   endif
1415   let url = a:into.remote_name
1417   if url ==# '.git'
1418     let url = s:GitDir(a:config)
1419   elseif url !~# ':\|^/\|^\a:[\/]\|^\.\.\=/'
1420     let url = FugitiveConfigGet('remote.' . url . '.url', a:config)
1421   endif
1422   let instead_of = []
1423   for [k, vs] in items(fugitive#ConfigGetRegexp('^url\.\zs.\{-\}\ze\.insteadof$', a:config))
1424     for v in vs
1425       call add(instead_of, [v, k])
1426     endfor
1427   endfor
1428   call sort(instead_of, 's:ConfigLengthSort')
1429   for [orig, replacement] in instead_of
1430     if strpart(url, 0, len(orig)) ==# orig
1431       let url = replacement . strpart(url, len(orig))
1432       break
1433     endif
1434   endfor
1435   if index(a:flags, ':noresolve') < 0
1436     call extend(a:into, s:RemoteResolve(url, a:flags))
1437   else
1438     call extend(a:into, s:UrlParse(url))
1439   endif
1440   call s:UrlPopulate(url, a:into)
1441   if len(a:cb)
1442     call call(a:cb[0], [a:into] + a:cb[1:-1])
1443   endif
1444 endfunction
1446 function! s:Remote(dir, remote, flags, cb) abort
1447   let into = {'remote_name': a:remote, 'git_dir': s:GitDir(a:dir)}
1448   let config = fugitive#Config(a:dir, function('s:RemoteCallback'), into, a:flags, a:cb)
1449   if len(a:cb)
1450     return config
1451   else
1452     call fugitive#Wait(config)
1453     return into
1454   endif
1455 endfunction
1457 function! s:RemoteParseArgs(args) abort
1458   " Extract ':noresolve' style flags and an optional callback
1459   let args = []
1460   let flags = []
1461   let cb = copy(a:args)
1462   while len(cb)
1463     if type(cb[0]) ==# type(function('tr'))
1464       break
1465     elseif len(args) > 1 || type(cb[0]) ==# type('') && cb[0] =~# '^:'
1466       call add(flags, remove(cb, 0))
1467     else
1468       call add(args, remove(cb, 0))
1469     endif
1470   endwhile
1472   " From the remaining 0-2 arguments, extract the remote and Git config
1473   let remote = ''
1474   if empty(args)
1475     let dir_or_config = s:Dir()
1476   elseif len(args) == 1 && type(args[0]) ==# type('') && args[0] !~# '^/\|^\a:[\\/]'
1477     let dir_or_config = s:Dir()
1478     let remote = args[0]
1479   elseif len(args) == 1
1480     let dir_or_config = args[0]
1481     if type(args[0]) ==# type({}) && has_key(args[0], 'remote_name')
1482       let remote = args[0].remote_name
1483     endif
1484   elseif type(args[1]) !=# type('') || args[1] =~# '^/\|^\a:[\\/]'
1485     let dir_or_config = args[1]
1486     let remote = args[0]
1487   else
1488     let dir_or_config = args[0]
1489     let remote = args[1]
1490   endif
1491   return [dir_or_config, remote, flags, cb]
1492 endfunction
1494 function! fugitive#Remote(...) abort
1495   let [dir_or_config, remote, flags, cb] = s:RemoteParseArgs(a:000)
1496   return s:Remote(dir_or_config, remote, flags, cb)
1497 endfunction
1499 function! s:RemoteUrlCallback(remote, callback) abort
1500   return call(a:callback[0], [a:remote.url] + a:callback[1:-1])
1501 endfunction
1503 function! fugitive#RemoteUrl(...) abort
1504   let [dir_or_config, remote_url, flags, cb] = s:RemoteParseArgs(a:000)
1505   if len(cb)
1506     let cb = [function('s:RemoteUrlCallback'), cb]
1507   endif
1508   let remote = s:Remote(dir_or_config, remote_url, flags, cb)
1509   return get(remote, 'url', remote_url)
1510 endfunction
1512 " Section: Quickfix
1514 function! s:QuickfixGet(nr, ...) abort
1515   if a:nr < 0
1516     return call('getqflist', a:000)
1517   else
1518     return call('getloclist', [a:nr] + a:000)
1519   endif
1520 endfunction
1522 function! s:QuickfixSet(nr, ...) abort
1523   if a:nr < 0
1524     return call('setqflist', a:000)
1525   else
1526     return call('setloclist', [a:nr] + a:000)
1527   endif
1528 endfunction
1530 function! s:QuickfixCreate(nr, opts) abort
1531   if has('patch-7.4.2200')
1532     call s:QuickfixSet(a:nr, [], ' ', a:opts)
1533   else
1534     call s:QuickfixSet(a:nr, [], ' ')
1535   endif
1536 endfunction
1538 function! s:QuickfixOpen(nr, mods) abort
1539   let mods = substitute(s:Mods(a:mods), '\<\d*tab\>', '', '')
1540   return mods . (a:nr < 0 ? 'c' : 'l').'open' . (mods =~# '\<vertical\>' ? ' 20' : '')
1541 endfunction
1543 function! s:QuickfixStream(nr, event, title, cmd, first, mods, callback, ...) abort
1544   call s:BlurStatus()
1545   let opts = {'title': a:title, 'context': {'items': []}}
1546   call s:QuickfixCreate(a:nr, opts)
1547   let event = (a:nr < 0 ? 'c' : 'l') . 'fugitive-' . a:event
1548   exe s:DoAutocmd('QuickFixCmdPre ' . event)
1549   let winnr = winnr()
1550   exe s:QuickfixOpen(a:nr, a:mods)
1551   if winnr != winnr()
1552     wincmd p
1553   endif
1555   let buffer = []
1556   let lines = s:SystemList(a:cmd)[0]
1557   for line in lines
1558     call extend(buffer, call(a:callback, a:000 + [line]))
1559     if len(buffer) >= 20
1560       let contexts = map(copy(buffer), 'get(v:val, "context", {})')
1561       lockvar contexts
1562       call extend(opts.context.items, contexts)
1563       unlet contexts
1564       call s:QuickfixSet(a:nr, remove(buffer, 0, -1), 'a')
1565       if a:mods !~# '\<silent\>'
1566         redraw
1567       endif
1568     endif
1569   endfor
1570   call extend(buffer, call(a:callback, a:000 + [0]))
1571   call extend(opts.context.items, map(copy(buffer), 'get(v:val, "context", {})'))
1572   lockvar opts.context.items
1573   call s:QuickfixSet(a:nr, buffer, 'a')
1575   exe s:DoAutocmd('QuickFixCmdPost ' . event)
1576   if a:first
1577     let list = s:QuickfixGet(a:nr)
1578     for index in range(len(list))
1579       if list[index].valid
1580         return (index+1) . (a:nr < 0 ? 'cfirst' : 'lfirst')
1581       endif
1582     endfor
1583   endif
1584   return 'exe'
1585 endfunction
1587 function! fugitive#Cwindow() abort
1588   if &buftype == 'quickfix'
1589     cwindow
1590   else
1591     botright cwindow
1592     if &buftype == 'quickfix'
1593       wincmd p
1594     endif
1595   endif
1596 endfunction
1598 " Section: Repository Object
1600 let s:repo_prototype = {}
1602 function! fugitive#repo(...) abort
1603   let dir = a:0 ? s:GitDir(a:1) : (len(s:GitDir()) ? s:GitDir() : FugitiveExtractGitDir(expand('%:p')))
1604   if dir !=# ''
1605     return extend({'git_dir': dir, 'fugitive_dir': dir}, s:repo_prototype, 'keep')
1606   endif
1607   throw 'fugitive: not a Git repository'
1608 endfunction
1610 function! s:repo_dir(...) dict abort
1611   if !a:0
1612     return self.git_dir
1613   endif
1614   throw 'fugitive: fugitive#repo().dir("...") has been replaced by FugitiveFind(".git/...")'
1615 endfunction
1617 function! s:repo_tree(...) dict abort
1618   let tree = s:Tree(self.git_dir)
1619   if empty(tree)
1620     throw 'fugitive: no work tree'
1621   elseif !a:0
1622     return tree
1623   endif
1624   throw 'fugitive: fugitive#repo().tree("...") has been replaced by FugitiveFind(":(top)...")'
1625 endfunction
1627 function! s:repo_bare() dict abort
1628   throw 'fugitive: fugitive#repo().bare() has been replaced by !empty(FugitiveWorkTree())'
1629 endfunction
1631 function! s:repo_find(object) dict abort
1632   throw 'fugitive: fugitive#repo().find(...) has been replaced by FugitiveFind(...)'
1633 endfunction
1635 function! s:repo_translate(rev) dict abort
1636   throw 'fugitive: fugitive#repo().translate(...) has been replaced by FugitiveFind(...)'
1637 endfunction
1639 function! s:repo_head(...) dict abort
1640   throw 'fugitive: fugitive#repo().head(...) has been replaced by FugitiveHead(...)'
1641 endfunction
1643 call s:add_methods('repo',['dir','tree','bare','find','translate','head'])
1645 function! s:repo_git_command(...) dict abort
1646   throw 'fugitive: fugitive#repo().git_command(...) has been replaced by FugitiveShellCommand(...)'
1647 endfunction
1649 function! s:repo_git_chomp(...) dict abort
1650   silent return substitute(system(fugitive#ShellCommand(a:000, self.git_dir)), '\n$', '', '')
1651 endfunction
1653 function! s:repo_git_chomp_in_tree(...) dict abort
1654   return call(self.git_chomp, a:000, self)
1655 endfunction
1657 function! s:repo_rev_parse(rev) dict abort
1658   throw 'fugitive: fugitive#repo().rev_parse(...) has been replaced by FugitiveExecute("rev-parse", "--verify", ...).stdout'
1659 endfunction
1661 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
1663 function! s:repo_config(name) dict abort
1664   return FugitiveConfigGet(a:name, self.git_dir)
1665 endfunction
1667 call s:add_methods('repo',['config'])
1669 " Section: File API
1671 function! s:DirCommitFile(path) abort
1672   let vals = matchlist(s:Slash(a:path), s:dir_commit_file)
1673   if empty(vals)
1674     return ['', '', '']
1675   endif
1676   return [s:Dir(fugitive#UrlDecode(vals[1])), vals[2], empty(vals[2]) ? '/.git/index' : fugitive#UrlDecode(vals[3])]
1677 endfunction
1679 function! s:DirRev(url) abort
1680   let [dir, commit, file] = s:DirCommitFile(a:url)
1681   return [dir, commit . file ==# '/.git/index' ? ':' : (!empty(dir) && commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
1682 endfunction
1684 function! fugitive#Parse(url) abort
1685   return reverse(s:DirRev(a:url))
1686 endfunction
1688 let s:merge_heads = ['MERGE_HEAD', 'REBASE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD']
1689 function! s:MergeHead(dir) abort
1690   let dir = fugitive#Find('.git/', a:dir)
1691   for head in s:merge_heads
1692     if filereadable(dir . head)
1693       return head
1694     endif
1695   endfor
1696   return ''
1697 endfunction
1699 function! s:Owner(path, ...) abort
1700   let dir = a:0 ? s:Dir(a:1) : s:Dir()
1701   if empty(dir)
1702     return ''
1703   endif
1704   let actualdir = fugitive#Find('.git/', dir)
1705   let [pdir, commit, file] = s:DirCommitFile(a:path)
1706   if s:SameRepo(dir, pdir)
1707     if commit =~# '^\x\{40,\}$'
1708       return commit
1709     elseif commit ==# '2'
1710       return '@'
1711     elseif commit ==# '0'
1712       return ''
1713     endif
1714     let merge_head = s:MergeHead(dir)
1715     if empty(merge_head)
1716       return ''
1717     endif
1718     if commit ==# '3'
1719       return merge_head
1720     elseif commit ==# '1'
1721       return s:TreeChomp('merge-base', 'HEAD', merge_head, '--')
1722     endif
1723   endif
1724   let path = fnamemodify(a:path, ':p')
1725   if s:cpath(actualdir, strpart(path, 0, len(actualdir))) && a:path =~# 'HEAD$'
1726     return strpart(path, len(actualdir))
1727   endif
1728   let refs = fugitive#Find('.git/refs', dir)
1729   if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
1730     return strpart(path, len(refs) - 4)
1731   endif
1732   return ''
1733 endfunction
1735 function! fugitive#Real(url) abort
1736   if empty(a:url)
1737     return ''
1738   endif
1739   let [dir, commit, file] = s:DirCommitFile(a:url)
1740   if len(dir)
1741     let tree = s:Tree(dir)
1742     return s:VimSlash((len(tree) ? tree : s:GitDir(dir)) . file)
1743   endif
1744   let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
1745   if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
1746     let url = {pre}Real(a:url)
1747   else
1748     let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
1749   endif
1750   return s:VimSlash(empty(url) ? a:url : url)
1751 endfunction
1753 function! fugitive#Path(url, ...) abort
1754   if empty(a:url)
1755     return ''
1756   endif
1757   let repo = call('s:Dir', a:000[1:-1])
1758   let dir_s = fugitive#Find('.git/', repo)
1759   let tree = fugitive#Find(':/', repo)
1760   if !a:0
1761     return fugitive#Real(a:url)
1762   elseif a:1 =~# '\.$'
1763     let path = s:Slash(fugitive#Real(a:url))
1764     let cwd = getcwd()
1765     let lead = ''
1766     while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
1767       if s:cpath(cwd . '/', path[0 : len(cwd)])
1768         if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
1769           break
1770         endif
1771         return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
1772       endif
1773       let cwd = fnamemodify(cwd, ':h')
1774       let lead .= '../'
1775     endwhile
1776     return a:1[0:-2] . path
1777   endif
1778   let url = a:url
1779   let temp_state = s:TempState(url)
1780   if has_key(temp_state, 'origin_bufnr')
1781     let url = bufname(temp_state.origin_bufnr)
1782   endif
1783   let url = s:Slash(fnamemodify(url, ':p'))
1784   if url =~# '/$' && s:Slash(a:url) !~# '/$'
1785     let url = url[0:-2]
1786   endif
1787   let [argdir, commit, file] = s:DirCommitFile(url)
1788   if !empty(argdir) && !s:SameRepo(argdir, repo)
1789     let file = ''
1790   elseif len(dir_s) && s:cpath(strpart(url, 0, len(dir_s)), dir_s)
1791     let file = '/.git' . strpart(url, len(dir_s)-1)
1792   elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
1793     let file = url[len(tree) : -1]
1794   elseif s:cpath(url) ==# s:cpath(tree)
1795     let file = '/'
1796   endif
1797   if empty(file) && a:1 =~# '^$\|^[.:]/$'
1798     return FugitiveGitPath(fugitive#Real(a:url))
1799   endif
1800   return substitute(file, '^/', '\=a:1', '')
1801 endfunction
1803 function! s:Relative(...) abort
1804   return fugitive#Path(@%, a:0 ? a:1 : ':(top)', a:0 > 1 ? a:2 : s:Dir())
1805 endfunction
1807 function! fugitive#Find(object, ...) abort
1808   if type(a:object) == type(0)
1809     let name = bufname(a:object)
1810     return s:VimSlash(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
1811   elseif a:object =~# '^[~$]'
1812     let prefix = matchstr(a:object, '^[~$]\i*')
1813     let owner = expand(prefix)
1814     return s:VimSlash(FugitiveVimPath((len(owner) ? owner : prefix) . strpart(a:object, len(prefix))))
1815   endif
1816   let rev = s:Slash(a:object)
1817   if rev =~# '^\a\+://' && rev !~# '^fugitive:'
1818     return rev
1819   elseif rev =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
1820     return s:VimSlash(a:object)
1821   elseif rev =~# '^\.\.\=\%(/\|$\)'
1822     return s:VimSlash(simplify(getcwd() . '/' . a:object))
1823   endif
1824   let dir = call('s:GitDir', a:000)
1825   if empty(dir)
1826     let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs\%(\.\.\=$\|\.\.\=/.*\|/.*\|\w:/.*\)')
1827     let dir = FugitiveExtractGitDir(file)
1828     if empty(dir)
1829       return ''
1830     endif
1831   endif
1832   let tree = s:Tree(dir)
1833   let urlprefix = s:DirUrlPrefix(dir)
1834   let base = len(tree) ? tree : urlprefix . '0'
1835   if rev ==# '.git'
1836     let f = len(tree) && len(getftype(tree . '/.git')) ? tree . '/.git' : dir
1837   elseif rev =~# '^\.git/'
1838     let f = strpart(rev, 5)
1839     let fdir = simplify(FugitiveActualDir(dir) . '/')
1840     let cdir = simplify(FugitiveCommonDir(dir) . '/')
1841     if f =~# '^\.\./\.\.\%(/\|$\)'
1842       let f = simplify(len(tree) ? tree . f[2:-1] : fdir . f)
1843     elseif f =~# '^\.\.\%(/\|$\)'
1844       let f = s:PathJoin(base, f[2:-1])
1845     elseif cdir !=# fdir && (
1846           \ f =~# '^\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
1847           \ f !~# '^\%(index$\|index\.lock$\|\w*MSG$\|\w*HEAD$\|logs/\w*HEAD$\|logs$\|rebase-\w\+\)\%(/\|$\)' &&
1848           \ getftime(fdir . f) < 0 && getftime(cdir . f) >= 0)
1849       let f = simplify(cdir . f)
1850     else
1851       let f = simplify(fdir . f)
1852     endif
1853   elseif rev ==# ':/'
1854     let f = tree
1855   elseif rev =~# '^\.\%(/\|$\)'
1856     let f = s:PathJoin(base, rev[1:-1])
1857   elseif rev =~# '^::\%(/\|\a\+\:\)'
1858     let f = rev[2:-1]
1859   elseif rev =~# '^::\.\.\=\%(/\|$\)'
1860     let f = simplify(getcwd() . '/' . rev[2:-1])
1861   elseif rev =~# '^::'
1862     let f = s:PathJoin(base, '/' . rev[2:-1])
1863   elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
1864     let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
1865     if s:cpath(base . '/', (f . '/')[0 : len(base)])
1866       let f = s:PathJoin(urlprefix, +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1))
1867     else
1868       let altdir = FugitiveExtractGitDir(f)
1869       if len(altdir) && !s:cpath(dir, altdir)
1870         return fugitive#Find(a:object, altdir)
1871       endif
1872     endif
1873   elseif rev =~# '^:[0-3]:'
1874     let f = s:PathJoin(urlprefix, rev[1] . '/' . rev[3:-1])
1875   elseif rev ==# ':'
1876     let f = urlprefix
1877   elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
1878     let f = matchstr(rev, ')\zs.*')
1879     if f=~# '^\.\.\=\%(/\|$\)'
1880       let f = simplify(getcwd() . '/' . f)
1881     elseif f !~# '^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
1882       let f = s:PathJoin(base, '/' . f)
1883     endif
1884   elseif rev =~# '^:/\@!'
1885     let f = s:PathJoin(urlprefix, '0/' . rev[1:-1])
1886   else
1887     if !exists('f')
1888       let commit = matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\|^:.*')
1889       let file = substitute(matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\zs:.*'), '^:', '/', '')
1890       if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
1891         let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
1892         if s:cpath(base . '/', (file . '/')[0 : len(base)])
1893           let file = '/' . strpart(file, len(base) + 1)
1894         else
1895           let altdir = FugitiveExtractGitDir(file)
1896           if len(altdir) && !s:cpath(dir, altdir)
1897             return fugitive#Find(a:object, altdir)
1898           endif
1899           return file
1900         endif
1901       endif
1902       let commits = split(commit, '\.\.\.-\@!', 1)
1903       if len(commits) == 2
1904         call map(commits, 'empty(v:val) ? "@" : v:val')
1905         let commit = matchstr(s:ChompDefault('', [dir, 'merge-base'] + commits + ['--']), '\<[0-9a-f]\{40,\}\>')
1906       endif
1907       if commit !~# '^[0-9a-f]\{40,\}$\|^$'
1908         let commit = matchstr(s:ChompDefault('', [dir, 'rev-parse', '--verify', commit . (len(file) ? '^{}' : ''), '--']), '\<[0-9a-f]\{40,\}\>')
1909         if empty(commit) && len(file)
1910           let commit = repeat('0', 40)
1911         endif
1912       endif
1913       if len(commit)
1914         let f = s:PathJoin(urlprefix, commit . file)
1915       else
1916         let f = s:PathJoin(base, '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', ''))
1917       endif
1918     endif
1919   endif
1920   return s:VimSlash(f)
1921 endfunction
1923 function! s:Generate(object, ...) abort
1924   let dir = a:0 ? a:1 : s:Dir()
1925   let f = fugitive#Find(a:object, dir)
1926   if !empty(f)
1927     return f
1928   elseif a:object ==# ':/'
1929     return len(dir) ? s:VimSlash(s:DirUrlPrefix(dir) . '0') : '.'
1930   endif
1931   let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\=\zs.*')
1932   return empty(file) ? '' : fnamemodify(s:VimSlash(file), ':p')
1933 endfunction
1935 function! s:DotRelative(path, ...) abort
1936   let cwd = a:0 ? a:1 : getcwd()
1937   let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
1938   if len(cwd) && s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
1939     return '.' . strpart(path, len(cwd))
1940   endif
1941   return a:path
1942 endfunction
1944 function! fugitive#Object(...) abort
1945   let dir = a:0 > 1 ? s:Dir(a:2) : s:Dir()
1946   let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
1947   if !s:SameRepo(dir, fdir)
1948     let rev = ''
1949   endif
1950   let tree = s:Tree(dir)
1951   let full = a:0 ? a:1 : s:BufName('%')
1952   let full = fnamemodify(full, ':p' . (s:Slash(full) =~# '/$' ? '' : ':s?/$??'))
1953   if empty(rev) && empty(tree)
1954     return FugitiveGitPath(full)
1955   elseif empty(rev)
1956     let rev = fugitive#Path(full, './', dir)
1957     if rev =~# '^\./.git\%(/\|$\)'
1958       return FugitiveGitPath(full)
1959     endif
1960   endif
1961   if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
1962     return rev
1963   else
1964     return FugitiveGitPath(tree . rev[1:-1])
1965   endif
1966 endfunction
1968 let s:var = '\%(<\%(cword\|cWORD\|cexpr\|cfile\|sfile\|slnum\|afile\|abuf\|amatch' . (has('clientserver') ? '\|client' : '') . '\)>\|%\|#<\=\d\+\|##\=\)'
1969 let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
1970 let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
1971 let s:commit_expand = '!\\\@!#\=\d*\|!%'
1973 function! s:BufName(var) abort
1974   if a:var ==# '%'
1975     return bufname(get(s:TempState(), 'origin_bufnr', ''))
1976   elseif a:var =~# '^#\d*$'
1977     let nr = get(s:TempState(+a:var[1:-1]), 'origin_bufnr', '')
1978     return bufname(nr ? nr : +a:var[1:-1])
1979   else
1980     return expand(a:var)
1981   endif
1982 endfunction
1984 function! s:ExpandVar(other, var, flags, esc, ...) abort
1985   let cwd = a:0 ? a:1 : getcwd()
1986   if a:other =~# '^\'
1987     return a:other[1:-1]
1988   elseif a:other =~# '^'''
1989     return substitute(a:other[1:-2], "''", "'", "g")
1990   elseif a:other =~# '^"'
1991     return substitute(a:other[1:-2], '""', '"', "g")
1992   elseif a:other =~# '^[!`]'
1993     let buffer = s:BufName(a:other =~# '[0-9#]' ? '#' . matchstr(a:other, '\d\+') : '%')
1994     let owner = s:Owner(buffer)
1995     return len(owner) ? owner : '@'
1996   elseif a:other =~# '^\~[~.]$'
1997     return s:Slash(getcwd())
1998   elseif len(a:other)
1999     return expand(a:other)
2000   elseif a:var ==# '<cfile>'
2001     let bufnames = [expand('<cfile>')]
2002     if get(maparg('<Plug><cfile>', 'c', 0, 1), 'expr')
2003       try
2004         let bufnames = [eval(maparg('<Plug><cfile>', 'c'))]
2005         if bufnames[0] ==# "\<C-R>\<C-F>"
2006           let bufnames = [expand('<cfile>')]
2007         endif
2008       catch
2009       endtry
2010     endif
2011   elseif a:var =~# '^<'
2012     let bufnames = [s:BufName(a:var)]
2013   elseif a:var ==# '##'
2014     let bufnames = map(argv(), 'fugitive#Real(v:val)')
2015   else
2016     let bufnames = [fugitive#Real(s:BufName(a:var))]
2017   endif
2018   let files = []
2019   for bufname in bufnames
2020     let flags = a:flags
2021     let file = s:DotRelative(bufname, cwd)
2022     while len(flags)
2023       let flag = matchstr(flags, s:flag)
2024       let flags = strpart(flags, len(flag))
2025       if flag ==# ':.'
2026         let file = s:DotRelative(fugitive#Real(file), cwd)
2027       else
2028         let file = fnamemodify(file, flag)
2029       endif
2030     endwhile
2031     let file = s:Slash(file)
2032     if file =~# '^fugitive://'
2033       let [dir, commit, file_candidate] = s:DirCommitFile(file)
2034       let tree = s:Tree(dir)
2035       if len(tree) && len(file_candidate)
2036         let file = (commit =~# '^.$' ? ':' : '') . commit . ':' .
2037               \ s:DotRelative(tree . file_candidate)
2038       elseif empty(file_candidate) && commit !~# '^.$'
2039         let file = commit
2040       endif
2041     endif
2042     call add(files, len(a:esc) ? shellescape(file) : file)
2043   endfor
2044   return join(files, "\1")
2045 endfunction
2047 if has('win32')
2048   let s:fnameescape = " \t\n*?`%#'\"|!<"
2049 else
2050   let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
2051 endif
2053 function! s:Expand(rev, ...) abort
2054   if a:rev =~# '^>' && s:Slash(@%) =~# '^fugitive://' && empty(s:DirCommitFile(@%)[1])
2055     return s:Slash(@%)
2056   elseif a:rev =~# '^>\=:[0-3]$'
2057     let file = len(expand('%')) ? a:rev[-2:-1] . ':%' : '%'
2058   elseif a:rev =~# '^>\%(:\=/\)\=$'
2059     let file = '%'
2060   elseif a:rev =~# '^>[> ]\@!' && @% !~# '^fugitive:' && s:Slash(@%) =~# '://\|^$'
2061     let file = '%'
2062   elseif a:rev ==# '>:'
2063     let file = empty(s:DirCommitFile(@%)[0]) ? ':0:%' : '%'
2064   elseif a:rev =~# '^>[> ]\@!'
2065     let rev = (a:rev =~# '^>[~^]' ? '!' : '') . a:rev[1:-1]
2066     let prefix = matchstr(rev, '^\%(\\.\|{[^{}]*}\|[^:]\)*')
2067     if prefix !=# rev
2068       let file = rev
2069     else
2070       let file = len(expand('%')) ? rev . ':%' : '%'
2071     endif
2072   elseif s:Slash(a:rev) =~# '^\a\a\+://'
2073     let file = substitute(a:rev, '\\\@<!\%(#\a\|%\x\x\)', '\\&', 'g')
2074   else
2075     let file = a:rev
2076   endif
2077   return substitute(file,
2078         \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~.]\)\|' . s:expand,
2079         \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),"", a:0 ? a:1 : getcwd()), "\1", " ")', 'g')
2080 endfunction
2082 function! fugitive#Expand(object) abort
2083   return substitute(a:object,
2084         \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~.]\)\|' . s:expand,
2085         \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5)), "\1", " ")', 'g')
2086 endfunction
2088 function! s:SplitExpandChain(string, ...) abort
2089   let list = []
2090   let string = a:string
2091   let dquote = '"\%([^"]\|""\|\\"\)*"\|'
2092   let cwd = a:0 ? a:1 : getcwd()
2093   while string =~# '\S'
2094     if string =~# '^\s*|'
2095       return [list, substitute(string, '^\s*', '', '')]
2096     endif
2097     let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
2098     let string = strpart(string, len(arg))
2099     let arg = substitute(arg, '^\s\+', '', '')
2100     if !exists('seen_separator')
2101       let arg = substitute(arg, '^\%([^:.][^:]*:\|^:\%((literal)\)\=\|^:[0-3]:\)\=\zs\.\.\=\%(/.*\)\=$',
2102             \ '\=s:DotRelative(s:Slash(simplify(getcwd() . "/" . submatch(0))), cwd)', '')
2103     endif
2104     let arg = substitute(arg,
2105           \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~]\|^\~\w*\|\$\w\+\)\|' . s:expand,
2106           \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5), cwd)', 'g')
2107     call extend(list, split(arg, "\1", 1))
2108     if arg ==# '--'
2109       let seen_separator = 1
2110     endif
2111   endwhile
2112   return [list, '']
2113 endfunction
2115 let s:trees = {}
2116 let s:indexes = {}
2117 function! s:TreeInfo(dir, commit) abort
2118   let key = s:GitDir(a:dir)
2119   if a:commit =~# '^:\=[0-3]$'
2120     let index = get(s:indexes, key, [])
2121     let newftime = getftime(fugitive#Find('.git/index', a:dir))
2122     if get(index, 0, -2) < newftime
2123       let [lines, exec_error] = s:LinesError([a:dir, 'ls-files', '--stage', '--'])
2124       let s:indexes[key] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
2125       if exec_error
2126         return [{}, -1]
2127       endif
2128       for line in lines
2129         let [info, filename] = split(line, "\t")
2130         let [mode, sha, stage] = split(info, '\s\+')
2131         let s:indexes[key][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
2132         while filename =~# '/'
2133           let filename = substitute(filename, '/[^/]*$', '', '')
2134           let s:indexes[key][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
2135         endwhile
2136       endfor
2137     endif
2138     return [get(s:indexes[key][1], a:commit[-1:-1], {}), newftime]
2139   elseif a:commit =~# '^\x\{40,\}$'
2140     if !has_key(s:trees, key)
2141       let s:trees[key] = {}
2142     endif
2143     if !has_key(s:trees[key], a:commit)
2144       let ftime = s:ChompDefault('', [a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'])
2145       if empty(ftime)
2146         let s:trees[key][a:commit] = [{}, -1]
2147         return s:trees[key][a:commit]
2148       endif
2149       let s:trees[key][a:commit] = [{}, +ftime]
2150       let [lines, exec_error] = s:LinesError([a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'])
2151       if exec_error
2152         return s:trees[key][a:commit]
2153       endif
2154       for line in lines
2155         let [info, filename] = split(line, "\t")
2156         let [mode, type, sha, size] = split(info, '\s\+')
2157         let s:trees[key][a:commit][0][filename] = [+ftime, mode, type, sha, +size, filename]
2158       endfor
2159     endif
2160     return s:trees[key][a:commit]
2161   endif
2162   return [{}, -1]
2163 endfunction
2165 function! s:PathInfo(url) abort
2166   let [dir, commit, file] = s:DirCommitFile(a:url)
2167   if empty(dir) || !get(g:, 'fugitive_file_api', 1)
2168     return [-1, '000000', '', '', -1]
2169   endif
2170   let path = substitute(file[1:-1], '/*$', '', '')
2171   let [tree, ftime] = s:TreeInfo(dir, commit)
2172   let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
2173   if empty(entry) || file =~# '/$' && entry[2] !=# 'tree'
2174     return [-1, '000000', '', '', -1]
2175   else
2176     return entry
2177   endif
2178 endfunction
2180 function! fugitive#simplify(url) abort
2181   let [dir, commit, file] = s:DirCommitFile(a:url)
2182   if empty(dir)
2183     return ''
2184   elseif empty(commit)
2185     return s:VimSlash(s:DirUrlPrefix(simplify(s:GitDir(dir))))
2186   endif
2187   if file =~# '/\.\.\%(/\|$\)'
2188     let tree = s:Tree(dir)
2189     if len(tree)
2190       let path = simplify(tree . file)
2191       if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
2192         return s:VimSlash(path)
2193       endif
2194     endif
2195   endif
2196   return s:VimSlash(s:PathJoin(s:DirUrlPrefix(simplify(s:GitDir(dir))), commit . simplify(file)))
2197 endfunction
2199 function! fugitive#resolve(url) abort
2200   let url = fugitive#simplify(a:url)
2201   if url =~? '^fugitive:'
2202     return url
2203   else
2204     return resolve(url)
2205   endif
2206 endfunction
2208 function! fugitive#getftime(url) abort
2209   return s:PathInfo(a:url)[0]
2210 endfunction
2212 function! fugitive#getfsize(url) abort
2213   let entry = s:PathInfo(a:url)
2214   if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
2215     let dir = s:DirCommitFile(a:url)[0]
2216     let entry[4] = +s:ChompDefault(-1, [dir, 'cat-file', '-s', entry[3]])
2217   endif
2218   return entry[4]
2219 endfunction
2221 function! fugitive#getftype(url) abort
2222   return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
2223 endfunction
2225 function! fugitive#filereadable(url) abort
2226   return s:PathInfo(a:url)[2] ==# 'blob'
2227 endfunction
2229 function! fugitive#filewritable(url) abort
2230   let [dir, commit, file] = s:DirCommitFile(a:url)
2231   if commit !~# '^\d$' || !filewritable(fugitive#Find('.git/index', dir))
2232     return 0
2233   endif
2234   return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
2235 endfunction
2237 function! fugitive#isdirectory(url) abort
2238   return s:PathInfo(a:url)[2] ==# 'tree'
2239 endfunction
2241 function! fugitive#getfperm(url) abort
2242   let [dir, commit, file] = s:DirCommitFile(a:url)
2243   let perm = getfperm(dir)
2244   let fperm = s:PathInfo(a:url)[1]
2245   if fperm ==# '040000'
2246     let fperm = '000755'
2247   endif
2248   if fperm !~# '[15]'
2249     let perm = tr(perm, 'x', '-')
2250   endif
2251   if fperm !~# '[45]$'
2252     let perm = tr(perm, 'rw', '--')
2253   endif
2254   if commit !~# '^\d$'
2255     let perm = tr(perm, 'w', '-')
2256   endif
2257   return perm ==# '---------' ? '' : perm
2258 endfunction
2260 function! s:UpdateIndex(dir, info) abort
2261   let info = join(a:info[0:-2]) . "\t" . a:info[-1] . "\n"
2262   let [error, exec_error] = s:StdoutToFile('', [a:dir, 'update-index', '--index-info'], info)
2263   return !exec_error ? '' : len(error) ? error : 'unknown update-index error'
2264 endfunction
2266 function! fugitive#setfperm(url, perm) abort
2267   let [dir, commit, file] = s:DirCommitFile(a:url)
2268   let entry = s:PathInfo(a:url)
2269   let perm = fugitive#getfperm(a:url)
2270   if commit !~# '^\d$' || entry[2] !=# 'blob' ||
2271       \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
2272     return -2
2273   endif
2274   let error = s:UpdateIndex(dir, [a:perm =~# 'x' ? '000755' : '000644', entry[3], commit, file[1:-1]])
2275   return len(error) ? -1 : 0
2276 endfunction
2278 if !exists('s:blobdirs')
2279   let s:blobdirs = {}
2280 endif
2281 function! s:BlobTemp(url) abort
2282   let [dir, commit, file] = s:DirCommitFile(a:url)
2283   if empty(file)
2284     return ''
2285   endif
2286   let key = s:GitDir(dir)
2287   if !has_key(s:blobdirs, key)
2288     let s:blobdirs[key] = tempname()
2289   endif
2290   let tempfile = s:blobdirs[key] . '/' . commit . file
2291   let tempparent = fnamemodify(tempfile, ':h')
2292   if !isdirectory(tempparent)
2293     call mkdir(tempparent, 'p')
2294   elseif isdirectory(tempfile)
2295     if commit =~# '^\d$' && has('patch-7.4.1107')
2296       call delete(tempfile, 'rf')
2297     else
2298       return ''
2299     endif
2300   endif
2301   if commit =~# '^\d$' || !filereadable(tempfile)
2302     let rev = s:DirRev(a:url)[1]
2303     let blob_or_filters = fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
2304     let exec_error = s:StdoutToFile(tempfile, [dir, 'cat-file', blob_or_filters, rev])[1]
2305     if exec_error
2306       call delete(tempfile)
2307       return ''
2308     endif
2309   endif
2310   return s:Resolve(tempfile)
2311 endfunction
2313 function! fugitive#readfile(url, ...) abort
2314   let entry = s:PathInfo(a:url)
2315   if entry[2] !=# 'blob'
2316     return []
2317   endif
2318   let temp = s:BlobTemp(a:url)
2319   if empty(temp)
2320     return []
2321   endif
2322   return call('readfile', [temp] + a:000)
2323 endfunction
2325 function! fugitive#writefile(lines, url, ...) abort
2326   let url = type(a:url) ==# type('') ? a:url : ''
2327   let [dir, commit, file] = s:DirCommitFile(url)
2328   let entry = s:PathInfo(url)
2329   if commit =~# '^\d$' && entry[2] !=# 'tree'
2330     let temp = tempname()
2331     if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
2332       call writefile(fugitive#readfile(url, 'b'), temp, 'b')
2333     endif
2334     call call('writefile', [a:lines, temp] + a:000)
2335     let hash = s:ChompDefault('', [dir, '--literal-pathspecs', 'hash-object', '-w', FugitiveGitPath(temp)])
2336     let mode = entry[1] !=# '000000' ? entry[1] : '100644'
2337     if hash =~# '^\x\{40,\}$'
2338       let error = s:UpdateIndex(dir, [mode, hash, commit, file[1:-1]])
2339       if empty(error)
2340         return 0
2341       endif
2342     endif
2343   endif
2344   return call('writefile', [a:lines, a:url] + a:000)
2345 endfunction
2347 let s:globsubs = {
2348       \ '/**/': '/\%([^./][^/]*/\)*',
2349       \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
2350       \ '**/': '[^/]*\%(/[^./][^/]*\)*',
2351       \ '**': '.*',
2352       \ '/*': '/[^/.][^/]*',
2353       \ '*': '[^/]*',
2354       \ '?': '[^/]'}
2355 function! fugitive#glob(url, ...) abort
2356   let [repo, commit, glob] = s:DirCommitFile(a:url)
2357   let dirglob = s:GitDir(repo)
2358   let append = matchstr(glob, '/*$')
2359   let glob = substitute(glob, '/*$', '', '')
2360   let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\$]\|^^', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
2361   let results = []
2362   for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
2363     if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(fugitive#Find('.git/HEAD', dir))
2364       continue
2365     endif
2366     let files = items(s:TreeInfo(dir, commit)[0])
2367     if len(append)
2368       call filter(files, 'v:val[1][2] ==# "tree"')
2369     endif
2370     call map(files, 'v:val[0]')
2371     call filter(files, 'v:val =~# pattern')
2372     let prepend = s:DirUrlPrefix(dir) . substitute(commit, '^:', '', '') . '/'
2373     call sort(files)
2374     call map(files, 's:VimSlash(s:PathJoin(prepend, v:val . append))')
2375     call extend(results, files)
2376   endfor
2377   if a:0 > 1 && a:2
2378     return results
2379   else
2380     return join(results, "\n")
2381   endif
2382 endfunction
2384 function! fugitive#delete(url, ...) abort
2385   let [dir, commit, file] = s:DirCommitFile(a:url)
2386   if a:0 && len(a:1) || commit !~# '^\d$'
2387     return -1
2388   endif
2389   let entry = s:PathInfo(a:url)
2390   if entry[2] !=# 'blob'
2391     return -1
2392   endif
2393   let error = s:UpdateIndex(dir, ['000000', '0000000000000000000000000000000000000000', commit, file[1:-1]])
2394   return len(error) ? -1 : 0
2395 endfunction
2397 " Section: Completion
2399 function! s:FilterEscape(items, ...) abort
2400   let items = copy(a:items)
2401   call map(items, 'fnameescape(v:val)')
2402   if !a:0 || type(a:1) != type('')
2403     let match = ''
2404   else
2405     let match = substitute(a:1, '^[+>]\|\\\@<![' . substitute(s:fnameescape, '\\', '', '') . ']', '\\&', 'g')
2406   endif
2407   let cmp = s:FileIgnoreCase(1) ? '==?' : '==#'
2408   return filter(items, 'strpart(v:val, 0, strlen(match)) ' . cmp . ' match')
2409 endfunction
2411 function! s:GlobComplete(lead, pattern, ...) abort
2412   if a:lead ==# '/'
2413     return []
2414   else
2415     let results = glob(substitute(a:lead . a:pattern, '[\{}]', '\\&', 'g'), a:0 ? a:1 : 0, 1)
2416   endif
2417   call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
2418   call map(results, 'v:val[ strlen(a:lead) : -1 ]')
2419   return results
2420 endfunction
2422 function! fugitive#CompletePath(base, ...) abort
2423   let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
2424   let stripped = matchstr(a:base, '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\)')
2425   let base = strpart(a:base, len(stripped))
2426   if len(stripped) || a:0 < 4
2427     let root = s:Tree(dir)
2428   else
2429     let root = a:4
2430   endif
2431   if root !=# '/' && len(root)
2432     let root .= '/'
2433   endif
2434   if empty(stripped)
2435     let stripped = matchstr(a:base, '^\%(:(literal)\|:\)')
2436     let base = strpart(a:base, len(stripped))
2437   endif
2438   if base =~# '^\.git/' && len(dir)
2439     let pattern = s:gsub(base[5:-1], '/', '*&').'*'
2440     let fdir = fugitive#Find('.git/', dir)
2441     let matches = s:GlobComplete(fdir, pattern)
2442     let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
2443     if len(cdir) && s:cpath(fdir) !=# s:cpath(cdir)
2444       call extend(matches, s:GlobComplete(cdir, pattern))
2445     endif
2446     call s:Uniq(matches)
2447     call map(matches, "'.git/' . v:val")
2448   elseif base =~# '^\~/'
2449     let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
2450   elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/'
2451     let matches = s:GlobComplete('', base . '*')
2452   elseif len(root)
2453     let matches = s:GlobComplete(root, s:gsub(base, '/', '*&').'*')
2454   else
2455     let matches = []
2456   endif
2457   call map(matches, 's:fnameescape(s:Slash(stripped . v:val))')
2458   return matches
2459 endfunction
2461 function! fugitive#PathComplete(...) abort
2462   return call('fugitive#CompletePath', a:000)
2463 endfunction
2465 function! s:CompleteHeads(dir) abort
2466   if empty(a:dir)
2467     return []
2468   endif
2469   let dir = fugitive#Find('.git/', a:dir)
2470   return sort(filter(['HEAD', 'FETCH_HEAD', 'ORIG_HEAD'] + s:merge_heads, 'filereadable(dir . v:val)')) +
2471         \ sort(s:LinesError([a:dir, 'rev-parse', '--symbolic', '--branches', '--tags', '--remotes'])[0])
2472 endfunction
2474 function! fugitive#CompleteObject(base, ...) abort
2475   let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
2476   let tree = s:Tree(dir)
2477   let cwd = getcwd()
2478   let subdir = ''
2479   if len(tree) && s:cpath(tree . '/', cwd[0 : len(tree)])
2480     let subdir = strpart(cwd, len(tree) + 1) . '/'
2481   endif
2482   let base = s:Expand(a:base)
2484   if a:base =~# '^!\d*$' && base !~# '^!'
2485     return [base]
2486   elseif base =~# '^\.\=/\|^:(' || base !~# ':'
2487     let results = []
2488     if base =~# '^refs/'
2489       let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
2490       let results += map(s:GlobComplete(cdir, base . '*'), 's:Slash(v:val)')
2491       call map(results, 's:fnameescape(v:val)')
2492     elseif base !~# '^\.\=/\|^:('
2493       let heads = s:CompleteHeads(dir)
2494       if filereadable(fugitive#Find('.git/refs/stash', dir))
2495         let heads += ["stash"]
2496         let heads += sort(s:LinesError(["stash","list","--pretty=format:%gd"], dir)[0])
2497       endif
2498       let results += s:FilterEscape(heads, fnameescape(base))
2499     endif
2500     let results += a:0 == 1 || a:0 >= 3 ? fugitive#CompletePath(base, 0, '', dir, a:0 >= 4 ? a:4 : tree) : fugitive#CompletePath(base)
2501     return results
2503   elseif base =~# '^:'
2504     let entries = s:LinesError(['ls-files','--stage'], dir)[0]
2505     if base =~# ':\./'
2506       call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
2507     endif
2508     call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
2509     if base !~# '^:[0-3]\%(:\|$\)'
2510       call filter(entries,'v:val[1] == "0"')
2511       call map(entries,'v:val[2:-1]')
2512     endif
2514   else
2515     let parent = matchstr(base, '.*[:/]')
2516     let entries = s:LinesError(['ls-tree', substitute(parent,  ':\zs\./', '\=subdir', '')], dir)[0]
2517     call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
2518     call map(entries,'parent.s:sub(v:val,".*\t","")')
2519   endif
2520   return s:FilterEscape(entries, fnameescape(base))
2521 endfunction
2523 function! s:CompleteSub(subcommand, A, L, P, ...) abort
2524   let pre = strpart(a:L, 0, a:P)
2525   if pre =~# ' -- '
2526     return fugitive#CompletePath(a:A)
2527   elseif a:A =~# '^-' || a:A is# 0
2528     return s:FilterEscape(split(s:ChompDefault('', [a:subcommand, '--git-completion-helper']), ' '), a:A)
2529   elseif !a:0
2530     return fugitive#CompleteObject(a:A, s:Dir())
2531   elseif type(a:1) == type(function('tr'))
2532     return call(a:1, [a:A, a:L, a:P] + (a:0 > 1 ? a:2 : []))
2533   else
2534     return s:FilterEscape(a:1, a:A)
2535   endif
2536 endfunction
2538 function! s:CompleteRevision(A, L, P, ...) abort
2539   return s:FilterEscape(s:CompleteHeads(a:0 ? a:1 : s:Dir()), a:A)
2540 endfunction
2542 function! s:CompleteRemote(A, L, P, ...) abort
2543   let dir = a:0 ? a:1 : s:Dir()
2544   let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
2545   if !empty(remote)
2546     let matches = s:LinesError([dir, 'ls-remote', remote])[0]
2547     call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
2548     call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
2549   else
2550     let matches = s:LinesError([dir, 'remote'])[0]
2551   endif
2552   return s:FilterEscape(matches, a:A)
2553 endfunction
2555 " Section: Buffer auto-commands
2557 augroup fugitive_dummy_events
2558   autocmd!
2559   autocmd User Fugitive* "
2560   autocmd BufWritePre,FileWritePre,FileWritePost * "
2561   autocmd BufNewFile * "
2562   autocmd QuickfixCmdPre,QuickfixCmdPost * "
2563 augroup END
2565 function! s:ReplaceCmd(cmd) abort
2566   let temp = tempname()
2567   let [err, exec_error] = s:StdoutToFile(temp, a:cmd)
2568   if exec_error
2569     throw 'fugitive: ' . (len(err) ? substitute(err, "\n$", '', '') : 'unknown error running ' . string(a:cmd))
2570   endif
2571   setlocal noswapfile
2572   silent exe 'lockmarks keepalt noautocmd 0read ++edit' s:fnameescape(temp)
2573   if &foldenable && foldlevel('$') > 0
2574     set nofoldenable
2575     silent keepjumps $delete _
2576     set foldenable
2577   else
2578     silent keepjumps $delete _
2579   endif
2580   call delete(temp)
2581   if s:cpath(s:AbsoluteVimPath(bufnr('$')), temp)
2582     silent! noautocmd execute bufnr('$') . 'bwipeout'
2583   endif
2584 endfunction
2586 function! s:FormatLog(dict) abort
2587   return a:dict.commit . ' ' . a:dict.subject
2588 endfunction
2590 function! s:FormatRebase(dict) abort
2591   return a:dict.status . ' ' . a:dict.commit . ' ' . a:dict.subject
2592 endfunction
2594 function! s:FormatFile(dict) abort
2595   return a:dict.status . ' ' . a:dict.filename
2596 endfunction
2598 function! s:Format(val) abort
2599   if type(a:val) == type({})
2600     return s:Format{a:val.type}(a:val)
2601   elseif type(a:val) == type([])
2602     return map(copy(a:val), 's:Format(v:val)')
2603   else
2604     return '' . a:val
2605   endif
2606 endfunction
2608 function! s:AddHeader(to, key, value) abort
2609   if empty(a:value)
2610     return
2611   endif
2612   call add(a:to.lines, a:key . ':' . (len(a:value) ? ' ' . a:value : ''))
2613 endfunction
2615 function! s:AddSection(to, label, lines, ...) abort
2616   let note = a:0 ? a:1 : ''
2617   if empty(a:lines) && empty(note)
2618     return
2619   endif
2620   call extend(a:to.lines, ['', a:label . (len(note) ? ': ' . note : ' (' . len(a:lines) . ')')] + s:Format(a:lines))
2621 endfunction
2623 function! s:AddDiffSection(to, stat, label, files) abort
2624   if empty(a:files)
2625     return
2626   endif
2627   let diff_section = a:stat.diff[a:label]
2628   let expanded = a:stat.expanded[a:label]
2629   let was_expanded = get(getbufvar(a:stat.bufnr, 'fugitive_expanded', {}), a:label, {})
2630   call extend(a:to.lines, ['', a:label . ' (' . len(a:files) . ')'])
2631   for file in a:files
2632     call add(a:to.lines, s:Format(file))
2633     if has_key(was_expanded, file.filename)
2634       let [diff, start] = s:StageInlineGetDiff(diff_section, file)
2635       if len(diff)
2636         let expanded[file.filename] = [start]
2637         call extend(a:to.lines, diff)
2638       endif
2639     endif
2640   endfor
2641 endfunction
2643 function! s:QueryLog(refspec, limit, dir) abort
2644   let [log, exec_error] = s:LinesError(['log', '-n', '' . a:limit, '--pretty=format:%h%x09%s'] + a:refspec + ['--'], a:dir)
2645   call map(log, 'split(v:val, "\t", 1)')
2646   call map(log, '{"type": "Log", "commit": v:val[0], "subject": join(v:val[1 : -1], "\t")}')
2647   let result = {'error': exec_error ? 1 : 0, 'overflow': 0, 'entries': log}
2648   if len(log) == a:limit
2649     call remove(log, -1)
2650     let result.overflow = 1
2651   endif
2652   return result
2653 endfunction
2655 function! s:QueryLogRange(old, new, dir) abort
2656   if empty(a:old) || empty(a:new)
2657     return {'error': 2, 'overflow': 0, 'entries': []}
2658   endif
2659   return s:QueryLog([a:old . '..' . a:new], 256, a:dir)
2660 endfunction
2662 function! s:AddLogSection(to, label, log) abort
2663   if empty(a:log.entries)
2664     return
2665   endif
2666   let label = a:label . ' (' . len(a:log.entries) . (a:log.overflow ? '+' : '') . ')'
2667   call extend(a:to.lines, ['', label] + s:Format(a:log.entries))
2668 endfunction
2670 let s:rebase_abbrevs = {
2671       \ 'p': 'pick',
2672       \ 'r': 'reword',
2673       \ 'e': 'edit',
2674       \ 's': 'squash',
2675       \ 'f': 'fixup',
2676       \ 'x': 'exec',
2677       \ 'd': 'drop',
2678       \ 'l': 'label',
2679       \ 't': 'reset',
2680       \ 'm': 'merge',
2681       \ 'b': 'break',
2682       \ }
2684 function! s:MapStatus() abort
2685   call fugitive#MapJumps()
2686   call s:Map('n', '-', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
2687   call s:Map('x', '-', ":<C-U>execute <SID>Do('Toggle',1)<CR>", '<silent>')
2688   call s:Map('n', 's', ":<C-U>execute <SID>Do('Stage',0)<CR>", '<silent>')
2689   call s:Map('x', 's', ":<C-U>execute <SID>Do('Stage',1)<CR>", '<silent>')
2690   call s:Map('n', 'u', ":<C-U>execute <SID>Do('Unstage',0)<CR>", '<silent>')
2691   call s:Map('x', 'u', ":<C-U>execute <SID>Do('Unstage',1)<CR>", '<silent>')
2692   call s:Map('n', 'U', ":<C-U>Git reset -q<CR>", '<silent>')
2693   call s:MapMotion('gu', "exe <SID>StageJump(v:count, 'Untracked', 'Unstaged')")
2694   call s:MapMotion('gU', "exe <SID>StageJump(v:count, 'Unstaged', 'Untracked')")
2695   call s:MapMotion('gs', "exe <SID>StageJump(v:count, 'Staged')")
2696   call s:MapMotion('gp', "exe <SID>StageJump(v:count, 'Unpushed')")
2697   call s:MapMotion('gP', "exe <SID>StageJump(v:count, 'Unpulled')")
2698   call s:MapMotion('gr', "exe <SID>StageJump(v:count, 'Rebasing')")
2699   call s:Map('n', 'C', ":echoerr 'fugitive: C has been removed in favor of cc'<CR>", '<silent><unique>')
2700   call s:Map('n', 'a', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
2701   call s:Map('n', 'i', ":<C-U>execute <SID>NextExpandedHunk(v:count1)<CR>", '<silent>')
2702   call s:Map('n', "=", ":<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>", '<silent>')
2703   call s:Map('n', "<", ":<C-U>execute <SID>StageInline('hide',  line('.'),v:count)<CR>", '<silent>')
2704   call s:Map('n', ">", ":<C-U>execute <SID>StageInline('show',  line('.'),v:count)<CR>", '<silent>')
2705   call s:Map('x', "=", ":<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2706   call s:Map('x', "<", ":<C-U>execute <SID>StageInline('hide',  line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2707   call s:Map('x', ">", ":<C-U>execute <SID>StageInline('show',  line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2708   call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
2709   call s:Map('n', 'dd', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>", '<silent>')
2710   call s:Map('n', 'dh', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
2711   call s:Map('n', 'ds', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
2712   call s:Map('n', 'dp', ":<C-U>execute <SID>StageDiffEdit()<CR>", '<silent>')
2713   call s:Map('n', 'dv', ":<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>", '<silent>')
2714   call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
2715   call s:Map('n', 'P', ":<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>", '<silent>')
2716   call s:Map('x', 'P', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
2717   call s:Map('n', 'p', ":<C-U>if v:count<Bar>silent exe <SID>GF('pedit')<Bar>else<Bar>echoerr 'Use = for inline diff, I for :Git add/reset --patch, 1p for :pedit'<Bar>endif<CR>", '<silent>')
2718   call s:Map('x', 'p', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
2719   call s:Map('n', 'I', ":<C-U>execute <SID>StagePatch(line('.'),line('.'), 1)<CR>", '<silent>')
2720   call s:Map('x', 'I', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"), 1)<CR>", '<silent>')
2721   call s:Map('n', 'gq', ":<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>", '<silent>')
2722   call s:Map('n', 'R', ":echohl WarningMsg<Bar>echo 'Reloading is automatic.  Use :e to force'<Bar>echohl NONE<CR>", '<silent>')
2723   call s:Map('n', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
2724   call s:Map('x', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
2725   call s:Map('n', 'X', ":<C-U>execute <SID>StageDelete(line('.'), 0, v:count)<CR>", '<silent>')
2726   call s:Map('x', 'X', ":<C-U>execute <SID>StageDelete(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
2727   call s:Map('n', 'gI', ":<C-U>execute <SID>StageIgnore(line('.'), line('.'), v:count)<CR>", '<silent>')
2728   call s:Map('x', 'gI', ":<C-U>execute <SID>StageIgnore(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
2729   call s:Map('n', '.', ':<C-U> <C-R>=<SID>StageArgs(0)<CR><Home>')
2730   call s:Map('x', '.', ':<C-U> <C-R>=<SID>StageArgs(1)<CR><Home>')
2731 endfunction
2733 function! s:StatusProcess(result, stat) abort
2734   let stat = a:stat
2735   let status_exec = a:stat.status
2736   let config = a:stat.config
2737   let dir = s:Dir(config)
2738   try
2739     let [staged, unstaged, untracked] = [[], [], []]
2740     let stat.props = {}
2742     if empty(status_exec)
2743       let stat.branch = FugitiveHead(0, config)
2745     elseif status_exec.exit_status
2746       let stat.error = s:JoinChomp(status_exec.stderr)
2747       return
2749     elseif status_exec.args[-1] ==# '--porcelain=v2'
2750       let output = split(tr(join(status_exec.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
2751       let i = 0
2752       while i < len(output)
2753         let line = output[i]
2754         let prop = matchlist(line, '# \(\S\+\) \(.*\)')
2755         if len(prop)
2756           let stat.props[prop[1]] = prop[2]
2757         elseif line[0] ==# '?'
2758           call add(untracked, {'type': 'File', 'status': line[0], 'filename': line[2:-1], 'relative': [line[2:-1]]})
2759         elseif line[0] !=# '#'
2760           if line[0] ==# 'u'
2761             let file = matchstr(line, '^.\{37\} \x\{40,\} \x\{40,\} \x\{40,\} \zs.*$')
2762           else
2763             let file = matchstr(line, '^.\{30\} \x\{40,\} \x\{40,\} \zs.*$')
2764           endif
2765           if line[0] ==# '2'
2766             let i += 1
2767             let file = matchstr(file, ' \zs.*')
2768             let relative = [file, output[i]]
2769           else
2770             let relative = [file]
2771           endif
2772           let filename = join(reverse(copy(relative)), ' -> ')
2773           let sub = matchstr(line, '^[12u] .. \zs....')
2774           if line[2] !=# '.'
2775             call add(staged, {'type': 'File', 'status': line[2], 'filename': filename, 'relative': relative, 'submodule': sub})
2776           endif
2777           if line[3] !=# '.'
2778             let sub = matchstr(line, '^[12u] .. \zs....')
2779             call add(unstaged, {'type': 'File', 'status': get({'C':'M','M':'?','U':'?'}, matchstr(sub, 'S\.*\zs[CMU]'), line[3]), 'filename': file, 'relative': [file], 'submodule': sub})
2780           endif
2781         endif
2782         let i += 1
2783       endwhile
2784       let stat.branch = substitute(get(stat.props, 'branch.head', '(unknown)'), '\C^(\%(detached\|unknown\))$', '', '')
2786     else
2787       let output = split(tr(join(status_exec.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
2788       while get(output, 0, '') =~# '^\l\+:'
2789         call remove(output, 0)
2790       endwhile
2791       let branch = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
2792       if branch =~# '\.\.\.'
2793         let stat.branch = split(branch, '\.\.\.')[0]
2794       else
2795         let stat.branch = branch ==# 'HEAD' ? '' : branch
2796       endif
2798       let i = 0
2799       while i < len(output)
2800         let line = output[i]
2801         let file = line[3:-1]
2802         let i += 1
2803         if line[2] !=# ' '
2804           continue
2805         endif
2806         if line[0:1] =~# '[RC]'
2807           let relative = [file, output[i]]
2808           let i += 1
2809         else
2810           let relative = [file]
2811         endif
2812         let filename = join(reverse(copy(relative)), ' -> ')
2813         if line[0] !~# '[ ?!#]'
2814           call add(staged, {'type': 'File', 'status': line[0], 'filename': filename, 'relative': relative, 'submodule': ''})
2815         endif
2816         if line[0:1] ==# '??'
2817           call add(untracked, {'type': 'File', 'status': line[1], 'filename': filename, 'relative': relative})
2818         elseif line[1] !~# '[ !#]'
2819           call add(unstaged, {'type': 'File', 'status': line[1], 'filename': file, 'relative': [file], 'submodule': ''})
2820         endif
2821       endwhile
2822     endif
2824     let diff_cmd = stat.cmd + ['-c', 'diff.suppressBlankEmpty=false', '-c', 'core.quotePath=false', 'diff', '--color=never', '--no-ext-diff', '--no-prefix']
2825     let stat.diff = {'Staged': {'stdout': ['']}, 'Unstaged': {'stdout': ['']}}
2826     if len(staged)
2827       let stat.diff['Staged'] = fugitive#Execute(diff_cmd + ['--cached'], function('len'))
2828     endif
2829     if len(unstaged)
2830       let stat.diff['Unstaged'] = fugitive#Execute(diff_cmd + ['--'] + map(copy(unstaged), 'stat.work_tree . "/" . v:val.relative[0]'), function('len'))
2831     endif
2833     let [stat.staged, stat.unstaged, stat.untracked] = [staged, unstaged, untracked]
2835     let stat.files = {'Staged': {}, 'Unstaged': {}}
2836     for dict in staged
2837       let stat.files['Staged'][dict.filename] = dict
2838     endfor
2839     for dict in unstaged
2840       let stat.files['Unstaged'][dict.filename] = dict
2841     endfor
2843     let branch = stat.branch
2844     let fetch_remote = config.Get('branch.' . branch . '.remote', 'origin')
2845     let push_remote = config.Get('branch.' . branch . '.pushRemote',
2846           \ config.Get('remote.pushDefault', fetch_remote))
2847     if fetch_remote !=# '.' && empty(config.Get('remote.' . fetch_remote . '.fetch'))
2848       let fetch_remote = ''
2849     endif
2850     if push_remote !=# '.' && empty(config.Get('remote.' . push_remote . '.push', config.Get('remote.' . push_remote . '.fetch')))
2851       let push_remote = ''
2852     endif
2853     let stat.fetch_remote = fetch_remote
2854     let stat.push_remote = push_remote
2856     if empty(stat.fetch_remote) || empty(branch)
2857       let stat.merge = ''
2858     else
2859       let stat.merge = config.Get('branch.' . branch . '.merge')
2860     endif
2862     let push_default = FugitiveConfigGet('push.default', config)
2863     if empty(push_default)
2864       let push_default = fugitive#GitVersion(2) ? 'simple' : 'matching'
2865     endif
2866     if push_default ==# 'upstream'
2867       let stat.push = stat.merge
2868     elseif empty(stat.push_remote) || empty(branch)
2869       let stat.push = ''
2870     else
2871       let stat.push = 'refs/heads/' . branch
2872     endif
2874     let stat.pull_type = 'Pull'
2875     if len(stat.merge)
2876       let rebase = FugitiveConfigGet('branch.' . branch . '.rebase', config)
2877       if empty(rebase)
2878         let rebase = FugitiveConfigGet('pull.rebase', config)
2879       endif
2880       if rebase =~# '^\%(true\|yes\|on\|1\|interactive\|merges\|preserve\)$'
2881         let stat.pull_type = 'Rebase'
2882       elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
2883         let stat.pull_type = 'Merge'
2884       endif
2885     endif
2886   endtry
2887 endfunction
2889 function! s:StatusRender(stat) abort
2890   try
2891     let stat = a:stat
2892     call fugitive#Wait(stat.running)
2893     if has_key(stat, 'error')
2894       return 'echoerr ' . string('fugitive: ' . stat.error)
2895     endif
2896     let [staged, unstaged, untracked, config] = [stat.staged, stat.unstaged, stat.untracked, stat.config]
2897     let dir = s:Dir(config)
2899     let pull_ref = stat.merge
2900     if stat.fetch_remote !=# '.'
2901       let pull_ref = substitute(pull_ref, '^refs/heads/', 'refs/remotes/' . stat.fetch_remote . '/', '')
2902     endif
2904     let push_ref = stat.push
2905     if stat.push_remote !=# '.'
2906       let push_ref = substitute(push_ref, '^refs/heads/', 'refs/remotes/' . stat.push_remote . '/', '')
2907     endif
2909     let push_short = substitute(push_ref, '^refs/\w\+/', '', '')
2910     let pull_short = substitute(pull_ref, '^refs/\w\+/', '', '')
2912     if isdirectory(fugitive#Find('.git/rebase-merge/', dir))
2913       let rebasing_dir = fugitive#Find('.git/rebase-merge/', dir)
2914     elseif isdirectory(fugitive#Find('.git/rebase-apply/', dir))
2915       let rebasing_dir = fugitive#Find('.git/rebase-apply/', dir)
2916     endif
2918     call fugitive#Wait(stat.rev_parse)
2919     let head = empty(stat.branch) ? stat.rev_parse.stdout[0] : stat.branch
2921     let rebasing = []
2922     let rebasing_head = 'detached HEAD'
2923     if exists('rebasing_dir') && filereadable(rebasing_dir . 'git-rebase-todo')
2924       let rebasing_head = substitute(readfile(rebasing_dir . 'head-name')[0], '\C^refs/heads/', '', '')
2925       let len = len(stat.rev_parse.stdout[0])
2926       let lines = readfile(rebasing_dir . 'git-rebase-todo')
2927       if getfsize(rebasing_dir . 'done') > 0
2928         let done = readfile(rebasing_dir . 'done')
2929         call map(done, 'substitute(v:val, ''^\l\+\>'', "done", "")')
2930         let done[-1] = substitute(done[-1], '^\l\+\>', 'stop', '')
2931         let lines = done + lines
2932       endif
2933       call reverse(lines)
2934       for line in lines
2935         let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
2936         if len(match) && match[1] !~# 'exec\|merge\|label'
2937           call add(rebasing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': strpart(match[2], 0, len), 'subject': match[3]})
2938         endif
2939       endfor
2940     endif
2942     let sequencing = []
2943     if filereadable(fugitive#Find('.git/sequencer/todo', dir))
2944       for line in reverse(readfile(fugitive#Find('.git/sequencer/todo', dir)))
2945         let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
2946         if len(match) && match[1] !~# 'exec\|merge\|label'
2947           call add(sequencing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': match[2], 'subject': match[3]})
2948         endif
2949       endfor
2950     elseif filereadable(fugitive#Find('.git/MERGE_MSG', dir))
2951       if filereadable(fugitive#Find('.git/CHERRY_PICK_HEAD', dir))
2952         let pick_head = fugitive#Execute(['rev-parse', '--short', 'CHERRY_PICK_HEAD', '--'], dir).stdout[0]
2953         call add(sequencing, {'type': 'Rebase', 'status': 'pick', 'commit': pick_head, 'subject': get(readfile(fugitive#Find('.git/MERGE_MSG', dir)), 0, '')})
2954       elseif filereadable(fugitive#Find('.git/REVERT_HEAD', dir))
2955         let pick_head = fugitive#Execute(['rev-parse', '--short', 'REVERT_HEAD', '--'], dir).stdout[0]
2956         call add(sequencing, {'type': 'Rebase', 'status': 'revert', 'commit': pick_head, 'subject': get(readfile(fugitive#Find('.git/MERGE_MSG', dir)), 0, '')})
2957       endif
2958     endif
2960     let stat.expanded = {'Staged': {}, 'Unstaged': {}}
2961     let to = {'lines': []}
2962     call s:AddHeader(to, 'Head', head)
2963     call s:AddHeader(to, stat.pull_type, pull_short)
2964     if push_ref !=# pull_ref
2965       call s:AddHeader(to, 'Push', push_short)
2966     endif
2967     if empty(stat.work_tree)
2968       if get(fugitive#ConfigGetAll('core.bare', config), 0, '') !~# '^\%(false\|no|off\|0\|\)$'
2969         call s:AddHeader(to, 'Bare', 'yes')
2970       else
2971         call s:AddHeader(to, 'Error', s:worktree_error)
2972       endif
2973     endif
2974     if get(fugitive#ConfigGetAll('advice.statusHints', config), 0, 'true') !~# '^\%(false\|no|off\|0\|\)$'
2975       call s:AddHeader(to, 'Help', 'g?')
2976     endif
2978     call s:AddSection(to, 'Rebasing ' . rebasing_head, rebasing)
2979     call s:AddSection(to, get(get(sequencing, 0, {}), 'tous', '') ==# 'revert' ? 'Reverting' : 'Cherry Picking', sequencing)
2980     call s:AddSection(to, 'Untracked', untracked)
2981     call s:AddDiffSection(to, stat, 'Unstaged', unstaged)
2982     call s:AddDiffSection(to, stat, 'Staged', staged)
2984     let unique_push_ref = push_ref ==# pull_ref ? '' : push_ref
2985     let unpushed_push = s:QueryLogRange(unique_push_ref, head, dir)
2986     if get(stat.props, 'branch.ab') =~# '^+0 '
2987       let unpushed_pull = {'error': 0, 'overflow': 0, 'entries': []}
2988     else
2989       let unpushed_pull = s:QueryLogRange(pull_ref, head, dir)
2990     endif
2991     " If the push ref is defined but nowhere to be found at the remote,
2992     " pretend it's the same as the pull ref
2993     if unpushed_push.error == 1
2994       let unpushed_push = unpushed_pull
2995     endif
2996     call s:AddLogSection(to, 'Unpushed to ' . push_short, unpushed_push)
2997     call s:AddLogSection(to, 'Unpushed to ' . pull_short, unpushed_pull)
2998     if unpushed_push.error && unpushed_pull.error && empty(rebasing) &&
2999           \ !empty(stat.push_remote . stat.fetch_remote)
3000       call s:AddLogSection(to, 'Unpushed to *', s:QueryLog([head, '--not', '--remotes'], 256, dir))
3001     endif
3002     call s:AddLogSection(to, 'Unpulled from ' . push_short, s:QueryLogRange(head, unique_push_ref, dir))
3003     if len(pull_ref) && get(stat.props, 'branch.ab') !~# ' -0$'
3004       call s:AddLogSection(to, 'Unpulled from ' . pull_short, s:QueryLogRange(head, pull_ref, dir))
3005     endif
3007     let bufnr = stat.bufnr
3008     setlocal noreadonly modifiable
3009     if len(to.lines) < line('$')
3010       silent keepjumps execute (len(to.lines)+1) . ',$delete_'
3011     endif
3012     call setline(1, to.lines)
3013     call setbufvar(bufnr, 'fugitive_status', stat)
3014     call setbufvar(bufnr, 'fugitive_expanded', stat.expanded)
3015     setlocal nomodified readonly nomodifiable
3016     return ''
3017   finally
3018     let b:fugitive_type = 'index'
3019   endtry
3020 endfunction
3022 function! s:StatusRetrieve(bufnr, ...) abort
3023   let amatch = s:Slash(fnamemodify(bufname(a:bufnr), ':p'))
3024   let dir = s:Dir(a:bufnr)
3025   let config = fugitive#Config(dir, function('len'))
3027   let cmd = [dir]
3028   if amatch !~# '^fugitive:' && s:cpath($GIT_INDEX_FILE !=# '' ? resolve(s:GitIndexFileEnv()) : fugitive#Find('.git/index', dir)) !=# s:cpath(amatch)
3029     let cmd += [{'env': {'GIT_INDEX_FILE': FugitiveGitPath(amatch)}}]
3030   endif
3032   if fugitive#GitVersion(2, 15)
3033     call add(cmd, '--no-optional-locks')
3034   endif
3036   let rev_parse_cmd = cmd + ['rev-parse', '--short', 'HEAD', '--']
3038   let stat = {'bufnr': a:bufnr, 'reltime': reltime(), 'work_tree': s:Tree(dir), 'cmd': cmd, 'config': config}
3039   if empty(stat.work_tree)
3040     let stat.rev_parse = call('fugitive#Execute', [rev_parse_cmd, function('s:StatusProcess'), stat] + a:000)
3041     let stat.status = {}
3042     let stat.running = stat.rev_parse
3043   else
3044     let stat.rev_parse = fugitive#Execute(rev_parse_cmd)
3045     let status_cmd = cmd + ['status', '-bz', fugitive#GitVersion(2, 11) ? '--porcelain=v2' : '--porcelain']
3046     let stat.status = call('fugitive#Execute', [status_cmd, function('s:StatusProcess'), stat] + a:000)
3047     let stat.running = stat.status
3048   endif
3049   return stat
3050 endfunction
3052 function! fugitive#BufReadStatus(cmdbang) abort
3053   exe s:VersionCheck()
3054   if a:cmdbang
3055     unlet! b:fugitive_expanded
3056   endif
3057   let b:fugitive_type = 'index'
3058   let stat = s:StatusRetrieve(bufnr(''))
3059   try
3060     let b:fugitive_loading = stat
3061     doautocmd <nomodeline> BufReadPre
3063     setlocal readonly nomodifiable noswapfile nomodifiable buftype=nowrite
3064     call s:MapStatus()
3066     call s:StatusRender(stat)
3068     doautocmd <nomodeline> BufReadPost
3069     if &bufhidden ==# ''
3070       setlocal bufhidden=delete
3071     endif
3072     if !exists('b:dispatch')
3073       let b:dispatch = ':Git fetch --all'
3074     endif
3075     setlocal filetype=fugitive
3077     return s:DoAutocmd('User FugitiveIndex')
3078   finally
3079     call setbufvar(stat.bufnr, 'fugitive_loading', {})
3080   endtry
3081 endfunction
3083 function! fugitive#FileReadCmd(...) abort
3084   let amatch = a:0 ? a:1 : expand('<amatch>')
3085   let [dir, rev] = s:DirRev(amatch)
3086   let line = a:0 > 1 ? a:2 : line("'[")
3087   if empty(dir)
3088     return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
3089   endif
3090   if rev !~# ':' && s:ChompDefault('', [dir, 'cat-file', '-t', rev]) =~# '^\%(commit\|tag\)$'
3091     let cmd = [dir, 'log', '--pretty=format:%B', '-1', rev, '--']
3092   elseif rev ==# ':'
3093     let cmd = [dir, 'status', '--short']
3094   else
3095     let cmd = [dir, 'cat-file', '-p', rev, '--']
3096   endif
3097   let temp = tempname()
3098   let [err, exec_error] = s:StdoutToFile(temp, cmd)
3099   if exec_error
3100     call delete(temp)
3101     return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
3102   else
3103     return 'silent keepalt ' . line . 'read ' . s:fnameescape(temp) . '|call delete(' . string(temp) . ')'
3104   endif
3105 endfunction
3107 function! fugitive#FileWriteCmd(...) abort
3108   let temp = tempname()
3109   let amatch = a:0 ? a:1 : expand('<amatch>')
3110   let autype = a:0 > 1 ? 'Buf' : 'File'
3111   if exists('#' . autype . 'WritePre')
3112     execute s:DoAutocmd(autype . 'WritePre ' . s:fnameescape(amatch))
3113   endif
3114   try
3115     let [dir, commit, file] = s:DirCommitFile(amatch)
3116     if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
3117       return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
3118     endif
3119     silent execute "noautocmd keepalt '[,']write ".temp
3120     let hash = s:TreeChomp([dir, '--literal-pathspecs', 'hash-object', '-w', '--', FugitiveGitPath(temp)])
3121     let old_mode = matchstr(s:ChompDefault('', ['ls-files', '--stage', '.' . file], dir), '^\d\+')
3122     if empty(old_mode)
3123       let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
3124     endif
3125     let error = s:UpdateIndex(dir, [old_mode, hash, commit, file[1:-1]])
3126     if empty(error)
3127       setlocal nomodified
3128       if exists('#' . autype . 'WritePost')
3129         execute s:DoAutocmd(autype . 'WritePost ' . s:fnameescape(amatch))
3130       endif
3131       exe s:DoAutocmdChanged(dir)
3132       return ''
3133     else
3134       return 'echoerr '.string('fugitive: '.error)
3135     endif
3136   catch /^fugitive:/
3137     return 'echoerr ' . string(v:exception)
3138   finally
3139     call delete(temp)
3140   endtry
3141 endfunction
3143 function! fugitive#BufReadCmd(...) abort
3144   let amatch = a:0 ? a:1 : expand('<amatch>')
3145   let [dir, rev] = s:DirRev(amatch)
3146   if empty(dir)
3147     return 'echo "Invalid Fugitive URL"'
3148   endif
3149   call s:InitializeBuffer(dir)
3150   if rev ==# ':'
3151     return fugitive#BufReadStatus(v:cmdbang)
3152   endif
3153   try
3154     if rev =~# '^:\d$'
3155       let b:fugitive_type = 'stage'
3156     else
3157       let r = fugitive#Execute([dir, 'cat-file', '-t', rev])
3158       let b:fugitive_type = get(r.stdout, 0, '')
3159       if r.exit_status && rev =~# '^:0'
3160         let r = fugitive#Execute([dir, 'write-tree', '--prefix=' . rev[3:-1]])
3161         let sha = get(r.stdout, 0, '')
3162         let b:fugitive_type = 'tree'
3163       endif
3164       if r.exit_status
3165         let error = substitute(join(r.stderr, "\n"), "\n*$", '', '')
3166         unlet b:fugitive_type
3167         setlocal noswapfile
3168         if empty(&bufhidden)
3169           setlocal bufhidden=delete
3170         endif
3171         if rev =~# '^:\d:'
3172           let &l:readonly = !filewritable(fugitive#Find('.git/index', dir))
3173           return 'doautocmd BufNewFile'
3174         else
3175           setlocal readonly nomodifiable
3176           return 'doautocmd BufNewFile|echo ' . string(error)
3177         endif
3178       elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
3179         return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
3180       endif
3181       if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
3182         let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
3183       endif
3184     endif
3186     if b:fugitive_type !=# 'blob'
3187       setlocal nomodeline
3188     endif
3190     setlocal noreadonly modifiable
3191     let pos = getpos('.')
3192     silent keepjumps %delete_
3193     setlocal endofline
3195     let events = ['User FugitiveObject', 'User Fugitive' . substitute(b:fugitive_type, '^\l', '\u&', '')]
3197     try
3198       if b:fugitive_type !=# 'blob'
3199         setlocal foldmarker=<<<<<<<<,>>>>>>>>
3200       endif
3201       exe s:DoAutocmd('BufReadPre')
3202       if b:fugitive_type ==# 'tree'
3203         let b:fugitive_display_format = b:fugitive_display_format % 2
3204         if b:fugitive_display_format
3205           call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
3206         else
3207           if !exists('sha')
3208             let sha = s:TreeChomp(dir, 'rev-parse', '--verify', rev, '--')
3209           endif
3210           call s:ReplaceCmd([dir, 'show', '--no-color', sha])
3211         endif
3212       elseif b:fugitive_type ==# 'tag'
3213         let b:fugitive_display_format = b:fugitive_display_format % 2
3214         if b:fugitive_display_format
3215           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
3216         else
3217           call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
3218         endif
3219       elseif b:fugitive_type ==# 'commit'
3220         let b:fugitive_display_format = b:fugitive_display_format % 2
3221         if b:fugitive_display_format
3222           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
3223         else
3224           call s:ReplaceCmd([dir, '-c', 'diff.noprefix=false', '-c', 'log.showRoot=false', 'show', '--no-color', '-m', '--first-parent', '--pretty=format:tree%x20%T%nparent%x20%P%nauthor%x20%an%x20<%ae>%x20%ad%ncommitter%x20%cn%x20<%ce>%x20%cd%nencoding%x20%e%n%n%B', rev])
3225           keepjumps 1
3226           keepjumps call search('^parent ')
3227           if getline('.') ==# 'parent '
3228             silent lockmarks keepjumps delete_
3229           else
3230             silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
3231           endif
3232           keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
3233           if lnum
3234             silent lockmarks keepjumps delete_
3235           end
3236           silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/s/\r$//e'
3237           keepjumps 1
3238         endif
3239       elseif b:fugitive_type ==# 'stage'
3240         call s:ReplaceCmd([dir, 'ls-files', '--stage'])
3241       elseif b:fugitive_type ==# 'blob'
3242         let blob_or_filters = rev =~# ':' && fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
3243         call s:ReplaceCmd([dir, 'cat-file', blob_or_filters, rev])
3244       endif
3245     finally
3246       keepjumps call setpos('.',pos)
3247       setlocal nomodified noswapfile
3248       let modifiable = rev =~# '^:.:' && b:fugitive_type !=# 'tree'
3249       if modifiable
3250         let events = ['User FugitiveStageBlob']
3251       endif
3252       let &l:readonly = !modifiable || !filewritable(fugitive#Find('.git/index', dir))
3253       if empty(&bufhidden)
3254         setlocal bufhidden=delete
3255       endif
3256       let &l:modifiable = modifiable
3257       call fugitive#MapJumps()
3258       if b:fugitive_type !=# 'blob'
3259         call s:Map('n', 'a', ":<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
3260         call s:Map('n', 'i', ":<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
3261         setlocal filetype=git
3262       endif
3263     endtry
3265     setlocal modifiable
3267     return s:DoAutocmd('BufReadPost') .
3268           \ (modifiable ? '' : '|setl nomodifiable') . '|' .
3269           \ call('s:DoAutocmd', events)
3270   catch /^fugitive:/
3271     return 'echoerr ' . string(v:exception)
3272   endtry
3273 endfunction
3275 function! fugitive#BufWriteCmd(...) abort
3276   return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
3277 endfunction
3279 function! fugitive#SourceCmd(...) abort
3280   let amatch = a:0 ? a:1 : expand('<amatch>')
3281   let temp = s:BlobTemp(amatch)
3282   if empty(temp)
3283     return 'noautocmd source ' . s:fnameescape(amatch)
3284   endif
3285   if !exists('g:virtual_scriptnames')
3286     let g:virtual_scriptnames = {}
3287   endif
3288   let g:virtual_scriptnames[temp] = amatch
3289   return 'source ' . s:fnameescape(temp)
3290 endfunction
3292 " Section: Temp files
3294 if !exists('s:temp_files')
3295   let s:temp_files = {}
3296 endif
3298 function! s:TempState(...) abort
3299   return get(s:temp_files, s:cpath(s:AbsoluteVimPath(a:0 ? a:1 : -1)), {})
3300 endfunction
3302 function! fugitive#Result(...) abort
3303   if !a:0 && exists('g:fugitive_event')
3304     return get(g:, 'fugitive_result', {})
3305   elseif !a:0 || type(a:1) == type('') && a:1 =~# '^-\=$'
3306     return get(g:, '_fugitive_last_job', {})
3307   elseif type(a:1) == type(0)
3308     return s:TempState(a:1)
3309   elseif type(a:1) == type('')
3310     return s:TempState(a:1)
3311   elseif type(a:1) == type({}) && has_key(a:1, 'file')
3312     return s:TempState(a:1.file)
3313   else
3314     return {}
3315   endif
3316 endfunction
3318 function! s:TempDotMap() abort
3319   let cfile = s:cfile()
3320   if empty(cfile)
3321     if getline('.') =~# '^[*+] \+\f' && col('.') < 2
3322       return matchstr(getline('.'), '^. \+\zs\f\+')
3323     else
3324       return expand('<cfile>')
3325     endif
3326   endif
3327   let name = fugitive#Find(cfile[0])
3328   let [dir, commit, file] = s:DirCommitFile(name)
3329   if len(commit) && empty(file)
3330     return commit
3331   elseif s:cpath(s:Tree(), getcwd())
3332     return fugitive#Path(name, "./")
3333   else
3334     return fugitive#Real(name)
3335   endif
3336 endfunction
3338 function! s:TempReadPre(file) abort
3339   let key = s:cpath(s:AbsoluteVimPath(a:file))
3340   if has_key(s:temp_files, key)
3341     let dict = s:temp_files[key]
3342     setlocal nomodeline
3343     if empty(&bufhidden)
3344       setlocal bufhidden=delete
3345     endif
3346     setlocal buftype=nowrite
3347     setlocal nomodifiable
3348     call s:InitializeBuffer(dict)
3349     if len(dict.git_dir)
3350       call extend(b:, {'fugitive_type': 'temp'}, 'keep')
3351     endif
3352   endif
3353   return ''
3354 endfunction
3356 function! s:TempReadPost(file) abort
3357   let key = s:cpath(s:AbsoluteVimPath(a:file))
3358   if has_key(s:temp_files, key)
3359     let dict = s:temp_files[key]
3360     if !has_key(dict, 'job')
3361       setlocal nobuflisted
3362     endif
3363     if get(dict, 'filetype', '') ==# 'git'
3364       call fugitive#MapJumps()
3365       call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
3366       call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
3367     endif
3368     if has_key(dict, 'filetype')
3369       if dict.filetype ==# 'man' && has('nvim')
3370         let b:man_sect = matchstr(getline(1), '^\w\+(\zs\d\+\ze)')
3371       endif
3372       if !get(g:, 'did_load_ftplugin') && dict.filetype ==# 'fugitiveblame'
3373         call s:BlameMaps(0)
3374       endif
3375       let &l:filetype = dict.filetype
3376     endif
3377     setlocal foldmarker=<<<<<<<<,>>>>>>>>
3378     if !&modifiable
3379       call s:Map('n', 'gq', ":<C-U>bdelete<CR>", '<silent> <unique>')
3380     endif
3381     return 'doautocmd <nomodeline> User FugitivePager'
3382   endif
3383   return ''
3384 endfunction
3386 function! s:TempDelete(file) abort
3387   let key = s:cpath(s:AbsoluteVimPath(a:file))
3388   if has_key(s:temp_files, key) && !has_key(s:temp_files[key], 'job') && key !=# s:cpath(get(get(g:, '_fugitive_last_job', {}), 'file', ''))
3389     call delete(a:file)
3390     call remove(s:temp_files, key)
3391   endif
3392   return ''
3393 endfunction
3395 function! s:OriginBufnr(...) abort
3396   let state = s:TempState(a:0 ? a:1 : bufnr(''))
3397   return get(state, 'origin_bufnr', -1)
3398 endfunction
3400 augroup fugitive_temp
3401   autocmd!
3402   autocmd BufReadPre  * exe s:TempReadPre( +expand('<abuf>'))
3403   autocmd BufReadPost * exe s:TempReadPost(+expand('<abuf>'))
3404   autocmd BufWipeout  * exe s:TempDelete(  +expand('<abuf>'))
3405 augroup END
3407 " Section: :Git
3409 function! s:AskPassArgs(dir) abort
3410   if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) &&
3411         \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#ConfigGetAll('core.askpass', a:dir))
3412     if s:executable(s:VimExecPath() . '/git-gui--askpass')
3413       return ['-c', 'core.askPass=' . s:ExecPath()[0] . '/git-gui--askpass']
3414     elseif s:executable('ssh-askpass')
3415       return ['-c', 'core.askPass=ssh-askpass']
3416     endif
3417   endif
3418   return []
3419 endfunction
3421 function! s:RunSave(state) abort
3422   let s:temp_files[s:cpath(a:state.file)] = a:state
3423 endfunction
3425 function! s:RunFinished(state, ...) abort
3426   if has_key(get(g:, '_fugitive_last_job', {}), 'file') && bufnr(g:_fugitive_last_job.file) < 0
3427     exe s:TempDelete(remove(g:, '_fugitive_last_job').file)
3428   endif
3429   let g:_fugitive_last_job = a:state
3430   let first = join(readfile(a:state.file, '', 2), "\n")
3431   if get(a:state, 'filetype', '') ==# 'git' && first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
3432     let a:state.filetype = 'man'
3433   endif
3434   if !has_key(a:state, 'capture_bufnr')
3435     return
3436   endif
3437   call fugitive#DidChange(a:state)
3438 endfunction
3440 function! s:RunEdit(state, tmp, job) abort
3441   if get(a:state, 'request', '') !=# 'edit'
3442     return 0
3443   endif
3444   call remove(a:state, 'request')
3445   let sentinel = a:state.file . '.edit'
3446   let file = FugitiveVimPath(readfile(sentinel, '', 1)[0])
3447   try
3448     if !&equalalways && a:state.mods !~# '\<\d*tab\>' && 3 > (a:state.mods =~# '\<vert' ? winwidth(0) : winheight(0))
3449       let noequalalways = 1
3450       setglobal equalalways
3451     endif
3452     let mods = s:Mods(a:state.mods, 'SpanOrigin')
3453     exe substitute(mods, '\<tab\>', '-tab', 'g') 'keepalt split' s:fnameescape(file)
3454   finally
3455     if exists('l:noequalalways')
3456       setglobal noequalalways
3457     endif
3458   endtry
3459   set bufhidden=wipe
3460   call s:InitializeBuffer(a:state)
3461   let bufnr = bufnr('')
3462   let s:edit_jobs[bufnr] = [a:state, a:tmp, a:job, sentinel]
3463   call fugitive#DidChange(a:state.git_dir)
3464   if bufnr == bufnr('') && !exists('g:fugitive_event')
3465     try
3466       let g:fugitive_event = a:state.git_dir
3467       let g:fugitive_result = a:state
3468       exe s:DoAutocmd('User FugitiveEditor')
3469     finally
3470       unlet! g:fugitive_event g:fugitive_result
3471     endtry
3472   endif
3473   return 1
3474 endfunction
3476 function! s:RunReceive(state, tmp, type, job, data, ...) abort
3477   if a:type ==# 'err' || a:state.pty
3478     let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
3479     let data = a:tmp.escape . data
3480     let escape = "\033]51;[^\007]*"
3481     let a:tmp.escape = matchstr(data, escape . '$')
3482     if len(a:tmp.escape)
3483       let data = strpart(data, 0, len(data) - len(a:tmp.escape))
3484     endif
3485     let cmd = matchstr(data, escape . "\007")[5:-2]
3486     let data = substitute(data, escape . "\007", '', 'g')
3487     if cmd =~# '^fugitive:'
3488       let a:state.request = strpart(cmd, 9)
3489     endif
3490     let lines = split(a:tmp.err . data, "\r\\=\n", 1)
3491     let a:tmp.err = lines[-1]
3492     let lines[-1] = ''
3493     call map(lines, 'substitute(v:val, ".*\r", "", "")')
3494   else
3495     let lines = type(a:data) == type([]) ? a:data : split(a:data, "\n", 1)
3496     if len(a:tmp.out)
3497       let lines[0] = a:tmp.out . lines[0]
3498     endif
3499     let a:tmp.out = lines[-1]
3500     let lines[-1] = ''
3501   endif
3502   call writefile(lines, a:state.file, 'ba')
3503   if has_key(a:tmp, 'echo')
3504     if !exists('l:data')
3505       let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
3506     endif
3507     let a:tmp.echo .= data
3508   endif
3509   let line_count = a:tmp.line_count
3510   let a:tmp.line_count += len(lines) - 1
3511   if !has_key(a:state, 'capture_bufnr') || !bufloaded(a:state.capture_bufnr)
3512     return
3513   endif
3514   call remove(lines, -1)
3515   try
3516     call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
3517     if !line_count && len(lines) > 1000
3518       let first = remove(lines, 0, 999)
3519       call setbufline(a:state.capture_bufnr, 1, first)
3520       redraw
3521       call setbufline(a:state.capture_bufnr, 1001, lines)
3522     else
3523       call setbufline(a:state.capture_bufnr, line_count + 1, lines)
3524     endif
3525     call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
3526     if !a:state.pager && getwinvar(bufwinid(a:state.capture_bufnr), '&previewwindow')
3527       let winnr = bufwinnr(a:state.capture_bufnr)
3528       if winnr > 0
3529         let old_winnr = winnr()
3530         exe 'noautocmd' winnr.'wincmd w'
3531         $
3532         exe 'noautocmd' old_winnr.'wincmd w'
3533       endif
3534     endif
3535   catch
3536   endtry
3537 endfunction
3539 function! s:RunExit(state, tmp, job, exit_status) abort
3540   let a:state.exit_status = a:exit_status
3541   if has_key(a:state, 'job')
3542     return
3543   endif
3544   call s:RunFinished(a:state)
3545 endfunction
3547 function! s:RunClose(state, tmp, job, ...) abort
3548   if a:0
3549     call s:RunExit(a:state, a:tmp, a:job, a:1)
3550   endif
3551   let noeol = substitute(substitute(a:tmp.err, "\r$", '', ''), ".*\r", '', '') . a:tmp.out
3552   call writefile([noeol], a:state.file, 'ba')
3553   call remove(a:state, 'job')
3554   if has_key(a:state, 'capture_bufnr') && bufloaded(a:state.capture_bufnr)
3555     if len(noeol)
3556       call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
3557       call setbufline(a:state.capture_bufnr, a:tmp.line_count + 1, [noeol])
3558       call setbufvar(a:state.capture_bufnr, '&eol', 0)
3559       call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
3560     endif
3561     call setbufvar(a:state.capture_bufnr, '&modified', 0)
3562     call setbufvar(a:state.capture_bufnr, '&buflisted', 0)
3563     if a:state.filetype !=# getbufvar(a:state.capture_bufnr, '&filetype', '')
3564       call setbufvar(a:state.capture_bufnr, '&filetype', a:state.filetype)
3565     endif
3566   endif
3567   if !has_key(a:state, 'exit_status')
3568     return
3569   endif
3570   call s:RunFinished(a:state)
3571 endfunction
3573 function! s:RunSend(job, str) abort
3574   try
3575     if type(a:job) == type(0)
3576       call chansend(a:job, a:str)
3577     else
3578       call ch_sendraw(a:job, a:str)
3579     endif
3580     return len(a:str)
3581   catch /^Vim\%((\a\+)\)\=:E90[06]:/
3582     return 0
3583   endtry
3584 endfunction
3586 function! s:RunCloseIn(job) abort
3587   try
3588     if type(a:job) ==# type(0)
3589       call chanclose(a:job, 'stdin')
3590     else
3591       call ch_close_in(a:job)
3592     endif
3593     return 1
3594   catch /^Vim\%((\a\+)\)\=:E90[06]:/
3595     return 0
3596   endtry
3597 endfunction
3599 function! s:RunEcho(tmp) abort
3600   if !has_key(a:tmp, 'echo')
3601     return
3602   endif
3603   let data = a:tmp.echo
3604   let a:tmp.echo = matchstr(data, "[\r\n]\\+$")
3605   if len(a:tmp.echo)
3606     let data = strpart(data, 0, len(data) - len(a:tmp.echo))
3607   endif
3608   echon substitute(data, "\r\\ze\n", '', 'g')
3609 endfunction
3611 function! s:RunTick(job) abort
3612   if type(a:job) == v:t_number
3613     return jobwait([a:job], 1)[0] == -1
3614   elseif type(a:job) == 8
3615     let running = ch_status(a:job) !~# '^closed$\|^fail$' || job_status(a:job) ==# 'run'
3616     sleep 1m
3617     return running
3618   endif
3619 endfunction
3621 if !exists('s:edit_jobs')
3622   let s:edit_jobs = {}
3623 endif
3624 function! s:RunWait(state, tmp, job, ...) abort
3625   if a:0 && filereadable(a:1)
3626     call delete(a:1)
3627   endif
3628   try
3629     if a:tmp.no_more && &more
3630       let more = &more
3631       let &more = 0
3632     endif
3633     while get(a:state, 'request', '') !=# 'edit' && s:RunTick(a:job)
3634       call s:RunEcho(a:tmp)
3635       if !get(a:tmp, 'closed_in')
3636         let peek = getchar(1)
3637         if peek != 0 && !(has('win32') && peek == 128)
3638           let c = getchar()
3639           let c = type(c) == type(0) ? nr2char(c) : c
3640           if c ==# "\<C-D>" || c ==# "\<Esc>"
3641             let a:tmp.closed_in = 1
3642             let can_pedit = s:RunCloseIn(a:job) && exists('*setbufline')
3643             for winnr in range(1, winnr('$'))
3644               if getwinvar(winnr, '&previewwindow') && getbufvar(winbufnr(winnr), '&modified')
3645                 let can_pedit = 0
3646               endif
3647             endfor
3648             if can_pedit
3649               if has_key(a:tmp, 'echo')
3650                 call remove(a:tmp, 'echo')
3651               endif
3652               call writefile(['fugitive: aborting edit due to background operation.'], a:state.file . '.exit')
3653               exe (&splitbelow ? 'botright' : 'topleft') 'silent pedit ++ff=unix' s:fnameescape(a:state.file)
3654               let a:state.capture_bufnr = bufnr(a:state.file)
3655               call setbufvar(a:state.capture_bufnr, '&modified', 1)
3656               let finished = 0
3657               redraw!
3658               return ''
3659             endif
3660           else
3661             call s:RunSend(a:job, c)
3662             if !a:state.pty
3663               echon c
3664             endif
3665           endif
3666         endif
3667       endif
3668     endwhile
3669     if !has_key(a:state, 'request') && has_key(a:state, 'job') && exists('*job_status') && job_status(a:job) ==# "dead"
3670       throw 'fugitive: close callback did not fire; this should never happen'
3671     endif
3672     call s:RunEcho(a:tmp)
3673     if has_key(a:tmp, 'echo')
3674       let a:tmp.echo = substitute(a:tmp.echo, "^\r\\=\n", '', '')
3675       echo
3676     endif
3677     let finished = !s:RunEdit(a:state, a:tmp, a:job)
3678   finally
3679     if exists('l:more')
3680       let &more = more
3681     endif
3682     if !exists('finished')
3683       try
3684         if a:state.pty && !get(a:tmp, 'closed_in')
3685           call s:RunSend(a:job, "\<C-C>")
3686         elseif type(a:job) == type(0)
3687           call jobstop(a:job)
3688         else
3689           call job_stop(a:job)
3690         endif
3691       catch /.*/
3692       endtry
3693     elseif finished
3694       call fugitive#DidChange(a:state)
3695     endif
3696   endtry
3697   return ''
3698 endfunction
3700 if !exists('s:resume_queue')
3701   let s:resume_queue = []
3702 endif
3703 function! fugitive#Resume() abort
3704   while len(s:resume_queue)
3705     let enqueued = remove(s:resume_queue, 0)
3706     if enqueued[2] isnot# ''
3707       try
3708         call call('s:RunWait', enqueued)
3709       endtry
3710     endif
3711   endwhile
3712 endfunction
3714 function! s:RunBufDelete(bufnr) abort
3715   let state = s:TempState(+a:bufnr)
3716   if has_key(state, 'job')
3717     try
3718       if type(state.job) == type(0)
3719         call jobstop(state.job)
3720       else
3721         call job_stop(state.job)
3722       endif
3723     catch
3724     endtry
3725   endif
3726   if has_key(s:edit_jobs, a:bufnr) |
3727     call add(s:resume_queue, remove(s:edit_jobs, a:bufnr))
3728     call feedkeys("\<C-\>\<C-N>:redraw!|call delete(" . string(s:resume_queue[-1][0].file . '.edit') .
3729           \ ")|call fugitive#Resume()|checktime\r", 'n')
3730   endif
3731 endfunction
3733 augroup fugitive_job
3734   autocmd!
3735   autocmd BufDelete * call s:RunBufDelete(+expand('<abuf>'))
3736   autocmd VimLeave *
3737         \ for s:jobbuf in keys(s:edit_jobs) |
3738         \   call writefile(['Aborting edit due to Vim exit.'], s:edit_jobs[s:jobbuf][0].file . '.exit') |
3739         \   redraw! |
3740         \   call call('s:RunWait', remove(s:edit_jobs, s:jobbuf)) |
3741         \ endfor
3742 augroup END
3744 function! fugitive#CanPty() abort
3745   return get(g:, 'fugitive_pty_debug_override',
3746         \ has('unix') && !has('win32unix') && (has('patch-8.0.0744') || has('nvim')) && fugitive#GitVersion() !~# '\.windows\>')
3747 endfunction
3749 function! fugitive#PagerFor(argv, ...) abort
3750   let args = a:argv
3751   if empty(args)
3752     return 0
3753   elseif (args[0] ==# 'help' || get(args, 1, '') ==# '--help') && !s:HasOpt(args, '--web')
3754     return 1
3755   endif
3756   if args[0] ==# 'config' && (s:HasOpt(args, '-e', '--edit') ||
3757         \   !s:HasOpt(args, '--list', '--get-all', '--get-regexp', '--get-urlmatch')) ||
3758         \ args[0] =~# '^\%(tag\|branch\)$' && (
3759         \    s:HasOpt(args, '--edit-description', '--unset-upstream', '-m', '-M', '--move', '-c', '-C', '--copy', '-d', '-D', '--delete') ||
3760         \   len(filter(args[1:-1], 'v:val =~# "^[^-]\\|^--set-upstream-to="')) &&
3761         \   !s:HasOpt(args, '--contains', '--no-contains', '--merged', '--no-merged', '--points-at'))
3762     return 0
3763   endif
3764   let config = a:0 ? a:1 : fugitive#Config()
3765   let value = get(fugitive#ConfigGetAll('pager.' . args[0], config), 0, -1)
3766   if value =~# '^\%(true\|yes\|on\|1\)$'
3767     return 1
3768   elseif value =~# '^\%(false\|no|off\|0\|\)$'
3769     return 0
3770   elseif type(value) == type('')
3771     return value
3772   elseif args[0] =~# '^\%(branch\|config\|diff\|grep\|log\|range-diff\|shortlog\|show\|tag\|whatchanged\)$' ||
3773         \ (args[0] ==# 'stash' && get(args, 1, '') ==# 'show') ||
3774         \ (args[0] ==# 'reflog' && get(args, 1, '') !~# '^\%(expire\|delete\|exists\)$') ||
3775         \ (args[0] ==# 'am' && s:HasOpt(args, '--show-current-patch'))
3776     return 1
3777   else
3778     return 0
3779   endif
3780 endfunction
3782 let s:disable_colors = []
3783 for s:colortype in ['advice', 'branch', 'diff', 'grep', 'interactive', 'pager', 'push', 'remote', 'showBranch', 'status', 'transport', 'ui']
3784   call extend(s:disable_colors, ['-c', 'color.' . s:colortype . '=false'])
3785 endfor
3786 unlet s:colortype
3787 function! fugitive#Command(line1, line2, range, bang, mods, arg, ...) abort
3788   exe s:VersionCheck()
3789   let dir = call('s:Dir', a:000)
3790   if len(dir)
3791     exe s:DirCheck(dir)
3792   endif
3793   let config = copy(fugitive#Config(dir))
3794   let curwin = a:arg =~# '^++curwin\>' || !a:line2
3795   let [args, after] = s:SplitExpandChain(substitute(a:arg, '^++curwin\>\s*', '', ''), s:Tree(dir))
3796   let flags = []
3797   let pager = -1
3798   let explicit_pathspec_option = 0
3799   let did_expand_alias = 0
3800   while len(args)
3801     if args[0] ==# '-c' && len(args) > 1
3802       call extend(flags, remove(args, 0, 1))
3803     elseif args[0] =~# '^-p$\|^--paginate$'
3804       let pager = 2
3805       call remove(args, 0)
3806     elseif args[0] =~# '^-P$\|^--no-pager$'
3807       let pager = 0
3808       call remove(args, 0)
3809     elseif args[0] =~# '^--\%([[:lower:]-]\+-pathspecs\)$'
3810       let explicit_pathspec_option = 1
3811       call add(flags, remove(args, 0))
3812     elseif args[0] =~# '^\%(--no-optional-locks\)$'
3813       call add(flags, remove(args, 0))
3814     elseif args[0] =~# '^-C$\|^--\%(exec-path=\|git-dir=\|work-tree=\|bare$\)'
3815       return 'echoerr ' . string('fugitive: ' . args[0] . ' is not supported')
3816     elseif did_expand_alias
3817       break
3818     else
3819       let alias = FugitiveConfigGet('alias.' . get(args, 0, ''), config)
3820       if get(args, 1, '') !=# '--help' && alias !~# '^$\|^!\|[\"'']' && !filereadable(s:VimExecPath() . '/git-' . args[0])
3821             \ && !(has('win32') && filereadable(s:VimExecPath() . '/git-' . args[0] . '.exe'))
3822         call remove(args, 0)
3823         call extend(args, split(alias, '\s\+'), 'keep')
3824         let did_expand_alias = 1
3825       else
3826         break
3827       endif
3828     endif
3829   endwhile
3830   if !explicit_pathspec_option
3831     call insert(flags, '--no-literal-pathspecs')
3832   endif
3833   let no_pager = pager is# 0
3834   if no_pager
3835     call add(flags, '--no-pager')
3836   endif
3837   let env = {}
3838   let i = 0
3839   while i < len(flags) - 1
3840     if flags[i] ==# '-c'
3841       let i += 1
3842       let config_name = tolower(matchstr(flags[i], '^[^=]\+'))
3843       if has_key(s:prepare_env, config_name) && flags[i] =~# '=.'
3844         let env[s:prepare_env[config_name]] = matchstr(flags[i], '=\zs.*')
3845       endif
3846       if flags[i] =~# '='
3847         let config[config_name] = [matchstr(flags[i], '=\zs.*')]
3848       else
3849         let config[config_name] = [1]
3850       endif
3851     endif
3852     let i += 1
3853   endwhile
3854   let options = {'git': s:UserCommandList(), 'git_dir': s:GitDir(dir), 'flags': flags, 'curwin': curwin}
3855   if empty(args) && pager is# -1
3856     let cmd = s:StatusCommand(a:line1, a:line2, a:range, curwin ? 0 : a:line2, a:bang, a:mods, '', '', [], options)
3857     return (empty(cmd) ? 'exe' : cmd) . after
3858   endif
3859   let name = substitute(get(args, 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
3860   if pager is# -1 && name =~# '^\a\+$' && exists('*s:' . name . 'Subcommand') && get(args, 1, '') !=# '--help'
3861     try
3862       let overrides = s:{name}Subcommand(a:line1, a:line2, a:range, a:bang, a:mods, extend({'subcommand': args[0], 'subcommand_args': args[1:-1]}, options))
3863       if type(overrides) == type('')
3864         return 'exe ' . string(overrides) . after
3865       endif
3866       let args = [get(overrides, 'command', args[0])] + get(overrides, 'insert_args', []) + args[1:-1]
3867     catch /^fugitive:/
3868       return 'echoerr ' . string(v:exception)
3869     endtry
3870   else
3871     let overrides = {}
3872   endif
3873   call extend(env, get(overrides, 'env', {}))
3874   call s:PrepareEnv(env, dir)
3875   if pager is# -1
3876     let pager = fugitive#PagerFor(args, config)
3877   endif
3878   let wants_terminal = type(pager) ==# type('') ||
3879         \ (s:HasOpt(args, ['add', 'checkout', 'commit', 'reset', 'restore', 'stage', 'stash'], '-p', '--patch') ||
3880         \ s:HasOpt(args, ['add', 'clean', 'stage'], '-i', '--interactive')) && pager is# 0
3881   if wants_terminal
3882     let mods = substitute(s:Mods(a:mods), '\<tab\>', '-tab', 'g')
3883     let assign = len(dir) ? "|call FugitiveDetect({'git_dir':" . string(options.git_dir) . '})' : ''
3884     let argv = s:UserCommandList(options) + args
3885     let term_opts = len(env) ? {'env': env} : {}
3886     if has('nvim')
3887       call fugitive#Autowrite()
3888       return mods . (curwin ? 'enew' : 'new') . '|call termopen(' . string(argv) . ', ' . string(term_opts) . ')' . assign . '|startinsert' . after
3889     elseif exists('*term_start')
3890       call fugitive#Autowrite()
3891       if curwin
3892         let term_opts.curwin = 1
3893       endif
3894       return mods . 'call term_start(' . string(argv) . ', ' . string(term_opts) . ')' . assign . after
3895     endif
3896   endif
3897   let state = {
3898         \ 'git': options.git,
3899         \ 'flags': flags,
3900         \ 'args': args,
3901         \ 'git_dir': options.git_dir,
3902         \ 'cwd': s:UserCommandCwd(dir),
3903         \ 'filetype': 'git',
3904         \ 'mods': s:Mods(a:mods),
3905         \ 'file': s:Resolve(tempname())}
3906   let allow_pty = 1
3907   let after_edit = ''
3908   let stream = 0
3909   if a:bang && pager isnot# 2
3910     let state.pager = pager
3911     let pager = 1
3912     let stream = exists('*setbufline')
3913     let do_edit = substitute(s:Mods(a:mods, 'Edge'), '\<tab\>', '-tab', 'g') . 'pedit!'
3914   elseif pager
3915     let allow_pty = get(args, 0, '') is# 'shortlog'
3916     if pager is# 2 && a:bang && a:line2 >= 0
3917       let [do_edit, after_edit] = s:ReadPrepare(a:line1, a:line2, a:range, a:mods)
3918     elseif pager is# 2 && a:bang
3919       let do_edit = s:Mods(a:mods, 'SpanOrigin') . 'pedit'
3920     elseif !curwin
3921       let do_edit = s:Mods(a:mods, 'SpanOrigin') . 'split'
3922     else
3923       let do_edit = s:Mods(a:mods) . 'edit'
3924       call s:BlurStatus()
3925     endif
3926     call extend(env, {'COLUMNS': '' . get(g:, 'fugitive_columns', 80)}, 'keep')
3927   endif
3928   if s:run_jobs
3929     call extend(env, {'COLUMNS': '' . (&columns - 1)}, 'keep')
3930     let state.pty = allow_pty && fugitive#CanPty()
3931     if !state.pty
3932       let args = s:AskPassArgs(dir) + args
3933     endif
3934     let tmp = {
3935           \ 'no_more': no_pager || get(overrides, 'no_more'),
3936           \ 'line_count': 0,
3937           \ 'err': '',
3938           \ 'out': '',
3939           \ 'escape': ''}
3940     let env.FUGITIVE = state.file
3941     let editor = 'sh ' . s:TempScript(
3942           \ '[ -f "$FUGITIVE.exit" ] && cat "$FUGITIVE.exit" >&2 && exit 1',
3943           \ 'echo "$1" > "$FUGITIVE.edit"',
3944           \ 'printf "\033]51;fugitive:edit\007" >&2',
3945           \ 'while [ -f "$FUGITIVE.edit" -a ! -f "$FUGITIVE.exit" ]; do sleep 0.05 2>/dev/null || sleep 1; done',
3946           \ 'exit 0')
3947     call extend(env, {
3948           \ 'NO_COLOR': '1',
3949           \ 'GIT_EDITOR': editor,
3950           \ 'GIT_SEQUENCE_EDITOR': editor,
3951           \ 'GIT_PAGER': 'cat',
3952           \ 'PAGER': 'cat'}, 'keep')
3953     if s:executable('col')
3954       let env.MANPAGER = 'col -b'
3955     endif
3956     if len($GPG_TTY) && !has_key(env, 'GPG_TTY')
3957       let env.GPG_TTY = ''
3958       let did_override_gpg_tty = 1
3959     endif
3960     if stream
3961       call writefile(['fugitive: aborting edit due to background operation.'], state.file . '.exit')
3962     elseif pager
3963       call writefile(['fugitive: aborting edit due to use of pager.'], state.file . '.exit')
3964       let after = '|' . do_edit . ' ' . s:fnameescape(state.file) . after_edit . after
3965     else
3966       let env.GIT_MERGE_AUTOEDIT = '1'
3967       let tmp.echo = ''
3968     endif
3969     let args = s:disable_colors + flags + ['-c', 'advice.waitingForEditor=false'] + args
3970     let argv = s:UserCommandList({'git': options.git, 'git_dir': options.git_dir}) + args
3971     let [argv, jobopts] = s:JobOpts(argv, env)
3972     call fugitive#Autowrite()
3973     call writefile([], state.file, 'b')
3974     call s:RunSave(state)
3975     if has_key(tmp, 'echo')
3976       echo ""
3977     endif
3978     if exists('*ch_close_in')
3979       call extend(jobopts, {
3980             \ 'mode': 'raw',
3981             \ 'out_cb': function('s:RunReceive', [state, tmp, 'out']),
3982             \ 'err_cb': function('s:RunReceive', [state, tmp, 'err']),
3983             \ 'close_cb': function('s:RunClose', [state, tmp]),
3984             \ 'exit_cb': function('s:RunExit', [state, tmp]),
3985             \ })
3986       if state.pty
3987         let jobopts.pty = 1
3988       endif
3989       let job = job_start(argv, jobopts)
3990     else
3991       let job = jobstart(argv, extend(jobopts, {
3992             \ 'pty': state.pty,
3993             \ 'TERM': 'dumb',
3994             \ 'stdout_buffered': pager,
3995             \ 'stderr_buffered': pager,
3996             \ 'on_stdout': function('s:RunReceive', [state, tmp, 'out']),
3997             \ 'on_stderr': function('s:RunReceive', [state, tmp, 'err']),
3998             \ 'on_exit': function('s:RunClose', [state, tmp]),
3999             \ }))
4000     endif
4001     let state.job = job
4002     if pager
4003       let tmp.closed_in = 1
4004       call s:RunCloseIn(job)
4005     endif
4006     if stream
4007       exe 'silent' do_edit '++ff=unix' s:fnameescape(state.file)
4008       let state.capture_bufnr = bufnr(state.file)
4009       call setbufvar(state.capture_bufnr, '&modified', 1)
4010       return (after_edit . after)[1:-1]
4011     endif
4012     call add(s:resume_queue, [state, tmp, job])
4013     return 'call fugitive#Resume()|checktime' . after
4014   elseif pager
4015     let pre = s:BuildEnvPrefix(env)
4016     try
4017       if exists('+guioptions') && &guioptions =~# '!'
4018         let guioptions = &guioptions
4019         set guioptions-=!
4020       endif
4021       silent! execute '!' . escape(pre . s:shellesc(s:UserCommandList(options) + s:disable_colors + flags + ['--no-pager'] + args), '!#%') .
4022             \ (&shell =~# 'csh' ? ' >& ' . s:shellesc(state.file) : ' > ' . s:shellesc(state.file) . ' 2>&1')
4023       let state.exit_status = v:shell_error
4024     finally
4025       if exists('guioptions')
4026         let &guioptions = guioptions
4027       endif
4028     endtry
4029     redraw!
4030     call s:RunSave(state)
4031     call s:RunFinished(state)
4032     return do_edit . ' ' . s:fnameescape(state.file) . after_edit .
4033           \ '|call fugitive#DidChange(fugitive#Result(' . string(state.file) . '))' . after
4034   elseif has('win32')
4035     return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git on Windows')
4036   elseif has('gui_running')
4037     return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git in GVim')
4038   else
4039     if !explicit_pathspec_option && get(options.flags, 0, '') ==# '--no-literal-pathspecs'
4040       call remove(options.flags, 0)
4041     endif
4042     if exists('l:did_override_gpg_tty')
4043       call remove(env, 'GPG_TTY')
4044     endif
4045     let cmd = s:BuildEnvPrefix(env) . s:shellesc(s:UserCommandList(options) + args)
4046     let after = '|call fugitive#DidChange(' . string(dir) . ')' . after
4047     if !wants_terminal && (no_pager || index(['add', 'clean', 'reset', 'restore', 'stage'], get(args, 0, '')) >= 0 || s:HasOpt(args, ['checkout'], '-q', '--quiet', '--no-progress'))
4048       let output = substitute(s:SystemError(cmd)[0], "\n$", '', '')
4049       if len(output)
4050         try
4051           if &more && no_pager
4052             let more = 1
4053             set nomore
4054           endif
4055           echo substitute(output, "\n$", "", "")
4056         finally
4057           if exists('l:more')
4058             set more
4059           endif
4060         endtry
4061       endif
4062       return 'checktime' . after
4063     else
4064       return 'exe ' . string('noautocmd !' . escape(cmd, '!#%')) . after
4065     endif
4066   endif
4067 endfunction
4069 let s:exec_paths = {}
4070 function! s:ExecPath() abort
4071   let git = s:GitShellCmd()
4072   if !has_key(s:exec_paths, git)
4073     let path = get(s:JobExecute(s:GitCmd() + ['--exec-path'], {}, [], [], {}).stdout, 0, '')
4074     let s:exec_paths[git] = [path, FugitiveVimPath(path)]
4075   endif
4076   return s:exec_paths[git]
4077 endfunction
4079 function! s:VimExecPath() abort
4080   return s:ExecPath()[1]
4081 endfunction
4083 let s:subcommands_before_2_5 = [
4084       \ 'add', 'am', 'apply', 'archive', 'bisect', 'blame', 'branch', 'bundle',
4085       \ 'checkout', 'cherry', 'cherry-pick', 'citool', 'clean', 'clone', 'commit', 'config',
4086       \ 'describe', 'diff', 'difftool', 'fetch', 'format-patch', 'fsck',
4087       \ 'gc', 'grep', 'gui', 'help', 'init', 'instaweb', 'log',
4088       \ 'merge', 'mergetool', 'mv', 'notes', 'pull', 'push',
4089       \ 'rebase', 'reflog', 'remote', 'repack', 'replace', 'request-pull', 'reset', 'revert', 'rm',
4090       \ 'send-email', 'shortlog', 'show', 'show-branch', 'stash', 'stage', 'status', 'submodule',
4091       \ 'tag', 'whatchanged',
4092       \ ]
4093 let s:path_subcommands = {}
4094 function! s:CompletableSubcommands(dir) abort
4095   let c_exec_path = s:cpath(s:VimExecPath())
4096   if !has_key(s:path_subcommands, c_exec_path)
4097     if fugitive#GitVersion(2, 18)
4098       let [lines, exec_error] = s:LinesError([a:dir, '--list-cmds=list-mainporcelain,nohelpers,list-complete'])
4099       call filter(lines, 'v:val =~# "^\\S\\+$"')
4100       if !exec_error && len(lines)
4101         let s:path_subcommands[c_exec_path] = lines
4102       else
4103         let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
4104               \ ['maintenance', 'prune', 'range-diff', 'restore', 'sparse-checkout', 'switch', 'worktree']
4105       endif
4106     else
4107       let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
4108             \ (fugitive#GitVersion(2, 5) ? ['worktree'] : [])
4109     endif
4110   endif
4111   let commands = copy(s:path_subcommands[c_exec_path])
4112   for path in split($PATH, has('win32') ? ';' : ':')
4113     if path !~# '^/\|^\a:[\\/]'
4114       continue
4115     endif
4116     let cpath = s:cpath(path)
4117     if !has_key(s:path_subcommands, cpath)
4118       let s:path_subcommands[cpath] = filter(map(s:GlobComplete(path.'/git-', '*', 1),'substitute(v:val,"\\.exe$","","")'), 'v:val !~# "--\\|/"')
4119     endif
4120     call extend(commands, s:path_subcommands[cpath])
4121   endfor
4122   call extend(commands, keys(fugitive#ConfigGetRegexp('^alias\.\zs[^.]\+$', a:dir)))
4123   let configured = split(FugitiveConfigGet('completion.commands', a:dir), '\s\+')
4124   let rejected = {}
4125   for command in configured
4126     if command =~# '^-.'
4127       let rejected[strpart(command, 1)] = 1
4128     endif
4129   endfor
4130   call filter(configured, 'v:val !~# "^-"')
4131   let results = filter(sort(commands + configured), '!has_key(rejected, v:val)')
4132   if exists('*uniq')
4133     return uniq(results)
4134   else
4135     let i = 1
4136     while i < len(results)
4137       if results[i] ==# results[i-1]
4138         call remove(results, i)
4139       else
4140         let i += 1
4141       endif
4142     endwhile
4143     return results
4144   endif
4145 endfunction
4147 function! fugitive#Complete(lead, ...) abort
4148   let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? s:Dir(a:3) : s:Dir()
4149   let root = a:0 >= 4 ? a:4 : s:Tree(s:Dir())
4150   let pre = a:0 > 1 ? strpart(a:1, 0, a:2) : ''
4151   let subcmd = matchstr(pre, '\u\w*[! ] *\%(\%(++\S\+\|--\S\+-pathspecs\|-c\s\+\S\+\)\s\+\)*\zs[[:alnum:]][[:alnum:]-]*\ze ')
4152   if empty(subcmd) && a:lead =~# '^+'
4153     let results = ['++curwin']
4154   elseif empty(subcmd) && a:lead =~# '^-'
4155     let results = ['--literal-pathspecs', '--no-literal-pathspecs', '--glob-pathspecs', '--noglob-pathspecs', '--icase-pathspecs', '--no-optional-locks']
4156   elseif empty(subcmd)
4157     let results = s:CompletableSubcommands(dir)
4158   elseif a:0 ==# 2 && subcmd =~# '^\%(commit\|revert\|push\|fetch\|pull\|merge\|rebase\|bisect\)$'
4159     let cmdline = substitute(a:1, '\u\w*\([! ] *\)' . subcmd, 'G' . subcmd, '')
4160     let caps_subcmd = substitute(subcmd, '\%(^\|-\)\l', '\u&', 'g')
4161     return fugitive#{caps_subcmd}Complete(a:lead, cmdline, a:2 + len(cmdline) - len(a:1), dir, root)
4162   elseif pre =~# ' -- '
4163     return fugitive#CompletePath(a:lead, a:1, a:2, dir, root)
4164   elseif a:lead =~# '^-'
4165     let results = split(s:ChompDefault('', [dir, subcmd, '--git-completion-helper']), ' ')
4166   else
4167     return fugitive#CompleteObject(a:lead, a:1, a:2, dir, root)
4168   endif
4169   return filter(results, 'strpart(v:val, 0, strlen(a:lead)) ==# a:lead')
4170 endfunction
4172 function! fugitive#CompleteForWorkingDir(A, L, P, ...) abort
4173   let path = a:0 ? a:1 : getcwd()
4174   return fugitive#Complete(a:A, a:L, a:P, FugitiveExtractGitDir(path), path)
4175 endfunction
4177 " Section: :Gcd, :Glcd
4179 function! fugitive#CdComplete(A, L, P) abort
4180   return filter(fugitive#CompletePath(a:A), 'v:val =~# "/$"')
4181 endfunction
4183 function! fugitive#Cd(path, ...) abort
4184   exe s:VersionCheck()
4185   let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
4186   if path !~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
4187     let dir = s:Dir()
4188     exe s:DirCheck(dir)
4189     let path = (empty(s:Tree(dir)) ? dir : s:Tree(dir)) . '/' . path
4190   endif
4191   return (a:0 && a:1 ? 'lcd ' : 'cd ') . fnameescape(s:VimSlash(path))
4192 endfunction
4194 " Section: :Gstatus
4196 function! s:StatusCommand(line1, line2, range, count, bang, mods, reg, arg, args, ...) abort
4197   let dir = a:0 ? s:Dir(a:1) : s:Dir()
4198   exe s:DirCheck(dir)
4199   try
4200     let mods = s:Mods(a:mods, 'Edge')
4201     let file = fugitive#Find(':', dir)
4202     let arg = ' +setl\ foldmarker=<<<<<<<<,>>>>>>>>\|let\ w:fugitive_status=FugitiveGitDir() ' .
4203           \ s:fnameescape(file)
4204     for tabnr in [tabpagenr()] + (mods =~# '\<tab\>' ? range(1, tabpagenr('$')) : [])
4205       let bufs = tabpagebuflist(tabnr)
4206       for winnr in range(1, tabpagewinnr(tabnr, '$'))
4207         if s:cpath(file, fnamemodify(bufname(bufs[winnr-1]), ':p'))
4208           if tabnr == tabpagenr() && winnr == winnr()
4209             call s:ReloadStatus()
4210           else
4211             call s:ExpireStatus(dir)
4212             exe tabnr . 'tabnext'
4213             exe winnr . 'wincmd w'
4214           endif
4215           let w:fugitive_status = dir
4216           1
4217           return ''
4218         endif
4219       endfor
4220     endfor
4221     if a:count ==# 0
4222       return mods . 'edit' . (a:bang ? '!' : '') . arg
4223     elseif a:bang
4224       return mods . 'pedit' . arg . '|wincmd P'
4225     else
4226       return mods . 'keepalt split' . arg
4227     endif
4228   catch /^fugitive:/
4229     return 'echoerr ' . string(v:exception)
4230   endtry
4231   return ''
4232 endfunction
4234 function! s:StageJump(offset, section, ...) abort
4235   let line = search('^\%(' . a:section . '\)', 'nw')
4236   if !line && a:0
4237     let line = search('^\%(' . a:1 . '\)', 'nw')
4238   endif
4239   if line
4240     exe line
4241     if a:offset
4242       for i in range(a:offset)
4243         call search(s:file_commit_pattern . '\|^$', 'W')
4244         if empty(getline('.')) && a:0 && getline(line('.') + 1) =~# '^\%(' . a:1 . '\)'
4245           call search(s:file_commit_pattern . '\|^$', 'W')
4246         endif
4247         if empty(getline('.'))
4248           return ''
4249         endif
4250       endfor
4251       call s:StageReveal()
4252     else
4253       call s:StageReveal()
4254       +
4255     endif
4256   endif
4257   return ''
4258 endfunction
4260 function! s:StageSeek(info, fallback) abort
4261   let info = a:info
4262   if empty(info.heading)
4263     return a:fallback
4264   endif
4265   let line = search('^' . escape(info.heading, '^$.*[]~\') . ' (\d\++\=)$', 'wn')
4266   if !line
4267     for section in get({'Staged': ['Unstaged', 'Untracked'], 'Unstaged': ['Untracked', 'Staged'], 'Untracked': ['Unstaged', 'Staged']}, info.section, [])
4268       let line = search('^' . section, 'wn')
4269       if line
4270         return line + (info.index > 0 ? 1 : 0)
4271       endif
4272     endfor
4273     return 1
4274   endif
4275   let i = 0
4276   while len(getline(line))
4277     let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
4278     if len(filename) &&
4279           \ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
4280           \ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
4281           \ filename ==# info.filename)
4282       if info.offset < 0
4283         return line
4284       else
4285         if getline(line+1) !~# '^@'
4286           exe s:StageInline('show', line)
4287         endif
4288         if getline(line+1) !~# '^@'
4289           return line
4290         endif
4291         let type = info.sigil ==# '-' ? '-' : '+'
4292         let offset = -1
4293         while offset < info.offset
4294           let line += 1
4295           if getline(line) =~# '^@'
4296             let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
4297           elseif getline(line) =~# '^[ ' . type . ']'
4298             let offset += 1
4299           elseif getline(line) !~# '^[ @\+-]'
4300             return line - 1
4301           endif
4302         endwhile
4303         return line
4304       endif
4305     endif
4306     let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
4307     if len(commit) && commit ==# info.commit
4308       return line
4309     endif
4310     if i ==# info.index
4311       let backup = line
4312     endif
4313     let i += getline(line) !~# '^[ @\+-]'
4314     let line += 1
4315   endwhile
4316   return exists('backup') ? backup : line - 1
4317 endfunction
4319 function! s:DoAutocmdChanged(dir) abort
4320   let dir = a:dir is# -2 ? '' : FugitiveGitDir(a:dir)
4321   if empty(dir) || !exists('#User#FugitiveChanged') || exists('g:fugitive_event')
4322     return ''
4323   endif
4324   try
4325     let g:fugitive_event = dir
4326     if type(a:dir) == type({}) && has_key(a:dir, 'args') && has_key(a:dir, 'exit_status')
4327       let g:fugitive_result = a:dir
4328     endif
4329     exe s:DoAutocmd('User FugitiveChanged')
4330   finally
4331     unlet! g:fugitive_event g:fugitive_result
4332     " Force statusline reload with the buffer's Git dir
4333     if dir isnot# FugitiveGitDir()
4334       let &l:ro = &l:ro
4335     endif
4336   endtry
4337   return ''
4338 endfunction
4340 function! s:ReloadStatusBuffer() abort
4341   if get(b:, 'fugitive_type', '') !=# 'index' || !empty(get(b:, 'fugitive_loading'))
4342     return ''
4343   endif
4344   let original_lnum = line('.')
4345   let info = s:StageInfo(original_lnum)
4346   exe fugitive#BufReadStatus(0)
4347   call setpos('.', [0, s:StageSeek(info, original_lnum), 1, 0])
4348   return ''
4349 endfunction
4351 function! s:ReloadStatus() abort
4352   call s:ExpireStatus(-1)
4353   call s:ReloadStatusBuffer()
4354   exe s:DoAutocmdChanged(-1)
4355   return ''
4356 endfunction
4358 let s:last_time = reltime()
4359 if !exists('s:last_times')
4360   let s:last_times = {}
4361 endif
4363 function! s:ExpireStatus(bufnr) abort
4364   if a:bufnr is# -2 || a:bufnr is# 0
4365     let s:head_cache = {}
4366     let s:last_time = reltime()
4367     return ''
4368   endif
4369   let head_file = fugitive#Find('.git/HEAD', a:bufnr)
4370   if !empty(head_file)
4371     let s:last_times[s:Tree(a:bufnr) . '/'] = reltime()
4372     if has_key(s:head_cache, head_file)
4373       call remove(s:head_cache, head_file)
4374     endif
4375   endif
4376   return ''
4377 endfunction
4379 function! s:ReloadWinStatus(...) abort
4380   if get(b:, 'fugitive_type', '') !=# 'index' || !empty(get(b:, 'fugitive_loading')) || &modified
4381     return
4382   endif
4383   if !exists('b:fugitive_status.reltime')
4384     exe call('s:ReloadStatusBuffer', a:000)
4385     return
4386   endif
4387   let t = b:fugitive_status.reltime
4388   if reltimestr(reltime(s:last_time, t)) =~# '-\|\d\{10\}\.' ||
4389         \ reltimestr(reltime(get(s:last_times, s:Tree() . '/', t), t)) =~# '-\|\d\{10\}\.'
4390     exe call('s:ReloadStatusBuffer', a:000)
4391   endif
4392 endfunction
4394 function! s:ReloadTabStatus() abort
4395   if !exists('g:fugitive_did_change_at')
4396     return
4397   elseif exists('t:fugitive_reloaded_at')
4398     let time_ahead = reltime(g:fugitive_did_change_at, t:fugitive_reloaded_at)
4399     if reltimefloat(time_ahead) >= 0
4400       return
4401     endif
4402   endif
4403   let t:fugitive_reloaded_at = reltime()
4404   let winnr = 1
4405   while winnr <= winnr('$')
4406     if getbufvar(winbufnr(winnr), 'fugitive_type') ==# 'index'
4407       if winnr != winnr()
4408         execute 'noautocmd' winnr.'wincmd w'
4409         let restorewinnr = 1
4410       endif
4411       try
4412         call s:ReloadWinStatus()
4413       finally
4414         if exists('restorewinnr')
4415           unlet restorewinnr
4416           noautocmd wincmd p
4417         endif
4418       endtry
4419     endif
4420     let winnr += 1
4421   endwhile
4422 endfunction
4424 function! fugitive#DidChange(...) abort
4425   call s:ExpireStatus(a:0 ? a:1 : -1)
4426   if a:0 > 1 ? a:2 : (!a:0 || a:1 isnot# 0)
4427     let g:fugitive_did_change_at = reltime()
4428     call s:ReloadTabStatus()
4429   else
4430     call s:ReloadWinStatus()
4431     return ''
4432   endif
4433   exe s:DoAutocmdChanged(a:0 ? a:1 : -1)
4434   return ''
4435 endfunction
4437 function! fugitive#ReloadStatus(...) abort
4438   return call('fugitive#DidChange', a:000)
4439 endfunction
4441 function! fugitive#EfmDir(...) abort
4442   let dir = matchstr(a:0 ? a:1 : &errorformat, '\c,%\\&\%(git\|fugitive\)_\=dir=\zs\%(\\.\|[^,]\)*')
4443   let dir = substitute(dir, '%%', '%', 'g')
4444   let dir = substitute(dir, '\\\ze[\,]', '', 'g')
4445   return dir
4446 endfunction
4448 augroup fugitive_status
4449   autocmd!
4450   autocmd BufWritePost         * call fugitive#DidChange(+expand('<abuf>'), 0)
4451   autocmd User FileChmodPost,FileUnlinkPost call fugitive#DidChange(+expand('<abuf>'), 0)
4452   autocmd ShellCmdPost,ShellFilterPost * nested call fugitive#DidChange(0)
4453   autocmd BufDelete * nested
4454         \ if getbufvar(+expand('<abuf>'), 'buftype') ==# 'terminal' |
4455         \   if !empty(FugitiveGitDir(+expand('<abuf>'))) |
4456         \     call fugitive#DidChange(+expand('<abuf>')) |
4457         \   else |
4458         \     call fugitive#DidChange(0) |
4459         \  endif |
4460         \ endif
4461   autocmd QuickFixCmdPost make,lmake,[cl]file,[cl]getfile nested
4462         \ call fugitive#DidChange(fugitive#EfmDir())
4463   autocmd FocusGained        *
4464         \ if get(g:, 'fugitive_focus_gained', !has('win32')) |
4465         \   call fugitive#DidChange(0) |
4466         \ endif
4467   autocmd BufEnter index,index.lock,fugitive://*//
4468         \ call s:ReloadWinStatus()
4469   autocmd TabEnter *
4470         \ call s:ReloadTabStatus()
4471 augroup END
4473 function! s:StatusSectionFile(heading, filename) abort
4474   return get(get(get(get(b:, 'fugitive_status', {}), 'files', {}), a:heading, {}), a:filename, {})
4475 endfunction
4477 function! s:StageInfo(...) abort
4478   let lnum = a:0 ? a:1 : line('.')
4479   let sigil = matchstr(getline(lnum), '^[ @\+-]')
4480   let offset = -1
4481   if len(sigil)
4482     let [lnum, old_lnum, new_lnum] = s:HunkPosition(lnum)
4483     let offset = sigil ==# '-' ? old_lnum : new_lnum
4484     while getline(lnum) =~# '^[ @\+-]'
4485       let lnum -= 1
4486     endwhile
4487   endif
4488   let slnum = lnum + 1
4489   let heading = ''
4490   let index = 0
4491   while len(getline(slnum - 1)) && empty(heading)
4492     let slnum -= 1
4493     let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
4494     if empty(heading) && getline(slnum) !~# '^[ @\+-]'
4495       let index += 1
4496     endif
4497   endwhile
4498   let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
4499   let file = s:StatusSectionFile(heading, text)
4500   let relative = get(file, 'relative', len(text) ? [text] : [])
4501   return {'section': matchstr(heading, '^\u\l\+'),
4502         \ 'heading': heading,
4503         \ 'sigil': sigil,
4504         \ 'offset': offset,
4505         \ 'filename': text,
4506         \ 'relative': copy(relative),
4507         \ 'paths': map(copy(relative), 's:Tree() . "/" . v:val'),
4508         \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
4509         \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
4510         \ 'submodule': get(file, 'submodule', ''),
4511         \ 'index': index}
4512 endfunction
4514 function! s:Selection(arg1, ...) abort
4515   if a:arg1 ==# 'n'
4516     let arg1 = line('.')
4517     let arg2 = -v:count
4518   elseif a:arg1 ==# 'v'
4519     let arg1 = line("'<")
4520     let arg2 = line("'>")
4521   else
4522     let arg1 = a:arg1
4523     let arg2 = a:0 ? a:1 : 0
4524   endif
4525   let first = arg1
4526   if arg2 < 0
4527     let last = first - arg2 - 1
4528   elseif arg2 > 0
4529     let last = arg2
4530   else
4531     let last = first
4532   endif
4533   while first <= line('$') && getline(first) =~# '^$\|^[A-Z][a-z]'
4534     let first += 1
4535   endwhile
4536   if first > last || &filetype !=# 'fugitive'
4537     return []
4538   endif
4539   let flnum = first
4540   while getline(flnum) =~# '^[ @\+-]'
4541     let flnum -= 1
4542   endwhile
4543   let slnum = flnum + 1
4544   let heading = ''
4545   let index = 0
4546   while empty(heading)
4547     let slnum -= 1
4548     let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
4549     if empty(heading) && getline(slnum) !~# '^[ @\+-]'
4550       let index += 1
4551     endif
4552   endwhile
4553   let results = []
4554   let template = {
4555         \ 'heading': heading,
4556         \ 'section': matchstr(heading, '^\u\l\+'),
4557         \ 'filename': '',
4558         \ 'relative': [],
4559         \ 'paths': [],
4560         \ 'commit': '',
4561         \ 'status': '',
4562         \ 'patch': 0,
4563         \ 'index': index}
4564   let line = getline(flnum)
4565   let lnum = first - (arg1 == flnum ? 0 : 1)
4566   let root = s:Tree() . '/'
4567   while lnum <= last
4568     let heading = matchstr(line, '^\u\l\+\ze.\{-\}\ze (\d\++\=)$')
4569     if len(heading)
4570       let template.heading = heading
4571       let template.section = matchstr(heading, '^\u\l\+')
4572       let template.index = 0
4573     elseif line =~# '^[ @\+-]'
4574       let template.index -= 1
4575       if !results[-1].patch
4576         let results[-1].patch = lnum
4577       endif
4578       let results[-1].lnum = lnum
4579     elseif line =~# '^[A-Z?] '
4580       let text = matchstr(line, '^[A-Z?] \zs.*')
4581       let file = s:StatusSectionFile(template.heading, text)
4582       let relative = get(file, 'relative', len(text) ? [text] : [])
4583       call add(results, extend(deepcopy(template), {
4584             \ 'lnum': lnum,
4585             \ 'filename': text,
4586             \ 'relative': copy(relative),
4587             \ 'paths': map(copy(relative), 'root . v:val'),
4588             \ 'status': matchstr(line, '^[A-Z?]'),
4589             \ }))
4590     elseif line =~# '^\x\x\x\+ '
4591       call add(results, extend({
4592             \ 'lnum': lnum,
4593             \ 'commit': matchstr(line, '^\x\x\x\+'),
4594             \ }, template, 'keep'))
4595     elseif line =~# '^\l\+ \x\x\x\+ '
4596       call add(results, extend({
4597             \ 'lnum': lnum,
4598             \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
4599             \ 'status': matchstr(line, '^\l\+'),
4600             \ }, template, 'keep'))
4601     endif
4602     let lnum += 1
4603     let template.index += 1
4604     let line = getline(lnum)
4605   endwhile
4606   if len(results) && results[0].patch && arg2 == 0
4607     while getline(results[0].patch) =~# '^[ \+-]'
4608       let results[0].patch -= 1
4609     endwhile
4610     while getline(results[0].lnum + 1) =~# '^[ \+-]'
4611       let results[0].lnum += 1
4612     endwhile
4613   endif
4614   return results
4615 endfunction
4617 function! s:StageArgs(visual) abort
4618   let commits = []
4619   let paths = []
4620   for record in s:Selection(a:visual ? 'v' : 'n')
4621     if len(record.commit)
4622       call add(commits, record.commit)
4623     endif
4624     call extend(paths, record.paths)
4625   endfor
4626   if s:cpath(s:Tree(), getcwd())
4627     call map(paths, 'fugitive#Path(v:val, "./")')
4628   endif
4629   return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
4630 endfunction
4632 function! s:Do(action, visual) abort
4633   let line = getline('.')
4634   let reload = 0
4635   if !a:visual && !v:count && line =~# '^[A-Z][a-z]'
4636     let header = matchstr(line, '^\S\+\ze:')
4637     if len(header) && exists('*s:Do' . a:action . header . 'Header')
4638       let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
4639     else
4640       let section = matchstr(line, '^\S\+')
4641       if exists('*s:Do' . a:action . section . 'Heading')
4642         let reload = s:Do{a:action}{section}Heading(line) > 0
4643       endif
4644     endif
4645     return reload ? s:ReloadStatus() : ''
4646   endif
4647   let selection = s:Selection(a:visual ? 'v' : 'n')
4648   if empty(selection)
4649     return ''
4650   endif
4651   call filter(selection, 'v:val.section ==# selection[0].section')
4652   let status = 0
4653   let err = ''
4654   try
4655     for record in selection
4656       if exists('*s:Do' . a:action . record.section)
4657         let status = s:Do{a:action}{record.section}(record)
4658       else
4659         continue
4660       endif
4661       if !status
4662         return ''
4663       endif
4664       let reload = reload || (status > 0)
4665     endfor
4666     if status < 0
4667       execute record.lnum + 1
4668     endif
4669     let success = 1
4670   catch /^fugitive:/
4671     return 'echoerr ' . string(v:exception)
4672   finally
4673     if reload
4674       execute s:ReloadStatus()
4675     endif
4676     if exists('success')
4677       call s:StageReveal()
4678     endif
4679   endtry
4680   return ''
4681 endfunction
4683 function! s:StageReveal() abort
4684   exe 'normal! zv'
4685   let begin = line('.')
4686   if getline(begin) =~# '^@'
4687     let end = begin + 1
4688     while getline(end) =~# '^[ \+-]'
4689       let end += 1
4690     endwhile
4691   elseif getline(begin) =~# '^commit '
4692     let end = begin
4693     while end < line('$') && getline(end + 1) !~# '^commit '
4694       let end += 1
4695     endwhile
4696   elseif getline(begin) =~# s:section_pattern
4697     let end = begin
4698     while len(getline(end + 1))
4699       let end += 1
4700     endwhile
4701   endif
4702   if exists('end')
4703     while line('.') > line('w0') + &scrolloff && end > line('w$')
4704       execute "normal! \<C-E>"
4705     endwhile
4706   endif
4707 endfunction
4709 let s:file_pattern = '^[A-Z?] .\|^diff --'
4710 let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
4711 let s:item_pattern = s:file_commit_pattern . '\|^@@'
4713 function! s:NextHunk(count) abort
4714   if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
4715     exe s:StageInline('show')
4716   endif
4717   for i in range(a:count)
4718     if &filetype ==# 'fugitive'
4719       call search(s:file_pattern . '\|^@', 'W')
4720       if getline('.') =~# s:file_pattern
4721         exe s:StageInline('show')
4722         if getline(line('.') + 1) =~# '^@'
4723           +
4724         endif
4725       endif
4726     else
4727       call search('^@@', 'W')
4728     endif
4729   endfor
4730   call s:StageReveal()
4731   return '.'
4732 endfunction
4734 function! s:PreviousHunk(count) abort
4735   normal! 0
4736   for i in range(a:count)
4737     if &filetype ==# 'fugitive'
4738       if getline('.') =~# '^@' && getline(line('.') - 1) =~# s:file_pattern
4739         -
4740       endif
4741       let lnum = search(s:file_pattern . '\|^@','Wbn')
4742       call s:StageInline('show', lnum)
4743       call search('^? .\|^@','Wb')
4744     else
4745       call search('^@@', 'Wb')
4746     endif
4747   endfor
4748   call s:StageReveal()
4749   return '.'
4750 endfunction
4752 function! s:NextFile(count) abort
4753   for i in range(a:count)
4754     exe s:StageInline('hide')
4755     if !search(s:file_pattern, 'W')
4756       break
4757     endif
4758   endfor
4759   exe s:StageInline('hide')
4760   return '.'
4761 endfunction
4763 function! s:PreviousFile(count) abort
4764   exe s:StageInline('hide')
4765   normal! 0
4766   for i in range(a:count)
4767     if !search(s:file_pattern, 'Wb')
4768       break
4769     endif
4770     exe s:StageInline('hide')
4771   endfor
4772   return '.'
4773 endfunction
4775 function! s:NextItem(count) abort
4776   for i in range(a:count)
4777     if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
4778       call search('^commit ', 'W')
4779     endif
4780   endfor
4781   call s:StageReveal()
4782   return '.'
4783 endfunction
4785 function! s:PreviousItem(count) abort
4786   normal! 0
4787   for i in range(a:count)
4788     if !search(s:item_pattern, 'Wb') && getline('.') !~# s:item_pattern
4789       call search('^commit ', 'Wb')
4790     endif
4791   endfor
4792   call s:StageReveal()
4793   return '.'
4794 endfunction
4796 let s:section_pattern = '^[A-Z][a-z][^:]*$'
4797 let s:section_commit_pattern = s:section_pattern . '\|^commit '
4799 function! s:NextSection(count) abort
4800   let orig = line('.')
4801   if getline('.') !~# '^commit '
4802     -
4803   endif
4804   for i in range(a:count)
4805     if !search(s:section_commit_pattern, 'W')
4806       break
4807     endif
4808   endfor
4809   if getline('.') =~# s:section_commit_pattern
4810     call s:StageReveal()
4811     return getline('.') =~# s:section_pattern ? '+' : ':'
4812   else
4813     return orig
4814   endif
4815 endfunction
4817 function! s:PreviousSection(count) abort
4818   let orig = line('.')
4819   if getline('.') !~# '^commit '
4820     -
4821   endif
4822   normal! 0
4823   for i in range(a:count)
4824     if !search(s:section_commit_pattern . '\|\%^', 'bW')
4825       break
4826     endif
4827   endfor
4828   if getline('.') =~# s:section_commit_pattern || line('.') == 1
4829     call s:StageReveal()
4830     return getline('.') =~# s:section_pattern ? '+' : ':'
4831   else
4832     return orig
4833   endif
4834 endfunction
4836 function! s:NextSectionEnd(count) abort
4837   +
4838   if empty(getline('.'))
4839     +
4840   endif
4841   for i in range(a:count)
4842     if !search(s:section_commit_pattern, 'W')
4843       return '$'
4844     endif
4845   endfor
4846   return search('^.', 'Wb')
4847 endfunction
4849 function! s:PreviousSectionEnd(count) abort
4850   let old = line('.')
4851   for i in range(a:count)
4852     if search(s:section_commit_pattern, 'Wb') <= 1
4853       exe old
4854       if i
4855         break
4856       else
4857         return ''
4858       endif
4859     endif
4860     let old = line('.')
4861   endfor
4862   return search('^.', 'Wb')
4863 endfunction
4865 function! s:PatchSearchExpr(reverse) abort
4866   let line = getline('.')
4867   if col('.') ==# 1 && line =~# '^[+-]'
4868     if line =~# '^[+-]\{3\} '
4869       let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
4870     else
4871       let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
4872     endif
4873     if a:reverse
4874       return '?' . escape(pattern, '/?') . "\<CR>"
4875     else
4876       return '/' . escape(pattern, '/') . "\<CR>"
4877     endif
4878   endif
4879   return a:reverse ? '#' : '*'
4880 endfunction
4882 function! s:StageInlineGetDiff(diff_section, info) abort
4883   let diff = []
4884   if a:info.status ==# 'U'
4885     let diff_header = 'diff --cc ' . s:Quote(a:info.relative[0])
4886   else
4887     let diff_header = 'diff --git ' . s:Quote(a:info.relative[-1]) . ' ' . s:Quote(a:info.relative[0])
4888   endif
4889   let stdout = fugitive#Wait(a:diff_section).stdout
4890   let start = index(stdout, diff_header)
4891   if start == -1
4892     return [[], -1]
4893   endif
4894   let index = start + 1
4895   while get(stdout, index, '@@') !~# '^@@\|^diff '
4896     let index += 1
4897   endwhile
4898   while get(stdout, index, '') =~# '^[@ \+-]'
4899     call add(diff, stdout[index])
4900     let index += 1
4901   endwhile
4902   return [diff, start]
4903 endfunction
4905 function! s:StageInline(mode, ...) abort
4906   if &filetype !=# 'fugitive'
4907     return ''
4908   endif
4909   let lnum1 = a:0 ? a:1 : line('.')
4910   let lnum = lnum1 + 1
4911   if a:0 > 1 && a:2 == 0 && lnum1 == 1
4912     let lnum = line('$') - 1
4913   elseif a:0 > 1 && a:2 == 0
4914     let info = s:StageInfo(lnum - 1)
4915     if empty(info.paths) && len(info.section)
4916       while len(getline(lnum))
4917         let lnum += 1
4918       endwhile
4919     endif
4920   elseif a:0 > 1
4921     let lnum += a:2 - 1
4922   endif
4923   while lnum > lnum1
4924     let lnum -= 1
4925     while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
4926       let lnum -= 1
4927     endwhile
4928     let info = s:StageInfo(lnum)
4929     let diff_section = get(get(get(b:, 'fugitive_status', {}), 'diff', {}), info.section, {})
4930     if empty(diff_section)
4931       continue
4932     endif
4933     if getline(lnum + 1) =~# '^[ @\+-]'
4934       let lnum2 = lnum + 1
4935       while getline(lnum2 + 1) =~# '^[ @\+-]'
4936         let lnum2 += 1
4937       endwhile
4938       if a:mode !=# 'show'
4939         setlocal modifiable noreadonly
4940         exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
4941         call remove(b:fugitive_expanded[info.section], info.filename)
4942         setlocal nomodifiable readonly nomodified
4943       endif
4944       continue
4945     endif
4946     if info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
4947       continue
4948     endif
4949     let [diff, start] = s:StageInlineGetDiff(diff_section, info)
4950     if len(diff)
4951       setlocal modifiable noreadonly
4952       silent call append(lnum, diff)
4953       let b:fugitive_expanded[info.section][info.filename] = [start]
4954       setlocal nomodifiable readonly nomodified
4955       if foldclosed(lnum+1) > 0
4956         silent exe (lnum+1) . ',' . (lnum+len(diff)) . 'foldopen!'
4957       endif
4958     endif
4959   endwhile
4960   return lnum
4961 endfunction
4963 function! s:NextExpandedHunk(count) abort
4964   for i in range(a:count)
4965     call s:StageInline('show', line('.'), 1)
4966     call search(s:file_pattern . '\|^@','W')
4967   endfor
4968   return '.'
4969 endfunction
4971 function! s:StageDiff(diff) abort
4972   let lnum = line('.')
4973   let info = s:StageInfo(lnum)
4974   let prefix = info.offset > 0 ? '+' . info.offset : ''
4975   if info.submodule =~# '^S'
4976     if info.section ==# 'Staged'
4977       return 'Git --paginate diff --no-ext-diff --submodule=log --cached -- ' . info.paths[0]
4978     elseif info.submodule =~# '^SC'
4979       return 'Git --paginate diff --no-ext-diff --submodule=log -- ' . info.paths[0]
4980     else
4981       return 'Git --paginate diff --no-ext-diff --submodule=diff -- ' . info.paths[0]
4982     endif
4983   elseif empty(info.paths) && info.section ==# 'Staged'
4984     return 'Git --paginate diff --no-ext-diff --cached'
4985   elseif empty(info.paths)
4986     return 'Git --paginate diff --no-ext-diff'
4987   elseif len(info.paths) > 1
4988     execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
4989     return a:diff . '! @:'.s:fnameescape(info.paths[1])
4990   elseif info.section ==# 'Staged' && info.sigil ==# '-'
4991     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4992     return a:diff . '! :0:%'
4993   elseif info.section ==# 'Staged'
4994     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4995     return a:diff . '! @:%'
4996   elseif info.sigil ==# '-'
4997     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4998     return a:diff . '! :(top)%'
4999   else
5000     execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
5001     return a:diff . '!'
5002   endif
5003 endfunction
5005 function! s:StageDiffEdit() abort
5006   let info = s:StageInfo(line('.'))
5007   let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
5008   if info.section ==# 'Staged'
5009     return 'Git --paginate diff --no-ext-diff --cached '.s:fnameescape(arg)
5010   elseif info.status ==# '?'
5011     call s:TreeChomp('add', '--intent-to-add', '--', arg)
5012     return s:ReloadStatus()
5013   else
5014     return 'Git --paginate diff --no-ext-diff '.s:fnameescape(arg)
5015   endif
5016 endfunction
5018 function! s:StageApply(info, reverse, extra) abort
5019   if a:info.status ==# 'R'
5020     throw 'fugitive: patching renamed file not yet supported'
5021   endif
5022   let cmd = ['apply', '-p0', '--recount'] + a:extra
5023   let info = a:info
5024   let start = info.patch
5025   let end = info.lnum
5026   let lines = getline(start, end)
5027   if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
5028     return -1
5029   endif
5030   while getline(end) =~# '^[-+\ ]'
5031     let end += 1
5032     if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . '\ ]'
5033       call add(lines, ' ' . getline(end)[1:-1])
5034     endif
5035   endwhile
5036   while start > 0 && getline(start) !~# '^@'
5037     let start -= 1
5038     if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
5039       call insert(lines, ' ' . getline(start)[1:-1])
5040     elseif getline(start) =~# '^@'
5041       call insert(lines, getline(start))
5042     endif
5043   endwhile
5044   if start == 0
5045     throw 'fugitive: could not find hunk'
5046   elseif getline(start) !~# '^@@ '
5047     throw 'fugitive: cannot apply conflict hunk'
5048   endif
5049   let i = b:fugitive_expanded[info.section][info.filename][0]
5050   let head = []
5051   let diff_lines = fugitive#Wait(b:fugitive_status.diff[info.section]).stdout
5052   while get(diff_lines, i, '@') !~# '^@'
5053     let line = diff_lines[i]
5054     if line ==# '--- /dev/null'
5055       call add(head, '--- ' . get(diff_lines, i + 1, '')[4:-1])
5056     elseif line !~# '^new file '
5057       call add(head, line)
5058     endif
5059     let i += 1
5060   endwhile
5061   call extend(lines, head, 'keep')
5062   let temp = tempname()
5063   call writefile(lines, temp)
5064   if a:reverse
5065     call add(cmd, '--reverse')
5066   endif
5067   call extend(cmd, ['--', temp])
5068   let output = s:ChompStderr(cmd)
5069   if empty(output)
5070     return 1
5071   endif
5072   call s:throw(output)
5073 endfunction
5075 function! s:StageDelete(lnum1, lnum2, count) abort
5076   let restore = []
5078   let err = ''
5079   let did_conflict_err = 0
5080   let reset_commit = matchstr(getline(a:lnum1), '^Un\w\+ \%(to\| from\) \zs\S\+')
5081   try
5082     for info in s:Selection(a:lnum1, a:lnum2)
5083       if empty(info.paths)
5084         if len(info.commit)
5085           let reset_commit = info.commit . '^'
5086         endif
5087         continue
5088       endif
5089       let sub = get(s:StatusSectionFile(info.section, info.filename), 'submodule', '')
5090       if sub =~# '^S' && info.status ==# 'M'
5091         let undo = 'Git checkout ' . fugitive#RevParse('HEAD', FugitiveExtractGitDir(info.paths[0]))[0:10] . ' --'
5092       elseif sub =~# '^S'
5093         let err .= '|echoerr ' . string('fugitive: will not touch submodule ' . string(info.relative[0]))
5094         break
5095       elseif info.status ==# 'D'
5096         let undo = 'GRemove'
5097       elseif info.paths[0] =~# '/$'
5098         let err .= '|echoerr ' . string('fugitive: will not delete directory ' . string(info.relative[0]))
5099         break
5100       else
5101         let undo = 'Gread ' . s:TreeChomp('hash-object', '-w', '--', info.paths[0])[0:10]
5102       endif
5103       if info.patch
5104         call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
5105       elseif sub =~# '^S'
5106         if info.section ==# 'Staged'
5107           call s:TreeChomp('reset', '--', info.paths[0])
5108         endif
5109         call s:TreeChomp('submodule', 'update', '--', info.paths[0])
5110       elseif info.status ==# '?'
5111         call s:TreeChomp('clean', '-f', '--', info.paths[0])
5112       elseif a:count == 2
5113         if get(s:StatusSectionFile('Staged', info.filename), 'status', '') ==# 'D'
5114           call delete(info.paths[0])
5115         else
5116           call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
5117         endif
5118       elseif a:count == 3
5119         if get(s:StatusSectionFile('Unstaged', info.filename), 'status', '') ==# 'D'
5120           call delete(info.paths[0])
5121         else
5122           call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
5123         endif
5124       elseif info.status =~# '[ADU]' &&
5125             \ get(s:StatusSectionFile(info.section ==# 'Staged' ? 'Unstaged' : 'Staged', info.filename), 'status', '') =~# '[AU]'
5126         if get(g:, 'fugitive_conflict_x', 0)
5127           call s:TreeChomp('checkout', info.section ==# 'Unstaged' ? '--ours' : '--theirs', '--', info.paths[0])
5128         else
5129           if !did_conflict_err
5130             let err .= '|echoerr "Use 2X for --ours or 3X for --theirs"'
5131             let did_conflict_err = 1
5132           endif
5133           continue
5134         endif
5135       elseif info.status ==# 'U'
5136         call delete(info.paths[0])
5137       elseif info.status ==# 'A'
5138         call s:TreeChomp('rm', '-f', '--', info.paths[0])
5139       elseif info.section ==# 'Unstaged'
5140         call s:TreeChomp('checkout', '--', info.paths[0])
5141       else
5142         call s:TreeChomp('checkout', '@', '--', info.paths[0])
5143       endif
5144       if len(undo)
5145         call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|' . undo)
5146       endif
5147     endfor
5148   catch /^fugitive:/
5149     let err .= '|echoerr ' . string(v:exception)
5150   endtry
5151   if empty(restore)
5152     if len(reset_commit) && empty(err)
5153       call feedkeys(':Git reset ' . reset_commit)
5154     endif
5155     return err[1:-1]
5156   endif
5157   exe s:ReloadStatus()
5158   call s:StageReveal()
5159   return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
5160 endfunction
5162 function! s:StageIgnore(lnum1, lnum2, count) abort
5163   let paths = []
5164   for info in s:Selection(a:lnum1, a:lnum2)
5165     call extend(paths, info.relative)
5166   endfor
5167   call map(paths, '"/" . v:val')
5168   if !a:0
5169     let dir = fugitive#Find('.git/info/')
5170     if !isdirectory(dir)
5171       try
5172         call mkdir(dir)
5173       catch
5174       endtry
5175     endif
5176   endif
5177   exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
5178   let last = line('$')
5179   if last == 1 && empty(getline(1))
5180     call setline(last, paths)
5181   else
5182     call append(last, paths)
5183     exe last + 1
5184   endif
5185   return ''
5186 endfunction
5188 function! s:DoToggleHeadHeader(value) abort
5189   exe 'edit' fnameescape(fugitive#Find('.git/'))
5190   call search('\C^index$', 'wc')
5191 endfunction
5193 function! s:DoToggleHelpHeader(value) abort
5194   exe 'help fugitive-map'
5195 endfunction
5197 function! s:DoStagePushHeader(value) abort
5198   let stat = get(b:, 'fugitive_status', {})
5199   let remote = get(stat, 'push_remote', '')
5200   let branch = substitute(get(stat, 'push', ''), '^ref/heads/', '', '')
5201   if empty(remote) || empty(branch)
5202     return
5203   endif
5204   call feedkeys(':Git push ' . remote . ' ' . branch)
5205 endfunction
5207 function! s:DoTogglePushHeader(value) abort
5208   return s:DoStagePushHeader(a:value)
5209 endfunction
5211 function! s:DoStageUnpushedHeading(heading) abort
5212   let stat = get(b:, 'fugitive_status', {})
5213   let remote = get(stat, 'push_remote', '')
5214   let push = get(stat, 'push', '')
5215   if empty(remote) || empty(push)
5216     return
5217   endif
5218   call feedkeys(':Git push ' . remote . ' ' . '@:' . push)
5219 endfunction
5221 function! s:DoToggleUnpushedHeading(heading) abort
5222   return s:DoStageUnpushedHeading(a:heading)
5223 endfunction
5225 function! s:DoStageUnpushed(record) abort
5226   let stat = get(b:, 'fugitive_status', {})
5227   let remote = get(stat, 'push_remote', '')
5228   let push = get(stat, 'push', '')
5229   if empty(remote) || empty(push)
5230     return
5231   endif
5232   call feedkeys(':Git push ' . remote . ' ' . a:record.commit . ':' . push)
5233 endfunction
5235 function! s:DoToggleUnpushed(record) abort
5236   return s:DoStageUnpushed(a:record)
5237 endfunction
5239 function! s:DoUnstageUnpulledHeading(heading) abort
5240   call feedkeys(':Git rebase')
5241 endfunction
5243 function! s:DoToggleUnpulledHeading(heading) abort
5244   call s:DoUnstageUnpulledHeading(a:heading)
5245 endfunction
5247 function! s:DoUnstageUnpulled(record) abort
5248   call feedkeys(':Git rebase ' . a:record.commit)
5249 endfunction
5251 function! s:DoToggleUnpulled(record) abort
5252   call s:DoUnstageUnpulled(a:record)
5253 endfunction
5255 function! s:DoUnstageUnpushed(record) abort
5256   call feedkeys(':Git -c sequence.editor=true rebase --interactive --autosquash ' . a:record.commit . '^')
5257 endfunction
5259 function! s:DoToggleStagedHeading(...) abort
5260   call s:TreeChomp('reset', '-q')
5261   return 1
5262 endfunction
5264 function! s:DoUnstageStagedHeading(heading) abort
5265   return s:DoToggleStagedHeading(a:heading)
5266 endfunction
5268 function! s:DoToggleUnstagedHeading(...) abort
5269   call s:TreeChomp('add', '-u')
5270   return 1
5271 endfunction
5273 function! s:DoStageUnstagedHeading(heading) abort
5274   return s:DoToggleUnstagedHeading(a:heading)
5275 endfunction
5277 function! s:DoToggleUntrackedHeading(...) abort
5278   call s:TreeChomp('add', '.')
5279   return 1
5280 endfunction
5282 function! s:DoStageUntrackedHeading(heading) abort
5283   return s:DoToggleUntrackedHeading(a:heading)
5284 endfunction
5286 function! s:DoToggleStaged(record) abort
5287   if a:record.patch
5288     return s:StageApply(a:record, 1, ['--cached'])
5289   else
5290     call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
5291     return 1
5292   endif
5293 endfunction
5295 function! s:DoUnstageStaged(record) abort
5296   return s:DoToggleStaged(a:record)
5297 endfunction
5299 function! s:DoToggleUnstaged(record) abort
5300   if a:record.patch
5301     return s:StageApply(a:record, 0, ['--cached'])
5302   else
5303     call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
5304     return 1
5305   endif
5306 endfunction
5308 function! s:DoStageUnstaged(record) abort
5309   return s:DoToggleUnstaged(a:record)
5310 endfunction
5312 function! s:DoUnstageUnstaged(record) abort
5313   if a:record.status ==# 'A'
5314     call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
5315     return 1
5316   else
5317     return -1
5318   endif
5319 endfunction
5321 function! s:DoToggleUntracked(record) abort
5322   call s:TreeChomp(['add', '--'] + a:record.paths)
5323   return 1
5324 endfunction
5326 function! s:DoStageUntracked(record) abort
5327   return s:DoToggleUntracked(a:record)
5328 endfunction
5330 function! s:StagePatch(lnum1, lnum2, ...) abort
5331   let add = []
5332   let reset = []
5333   let intend = []
5334   let patch_only = a:0 && a:1
5336   for lnum in range(a:lnum1,a:lnum2)
5337     let info = s:StageInfo(lnum)
5338     if empty(info.paths) && info.section ==# 'Staged'
5339       execute 'tab Git reset --patch'
5340       break
5341     elseif empty(info.paths) && info.section ==# 'Unstaged'
5342       execute 'tab Git add --patch'
5343       break
5344     elseif empty(info.paths) && info.section ==# 'Untracked'
5345       execute 'tab Git add --interactive'
5346       break
5347     elseif !patch_only && info.section ==# 'Unpushed'
5348       if empty(info.commit)
5349         call s:DoStageUnpushedHeading(info)
5350       else
5351         call s:DoStageUnpushed(info)
5352       endif
5353       return ''
5354     elseif empty(info.paths)
5355       continue
5356     endif
5357     execute lnum
5358     if info.section ==# 'Staged'
5359       let reset += info.relative
5360     elseif info.section ==# 'Untracked'
5361       let intend += info.paths
5362     elseif info.status !~# '^D'
5363       let add += info.relative
5364     endif
5365   endfor
5366   try
5367     if !empty(intend)
5368       call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
5369     endif
5370     if !empty(add)
5371       execute "tab Git add --patch -- ".join(map(add,'fnameescape(v:val)'))
5372     endif
5373     if !empty(reset)
5374       execute "tab Git reset --patch -- ".join(map(reset,'fnameescape(v:val)'))
5375     endif
5376   catch /^fugitive:/
5377     return 'echoerr ' . string(v:exception)
5378   endtry
5379   return s:ReloadStatus()
5380 endfunction
5382 " Section: :Git commit, :Git revert
5384 function! s:CommitInteractive(line1, line2, range, bang, mods, options, patch) abort
5385   let status = s:StatusCommand(a:line1, a:line2, a:range, get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2, a:bang, a:mods, '', '', [], a:options)
5386   let status = len(status) ? status . '|' : ''
5387   if a:patch
5388     return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
5389   else
5390     return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
5391   endif
5392 endfunction
5394 function! s:CommitSubcommand(line1, line2, range, bang, mods, options) abort
5395   let argv = copy(a:options.subcommand_args)
5396   let i = 0
5397   while get(argv, i, '--') !=# '--'
5398     if argv[i] =~# '^-[apzsneiovq].'
5399       call insert(argv, argv[i][0:1])
5400       let argv[i+1] = '-' . argv[i+1][2:-1]
5401     else
5402       let i += 1
5403     endif
5404   endwhile
5405   if s:HasOpt(argv, '-i', '--interactive')
5406     return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 0)
5407   elseif s:HasOpt(argv, '-p', '--patch')
5408     return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 1)
5409   else
5410     return {}
5411   endif
5412 endfunction
5414 function! s:RevertSubcommand(line1, line2, range, bang, mods, options) abort
5415   return {'insert_args': ['--edit']}
5416 endfunction
5418 function! fugitive#CommitComplete(A, L, P, ...) abort
5419   let dir = a:0 ? a:1 : s:Dir()
5420   if a:A =~# '^--fixup=\|^--squash='
5421     let commits = s:LinesError([dir, 'log', '--pretty=format:%s', '@{upstream}..'])[0]
5422     let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
5423     if pre =~# "'"
5424       call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
5425       call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
5426       return commits
5427     else
5428       return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
5429     endif
5430   else
5431     return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'), a:000)
5432   endif
5433   return []
5434 endfunction
5436 function! fugitive#RevertComplete(A, L, P, ...) abort
5437   return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5438 endfunction
5440 " Section: :Git merge, :Git rebase, :Git pull
5442 function! fugitive#MergeComplete(A, L, P, ...) abort
5443   return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5444 endfunction
5446 function! fugitive#RebaseComplete(A, L, P, ...) abort
5447   return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5448 endfunction
5450 function! fugitive#PullComplete(A, L, P, ...) abort
5451   return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
5452 endfunction
5454 function! s:MergeSubcommand(line1, line2, range, bang, mods, options) abort
5455   if empty(a:options.subcommand_args) && (
5456         \ filereadable(fugitive#Find('.git/MERGE_MSG', a:options)) ||
5457         \ isdirectory(fugitive#Find('.git/rebase-apply', a:options)) ||
5458         \  !empty(s:TreeChomp([a:options.git_dir, 'diff-files', '--diff-filter=U'])))
5459     return 'echoerr ":Git merge for loading conflicts has been removed in favor of :Git mergetool"'
5460   endif
5461   return {}
5462 endfunction
5464 function! s:RebaseSubcommand(line1, line2, range, bang, mods, options) abort
5465   let args = a:options.subcommand_args
5466   if s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '-i', '--interactive')
5467     return {'env': {'GIT_SEQUENCE_EDITOR': 'true'}, 'insert_args': ['--interactive']}
5468   endif
5469   return {}
5470 endfunction
5472 " Section: :Git bisect
5474 function! s:CompleteBisect(A, L, P, ...) abort
5475   let bisect_subcmd = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
5476   if empty(bisect_subcmd)
5477     let subcmds = ['start', 'bad', 'new', 'good', 'old', 'terms', 'skip', 'next', 'reset', 'replay', 'log', 'run']
5478     return s:FilterEscape(subcmds, a:A)
5479   endif
5480   let dir = a:0 ? a:1 : s:Dir()
5481   return fugitive#CompleteObject(a:A, dir)
5482 endfunction
5484 function! fugitive#BisectComplete(A, L, P, ...) abort
5485   return s:CompleteSub('bisect', a:A, a:L, a:P, function('s:CompleteBisect'), a:000)
5486 endfunction
5488 " Section: :Git difftool, :Git mergetool
5490 function! s:ToolItems(state, from, to, offsets, text, ...) abort
5491   let items = []
5492   for i in range(len(a:state.diff))
5493     let diff = a:state.diff[i]
5494     let path = (i == len(a:state.diff) - 1) ? a:to : a:from
5495     if empty(path)
5496       return []
5497     endif
5498     let item = {
5499           \ 'valid': a:0 ? a:1 : 1,
5500           \ 'filename': diff.filename . s:VimSlash(path),
5501           \ 'lnum': matchstr(get(a:offsets, i), '\d\+'),
5502           \ 'text': a:text}
5503     if len(get(diff, 'module', ''))
5504       let item.module = diff.module . path
5505     endif
5506     call add(items, item)
5507   endfor
5508   if get(a:offsets, 0, '') isnot# 'none'
5509     let items[-1].context = {'diff': items[0:-2]}
5510   endif
5511   return [items[-1]]
5512 endfunction
5514 function! s:ToolToFrom(str) abort
5515   if a:str =~# ' => '
5516     let str = a:str =~# '{.* => .*}' ? a:str : '{' . a:str . '}'
5517     return [substitute(str, '{.* => \(.*\)}', '\1', ''),
5518           \ substitute(str, '{\(.*\) => .*}', '\1', '')]
5519   else
5520     return [a:str, a:str]
5521   endif
5522 endfunction
5524 function! s:ToolParse(state, line) abort
5525   if type(a:line) !=# type('') || a:state.mode ==# 'hunk' && a:line =~# '^[ +-]'
5526     return []
5527   elseif a:line =~# '^diff '
5528     let a:state.mode = 'diffhead'
5529     let a:state.from = ''
5530     let a:state.to = ''
5531   elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- [^/]'
5532     let a:state.from = a:line[4:-1]
5533     let a:state.to = a:state.from
5534   elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ [^/]'
5535     let a:state.to = a:line[4:-1]
5536     if empty(get(a:state, 'from', ''))
5537       let a:state.from = a:state.to
5538     endif
5539   elseif a:line[0] ==# '@'
5540     let a:state.mode = 'hunk'
5541     if has_key(a:state, 'from')
5542       let offsets = split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' ')
5543       return s:ToolItems(a:state, a:state.from, a:state.to, offsets, matchstr(a:line, ' @@\+ \zs.*'))
5544     endif
5545   elseif a:line =~# '^\* Unmerged path .'
5546     let file = a:line[16:-1]
5547     return s:ToolItems(a:state, file, file, [], '')
5548   elseif a:line =~# '^[A-Z]\d*\t.\|^:.*\t.'
5549     " --raw, --name-status
5550     let [status; files] = split(a:line, "\t")
5551     return s:ToolItems(a:state, files[0], files[-1], [], a:state.name_only ? '' : status)
5552   elseif a:line =~# '^ \S.* |'
5553     " --stat
5554     let [_, to, changes; __] = matchlist(a:line, '^ \(.\{-\}\) \+|\zs \(.*\)$')
5555     let [to, from] = s:ToolToFrom(to)
5556     return s:ToolItems(a:state, from, to, [], changes)
5557   elseif a:line =~# '^ *\([0-9.]\+%\) .'
5558     " --dirstat
5559     let [_, changes, to; __] = matchlist(a:line, '^ *\([0-9.]\+%\) \(.*\)')
5560     return s:ToolItems(a:state, to, to, [], changes)
5561   elseif a:line =~# '^\(\d\+\|-\)\t\(\d\+\|-\)\t.'
5562     " --numstat
5563     let [_, add, remove, to; __] = matchlist(a:line, '^\(\d\+\|-\)\t\(\d\+\|-\)\t\(.*\)')
5564     let [to, from] = s:ToolToFrom(to)
5565     return s:ToolItems(a:state, from, to, [], add ==# '-' ? 'Binary file' : '+' . add . ' -' . remove, add !=# '-')
5566   elseif a:line =~# '^\f\+:\d\+: \D'
5567     " --check
5568     let [_, to, line, text; __] = matchlist(a:line, '^\(\f\+\):\(\d\+\):\s*\(.*\)$')
5569     return s:ToolItems(a:state, to, to, ['none', line], text)
5570   elseif a:state.mode !=# 'diffhead' && a:state.mode !=# 'hunk' && len(a:line) || a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
5571     return [{'text': a:line}]
5572   endif
5573   return []
5574 endfunction
5576 function! s:ToolStream(line1, line2, range, bang, mods, options, args, state) abort
5577   let i = 0
5578   let argv = copy(a:args)
5579   let prompt = 1
5580   let state = a:state
5581   while i < len(argv)
5582     let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
5583     if len(match) && len(match[2])
5584       call insert(argv, match[1])
5585       let argv[i+1] = '-' . match[2]
5586       continue
5587     endif
5588     let arg = argv[i]
5589     if arg =~# '^-t$\|^--tool=\|^--tool-help$\|^--help$'
5590       return {}
5591     elseif arg =~# '^-y$\|^--no-prompt$'
5592       let prompt = 0
5593       call remove(argv, i)
5594       continue
5595     elseif arg ==# '--prompt'
5596       let prompt = 1
5597       call remove(argv, i)
5598       continue
5599     elseif arg =~# '^--\%(no-\)\=\(symlinks\|trust-exit-code\|gui\)$'
5600       call remove(argv, i)
5601       continue
5602     elseif arg ==# '--'
5603       break
5604     endif
5605     let i += 1
5606   endwhile
5607   call fugitive#Autowrite()
5608   let a:state.mode = 'init'
5609   let a:state.from = ''
5610   let a:state.to = ''
5611   let exec = s:UserCommandList({'git': a:options.git, 'git_dir': a:options.git_dir}) + ['-c', 'diff.context=0']
5612   let exec += a:options.flags + ['--no-pager', 'diff', '--no-ext-diff', '--no-color', '--no-prefix'] + argv
5613   if prompt
5614     let title = ':Git ' . s:fnameescape(a:options.flags + [a:options.subcommand] + a:options.subcommand_args)
5615     return s:QuickfixStream(get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2, 'difftool', title, exec, !a:bang, a:mods, s:function('s:ToolParse'), a:state)
5616   else
5617     let filename = ''
5618     let cmd = []
5619     let tabnr = tabpagenr() + 1
5620     for line in s:SystemList(exec)[0]
5621       for item in s:ToolParse(a:state, line)
5622         if len(get(item, 'filename', '')) && item.filename != filename
5623           call add(cmd, 'tabedit ' . s:fnameescape(item.filename))
5624           for i in reverse(range(len(get(get(item, 'context', {}), 'diff', []))))
5625             call add(cmd, (i ? 'rightbelow' : 'leftabove') . ' vertical Gdiffsplit! ' . s:fnameescape(item.context.diff[i].filename))
5626           endfor
5627           call add(cmd, 'wincmd =')
5628           let filename = item.filename
5629         endif
5630       endfor
5631     endfor
5632     return join(cmd, '|') . (empty(cmd) ? '' : '|' . tabnr . 'tabnext')
5633   endif
5634 endfunction
5636 function! s:MergetoolSubcommand(line1, line2, range, bang, mods, options) abort
5637   let dir = a:options.git_dir
5638   exe s:DirCheck(dir)
5639   let i = 0
5640   let prompt = 1
5641   let cmd = ['diff', '--diff-filter=U']
5642   let state = {'name_only': 0}
5643   let state.diff = [{'prefix': ':2:', 'module': ':2:'}, {'prefix': ':3:', 'module': ':3:'}, {'prefix': ':(top)'}]
5644   call map(state.diff, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
5645   return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, ['--diff-filter=U'] + a:options.subcommand_args, state)
5646 endfunction
5648 function! s:DifftoolSubcommand(line1, line2, range, bang, mods, options) abort
5649   let dir = s:Dir(a:options)
5650   exe s:DirCheck(dir)
5651   let i = 0
5652   let argv = copy(a:options.subcommand_args)
5653   let commits = []
5654   let cached = 0
5655   let reverse = 1
5656   let prompt = 1
5657   let state = {'name_only': 0}
5658   let merge_base_against = {}
5659   let dash = (index(argv, '--') > i ? ['--'] : [])
5660   while i < len(argv)
5661     let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
5662     if len(match) && len(match[2])
5663       call insert(argv, match[1])
5664       let argv[i+1] = '-' . match[2]
5665       continue
5666     endif
5667     let arg = argv[i]
5668     if arg ==# '--cached'
5669       let cached = 1
5670     elseif arg ==# '-R'
5671       let reverse = 1
5672     elseif arg ==# '--name-only'
5673       let state.name_only = 1
5674       let argv[0] = '--name-status'
5675     elseif arg ==# '--'
5676       break
5677     elseif arg !~# '^-\|^\.\.\=\%(/\|$\)'
5678       let parsed = s:LinesError(['rev-parse', '--revs-only', substitute(arg, ':.*', '', '')] + dash)[0]
5679       call map(parsed, '{"uninteresting": v:val =~# "^\\^", "prefix": substitute(v:val, "^\\^", "", "") . ":"}')
5680       let merge_base_against = {}
5681       if arg =~# '\.\.\.' && len(parsed) > 2
5682         let display = map(split(arg, '\.\.\.', 1), 'empty(v:val) ? "@" : v:val')
5683         if len(display) == 2
5684           let parsed[0].module = display[1] . ':'
5685           let parsed[1].module = display[0] . ':'
5686         endif
5687         let parsed[2].module = arg . ':'
5688         if empty(commits)
5689           let merge_base_against = parsed[0]
5690           let parsed = [parsed[2]]
5691         endif
5692       elseif arg =~# '\.\.' && len(parsed) == 2
5693         let display = map(split(arg, '\.\.', 1), 'empty(v:val) ? "@" : v:val')
5694         if len(display) == 2
5695           let parsed[0].module = display[0] . ':'
5696           let parsed[1].module = display[1] . ':'
5697         endif
5698       elseif len(parsed) == 1
5699         let parsed[0].module = arg . ':'
5700       endif
5701       call extend(commits, parsed)
5702     endif
5703     let i += 1
5704   endwhile
5705   if len(merge_base_against)
5706     call add(commits, merge_base_against)
5707   endif
5708   let commits = filter(copy(commits), 'v:val.uninteresting') + filter(commits, '!v:val.uninteresting')
5709   if cached
5710     if empty(commits)
5711       call add(commits, {'prefix': '@:', 'module': '@:'})
5712     endif
5713     call add(commits, {'prefix': ':0:', 'module': ':0:'})
5714   elseif len(commits) < 2
5715     call add(commits, {'prefix': ':(top)'})
5716     if len(commits) < 2
5717       call insert(commits, {'prefix': ':0:', 'module': ':0:'})
5718     endif
5719   endif
5720   if reverse
5721     let commits = [commits[-1]] + repeat([commits[0]], len(commits) - 1)
5722     call reverse(commits)
5723   endif
5724   if len(commits) > 2
5725     call add(commits, remove(commits, 0))
5726   endif
5727   call map(commits, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
5728   let state.diff = commits
5729   return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, argv, state)
5730 endfunction
5732 " Section: :Ggrep, :Glog
5734 if !exists('g:fugitive_summary_format')
5735   let g:fugitive_summary_format = '%s'
5736 endif
5738 function! fugitive#GrepComplete(A, L, P) abort
5739   return s:CompleteSub('grep', a:A, a:L, a:P)
5740 endfunction
5742 function! fugitive#LogComplete(A, L, P) abort
5743   return s:CompleteSub('log', a:A, a:L, a:P)
5744 endfunction
5746 function! s:GrepParseLine(options, quiet, dir, line) abort
5747   if !a:quiet
5748     echo a:line
5749   endif
5750   let entry = {'valid': 1}
5751   let match = matchlist(a:line, '^\(.\{-\}\):\([1-9]\d*\):\([1-9]\d*:\)\=\(.*\)$')
5752   if a:line =~# '^git: \|^usage: \|^error: \|^fatal: \|^BUG: '
5753     return {'text': a:line}
5754   elseif len(match)
5755     let entry.module = match[1]
5756     let entry.lnum = +match[2]
5757     let entry.col = +match[3]
5758     let entry.text = match[4]
5759   else
5760     let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
5761     if len(entry.module)
5762       let entry.text = 'Binary file'
5763       let entry.valid = 0
5764     endif
5765   endif
5766   if empty(entry.module) && !a:options.line_number
5767     let match = matchlist(a:line, '^\(.\{-\}\):\(.*\)$')
5768     if len(match)
5769       let entry.module = match[1]
5770       let entry.pattern = '\M^' . escape(match[2], '\.^$/') . '$'
5771     endif
5772   endif
5773   if empty(entry.module) && a:options.name_count && a:line =~# ':\d\+$'
5774     let entry.text = matchstr(a:line, '\d\+$')
5775     let entry.module = strpart(a:line, 0, len(a:line) - len(entry.text) - 1)
5776   endif
5777   if empty(entry.module) && a:options.name_only
5778     let entry.module = a:line
5779   endif
5780   if empty(entry.module)
5781     return {'text': a:line}
5782   endif
5783   if entry.module !~# ':'
5784     let entry.filename = s:PathJoin(a:options.prefix, entry.module)
5785   else
5786     let entry.filename = fugitive#Find(matchstr(entry.module, '^[^:]*:') .
5787           \ substitute(matchstr(entry.module, ':\zs.*'), '/\=:', '/', 'g'), a:dir)
5788   endif
5789   return entry
5790 endfunction
5792 let s:grep_combine_flags = '[aiIrhHEGPFnlLzocpWq]\{-\}'
5793 function! s:GrepOptions(args, dir) abort
5794   let options = {'name_only': 0, 'name_count': 0, 'line_number': 0}
5795   let tree = s:Tree(a:dir)
5796   let prefix = empty(tree) ? fugitive#Find(':0:', a:dir) :
5797         \ s:VimSlash(tree . '/')
5798   let options.prefix = prefix
5799   for arg in a:args
5800     if arg ==# '--'
5801       break
5802     endif
5803     if arg =~# '^\%(-' . s:grep_combine_flags . 'c\|--count\)$'
5804       let options.name_count = 1
5805     endif
5806     if arg =~# '^\%(-' . s:grep_combine_flags . 'n\|--line-number\)$'
5807       let options.line_number = 1
5808     elseif arg =~# '^\%(--no-line-number\)$'
5809       let options.line_number = 0
5810     endif
5811     if arg =~# '^\%(-' . s:grep_combine_flags . '[lL]\|--files-with-matches\|--name-only\|--files-without-match\)$'
5812       let options.name_only = 1
5813     endif
5814     if arg ==# '--cached'
5815       let options.prefix = fugitive#Find(':0:', a:dir)
5816     elseif arg ==# '--no-cached'
5817       let options.prefix = prefix
5818     endif
5819   endfor
5820   return options
5821 endfunction
5823 function! s:GrepCfile(result) abort
5824   let options = s:GrepOptions(a:result.args, a:result)
5825   let entry = s:GrepParseLine(options, 1, a:result, getline('.'))
5826   if get(entry, 'col')
5827     return [entry.filename, entry.lnum, "norm!" . entry.col . "|"]
5828   elseif has_key(entry, 'lnum')
5829     return [entry.filename, entry.lnum]
5830   elseif has_key(entry, 'pattern')
5831     return [entry.filename, '', 'silent /' . entry.pattern]
5832   elseif has_key(entry, 'filename')
5833     return [entry.filename]
5834   else
5835     return []
5836   endif
5837 endfunction
5839 function! s:GrepSubcommand(line1, line2, range, bang, mods, options) abort
5840   let args = copy(a:options.subcommand_args)
5841   let handle = -1
5842   let quiet = 0
5843   let i = 0
5844   while i < len(args) && args[i] !=# '--'
5845     let partition = matchstr(args[i], '^-' . s:grep_combine_flags . '\ze[qzO]')
5846     if len(partition) > 1
5847       call insert(args, '-' . strpart(args[i], len(partition)), i+1)
5848       let args[i] = partition
5849     elseif args[i] =~# '^\%(-' . s:grep_combine_flags . '[eABC]\|--max-depth\|--context\|--after-context\|--before-context\|--threads\)$'
5850       let i += 1
5851     elseif args[i] =~# '^\%(-O\|--open-files-in-pager\)$'
5852       let handle = 1
5853       call remove(args, i)
5854       continue
5855     elseif args[i] =~# '^\%(-O\|--open-files-in-pager=\)'
5856       let handle = 0
5857     elseif args[i] =~# '^-[qz].'
5858       let args[i] = '-' . args[i][2:-1]
5859       let quiet = 1
5860     elseif args[i] =~# '^\%(-[qz]\|--quiet\)$'
5861       let quiet = 1
5862       call remove(args, i)
5863       continue
5864     elseif args[i] =~# '^--no-quiet$'
5865       let quiet = 0
5866     elseif args[i] =~# '^\%(--heading\)$'
5867       call remove(args, i)
5868       continue
5869     endif
5870     let i += 1
5871   endwhile
5872   if handle < 0 ? !quiet : !handle
5873     return {}
5874   endif
5875   call fugitive#Autowrite()
5876   let listnr = get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2
5877   if s:HasOpt(args, '--no-line-number')
5878     let lc = []
5879   else
5880     let lc = fugitive#GitVersion(2, 19) ? ['-n', '--column'] : ['-n']
5881   endif
5882   let cmd = ['grep', '--no-color', '--full-name'] + lc
5883   let dir = s:Dir(a:options)
5884   let options = s:GrepOptions(lc + args, dir)
5885   if listnr > 0
5886     exe listnr 'wincmd w'
5887   else
5888     call s:BlurStatus()
5889   endif
5890   let title = (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)
5891   call s:QuickfixCreate(listnr, {'title': title})
5892   let tempfile = tempname()
5893   let state = {
5894         \ 'git': a:options.git,
5895         \ 'flags': a:options.flags,
5896         \ 'args': cmd + args,
5897         \ 'git_dir': s:GitDir(a:options),
5898         \ 'cwd': s:UserCommandCwd(a:options),
5899         \ 'filetype': 'git',
5900         \ 'mods': s:Mods(a:mods),
5901         \ 'file': s:Resolve(tempfile)}
5902   let event = listnr < 0 ? 'grep-fugitive' : 'lgrep-fugitive'
5903   exe s:DoAutocmd('QuickFixCmdPre ' . event)
5904   try
5905     if !quiet && &more
5906       let more = 1
5907       set nomore
5908     endif
5909     if !quiet
5910       echo title
5911     endif
5912     let list = s:SystemList(s:UserCommandList(a:options) + cmd + args)[0]
5913     call writefile(list + [''], tempfile, 'b')
5914     call s:RunSave(state)
5915     call map(list, 's:GrepParseLine(options, ' . quiet . ', dir, v:val)')
5916     call s:QuickfixSet(listnr, list, 'a')
5917     let press_enter_shortfall = &cmdheight - len(list)
5918     if press_enter_shortfall > 0 && !quiet
5919       echo repeat("\n", press_enter_shortfall - 1)
5920     endif
5921   finally
5922     if exists('l:more')
5923       let &more = more
5924     endif
5925   endtry
5926   call s:RunFinished(state)
5927   exe s:DoAutocmd('QuickFixCmdPost ' . event)
5928   if quiet
5929     let bufnr = bufnr('')
5930     exe s:QuickfixOpen(listnr, a:mods)
5931     if bufnr != bufnr('') && !a:bang
5932       wincmd p
5933     endif
5934   end
5935   if !a:bang && !empty(list)
5936     return 'silent ' . (listnr < 0 ? 'c' : 'l').'first'
5937   else
5938     return ''
5939   endif
5940 endfunction
5942 function! fugitive#GrepCommand(line1, line2, range, bang, mods, arg) abort
5943   return fugitive#Command(a:line1, a:line2, a:range, a:bang, a:mods,
5944         \ "grep -O " . a:arg)
5945 endfunction
5947 let s:log_diff_context = '{"filename": fugitive#Find(v:val . from, a:dir), "lnum": get(offsets, v:key), "module": strpart(v:val, 0, len(a:state.base_module)) . from}'
5949 function! s:LogFlushQueue(state, dir) abort
5950   let queue = remove(a:state, 'queue')
5951   if a:state.child_found && get(a:state, 'ignore_commit')
5952     call remove(queue, 0)
5953   elseif len(queue) && len(a:state.target) && len(get(a:state, 'parents', []))
5954     let from = substitute(a:state.target, '^/', ':', '')
5955     let offsets = []
5956     let queue[0].context.diff = map(copy(a:state.parents), s:log_diff_context)
5957   endif
5958   if len(queue) && queue[-1] ==# {'text': ''}
5959     call remove(queue, -1)
5960   endif
5961   return queue
5962 endfunction
5964 function! s:LogParse(state, dir, prefix, line) abort
5965   if a:state.mode ==# 'hunk' && a:line =~# '^[-+ ]'
5966     return []
5967   endif
5968   let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
5969   if len(list)
5970     let queue = s:LogFlushQueue(a:state, a:dir)
5971     let a:state.mode = 'commit'
5972     let a:state.base = a:prefix . list[2]
5973     if len(list[1])
5974       let [a:state.base_module; a:state.parents] = split(list[1], ' ')
5975     else
5976       let a:state.base_module = list[2]
5977       let a:state.parents = []
5978     endif
5979     let a:state.message = list[3]
5980     let a:state.from = ''
5981     let a:state.to = ''
5982     let context = {}
5983     let a:state.queue = [{
5984           \ 'valid': 1,
5985           \ 'context': context,
5986           \ 'filename': s:PathJoin(a:state.base, a:state.target),
5987           \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
5988           \ 'text': a:state.message}]
5989     let a:state.child_found = 0
5990     return queue
5991   elseif type(a:line) == type(0)
5992     return s:LogFlushQueue(a:state, a:dir)
5993   elseif a:line =~# '^diff'
5994     let a:state.mode = 'diffhead'
5995     let a:state.from = ''
5996     let a:state.to = ''
5997   elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- \w/'
5998     let a:state.from = a:line[6:-1]
5999     let a:state.to = a:state.from
6000   elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ \w/'
6001     let a:state.to = a:line[6:-1]
6002     if empty(get(a:state, 'from', ''))
6003       let a:state.from = a:state.to
6004     endif
6005   elseif a:line =~# '^@@[^@]*+\d' && len(get(a:state, 'to', '')) && has_key(a:state, 'base')
6006     let a:state.mode = 'hunk'
6007     if empty(a:state.target) || a:state.target ==# '/' . a:state.to
6008       if !a:state.child_found && len(a:state.queue) && a:state.queue[-1] ==# {'text': ''}
6009         call remove(a:state.queue, -1)
6010       endif
6011       let a:state.child_found = 1
6012       let offsets = map(split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' '), '+matchstr(v:val, "\\d\\+")')
6013       let context = {}
6014       if len(a:state.parents)
6015         let from = ":" . a:state.from
6016         let context.diff = map(copy(a:state.parents), s:log_diff_context)
6017       endif
6018       call add(a:state.queue, {
6019             \ 'valid': 1,
6020             \ 'context': context,
6021             \ 'filename': s:VimSlash(a:state.base . '/' . a:state.to),
6022             \ 'module': a:state.base_module . ':' . a:state.to,
6023             \ 'lnum': offsets[-1],
6024             \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
6025     endif
6026   elseif a:state.follow &&
6027         \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
6028     let rename = matchstr(a:line, '^ \%(copy\|rename\) \zs.* => .*\ze (\d\+%)$')
6029     if len(rename)
6030       let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
6031       if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
6032         let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
6033       endif
6034     endif
6035     if !get(a:state, 'ignore_summary')
6036       call add(a:state.queue, {'text': a:line})
6037     endif
6038   elseif a:state.mode ==# 'commit' || a:state.mode ==# 'init'
6039     call add(a:state.queue, {'text': a:line})
6040   endif
6041   return []
6042 endfunction
6044 function! fugitive#LogCommand(line1, count, range, bang, mods, args, type) abort
6045   exe s:VersionCheck()
6046   let dir = s:Dir()
6047   exe s:DirCheck(dir)
6048   let listnr = a:type =~# '^l' ? 0 : -1
6049   let [args, after] = s:SplitExpandChain('log ' . a:args, s:Tree(dir))
6050   call remove(args, 0)
6051   let split = index(args, '--')
6052   if split > 0
6053     let paths = args[split : -1]
6054     let args = args[0 : split - 1]
6055   elseif split == 0
6056     let paths = args
6057     let args = []
6058   else
6059     let paths = []
6060   endif
6061   if a:line1 == 0 && a:count
6062     let path = fugitive#Path(bufname(a:count), '/', dir)
6063     let titlepre = ':0,' . a:count
6064   elseif a:count >= 0
6065     let path = fugitive#Path(@%, '/', dir)
6066     let titlepre = a:count == 0 ? ':0,' . bufnr('') : ':'
6067   else
6068     let titlepre = ':'
6069     let path = ''
6070   endif
6071   let range = ''
6072   let extra_args = []
6073   let extra_paths = []
6074   let state = {'mode': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
6075   if path =~# '^/\.git\%(/\|$\)\|^$'
6076     let path = ''
6077   elseif a:line1 == 0
6078     let range = "0," . (a:count ? a:count : bufnr(''))
6079     let extra_paths = ['.' . path]
6080     if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
6081       let state.follow = 1
6082       if !s:HasOpt(args, '--follow')
6083         call insert(extra_args, '--follow')
6084       endif
6085       if !s:HasOpt(args, '--summary')
6086         call insert(extra_args, '--summary')
6087         let state.ignore_summary = 1
6088       endif
6089     endif
6090     let state.ignore_commit = 1
6091   elseif a:count > 0
6092     if !s:HasOpt(args, '--merges', '--no-merges')
6093       call insert(extra_args, '--no-merges')
6094     endif
6095     call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
6096     let state.ignore_commit = 1
6097   endif
6098   if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
6099     let owner = s:Owner(@%, dir)
6100     if len(owner)
6101       call add(args, owner . (owner =~# '^\x\{40,}' ? '' : '^{}'))
6102     endif
6103   endif
6104   if empty(extra_paths)
6105     let path = ''
6106   endif
6107   if s:HasOpt(args, '-g', '--walk-reflogs')
6108     let format = "%gd %P\t%H %gs"
6109   else
6110     let format = "%h %P\t%H " . g:fugitive_summary_format
6111   endif
6112   let cmd = ['--no-pager']
6113   call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'] +
6114         \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
6115         \ args + extra_args + paths + extra_paths)
6116   let state.target = path
6117   let title = titlepre . (listnr < 0 ? 'Gclog ' : 'Gllog ') . s:fnameescape(args + paths)
6118   return s:QuickfixStream(listnr, 'log', title, s:UserCommandList(dir) + cmd, !a:bang, a:mods, s:function('s:LogParse'), state, dir, s:DirUrlPrefix(dir)) . after
6119 endfunction
6121 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
6123 function! s:UsableWin(nr) abort
6124   return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
6125         \ !getwinvar(a:nr, '&winfixbuf') &&
6126         \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
6127         \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
6128         \ index(['nofile','help','quickfix', 'terminal'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
6129 endfunction
6131 function! s:ArgSplit(string) abort
6132   let string = a:string
6133   let args = []
6134   while string =~# '\S'
6135     let arg = matchstr(string, '^\s*\%(\\.\|\S\)\+')
6136     let string = strpart(string, len(arg))
6137     let arg = substitute(arg, '^\s\+', '', '')
6138     call add(args, substitute(arg, '\\\+[|" ]', '\=submatch(0)[len(submatch(0))/2 : -1]', 'g'))
6139   endwhile
6140   return args
6141 endfunction
6143 function! s:PlusEscape(string) abort
6144   return substitute(a:string, '\\*[|" ]', '\=repeat("\\", len(submatch(0))).submatch(0)', 'g')
6145 endfunction
6147 function! s:OpenParse(string, wants_cmd, wants_multiple) abort
6148   let opts = []
6149   let cmds = []
6150   let args = s:ArgSplit(a:string)
6151   while !empty(args)
6152     if args[0] =~# '^++'
6153       call add(opts, ' ' . s:PlusEscape(remove(args, 0)))
6154     elseif a:wants_cmd && args[0] ==# '+'
6155       call remove(args, 0)
6156       call add(cmds, '$')
6157     elseif a:wants_cmd && args[0] =~# '^+'
6158       call add(cmds, remove(args, 0)[1:-1])
6159     else
6160       break
6161     endif
6162   endwhile
6163   if !a:wants_multiple && empty(args)
6164     let args = ['>:']
6165   endif
6166   let dir = s:Dir()
6167   let wants_cmd = a:wants_cmd
6168   let urls = []
6169   for arg in args
6170     let [url, lnum] = s:OpenExpand(dir, arg, wants_cmd)
6171     if lnum
6172       call insert(cmds, lnum)
6173     endif
6174     call add(urls, url)
6175     let wants_cmd = 0
6176   endfor
6178   let pre = join(opts, '')
6179   if len(cmds) > 1
6180     let pre .= ' +' . s:PlusEscape(join(map(cmds, '"exe ".string(v:val)'), '|'))
6181   elseif len(cmds)
6182     let pre .= ' +' . s:PlusEscape(cmds[0])
6183   endif
6184   return [a:wants_multiple ? urls : urls[0], pre]
6185 endfunction
6187 function! s:OpenExpand(dir, file, wants_cmd) abort
6188   if a:file ==# '-'
6189     let result = fugitive#Result()
6190     if has_key(result, 'file')
6191       let efile = result.file
6192     else
6193       throw 'fugitive: no previous command output'
6194     endif
6195   else
6196     let efile = s:Expand(a:file)
6197   endif
6198   if efile =~# '^https\=://'
6199     let [url, lnum] = s:ResolveUrl(efile, a:dir)
6200     return [url, a:wants_cmd ? lnum : 0]
6201   endif
6202   let url = s:Generate(efile, a:dir)
6203   if a:wants_cmd && a:file[0] ==# '>' && efile[0] !=# '>' && get(b:, 'fugitive_type', '') isnot# 'tree' && &filetype !=# 'netrw'
6204     let line = line('.')
6205     if s:Slash(expand('%:p')) !=# s:Slash(url)
6206       let diffcmd = 'diff'
6207       let from = s:DirRev(@%)[1]
6208       let to = s:DirRev(url)[1]
6209       if empty(from) && empty(to)
6210         let diffcmd = 'diff-files'
6211         let args = ['--', expand('%:p'), url]
6212       elseif empty(to)
6213         let args = [from, '--', url]
6214       elseif empty(from)
6215         let args = [to, '--', expand('%:p')]
6216         let reverse = 1
6217       else
6218         let args = [from, to]
6219       endif
6220       let [res, exec_error] = s:LinesError([a:dir, diffcmd, '-U0'] + args)
6221       if !exec_error
6222         call filter(res, 'v:val =~# "^@@ "')
6223         call map(res, 'substitute(v:val, ''[-+]\d\+\zs '', ",1 ", "g")')
6224         call map(res, 'matchlist(v:val, ''^@@ -\(\d\+\),\(\d\+\) +\(\d\+\),\(\d\+\) @@'')[1:4]')
6225         if exists('reverse')
6226           call map(res, 'v:val[2:3] + v:val[0:1]')
6227         endif
6228         call filter(res, 'v:val[0] < '.line('.'))
6229         let hunk = get(res, -1, [0,0,0,0])
6230         if hunk[0] + hunk[1] > line('.')
6231           let line = hunk[2] + max([1 - hunk[3], 0])
6232         else
6233           let line = hunk[2] + max([hunk[3], 1]) + line('.') - hunk[0] - max([hunk[1], 1])
6234         endif
6235       endif
6236     endif
6237     return [url, line]
6238   endif
6239   return [url, 0]
6240 endfunction
6242 function! fugitive#DiffClose() abort
6243   let mywinnr = winnr()
6244   for winnr in [winnr('#')] + range(winnr('$'),1,-1)
6245     if winnr != mywinnr && getwinvar(winnr,'&diff')
6246       execute winnr.'wincmd w'
6247       close
6248       if winnr('$') > 1
6249         wincmd p
6250       endif
6251     endif
6252   endfor
6253   diffoff!
6254 endfunction
6256 function! s:BlurStatus() abort
6257   if (&previewwindow || getwinvar(winnr(), '&winfixbuf') is# 1 || exists('w:fugitive_status')) && get(b:, 'fugitive_type', '') ==# 'index'
6258     let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
6259     if len(winnrs)
6260       exe winnrs[0].'wincmd w'
6261     else
6262       belowright new +setl\ bufhidden=delete
6263     endif
6264     if &diff
6265       call fugitive#DiffClose()
6266     endif
6267   endif
6268 endfunction
6270 let s:bang_edits = {'split': 'Git', 'vsplit': 'vertical Git', 'tabedit': 'tab Git', 'pedit': 'Git!'}
6271 function! fugitive#Open(cmd, bang, mods, arg, ...) abort
6272   exe s:VersionCheck()
6273   if a:bang
6274     return 'echoerr ' . string(':G' . a:cmd . '! for temp buffer output has been replaced by :' . get(s:bang_edits, a:cmd, 'Git') . ' --paginate')
6275   endif
6277   try
6278     let [file, pre] = s:OpenParse(a:arg, 1, 0)
6279   catch /^fugitive:/
6280     return 'echoerr ' . string(v:exception)
6281   endtry
6282   let mods = s:Mods(a:mods)
6283   if a:cmd ==# 'edit'
6284     call s:BlurStatus()
6285   endif
6286   return mods . a:cmd . pre . ' ' . s:fnameescape(file)
6287 endfunction
6289 function! fugitive#DropCommand(line1, count, range, bang, mods, arg, ...) abort
6290   exe s:VersionCheck()
6292   let mods = s:Mods(a:mods)
6293   try
6294     let [files, pre] = s:OpenParse(a:arg, 1, 1)
6295   catch /^fugitive:/
6296     return 'echoerr ' . string(v:exception)
6297   endtry
6298   if empty(files)
6299     return 'drop'
6300   endif
6301   call s:BlurStatus()
6302   return mods . 'drop' . ' ' . s:fnameescape(files) . substitute(pre, '^ *+', '|', '')
6303 endfunction
6305 function! s:ReadPrepare(line1, count, range, mods) abort
6306   let mods = s:Mods(a:mods)
6307   let after = a:count
6308   if a:count < 0
6309     let delete = 'silent 1,' . line('$') . 'delete_|'
6310     let after = line('$')
6311   elseif a:range == 2
6312     let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
6313   else
6314     let delete = ''
6315   endif
6316   if foldlevel(after)
6317     let pre = after . 'foldopen!|'
6318   else
6319     let pre = ''
6320   endif
6321   return [pre . 'keepalt ' . mods . after . 'read', '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')]
6322 endfunction
6324 function! fugitive#ReadCommand(line1, count, range, bang, mods, arg, ...) abort
6325   exe s:VersionCheck()
6326   let [read, post] = s:ReadPrepare(a:line1, a:count, a:range, a:mods)
6327   try
6328     let [file, pre] = s:OpenParse(a:arg, 0, 0)
6329   catch /^fugitive:/
6330     return 'echoerr ' . string(v:exception)
6331   endtry
6332   if file =~# '^fugitive:' && a:count is# 0
6333     return 'exe ' .string('keepalt ' . s:Mods(a:mods) . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
6334   endif
6335   return read . ' ' . pre . ' ' . s:fnameescape(file) . post
6336 endfunction
6338 function! fugitive#EditComplete(A, L, P) abort
6339   if a:A =~# '^>'
6340     return map(s:FilterEscape(s:CompleteHeads(s:Dir()), a:A[1:-1]), "'>' . v:val")
6341   else
6342     return fugitive#CompleteObject(a:A, a:L, a:P)
6343   endif
6344 endfunction
6346 function! fugitive#ReadComplete(A, L, P) abort
6347   return fugitive#EditComplete(a:A, a:L, a:P)
6348 endfunction
6350 " Section: :Gwrite, :Gwq
6352 function! fugitive#WriteCommand(line1, line2, range, bang, mods, arg, ...) abort
6353   exe s:VersionCheck()
6354   if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG')) && empty(a:arg)
6355     return (empty($GIT_INDEX_FILE) ? 'write|bdelete' : 'wq') . (a:bang ? '!' : '')
6356   elseif get(b:, 'fugitive_type', '') ==# 'index' && empty(a:arg)
6357     return 'Git commit'
6358   elseif &buftype ==# 'nowrite' && getline(4) =~# '^[+-]\{3\} '
6359     return 'echoerr ' . string('fugitive: :Gwrite from :Git diff has been removed in favor of :Git add --edit')
6360   endif
6361   let mytab = tabpagenr()
6362   let mybufnr = bufnr('')
6363   let args = s:ArgSplit(a:arg)
6364   let after = ''
6365   if get(args, 0) =~# '^+'
6366     let after = '|' . remove(args, 0)[1:-1]
6367   endif
6368   try
6369     let file = len(args) ? s:Generate(s:Expand(join(args, ' '))) : fugitive#Real(@%)
6370   catch /^fugitive:/
6371     return 'echoerr ' . string(v:exception)
6372   endtry
6373   if empty(file)
6374     return 'echoerr '.string('fugitive: cannot determine file path')
6375   endif
6376   if file =~# '^fugitive:'
6377     return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
6378   endif
6379   exe s:DirCheck()
6380   let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
6381   if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
6382     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
6383     return 'echoerr v:errmsg'
6384   endif
6385   let treebufnr = 0
6386   for nr in range(1,bufnr('$'))
6387     if fnamemodify(bufname(nr),':p') ==# file
6388       let treebufnr = nr
6389     endif
6390   endfor
6392   if treebufnr > 0 && treebufnr != bufnr('')
6393     let temp = tempname()
6394     silent execute 'keepalt %write '.temp
6395     for tab in [mytab] + range(1,tabpagenr('$'))
6396       for winnr in range(1,tabpagewinnr(tab,'$'))
6397         if tabpagebuflist(tab)[winnr-1] == treebufnr
6398           execute 'tabnext '.tab
6399           if winnr != winnr()
6400             execute winnr.'wincmd w'
6401             let restorewinnr = 1
6402           endif
6403           try
6404             let lnum = line('.')
6405             let last = line('$')
6406             silent execute '$read '.temp
6407             silent execute '1,'.last.'delete_'
6408             silent write!
6409             silent execute lnum
6410             diffupdate
6411             let did = 1
6412           finally
6413             if exists('restorewinnr')
6414               wincmd p
6415             endif
6416             execute 'tabnext '.mytab
6417           endtry
6418           break
6419         endif
6420       endfor
6421     endfor
6422     if !exists('did')
6423       call writefile(readfile(temp,'b'),file,'b')
6424     endif
6425   else
6426     execute 'write! '.s:fnameescape(file)
6427   endif
6429   let message = s:ChompStderr(['add'] + (a:bang ? ['--force'] : []) + ['--', file])
6430   if len(message)
6431     let v:errmsg = 'fugitive: '.message
6432     return 'echoerr v:errmsg'
6433   endif
6434   if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
6435     setlocal nomodified
6436   endif
6438   let one = fugitive#Find(':1:'.file)
6439   let two = fugitive#Find(':2:'.file)
6440   let three = fugitive#Find(':3:'.file)
6441   for nr in range(1,bufnr('$'))
6442     let name = fnamemodify(bufname(nr), ':p')
6443     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
6444       execute nr.'bdelete'
6445     endif
6446   endfor
6448   unlet! restorewinnr
6449   let zero = fugitive#Find(':0:'.file)
6450   exe s:DoAutocmd('BufWritePost ' . s:fnameescape(zero))
6451   for tab in range(1,tabpagenr('$'))
6452     for winnr in range(1,tabpagewinnr(tab,'$'))
6453       let bufnr = tabpagebuflist(tab)[winnr-1]
6454       let bufname = fnamemodify(bufname(bufnr), ':p')
6455       if bufname ==# zero && bufnr != mybufnr
6456         execute 'tabnext '.tab
6457         if winnr != winnr()
6458           execute winnr.'wincmd w'
6459           let restorewinnr = 1
6460         endif
6461         try
6462           let lnum = line('.')
6463           let last = line('$')
6464           silent execute '$read '.s:fnameescape(file)
6465           silent execute '1,'.last.'delete_'
6466           silent execute lnum
6467           setlocal nomodified
6468           diffupdate
6469         finally
6470           if exists('restorewinnr')
6471             wincmd p
6472           endif
6473           execute 'tabnext '.mytab
6474         endtry
6475         break
6476       endif
6477     endfor
6478   endfor
6479   call fugitive#DidChange()
6480   return 'checktime' . after
6481 endfunction
6483 function! fugitive#WqCommand(...) abort
6484   let bang = a:4 ? '!' : ''
6485   if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG'))
6486     return 'wq'.bang
6487   endif
6488   let result = call('fugitive#WriteCommand', a:000)
6489   if result =~# '^\%(write\|wq\|echoerr\)'
6490     return s:sub(result,'^write','wq')
6491   else
6492     return result.'|quit'.bang
6493   endif
6494 endfunction
6496 " Section: :Git push, :Git fetch
6498 function! s:CompletePush(A, L, P, ...) abort
6499   let dir = a:0 ? a:1 : s:Dir()
6500   let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
6501   if empty(remote)
6502     let matches = s:LinesError([dir, 'remote'])[0]
6503   elseif a:A =~# ':'
6504     let lead = matchstr(a:A, '^[^:]*:')
6505     let matches = s:LinesError([dir, 'ls-remote', remote])[0]
6506     call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
6507     call map(matches, 'lead . s:sub(v:val, "^.*\t", "")')
6508   else
6509     let matches = s:CompleteHeads(dir)
6510     if a:A =~# '^[\''"]\=+'
6511       call map(matches, '"+" . v:val')
6512     endif
6513   endif
6514   return s:FilterEscape(matches, a:A)
6515 endfunction
6517 function! fugitive#PushComplete(A, L, P, ...) abort
6518   return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompletePush'), a:000)
6519 endfunction
6521 function! fugitive#FetchComplete(A, L, P, ...) abort
6522   return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
6523 endfunction
6525 function! s:PushSubcommand(...) abort
6526   return {'no_more': 1}
6527 endfunction
6529 function! s:FetchSubcommand(...) abort
6530   return {'no_more': 1}
6531 endfunction
6533 " Section: :Gdiff
6535 augroup fugitive_diff
6536   autocmd!
6537   autocmd BufWinLeave * nested
6538         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
6539         \   call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
6540         \ endif
6541   autocmd BufWinEnter * nested
6542         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
6543         \   call s:diffoff() |
6544         \ endif
6545 augroup END
6547 function! s:can_diffoff(buf) abort
6548   return getwinvar(bufwinnr(a:buf), '&diff') &&
6549         \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
6550 endfunction
6552 function! fugitive#CanDiffoff(buf) abort
6553   return s:can_diffoff(bufnr(a:buf))
6554 endfunction
6556 function! s:DiffModifier(count, default) abort
6557   let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
6558   if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
6559     return ''
6560   elseif &diffopt =~# 'vertical'
6561     return 'vertical '
6562   elseif !get(g:, 'fugitive_diffsplit_directional_fit', a:default)
6563     return ''
6564   elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
6565     return ''
6566   else
6567     return 'vertical '
6568   endif
6569 endfunction
6571 function! s:diff_window_count() abort
6572   let c = 0
6573   for nr in range(1,winnr('$'))
6574     let c += getwinvar(nr,'&diff')
6575   endfor
6576   return c
6577 endfunction
6579 function! s:diffthis() abort
6580   if !&diff
6581     let w:fugitive_diff_restore = 1
6582     diffthis
6583   endif
6584 endfunction
6586 function! s:diffoff() abort
6587   unlet! w:fugitive_diff_restore
6588   diffoff
6589 endfunction
6591 function! s:diffoff_all(dir) abort
6592   let curwin = winnr()
6593   for nr in range(1,winnr('$'))
6594     if getwinvar(nr, '&diff') && !empty(getwinvar(nr, 'fugitive_diff_restore'))
6595       call setwinvar(nr, 'fugitive_diff_restore', '')
6596     endif
6597   endfor
6598   if curwin != winnr()
6599     execute curwin.'wincmd w'
6600   endif
6601   diffoff!
6602 endfunction
6604 function! s:IsConflicted() abort
6605   return len(@%) && !empty(s:ChompDefault('', ['ls-files', '--unmerged', '--', expand('%:p')]))
6606 endfunction
6608 function! fugitive#Diffsplit(autodir, keepfocus, mods, arg, ...) abort
6609   exe s:VersionCheck()
6610   let args = s:ArgSplit(a:arg)
6611   let post = ''
6612   let autodir = a:autodir
6613   while get(args, 0, '') =~# '^++'
6614     if args[0] =~? '^++novertical$'
6615       let autodir = 0
6616     else
6617       return 'echoerr ' . string('fugitive: unknown option ' . args[0])
6618     endif
6619     call remove(args, 0)
6620   endwhile
6621   if get(args, 0) =~# '^+'
6622     let post = remove(args, 0)[1:-1]
6623   endif
6624   if exists(':DiffGitCached') && empty(args)
6625     return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
6626   endif
6627   let commit = s:DirCommitFile(@%)[1]
6628   if a:mods =~# '\<\d*tab\>'
6629     let mods = substitute(a:mods, '\<\d*tab\>', '', 'g')
6630     let pre = matchstr(a:mods, '\<\d*tab\>') . 'edit'
6631   else
6632     let mods = 'keepalt ' . a:mods
6633     let pre = ''
6634   endif
6635   let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
6636   if (empty(args) || args[0] =~# '^>\=:$') && a:keepfocus
6637     exe s:DirCheck()
6638     if commit =~# '^1\=$' && s:IsConflicted()
6639       let parents = [s:Relative(':2:'), s:Relative(':3:')]
6640     elseif empty(commit)
6641       let parents = [s:Relative(':0:')]
6642     elseif commit =~# '^\d\=$'
6643       let parents = [s:Relative('@:')]
6644     elseif commit =~# '^\x\x\+$'
6645       let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
6646       call map(parents, 's:Relative(v:val . ":")')
6647     endif
6648   endif
6649   try
6650     if exists('parents') && len(parents) > 1
6651       exe pre
6652       let mods = (autodir ? s:DiffModifier(len(parents) + 1, empty(args) || args[0] =~# '^>') : '') . s:Mods(mods, 'leftabove')
6653       let nr = bufnr('')
6654       if len(parents) > 1 && !&equalalways
6655         let equalalways = 0
6656         set equalalways
6657       endif
6658       execute mods 'split' s:fnameescape(fugitive#Find(parents[0]))
6659       call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
6660       let nr2 = bufnr('')
6661       call s:diffthis()
6662       exe back
6663       call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
6664       let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
6665       for i in range(len(parents)-1, 1, -1)
6666         execute mods 'split' s:fnameescape(fugitive#Find(parents[i]))
6667         call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
6668         let nrx = bufnr('')
6669         call s:diffthis()
6670         exe back
6671         call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
6672       endfor
6673       call s:diffthis()
6674       return post
6675     elseif len(args)
6676       let arg = join(args, ' ')
6677       if arg ==# ''
6678         return post
6679       elseif arg ==# ':/'
6680         exe s:DirCheck()
6681         let file = s:Relative()
6682       elseif arg ==# ':'
6683         exe s:DirCheck()
6684         let file = len(commit) ? s:Relative() : s:Relative(s:IsConflicted() ? ':1:' : ':0:')
6685       elseif arg =~# '^:\d$'
6686         exe s:DirCheck()
6687         let file = s:Relative(arg . ':')
6688       elseif arg =~# '^[~^]\d*$'
6689         return 'echoerr ' . string('fugitive: change ' . arg . ' to !' . arg . ' to diff against ancestor')
6690       else
6691         try
6692           let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
6693         catch /^fugitive:/
6694           return 'echoerr ' . string(v:exception)
6695         endtry
6696       endif
6697       if a:keepfocus || arg =~# '^>'
6698         let mods = s:Mods(a:mods, 'leftabove')
6699       else
6700         let mods = s:Mods(a:mods)
6701       endif
6702     elseif exists('parents')
6703       let file = get(parents, -1, s:Relative(repeat('0', 40). ':'))
6704       let mods = s:Mods(a:mods, 'leftabove')
6705     elseif len(commit)
6706       let file = s:Relative()
6707       let mods = s:Mods(a:mods, 'rightbelow')
6708     elseif s:IsConflicted()
6709       let file = s:Relative(':1:')
6710       let mods = s:Mods(a:mods, 'leftabove')
6711       if get(g:, 'fugitive_legacy_commands', 1)
6712         let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
6713       endif
6714     else
6715       exe s:DirCheck()
6716       let file = s:Relative(':0:')
6717       let mods = s:Mods(a:mods, 'leftabove')
6718     endif
6719     let spec = s:Generate(file)
6720     if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
6721       let spec = s:VimSlash(spec . s:Relative('/'))
6722     endif
6723     exe pre
6724     let w:fugitive_diff_restore = 1
6725     let mods = (autodir ? s:DiffModifier(2, empty(args) || args[0] =~# '^>') : '') . mods
6726     if &diffopt =~# 'vertical'
6727       let diffopt = &diffopt
6728       set diffopt-=vertical
6729     endif
6730     execute mods 'diffsplit' s:fnameescape(spec)
6731     let w:fugitive_diff_restore = 1
6732     let winnr = winnr()
6733     if getwinvar('#', '&diff')
6734       if a:keepfocus
6735         exe back
6736       endif
6737     endif
6738     return post
6739   catch /^fugitive:/
6740     return 'echoerr ' . string(v:exception)
6741   finally
6742     if exists('l:equalalways')
6743       let &g:equalalways = equalalways
6744     endif
6745     if exists('diffopt')
6746       let &diffopt = diffopt
6747     endif
6748   endtry
6749 endfunction
6751 " Section: :GMove, :GRemove
6753 function! s:Move(force, rename, destination) abort
6754   exe s:VersionCheck()
6755   let dir = s:Dir()
6756   exe s:DirCheck(dir)
6757   if s:DirCommitFile(@%)[1] !~# '^0\=$' || empty(@%)
6758     return 'echoerr ' . string('fugitive: mv not supported for this buffer')
6759   endif
6760   if a:rename
6761     let default_root = expand('%:p:s?[\/]$??:h') . '/'
6762   else
6763     let default_root = s:Tree(dir) . '/'
6764   endif
6765   if a:destination =~# '^:/:\='
6766     let destination = s:Tree(dir) . s:Expand(substitute(a:destination, '^:/:\=', '', ''))
6767   elseif a:destination =~# '^:(top)'
6768     let destination = s:Expand(matchstr(a:destination, ')\zs.*'))
6769     if destination !~# '^/\|^\a\+:'
6770       let destination = s:Tree(dir) . '/' . destination
6771     endif
6772     let destination = s:Tree(dir) .
6773   elseif a:destination =~# '^:(\%(top,literal\|literal,top\))'
6774     let destination = s:Tree(dir) . matchstr(a:destination, ')\zs.*')
6775   elseif a:destination =~# '^:(literal)\.\.\=\%(/\|$\)'
6776     let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
6777   elseif a:destination =~# '^:(literal)'
6778     let destination = simplify(default_root . matchstr(a:destination, ')\zs.*'))
6779   else
6780     let destination = s:Expand(a:destination)
6781     if destination =~# '^\.\.\=\%(/\|$\)'
6782       let destination = simplify(getcwd() . '/' . destination)
6783     elseif destination !~# '^\a\+:\|^/'
6784       let destination = default_root . destination
6785     endif
6786   endif
6787   let destination = s:Slash(destination)
6788   if isdirectory(@%)
6789     setlocal noswapfile
6790   endif
6791   let exec = fugitive#Execute(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination], dir)
6792   if exec.exit_status && exec.stderr !=# ['']
6793     return 'echoerr ' .string('fugitive: '.s:JoinChomp(exec.stderr))
6794   endif
6795   if isdirectory(destination)
6796     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
6797   endif
6798   let reload = '|call fugitive#DidChange(' . string(exec) . ')'
6799   if empty(s:DirCommitFile(@%)[1])
6800     if isdirectory(destination)
6801       return 'keepalt edit '.s:fnameescape(destination) . reload
6802     else
6803       return 'keepalt saveas! '.s:fnameescape(destination) . reload
6804     endif
6805   else
6806     return 'file '.s:fnameescape(fugitive#Find(':0:'.destination, dir)) . reload
6807   endif
6808 endfunction
6810 function! fugitive#RenameComplete(A,L,P) abort
6811   if a:A =~# '^[.:]\=/'
6812     return fugitive#CompletePath(a:A)
6813   else
6814     let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
6815     return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
6816   endif
6817 endfunction
6819 function! fugitive#MoveCommand(line1, line2, range, bang, mods, arg, ...) abort
6820   return s:Move(a:bang, 0, a:arg)
6821 endfunction
6823 function! fugitive#RenameCommand(line1, line2, range, bang, mods, arg, ...) abort
6824   return s:Move(a:bang, 1, a:arg)
6825 endfunction
6827 function! s:Remove(after, force) abort
6828   exe s:VersionCheck()
6829   let dir = s:Dir()
6830   exe s:DirCheck(dir)
6831   if len(@%) && s:DirCommitFile(@%)[1] ==# ''
6832     let cmd = ['rm']
6833   elseif s:DirCommitFile(@%)[1] ==# '0'
6834     let cmd = ['rm','--cached']
6835   else
6836     return 'echoerr ' . string('fugitive: rm not supported for this buffer')
6837   endif
6838   if a:force
6839     let cmd += ['--force']
6840   endif
6841   let message = s:ChompStderr(cmd + ['--', expand('%:p')], dir)
6842   if len(message)
6843     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
6844     return 'echoerr '.string(v:errmsg)
6845   else
6846     return a:after . (a:force ? '!' : ''). '|call fugitive#DidChange(' . string(dir) . ')'
6847   endif
6848 endfunction
6850 function! fugitive#RemoveCommand(line1, line2, range, bang, mods, arg, ...) abort
6851   return s:Remove('edit', a:bang)
6852 endfunction
6854 function! fugitive#UnlinkCommand(line1, line2, range, bang, mods, arg, ...) abort
6855   return s:Remove('edit', a:bang)
6856 endfunction
6858 function! fugitive#DeleteCommand(line1, line2, range, bang, mods, arg, ...) abort
6859   return s:Remove('bdelete', a:bang)
6860 endfunction
6862 " Section: :Git blame
6864 function! s:Keywordprg() abort
6865   let args = ' --git-dir=' . escape(FugitiveGitPath(s:GitDir()), "\\\"' ")
6866   if has('gui_running') && !has('win32')
6867     return s:GitShellCmd() . ' --no-pager' . args . ' log -1'
6868   else
6869     return s:GitShellCmd() . args . ' show'
6870   endif
6871 endfunction
6873 function! s:linechars(pattern) abort
6874   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
6875   if &conceallevel > 1
6876     for col in range(1, chars)
6877       let chars -= synconcealed(line('.'), col)[0]
6878     endfor
6879   endif
6880   return chars
6881 endfunction
6883 function! s:BlameBufnr(...) abort
6884   let state = s:TempState(a:0 ? a:1 : bufnr(''))
6885   if get(state, 'filetype', '') ==# 'fugitiveblame'
6886     return get(state, 'origin_bufnr', -1)
6887   else
6888     return -1
6889   endif
6890 endfunction
6892 function! s:BlameCommitFileLnum(...) abort
6893   let line = a:0 ? a:1 : getline('.')
6894   let state = a:0 > 1 ? a:2 : s:TempState()
6895   if get(state, 'filetype', '') !=# 'fugitiveblame'
6896     return ['', '', 0]
6897   endif
6898   let commit = matchstr(line, '^\^\=[?*]*\zs\x\+')
6899   if commit =~# '^0\+$'
6900     let commit = ''
6901   elseif has_key(state, 'blame_reverse_end')
6902     let commit = get(s:LinesError([state.git_dir, 'rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end])[0], 0, '')
6903   endif
6904   let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
6905   let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s*\d\+ \%((\| *\d\+)\)')
6906   if empty(path) && lnum
6907     let path = get(state, 'blame_file', '')
6908   endif
6909   return [commit, path, lnum]
6910 endfunction
6912 function! s:BlameLeave() abort
6913   let state = s:TempState()
6914   let bufwinnr = exists('*win_id2win') ? win_id2win(get(state, 'origin_winid')) : 0
6915   if bufwinnr == 0
6916     let bufwinnr = bufwinnr(get(state, 'origin_bufnr', -1))
6917   endif
6918   if get(state, 'filetype', '') ==# 'fugitiveblame' && bufwinnr > 0
6919     let bufnr = bufnr('')
6920     exe bufwinnr . 'wincmd w'
6921     return bufnr . 'bdelete'
6922   endif
6923   return ''
6924 endfunction
6926 function! s:BlameQuit() abort
6927   let cmd = s:BlameLeave()
6928   if empty(cmd)
6929     return 'bdelete'
6930   elseif len(s:DirCommitFile(@%)[1])
6931     return cmd . '|Gedit'
6932   else
6933     return cmd
6934   endif
6935 endfunction
6937 function! fugitive#BlameComplete(A, L, P) abort
6938   return s:CompleteSub('blame', a:A, a:L, a:P)
6939 endfunction
6941 function! s:BlameSubcommand(line1, count, range, bang, mods, options) abort
6942   let dir = s:Dir(a:options)
6943   exe s:DirCheck(dir)
6944   let flags = copy(a:options.subcommand_args)
6945   let i = 0
6946   let raw = 0
6947   let commits = []
6948   let files = []
6949   let ranges = []
6950   if a:line1 > 0 && a:count > 0 && a:range != 1
6951     call extend(ranges, ['-L', a:line1 . ',' . a:count])
6952   endif
6953   while i < len(flags)
6954     let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
6955     if len(match) && len(match[2])
6956       call insert(flags, match[1])
6957       let flags[i+1] = '-' . match[2]
6958       continue
6959     endif
6960     let arg = flags[i]
6961     if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
6962       let raw = 1
6963     elseif arg ==# '--contents' && i + 1 < len(flags)
6964       call extend(commits, remove(flags, i, i+1))
6965       continue
6966     elseif arg ==# '-L' && i + 1 < len(flags)
6967       call extend(ranges, remove(flags, i, i+1))
6968       continue
6969     elseif arg =~# '^--contents='
6970       call add(commits, remove(flags, i))
6971       continue
6972     elseif arg =~# '^-L.'
6973       call add(ranges, remove(flags, i))
6974       continue
6975     elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
6976       let i += 1
6977       if i == len(flags)
6978         echohl ErrorMsg
6979         echo s:ChompStderr([dir, 'blame', arg])
6980         echohl NONE
6981         return ''
6982       endif
6983     elseif arg ==# '--'
6984       if i + 1 < len(flags)
6985         call extend(files, remove(flags, i + 1, -1))
6986       endif
6987       call remove(flags, i)
6988       break
6989     elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
6990       if index(flags, '--') >= 0
6991         call add(commits, remove(flags, i))
6992         continue
6993       endif
6994       if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
6995         call add(commits, remove(flags, i))
6996         continue
6997       endif
6998       try
6999         let dcf = s:DirCommitFile(fugitive#Find(arg, dir))
7000         if len(dcf[1]) && empty(dcf[2])
7001           call add(commits, remove(flags, i))
7002           continue
7003         endif
7004       catch /^fugitive:/
7005       endtry
7006       call add(files, remove(flags, i))
7007       continue
7008     endif
7009     let i += 1
7010   endwhile
7011   let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./', dir))), '^\.\%(/\|$\)', '', '')
7012   if empty(commits) && len(files) > 1
7013     call add(commits, remove(files, 1))
7014   endif
7015   exe s:BlameLeave()
7016   try
7017     let cmd = a:options.flags + ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', a:options.subcommand, '--show-number']
7018     call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
7019     if a:count > 0 && empty(ranges)
7020       let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
7021     endif
7022     call extend(cmd, ranges)
7023     let tempname = tempname()
7024     let temp = tempname . (raw ? '' : '.fugitiveblame')
7025     if len(commits)
7026       let cmd += commits
7027     elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
7028       let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
7029     elseif empty(files) && !s:HasOpt(flags, '--reverse')
7030       if &modified || !empty(s:DirCommitFile(@%)[1])
7031         let cmd += ['--contents', tempname . '.in']
7032         silent execute 'noautocmd keepalt %write ' . s:fnameescape(tempname . '.in')
7033         let delete_in = 1
7034       elseif &autoread
7035         exe 'checktime ' . bufnr('')
7036       endif
7037     else
7038       call fugitive#Autowrite()
7039     endif
7040     let basecmd = [{'git': a:options.git}, dir, '--literal-pathspecs'] + cmd + ['--'] + (len(files) ? files : [file])
7041     let [err, exec_error] = s:StdoutToFile(temp, basecmd)
7042     if exists('delete_in')
7043       call delete(tempname . '.in')
7044     endif
7045     redraw
7046     try
7047       if exec_error
7048         let lines = split(err, "\n")
7049         if empty(lines)
7050           let lines = readfile(temp)
7051         endif
7052         for i in range(len(lines))
7053           if lines[i] =~# '^error: \|^fatal: '
7054             echohl ErrorMsg
7055             echon lines[i]
7056             echohl NONE
7057             break
7058           else
7059             echon lines[i]
7060           endif
7061           if i != len(lines) - 1
7062             echon "\n"
7063           endif
7064         endfor
7065         return ''
7066       endif
7067       let temp_state = {
7068             \ 'git': a:options.git,
7069             \ 'flags': a:options.flags,
7070             \ 'args': [a:options.subcommand] + a:options.subcommand_args,
7071             \ 'git_dir': s:GitDir(a:options),
7072             \ 'cwd': s:UserCommandCwd(a:options),
7073             \ 'filetype': (raw ? 'git' : 'fugitiveblame'),
7074             \ 'blame_options': a:options,
7075             \ 'blame_flags': flags,
7076             \ 'blame_file': file}
7077       if s:HasOpt(flags, '--reverse')
7078         let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
7079       endif
7080       if a:line1 == 0 && a:count == 1
7081         if get(a:options, 'curwin')
7082           let edit = 'edit'
7083         elseif a:bang
7084           let edit = 'pedit'
7085         else
7086           let edit = 'split'
7087         endif
7088         return s:BlameCommit(s:Mods(a:mods) . edit, get(readfile(temp), 0, ''), temp_state)
7089       elseif (a:line1 == 0 || a:range == 1) && a:count > 0
7090         let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit', 'edit'], a:count - (a:line1 ? a:line1 : 1), 'split')
7091         return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
7092       else
7093         let temp = s:Resolve(temp)
7094         let temp_state.file = temp
7095         call s:RunSave(temp_state)
7096         if len(ranges + commits + files) || raw
7097           let reload = '|call fugitive#DidChange(fugitive#Result(' . string(temp_state.file) . '))'
7098           let mods = s:Mods(a:mods)
7099           if a:count != 0
7100             exe 'silent keepalt' mods get(a:options, 'curwin') ? 'edit' : 'split' s:fnameescape(temp)
7101           elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
7102             exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
7103           else
7104             return mods . 'edit ' . s:fnameescape(temp) . reload
7105           endif
7106           return reload[1 : -1]
7107         endif
7108         let tabmod = matchstr(a:mods, '\<\d*tab\>')
7109         let mods = substitute(a:mods, '\<\d*tab\>', '', 'g')
7110         if !empty(tabmod)
7111           silent execute tabmod . 'edit %'
7112         endif
7113         let temp_state.origin_bufnr = bufnr('')
7114         if exists('*win_getid')
7115           let temp_state.origin_winid = win_getid()
7116         endif
7117         let restore = []
7118         for winnr in range(winnr('$'),1,-1)
7119           if getwinvar(winnr, '&scrollbind')
7120             if !&l:scrollbind
7121               call setwinvar(winnr, '&scrollbind', 0)
7122             elseif winnr != winnr() && getwinvar(winnr, '&foldenable')
7123               call setwinvar(winnr, '&foldenable', 0)
7124               call add(restore, 'call setwinvar(bufwinnr('.winbufnr(winnr).'),"&foldenable",1)')
7125             endif
7126           endif
7127           let win_blame_bufnr = s:BlameBufnr(winbufnr(winnr))
7128           if getwinvar(winnr, '&scrollbind') ? win_blame_bufnr == temp_state.origin_bufnr : win_blame_bufnr > 0
7129             execute winbufnr(winnr).'bdelete'
7130           endif
7131         endfor
7132         let restore_winnr = get(temp_state, 'origin_winid', 'bufwinnr(' . temp_state.origin_bufnr . ')')
7133         if !&l:scrollbind
7134           call add(restore, 'call setwinvar(' . restore_winnr . ',"&scrollbind",0)')
7135         endif
7136         if &l:wrap
7137           call add(restore, 'call setwinvar(' . restore_winnr . ',"&wrap",1)')
7138         endif
7139         if &l:foldenable
7140           call add(restore, 'call setwinvar(' . restore_winnr . ',"&foldenable",1)')
7141         endif
7142         setlocal scrollbind nowrap nofoldenable
7143         let top = line('w0') + &scrolloff
7144         let current = line('.')
7145         exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
7146         let w:fugitive_leave = join(restore, '|')
7147         execute top
7148         normal! zt
7149         execute current
7150         setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
7151         if exists('&winfixbuf')
7152           setlocal winfixbuf
7153         endif
7154         if exists('+relativenumber')
7155           setlocal norelativenumber
7156         endif
7157         if exists('+signcolumn')
7158           setlocal signcolumn=no
7159         endif
7160         execute "vertical resize ".(s:linechars('.\{-\}\s\+\d\+\ze)')+1)
7161         redraw
7162         syncbind
7163         exe s:DoAutocmdChanged(temp_state)
7164       endif
7165     endtry
7166     return ''
7167   catch /^fugitive:/
7168     return 'echoerr ' . string(v:exception)
7169   endtry
7170 endfunction
7172 function! s:BlameCommit(cmd, ...) abort
7173   let line = a:0 ? a:1 : getline('.')
7174   let state = a:0 ? a:2 : s:TempState()
7175   let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
7176   let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
7177   let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
7178   if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
7179     let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
7180     return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
7181   endif
7182   if commit =~# '^0*$'
7183     return 'echoerr ' . string('fugitive: no commit')
7184   endif
7185   if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
7186     let path = commit . ':' . path
7187     return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
7188   endif
7189   let cmd = fugitive#Open(mods . a:cmd, 0, '', commit, [commit])
7190   if cmd =~# '^echoerr'
7191     return cmd
7192   endif
7193   execute cmd
7194   if a:cmd ==# 'pedit' || empty(path)
7195     return ''
7196   endif
7197   if search('^diff .* b/\M'.escape(path,'\').'$','W')
7198     call search('^+++')
7199     let head = line('.')
7200     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
7201       let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
7202       let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
7203       if lnum >= top && lnum <= top + len
7204         let offset = lnum - top
7205         if &scrolloff
7206           +
7207           normal! zt
7208         else
7209           normal! zt
7210           +
7211         endif
7212         while offset > 0 && line('.') < line('$')
7213           +
7214           if getline('.') =~# '^[ ' . sigil . ']'
7215             let offset -= 1
7216           endif
7217         endwhile
7218         return 'normal! zv'
7219       endif
7220     endwhile
7221     execute head
7222     normal! zt
7223   endif
7224   return ''
7225 endfunction
7227 function! s:BlameJump(suffix, ...) abort
7228   let suffix = a:suffix
7229   let [commit, path, lnum] = s:BlameCommitFileLnum()
7230   if empty(path)
7231     return 'echoerr ' . string('fugitive: could not determine filename for blame')
7232   endif
7233   if commit =~# '^0*$'
7234     let commit = '@'
7235     let suffix = ''
7236   endif
7237   let offset = line('.') - line('w0')
7238   let state = s:TempState()
7239   let flags = get(state, 'blame_flags', [])
7240   let blame_bufnr = s:BlameBufnr()
7241   if blame_bufnr > 0
7242     let bufnr = bufnr('')
7243     let winnr = bufwinnr(blame_bufnr)
7244     if winnr > 0
7245       exe winnr.'wincmd w'
7246       exe bufnr.'bdelete'
7247     endif
7248     execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
7249     execute lnum
7250   endif
7251   let my_bufnr = bufnr('')
7252   if blame_bufnr < 0
7253     let blame_args = flags + [commit . suffix, '--', path]
7254     let result = s:BlameSubcommand(0, 0, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
7255   else
7256     let blame_args = flags
7257     let result = s:BlameSubcommand(-1, -1, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
7258   endif
7259   if bufnr('') == my_bufnr
7260     return result
7261   endif
7262   execute result
7263   execute lnum
7264   let delta = line('.') - line('w0') - offset
7265   if delta > 0
7266     execute 'normal! '.delta."\<C-E>"
7267   elseif delta < 0
7268     execute 'normal! '.(-delta)."\<C-Y>"
7269   endif
7270   keepjumps syncbind
7271   redraw
7272   echo ':Git blame' s:fnameescape(blame_args)
7273   return ''
7274 endfunction
7276 let s:hash_colors = {}
7278 function! fugitive#BlameSyntax() abort
7279   let conceal = has('conceal') ? ' conceal' : ''
7280   let flags = get(s:TempState(), 'blame_flags', [])
7281   syn spell notoplevel
7282   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
7283   syn match FugitiveblameHash       "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7284   if s:HasOpt(flags, '-b') || FugitiveConfigGet('blame.blankBoundary') =~# '^1$\|^true$'
7285     syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7286   else
7287     syn match FugitiveblameBoundary "^\^"
7288   endif
7289   syn match FugitiveblameScoreDebug        " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
7290   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
7291   syn match FugitiveblameTime "\<[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
7292   exec 'syn match FugitiveblameLineNumber         "\s[[:digit:][:space:]]\{0,' . (len(line('$'))-1). '\}\d)\@=" contained containedin=FugitiveblameAnnotation' conceal
7293   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)
7294   exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
7295   exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
7296   syn match FugitiveblameShort              " \+\d\+)" contained contains=FugitiveblameLineNumber
7297   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
7298   hi def link FugitiveblameBoundary           Keyword
7299   hi def link FugitiveblameHash               Identifier
7300   hi def link FugitiveblameBoundaryIgnore     Ignore
7301   hi def link FugitiveblameUncommitted        Ignore
7302   hi def link FugitiveblameScoreDebug         Debug
7303   hi def link FugitiveblameTime               PreProc
7304   hi def link FugitiveblameLineNumber         Number
7305   hi def link FugitiveblameOriginalFile       String
7306   hi def link FugitiveblameOriginalLineNumber Float
7307   hi def link FugitiveblameShort              FugitiveblameDelimiter
7308   hi def link FugitiveblameDelimiter          Delimiter
7309   hi def link FugitiveblameNotCommittedYet    Comment
7310   if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
7311     return
7312   endif
7313   let seen = {}
7314   for x in split('01234567890abcdef', '\zs')
7315     exe 'syn match FugitiveblameHashGroup'.x '"\%(^\^\=[*?]*\)\@<='.x.'\x\{5,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
7316   endfor
7317   for lnum in range(1, line('$'))
7318     let orig_hash = matchstr(getline(lnum), '^\^\=[*?]*\zs\x\{6\}')
7319     let hash = orig_hash
7320     let hash = substitute(hash, '\(\x\)\x', '\=submatch(1).printf("%x", 15-str2nr(submatch(1),16))', 'g')
7321     let hash = substitute(hash, '\(\x\x\)', '\=printf("%02x", str2nr(submatch(1),16)*3/4+32)', 'g')
7322     if hash ==# '' || orig_hash ==# '000000' || has_key(seen, hash)
7323       continue
7324     endif
7325     let seen[hash] = 1
7326     if &t_Co == 256
7327       let [s, r, g, b; __] = map(matchlist(orig_hash, '\(\x\)\x\(\x\)\x\(\x\)\x'), 'str2nr(v:val,16)')
7328       let color = 16 + (r + 1) / 3 * 36 + (g + 1) / 3 * 6 + (b + 1) / 3
7329       if color == 16
7330         let color = 235
7331       elseif color == 231
7332         let color = 255
7333       endif
7334       let s:hash_colors[hash] = ' ctermfg='.color
7335     else
7336       let s:hash_colors[hash] = ''
7337     endif
7338     let pattern = substitute(orig_hash, '^\(\x\)\x\(\x\)\x\(\x\)\x$', '\1\\x\2\\x\3\\x', '') . '*'
7339     exe 'syn match FugitiveblameHash'.hash.'       "\%(^\^\=[*?]*\)\@<='.pattern.'" contained containedin=FugitiveblameHashGroup' . orig_hash[0]
7340   endfor
7341   syn match FugitiveblameUncommitted "\%(^\^\=[?*]*\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7342   call s:BlameRehighlight()
7343 endfunction
7345 function! s:BlameRehighlight() abort
7346   for [hash, cterm] in items(s:hash_colors)
7347     if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
7348       exe 'hi FugitiveblameHash'.hash.' guifg=#' . hash . cterm
7349     else
7350       exe 'hi link FugitiveblameHash'.hash.' Identifier'
7351     endif
7352   endfor
7353 endfunction
7355 function! s:BlameMaps(is_ftplugin) abort
7356   let ft = a:is_ftplugin
7357   call s:MapGitOps(ft)
7358   call s:Map('n', '<F1>', ':help :Git_blame<CR>', '<silent>', ft)
7359   call s:Map('n', 'g?',   ':help :Git_blame<CR>', '<silent>', ft)
7360   call s:Map('n', 'gq',   ':exe <SID>BlameQuit()<CR>', '<silent>', ft)
7361   call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7362   call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7363   call s:Map('n', '-',    ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7364   call s:Map('n', 's',    ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7365   call s:Map('n', 'u',    ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7366   call s:Map('n', 'P',    ':<C-U>if !v:count<Bar>echoerr "Use ~ (or provide a count)"<Bar>else<Bar>exe <SID>BlameJump("^".v:count1)<Bar>endif<CR>', '<silent>', ft)
7367   call s:Map('n', '~',    ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>', ft)
7368   call s:Map('n', 'i',    ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7369   call s:Map('n', 'o',    ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>', ft)
7370   call s:Map('n', 'O',    ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>', ft)
7371   call s:Map('n', 'p',    ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>', ft)
7372   exe s:Map('n', '.',    ":<C-U> <C-R>=substitute(<SID>BlameCommitFileLnum()[0],'^$','@','')<CR><Home>", '', ft)
7373   exe s:Map('n', '(',    "-", '', ft)
7374   exe s:Map('n', ')',    "+", '', ft)
7375   call s:Map('n', 'A',    ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>', ft)
7376   call s:Map('n', 'C',    ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>', ft)
7377   call s:Map('n', 'D',    ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>', ft)
7378 endfunction
7380 function! fugitive#BlameFileType() abort
7381   setlocal nomodeline
7382   setlocal foldmethod=manual
7383   if len(s:GitDir())
7384     let &l:keywordprg = s:Keywordprg()
7385   endif
7386   let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
7387   if exists('+concealcursor')
7388     setlocal concealcursor=nc conceallevel=2
7389     let b:undo_ftplugin .= ' concealcursor< conceallevel<'
7390   endif
7391   if &modifiable
7392     return ''
7393   endif
7394   call s:BlameMaps(1)
7395 endfunction
7397 function! s:BlameCursorSync(bufnr, line) abort
7398   if a:line == line('.')
7399     return
7400   endif
7401   if get(s:TempState(), 'origin_bufnr') == a:bufnr || get(s:TempState(a:bufnr), 'origin_bufnr') == bufnr('')
7402     if &startofline
7403       execute a:line
7404     else
7405       let pos = getpos('.')
7406       let pos[1] = a:line
7407       call setpos('.', pos)
7408     endif
7409   endif
7410 endfunction
7412 augroup fugitive_blame
7413   autocmd!
7414   autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
7415   autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
7416   autocmd WinLeave * let s:cursor_for_blame = [bufnr(''), line('.')]
7417   autocmd WinEnter * if exists('s:cursor_for_blame') | call call('s:BlameCursorSync', s:cursor_for_blame) | endif
7418 augroup END
7420 " Section: :GBrowse
7422 function! s:BrowserOpen(url, mods, echo_copy) abort
7423   let [_, main, query, anchor; __] = matchlist(a:url, '^\([^#?]*\)\(?[^#]*\)\=\(#.*\)\=')
7424   let url = main . tr(query, ' ', '+') . anchor
7425   let url = substitute(url, '[ <>\|"]', '\="%".printf("%02X",char2nr(submatch(0)))', 'g')
7426   let mods = s:Mods(a:mods)
7427   if a:echo_copy
7428     if has('clipboard')
7429       let @+ = url
7430     endif
7431     return 'echo '.string(url)
7432   elseif exists(':Browse') == 2
7433     return 'echo '.string(url).'|' . mods . 'Browse '.url
7434   elseif exists(':OpenBrowser') == 2
7435     return 'echo '.string(url).'|' . mods . 'OpenBrowser '.url
7436   else
7437     if !exists('g:loaded_netrw')
7438       runtime! autoload/netrw.vim
7439     endif
7440     if exists('*netrw#BrowseX')
7441       return 'echo '.string(url).'|' . mods . 'call netrw#BrowseX('.string(url).', 0)'
7442     elseif exists('*netrw#NetrwBrowseX')
7443       return 'echo '.string(url).'|' . mods . 'call netrw#NetrwBrowseX('.string(url).', 0)'
7444     elseif has('nvim-0.10')
7445       return mods . 'echo luaeval("({vim.ui.open(_A)})[2] or _A", ' . string(url) . ')'
7446     else
7447       return 'echoerr ' . string('Netrw not found. Define your own :Browse to use :GBrowse')
7448     endif
7449   endif
7450 endfunction
7452 function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, ...) abort
7453   exe s:VersionCheck()
7454   let dir = s:Dir()
7455   try
7456     let arg = a:arg
7457     if arg =~# '^++\%([Gg]it\)\=[Rr]emote='
7458       let remote = matchstr(arg, '^++\%([Gg]it\)\=[Rr]emote=\zs\S\+')
7459       let arg = matchstr(arg, '\s\zs\S.*')
7460     endif
7461     let validremote = '\.\%(git\)\=\|\.\=/.*\|\a[[:alnum:]_-]*\%(://.\{-\}\)\='
7462     if arg ==# '-'
7463       let remote = ''
7464       let rev = ''
7465       let result = fugitive#Result()
7466       if filereadable(get(result, 'file', ''))
7467         let rev = s:fnameescape(result.file)
7468       else
7469         return 'echoerr ' . string('fugitive: could not find prior :Git invocation')
7470       endif
7471     elseif !exists('l:remote')
7472       let remote = matchstr(arg, '\\\@<!\%(\\\\\)*[!@]\zs\%('.validremote.'\)$')
7473       let rev = strpart(arg, 0, len(arg) - len(remote) - (empty(remote) ? 0 : 1))
7474     else
7475       let rev = arg
7476     endif
7477     let expanded = s:Expand(rev)
7478     if expanded =~? '^\a\a\+:[\/][\/]' && expanded !~? '^fugitive:'
7479       return s:BrowserOpen(s:Slash(expanded), a:mods, a:bang)
7480     endif
7481     if !exists('l:result')
7482       let result = s:TempState(empty(expanded) ? bufnr('') : expanded)
7483     endif
7484     if !get(result, 'origin_bufnr', 1) && filereadable(get(result, 'file', ''))
7485       for line in readfile(result.file, '', 4096)
7486         let rev = s:fnameescape(matchstr(line, '\<https\=://[^[:space:]<>]*[^[:space:]<>.,;:"''!?]'))
7487         if len(rev)
7488           return s:BrowserOpen(rev, a:mods, a:bang)
7489         endif
7490       endfor
7491       return 'echoerr ' . string('fugitive: no URL found in output of :Git')
7492     endif
7493     if empty(remote) && expanded =~# '^[^-./:^~][^:^~]*$' && !empty(dir)
7494       let config = fugitive#Config(dir)
7495       if !empty(FugitiveConfigGet('remote.' . expanded . '.url', config))
7496         let remote = expanded
7497         let expanded = ''
7498       endif
7499     endif
7500     if empty(expanded)
7501       let bufname = &buftype =~# '^\%(nofile\|terminal\)$' ? '' : s:BufName('%')
7502       let expanded = s:DirRev(bufname)[1]
7503       if empty(expanded)
7504         let expanded = fugitive#Path(bufname, ':(top)', dir)
7505       endif
7506       if a:count > 0 && has_key(result, 'origin_bufnr') && a:range != 2
7507         let blame = s:BlameCommitFileLnum(getline(a:count))
7508         if len(blame[0])
7509           let expanded = blame[0]
7510         endif
7511       endif
7512     endif
7513     let full = s:Generate(expanded, dir)
7514     let commit = ''
7515     let ref = ''
7516     let forbid_ref_as_commit = 0
7517     if full =~# '^fugitive:'
7518       let [dir, commit, path] = s:DirCommitFile(full)
7519       if commit =~# '^\d\=$'
7520         let commit = ''
7521         let type = path =~# '^/\=$' ? 'tree' : 'blob'
7522       else
7523         let ref_match = matchlist(expanded, '^\(@{\@!\|[^:~^@]\+\)\(:\%(//\)\@!\|[~^@]\|$\)')
7524         let ref = get(ref_match, 1, '')
7525         let forbid_ref_as_commit = ref =~# '^@\=$' || ref_match[2] !~# '^:\=$'
7526         if empty(path) && !forbid_ref_as_commit
7527           let type = 'ref'
7528         else
7529           let type = s:ChompDefault(empty(path) ? 'commit': 'blob',
7530                 \ ['cat-file', '-t', commit . substitute(path, '^/', ':', '')], dir)
7531         endif
7532       endif
7533       let path = path[1:-1]
7534     elseif !empty(s:Tree(dir))
7535       let relevant_dir = FugitiveExtractGitDir(full)
7536       if !empty(relevant_dir)
7537         let dir = relevant_dir
7538       endif
7539       let path = fugitive#Path(full, '/', dir)[1:-1]
7540       if empty(path) || isdirectory(full)
7541         let type = 'tree'
7542       else
7543         let type = 'blob'
7544       endif
7545     else
7546       let path = '.git/' . full[strlen(dir)+1:-1]
7547       let type = ''
7548     endif
7549     exe s:DirCheck(dir)
7550     if path =~# '^\.git/'
7551       let ref = matchstr(path, '^.git/\zs\%(refs/[^/]\+/[^/].*\|\w*HEAD\)$')
7552       let type = empty(ref) ? 'root': 'ref'
7553       let path = ''
7554     endif
7555     if empty(ref) || ref ==# 'HEAD' || ref ==# '@'
7556       let ref = fugitive#Head(-1, dir)
7557     endif
7558     if ref =~# '^\x\{40,\}$'
7559       let ref = ''
7560     elseif !empty(ref) && ref !~# '^refs/'
7561       let ref = FugitiveExecute(['rev-parse', '--symbolic-full-name', ref], dir).stdout[0]
7562       if ref !~# '^refs/'
7563         let ref = ''
7564       endif
7565     endif
7567     if !exists('l:config') || s:Dir(config) !=# dir
7568       let config = fugitive#Config(dir)
7569     endif
7570     let merge = ''
7571     if !empty(remote) && ref =~# '^refs/remotes/[^/]\+/[^/]\|^refs/heads/[^/]'
7572       let merge = matchstr(ref, '^refs/\%(heads/\|remotes/[^/]\+/\)\zs.\+')
7573       let ref = 'refs/heads/' . merge
7574     elseif ref =~# '^refs/remotes/[^/]\+/[^/]'
7575       let remote = matchstr(ref, '^refs/remotes/\zs[^/]\+')
7576       let merge = matchstr(ref, '^refs/remotes/[^/]\+/\zs.\+')
7577       let ref = 'refs/heads/' . merge
7578     elseif ref =~# '^refs/heads/[^/]'
7579       let merge = strpart(ref, 11)
7580       let r = FugitiveConfigGet('branch.' . merge . '.remote', config)
7581       let m = FugitiveConfigGet('branch.' . merge . '.merge', config)[11:-1]
7582       if r ==# '.' && !empty(m)
7583         let r2 = FugitiveConfigGet('branch.'.m.'.remote', config)
7584         if r2 !~# '^\.\=$'
7585           let r = r2
7586           let m = FugitiveConfigGet('branch.'.m.'.merge', config)[11:-1]
7587         endif
7588       endif
7589       if r !~# '^\.\=$'
7590         let remote = r
7591       endif
7592       if !empty(remote)
7593         let remote_ref = 'refs/remotes/' . remote . '/' . merge
7594         if FugitiveConfigGet('push.default', config) ==# 'upstream' ||
7595               \ !filereadable(FugitiveFind('.git/' . remote_ref, dir)) && empty(s:ChompDefault('', ['rev-parse', '--verify', remote_ref, '--'], dir))
7596           let merge = m
7597           let ref = 'refs/heads/' . merge
7598         endif
7599       endif
7600     endif
7602     if empty(remote) || remote ==# '.'
7603       let remote = s:RemoteDefault(config)
7604     endif
7605     if empty(merge) || empty(remote)
7606       let provider_ref = ref
7607     else
7608       let provider_ref = 'refs/remotes/' . remote . '/' . merge
7609     endif
7610     if forbid_ref_as_commit || a:count >= 0
7611       let ref = ''
7612       if type ==# 'ref'
7613         let type = 'commit'
7614       endif
7615     elseif type ==# 'ref' && ref =~# '^refs/\%(heads\|tags\)/[^/]'
7616         let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
7617     endif
7619     let line1 = a:count > 0 && type ==# 'blob' ? a:line1 : 0
7620     let line2 = a:count > 0 && type ==# 'blob' ? a:count : 0
7621     if empty(commit) && type =~# '^\%(tree\|blob\)$'
7622       if a:count < 0
7623         let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
7624       elseif len(provider_ref)
7625         let owner = s:Owner(@%, dir)
7626         let commit = s:ChompDefault('', ['merge-base', provider_ref, empty(owner) ? '@' : owner, '--'], dir)
7627         if line2 > 0 && empty(arg) && commit =~# '^\x\{40,\}$' && type ==# 'blob'
7628           let blame_list = tempname()
7629           call writefile([commit, ''], blame_list, 'b')
7630           let blame_cmd = ['-c', 'blame.coloring=none', 'blame', '-L', line1.','.line2, '-S', blame_list, '-s', '--show-number']
7631           if !&l:modified || has_key(result, 'origin_bufnr')
7632             let [blame, exec_error] = s:LinesError(blame_cmd + ['./' . path], dir)
7633           else
7634             let blame_in = tempname()
7635             silent exe 'noautocmd keepalt %write' blame_in
7636             let [blame, exec_error] = s:LinesError(blame_cmd + ['--contents', blame_in, './' . path], dir)
7637             call delete(blame_in)
7638           endif
7639           call delete(blame_list)
7640           if !exec_error
7641             let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
7642             if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
7643               let line1 = +matchstr(blame[0], blame_regex)
7644               let line2 = +matchstr(blame[-1], blame_regex)
7645             else
7646               throw "fugitive: can't browse to unpushed change"
7647             endif
7648           endif
7649         endif
7650       endif
7651       if empty(commit)
7652         let commit = fugitive#RevParse(empty(ref) ? 'HEAD' : ref, dir)
7653       endif
7654     endif
7656     if remote =~# ':'
7657       let remote_url = remote
7658     else
7659       let remote_url = fugitive#RemoteUrl(remote, config)
7660     endif
7661     let raw = empty(remote_url) ? remote : remote_url
7662     let git_dir = s:GitDir(dir)
7664     let opts = {
7665           \ 'git_dir': git_dir,
7666           \ 'repo': {'git_dir': git_dir},
7667           \ 'remote': raw,
7668           \ 'remote_name': remote,
7669           \ 'commit': s:UrlEncode(commit),
7670           \ 'path': substitute(s:UrlEncode(path), '%20', ' ', 'g'),
7671           \ 'type': type,
7672           \ 'line1': line1,
7673           \ 'line2': line2}
7675     if empty(path)
7676       if type ==# 'ref' && ref =~# '^refs/'
7677         let opts.path = '.git/' . s:UrlEncode(ref)
7678         let opts.type = ''
7679       elseif type ==# 'root'
7680         let opts.path ='.git/index'
7681         let opts.type = ''
7682       endif
7683     elseif type ==# 'tree' && !empty(path)
7684       let opts.path = s:sub(opts.path, '/\=$', '/')
7685     endif
7687     for l:.Handler in get(g:, 'fugitive_browse_handlers', [])
7688       let l:.url = call(Handler, [copy(opts)])
7689       if type(url) == type('') && url =~# '://'
7690         return s:BrowserOpen(url, a:mods, a:bang)
7691       endif
7692     endfor
7694     if !empty(remote_url)
7695       return 'echoerr ' . string("fugitive: no GBrowse handler installed for '".remote_url."'")
7696     else
7697       return 'echoerr ' . string("fugitive: could not find remote named '".remote."'")
7698     endif
7699   catch /^fugitive:/
7700     return 'echoerr ' . string(v:exception)
7701   endtry
7702 endfunction
7704 function! s:RemoteRefToLocalRef(repo, remote_url, ref_path) abort
7705   let ref_path = substitute(a:ref_path, ':', '/', '')
7706   let rev = ''
7707   if ref_path =~# '^\x\{40,\}\%(/\|$\)'
7708     let rev = substitute(ref_path, '/', ':', '')
7709   elseif ref_path =~# '^[^:/^~]\+'
7710     let first_component = matchstr(ref_path, '^[^:/^~]\+')
7711     let lines = fugitive#Execute(['ls-remote', a:remote_url, first_component, first_component . '/*'], a:repo).stdout[0:-2]
7712     for line in lines
7713       let full = matchstr(line, "\t\\zs.*")
7714       for candidate in [full, matchstr(full, '^refs/\w\+/\zs.*')]
7715         if candidate ==# first_component || strpart(ref_path . '/', 0, len(candidate) + 1) ==# candidate . '/'
7716           let rev = matchstr(line, '^\x\+') . substitute(strpart(ref_path, len(candidate)), '/', ':', '')
7717         endif
7718       endfor
7719     endfor
7720   endif
7721   if empty(rev)
7722     return ''
7723   endif
7724   let commitish = matchstr(rev, '^[^:^~]*')
7725   let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo)
7726   if rev_parse.exit_status
7727     if fugitive#Execute(['fetch', remote_url, commitish], a:repo).exit_status
7728       return ''
7729     endif
7730     let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo)
7731   endif
7732   if rev_parse.exit_status
7733     return ''
7734   endif
7735   return rev_parse.stdout[0] . matchstr(rev, ':.*')
7736 endfunction
7738 function! fugitive#ResolveUrl(target, ...) abort
7739   let repo = call('s:Dir', a:000)
7740   let origins = get(g:, 'fugitive_url_origins', {})
7741   let prefix = substitute(s:Slash(a:target), '#.*', '', '')
7742   while prefix =~# '://'
7743     let extracted = FugitiveExtractGitDir(expand(get(origins, prefix, '')))
7744     if !empty(extracted)
7745       let repo = s:Dir(extracted)
7746       break
7747     endif
7748     let prefix = matchstr(prefix, '.*\ze/')
7749   endwhile
7750   let git_dir = s:GitDir(repo)
7751   for remote_name in keys(FugitiveConfigGetRegexp('^remote\.\zs.*\ze\.url$', repo))
7752     let remote_url = fugitive#RemoteUrl(remote_name, repo)
7753     for [no_anchor; variant] in [[1, 'commit'], [1, 'tree'], [1, 'tree', 1], [1, 'blob', 1], [0, 'blob', 1, '1`line1`', '1`line1`'], [0, 'blob', 1, '1`line1`', '2`line2`']]
7754       let handler_opts = {
7755             \ 'git_dir': git_dir,
7756             \ 'repo': {'git_dir': git_dir},
7757             \ 'remote': remote_url,
7758             \ 'remote_name': remote_name,
7759             \ 'commit': '1`commit`',
7760             \ 'type': get(variant, 0),
7761             \ 'path': get(variant, 1) ? '1`path`' : '',
7762             \ 'line1': get(variant, 2),
7763             \ 'line2': get(variant, 3)}
7764       let url = ''
7765       for l:.Handler in get(g:, 'fugitive_browse_handlers', [])
7766         let l:.url = call(Handler, [copy(handler_opts)])
7767         if type(url) == type('') && url =~# '://'
7768           break
7769         endif
7770       endfor
7771       if type(url) != type('') || url !~# '://'
7772         continue
7773       endif
7774       let keys = split(substitute(url, '\d`\(\w\+`\)\|.', '\1', 'g'), '`')
7775       let pattern = substitute(url, '\d`\w\+`\|[][^$.*\~]', '\=len(submatch(0)) == 1 ? "\\" . submatch(0) : "\\([^#?&;]\\{-\\}\\)"', 'g')
7776       let pattern = '^' . substitute(pattern, '^https\=:', 'https\\=:', '') . '$'
7777       let target = s:Slash(no_anchor ? substitute(a:target, '#.*', '', '') : a:target)
7778       let values = matchlist(s:Slash(a:target), pattern)[1:-1]
7779       if empty(values)
7780         continue
7781       endif
7782       let kvs = {}
7783       for i in range(len(keys))
7784         let kvs[keys[i]] = values[i]
7785       endfor
7786       if has_key(kvs, 'commit') && has_key(kvs, 'path')
7787         let ref_path = kvs.commit . '/' . kvs.path
7788       elseif has_key(kvs, 'commit') && variant[0] ==# 'tree'
7789         let ref_path = kvs.commit . '/'
7790       elseif has_key(kvs, 'commit')
7791         let ref_path = kvs.commit
7792       else
7793         continue
7794       endif
7795       let rev = s:RemoteRefToLocalRef(repo, remote_url, fugitive#UrlDecode(ref_path))
7796       return [fugitive#Find(rev, repo), empty(rev) ? 0 : +get(kvs, 'line1')]
7797     endfor
7798   endfor
7799   return ['', 0]
7800 endfunction
7802 function! s:ResolveUrl(target, ...) abort
7803   try
7804     let [url, lnum] = call('fugitive#ResolveUrl', [a:target] + a:000)
7805     if !empty(url)
7806       return [url, lnum]
7807     endif
7808   catch
7809   endtry
7810   return [substitute(a:target, '#.*', '', ''), 0]
7811 endfunction
7813 " Section: Maps
7815 let s:ref_header = '\%(Merge\|Rebase\|Upstream\|Pull\|Push\)'
7817 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
7818 function! fugitive#MapCfile(...) abort
7819   exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
7820   let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
7821   if !exists('g:fugitive_no_maps')
7822     call s:Map('n', 'gf',          '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
7823     call s:Map('n', '<C-W>f',     '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
7824     call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
7825     call s:Map('n', '<C-W>gf',  '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
7826     call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<unique>', 1)
7827   endif
7828 endfunction
7830 function! s:ContainingCommit() abort
7831   let commit = s:Owner(@%)
7832   return empty(commit) ? '@' : commit
7833 endfunction
7835 function! s:SquashArgument(...) abort
7836   if &filetype == 'fugitive'
7837     let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze \|^' . s:ref_header . ': \zs\S\+')
7838   elseif has_key(s:temp_files, s:cpath(expand('%:p')))
7839     let commit = matchstr(getline('.'), '\S\@<!\x\{4,\}\S\@!')
7840   else
7841     let commit = s:Owner(@%)
7842   endif
7843   return len(commit) && a:0 ? printf(a:1, commit) : commit
7844 endfunction
7846 function! s:RebaseArgument() abort
7847   return s:SquashArgument(' %s^')
7848 endfunction
7850 function! s:NavigateUp(count) abort
7851   let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
7852   let c = a:count
7853   while c
7854     if rev =~# ':.*/.'
7855       let rev = matchstr(rev, '.*\ze/.\+', '')
7856     elseif rev =~# '.:.'
7857       let rev = matchstr(rev, '^.[^:]*:')
7858     elseif rev =~# '^:'
7859       let rev = '@^{}'
7860     elseif rev =~# ':$'
7861       let rev = rev[0:-2]
7862     else
7863       return rev.'~'.c
7864     endif
7865     let c -= 1
7866   endwhile
7867   return rev
7868 endfunction
7870 function! s:ParseDiffHeader(str) abort
7871   let list = matchlist(a:str, '\Cdiff --git \("\=\w/.*\|/dev/null\) \("\=\w/.*\|/dev/null\)$')
7872   if empty(list)
7873     let list = matchlist(a:str, '\Cdiff --git \("\=[^/].*\|/dev/null\) \("\=[^/].*\|/dev/null\)$')
7874   endif
7875   return [fugitive#Unquote(get(list, 1, '')), fugitive#Unquote(get(list, 2, ''))]
7876 endfunction
7878 function! s:HunkPosition(lnum) abort
7879   let lnum = a:lnum + get({'@': 1, '\': -1}, getline(a:lnum)[0], 0)
7880   let offsets = {' ': -1, '+': 0, '-': 0}
7881   let sigil = getline(lnum)[0]
7882   let line_char = sigil
7883   while has_key(offsets, line_char)
7884     let offsets[line_char] += 1
7885     let lnum -= 1
7886     let line_char = getline(lnum)[0]
7887   endwhile
7888   let starts = matchlist(getline(lnum), '^@@\+[ 0-9,-]* -\(\d\+\)\%(,\d\+\)\= +\(\d\+\)[ ,]')
7889   if empty(starts)
7890     return [0, 0, 0]
7891   endif
7892   return [lnum,
7893         \ sigil ==# '+' ? 0 : starts[1] + offsets[' '] + offsets['-'],
7894         \ sigil ==# '-' ? 0 : starts[2] + offsets[' '] + offsets['+']]
7895 endfunction
7897 function! s:MapMotion(lhs, rhs) abort
7898   let maps = [
7899         \ s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
7900         \ s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
7901         \ s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")]
7902   call filter(maps, '!empty(v:val)')
7903   return join(maps, '|')
7904 endfunction
7906 function! s:MapGitOps(is_ftplugin) abort
7907   let ft = a:is_ftplugin
7908   if &modifiable
7909     return ''
7910   endif
7911   exe s:Map('n', 'c<Space>', ':Git commit<Space>', '', ft)
7912   exe s:Map('n', 'c<CR>', ':Git commit<CR>', '', ft)
7913   exe s:Map('n', 'cv<Space>', ':tab Git commit -v<Space>', '', ft)
7914   exe s:Map('n', 'cv<CR>', ':tab Git commit -v<CR>', '', ft)
7915   exe s:Map('n', 'ca', ':<C-U>Git commit --amend<CR>', '<silent>', ft)
7916   exe s:Map('n', 'cc', ':<C-U>Git commit<CR>', '<silent>', ft)
7917   exe s:Map('n', 'ce', ':<C-U>Git commit --amend --no-edit<CR>', '<silent>', ft)
7918   exe s:Map('n', 'cw', ':<C-U>Git commit --amend --only<CR>', '<silent>', ft)
7919   exe s:Map('n', 'cva', ':<C-U>tab Git commit -v --amend<CR>', '<silent>', ft)
7920   exe s:Map('n', 'cvc', ':<C-U>tab Git commit -v<CR>', '<silent>', ft)
7921   exe s:Map('n', 'cRa', ':<C-U>Git commit --reset-author --amend<CR>', '<silent>', ft)
7922   exe s:Map('n', 'cRe', ':<C-U>Git commit --reset-author --amend --no-edit<CR>', '<silent>', ft)
7923   exe s:Map('n', 'cRw', ':<C-U>Git commit --reset-author --amend --only<CR>', '<silent>', ft)
7924   exe s:Map('n', 'cf', ':<C-U>Git commit --fixup=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7925   exe s:Map('n', 'cF', ':<C-U><Bar>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Git commit --fixup=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7926   exe s:Map('n', 'cs', ':<C-U>Git commit --no-edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7927   exe s:Map('n', 'cS', ':<C-U><Bar>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Git commit --no-edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7928   exe s:Map('n', 'cA', ':<C-U>Git commit --edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7929   exe s:Map('n', 'c?', ':<C-U>help fugitive_c<CR>', '<silent>', ft)
7931   exe s:Map('n', 'cr<Space>', ':Git revert<Space>', '', ft)
7932   exe s:Map('n', 'cr<CR>', ':Git revert<CR>', '', ft)
7933   exe s:Map('n', 'crc', ':<C-U>Git revert <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>', ft)
7934   exe s:Map('n', 'crn', ':<C-U>Git revert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>', ft)
7935   exe s:Map('n', 'cr?', ':<C-U>help fugitive_cr<CR>', '<silent>', ft)
7937   exe s:Map('n', 'cm<Space>', ':Git merge<Space>', '', ft)
7938   exe s:Map('n', 'cm<CR>', ':Git merge<CR>', '', ft)
7939   exe s:Map('n', 'cmt', ':Git mergetool', '', ft)
7940   exe s:Map('n', 'cm?', ':<C-U>help fugitive_cm<CR>', '<silent>', ft)
7942   exe s:Map('n', 'cz<Space>', ':Git stash<Space>', '', ft)
7943   exe s:Map('n', 'cz<CR>', ':Git stash<CR>', '', ft)
7944   exe s:Map('n', 'cza', ':<C-U>Git stash apply --quiet --index stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7945   exe s:Map('n', 'czA', ':<C-U>Git stash apply --quiet stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7946   exe s:Map('n', 'czp', ':<C-U>Git stash pop --quiet --index stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7947   exe s:Map('n', 'czP', ':<C-U>Git stash pop --quiet stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7948   exe s:Map('n', 'czs', ':<C-U>Git stash push --staged<CR>', '', ft)
7949   exe s:Map('n', 'czv', ':<C-U>exe "Gedit" fugitive#RevParse("stash@{" . v:count . "}")<CR>', '<silent>', ft)
7950   exe s:Map('n', 'czw', ':<C-U>Git stash push --keep-index<C-R>=v:count > 1 ? " --all" : v:count ? " --include-untracked" : ""<CR><CR>', '', ft)
7951   exe s:Map('n', 'czz', ':<C-U>Git stash push <C-R>=v:count > 1 ? " --all" : v:count ? " --include-untracked" : ""<CR><CR>', '', ft)
7952   exe s:Map('n', 'cz?', ':<C-U>help fugitive_cz<CR>', '<silent>', ft)
7954   exe s:Map('n', 'co<Space>', ':Git checkout<Space>', '', ft)
7955   exe s:Map('n', 'co<CR>', ':Git checkout<CR>', '', ft)
7956   exe s:Map('n', 'coo', ':<C-U>Git checkout <C-R>=substitute(<SID>SquashArgument(),"^$",get(<SID>TempState(),"filetype","") ==# "git" ? expand("<cfile>") : "","")<CR> --<CR>', '', ft)
7957   exe s:Map('n', 'co?', ':<C-U>help fugitive_co<CR>', '<silent>', ft)
7959   exe s:Map('n', 'cb<Space>', ':Git branch<Space>', '', ft)
7960   exe s:Map('n', 'cb<CR>', ':Git branch<CR>', '', ft)
7961   exe s:Map('n', 'cb?', ':<C-U>help fugitive_cb<CR>', '<silent>', ft)
7963   exe s:Map('n', 'r<Space>', ':Git rebase<Space>', '', ft)
7964   exe s:Map('n', 'r<CR>', ':Git rebase<CR>', '', ft)
7965   exe s:Map('n', 'ri', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>', ft)
7966   exe s:Map('n', 'rf', ':<C-U>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>', ft)
7967   exe s:Map('n', 'ru', ':<C-U>Git rebase --interactive @{upstream}<CR>', '<silent>', ft)
7968   exe s:Map('n', 'rp', ':<C-U>Git rebase --interactive @{push}<CR>', '<silent>', ft)
7969   exe s:Map('n', 'rw', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>', '<silent>', ft)
7970   exe s:Map('n', 'rm', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>', '<silent>', ft)
7971   exe s:Map('n', 'rd', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7972   exe s:Map('n', 'rk', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7973   exe s:Map('n', 'rx', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7974   exe s:Map('n', 'rr', ':<C-U>Git rebase --continue<CR>', '<silent>', ft)
7975   exe s:Map('n', 'rs', ':<C-U>Git rebase --skip<CR>', '<silent>', ft)
7976   exe s:Map('n', 're', ':<C-U>Git rebase --edit-todo<CR>', '<silent>', ft)
7977   exe s:Map('n', 'ra', ':<C-U>Git rebase --abort<CR>', '<silent>', ft)
7978   exe s:Map('n', 'r?', ':<C-U>help fugitive_r<CR>', '<silent>', ft)
7979 endfunction
7981 function! fugitive#MapJumps(...) abort
7982   if !&modifiable
7983     if get(b:, 'fugitive_type', '') ==# 'blob'
7984       let blame_tail = '<C-R>=v:count ? " --reverse" : ""<CR><CR>'
7985       exe s:Map('n', '<2-LeftMouse>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
7986       exe s:Map('n', '<CR>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
7987       exe s:Map('n', 'o',    ':<C-U>0,1Git blame' . blame_tail, '<silent>')
7988       exe s:Map('n', 'p',    ':<C-U>0,1Git! blame' . blame_tail, '<silent>')
7989       if has('patch-7.4.1898')
7990         exe s:Map('n', 'gO',   ':<C-U>vertical 0,1Git blame' . blame_tail, '<silent>')
7991         exe s:Map('n', 'O',    ':<C-U>tab 0,1Git blame' . blame_tail, '<silent>')
7992       else
7993         exe s:Map('n', 'gO',   ':<C-U>0,4Git blame' . blame_tail, '<silent>')
7994         exe s:Map('n', 'O',    ':<C-U>0,5Git blame' . blame_tail, '<silent>')
7995       endif
7997       call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
7998       call s:Map('n', 'dd', ":<C-U>call fugitive#DiffClose()<Bar>Gdiffsplit!<CR>", '<silent>')
7999       call s:Map('n', 'dh', ":<C-U>call fugitive#DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
8000       call s:Map('n', 'ds', ":<C-U>call fugitive#DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
8001       call s:Map('n', 'dv', ":<C-U>call fugitive#DiffClose()<Bar>Gvdiffsplit!<CR>", '<silent>')
8002       call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
8004     else
8005       call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
8006       call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
8007       call s:Map('n', 'o',    ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
8008       call s:Map('n', 'gO',   ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
8009       call s:Map('n', 'O',    ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
8010       call s:Map('n', 'p',    ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
8012       if !exists('g:fugitive_no_maps')
8013         call s:Map('n', '<C-P>', ':exe <SID>PreviousItem(v:count1)<Bar>echohl WarningMsg<Bar>echo "CTRL-P is deprecated in favor of ("<Bar>echohl NONE<CR>', '<unique>')
8014         call s:Map('n', '<C-N>', ':exe <SID>NextItem(v:count1)<Bar>echohl WarningMsg<Bar>echo "CTRL-N is deprecated in favor of )"<Bar>echohl NONE<CR>', '<unique>')
8015       endif
8016       call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
8017       call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
8018       call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
8019       call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
8020       call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
8021       call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
8022       call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
8023       call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
8024       call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
8025       call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
8026       call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
8027       call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
8028       call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
8029       call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
8030       call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
8031       call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
8032     endif
8033     call s:Map('n', 'S',    ':<C-U>echoerr "Use gO"<CR>', '<silent><unique>')
8034     call s:Map('n', 'dq', ":<C-U>call fugitive#DiffClose()<CR>", '<silent>')
8035     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>')
8036     call s:Map('n', 'P',     ":<C-U>if !v:count<Bar>echoerr 'Use ~ (or provide a count)'<Bar>else<Bar>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<Bar>endif<CR>", '<silent>')
8037     call s:Map('n', '~',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
8038     call s:Map('n', 'C',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
8039     call s:Map('n', 'cp',    ":<C-U>echoerr 'Use gC'<CR>", '<silent><unique>')
8040     call s:Map('n', 'gC',    ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
8041     call s:Map('n', 'gc',    ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
8042     call s:Map('n', 'gi',    ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
8043     call s:Map('x', 'gi',    ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
8045     call s:Map('n', '.',     ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
8046     call s:Map('x', '.',     ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
8047     call s:Map('n', 'g?',    ":<C-U>help fugitive-map<CR>", '<silent>')
8048     call s:Map('n', '<F1>',  ":<C-U>help fugitive-map<CR>", '<silent>')
8049   endif
8051   let old_browsex = maparg('<Plug>NetrwBrowseX', 'n')
8052   let new_browsex = substitute(old_browsex, '\Cnetrw#CheckIfRemote(\%(netrw#GX()\)\=)', '0', 'g')
8053   let new_browsex = substitute(new_browsex, 'netrw#GX()\|expand((exists("g:netrw_gx")? g:netrw_gx : ''<cfile>''))', 'fugitive#GX()', 'g')
8054   if new_browsex !=# old_browsex
8055     exe 'nnoremap <silent> <buffer> <Plug>NetrwBrowseX' new_browsex
8056   endif
8057   call s:MapGitOps(0)
8058 endfunction
8060 function! fugitive#GX() abort
8061   try
8062     let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'git' ? s:cfile() : []
8063     if len(results) && len(results[0])
8064       return FugitiveReal(s:Generate(results[0]))
8065     endif
8066   catch /^fugitive:/
8067   endtry
8068   return expand(get(g:, 'netrw_gx', expand('<cfile>')))
8069 endfunction
8071 function! s:CfilePorcelain(...) abort
8072   let tree = s:Tree()
8073   if empty(tree)
8074     return ['']
8075   endif
8076   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
8077   let info = s:StageInfo()
8078   let line = getline('.')
8079   if len(info.sigil) && len(info.section) && len(info.paths)
8080     if info.section ==# 'Unstaged' && info.sigil !=# '-'
8081       return [lead . info.relative[0], info.offset, 'normal!zv']
8082     elseif info.section ==# 'Staged' && info.sigil ==# '-'
8083       return ['@:' . info.relative[0], info.offset, 'normal!zv']
8084     else
8085       return [':0:' . info.relative[0], info.offset, 'normal!zv']
8086     endif
8087   elseif len(info.paths)
8088     return [lead . info.relative[0]]
8089   elseif len(info.commit)
8090     return [info.commit]
8091   elseif line =~# '^' . s:ref_header . ': \|^Head: '
8092     return [matchstr(line, ' \zs.*')]
8093   else
8094     return ['']
8095   endif
8096 endfunction
8098 function! fugitive#PorcelainCfile() abort
8099   let file = fugitive#Find(s:CfilePorcelain()[0])
8100   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
8101 endfunction
8103 function! s:StatusCfile(...) abort
8104   let tree = s:Tree()
8105   if empty(tree)
8106     return []
8107   endif
8108   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
8109   if getline('.') =~# '^.\=\trenamed:.* -> '
8110     return [lead . matchstr(getline('.'),' -> \zs.*')]
8111   elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
8112     return [lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')]
8113   elseif getline('.') =~# '^.\=\t.'
8114     return [lead . matchstr(getline('.'),'\t\zs.*')]
8115   elseif getline('.') =~# ': needs merge$'
8116     return [lead . matchstr(getline('.'),'.*\ze: needs merge$')]
8117   elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
8118     return ['HEAD']
8119   elseif getline('.') =~# '^\%(. \)\=On branch '
8120     return ['refs/heads/'.getline('.')[12:]]
8121   elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
8122     return [matchstr(getline('.'),"'\\zs\\S\\+\\ze'")]
8123   else
8124     return []
8125   endif
8126 endfunction
8128 function! fugitive#MessageCfile() abort
8129   let file = fugitive#Find(get(s:StatusCfile(), 0, ''))
8130   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
8131 endfunction
8133 function! s:BranchCfile(result) abort
8134   return matchstr(getline('.'), '^. \zs\S\+')
8135 endfunction
8137 let s:diff_header_pattern = '^diff --git \%("\=[abciow12]/.*\|/dev/null\) \%("\=[abciow12]/.*\|/dev/null\)$'
8138 function! s:cfile() abort
8139   let temp_state = s:TempState()
8140   let name = substitute(get(get(temp_state, 'args', []), 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
8141   if exists('*s:' . name . 'Cfile')
8142     let cfile = s:{name}Cfile(temp_state)
8143     if !empty(cfile)
8144       return type(cfile) == type('') ? [cfile] : cfile
8145     endif
8146   endif
8147   if empty(FugitiveGitDir())
8148     return []
8149   endif
8150   try
8151     let myhash = s:DirRev(@%)[1]
8152     if len(myhash)
8153       try
8154         let myhash = fugitive#RevParse(myhash)
8155       catch /^fugitive:/
8156         let myhash = ''
8157       endtry
8158     endif
8159     if empty(myhash) && get(temp_state, 'filetype', '') ==# 'git'
8160       let lnum = line('.')
8161       while lnum > 0
8162         if getline(lnum) =~# '^\%(commit\|tag\) \w'
8163           let myhash = matchstr(getline(lnum),'^\w\+ \zs\S\+')
8164           break
8165         endif
8166         let lnum -= 1
8167       endwhile
8168     endif
8170     let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
8172     let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
8173           \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
8175     if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
8176       return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
8177     elseif showtree
8178       return [treebase . s:sub(getline('.'),'/$','')]
8180     else
8182       let dcmds = []
8184       " Index
8185       if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
8186         let ref = matchstr(getline('.'),'\x\{40,\}')
8187         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
8188         return [file]
8189       endif
8191       if getline('.') =~# '^ref: '
8192         let ref = strpart(getline('.'),5)
8194       elseif getline('.') =~# '^\%([|/\\_ ]*\*[|/\\_ ]*\)\=commit \x\{40,\}\>'
8195         let ref = matchstr(getline('.'),'\x\{40,\}')
8196         return [ref]
8198       elseif getline('.') =~# '^parent \x\{40,\}\>'
8199         let ref = matchstr(getline('.'),'\x\{40,\}')
8200         let line = line('.')
8201         let parent = 0
8202         while getline(line) =~# '^parent '
8203           let parent += 1
8204           let line -= 1
8205         endwhile
8206         return [ref]
8208       elseif getline('.') =~# '^tree \x\{40,\}$'
8209         let ref = matchstr(getline('.'),'\x\{40,\}')
8210         if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
8211           let ref = myhash.':'
8212         endif
8213         return [ref]
8215       elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
8216         let ref = matchstr(getline('.'),'\x\{40,\}')
8217         let type = matchstr(getline(line('.')+1),'type \zs.*')
8219       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
8220         let ref = s:DirRev(@%)[1]
8222       elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
8223         let ref = matchstr(getline('.'),'\x\{40,\}')
8224         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
8226       elseif getline('.') =~# '^[A-Z]\d*\t\S' && len(myhash)
8227         let files = split(getline('.'), "\t")[1:-1]
8228         let ref = 'b/' . files[-1]
8229         if getline('.') =~# '^D'
8230           let ref = 'a/' . files[0]
8231         elseif getline('.') !~# '^A'
8232           let dcmds = ['', 'Gdiffsplit! >' . myhash . '^:' . fnameescape(files[0])]
8233         endif
8235       elseif getline('.') =~# '^[+-]'
8236         let [header_lnum, old_lnum, new_lnum] = s:HunkPosition(line('.'))
8237         if new_lnum > 0
8238           let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[1]
8239           let dcmds = [new_lnum, 'normal!zv']
8240         elseif old_lnum > 0
8241           let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[0]
8242           let dcmds = [old_lnum, 'normal!zv']
8243         else
8244           let ref = fugitive#Unquote(matchstr(getline('.'), '\C[+-]\{3\} \zs"\=[abciow12]/.*'))
8245         endif
8247       elseif getline('.') =~# '^rename from '
8248         let ref = 'a/'.getline('.')[12:]
8249       elseif getline('.') =~# '^rename to '
8250         let ref = 'b/'.getline('.')[10:]
8252       elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
8253         let diff = getline(search(s:diff_header_pattern, 'bcnW'))
8254         let offset = matchstr(getline('.'), '+\zs\d\+')
8256         let [dref, ref] = s:ParseDiffHeader(diff)
8257         let dcmd = 'Gdiffsplit! +'.offset
8259       elseif getline('.') =~# s:diff_header_pattern
8260         let [dref, ref] = s:ParseDiffHeader(getline('.'))
8261         let dcmd = 'Gdiffsplit!'
8263       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# s:diff_header_pattern
8264         let [dref, ref] = s:ParseDiffHeader(getline(line('.') - '.'))
8265         let dcmd = 'Gdiffsplit!'
8267       elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
8268         let ref = getline('.')
8270       elseif expand('<cword>') =~# '^\x\{7,\}\>'
8271         return [expand('<cword>')]
8273       else
8274         let ref = ''
8275       endif
8277       let prefixes = {
8278             \ '1': '',
8279             \ '2': '',
8280             \ 'b': ':0:',
8281             \ 'i': ':0:',
8282             \ 'o': '',
8283             \ 'w': ''}
8285       if len(myhash)
8286         let prefixes.a = myhash.'^:'
8287         let prefixes.b = myhash.':'
8288       endif
8289       let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
8290       if exists('dref')
8291         let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
8292       endif
8294       if ref ==# '/dev/null'
8295         " Empty blob
8296         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
8297       endif
8299       if exists('dref')
8300         return [ref, dcmd . ' >' . s:fnameescape(dref)] + dcmds
8301       elseif ref != ""
8302         return [ref] + dcmds
8303       endif
8305     endif
8306     return []
8307   endtry
8308 endfunction
8310 function! s:GF(mode) abort
8311   try
8312     let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'gitcommit' ? s:StatusCfile() : s:cfile()
8313   catch /^fugitive:/
8314     return 'echoerr ' . string(v:exception)
8315   endtry
8316   if len(results) > 1
8317     let cmd = 'G' . a:mode .
8318           \ (empty(results[1]) ? '' : ' +' . s:PlusEscape(results[1])) . ' ' .
8319           \ fnameescape(results[0])
8320     let tail = join(map(results[2:-1], '"|" . v:val'), '')
8321     if a:mode ==# 'pedit' && len(tail)
8322       return cmd . '|wincmd P|exe ' . string(tail[1:-1]) . '|wincmd p'
8323     else
8324       return cmd . tail
8325     endif
8326   elseif len(results) && len(results[0])
8327     return 'G' . a:mode . ' ' . s:fnameescape(results[0])
8328   else
8329     return ''
8330   endif
8331 endfunction
8333 function! fugitive#Cfile() abort
8334   let pre = ''
8335   let results = s:cfile()
8336   if empty(results)
8337     if !empty(s:TempState())
8338       let cfile = s:TempDotMap()
8339       if !empty(cfile)
8340         return fnameescape(s:Generate(cfile))
8341       endif
8342     endif
8343     let cfile = expand('<cfile>')
8344     if &includeexpr =~# '\<v:fname\>'
8345       sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
8346     endif
8347     return cfile
8348   elseif len(results) > 1
8349     let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
8350   endif
8351   return pre . fnameescape(s:Generate(results[0]))
8352 endfunction
8354 " Section: Statusline
8356 function! fugitive#Statusline(...) abort
8357   let dir = s:Dir(bufnr(''))
8358   if empty(dir)
8359     return ''
8360   endif
8361   let status = ''
8362   let commit = s:DirCommitFile(@%)[1]
8363   if len(commit)
8364     let status .= ':' . commit[0:6]
8365   endif
8366   let status .= '('.fugitive#Head(7, dir).')'
8367   return '[Git'.status.']'
8368 endfunction
8370 function! fugitive#statusline(...) abort
8371   return fugitive#Statusline()
8372 endfunction
8374 " Section: Folding
8376 function! fugitive#Foldtext() abort
8377   if &foldmethod !=# 'syntax'
8378     return foldtext()
8379   endif
8381   let line_foldstart = getline(v:foldstart)
8382   if line_foldstart =~# '^diff '
8383     let [add, remove] = [-1, -1]
8384     let filename = ''
8385     for lnum in range(v:foldstart, v:foldend)
8386       let line = getline(lnum)
8387       if filename ==# '' && line =~# '^[+-]\{3\} "\=[abciow12]/'
8388         let filename = fugitive#Unquote(line[4:-1])[2:-1]
8389       endif
8390       if line =~# '^+'
8391         let add += 1
8392       elseif line =~# '^-'
8393         let remove += 1
8394       elseif line =~# '^Binary '
8395         let binary = 1
8396       endif
8397     endfor
8398     if filename ==# ''
8399       let filename = fugitive#Unquote(matchstr(line_foldstart, '^diff .\{-\} \zs"\=[abciow12]/\zs.*\ze "\=[abciow12]/'))[2:-1]
8400     endif
8401     if filename ==# ''
8402       let filename = line_foldstart[5:-1]
8403     endif
8404     if exists('binary')
8405       return 'Binary: '.filename
8406     else
8407       return '+-' . v:folddashes . ' ' . (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
8408     endif
8409   elseif line_foldstart =~# '^@@\+ .* @@'
8410     return '+-' . v:folddashes . ' ' . line_foldstart
8411   elseif &filetype ==# 'fugitive' && line_foldstart =~# '^[A-Z][a-z].* (\d\+)$'
8412     let c = +matchstr(line_foldstart, '(\zs\d\+\ze)$')
8413     return '+-' . v:folddashes . printf('%3d item', c) . (c == 1 ? ':  ' : 's: ') . matchstr(line_foldstart, '.*\ze (\d\+)$')
8414   elseif &filetype ==# 'gitcommit' && line_foldstart =~# '^# .*:$'
8415     let lines = getline(v:foldstart, v:foldend)
8416     call filter(lines, 'v:val =~# "^#\t"')
8417     call map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
8418     call map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
8419     return line_foldstart.' '.join(lines, ', ')
8420   endif
8421   return foldtext()
8422 endfunction
8424 function! fugitive#foldtext() abort
8425   return fugitive#Foldtext()
8426 endfunction
8428 " Section: End