Don't assume default value for branch.*.merge
[vim-fugitive.git] / autoload / fugitive.vim
blob4c8fed08d349cc0716b3b24ec8ef8cb5b8d67efe
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   if temp =~# '\s'
277     let temp = '"' . temp . '"'
278   endif
279   return FugitiveGitPath(temp)
280 endfunction
282 function! s:DoAutocmd(...) abort
283   return join(map(copy(a:000), "'doautocmd <nomodeline>' . v:val"), '|')
284 endfunction
286 function! s:Map(mode, lhs, rhs, ...) abort
287   let maps = []
288   let flags = a:0 && type(a:1) == type('') ? a:1 : ''
289   let defer = flags =~# '<unique>'
290   let flags = substitute(flags, '<unique>', '', '') . (a:rhs =~# '<Plug>' ? '' : '<script>') . '<nowait>'
291   for mode in split(a:mode, '\zs')
292     if a:0 <= 1
293       call add(maps, mode.'map <buffer>' . substitute(flags, '<unique>', '', '') . ' <Plug>fugitive:' . a:lhs . ' ' . a:rhs)
294     endif
295     let skip = 0
296     let head = a:lhs
297     let tail = ''
298     let keys = get(g:, mode.'remap', {})
299     if type(keys) == type([])
300       continue
301     endif
302     while !empty(head)
303       if has_key(keys, head)
304         let head = keys[head]
305         let skip = empty(head)
306         break
307       endif
308       let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
309       let head = substitute(head, '<[^<>]*>$\|.$', '', '')
310     endwhile
311     if !skip && (!defer || empty(mapcheck(head.tail, mode)))
312       call add(maps, mode.'map <buffer>' . flags . ' ' . head.tail . ' ' . a:rhs)
313       if a:0 > 1 && a:2
314         let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
315               \ '|sil! exe "' . mode . 'unmap <buffer> ' . head.tail . '"'
316       endif
317     endif
318   endfor
319   exe join(maps, '|')
320   return ''
321 endfunction
323 function! fugitive#Autowrite() abort
324   if &autowrite || &autowriteall
325     try
326       if &confirm
327         let reconfirm = 1
328         setglobal noconfirm
329       endif
330       silent! wall
331     finally
332       if exists('reconfirm')
333         setglobal confirm
334       endif
335     endtry
336   endif
337   return ''
338 endfunction
340 function! fugitive#Wait(job_or_jobs, ...) abort
341   let original = type(a:job_or_jobs) == type([]) ? copy(a:job_or_jobs) : [a:job_or_jobs]
342   let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
343   call filter(jobs, 'type(v:val) !=# type("")')
344   let timeout_ms = a:0 ? a:1 : -1
345   if exists('*jobwait')
346     call map(copy(jobs), 'chanclose(v:val, "stdin")')
347     call jobwait(jobs, timeout_ms)
348     let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
349     call filter(jobs, 'type(v:val) !=# type("")')
350     if len(jobs)
351       sleep 1m
352     endif
353   else
354     for job in jobs
355       if ch_status(job) ==# 'open'
356         call ch_close_in(job)
357       endif
358     endfor
359     let i = 0
360     for job in jobs
361       while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
362         if i == timeout_ms
363           break
364         endif
365         let i += 1
366         sleep 1m
367       endwhile
368     endfor
369   endif
370   return a:job_or_jobs
371 endfunction
373 function! s:JobVimExit(dict, callback, temp, job, status) abort
374   let a:dict.exit_status = a:status
375   let a:dict.stderr = readfile(a:temp . '.err', 'b')
376   call delete(a:temp . '.err')
377   let a:dict.stdout = readfile(a:temp . '.out', 'b')
378   call delete(a:temp . '.out')
379   call delete(a:temp . '.in')
380   call remove(a:dict, 'job')
381   call call(a:callback[0], [a:dict] + a:callback[1:-1])
382 endfunction
384 function! s:JobNvimExit(dict, callback, job, data, type) dict abort
385   let a:dict.stdout = self.stdout
386   let a:dict.stderr = self.stderr
387   let a:dict.exit_status = a:data
388   call remove(a:dict, 'job')
389   call call(a:callback[0], [a:dict] + a:callback[1:-1])
390 endfunction
392 function! s:JobExecute(argv, jopts, stdin, callback, ...) abort
393   let dict = a:0 ? a:1 : {}
394   let cb = len(a:callback) ? a:callback : [function('len')]
395   if exists('*jobstart')
396     call extend(a:jopts, {
397           \ 'stdout_buffered': v:true,
398           \ 'stderr_buffered': v:true,
399           \ 'on_exit': function('s:JobNvimExit', [dict, cb])})
400     try
401       let dict.job = jobstart(a:argv, a:jopts)
402       if !empty(a:stdin)
403         call chansend(dict.job, a:stdin)
404       endif
405       call chanclose(dict.job, 'stdin')
406     catch /^Vim\%((\a\+)\)\=:E475:/
407       let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
408     endtry
409   elseif exists('*ch_close_in')
410     let temp = tempname()
411     call extend(a:jopts, {
412           \ 'out_io': 'file',
413           \ 'out_name': temp . '.out',
414           \ 'err_io': 'file',
415           \ 'err_name': temp . '.err',
416           \ 'exit_cb': function('s:JobVimExit', [dict, cb, temp])})
417     if a:stdin ==# ['']
418       let a:jopts.in_io = 'null'
419     elseif !empty(a:stdin)
420       let a:jopts.in_io = 'file'
421       let a:jopts.in_name = temp . '.in'
422       call writefile(a:stdin, a:jopts.in_name, 'b')
423     endif
424     let dict.job = job_start(a:argv, a:jopts)
425     if job_status(dict.job) ==# 'fail'
426       let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
427       unlet dict.job
428     endif
429   elseif &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
430     throw 'fugitive: Vim 8 or higher required to use ' . &shell
431   else
432     let cmd = s:shellesc(a:argv)
433     let outfile = tempname()
434     try
435       if len(a:stdin)
436         call writefile(a:stdin, outfile . '.in', 'b')
437         let cmd = ' (' . cmd . ' >' . outfile . ' <' . outfile . '.in) '
438       else
439         let cmd = ' (' . cmd . ' >' . outfile . ') '
440       endif
441       let dict.stderr = split(system(cmd), "\n", 1)
442       let dict.exit_status = v:shell_error
443       let dict.stdout = readfile(outfile, 'b')
444       call call(cb[0], [dict] + cb[1:-1])
445     finally
446       call delete(outfile)
447       call delete(outfile . '.in')
448     endtry
449   endif
450   if empty(a:callback)
451     call fugitive#Wait(dict)
452   endif
453   return dict
454 endfunction
456 function! s:add_methods(namespace, method_names) abort
457   for name in a:method_names
458     let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
459   endfor
460 endfunction
462 " Section: Git
464 let s:run_jobs = (exists('*ch_close_in') || exists('*jobstart')) && exists('*bufwinid')
466 function! s:GitCmd() abort
467   if !exists('g:fugitive_git_executable')
468     return ['git']
469   elseif type(g:fugitive_git_executable) == type([])
470     return g:fugitive_git_executable
471   else
472     let dquote = '"\%([^"]\|""\|\\"\)*"\|'
473     let string = g:fugitive_git_executable
474     let list = []
475     if string =~# '^\w\+='
476       call add(list, '/usr/bin/env')
477     endif
478     while string =~# '\S'
479       let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
480       let string = strpart(string, len(arg))
481       let arg = substitute(arg, '^\s\+', '', '')
482       let arg = substitute(arg,
483             \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\)\|' . s:expand,
484             \ '\=submatch(0)[0] ==# "\\" ? submatch(0)[1] : submatch(0)[1:-2]', 'g')
485       call add(list, arg)
486     endwhile
487     return list
488   endif
489 endfunction
491 function! s:GitShellCmd() abort
492   if !exists('g:fugitive_git_executable')
493     return 'git'
494   elseif type(g:fugitive_git_executable) == type([])
495     return s:shellesc(g:fugitive_git_executable)
496   else
497     return g:fugitive_git_executable
498   endif
499 endfunction
501 function! s:UserCommandCwd(dir) abort
502   let tree = s:Tree(a:dir)
503   return len(tree) ? s:VimSlash(tree) : getcwd()
504 endfunction
506 function! s:UserCommandList(...) abort
507   if !fugitive#GitVersion(1, 8, 5)
508     throw 'fugitive: Git 1.8.5 or higher required'
509   endif
510   if !exists('g:fugitive_git_command')
511     let git = s:GitCmd()
512   elseif type(g:fugitive_git_command) == type([])
513     let git = g:fugitive_git_command
514   else
515     let git = split(g:fugitive_git_command, '\s\+')
516   endif
517   let flags = []
518   if a:0 && type(a:1) == type({})
519     let git = copy(get(a:1, 'git', git))
520     let flags = get(a:1, 'flags', flags)
521     let dir = a:1.git_dir
522   elseif a:0
523     let dir = s:GitDir(a:1)
524   else
525     let dir = ''
526   endif
527   if len(dir)
528     let tree = s:Tree(dir)
529     if empty(tree)
530       call add(git, '--git-dir=' . FugitiveGitPath(dir))
531     else
532       if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
533         call add(git, '--git-dir=' . FugitiveGitPath(dir))
534       endif
535       if !s:cpath(tree, getcwd())
536         call extend(git, ['-C', FugitiveGitPath(tree)])
537       endif
538     endif
539   endif
540   return git + flags
541 endfunction
543 let s:git_versions = {}
544 function! fugitive#GitVersion(...) abort
545   let git = s:GitShellCmd()
546   if !has_key(s:git_versions, git)
547     let s:git_versions[git] = matchstr(get(s:JobExecute(s:GitCmd() + ['--version'], {}, [], [], {}).stdout, 0, ''), '\d[^[:space:]]\+')
548   endif
549   if !a:0
550     return s:git_versions[git]
551   endif
552   let components = split(s:git_versions[git], '\D\+')
553   if empty(components)
554     return -1
555   endif
556   for i in range(len(a:000))
557     if a:000[i] > +get(components, i)
558       return 0
559     elseif a:000[i] < +get(components, i)
560       return 1
561     endif
562   endfor
563   return a:000[i] ==# get(components, i)
564 endfunction
566 function! s:Dir(...) abort
567   return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
568 endfunction
570 function! s:GitDir(...) abort
571   return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
572 endfunction
574 function! s:InitializeBuffer(repo) abort
575   let b:git_dir = s:GitDir(a:repo)
576 endfunction
578 function! s:SameRepo(one, two) abort
579   let one = s:GitDir(a:one)
580   return !empty(one) && one ==# s:GitDir(a:two)
581 endfunction
583 if exists('+shellslash')
584   function! s:DirUrlPrefix(dir) abort
585     let gd = s:GitDir(a:dir)
586     return 'fugitive://' . (gd =~# '^[^/]' ? '/' : '') . s:PathUrlEncode(gd) . '//'
587   endfunction
588 else
589   function! s:DirUrlPrefix(dir) abort
590     return 'fugitive://' . s:PathUrlEncode(s:GitDir(a:dir)) . '//'
591   endfunction
592 endif
594 function! s:Tree(...) abort
595   return a:0 ? FugitiveWorkTree(a:1) : FugitiveWorkTree()
596 endfunction
598 function! s:HasOpt(args, ...) abort
599   let args = a:args[0 : index(a:args, '--')]
600   let opts = copy(a:000)
601   if type(opts[0]) == type([])
602     if empty(args) || index(opts[0], args[0]) == -1
603       return 0
604     endif
605     call remove(opts, 0)
606   endif
607   for opt in opts
608     if index(args, opt) != -1
609       return 1
610     endif
611   endfor
612 endfunction
614 function! s:PreparePathArgs(cmd, dir, literal, explicit) abort
615   if !a:explicit
616     call insert(a:cmd, '--literal-pathspecs')
617   endif
618   let split = index(a:cmd, '--')
619   for i in range(split < 0 ? len(a:cmd) : split)
620       if type(a:cmd[i]) == type(0)
621         if a:literal
622           let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
623         else
624           let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
625         endif
626       endif
627   endfor
628   if split < 0
629     return a:cmd
630   endif
631   for i in range(split + 1, len(a:cmd) - 1)
632     if type(a:cmd[i]) == type(0)
633       if a:literal
634         let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
635       else
636         let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
637       endif
638     elseif !a:explicit
639       let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
640     endif
641   endfor
642   return a:cmd
643 endfunction
645 let s:git_index_file_env = {}
646 function! s:GitIndexFileEnv() abort
647   if $GIT_INDEX_FILE =~# '^/\|^\a:' && !has_key(s:git_index_file_env, $GIT_INDEX_FILE)
648     let s:git_index_file_env[$GIT_INDEX_FILE] = s:Slash(FugitiveVimPath($GIT_INDEX_FILE))
649   endif
650   return get(s:git_index_file_env, $GIT_INDEX_FILE, '')
651 endfunction
653 function! s:PrepareEnv(env, dir) abort
654   if len($GIT_INDEX_FILE) && len(s:Tree(a:dir)) && !has_key(a:env, 'GIT_INDEX_FILE')
655     let index_dir = substitute(s:GitIndexFileEnv(), '[^/]\+$', '', '')
656     let our_dir = fugitive#Find('.git/', a:dir)
657     if !s:cpath(index_dir, our_dir) && !s:cpath(resolve(index_dir), our_dir)
658       let a:env['GIT_INDEX_FILE'] = FugitiveGitPath(fugitive#Find('.git/index', a:dir))
659     endif
660   endif
661   if len($GIT_WORK_TREE)
662     let a:env['GIT_WORK_TREE'] = '.'
663   endif
664 endfunction
666 let s:prepare_env = {
667       \ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
668       \ 'core.editor': 'GIT_EDITOR',
669       \ 'core.askpass': 'GIT_ASKPASS',
670       \ }
671 function! fugitive#PrepareDirEnvGitFlagsArgs(...) abort
672   if !fugitive#GitVersion(1, 8, 5)
673     throw 'fugitive: Git 1.8.5 or higher required'
674   endif
675   let git = s:GitCmd()
676   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')
677     let cmd = a:1.flags + a:1.args
678     let dir = s:Dir(a:1)
679     if has_key(a:1, 'git')
680       let git = a:1.git
681     endif
682     let env = get(a:1, 'env', {})
683   else
684     let list_args = []
685     let cmd = []
686     for l:.arg in a:000
687       if type(arg) ==# type([])
688         call extend(list_args, arg)
689       else
690         call add(cmd, arg)
691       endif
692     endfor
693     call extend(cmd, list_args)
694     let env = {}
695   endif
696   let autoenv = {}
697   let explicit_pathspec_option = 0
698   let literal_pathspecs = 1
699   let i = 0
700   let arg_count = 0
701   while i < len(cmd)
702     if type(cmd[i]) == type({})
703       if has_key(cmd[i], 'fugitive_dir') || has_key(cmd[i], 'git_dir')
704         let dir = s:Dir(cmd[i])
705       endif
706       if has_key(cmd[i], 'git')
707         let git = cmd[i].git
708       endif
709       if has_key(cmd[i], 'env')
710         call extend(env, cmd[i].env)
711       endif
712       call remove(cmd, i)
713     elseif cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
714       let dir = s:Dir(remove(cmd, i))
715     elseif cmd[i] =~# '^--git-dir='
716       let dir = s:Dir(remove(cmd, i)[10:-1])
717     elseif type(cmd[i]) ==# type(0)
718       let dir = s:Dir(remove(cmd, i))
719     elseif cmd[i] ==# '-c' && len(cmd) > i + 1
720       let key = matchstr(cmd[i+1], '^[^=]*')
721       if has_key(s:prepare_env, tolower(key))
722         let var = s:prepare_env[tolower(key)]
723         let val = matchstr(cmd[i+1], '=\zs.*')
724         let autoenv[var] = val
725       endif
726       let i += 2
727     elseif cmd[i] =~# '^--.*pathspecs$'
728       let literal_pathspecs = (cmd[i] ==# '--literal-pathspecs')
729       let explicit_pathspec_option = 1
730       let i += 1
731     elseif cmd[i] !~# '^-'
732       let arg_count = len(cmd) - i
733       break
734     else
735       let i += 1
736     endif
737   endwhile
738   if !exists('dir')
739     let dir = s:Dir()
740   endif
741   call extend(autoenv, env)
742   call s:PrepareEnv(autoenv, dir)
743   if len($GPG_TTY) && !has_key(autoenv, 'GPG_TTY')
744     let autoenv.GPG_TTY = ''
745   endif
746   call s:PreparePathArgs(cmd, dir, literal_pathspecs, explicit_pathspec_option)
747   return [dir, env, extend(autoenv, env), git, cmd[0 : -arg_count-1], arg_count ? cmd[-arg_count : -1] : []]
748 endfunction
750 function! s:BuildEnvPrefix(env) abort
751   let pre = ''
752   let env = items(a:env)
753   if empty(env)
754     return ''
755   elseif &shell =~# '\%(powershell\|pwsh\)\%(\.exe\)\=$'
756     return join(map(env, '"$Env:" . v:val[0] . " = ''" . substitute(v:val[1], "''", "''''", "g") . "''; "'), '')
757   elseif s:winshell()
758     return join(map(env, '"set " . substitute(join(v:val, "="), "[&|<>^]", "^^^&", "g") . "& "'), '')
759   else
760     return '/usr/bin/env ' . s:shellesc(map(env, 'join(v:val, "=")')) . ' '
761   endif
762 endfunction
764 function! s:JobOpts(cmd, env) abort
765   if empty(a:env)
766     return [a:cmd, {}]
767   elseif has('patch-8.2.0239') ||
768         \ has('nvim') && api_info().version.api_level - api_info().version.api_prerelease >= 7 ||
769         \ has('patch-8.0.0902') && !has('nvim') && (!has('win32') || empty(filter(keys(a:env), 'exists("$" . v:val)')))
770     return [a:cmd, {'env': a:env}]
771   endif
772   let envlist = map(items(a:env), 'join(v:val, "=")')
773   if !has('win32')
774     return [['/usr/bin/env'] + envlist + a:cmd, {}]
775   else
776     let pre = join(map(envlist, '"set " . substitute(v:val, "[&|<>^]", "^^^&", "g") . "& "'), '')
777     if len(a:cmd) == 3 && a:cmd[0] ==# 'cmd.exe' && a:cmd[1] ==# '/c'
778       return [a:cmd[0:1] + [pre . a:cmd[2]], {}]
779     else
780       return [['cmd.exe', '/c', pre . s:WinShellEsc(a:cmd)], {}]
781     endif
782   endif
783 endfunction
785 function! s:PrepareJob(opts) abort
786   let dict = {'argv': a:opts.argv}
787   if has_key(a:opts, 'env')
788     let dict.env = a:opts.env
789   endif
790   let [argv, jopts] = s:JobOpts(a:opts.argv, get(a:opts, 'env', {}))
791   if has_key(a:opts, 'cwd')
792     if has('patch-8.0.0902')
793       let jopts.cwd = a:opts.cwd
794       let dict.cwd = a:opts.cwd
795     else
796       throw 'fugitive: cwd unsupported'
797     endif
798   endif
799   return [argv, jopts, dict]
800 endfunction
802 function! fugitive#PrepareJob(...) abort
803   if a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'argv') && !has_key(a:1, 'args')
804     return s:PrepareJob(a:1)
805   endif
806   let [repo, user_env, exec_env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
807   let dir = s:GitDir(repo)
808   let dict = {'git': git, 'git_dir': dir, 'flags': flags, 'args': args}
809   if len(user_env)
810     let dict.env = user_env
811   endif
812   let cmd = flags + args
813   let tree = s:Tree(repo)
814   if empty(tree) || index(cmd, '--') == len(cmd) - 1
815     let dict.cwd = getcwd()
816     call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
817   else
818     let dict.cwd = s:VimSlash(tree)
819     call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
820     if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
821       call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
822     endif
823   endif
824   call extend(cmd, git, 'keep')
825   return s:JobOpts(cmd, exec_env) + [dict]
826 endfunction
828 function! fugitive#Execute(...) abort
829   let cb = copy(a:000)
830   let cmd = []
831   let stdin = []
832   while len(cb) && type(cb[0]) !=# type(function('tr'))
833     if type(cb[0]) ==# type({}) && has_key(cb[0], 'stdin')
834       if type(cb[0].stdin) == type([])
835         call extend(stdin, cb[0].stdin)
836       elseif type(cb[0].stdin) == type('')
837         call extend(stdin, readfile(cb[0].stdin, 'b'))
838       endif
839       if len(keys(cb[0])) == 1
840         call remove(cb, 0)
841         continue
842       endif
843     endif
844     call add(cmd, remove(cb, 0))
845   endwhile
846   let [argv, jopts, dict] = call('fugitive#PrepareJob', cmd)
847   return s:JobExecute(argv, jopts, stdin, cb, dict)
848 endfunction
850 function! s:BuildShell(dir, env, git, args) abort
851   let cmd = copy(a:args)
852   let tree = s:Tree(a:dir)
853   let pre = s:BuildEnvPrefix(a:env)
854   if empty(tree) || index(cmd, '--') == len(cmd) - 1
855     call insert(cmd, '--git-dir=' . FugitiveGitPath(a:dir))
856   else
857     call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
858     if !s:cpath(tree . '/.git', a:dir) || len($GIT_DIR)
859       call extend(cmd, ['--git-dir=' . FugitiveGitPath(a:dir)], 'keep')
860     endif
861   endif
862   return pre . join(map(a:git + cmd, 's:shellesc(v:val)'))
863 endfunction
865 function! s:JobNvimCallback(lines, job, data, type) abort
866   let a:lines[-1] .= remove(a:data, 0)
867   call extend(a:lines, a:data)
868 endfunction
870 function! s:SystemList(cmd) abort
871   let exit = []
872   if exists('*jobstart')
873     let lines = ['']
874     let jopts = {
875           \ 'on_stdout': function('s:JobNvimCallback', [lines]),
876           \ 'on_stderr': function('s:JobNvimCallback', [lines]),
877           \ 'on_exit': { j, code, _ -> add(exit, code) }}
878     let job = jobstart(a:cmd, jopts)
879     call chanclose(job, 'stdin')
880     call jobwait([job])
881     if empty(lines[-1])
882       call remove(lines, -1)
883     endif
884     return [lines, exit[0]]
885   elseif exists('*ch_close_in')
886     let lines = []
887     let jopts = {
888           \ 'out_cb': { j, str -> add(lines, str) },
889           \ 'err_cb': { j, str -> add(lines, str) },
890           \ 'exit_cb': { j, code -> add(exit, code) }}
891     let job = job_start(a:cmd, jopts)
892     call ch_close_in(job)
893     while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
894       sleep 1m
895     endwhile
896     return [lines, exit[0]]
897   else
898     let [output, exec_error] = s:SystemError(s:shellesc(a:cmd))
899     let lines = split(output, "\n", 1)
900     if empty(lines[-1])
901       call remove(lines, -1)
902     endif
903     return [lines, v:shell_error]
904   endif
905 endfunction
907 function! fugitive#ShellCommand(...) abort
908   let [repo, _, env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
909   return s:BuildShell(s:GitDir(repo), env, git, flags + args)
910 endfunction
912 function! s:SystemError(cmd, ...) abort
913   let cmd = type(a:cmd) == type([]) ? s:shellesc(a:cmd) : a:cmd
914   try
915     if &shellredir ==# '>' && &shell =~# 'sh\|cmd'
916       let shellredir = &shellredir
917       if &shell =~# 'csh'
918         set shellredir=>&
919       else
920         set shellredir=>%s\ 2>&1
921       endif
922     endif
923     if exists('+guioptions') && &guioptions =~# '!'
924       let guioptions = &guioptions
925       set guioptions-=!
926     endif
927     let out = call('system', [cmd] + a:000)
928     return [out, v:shell_error]
929   catch /^Vim\%((\a\+)\)\=:E484:/
930     let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
931     call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
932     call map(opts, 'v:val."=".eval("&".v:val)')
933     call s:throw('failed to run `' . cmd . '` with ' . join(opts, ' '))
934   finally
935     if exists('shellredir')
936       let &shellredir = shellredir
937     endif
938     if exists('guioptions')
939       let &guioptions = guioptions
940     endif
941   endtry
942 endfunction
944 function! s:ChompStderr(...) abort
945   let r = call('fugitive#Execute', a:000)
946   return !r.exit_status ? '' : len(r.stderr) > 1 ? s:JoinChomp(r.stderr) : 'unknown Git error' . string(r)
947 endfunction
949 function! s:ChompDefault(default, ...) abort
950   let r = call('fugitive#Execute', a:000)
951   return r.exit_status ? a:default : s:JoinChomp(r.stdout)
952 endfunction
954 function! s:LinesError(...) abort
955   let r = call('fugitive#Execute', a:000)
956   if empty(r.stdout[-1])
957     call remove(r.stdout, -1)
958   endif
959   return [r.exit_status ? [] : r.stdout, r.exit_status]
960 endfunction
962 function! s:TreeChomp(...) abort
963   let r = call('fugitive#Execute', a:000)
964   if !r.exit_status
965     return s:JoinChomp(r.stdout)
966   endif
967   throw 'fugitive: error running `' . call('fugitive#ShellCommand', a:000) . '`: ' . s:JoinChomp(r.stderr)
968 endfunction
970 function! s:StdoutToFile(out, cmd, ...) abort
971   let [argv, jopts, _] = fugitive#PrepareJob(a:cmd)
972   let exit = []
973   if exists('*jobstart')
974     call extend(jopts, {
975           \ 'stdout_buffered': v:true,
976           \ 'stderr_buffered': v:true,
977           \ 'on_exit': { j, code, _ -> add(exit, code) }})
978     let job = jobstart(argv, jopts)
979     if a:0
980       call chansend(job, a:1)
981     endif
982     call chanclose(job, 'stdin')
983     call jobwait([job])
984     if len(a:out)
985       call writefile(jopts.stdout, a:out, 'b')
986     endif
987     return [join(jopts.stderr, "\n"), exit[0]]
988   elseif exists('*ch_close_in')
989     try
990       let err = tempname()
991       call extend(jopts, {
992             \ 'out_io': len(a:out) ? 'file' : 'null',
993             \ 'out_name': a:out,
994             \ 'err_io': 'file',
995             \ 'err_name': err,
996             \ 'exit_cb': { j, code -> add(exit, code) }})
997       let job = job_start(argv, jopts)
998       if a:0
999         call ch_sendraw(job, a:1)
1000       endif
1001       call ch_close_in(job)
1002       while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
1003         sleep 1m
1004       endwhile
1005       return [join(readfile(err, 'b'), "\n"), exit[0]]
1006     finally
1007       call delete(err)
1008     endtry
1009   elseif s:winshell() || &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
1010     throw 'fugitive: Vim 8 or higher required to use ' . &shell
1011   else
1012     let cmd = fugitive#ShellCommand(a:cmd)
1013     return call('s:SystemError', [' (' . cmd . ' >' . (len(a:out) ? a:out : '/dev/null') . ') '] + a:000)
1014   endif
1015 endfunction
1017 let s:head_cache = {}
1019 function! fugitive#Head(...) abort
1020   let dir = a:0 > 1 ? a:2 : s:Dir()
1021   if empty(dir)
1022     return ''
1023   endif
1024   let file = FugitiveActualDir(dir) . '/HEAD'
1025   let ftime = getftime(file)
1026   if ftime == -1
1027     return ''
1028   elseif ftime != get(s:head_cache, file, [-1])[0]
1029     let s:head_cache[file] = [ftime, readfile(file)[0]]
1030   endif
1031   let head = s:head_cache[file][1]
1032   let len = a:0 ? a:1 : 0
1033   if head =~# '^ref: '
1034     if len < 0
1035       return strpart(head, 5)
1036     else
1037       return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
1038     endif
1039   elseif head =~# '^\x\{40,\}$'
1040     return len < 0 ? head : strpart(head, 0, len)
1041   else
1042     return ''
1043   endif
1044 endfunction
1046 function! fugitive#RevParse(rev, ...) abort
1047   let hash = s:ChompDefault('', [a:0 ? a:1 : s:Dir(), 'rev-parse', '--verify', a:rev, '--'])
1048   if hash =~# '^\x\{40,\}$'
1049     return hash
1050   endif
1051   throw 'fugitive: failed to parse revision ' . a:rev
1052 endfunction
1054 " Section: Git config
1056 function! s:ConfigTimestamps(dir, dict) abort
1057   let files = ['/etc/gitconfig', '~/.gitconfig',
1058         \ len($XDG_CONFIG_HOME) ? $XDG_CONFIG_HOME . '/git/config' : '~/.config/git/config']
1059   if len(a:dir)
1060     call add(files, fugitive#Find('.git/config', a:dir))
1061   endif
1062   call extend(files, get(a:dict, 'include.path', []))
1063   return join(map(files, 'getftime(expand(v:val))'), ',')
1064 endfunction
1066 function! s:ConfigCallback(r, into) abort
1067   let dict = a:into[1]
1068   if has_key(dict, 'job')
1069     call remove(dict, 'job')
1070   endif
1071   let lines = a:r.exit_status ? [] : split(tr(join(a:r.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
1072   for line in lines
1073     let key = matchstr(line, "^[^\n]*")
1074     if !has_key(dict, key)
1075       let dict[key] = []
1076     endif
1077     if len(key) ==# len(line)
1078       call add(dict[key], 1)
1079     else
1080       call add(dict[key], strpart(line, len(key) + 1))
1081     endif
1082   endfor
1083   let callbacks = remove(dict, 'callbacks')
1084   lockvar! dict
1085   let a:into[0] = s:ConfigTimestamps(dict.git_dir, dict)
1086   for callback in callbacks
1087     call call(callback[0], [dict] + callback[1:-1])
1088   endfor
1089 endfunction
1091 let s:config_prototype = {}
1093 let s:config = {}
1094 function! fugitive#ExpireConfig(...) abort
1095   if !a:0 || a:1 is# 0
1096     let s:config = {}
1097   else
1098     let key = a:1 is# '' ? '_' : s:GitDir(a:0 ? a:1 : -1)
1099     if len(key) && has_key(s:config, key)
1100       call remove(s:config, key)
1101     endif
1102   endif
1103 endfunction
1105 function! fugitive#Config(...) abort
1106   let name = ''
1107   let default = get(a:, 3, '')
1108   if a:0 && type(a:1) == type(function('tr'))
1109     let dir = s:Dir()
1110     let callback = a:000
1111   elseif a:0 > 1 && type(a:2) == type(function('tr'))
1112     if type(a:1) == type({}) && has_key(a:1, 'GetAll')
1113       if has_key(a:1, 'callbacks')
1114         call add(a:1.callbacks, a:000[1:-1])
1115       else
1116         call call(a:2, [a:1] + a:000[2:-1])
1117       endif
1118       return a:1
1119     else
1120       let dir = s:Dir(a:1)
1121       let callback = a:000[1:-1]
1122     endif
1123   elseif a:0 >= 2 && type(a:2) == type({}) && has_key(a:2, 'GetAll')
1124     return get(fugitive#ConfigGetAll(a:1, a:2), -1, default)
1125   elseif a:0 >= 2
1126     let dir = s:Dir(a:2)
1127     let name = a:1
1128   elseif a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'GetAll')
1129     return a:1
1130   elseif a:0 == 1 && type(a:1) == type('') && a:1 =~# '^[[:alnum:]-]\+\.'
1131     let dir = s:Dir()
1132     let name = a:1
1133   elseif a:0 == 1
1134     let dir = s:Dir(a:1)
1135   else
1136     let dir = s:Dir()
1137   endif
1138   let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1139   let git_dir = s:GitDir(dir)
1140   let dir_key = len(git_dir) ? git_dir : '_'
1141   let [ts, dict] = get(s:config, dir_key, ['new', {}])
1142   if !has_key(dict, 'job') && ts !=# s:ConfigTimestamps(git_dir, dict)
1143     let dict = copy(s:config_prototype)
1144     let dict.git_dir = git_dir
1145     let into = ['running', dict]
1146     let dict.callbacks = []
1147     let exec = fugitive#Execute([dir, 'config', '--list', '-z', '--'], function('s:ConfigCallback'), into)
1148     if has_key(exec, 'job')
1149       let dict.job = exec.job
1150     endif
1151     let s:config[dir_key] = into
1152   endif
1153   if !exists('l:callback')
1154     call fugitive#Wait(dict)
1155   elseif has_key(dict, 'callbacks')
1156     call add(dict.callbacks, callback)
1157   else
1158     call call(callback[0], [dict] + callback[1:-1])
1159   endif
1160   return len(name) ? get(fugitive#ConfigGetAll(name, dict), 0, default) : dict
1161 endfunction
1163 function! fugitive#ConfigGetAll(name, ...) abort
1164   if a:0 && (type(a:name) !=# type('') || a:name !~# '^[[:alnum:]-]\+\.' && type(a:1) ==# type('') && a:1 =~# '^[[:alnum:]-]\+\.')
1165     let config = fugitive#Config(a:name)
1166     let name = a:1
1167   else
1168     let config = fugitive#Config(a:0 ? a:1 : s:Dir())
1169     let name = a:name
1170   endif
1171   let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1172   call fugitive#Wait(config)
1173   return name =~# '\.' ? copy(get(config, name, [])) : []
1174 endfunction
1176 function! fugitive#ConfigGetRegexp(pattern, ...) abort
1177   if type(a:pattern) !=# type('')
1178     let config = fugitive#Config(a:name)
1179     let pattern = a:0 ? a:1 : '.*'
1180   else
1181     let config = fugitive#Config(a:0 ? a:1 : s:Dir())
1182     let pattern = a:pattern
1183   endif
1184   call fugitive#Wait(config)
1185   let filtered = map(filter(copy(config), 'v:key =~# "\\." && v:key =~# pattern'), 'copy(v:val)')
1186   if pattern !~# '\\\@<!\%(\\\\\)*\\z[se]'
1187     return filtered
1188   endif
1189   let transformed = {}
1190   for [k, v] in items(filtered)
1191     let k = matchstr(k, pattern)
1192     if len(k)
1193       let transformed[k] = v
1194     endif
1195   endfor
1196   return transformed
1197 endfunction
1199 function! s:config_GetAll(name) dict abort
1200   let name = substitute(a:name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1201   call fugitive#Wait(self)
1202   return name =~# '\.' ? copy(get(self, name, [])) : []
1203 endfunction
1205 function! s:config_Get(name, ...) dict abort
1206   return get(self.GetAll(a:name), -1, a:0 ? a:1 : '')
1207 endfunction
1209 function! s:config_GetRegexp(pattern) dict abort
1210   return fugitive#ConfigGetRegexp(self, a:pattern)
1211 endfunction
1213 call s:add_methods('config', ['GetAll', 'Get', 'GetRegexp'])
1215 function! s:RemoteDefault(dir) abort
1216   let head = FugitiveHead(0, a:dir)
1217   let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
1218   let i = 10
1219   while remote ==# '.' && i > 0
1220     let head = matchstr(FugitiveConfigGet('branch.' . head . '.merge', a:dir), 'refs/heads/\zs.*')
1221     let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
1222     let i -= 1
1223   endwhile
1224   return remote =~# '^\.\=$' ? 'origin' : remote
1225 endfunction
1227 function! s:SshParseHost(value) abort
1228   let patterns = []
1229   let negates = []
1230   for host in split(a:value, '\s\+')
1231     let pattern = substitute(host, '[\\^$.*~?]', '\=submatch(0) == "*" ? ".*" : submatch(0) == "?" ? "." : "\\" . submatch(0)', 'g')
1232     if pattern[0] ==# '!'
1233       call add(negates, '\&\%(^' . pattern[1 : -1] . '$\)\@!')
1234     else
1235       call add(patterns, pattern)
1236     endif
1237   endfor
1238   return '^\%(' . join(patterns, '\|') . '\)$' . join(negates, '')
1239 endfunction
1241 function! s:SshParseConfig(into, root, file) abort
1242   try
1243     let lines = readfile(a:file)
1244   catch
1245     return a:into
1246   endtry
1247   let host = '^\%(.*\)$'
1248   while !empty(lines)
1249     let line = remove(lines, 0)
1250     let key = tolower(matchstr(line, '^\s*\zs\w\+\ze\s'))
1251     let value = matchstr(line, '^\s*\w\+\s\+\zs.*\S')
1252     if key ==# 'match'
1253       let host = value ==# 'all' ? '^\%(.*\)$' : ''
1254     elseif key ==# 'host'
1255       let host = s:SshParseHost(value)
1256     elseif key ==# 'include'
1257       for glob in split(value)
1258         if glob !~# '^/'
1259           let glob = a:root . glob
1260         endif
1261         for included in reverse(split(glob(glob), "\n"))
1262           call extend(lines, readfile(included), 'keep')
1263         endfor
1264       endfor
1265     elseif len(key) && len(host)
1266       call extend(a:into, {key : []}, 'keep')
1267       call add(a:into[key], [host, value])
1268     endif
1269   endwhile
1270   return a:into
1271 endfunction
1273 unlet! s:ssh_config
1274 function! fugitive#SshConfig(host, ...) abort
1275   if !exists('s:ssh_config')
1276     let s:ssh_config = {}
1277     for file in [expand("~/.ssh/config"), "/etc/ssh/ssh_config"]
1278       call s:SshParseConfig(s:ssh_config, substitute(file, '\w*$', '', ''), file)
1279     endfor
1280   endif
1281   let host_config = {}
1282   for key in a:0 ? a:1 : keys(s:ssh_config)
1283     for [host_pattern, value] in get(s:ssh_config, key, [])
1284       if a:host =~# host_pattern
1285         let host_config[key] = value
1286         break
1287       endif
1288     endfor
1289   endfor
1290   return host_config
1291 endfunction
1293 function! fugitive#SshHostAlias(authority) abort
1294   let [_, user, host, port; __] = matchlist(a:authority, '^\%(\([^/@]\+\)@\)\=\(.\{-\}\)\%(:\(\d\+\)\)\=$')
1295   let c = fugitive#SshConfig(host, ['user', 'hostname', 'port'])
1296   if empty(user)
1297     let user = get(c, 'user', '')
1298   endif
1299   if empty(port)
1300     let port = get(c, 'port', '')
1301   endif
1302   return (len(user) ? user . '@' : '') . get(c, 'hostname', host) . (port =~# '^\%(22\)\=$' ? '' : ':' . port)
1303 endfunction
1305 function! s:CurlResponse(result) abort
1306   let a:result.headers = {}
1307   for line in a:result.exit_status ? [] : remove(a:result, 'stdout')
1308     let header = matchlist(line, '^\([[:alnum:]-]\+\):\s\(.\{-\}\)'. "\r\\=$")
1309     if len(header)
1310       let k = tolower(header[1])
1311       if has_key(a:result.headers, k)
1312         let a:result.headers[k] .= ', ' . header[2]
1313       else
1314         let a:result.headers[k] = header[2]
1315       endif
1316     elseif empty(line)
1317       break
1318     endif
1319   endfor
1320 endfunction
1322 let s:remote_headers = {}
1324 function! fugitive#RemoteHttpHeaders(remote) abort
1325   let remote = type(a:remote) ==# type({}) ? get(a:remote, 'remote', '') : a:remote
1326   if type(remote) !=# type('') || remote !~# '^https\=://.' || !s:executable('curl')
1327     return {}
1328   endif
1329   let remote = substitute(remote, '#.*', '', '')
1330   if !has_key(s:remote_headers, remote)
1331     let url = remote . '/info/refs?service=git-upload-pack'
1332     let exec = s:JobExecute(
1333           \ ['curl', '--disable', '--silent', '--max-time', '5', '-X', 'GET', '-I',
1334           \ url], {}, [], [function('s:CurlResponse')], {})
1335     call fugitive#Wait(exec)
1336     let s:remote_headers[remote] = exec.headers
1337   endif
1338   return s:remote_headers[remote]
1339 endfunction
1341 function! s:UrlParse(url) abort
1342   let scp_authority = matchstr(a:url, '^[^:/]\+\ze:\%(//\)\@!')
1343   if len(scp_authority) && !(has('win32') && scp_authority =~# '^\a:[\/]')
1344     let url = {'scheme': 'ssh', 'authority': s:UrlEncode(scp_authority), 'hash': '',
1345           \ 'path': s:UrlEncode(strpart(a:url, len(scp_authority) + 1))}
1346   elseif empty(a:url)
1347     let url = {'scheme': '', 'authority': '', 'path': '', 'hash': ''}
1348   else
1349     let match = matchlist(a:url, '^\([[:alnum:].+-]\+\)://\([^/]*\)\(/[^#]*\)\=\(#.*\)\=$')
1350     if empty(match)
1351       let url = {'scheme': 'file', 'authority': '', 'hash': '',
1352             \ 'path': s:UrlEncode(a:url)}
1353     else
1354       let url = {'scheme': match[1], 'authority': match[2], 'hash': match[4]}
1355       let url.path = empty(match[3]) ? '/' : match[3]
1356     endif
1357   endif
1358   return url
1359 endfunction
1361 function! s:UrlPopulate(string, into) abort
1362   let url = a:into
1363   let url.protocol = substitute(url.scheme, '.\zs$', ':', '')
1364   let url.user = fugitive#UrlDecode(matchstr(url.authority, '.\{-\}\ze@', '', ''))
1365   let url.host = substitute(url.authority, '.\{-\}@', '', '')
1366   let url.hostname = substitute(url.host, ':\d\+$', '', '')
1367   let url.port = matchstr(url.host, ':\zs\d\+$', '', '')
1368   let url.origin = substitute(url.scheme, '.\zs$', '://', '') . url.host
1369   let url.search = matchstr(url.path, '?.*')
1370   let url.pathname = '/' . matchstr(url.path, '^/\=\zs[^?]*')
1371   if (url.scheme ==# 'ssh' || url.scheme ==# 'git') && url.path[0:1] ==# '/~'
1372     let url.path = strpart(url.path, 1)
1373   endif
1374   if url.path =~# '^/'
1375     let url.href = url.scheme . '://' . url.authority . url.path . url.hash
1376   elseif url.path =~# '^\~'
1377     let url.href = url.scheme . '://' . url.authority . '/' . url.path . url.hash
1378   elseif url.scheme ==# 'ssh' && url.authority !~# ':'
1379     let url.href = url.authority . ':' . url.path . url.hash
1380   else
1381     let url.href = a:string
1382   endif
1383   let url.path = fugitive#UrlDecode(matchstr(url.path, '^[^?]*'))
1384   let url.url = matchstr(url.href, '^[^#]*')
1385 endfunction
1387 function! s:RemoteResolve(url, flags) abort
1388   let remote = s:UrlParse(a:url)
1389   if remote.scheme =~# '^https\=$' && index(a:flags, ':nohttp') < 0
1390     let headers = fugitive#RemoteHttpHeaders(a:url)
1391     let loc = matchstr(get(headers, 'location', ''), '^https\=://.\{-\}\ze/info/refs?')
1392     if len(loc)
1393       let remote = s:UrlParse(loc)
1394     else
1395       let remote.headers = headers
1396     endif
1397   elseif remote.scheme ==# 'ssh'
1398     let remote.authority = fugitive#SshHostAlias(remote.authority)
1399   endif
1400   return remote
1401 endfunction
1403 function! s:ConfigLengthSort(i1, i2) abort
1404   return len(a:i2[0]) - len(a:i1[0])
1405 endfunction
1407 function! s:RemoteCallback(config, into, flags, cb) abort
1408   if a:into.remote_name =~# '^\.\=$'
1409     let a:into.remote_name = s:RemoteDefault(a:config)
1410   endif
1411   let url = a:into.remote_name
1413   if url ==# '.git'
1414     let url = s:GitDir(a:config)
1415   elseif url !~# ':\|^/\|^\a:[\/]\|^\.\.\=/'
1416     let url = FugitiveConfigGet('remote.' . url . '.url', a:config)
1417   endif
1418   let instead_of = []
1419   for [k, vs] in items(fugitive#ConfigGetRegexp('^url\.\zs.\{-\}\ze\.insteadof$', a:config))
1420     for v in vs
1421       call add(instead_of, [v, k])
1422     endfor
1423   endfor
1424   call sort(instead_of, 's:ConfigLengthSort')
1425   for [orig, replacement] in instead_of
1426     if strpart(url, 0, len(orig)) ==# orig
1427       let url = replacement . strpart(url, len(orig))
1428       break
1429     endif
1430   endfor
1431   if index(a:flags, ':noresolve') < 0
1432     call extend(a:into, s:RemoteResolve(url, a:flags))
1433   else
1434     call extend(a:into, s:UrlParse(url))
1435   endif
1436   call s:UrlPopulate(url, a:into)
1437   if len(a:cb)
1438     call call(a:cb[0], [a:into] + a:cb[1:-1])
1439   endif
1440 endfunction
1442 function! s:Remote(dir, remote, flags, cb) abort
1443   let into = {'remote_name': a:remote, 'git_dir': s:GitDir(a:dir)}
1444   let config = fugitive#Config(a:dir, function('s:RemoteCallback'), into, a:flags, a:cb)
1445   if len(a:cb)
1446     return config
1447   else
1448     call fugitive#Wait(config)
1449     return into
1450   endif
1451 endfunction
1453 function! s:RemoteParseArgs(args) abort
1454   " Extract ':noresolve' style flags and an optional callback
1455   let args = []
1456   let flags = []
1457   let cb = copy(a:args)
1458   while len(cb)
1459     if type(cb[0]) ==# type(function('tr'))
1460       break
1461     elseif len(args) > 1 || type(cb[0]) ==# type('') && cb[0] =~# '^:'
1462       call add(flags, remove(cb, 0))
1463     else
1464       call add(args, remove(cb, 0))
1465     endif
1466   endwhile
1468   " From the remaining 0-2 arguments, extract the remote and Git config
1469   let remote = ''
1470   if empty(args)
1471     let dir_or_config = s:Dir()
1472   elseif len(args) == 1 && type(args[0]) ==# type('') && args[0] !~# '^/\|^\a:[\\/]'
1473     let dir_or_config = s:Dir()
1474     let remote = args[0]
1475   elseif len(args) == 1
1476     let dir_or_config = args[0]
1477     if type(args[0]) ==# type({}) && has_key(args[0], 'remote_name')
1478       let remote = args[0].remote_name
1479     endif
1480   elseif type(args[1]) !=# type('') || args[1] =~# '^/\|^\a:[\\/]'
1481     let dir_or_config = args[1]
1482     let remote = args[0]
1483   else
1484     let dir_or_config = args[0]
1485     let remote = args[1]
1486   endif
1487   return [dir_or_config, remote, flags, cb]
1488 endfunction
1490 function! fugitive#Remote(...) abort
1491   let [dir_or_config, remote, flags, cb] = s:RemoteParseArgs(a:000)
1492   return s:Remote(dir_or_config, remote, flags, cb)
1493 endfunction
1495 function! s:RemoteUrlCallback(remote, callback) abort
1496   return call(a:callback[0], [a:remote.url] + a:callback[1:-1])
1497 endfunction
1499 function! fugitive#RemoteUrl(...) abort
1500   let [dir_or_config, remote_url, flags, cb] = s:RemoteParseArgs(a:000)
1501   if len(cb)
1502     let cb = [function('s:RemoteUrlCallback'), cb]
1503   endif
1504   let remote = s:Remote(dir_or_config, remote_url, flags, cb)
1505   return get(remote, 'url', remote_url)
1506 endfunction
1508 " Section: Quickfix
1510 function! s:QuickfixGet(nr, ...) abort
1511   if a:nr < 0
1512     return call('getqflist', a:000)
1513   else
1514     return call('getloclist', [a:nr] + a:000)
1515   endif
1516 endfunction
1518 function! s:QuickfixSet(nr, ...) abort
1519   if a:nr < 0
1520     return call('setqflist', a:000)
1521   else
1522     return call('setloclist', [a:nr] + a:000)
1523   endif
1524 endfunction
1526 function! s:QuickfixCreate(nr, opts) abort
1527   if has('patch-7.4.2200')
1528     call s:QuickfixSet(a:nr, [], ' ', a:opts)
1529   else
1530     call s:QuickfixSet(a:nr, [], ' ')
1531   endif
1532 endfunction
1534 function! s:QuickfixOpen(nr, mods) abort
1535   let mods = substitute(s:Mods(a:mods), '\<\d*tab\>', '', '')
1536   return mods . (a:nr < 0 ? 'c' : 'l').'open' . (mods =~# '\<vertical\>' ? ' 20' : '')
1537 endfunction
1539 function! s:QuickfixStream(nr, event, title, cmd, first, mods, callback, ...) abort
1540   call s:BlurStatus()
1541   let opts = {'title': a:title, 'context': {'items': []}}
1542   call s:QuickfixCreate(a:nr, opts)
1543   let event = (a:nr < 0 ? 'c' : 'l') . 'fugitive-' . a:event
1544   exe s:DoAutocmd('QuickFixCmdPre ' . event)
1545   let winnr = winnr()
1546   exe s:QuickfixOpen(a:nr, a:mods)
1547   if winnr != winnr()
1548     wincmd p
1549   endif
1551   let buffer = []
1552   let lines = s:SystemList(a:cmd)[0]
1553   for line in lines
1554     call extend(buffer, call(a:callback, a:000 + [line]))
1555     if len(buffer) >= 20
1556       let contexts = map(copy(buffer), 'get(v:val, "context", {})')
1557       lockvar contexts
1558       call extend(opts.context.items, contexts)
1559       unlet contexts
1560       call s:QuickfixSet(a:nr, remove(buffer, 0, -1), 'a')
1561       if a:mods !~# '\<silent\>'
1562         redraw
1563       endif
1564     endif
1565   endfor
1566   call extend(buffer, call(a:callback, a:000 + [0]))
1567   call extend(opts.context.items, map(copy(buffer), 'get(v:val, "context", {})'))
1568   lockvar opts.context.items
1569   call s:QuickfixSet(a:nr, buffer, 'a')
1571   exe s:DoAutocmd('QuickFixCmdPost ' . event)
1572   if a:first
1573     let list = s:QuickfixGet(a:nr)
1574     for index in range(len(list))
1575       if list[index].valid
1576         return (index+1) . (a:nr < 0 ? 'cfirst' : 'lfirst')
1577       endif
1578     endfor
1579   endif
1580   return 'exe'
1581 endfunction
1583 function! fugitive#Cwindow() abort
1584   if &buftype == 'quickfix'
1585     cwindow
1586   else
1587     botright cwindow
1588     if &buftype == 'quickfix'
1589       wincmd p
1590     endif
1591   endif
1592 endfunction
1594 " Section: Repository Object
1596 let s:repo_prototype = {}
1598 function! fugitive#repo(...) abort
1599   let dir = a:0 ? s:GitDir(a:1) : (len(s:GitDir()) ? s:GitDir() : FugitiveExtractGitDir(expand('%:p')))
1600   if dir !=# ''
1601     return extend({'git_dir': dir, 'fugitive_dir': dir}, s:repo_prototype, 'keep')
1602   endif
1603   throw 'fugitive: not a Git repository'
1604 endfunction
1606 function! s:repo_dir(...) dict abort
1607   if !a:0
1608     return self.git_dir
1609   endif
1610   throw 'fugitive: fugitive#repo().dir("...") has been replaced by FugitiveFind(".git/...")'
1611 endfunction
1613 function! s:repo_tree(...) dict abort
1614   let tree = s:Tree(self.git_dir)
1615   if empty(tree)
1616     throw 'fugitive: no work tree'
1617   elseif !a:0
1618     return tree
1619   endif
1620   throw 'fugitive: fugitive#repo().tree("...") has been replaced by FugitiveFind(":(top)...")'
1621 endfunction
1623 function! s:repo_bare() dict abort
1624   throw 'fugitive: fugitive#repo().bare() has been replaced by !empty(FugitiveWorkTree())'
1625 endfunction
1627 function! s:repo_find(object) dict abort
1628   throw 'fugitive: fugitive#repo().find(...) has been replaced by FugitiveFind(...)'
1629 endfunction
1631 function! s:repo_translate(rev) dict abort
1632   throw 'fugitive: fugitive#repo().translate(...) has been replaced by FugitiveFind(...)'
1633 endfunction
1635 function! s:repo_head(...) dict abort
1636   throw 'fugitive: fugitive#repo().head(...) has been replaced by FugitiveHead(...)'
1637 endfunction
1639 call s:add_methods('repo',['dir','tree','bare','find','translate','head'])
1641 function! s:repo_git_command(...) dict abort
1642   throw 'fugitive: fugitive#repo().git_command(...) has been replaced by FugitiveShellCommand(...)'
1643 endfunction
1645 function! s:repo_git_chomp(...) dict abort
1646   silent return substitute(system(fugitive#ShellCommand(a:000, self.git_dir)), '\n$', '', '')
1647 endfunction
1649 function! s:repo_git_chomp_in_tree(...) dict abort
1650   return call(self.git_chomp, a:000, self)
1651 endfunction
1653 function! s:repo_rev_parse(rev) dict abort
1654   throw 'fugitive: fugitive#repo().rev_parse(...) has been replaced by FugitiveExecute("rev-parse", "--verify", ...).stdout'
1655 endfunction
1657 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
1659 function! s:repo_config(name) dict abort
1660   return FugitiveConfigGet(a:name, self.git_dir)
1661 endfunction
1663 call s:add_methods('repo',['config'])
1665 " Section: File API
1667 function! s:DirCommitFile(path) abort
1668   let vals = matchlist(s:Slash(a:path), s:dir_commit_file)
1669   if empty(vals)
1670     return ['', '', '']
1671   endif
1672   return [s:Dir(fugitive#UrlDecode(vals[1])), vals[2], empty(vals[2]) ? '/.git/index' : fugitive#UrlDecode(vals[3])]
1673 endfunction
1675 function! s:DirRev(url) abort
1676   let [dir, commit, file] = s:DirCommitFile(a:url)
1677   return [dir, commit . file ==# '/.git/index' ? ':' : (!empty(dir) && commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
1678 endfunction
1680 function! fugitive#Parse(url) abort
1681   return reverse(s:DirRev(a:url))
1682 endfunction
1684 let s:merge_heads = ['MERGE_HEAD', 'REBASE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD']
1685 function! s:MergeHead(dir) abort
1686   let dir = fugitive#Find('.git/', a:dir)
1687   for head in s:merge_heads
1688     if filereadable(dir . head)
1689       return head
1690     endif
1691   endfor
1692   return ''
1693 endfunction
1695 function! s:Owner(path, ...) abort
1696   let dir = a:0 ? s:Dir(a:1) : s:Dir()
1697   if empty(dir)
1698     return ''
1699   endif
1700   let actualdir = fugitive#Find('.git/', dir)
1701   let [pdir, commit, file] = s:DirCommitFile(a:path)
1702   if s:SameRepo(dir, pdir)
1703     if commit =~# '^\x\{40,\}$'
1704       return commit
1705     elseif commit ==# '2'
1706       return '@'
1707     elseif commit ==# '0'
1708       return ''
1709     endif
1710     let merge_head = s:MergeHead(dir)
1711     if empty(merge_head)
1712       return ''
1713     endif
1714     if commit ==# '3'
1715       return merge_head
1716     elseif commit ==# '1'
1717       return s:TreeChomp('merge-base', 'HEAD', merge_head, '--')
1718     endif
1719   endif
1720   let path = fnamemodify(a:path, ':p')
1721   if s:cpath(actualdir, strpart(path, 0, len(actualdir))) && a:path =~# 'HEAD$'
1722     return strpart(path, len(actualdir))
1723   endif
1724   let refs = fugitive#Find('.git/refs', dir)
1725   if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
1726     return strpart(path, len(refs) - 4)
1727   endif
1728   return ''
1729 endfunction
1731 function! fugitive#Real(url) abort
1732   if empty(a:url)
1733     return ''
1734   endif
1735   let [dir, commit, file] = s:DirCommitFile(a:url)
1736   if len(dir)
1737     let tree = s:Tree(dir)
1738     return s:VimSlash((len(tree) ? tree : s:GitDir(dir)) . file)
1739   endif
1740   let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
1741   if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
1742     let url = {pre}Real(a:url)
1743   else
1744     let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
1745   endif
1746   return s:VimSlash(empty(url) ? a:url : url)
1747 endfunction
1749 function! fugitive#Path(url, ...) abort
1750   if empty(a:url)
1751     return ''
1752   endif
1753   let repo = call('s:Dir', a:000[1:-1])
1754   let dir_s = fugitive#Find('.git/', repo)
1755   let tree = fugitive#Find(':/', repo)
1756   if !a:0
1757     return fugitive#Real(a:url)
1758   elseif a:1 =~# '\.$'
1759     let path = s:Slash(fugitive#Real(a:url))
1760     let cwd = getcwd()
1761     let lead = ''
1762     while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
1763       if s:cpath(cwd . '/', path[0 : len(cwd)])
1764         if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
1765           break
1766         endif
1767         return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
1768       endif
1769       let cwd = fnamemodify(cwd, ':h')
1770       let lead .= '../'
1771     endwhile
1772     return a:1[0:-2] . path
1773   endif
1774   let url = a:url
1775   let temp_state = s:TempState(url)
1776   if has_key(temp_state, 'origin_bufnr')
1777     let url = bufname(temp_state.origin_bufnr)
1778   endif
1779   let url = s:Slash(fnamemodify(url, ':p'))
1780   if url =~# '/$' && s:Slash(a:url) !~# '/$'
1781     let url = url[0:-2]
1782   endif
1783   let [argdir, commit, file] = s:DirCommitFile(url)
1784   if !empty(argdir) && !s:SameRepo(argdir, repo)
1785     let file = ''
1786   elseif len(dir_s) && s:cpath(strpart(url, 0, len(dir_s)), dir_s)
1787     let file = '/.git' . strpart(url, len(dir_s)-1)
1788   elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
1789     let file = url[len(tree) : -1]
1790   elseif s:cpath(url) ==# s:cpath(tree)
1791     let file = '/'
1792   endif
1793   if empty(file) && a:1 =~# '^$\|^[.:]/$'
1794     return FugitiveGitPath(fugitive#Real(a:url))
1795   endif
1796   return substitute(file, '^/', '\=a:1', '')
1797 endfunction
1799 function! s:Relative(...) abort
1800   return fugitive#Path(@%, a:0 ? a:1 : ':(top)', a:0 > 1 ? a:2 : s:Dir())
1801 endfunction
1803 function! fugitive#Find(object, ...) abort
1804   if type(a:object) == type(0)
1805     let name = bufname(a:object)
1806     return s:VimSlash(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
1807   elseif a:object =~# '^[~$]'
1808     let prefix = matchstr(a:object, '^[~$]\i*')
1809     let owner = expand(prefix)
1810     return s:VimSlash(FugitiveVimPath((len(owner) ? owner : prefix) . strpart(a:object, len(prefix))))
1811   endif
1812   let rev = s:Slash(a:object)
1813   if rev =~# '^\a\+://' && rev !~# '^fugitive:'
1814     return rev
1815   elseif rev =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
1816     return s:VimSlash(a:object)
1817   elseif rev =~# '^\.\.\=\%(/\|$\)'
1818     return s:VimSlash(simplify(getcwd() . '/' . a:object))
1819   endif
1820   let dir = call('s:GitDir', a:000)
1821   if empty(dir)
1822     let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs\%(\.\.\=$\|\.\.\=/.*\|/.*\|\w:/.*\)')
1823     let dir = FugitiveExtractGitDir(file)
1824     if empty(dir)
1825       return ''
1826     endif
1827   endif
1828   let tree = s:Tree(dir)
1829   let urlprefix = s:DirUrlPrefix(dir)
1830   let base = len(tree) ? tree : urlprefix . '0'
1831   if rev ==# '.git'
1832     let f = len(tree) && len(getftype(tree . '/.git')) ? tree . '/.git' : dir
1833   elseif rev =~# '^\.git/'
1834     let f = strpart(rev, 5)
1835     let fdir = simplify(FugitiveActualDir(dir) . '/')
1836     let cdir = simplify(FugitiveCommonDir(dir) . '/')
1837     if f =~# '^\.\./\.\.\%(/\|$\)'
1838       let f = simplify(len(tree) ? tree . f[2:-1] : fdir . f)
1839     elseif f =~# '^\.\.\%(/\|$\)'
1840       let f = s:PathJoin(base, f[2:-1])
1841     elseif cdir !=# fdir && (
1842           \ f =~# '^\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
1843           \ f !~# '^\%(index$\|index\.lock$\|\w*MSG$\|\w*HEAD$\|logs/\w*HEAD$\|logs$\|rebase-\w\+\)\%(/\|$\)' &&
1844           \ getftime(fdir . f) < 0 && getftime(cdir . f) >= 0)
1845       let f = simplify(cdir . f)
1846     else
1847       let f = simplify(fdir . f)
1848     endif
1849   elseif rev ==# ':/'
1850     let f = tree
1851   elseif rev =~# '^\.\%(/\|$\)'
1852     let f = s:PathJoin(base, rev[1:-1])
1853   elseif rev =~# '^::\%(/\|\a\+\:\)'
1854     let f = rev[2:-1]
1855   elseif rev =~# '^::\.\.\=\%(/\|$\)'
1856     let f = simplify(getcwd() . '/' . rev[2:-1])
1857   elseif rev =~# '^::'
1858     let f = s:PathJoin(base, '/' . rev[2:-1])
1859   elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
1860     let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
1861     if s:cpath(base . '/', (f . '/')[0 : len(base)])
1862       let f = s:PathJoin(urlprefix, +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1))
1863     else
1864       let altdir = FugitiveExtractGitDir(f)
1865       if len(altdir) && !s:cpath(dir, altdir)
1866         return fugitive#Find(a:object, altdir)
1867       endif
1868     endif
1869   elseif rev =~# '^:[0-3]:'
1870     let f = s:PathJoin(urlprefix, rev[1] . '/' . rev[3:-1])
1871   elseif rev ==# ':'
1872     let f = urlprefix
1873   elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
1874     let f = matchstr(rev, ')\zs.*')
1875     if f=~# '^\.\.\=\%(/\|$\)'
1876       let f = simplify(getcwd() . '/' . f)
1877     elseif f !~# '^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
1878       let f = s:PathJoin(base, '/' . f)
1879     endif
1880   elseif rev =~# '^:/\@!'
1881     let f = s:PathJoin(urlprefix, '0/' . rev[1:-1])
1882   else
1883     if !exists('f')
1884       let commit = matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\|^:.*')
1885       let file = substitute(matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\zs:.*'), '^:', '/', '')
1886       if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
1887         let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
1888         if s:cpath(base . '/', (file . '/')[0 : len(base)])
1889           let file = '/' . strpart(file, len(base) + 1)
1890         else
1891           let altdir = FugitiveExtractGitDir(file)
1892           if len(altdir) && !s:cpath(dir, altdir)
1893             return fugitive#Find(a:object, altdir)
1894           endif
1895           return file
1896         endif
1897       endif
1898       let commits = split(commit, '\.\.\.-\@!', 1)
1899       if len(commits) == 2
1900         call map(commits, 'empty(v:val) ? "@" : v:val')
1901         let commit = matchstr(s:ChompDefault('', [dir, 'merge-base'] + commits + ['--']), '\<[0-9a-f]\{40,\}\>')
1902       endif
1903       if commit !~# '^[0-9a-f]\{40,\}$\|^$'
1904         let commit = matchstr(s:ChompDefault('', [dir, 'rev-parse', '--verify', commit . (len(file) ? '^{}' : ''), '--']), '\<[0-9a-f]\{40,\}\>')
1905         if empty(commit) && len(file)
1906           let commit = repeat('0', 40)
1907         endif
1908       endif
1909       if len(commit)
1910         let f = s:PathJoin(urlprefix, commit . file)
1911       else
1912         let f = s:PathJoin(base, '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', ''))
1913       endif
1914     endif
1915   endif
1916   return s:VimSlash(f)
1917 endfunction
1919 function! s:Generate(object, ...) abort
1920   let dir = a:0 ? a:1 : s:Dir()
1921   let f = fugitive#Find(a:object, dir)
1922   if !empty(f)
1923     return f
1924   elseif a:object ==# ':/'
1925     return len(dir) ? s:VimSlash(s:DirUrlPrefix(dir) . '0') : '.'
1926   endif
1927   let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\=\zs.*')
1928   return empty(file) ? '' : fnamemodify(s:VimSlash(file), ':p')
1929 endfunction
1931 function! s:DotRelative(path, ...) abort
1932   let cwd = a:0 ? a:1 : getcwd()
1933   let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
1934   if len(cwd) && s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
1935     return '.' . strpart(path, len(cwd))
1936   endif
1937   return a:path
1938 endfunction
1940 function! fugitive#Object(...) abort
1941   let dir = a:0 > 1 ? s:Dir(a:2) : s:Dir()
1942   let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
1943   if !s:SameRepo(dir, fdir)
1944     let rev = ''
1945   endif
1946   let tree = s:Tree(dir)
1947   let full = a:0 ? a:1 : s:BufName('%')
1948   let full = fnamemodify(full, ':p' . (s:Slash(full) =~# '/$' ? '' : ':s?/$??'))
1949   if empty(rev) && empty(tree)
1950     return FugitiveGitPath(full)
1951   elseif empty(rev)
1952     let rev = fugitive#Path(full, './', dir)
1953     if rev =~# '^\./.git\%(/\|$\)'
1954       return FugitiveGitPath(full)
1955     endif
1956   endif
1957   if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
1958     return rev
1959   else
1960     return FugitiveGitPath(tree . rev[1:-1])
1961   endif
1962 endfunction
1964 let s:var = '\%(<\%(cword\|cWORD\|cexpr\|cfile\|sfile\|slnum\|afile\|abuf\|amatch' . (has('clientserver') ? '\|client' : '') . '\)>\|%\|#<\=\d\+\|##\=\)'
1965 let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
1966 let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
1967 let s:commit_expand = '!\\\@!#\=\d*\|!%'
1969 function! s:BufName(var) abort
1970   if a:var ==# '%'
1971     return bufname(get(s:TempState(), 'origin_bufnr', ''))
1972   elseif a:var =~# '^#\d*$'
1973     let nr = get(s:TempState(+a:var[1:-1]), 'origin_bufnr', '')
1974     return bufname(nr ? nr : +a:var[1:-1])
1975   else
1976     return expand(a:var)
1977   endif
1978 endfunction
1980 function! s:ExpandVar(other, var, flags, esc, ...) abort
1981   let cwd = a:0 ? a:1 : getcwd()
1982   if a:other =~# '^\'
1983     return a:other[1:-1]
1984   elseif a:other =~# '^'''
1985     return substitute(a:other[1:-2], "''", "'", "g")
1986   elseif a:other =~# '^"'
1987     return substitute(a:other[1:-2], '""', '"', "g")
1988   elseif a:other =~# '^[!`]'
1989     let buffer = s:BufName(a:other =~# '[0-9#]' ? '#' . matchstr(a:other, '\d\+') : '%')
1990     let owner = s:Owner(buffer)
1991     return len(owner) ? owner : '@'
1992   elseif a:other =~# '^\~[~.]$'
1993     return s:Slash(getcwd())
1994   elseif len(a:other)
1995     return expand(a:other)
1996   elseif a:var ==# '<cfile>'
1997     let bufnames = [expand('<cfile>')]
1998     if get(maparg('<Plug><cfile>', 'c', 0, 1), 'expr')
1999       try
2000         let bufnames = [eval(maparg('<Plug><cfile>', 'c'))]
2001         if bufnames[0] ==# "\<C-R>\<C-F>"
2002           let bufnames = [expand('<cfile>')]
2003         endif
2004       catch
2005       endtry
2006     endif
2007   elseif a:var =~# '^<'
2008     let bufnames = [s:BufName(a:var)]
2009   elseif a:var ==# '##'
2010     let bufnames = map(argv(), 'fugitive#Real(v:val)')
2011   else
2012     let bufnames = [fugitive#Real(s:BufName(a:var))]
2013   endif
2014   let files = []
2015   for bufname in bufnames
2016     let flags = a:flags
2017     let file = s:DotRelative(bufname, cwd)
2018     while len(flags)
2019       let flag = matchstr(flags, s:flag)
2020       let flags = strpart(flags, len(flag))
2021       if flag ==# ':.'
2022         let file = s:DotRelative(fugitive#Real(file), cwd)
2023       else
2024         let file = fnamemodify(file, flag)
2025       endif
2026     endwhile
2027     let file = s:Slash(file)
2028     if file =~# '^fugitive://'
2029       let [dir, commit, file_candidate] = s:DirCommitFile(file)
2030       let tree = s:Tree(dir)
2031       if len(tree) && len(file_candidate)
2032         let file = (commit =~# '^.$' ? ':' : '') . commit . ':' .
2033               \ s:DotRelative(tree . file_candidate)
2034       elseif empty(file_candidate) && commit !~# '^.$'
2035         let file = commit
2036       endif
2037     endif
2038     call add(files, len(a:esc) ? shellescape(file) : file)
2039   endfor
2040   return join(files, "\1")
2041 endfunction
2043 if has('win32')
2044   let s:fnameescape = " \t\n*?`%#'\"|!<"
2045 else
2046   let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
2047 endif
2049 function! s:Expand(rev, ...) abort
2050   if a:rev =~# '^>' && s:Slash(@%) =~# '^fugitive://' && empty(s:DirCommitFile(@%)[1])
2051     return s:Slash(@%)
2052   elseif a:rev =~# '^>\=:[0-3]$'
2053     let file = len(expand('%')) ? a:rev[-2:-1] . ':%' : '%'
2054   elseif a:rev =~# '^>\%(:\=/\)\=$'
2055     let file = '%'
2056   elseif a:rev =~# '^>[> ]\@!' && @% !~# '^fugitive:' && s:Slash(@%) =~# '://\|^$'
2057     let file = '%'
2058   elseif a:rev ==# '>:'
2059     let file = empty(s:DirCommitFile(@%)[0]) ? ':0:%' : '%'
2060   elseif a:rev =~# '^>[> ]\@!'
2061     let rev = (a:rev =~# '^>[~^]' ? '!' : '') . a:rev[1:-1]
2062     let prefix = matchstr(rev, '^\%(\\.\|{[^{}]*}\|[^:]\)*')
2063     if prefix !=# rev
2064       let file = rev
2065     else
2066       let file = len(expand('%')) ? rev . ':%' : '%'
2067     endif
2068   elseif s:Slash(a:rev) =~# '^\a\a\+://'
2069     let file = substitute(a:rev, '\\\@<!\%(#\a\|%\x\x\)', '\\&', 'g')
2070   else
2071     let file = a:rev
2072   endif
2073   return substitute(file,
2074         \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~.]\)\|' . s:expand,
2075         \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),"", a:0 ? a:1 : getcwd()), "\1", " ")', 'g')
2076 endfunction
2078 function! fugitive#Expand(object) abort
2079   return substitute(a:object,
2080         \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~.]\)\|' . s:expand,
2081         \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5)), "\1", " ")', 'g')
2082 endfunction
2084 function! s:SplitExpandChain(string, ...) abort
2085   let list = []
2086   let string = a:string
2087   let dquote = '"\%([^"]\|""\|\\"\)*"\|'
2088   let cwd = a:0 ? a:1 : getcwd()
2089   while string =~# '\S'
2090     if string =~# '^\s*|'
2091       return [list, substitute(string, '^\s*', '', '')]
2092     endif
2093     let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
2094     let string = strpart(string, len(arg))
2095     let arg = substitute(arg, '^\s\+', '', '')
2096     if !exists('seen_separator')
2097       let arg = substitute(arg, '^\%([^:.][^:]*:\|^:\%((literal)\)\=\|^:[0-3]:\)\=\zs\.\.\=\%(/.*\)\=$',
2098             \ '\=s:DotRelative(s:Slash(simplify(getcwd() . "/" . submatch(0))), cwd)', '')
2099     endif
2100     let arg = substitute(arg,
2101           \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~]\|^\~\w*\|\$\w\+\)\|' . s:expand,
2102           \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5), cwd)', 'g')
2103     call extend(list, split(arg, "\1", 1))
2104     if arg ==# '--'
2105       let seen_separator = 1
2106     endif
2107   endwhile
2108   return [list, '']
2109 endfunction
2111 let s:trees = {}
2112 let s:indexes = {}
2113 function! s:TreeInfo(dir, commit) abort
2114   let key = s:GitDir(a:dir)
2115   if a:commit =~# '^:\=[0-3]$'
2116     let index = get(s:indexes, key, [])
2117     let newftime = getftime(fugitive#Find('.git/index', a:dir))
2118     if get(index, 0, -2) < newftime
2119       let [lines, exec_error] = s:LinesError([a:dir, 'ls-files', '--stage', '--'])
2120       let s:indexes[key] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
2121       if exec_error
2122         return [{}, -1]
2123       endif
2124       for line in lines
2125         let [info, filename] = split(line, "\t")
2126         let [mode, sha, stage] = split(info, '\s\+')
2127         let s:indexes[key][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
2128         while filename =~# '/'
2129           let filename = substitute(filename, '/[^/]*$', '', '')
2130           let s:indexes[key][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
2131         endwhile
2132       endfor
2133     endif
2134     return [get(s:indexes[key][1], a:commit[-1:-1], {}), newftime]
2135   elseif a:commit =~# '^\x\{40,\}$'
2136     if !has_key(s:trees, key)
2137       let s:trees[key] = {}
2138     endif
2139     if !has_key(s:trees[key], a:commit)
2140       let ftime = s:ChompDefault('', [a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'])
2141       if empty(ftime)
2142         let s:trees[key][a:commit] = [{}, -1]
2143         return s:trees[key][a:commit]
2144       endif
2145       let s:trees[key][a:commit] = [{}, +ftime]
2146       let [lines, exec_error] = s:LinesError([a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'])
2147       if exec_error
2148         return s:trees[key][a:commit]
2149       endif
2150       for line in lines
2151         let [info, filename] = split(line, "\t")
2152         let [mode, type, sha, size] = split(info, '\s\+')
2153         let s:trees[key][a:commit][0][filename] = [+ftime, mode, type, sha, +size, filename]
2154       endfor
2155     endif
2156     return s:trees[key][a:commit]
2157   endif
2158   return [{}, -1]
2159 endfunction
2161 function! s:PathInfo(url) abort
2162   let [dir, commit, file] = s:DirCommitFile(a:url)
2163   if empty(dir) || !get(g:, 'fugitive_file_api', 1)
2164     return [-1, '000000', '', '', -1]
2165   endif
2166   let path = substitute(file[1:-1], '/*$', '', '')
2167   let [tree, ftime] = s:TreeInfo(dir, commit)
2168   let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
2169   if empty(entry) || file =~# '/$' && entry[2] !=# 'tree'
2170     return [-1, '000000', '', '', -1]
2171   else
2172     return entry
2173   endif
2174 endfunction
2176 function! fugitive#simplify(url) abort
2177   let [dir, commit, file] = s:DirCommitFile(a:url)
2178   if empty(dir)
2179     return ''
2180   elseif empty(commit)
2181     return s:VimSlash(s:DirUrlPrefix(simplify(s:GitDir(dir))))
2182   endif
2183   if file =~# '/\.\.\%(/\|$\)'
2184     let tree = s:Tree(dir)
2185     if len(tree)
2186       let path = simplify(tree . file)
2187       if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
2188         return s:VimSlash(path)
2189       endif
2190     endif
2191   endif
2192   return s:VimSlash(s:PathJoin(s:DirUrlPrefix(simplify(s:GitDir(dir))), commit . simplify(file)))
2193 endfunction
2195 function! fugitive#resolve(url) abort
2196   let url = fugitive#simplify(a:url)
2197   if url =~? '^fugitive:'
2198     return url
2199   else
2200     return resolve(url)
2201   endif
2202 endfunction
2204 function! fugitive#getftime(url) abort
2205   return s:PathInfo(a:url)[0]
2206 endfunction
2208 function! fugitive#getfsize(url) abort
2209   let entry = s:PathInfo(a:url)
2210   if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
2211     let dir = s:DirCommitFile(a:url)[0]
2212     let entry[4] = +s:ChompDefault(-1, [dir, 'cat-file', '-s', entry[3]])
2213   endif
2214   return entry[4]
2215 endfunction
2217 function! fugitive#getftype(url) abort
2218   return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
2219 endfunction
2221 function! fugitive#filereadable(url) abort
2222   return s:PathInfo(a:url)[2] ==# 'blob'
2223 endfunction
2225 function! fugitive#filewritable(url) abort
2226   let [dir, commit, file] = s:DirCommitFile(a:url)
2227   if commit !~# '^\d$' || !filewritable(fugitive#Find('.git/index', dir))
2228     return 0
2229   endif
2230   return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
2231 endfunction
2233 function! fugitive#isdirectory(url) abort
2234   return s:PathInfo(a:url)[2] ==# 'tree'
2235 endfunction
2237 function! fugitive#getfperm(url) abort
2238   let [dir, commit, file] = s:DirCommitFile(a:url)
2239   let perm = getfperm(dir)
2240   let fperm = s:PathInfo(a:url)[1]
2241   if fperm ==# '040000'
2242     let fperm = '000755'
2243   endif
2244   if fperm !~# '[15]'
2245     let perm = tr(perm, 'x', '-')
2246   endif
2247   if fperm !~# '[45]$'
2248     let perm = tr(perm, 'rw', '--')
2249   endif
2250   if commit !~# '^\d$'
2251     let perm = tr(perm, 'w', '-')
2252   endif
2253   return perm ==# '---------' ? '' : perm
2254 endfunction
2256 function! s:UpdateIndex(dir, info) abort
2257   let info = join(a:info[0:-2]) . "\t" . a:info[-1] . "\n"
2258   let [error, exec_error] = s:StdoutToFile('', [a:dir, 'update-index', '--index-info'], info)
2259   return !exec_error ? '' : len(error) ? error : 'unknown update-index error'
2260 endfunction
2262 function! fugitive#setfperm(url, perm) abort
2263   let [dir, commit, file] = s:DirCommitFile(a:url)
2264   let entry = s:PathInfo(a:url)
2265   let perm = fugitive#getfperm(a:url)
2266   if commit !~# '^\d$' || entry[2] !=# 'blob' ||
2267       \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
2268     return -2
2269   endif
2270   let error = s:UpdateIndex(dir, [a:perm =~# 'x' ? '000755' : '000644', entry[3], commit, file[1:-1]])
2271   return len(error) ? -1 : 0
2272 endfunction
2274 if !exists('s:blobdirs')
2275   let s:blobdirs = {}
2276 endif
2277 function! s:BlobTemp(url) abort
2278   let [dir, commit, file] = s:DirCommitFile(a:url)
2279   if empty(file)
2280     return ''
2281   endif
2282   let key = s:GitDir(dir)
2283   if !has_key(s:blobdirs, key)
2284     let s:blobdirs[key] = tempname()
2285   endif
2286   let tempfile = s:blobdirs[key] . '/' . commit . file
2287   let tempparent = fnamemodify(tempfile, ':h')
2288   if !isdirectory(tempparent)
2289     call mkdir(tempparent, 'p')
2290   elseif isdirectory(tempfile)
2291     if commit =~# '^\d$' && has('patch-7.4.1107')
2292       call delete(tempfile, 'rf')
2293     else
2294       return ''
2295     endif
2296   endif
2297   if commit =~# '^\d$' || !filereadable(tempfile)
2298     let rev = s:DirRev(a:url)[1]
2299     let blob_or_filters = fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
2300     let exec_error = s:StdoutToFile(tempfile, [dir, 'cat-file', blob_or_filters, rev])[1]
2301     if exec_error
2302       call delete(tempfile)
2303       return ''
2304     endif
2305   endif
2306   return s:Resolve(tempfile)
2307 endfunction
2309 function! fugitive#readfile(url, ...) abort
2310   let entry = s:PathInfo(a:url)
2311   if entry[2] !=# 'blob'
2312     return []
2313   endif
2314   let temp = s:BlobTemp(a:url)
2315   if empty(temp)
2316     return []
2317   endif
2318   return call('readfile', [temp] + a:000)
2319 endfunction
2321 function! fugitive#writefile(lines, url, ...) abort
2322   let url = type(a:url) ==# type('') ? a:url : ''
2323   let [dir, commit, file] = s:DirCommitFile(url)
2324   let entry = s:PathInfo(url)
2325   if commit =~# '^\d$' && entry[2] !=# 'tree'
2326     let temp = tempname()
2327     if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
2328       call writefile(fugitive#readfile(url, 'b'), temp, 'b')
2329     endif
2330     call call('writefile', [a:lines, temp] + a:000)
2331     let hash = s:ChompDefault('', [dir, '--literal-pathspecs', 'hash-object', '-w', FugitiveGitPath(temp)])
2332     let mode = entry[1] !=# '000000' ? entry[1] : '100644'
2333     if hash =~# '^\x\{40,\}$'
2334       let error = s:UpdateIndex(dir, [mode, hash, commit, file[1:-1]])
2335       if empty(error)
2336         return 0
2337       endif
2338     endif
2339   endif
2340   return call('writefile', [a:lines, a:url] + a:000)
2341 endfunction
2343 let s:globsubs = {
2344       \ '/**/': '/\%([^./][^/]*/\)*',
2345       \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
2346       \ '**/': '[^/]*\%(/[^./][^/]*\)*',
2347       \ '**': '.*',
2348       \ '/*': '/[^/.][^/]*',
2349       \ '*': '[^/]*',
2350       \ '?': '[^/]'}
2351 function! fugitive#glob(url, ...) abort
2352   let [repo, commit, glob] = s:DirCommitFile(a:url)
2353   let dirglob = s:GitDir(repo)
2354   let append = matchstr(glob, '/*$')
2355   let glob = substitute(glob, '/*$', '', '')
2356   let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\$]\|^^', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
2357   let results = []
2358   for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
2359     if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(fugitive#Find('.git/HEAD', dir))
2360       continue
2361     endif
2362     let files = items(s:TreeInfo(dir, commit)[0])
2363     if len(append)
2364       call filter(files, 'v:val[1][2] ==# "tree"')
2365     endif
2366     call map(files, 'v:val[0]')
2367     call filter(files, 'v:val =~# pattern')
2368     let prepend = s:DirUrlPrefix(dir) . substitute(commit, '^:', '', '') . '/'
2369     call sort(files)
2370     call map(files, 's:VimSlash(s:PathJoin(prepend, v:val . append))')
2371     call extend(results, files)
2372   endfor
2373   if a:0 > 1 && a:2
2374     return results
2375   else
2376     return join(results, "\n")
2377   endif
2378 endfunction
2380 function! fugitive#delete(url, ...) abort
2381   let [dir, commit, file] = s:DirCommitFile(a:url)
2382   if a:0 && len(a:1) || commit !~# '^\d$'
2383     return -1
2384   endif
2385   let entry = s:PathInfo(a:url)
2386   if entry[2] !=# 'blob'
2387     return -1
2388   endif
2389   let error = s:UpdateIndex(dir, ['000000', '0000000000000000000000000000000000000000', commit, file[1:-1]])
2390   return len(error) ? -1 : 0
2391 endfunction
2393 " Section: Completion
2395 function! s:FilterEscape(items, ...) abort
2396   let items = copy(a:items)
2397   call map(items, 'fnameescape(v:val)')
2398   if !a:0 || type(a:1) != type('')
2399     let match = ''
2400   else
2401     let match = substitute(a:1, '^[+>]\|\\\@<![' . substitute(s:fnameescape, '\\', '', '') . ']', '\\&', 'g')
2402   endif
2403   let cmp = s:FileIgnoreCase(1) ? '==?' : '==#'
2404   return filter(items, 'strpart(v:val, 0, strlen(match)) ' . cmp . ' match')
2405 endfunction
2407 function! s:GlobComplete(lead, pattern, ...) abort
2408   if a:lead ==# '/'
2409     return []
2410   else
2411     let results = glob(substitute(a:lead . a:pattern, '[\{}]', '\\&', 'g'), a:0 ? a:1 : 0, 1)
2412   endif
2413   call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
2414   call map(results, 'v:val[ strlen(a:lead) : -1 ]')
2415   return results
2416 endfunction
2418 function! fugitive#CompletePath(base, ...) abort
2419   let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
2420   let stripped = matchstr(a:base, '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\)')
2421   let base = strpart(a:base, len(stripped))
2422   if len(stripped) || a:0 < 4
2423     let root = s:Tree(dir)
2424   else
2425     let root = a:4
2426   endif
2427   if root !=# '/' && len(root)
2428     let root .= '/'
2429   endif
2430   if empty(stripped)
2431     let stripped = matchstr(a:base, '^\%(:(literal)\|:\)')
2432     let base = strpart(a:base, len(stripped))
2433   endif
2434   if base =~# '^\.git/' && len(dir)
2435     let pattern = s:gsub(base[5:-1], '/', '*&').'*'
2436     let fdir = fugitive#Find('.git/', dir)
2437     let matches = s:GlobComplete(fdir, pattern)
2438     let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
2439     if len(cdir) && s:cpath(fdir) !=# s:cpath(cdir)
2440       call extend(matches, s:GlobComplete(cdir, pattern))
2441     endif
2442     call s:Uniq(matches)
2443     call map(matches, "'.git/' . v:val")
2444   elseif base =~# '^\~/'
2445     let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
2446   elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/'
2447     let matches = s:GlobComplete('', base . '*')
2448   elseif len(root)
2449     let matches = s:GlobComplete(root, s:gsub(base, '/', '*&').'*')
2450   else
2451     let matches = []
2452   endif
2453   call map(matches, 's:fnameescape(s:Slash(stripped . v:val))')
2454   return matches
2455 endfunction
2457 function! fugitive#PathComplete(...) abort
2458   return call('fugitive#CompletePath', a:000)
2459 endfunction
2461 function! s:CompleteHeads(dir) abort
2462   if empty(a:dir)
2463     return []
2464   endif
2465   let dir = fugitive#Find('.git/', a:dir)
2466   return sort(filter(['HEAD', 'FETCH_HEAD', 'ORIG_HEAD'] + s:merge_heads, 'filereadable(dir . v:val)')) +
2467         \ sort(s:LinesError([a:dir, 'rev-parse', '--symbolic', '--branches', '--tags', '--remotes'])[0])
2468 endfunction
2470 function! fugitive#CompleteObject(base, ...) abort
2471   let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
2472   let tree = s:Tree(dir)
2473   let cwd = getcwd()
2474   let subdir = ''
2475   if len(tree) && s:cpath(tree . '/', cwd[0 : len(tree)])
2476     let subdir = strpart(cwd, len(tree) + 1) . '/'
2477   endif
2478   let base = s:Expand(a:base)
2480   if a:base =~# '^!\d*$' && base !~# '^!'
2481     return [base]
2482   elseif base =~# '^\.\=/\|^:(' || base !~# ':'
2483     let results = []
2484     if base =~# '^refs/'
2485       let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
2486       let results += map(s:GlobComplete(cdir, base . '*'), 's:Slash(v:val)')
2487       call map(results, 's:fnameescape(v:val)')
2488     elseif base !~# '^\.\=/\|^:('
2489       let heads = s:CompleteHeads(dir)
2490       if filereadable(fugitive#Find('.git/refs/stash', dir))
2491         let heads += ["stash"]
2492         let heads += sort(s:LinesError(["stash","list","--pretty=format:%gd"], dir)[0])
2493       endif
2494       let results += s:FilterEscape(heads, fnameescape(base))
2495     endif
2496     let results += a:0 == 1 || a:0 >= 3 ? fugitive#CompletePath(base, 0, '', dir, a:0 >= 4 ? a:4 : tree) : fugitive#CompletePath(base)
2497     return results
2499   elseif base =~# '^:'
2500     let entries = s:LinesError(['ls-files','--stage'], dir)[0]
2501     if base =~# ':\./'
2502       call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
2503     endif
2504     call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
2505     if base !~# '^:[0-3]\%(:\|$\)'
2506       call filter(entries,'v:val[1] == "0"')
2507       call map(entries,'v:val[2:-1]')
2508     endif
2510   else
2511     let parent = matchstr(base, '.*[:/]')
2512     let entries = s:LinesError(['ls-tree', substitute(parent,  ':\zs\./', '\=subdir', '')], dir)[0]
2513     call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
2514     call map(entries,'parent.s:sub(v:val,".*\t","")')
2515   endif
2516   return s:FilterEscape(entries, fnameescape(base))
2517 endfunction
2519 function! s:CompleteSub(subcommand, A, L, P, ...) abort
2520   let pre = strpart(a:L, 0, a:P)
2521   if pre =~# ' -- '
2522     return fugitive#CompletePath(a:A)
2523   elseif a:A =~# '^-' || a:A is# 0
2524     return s:FilterEscape(split(s:ChompDefault('', [a:subcommand, '--git-completion-helper']), ' '), a:A)
2525   elseif !a:0
2526     return fugitive#CompleteObject(a:A, s:Dir())
2527   elseif type(a:1) == type(function('tr'))
2528     return call(a:1, [a:A, a:L, a:P] + (a:0 > 1 ? a:2 : []))
2529   else
2530     return s:FilterEscape(a:1, a:A)
2531   endif
2532 endfunction
2534 function! s:CompleteRevision(A, L, P, ...) abort
2535   return s:FilterEscape(s:CompleteHeads(a:0 ? a:1 : s:Dir()), a:A)
2536 endfunction
2538 function! s:CompleteRemote(A, L, P, ...) abort
2539   let dir = a:0 ? a:1 : s:Dir()
2540   let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
2541   if !empty(remote)
2542     let matches = s:LinesError([dir, 'ls-remote', remote])[0]
2543     call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
2544     call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
2545   else
2546     let matches = s:LinesError([dir, 'remote'])[0]
2547   endif
2548   return s:FilterEscape(matches, a:A)
2549 endfunction
2551 " Section: Buffer auto-commands
2553 augroup fugitive_dummy_events
2554   autocmd!
2555   autocmd User Fugitive* "
2556   autocmd BufWritePre,FileWritePre,FileWritePost * "
2557   autocmd BufNewFile * "
2558   autocmd QuickfixCmdPre,QuickfixCmdPost * "
2559 augroup END
2561 function! s:ReplaceCmd(cmd) abort
2562   let temp = tempname()
2563   let [err, exec_error] = s:StdoutToFile(temp, a:cmd)
2564   if exec_error
2565     throw 'fugitive: ' . (len(err) ? substitute(err, "\n$", '', '') : 'unknown error running ' . string(a:cmd))
2566   endif
2567   setlocal noswapfile
2568   silent exe 'lockmarks keepalt noautocmd 0read ++edit' s:fnameescape(temp)
2569   if &foldenable && foldlevel('$') > 0
2570     set nofoldenable
2571     silent keepjumps $delete _
2572     set foldenable
2573   else
2574     silent keepjumps $delete _
2575   endif
2576   call delete(temp)
2577   if s:cpath(s:AbsoluteVimPath(bufnr('$')), temp)
2578     silent! noautocmd execute bufnr('$') . 'bwipeout'
2579   endif
2580 endfunction
2582 function! s:FormatLog(dict) abort
2583   return a:dict.commit . ' ' . a:dict.subject
2584 endfunction
2586 function! s:FormatRebase(dict) abort
2587   return a:dict.status . ' ' . a:dict.commit . ' ' . a:dict.subject
2588 endfunction
2590 function! s:FormatFile(dict) abort
2591   return a:dict.status . ' ' . a:dict.filename
2592 endfunction
2594 function! s:Format(val) abort
2595   if type(a:val) == type({})
2596     return s:Format{a:val.type}(a:val)
2597   elseif type(a:val) == type([])
2598     return map(copy(a:val), 's:Format(v:val)')
2599   else
2600     return '' . a:val
2601   endif
2602 endfunction
2604 function! s:AddHeader(to, key, value) abort
2605   if empty(a:value)
2606     return
2607   endif
2608   call add(a:to.lines, a:key . ':' . (len(a:value) ? ' ' . a:value : ''))
2609 endfunction
2611 function! s:AddSection(to, label, lines, ...) abort
2612   let note = a:0 ? a:1 : ''
2613   if empty(a:lines) && empty(note)
2614     return
2615   endif
2616   call extend(a:to.lines, ['', a:label . (len(note) ? ': ' . note : ' (' . len(a:lines) . ')')] + s:Format(a:lines))
2617 endfunction
2619 function! s:AddDiffSection(to, stat, label, files) abort
2620   if empty(a:files)
2621     return
2622   endif
2623   let diff_section = a:stat.diff[a:label]
2624   let expanded = a:stat.expanded[a:label]
2625   let was_expanded = get(getbufvar(a:stat.bufnr, 'fugitive_expanded', {}), a:label, {})
2626   call extend(a:to.lines, ['', a:label . ' (' . len(a:files) . ')'])
2627   for file in a:files
2628     call add(a:to.lines, s:Format(file))
2629     if has_key(was_expanded, file.filename)
2630       let [diff, start] = s:StageInlineGetDiff(diff_section, file)
2631       if len(diff)
2632         let expanded[file.filename] = [start]
2633         call extend(a:to.lines, diff)
2634       endif
2635     endif
2636   endfor
2637 endfunction
2639 function! s:QueryLog(refspec, limit, dir) abort
2640   let [log, exec_error] = s:LinesError(['log', '-n', '' . a:limit, '--pretty=format:%h%x09%s'] + a:refspec + ['--'], a:dir)
2641   call map(log, 'split(v:val, "\t", 1)')
2642   call map(log, '{"type": "Log", "commit": v:val[0], "subject": join(v:val[1 : -1], "\t")}')
2643   let result = {'error': exec_error ? 1 : 0, 'overflow': 0, 'entries': log}
2644   if len(log) == a:limit
2645     call remove(log, -1)
2646     let result.overflow = 1
2647   endif
2648   return result
2649 endfunction
2651 function! s:QueryLogRange(old, new, dir) abort
2652   if empty(a:old) || empty(a:new)
2653     return {'error': 2, 'overflow': 0, 'entries': []}
2654   endif
2655   return s:QueryLog([a:old . '..' . a:new], 256, a:dir)
2656 endfunction
2658 function! s:AddLogSection(to, label, log) abort
2659   if empty(a:log.entries)
2660     return
2661   endif
2662   let label = a:label . ' (' . len(a:log.entries) . (a:log.overflow ? '+' : '') . ')'
2663   call extend(a:to.lines, ['', label] + s:Format(a:log.entries))
2664 endfunction
2666 let s:rebase_abbrevs = {
2667       \ 'p': 'pick',
2668       \ 'r': 'reword',
2669       \ 'e': 'edit',
2670       \ 's': 'squash',
2671       \ 'f': 'fixup',
2672       \ 'x': 'exec',
2673       \ 'd': 'drop',
2674       \ 'l': 'label',
2675       \ 't': 'reset',
2676       \ 'm': 'merge',
2677       \ 'b': 'break',
2678       \ }
2680 function! s:MapStatus() abort
2681   call fugitive#MapJumps()
2682   call s:Map('n', '-', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
2683   call s:Map('x', '-', ":<C-U>execute <SID>Do('Toggle',1)<CR>", '<silent>')
2684   call s:Map('n', 's', ":<C-U>execute <SID>Do('Stage',0)<CR>", '<silent>')
2685   call s:Map('x', 's', ":<C-U>execute <SID>Do('Stage',1)<CR>", '<silent>')
2686   call s:Map('n', 'u', ":<C-U>execute <SID>Do('Unstage',0)<CR>", '<silent>')
2687   call s:Map('x', 'u', ":<C-U>execute <SID>Do('Unstage',1)<CR>", '<silent>')
2688   call s:Map('n', 'U', ":<C-U>Git reset -q<CR>", '<silent>')
2689   call s:MapMotion('gu', "exe <SID>StageJump(v:count, 'Untracked', 'Unstaged')")
2690   call s:MapMotion('gU', "exe <SID>StageJump(v:count, 'Unstaged', 'Untracked')")
2691   call s:MapMotion('gs', "exe <SID>StageJump(v:count, 'Staged')")
2692   call s:MapMotion('gp', "exe <SID>StageJump(v:count, 'Unpushed')")
2693   call s:MapMotion('gP', "exe <SID>StageJump(v:count, 'Unpulled')")
2694   call s:MapMotion('gr', "exe <SID>StageJump(v:count, 'Rebasing')")
2695   call s:Map('n', 'C', ":echoerr 'fugitive: C has been removed in favor of cc'<CR>", '<silent><unique>')
2696   call s:Map('n', 'a', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
2697   call s:Map('n', 'i', ":<C-U>execute <SID>NextExpandedHunk(v:count1)<CR>", '<silent>')
2698   call s:Map('n', "=", ":<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>", '<silent>')
2699   call s:Map('n', "<", ":<C-U>execute <SID>StageInline('hide',  line('.'),v:count)<CR>", '<silent>')
2700   call s:Map('n', ">", ":<C-U>execute <SID>StageInline('show',  line('.'),v:count)<CR>", '<silent>')
2701   call s:Map('x', "=", ":<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2702   call s:Map('x', "<", ":<C-U>execute <SID>StageInline('hide',  line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2703   call s:Map('x', ">", ":<C-U>execute <SID>StageInline('show',  line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2704   call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
2705   call s:Map('n', 'dd', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>", '<silent>')
2706   call s:Map('n', 'dh', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
2707   call s:Map('n', 'ds', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
2708   call s:Map('n', 'dp', ":<C-U>execute <SID>StageDiffEdit()<CR>", '<silent>')
2709   call s:Map('n', 'dv', ":<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>", '<silent>')
2710   call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
2711   call s:Map('n', 'P', ":<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>", '<silent>')
2712   call s:Map('x', 'P', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
2713   call s:Map('n', 'p', ":<C-U>if v:count<Bar>silent exe <SID>GF('pedit')<Bar>else<Bar>echoerr 'Use = for inline diff, P for :Git add/reset --patch, 1p for :pedit'<Bar>endif<CR>", '<silent>')
2714   call s:Map('x', 'p', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
2715   call s:Map('n', 'I', ":<C-U>execute <SID>StagePatch(line('.'),line('.'))<CR>", '<silent>')
2716   call s:Map('x', 'I', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
2717   call s:Map('n', 'gq', ":<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>", '<silent>')
2718   call s:Map('n', 'R', ":echohl WarningMsg<Bar>echo 'Reloading is automatic.  Use :e to force'<Bar>echohl NONE<CR>", '<silent>')
2719   call s:Map('n', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
2720   call s:Map('x', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
2721   call s:Map('n', 'X', ":<C-U>execute <SID>StageDelete(line('.'), 0, v:count)<CR>", '<silent>')
2722   call s:Map('x', 'X', ":<C-U>execute <SID>StageDelete(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
2723   call s:Map('n', 'gI', ":<C-U>execute <SID>StageIgnore(line('.'), line('.'), v:count)<CR>", '<silent>')
2724   call s:Map('x', 'gI', ":<C-U>execute <SID>StageIgnore(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
2725   call s:Map('n', '.', ':<C-U> <C-R>=<SID>StageArgs(0)<CR><Home>')
2726   call s:Map('x', '.', ':<C-U> <C-R>=<SID>StageArgs(1)<CR><Home>')
2727 endfunction
2729 function! fugitive#BufReadStatus(cmdbang) abort
2730   exe s:VersionCheck()
2731   let amatch = s:Slash(expand('%:p'))
2732   if a:cmdbang
2733     unlet! b:fugitive_expanded
2734   endif
2735   unlet! b:fugitive_reltime b:fugitive_type
2736   let dir = s:Dir()
2737   let stat = {'bufnr': bufnr(''), 'reltime': reltime(), 'work_tree': s:Tree(dir)}
2738   try
2739     let config = fugitive#Config(dir)
2741     let cmd = [dir]
2742     if amatch !~# '^fugitive:' && s:cpath($GIT_INDEX_FILE !=# '' ? resolve(s:GitIndexFileEnv()) : fugitive#Find('.git/index', dir)) !=# s:cpath(amatch)
2743       let cmd += [{'env': {'GIT_INDEX_FILE': FugitiveGitPath(amatch)}}]
2744     endif
2746     if fugitive#GitVersion(2, 15)
2747       call add(cmd, '--no-optional-locks')
2748     endif
2750     if !empty(stat.work_tree)
2751       let status_cmd = cmd + ['status', '-bz']
2752       call add(status_cmd, fugitive#GitVersion(2, 11) ? '--porcelain=v2' : '--porcelain')
2753       let status_exec = fugitive#Execute(status_cmd, function('len'))
2754     endif
2756     doautocmd <nomodeline> BufReadPre
2758     setlocal readonly nomodifiable noswapfile nomodeline buftype=nowrite
2759     call s:MapStatus()
2761     let [staged, unstaged, untracked] = [[], [], []]
2762     let stat.props = {}
2764     if !exists('status_exec')
2765       let branch = FugitiveHead(0, dir)
2766       let head = FugitiveHead(11, dir)
2768     elseif fugitive#Wait(status_exec).exit_status
2769       return 'echoerr ' . string('fugitive: ' . s:JoinChomp(status_exec.stderr))
2771     elseif status_exec.args[-1] ==# '--porcelain=v2'
2772       let output = split(tr(join(status_exec.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
2773       let i = 0
2774       while i < len(output)
2775         let line = output[i]
2776         let prop = matchlist(line, '# \(\S\+\) \(.*\)')
2777         if len(prop)
2778           let stat.props[prop[1]] = prop[2]
2779         elseif line[0] ==# '?'
2780           call add(untracked, {'type': 'File', 'status': line[0], 'filename': line[2:-1], 'relative': [line[2:-1]]})
2781         elseif line[0] !=# '#'
2782           if line[0] ==# 'u'
2783             let file = matchstr(line, '^.\{37\} \x\{40,\} \x\{40,\} \x\{40,\} \zs.*$')
2784           else
2785             let file = matchstr(line, '^.\{30\} \x\{40,\} \x\{40,\} \zs.*$')
2786           endif
2787           if line[0] ==# '2'
2788             let i += 1
2789             let file = matchstr(file, ' \zs.*')
2790             let relative = [file, output[i]]
2791           else
2792             let relative = [file]
2793           endif
2794           let filename = join(reverse(copy(relative)), ' -> ')
2795           let sub = matchstr(line, '^[12u] .. \zs....')
2796           if line[2] !=# '.'
2797             call add(staged, {'type': 'File', 'status': line[2], 'filename': filename, 'relative': relative, 'submodule': sub})
2798           endif
2799           if line[3] !=# '.'
2800             let sub = matchstr(line, '^[12u] .. \zs....')
2801             call add(unstaged, {'type': 'File', 'status': get({'C':'M','M':'?','U':'?'}, matchstr(sub, 'S\.*\zs[CMU]'), line[3]), 'filename': file, 'relative': [file], 'submodule': sub})
2802           endif
2803         endif
2804         let i += 1
2805       endwhile
2806       let branch = substitute(get(stat.props, 'branch.head', '(unknown)'), '\C^(\%(detached\|unknown\))$', '', '')
2807       if len(branch)
2808         let head = branch
2809       elseif has_key(stat.props, 'branch.oid')
2810         let head = stat.props['branch.oid'][0:10]
2811       else
2812         let head = FugitiveHead(11, dir)
2813       endif
2815     else
2816       let output = split(tr(join(status_exec.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
2817       while get(output, 0, '') =~# '^\l\+:'
2818         call remove(output, 0)
2819       endwhile
2820       let head = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
2821       if head =~# '\.\.\.'
2822         let head = split(head, '\.\.\.')[0]
2823         let branch = head
2824       elseif head ==# 'HEAD' || empty(head)
2825         let head = FugitiveHead(11, dir)
2826         let branch = ''
2827       else
2828         let branch = head
2829       endif
2831       let i = 0
2832       while i < len(output)
2833         let line = output[i]
2834         let file = line[3:-1]
2835         let i += 1
2836         if line[2] !=# ' '
2837           continue
2838         endif
2839         if line[0:1] =~# '[RC]'
2840           let relative = [file, output[i]]
2841           let i += 1
2842         else
2843           let relative = [file]
2844         endif
2845         let filename = join(reverse(copy(relative)), ' -> ')
2846         if line[0] !~# '[ ?!#]'
2847           call add(staged, {'type': 'File', 'status': line[0], 'filename': filename, 'relative': relative, 'submodule': ''})
2848         endif
2849         if line[0:1] ==# '??'
2850           call add(untracked, {'type': 'File', 'status': line[1], 'filename': filename, 'relative': relative})
2851         elseif line[1] !~# '[ !#]'
2852           call add(unstaged, {'type': 'File', 'status': line[1], 'filename': file, 'relative': [file], 'submodule': ''})
2853         endif
2854       endwhile
2855     endif
2857     let diff_cmd = cmd + ['-c', 'diff.suppressBlankEmpty=false', '-c', 'core.quotePath=false', 'diff', '--color=never', '--no-ext-diff', '--no-prefix']
2858     let stat.diff = {'Staged': {'stdout': ['']}, 'Unstaged': {'stdout': ['']}}
2859     if len(staged)
2860       let stat.diff['Staged'] = fugitive#Execute(diff_cmd + ['--cached'], function('len'))
2861     endif
2862     if len(unstaged)
2863       let stat.diff['Unstaged'] = fugitive#Execute(diff_cmd + ['--'] + map(copy(unstaged), 'stat.work_tree . "/" . v:val.relative[0]'), function('len'))
2864     endif
2866     let stat.files = {'Staged': {}, 'Unstaged': {}}
2867     for dict in staged
2868       let stat.files['Staged'][dict.filename] = dict
2869     endfor
2870     for dict in unstaged
2871       let stat.files['Unstaged'][dict.filename] = dict
2872     endfor
2874     let fetch_remote = config.Get('branch.' . branch . '.remote', 'origin')
2875     let push_remote = config.Get('branch.' . branch . '.pushRemote',
2876           \ config.Get('remote.pushDefault', fetch_remote))
2877     if fetch_remote !=# '.' && empty(config.Get('remote.' . fetch_remote . '.fetch'))
2878       let fetch_remote = ''
2879     endif
2880     if push_remote !=# '.' && empty(config.Get('remote.' . push_remote . '.push', config.Get('remote.' . push_remote . '.fetch')))
2881       let push_remote = ''
2882     endif
2884     let pull_type = 'Pull'
2885     if empty(fetch_remote) || empty(branch)
2886       let pull_ref = ''
2887     elseif fetch_remote ==# '.'
2888       let pull_ref = config.Get('branch.' . branch . '.merge', '')
2889     else
2890       let pull_ref = substitute(config.Get('branch.' . branch . '.merge', ''), '^refs/heads/', 'refs/remotes/' . fetch_remote . '/', '')
2891     endif
2892     if len(pull_ref)
2893       let rebase = FugitiveConfigGet('branch.' . branch . '.rebase', config)
2894       if empty(rebase)
2895         let rebase = FugitiveConfigGet('pull.rebase', config)
2896       endif
2897       if rebase =~# '^\%(true\|yes\|on\|1\|interactive\|merges\|preserve\)$'
2898         let pull_type = 'Rebase'
2899       elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
2900         let pull_type = 'Merge'
2901       endif
2902     endif
2904     let push_default = FugitiveConfigGet('push.default', config)
2905     if empty(push_default)
2906       let push_default = fugitive#GitVersion(2) ? 'simple' : 'matching'
2907     endif
2908     if push_default ==# 'upstream'
2909       let push_ref = pull_ref
2910     elseif empty(push_remote) || empty(branch)
2911       let push_ref = ''
2912     elseif push_remote ==# '.'
2913       let push_ref = 'refs/heads/' . branch
2914     else
2915       let push_ref = 'refs/remotes/' . push_remote . '/' . branch
2916     endif
2918     let push_short = substitute(push_ref, '^refs/\w\+/', '', '')
2919     let pull_short = substitute(pull_ref, '^refs/\w\+/', '', '')
2921     if isdirectory(fugitive#Find('.git/rebase-merge/', dir))
2922       let rebasing_dir = fugitive#Find('.git/rebase-merge/', dir)
2923     elseif isdirectory(fugitive#Find('.git/rebase-apply/', dir))
2924       let rebasing_dir = fugitive#Find('.git/rebase-apply/', dir)
2925     endif
2927     let rebasing = []
2928     let rebasing_head = 'detached HEAD'
2929     if exists('rebasing_dir') && filereadable(rebasing_dir . 'git-rebase-todo')
2930       let rebasing_head = substitute(readfile(rebasing_dir . 'head-name')[0], '\C^refs/heads/', '', '')
2931       let len = 11
2932       let lines = readfile(rebasing_dir . 'git-rebase-todo')
2933       for line in lines
2934         let hash = matchstr(line, '^[^a-z].*\s\zs[0-9a-f]\{4,\}\ze\.\.')
2935         if len(hash)
2936           let len = len(hash)
2937           break
2938         endif
2939       endfor
2940       if getfsize(rebasing_dir . 'done') > 0
2941         let done = readfile(rebasing_dir . 'done')
2942         call map(done, 'substitute(v:val, ''^\l\+\>'', "done", "")')
2943         let done[-1] = substitute(done[-1], '^\l\+\>', 'stop', '')
2944         let lines = done + lines
2945       endif
2946       call reverse(lines)
2947       for line in lines
2948         let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
2949         if len(match) && match[1] !~# 'exec\|merge\|label'
2950           call add(rebasing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': strpart(match[2], 0, len), 'subject': match[3]})
2951         endif
2952       endfor
2953     endif
2955     let sequencing = []
2956     if filereadable(fugitive#Find('.git/sequencer/todo', dir))
2957       for line in reverse(readfile(fugitive#Find('.git/sequencer/todo', dir)))
2958         let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
2959         if len(match) && match[1] !~# 'exec\|merge\|label'
2960           call add(sequencing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': match[2], 'subject': match[3]})
2961         endif
2962       endfor
2963     elseif filereadable(fugitive#Find('.git/MERGE_MSG', dir))
2964       if filereadable(fugitive#Find('.git/CHERRY_PICK_HEAD', dir))
2965         let pick_head = fugitive#Execute(['rev-parse', '--short', 'CHERRY_PICK_HEAD', '--'], dir).stdout[0]
2966         call add(sequencing, {'type': 'Rebase', 'status': 'pick', 'commit': pick_head, 'subject': get(readfile(fugitive#Find('.git/MERGE_MSG', dir)), 0, '')})
2967       elseif filereadable(fugitive#Find('.git/REVERT_HEAD', dir))
2968         let pick_head = fugitive#Execute(['rev-parse', '--short', 'REVERT_HEAD', '--'], dir).stdout[0]
2969         call add(sequencing, {'type': 'Rebase', 'status': 'revert', 'commit': pick_head, 'subject': get(readfile(fugitive#Find('.git/MERGE_MSG', dir)), 0, '')})
2970       endif
2971     endif
2973     let stat.expanded = {'Staged': {}, 'Unstaged': {}}
2974     let to = {'lines': []}
2975     call s:AddHeader(to, 'Head', head)
2976     call s:AddHeader(to, pull_type, pull_short)
2977     if push_ref !=# pull_ref
2978       call s:AddHeader(to, 'Push', push_short)
2979     endif
2980     if empty(stat.work_tree)
2981       if get(fugitive#ConfigGetAll('core.bare', config), 0, '') !~# '^\%(false\|no|off\|0\|\)$'
2982         call s:AddHeader(to, 'Bare', 'yes')
2983       else
2984         call s:AddHeader(to, 'Error', s:worktree_error)
2985       endif
2986     endif
2987     if get(fugitive#ConfigGetAll('advice.tousHints', config), 0, 'true') !~# '^\%(false\|no|off\|0\|\)$'
2988       call s:AddHeader(to, 'Help', 'g?')
2989     endif
2991     call s:AddSection(to, 'Rebasing ' . rebasing_head, rebasing)
2992     call s:AddSection(to, get(get(sequencing, 0, {}), 'tous', '') ==# 'revert' ? 'Reverting' : 'Cherry Picking', sequencing)
2993     call s:AddSection(to, 'Untracked', untracked)
2994     call s:AddDiffSection(to, stat, 'Unstaged', unstaged)
2995     call s:AddDiffSection(to, stat, 'Staged', staged)
2997     let unique_push_ref = push_ref ==# pull_ref ? '' : push_ref
2998     let unpushed_push = s:QueryLogRange(unique_push_ref, head, dir)
2999     if get(stat.props, 'branch.ab') =~# '^+0 '
3000       let unpushed_pull = {'error': 0, 'overflow': 0, 'entries': []}
3001     else
3002       let unpushed_pull = s:QueryLogRange(pull_ref, head, dir)
3003     endif
3004     " If the push ref is defined but nowhere to be found at the remote,
3005     " pretend it's the same as the pull ref
3006     if unpushed_push.error == 1
3007       let unpushed_push = unpushed_pull
3008     endif
3009     call s:AddLogSection(to, 'Unpushed to ' . push_short, unpushed_push)
3010     call s:AddLogSection(to, 'Unpushed to ' . pull_short, unpushed_pull)
3011     if unpushed_push.error && unpushed_pull.error && empty(rebasing) &&
3012           \ !empty(push_remote . fetch_remote)
3013       call s:AddLogSection(to, 'Unpushed to *', s:QueryLog([head, '--not', '--remotes'], 256, dir))
3014     endif
3015     call s:AddLogSection(to, 'Unpulled from ' . push_short, s:QueryLogRange(head, unique_push_ref, dir))
3016     if len(pull_ref) && get(stat.props, 'branch.ab') !~# ' -0$'
3017       call s:AddLogSection(to, 'Unpulled from ' . pull_short, s:QueryLogRange(head, pull_ref, dir))
3018     endif
3020     let b:fugitive_files = stat.files
3021     let b:fugitive_diff = stat.diff
3022     let b:fugitive_expanded = stat.expanded
3023     let b:fugitive_reltime = stat.reltime
3024     setlocal noreadonly modifiable
3025     if len(to.lines) < line('$')
3026       silent keepjumps execute (len(to.lines)+1) . ',$delete_'
3027     endif
3028     call setline(1, to.lines)
3029     setlocal nomodified readonly nomodifiable
3031     doautocmd <nomodeline> BufReadPost
3032     if &bufhidden ==# ''
3033       setlocal bufhidden=delete
3034     endif
3035     if !exists('b:dispatch')
3036       let b:dispatch = ':Git fetch --all'
3037     endif
3038     setlocal filetype=fugitive
3040     return s:DoAutocmd('User FugitiveIndex')
3041   finally
3042     let b:fugitive_type = 'index'
3043   endtry
3044 endfunction
3046 function! fugitive#FileReadCmd(...) abort
3047   let amatch = a:0 ? a:1 : expand('<amatch>')
3048   let [dir, rev] = s:DirRev(amatch)
3049   let line = a:0 > 1 ? a:2 : line("'[")
3050   if empty(dir)
3051     return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
3052   endif
3053   if rev !~# ':' && s:ChompDefault('', [dir, 'cat-file', '-t', rev]) =~# '^\%(commit\|tag\)$'
3054     let cmd = [dir, 'log', '--pretty=format:%B', '-1', rev, '--']
3055   elseif rev ==# ':'
3056     let cmd = [dir, 'status', '--short']
3057   else
3058     let cmd = [dir, 'cat-file', '-p', rev, '--']
3059   endif
3060   let temp = tempname()
3061   let [err, exec_error] = s:StdoutToFile(temp, cmd)
3062   if exec_error
3063     call delete(temp)
3064     return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
3065   else
3066     return 'silent keepalt ' . line . 'read ' . s:fnameescape(temp) . '|call delete(' . string(temp) . ')'
3067   endif
3068 endfunction
3070 function! fugitive#FileWriteCmd(...) abort
3071   let temp = tempname()
3072   let amatch = a:0 ? a:1 : expand('<amatch>')
3073   let autype = a:0 > 1 ? 'Buf' : 'File'
3074   if exists('#' . autype . 'WritePre')
3075     execute s:DoAutocmd(autype . 'WritePre ' . s:fnameescape(amatch))
3076   endif
3077   try
3078     let [dir, commit, file] = s:DirCommitFile(amatch)
3079     if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
3080       return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
3081     endif
3082     silent execute "noautocmd keepalt '[,']write ".temp
3083     let hash = s:TreeChomp([dir, '--literal-pathspecs', 'hash-object', '-w', '--', FugitiveGitPath(temp)])
3084     let old_mode = matchstr(s:ChompDefault('', ['ls-files', '--stage', '.' . file], dir), '^\d\+')
3085     if empty(old_mode)
3086       let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
3087     endif
3088     let error = s:UpdateIndex(dir, [old_mode, hash, commit, file[1:-1]])
3089     if empty(error)
3090       setlocal nomodified
3091       if exists('#' . autype . 'WritePost')
3092         execute s:DoAutocmd(autype . 'WritePost ' . s:fnameescape(amatch))
3093       endif
3094       exe s:DoAutocmdChanged(dir)
3095       return ''
3096     else
3097       return 'echoerr '.string('fugitive: '.error)
3098     endif
3099   catch /^fugitive:/
3100     return 'echoerr ' . string(v:exception)
3101   finally
3102     call delete(temp)
3103   endtry
3104 endfunction
3106 function! fugitive#BufReadCmd(...) abort
3107   let amatch = a:0 ? a:1 : expand('<amatch>')
3108   let [dir, rev] = s:DirRev(amatch)
3109   if empty(dir)
3110     return 'echo "Invalid Fugitive URL"'
3111   endif
3112   call s:InitializeBuffer(dir)
3113   if rev ==# ':'
3114     return fugitive#BufReadStatus(v:cmdbang)
3115   endif
3116   try
3117     if rev =~# '^:\d$'
3118       let b:fugitive_type = 'stage'
3119     else
3120       let r = fugitive#Execute([dir, 'cat-file', '-t', rev])
3121       let b:fugitive_type = get(r.stdout, 0, '')
3122       if r.exit_status && rev =~# '^:0'
3123         let r = fugitive#Execute([dir, 'write-tree', '--prefix=' . rev[3:-1]])
3124         let sha = get(r.stdout, 0, '')
3125         let b:fugitive_type = 'tree'
3126       endif
3127       if r.exit_status
3128         let error = substitute(join(r.stderr, "\n"), "\n*$", '', '')
3129         unlet b:fugitive_type
3130         setlocal noswapfile
3131         if empty(&bufhidden)
3132           setlocal bufhidden=delete
3133         endif
3134         if rev =~# '^:\d:'
3135           let &l:readonly = !filewritable(fugitive#Find('.git/index', dir))
3136           return 'doautocmd BufNewFile'
3137         else
3138           setlocal readonly nomodifiable
3139           return 'doautocmd BufNewFile|echo ' . string(error)
3140         endif
3141       elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
3142         return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
3143       endif
3144       if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
3145         let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
3146       endif
3147     endif
3149     if b:fugitive_type !=# 'blob'
3150       setlocal nomodeline
3151     endif
3153     setlocal noreadonly modifiable
3154     let pos = getpos('.')
3155     silent keepjumps %delete_
3156     setlocal endofline
3158     let events = ['User FugitiveObject', 'User Fugitive' . substitute(b:fugitive_type, '^\l', '\u&', '')]
3160     try
3161       if b:fugitive_type !=# 'blob'
3162         setlocal foldmarker=<<<<<<<<,>>>>>>>>
3163       endif
3164       exe s:DoAutocmd('BufReadPre')
3165       if b:fugitive_type ==# 'tree'
3166         let b:fugitive_display_format = b:fugitive_display_format % 2
3167         if b:fugitive_display_format
3168           call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
3169         else
3170           if !exists('sha')
3171             let sha = s:TreeChomp(dir, 'rev-parse', '--verify', rev, '--')
3172           endif
3173           call s:ReplaceCmd([dir, 'show', '--no-color', sha])
3174         endif
3175       elseif b:fugitive_type ==# 'tag'
3176         let b:fugitive_display_format = b:fugitive_display_format % 2
3177         if b:fugitive_display_format
3178           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
3179         else
3180           call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
3181         endif
3182       elseif b:fugitive_type ==# 'commit'
3183         let b:fugitive_display_format = b:fugitive_display_format % 2
3184         if b:fugitive_display_format
3185           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
3186         else
3187           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%s%n%n%b', rev])
3188           keepjumps 1
3189           keepjumps call search('^parent ')
3190           if getline('.') ==# 'parent '
3191             silent lockmarks keepjumps delete_
3192           else
3193             silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
3194           endif
3195           keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
3196           if lnum
3197             silent lockmarks keepjumps delete_
3198           end
3199           silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/s/\r$//e'
3200           keepjumps 1
3201         endif
3202       elseif b:fugitive_type ==# 'stage'
3203         call s:ReplaceCmd([dir, 'ls-files', '--stage'])
3204       elseif b:fugitive_type ==# 'blob'
3205         let blob_or_filters = rev =~# ':' && fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
3206         call s:ReplaceCmd([dir, 'cat-file', blob_or_filters, rev])
3207       endif
3208     finally
3209       keepjumps call setpos('.',pos)
3210       setlocal nomodified noswapfile
3211       let modifiable = rev =~# '^:.:' && b:fugitive_type !=# 'tree'
3212       if modifiable
3213         let events = ['User FugitiveStageBlob']
3214       endif
3215       let &l:readonly = !modifiable || !filewritable(fugitive#Find('.git/index', dir))
3216       if empty(&bufhidden)
3217         setlocal bufhidden=delete
3218       endif
3219       let &l:modifiable = modifiable
3220       call fugitive#MapJumps()
3221       if b:fugitive_type !=# 'blob'
3222         call s:Map('n', 'a', ":<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
3223         call s:Map('n', 'i', ":<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
3224         setlocal filetype=git
3225       endif
3226     endtry
3228     setlocal modifiable
3230     return s:DoAutocmd('BufReadPost') .
3231           \ (modifiable ? '' : '|setl nomodifiable') . '|' .
3232           \ call('s:DoAutocmd', events)
3233   catch /^fugitive:/
3234     return 'echoerr ' . string(v:exception)
3235   endtry
3236 endfunction
3238 function! fugitive#BufWriteCmd(...) abort
3239   return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
3240 endfunction
3242 function! fugitive#SourceCmd(...) abort
3243   let amatch = a:0 ? a:1 : expand('<amatch>')
3244   let temp = s:BlobTemp(amatch)
3245   if empty(temp)
3246     return 'noautocmd source ' . s:fnameescape(amatch)
3247   endif
3248   if !exists('g:virtual_scriptnames')
3249     let g:virtual_scriptnames = {}
3250   endif
3251   let g:virtual_scriptnames[temp] = amatch
3252   return 'source ' . s:fnameescape(temp)
3253 endfunction
3255 " Section: Temp files
3257 if !exists('s:temp_files')
3258   let s:temp_files = {}
3259 endif
3261 function! s:TempState(...) abort
3262   return get(s:temp_files, s:cpath(s:AbsoluteVimPath(a:0 ? a:1 : -1)), {})
3263 endfunction
3265 function! fugitive#Result(...) abort
3266   if !a:0 && exists('g:fugitive_event')
3267     return get(g:, 'fugitive_result', {})
3268   elseif !a:0 || type(a:1) == type('') && a:1 =~# '^-\=$'
3269     return get(g:, '_fugitive_last_job', {})
3270   elseif type(a:1) == type(0)
3271     return s:TempState(a:1)
3272   elseif type(a:1) == type('')
3273     return s:TempState(a:1)
3274   elseif type(a:1) == type({}) && has_key(a:1, 'file')
3275     return s:TempState(a:1.file)
3276   else
3277     return {}
3278   endif
3279 endfunction
3281 function! s:TempDotMap() abort
3282   let cfile = s:cfile()
3283   if empty(cfile)
3284     if getline('.') =~# '^[*+] \+\f' && col('.') < 2
3285       return matchstr(getline('.'), '^. \+\zs\f\+')
3286     else
3287       return expand('<cfile>')
3288     endif
3289   endif
3290   let name = fugitive#Find(cfile[0])
3291   let [dir, commit, file] = s:DirCommitFile(name)
3292   if len(commit) && empty(file)
3293     return commit
3294   elseif s:cpath(s:Tree(), getcwd())
3295     return fugitive#Path(name, "./")
3296   else
3297     return fugitive#Real(name)
3298   endif
3299 endfunction
3301 function! s:TempReadPre(file) abort
3302   let key = s:cpath(s:AbsoluteVimPath(a:file))
3303   if has_key(s:temp_files, key)
3304     let dict = s:temp_files[key]
3305     setlocal nomodeline
3306     if empty(&bufhidden)
3307       setlocal bufhidden=delete
3308     endif
3309     setlocal buftype=nowrite
3310     setlocal nomodifiable
3311     call s:InitializeBuffer(dict)
3312     if len(dict.git_dir)
3313       call extend(b:, {'fugitive_type': 'temp'}, 'keep')
3314     endif
3315   endif
3316   return ''
3317 endfunction
3319 function! s:TempReadPost(file) abort
3320   let key = s:cpath(s:AbsoluteVimPath(a:file))
3321   if has_key(s:temp_files, key)
3322     let dict = s:temp_files[key]
3323     if !has_key(dict, 'job')
3324       setlocal nobuflisted
3325     endif
3326     if get(dict, 'filetype', '') ==# 'git'
3327       call fugitive#MapJumps()
3328       call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
3329       call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
3330     endif
3331     if has_key(dict, 'filetype')
3332       if dict.filetype ==# 'man' && has('nvim')
3333         let b:man_sect = matchstr(getline(1), '^\w\+(\zs\d\+\ze)')
3334       endif
3335       if !get(g:, 'did_load_ftplugin') && dict.filetype ==# 'fugitiveblame'
3336         call s:BlameMaps(0)
3337       endif
3338       let &l:filetype = dict.filetype
3339     endif
3340     setlocal foldmarker=<<<<<<<<,>>>>>>>>
3341     if !&modifiable
3342       call s:Map('n', 'gq', ":<C-U>bdelete<CR>", '<silent> <unique>')
3343     endif
3344   endif
3345   return s:DoAutocmd('User FugitivePager')
3346 endfunction
3348 function! s:TempDelete(file) abort
3349   let key = s:cpath(s:AbsoluteVimPath(a:file))
3350   if has_key(s:temp_files, key) && !has_key(s:temp_files[key], 'job') && key !=# s:cpath(get(get(g:, '_fugitive_last_job', {}), 'file', ''))
3351     call delete(a:file)
3352     call remove(s:temp_files, key)
3353   endif
3354   return ''
3355 endfunction
3357 function! s:OriginBufnr(...) abort
3358   let state = s:TempState(a:0 ? a:1 : bufnr(''))
3359   return get(state, 'origin_bufnr', -1)
3360 endfunction
3362 augroup fugitive_temp
3363   autocmd!
3364   autocmd BufReadPre  * exe s:TempReadPre( +expand('<abuf>'))
3365   autocmd BufReadPost * exe s:TempReadPost(+expand('<abuf>'))
3366   autocmd BufWipeout  * exe s:TempDelete(  +expand('<abuf>'))
3367 augroup END
3369 " Section: :Git
3371 function! s:AskPassArgs(dir) abort
3372   if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) &&
3373         \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#ConfigGetAll('core.askpass', a:dir))
3374     if s:executable(s:VimExecPath() . '/git-gui--askpass')
3375       return ['-c', 'core.askPass=' . s:ExecPath()[0] . '/git-gui--askpass']
3376     elseif s:executable('ssh-askpass')
3377       return ['-c', 'core.askPass=ssh-askpass']
3378     endif
3379   endif
3380   return []
3381 endfunction
3383 function! s:RunSave(state) abort
3384   let s:temp_files[s:cpath(a:state.file)] = a:state
3385 endfunction
3387 function! s:RunFinished(state, ...) abort
3388   if has_key(get(g:, '_fugitive_last_job', {}), 'file') && bufnr(g:_fugitive_last_job.file) < 0
3389     exe s:TempDelete(remove(g:, '_fugitive_last_job').file)
3390   endif
3391   let g:_fugitive_last_job = a:state
3392   let first = join(readfile(a:state.file, '', 2), "\n")
3393   if get(a:state, 'filetype', '') ==# 'git' && first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
3394     let a:state.filetype = 'man'
3395   endif
3396   if !has_key(a:state, 'capture_bufnr')
3397     return
3398   endif
3399   call fugitive#DidChange(a:state)
3400 endfunction
3402 function! s:RunEdit(state, tmp, job) abort
3403   if get(a:state, 'request', '') !=# 'edit'
3404     return 0
3405   endif
3406   call remove(a:state, 'request')
3407   let sentinel = a:state.file . '.edit'
3408   let file = FugitiveVimPath(readfile(sentinel, '', 1)[0])
3409   try
3410     if !&equalalways && a:state.mods !~# '\<\d*tab\>' && 3 > (a:state.mods =~# '\<vert' ? winwidth(0) : winheight(0))
3411       let noequalalways = 1
3412       setglobal equalalways
3413     endif
3414     let mods = s:Mods(a:state.mods, 'SpanOrigin')
3415     exe substitute(mods, '\<tab\>', '-tab', 'g') 'keepalt split' s:fnameescape(file)
3416   finally
3417     if exists('l:noequalalways')
3418       setglobal noequalalways
3419     endif
3420   endtry
3421   set bufhidden=wipe
3422   call s:InitializeBuffer(a:state)
3423   let bufnr = bufnr('')
3424   let s:edit_jobs[bufnr] = [a:state, a:tmp, a:job, sentinel]
3425   call fugitive#DidChange(a:state.git_dir)
3426   if bufnr == bufnr('') && !exists('g:fugitive_event')
3427     try
3428       let g:fugitive_event = a:state.git_dir
3429       let g:fugitive_result = a:state
3430       exe s:DoAutocmd('User FugitiveEditor')
3431     finally
3432       unlet! g:fugitive_event g:fugitive_result
3433     endtry
3434   endif
3435   return 1
3436 endfunction
3438 function! s:RunReceive(state, tmp, type, job, data, ...) abort
3439   if a:type ==# 'err' || a:state.pty
3440     let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
3441     let data = a:tmp.escape . data
3442     let escape = "\033]51;[^\007]*"
3443     let a:tmp.escape = matchstr(data, escape . '$')
3444     if len(a:tmp.escape)
3445       let data = strpart(data, 0, len(data) - len(a:tmp.escape))
3446     endif
3447     let cmd = matchstr(data, escape . "\007")[5:-2]
3448     let data = substitute(data, escape . "\007", '', 'g')
3449     if cmd =~# '^fugitive:'
3450       let a:state.request = strpart(cmd, 9)
3451     endif
3452     let lines = split(a:tmp.err . data, "\r\\=\n", 1)
3453     let a:tmp.err = lines[-1]
3454     let lines[-1] = ''
3455     call map(lines, 'substitute(v:val, ".*\r", "", "")')
3456   else
3457     let lines = type(a:data) == type([]) ? a:data : split(a:data, "\n", 1)
3458     if len(a:tmp.out)
3459       let lines[0] = a:tmp.out . lines[0]
3460     endif
3461     let a:tmp.out = lines[-1]
3462     let lines[-1] = ''
3463   endif
3464   call writefile(lines, a:state.file, 'ba')
3465   if has_key(a:tmp, 'echo')
3466     if !exists('l:data')
3467       let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
3468     endif
3469     let a:tmp.echo .= data
3470   endif
3471   let line_count = a:tmp.line_count
3472   let a:tmp.line_count += len(lines) - 1
3473   if !has_key(a:state, 'capture_bufnr') || !bufloaded(a:state.capture_bufnr)
3474     return
3475   endif
3476   call remove(lines, -1)
3477   try
3478     call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
3479     if !line_count && len(lines) > 1000
3480       let first = remove(lines, 0, 999)
3481       call setbufline(a:state.capture_bufnr, 1, first)
3482       redraw
3483       call setbufline(a:state.capture_bufnr, 1001, lines)
3484     else
3485       call setbufline(a:state.capture_bufnr, line_count + 1, lines)
3486     endif
3487     call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
3488     if !a:state.pager && getwinvar(bufwinid(a:state.capture_bufnr), '&previewwindow')
3489       let winnr = bufwinnr(a:state.capture_bufnr)
3490       if winnr > 0
3491         let old_winnr = winnr()
3492         exe 'noautocmd' winnr.'wincmd w'
3493         $
3494         exe 'noautocmd' old_winnr.'wincmd w'
3495       endif
3496     endif
3497   catch
3498   endtry
3499 endfunction
3501 function! s:RunExit(state, tmp, job, exit_status) abort
3502   let a:state.exit_status = a:exit_status
3503   if has_key(a:state, 'job')
3504     return
3505   endif
3506   call s:RunFinished(a:state)
3507 endfunction
3509 function! s:RunClose(state, tmp, job, ...) abort
3510   if a:0
3511     call s:RunExit(a:state, a:tmp, a:job, a:1)
3512   endif
3513   let noeol = substitute(substitute(a:tmp.err, "\r$", '', ''), ".*\r", '', '') . a:tmp.out
3514   call writefile([noeol], a:state.file, 'ba')
3515   call remove(a:state, 'job')
3516   if has_key(a:state, 'capture_bufnr') && bufloaded(a:state.capture_bufnr)
3517     if len(noeol)
3518       call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
3519       call setbufline(a:state.capture_bufnr, a:tmp.line_count + 1, [noeol])
3520       call setbufvar(a:state.capture_bufnr, '&eol', 0)
3521       call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
3522     endif
3523     call setbufvar(a:state.capture_bufnr, '&modified', 0)
3524     call setbufvar(a:state.capture_bufnr, '&buflisted', 0)
3525     if a:state.filetype !=# getbufvar(a:state.capture_bufnr, '&filetype', '')
3526       call setbufvar(a:state.capture_bufnr, '&filetype', a:state.filetype)
3527     endif
3528   endif
3529   if !has_key(a:state, 'exit_status')
3530     return
3531   endif
3532   call s:RunFinished(a:state)
3533 endfunction
3535 function! s:RunSend(job, str) abort
3536   try
3537     if type(a:job) == type(0)
3538       call chansend(a:job, a:str)
3539     else
3540       call ch_sendraw(a:job, a:str)
3541     endif
3542     return len(a:str)
3543   catch /^Vim\%((\a\+)\)\=:E90[06]:/
3544     return 0
3545   endtry
3546 endfunction
3548 function! s:RunCloseIn(job) abort
3549   try
3550     if type(a:job) ==# type(0)
3551       call chanclose(a:job, 'stdin')
3552     else
3553       call ch_close_in(a:job)
3554     endif
3555     return 1
3556   catch /^Vim\%((\a\+)\)\=:E90[06]:/
3557     return 0
3558   endtry
3559 endfunction
3561 function! s:RunEcho(tmp) abort
3562   if !has_key(a:tmp, 'echo')
3563     return
3564   endif
3565   let data = a:tmp.echo
3566   let a:tmp.echo = matchstr(data, "[\r\n]\\+$")
3567   if len(a:tmp.echo)
3568     let data = strpart(data, 0, len(data) - len(a:tmp.echo))
3569   endif
3570   echon substitute(data, "\r\\ze\n", '', 'g')
3571 endfunction
3573 function! s:RunTick(job) abort
3574   if type(a:job) == v:t_number
3575     return jobwait([a:job], 1)[0] == -1
3576   elseif type(a:job) == 8
3577     let running = ch_status(a:job) !~# '^closed$\|^fail$' || job_status(a:job) ==# 'run'
3578     sleep 1m
3579     return running
3580   endif
3581 endfunction
3583 if !exists('s:edit_jobs')
3584   let s:edit_jobs = {}
3585 endif
3586 function! s:RunWait(state, tmp, job, ...) abort
3587   if a:0 && filereadable(a:1)
3588     call delete(a:1)
3589   endif
3590   try
3591     if a:tmp.no_more && &more
3592       let more = &more
3593       let &more = 0
3594     endif
3595     while get(a:state, 'request', '') !=# 'edit' && s:RunTick(a:job)
3596       call s:RunEcho(a:tmp)
3597       if !get(a:tmp, 'closed_in')
3598         let peek = getchar(1)
3599         if peek != 0 && !(has('win32') && peek == 128)
3600           let c = getchar()
3601           let c = type(c) == type(0) ? nr2char(c) : c
3602           if c ==# "\<C-D>" || c ==# "\<Esc>"
3603             let a:tmp.closed_in = 1
3604             let can_pedit = s:RunCloseIn(a:job) && exists('*setbufline')
3605             for winnr in range(1, winnr('$'))
3606               if getwinvar(winnr, '&previewwindow') && getbufvar(winbufnr(winnr), '&modified')
3607                 let can_pedit = 0
3608               endif
3609             endfor
3610             if can_pedit
3611               if has_key(a:tmp, 'echo')
3612                 call remove(a:tmp, 'echo')
3613               endif
3614               call writefile(['fugitive: aborting edit due to background operation.'], a:state.file . '.exit')
3615               exe (&splitbelow ? 'botright' : 'topleft') 'silent pedit ++ff=unix' s:fnameescape(a:state.file)
3616               let a:state.capture_bufnr = bufnr(a:state.file)
3617               call setbufvar(a:state.capture_bufnr, '&modified', 1)
3618               let finished = 0
3619               redraw!
3620               return ''
3621             endif
3622           else
3623             call s:RunSend(a:job, c)
3624             if !a:state.pty
3625               echon c
3626             endif
3627           endif
3628         endif
3629       endif
3630     endwhile
3631     if !has_key(a:state, 'request') && has_key(a:state, 'job') && exists('*job_status') && job_status(a:job) ==# "dead"
3632       throw 'fugitive: close callback did not fire; this should never happen'
3633     endif
3634     call s:RunEcho(a:tmp)
3635     if has_key(a:tmp, 'echo')
3636       let a:tmp.echo = substitute(a:tmp.echo, "^\r\\=\n", '', '')
3637       echo
3638     endif
3639     let finished = !s:RunEdit(a:state, a:tmp, a:job)
3640   finally
3641     if exists('l:more')
3642       let &more = more
3643     endif
3644     if !exists('finished')
3645       try
3646         if a:state.pty && !get(a:tmp, 'closed_in')
3647           call s:RunSend(a:job, "\<C-C>")
3648         elseif type(a:job) == type(0)
3649           call jobstop(a:job)
3650         else
3651           call job_stop(a:job)
3652         endif
3653       catch /.*/
3654       endtry
3655     elseif finished
3656       call fugitive#DidChange(a:state)
3657     endif
3658   endtry
3659   return ''
3660 endfunction
3662 if !exists('s:resume_queue')
3663   let s:resume_queue = []
3664 endif
3665 function! fugitive#Resume() abort
3666   while len(s:resume_queue)
3667     let enqueued = remove(s:resume_queue, 0)
3668     if enqueued[2] isnot# ''
3669       try
3670         call call('s:RunWait', enqueued)
3671       endtry
3672     endif
3673   endwhile
3674 endfunction
3676 function! s:RunBufDelete(bufnr) abort
3677   let state = s:TempState(+a:bufnr)
3678   if has_key(state, 'job')
3679     try
3680       if type(state.job) == type(0)
3681         call jobstop(state.job)
3682       else
3683         call job_stop(state.job)
3684       endif
3685     catch
3686     endtry
3687   endif
3688   if has_key(s:edit_jobs, a:bufnr) |
3689     call add(s:resume_queue, remove(s:edit_jobs, a:bufnr))
3690     call feedkeys("\<C-\>\<C-N>:redraw!|call delete(" . string(s:resume_queue[-1][0].file . '.edit') .
3691           \ ")|call fugitive#Resume()|checktime\r", 'n')
3692   endif
3693 endfunction
3695 augroup fugitive_job
3696   autocmd!
3697   autocmd BufDelete * call s:RunBufDelete(+expand('<abuf>'))
3698   autocmd VimLeave *
3699         \ for s:jobbuf in keys(s:edit_jobs) |
3700         \   call writefile(['Aborting edit due to Vim exit.'], s:edit_jobs[s:jobbuf][0].file . '.exit') |
3701         \   redraw! |
3702         \   call call('s:RunWait', remove(s:edit_jobs, s:jobbuf)) |
3703         \ endfor
3704 augroup END
3706 function! fugitive#CanPty() abort
3707   return get(g:, 'fugitive_pty_debug_override',
3708         \ has('unix') && !has('win32unix') && (has('patch-8.0.0744') || has('nvim')) && fugitive#GitVersion() !~# '\.windows\>')
3709 endfunction
3711 function! fugitive#PagerFor(argv, ...) abort
3712   let args = a:argv
3713   if empty(args)
3714     return 0
3715   elseif (args[0] ==# 'help' || get(args, 1, '') ==# '--help') && !s:HasOpt(args, '--web')
3716     return 1
3717   endif
3718   if args[0] ==# 'config' && (s:HasOpt(args, '-e', '--edit') ||
3719         \   !s:HasOpt(args, '--list', '--get-all', '--get-regexp', '--get-urlmatch')) ||
3720         \ args[0] =~# '^\%(tag\|branch\)$' && (
3721         \    s:HasOpt(args, '--edit-description', '--unset-upstream', '-m', '-M', '--move', '-c', '-C', '--copy', '-d', '-D', '--delete') ||
3722         \   len(filter(args[1:-1], 'v:val =~# "^[^-]\\|^--set-upstream-to="')) &&
3723         \   !s:HasOpt(args, '--contains', '--no-contains', '--merged', '--no-merged', '--points-at'))
3724     return 0
3725   endif
3726   let config = a:0 ? a:1 : fugitive#Config()
3727   let value = get(fugitive#ConfigGetAll('pager.' . args[0], config), 0, -1)
3728   if value =~# '^\%(true\|yes\|on\|1\)$'
3729     return 1
3730   elseif value =~# '^\%(false\|no|off\|0\|\)$'
3731     return 0
3732   elseif type(value) == type('')
3733     return value
3734   elseif args[0] =~# '^\%(branch\|config\|diff\|grep\|log\|range-diff\|shortlog\|show\|tag\|whatchanged\)$' ||
3735         \ (args[0] ==# 'stash' && get(args, 1, '') ==# 'show') ||
3736         \ (args[0] ==# 'reflog' && get(args, 1, '') !~# '^\%(expire\|delete\|exists\)$') ||
3737         \ (args[0] ==# 'am' && s:HasOpt(args, '--show-current-patch'))
3738     return 1
3739   else
3740     return 0
3741   endif
3742 endfunction
3744 let s:disable_colors = []
3745 for s:colortype in ['advice', 'branch', 'diff', 'grep', 'interactive', 'pager', 'push', 'remote', 'showBranch', 'status', 'transport', 'ui']
3746   call extend(s:disable_colors, ['-c', 'color.' . s:colortype . '=false'])
3747 endfor
3748 unlet s:colortype
3749 function! fugitive#Command(line1, line2, range, bang, mods, arg, ...) abort
3750   exe s:VersionCheck()
3751   let dir = call('s:Dir', a:000)
3752   if len(dir)
3753     exe s:DirCheck(dir)
3754   endif
3755   let config = copy(fugitive#Config(dir))
3756   let curwin = a:arg =~# '^++curwin\>' || !a:line2
3757   let [args, after] = s:SplitExpandChain(substitute(a:arg, '^++curwin\>\s*', '', ''), s:Tree(dir))
3758   let flags = []
3759   let pager = -1
3760   let explicit_pathspec_option = 0
3761   while len(args)
3762     if args[0] ==# '-c' && len(args) > 1
3763       call extend(flags, remove(args, 0, 1))
3764     elseif args[0] =~# '^-p$\|^--paginate$'
3765       let pager = 2
3766       call remove(args, 0)
3767     elseif args[0] =~# '^-P$\|^--no-pager$'
3768       let pager = 0
3769       call remove(args, 0)
3770     elseif args[0] =~# '^--\%([[:lower:]-]\+-pathspecs\)$'
3771       let explicit_pathspec_option = 1
3772       call add(flags, remove(args, 0))
3773     elseif args[0] =~# '^\%(--no-optional-locks\)$'
3774       call add(flags, remove(args, 0))
3775     elseif args[0] =~# '^-C$\|^--\%(exec-path=\|git-dir=\|work-tree=\|bare$\)'
3776       return 'echoerr ' . string('fugitive: ' . args[0] . ' is not supported')
3777     else
3778       break
3779     endif
3780   endwhile
3781   if !explicit_pathspec_option
3782     call insert(flags, '--no-literal-pathspecs')
3783   endif
3784   let no_pager = pager is# 0
3785   if no_pager
3786     call add(flags, '--no-pager')
3787   endif
3788   let env = {}
3789   let i = 0
3790   while i < len(flags) - 1
3791     if flags[i] ==# '-c'
3792       let i += 1
3793       let config_name = tolower(matchstr(flags[i], '^[^=]\+'))
3794       if has_key(s:prepare_env, config_name) && flags[i] =~# '=.'
3795         let env[s:prepare_env[config_name]] = matchstr(flags[i], '=\zs.*')
3796       endif
3797       if flags[i] =~# '='
3798         let config[config_name] = [matchstr(flags[i], '=\zs.*')]
3799       else
3800         let config[config_name] = [1]
3801       endif
3802     endif
3803     let i += 1
3804   endwhile
3805   let options = {'git': s:UserCommandList(), 'git_dir': s:GitDir(dir), 'flags': flags, 'curwin': curwin}
3806   if empty(args) && pager is# -1
3807     let cmd = s:StatusCommand(a:line1, a:line2, a:range, curwin ? 0 : a:line2, a:bang, a:mods, '', '', [], options)
3808     return (empty(cmd) ? 'exe' : cmd) . after
3809   endif
3810   let alias = FugitiveConfigGet('alias.' . get(args, 0, ''), config)
3811   if get(args, 1, '') !=# '--help' && alias !~# '^$\|^!\|[\"'']' && !filereadable(s:VimExecPath() . '/git-' . args[0])
3812         \ && !(has('win32') && filereadable(s:VimExecPath() . '/git-' . args[0] . '.exe'))
3813     call remove(args, 0)
3814     call extend(args, split(alias, '\s\+'), 'keep')
3815   endif
3816   let name = substitute(get(args, 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
3817   if pager is# -1 && name =~# '^\a\+$' && exists('*s:' . name . 'Subcommand') && get(args, 1, '') !=# '--help'
3818     try
3819       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))
3820       if type(overrides) == type('')
3821         return 'exe ' . string(overrides) . after
3822       endif
3823       let args = [get(overrides, 'command', args[0])] + get(overrides, 'insert_args', []) + args[1:-1]
3824     catch /^fugitive:/
3825       return 'echoerr ' . string(v:exception)
3826     endtry
3827   else
3828     let overrides = {}
3829   endif
3830   call extend(env, get(overrides, 'env', {}))
3831   call s:PrepareEnv(env, dir)
3832   if pager is# -1
3833     let pager = fugitive#PagerFor(args, config)
3834   endif
3835   let wants_terminal = type(pager) ==# type('') ||
3836         \ (s:HasOpt(args, ['add', 'checkout', 'commit', 'reset', 'restore', 'stage', 'stash'], '-p', '--patch') ||
3837         \ s:HasOpt(args, ['add', 'clean', 'stage'], '-i', '--interactive')) && pager is# 0
3838   if wants_terminal
3839     let mods = substitute(s:Mods(a:mods), '\<tab\>', '-tab', 'g')
3840     let assign = len(dir) ? "|call FugitiveDetect({'git_dir':" . string(options.git_dir) . '})' : ''
3841     let argv = s:UserCommandList(options) + args
3842     let term_opts = len(env) ? {'env': env} : {}
3843     if has('nvim')
3844       call fugitive#Autowrite()
3845       return mods . (curwin ? 'enew' : 'new') . '|call termopen(' . string(argv) . ', ' . string(term_opts) . ')' . assign . '|startinsert' . after
3846     elseif exists('*term_start')
3847       call fugitive#Autowrite()
3848       if curwin
3849         let term_opts.curwin = 1
3850       endif
3851       return mods . 'call term_start(' . string(argv) . ', ' . string(term_opts) . ')' . assign . after
3852     endif
3853   endif
3854   let state = {
3855         \ 'git': options.git,
3856         \ 'flags': flags,
3857         \ 'args': args,
3858         \ 'git_dir': options.git_dir,
3859         \ 'cwd': s:UserCommandCwd(dir),
3860         \ 'filetype': 'git',
3861         \ 'mods': s:Mods(a:mods),
3862         \ 'file': s:Resolve(tempname())}
3863   let allow_pty = 1
3864   let after_edit = ''
3865   let stream = 0
3866   if a:bang && pager isnot# 2
3867     let state.pager = pager
3868     let pager = 1
3869     let stream = exists('*setbufline')
3870     let do_edit = substitute(s:Mods(a:mods, 'Edge'), '\<tab\>', '-tab', 'g') . 'pedit!'
3871   elseif pager
3872     let allow_pty = get(args, 0, '') is# 'shortlog'
3873     if pager is# 2 && a:bang && a:line2 >= 0
3874       let [do_edit, after_edit] = s:ReadPrepare(a:line1, a:line2, a:range, a:mods)
3875     elseif pager is# 2 && a:bang
3876       let do_edit = s:Mods(a:mods, 'SpanOrigin') . 'pedit'
3877     elseif !curwin
3878       let do_edit = s:Mods(a:mods, 'SpanOrigin') . 'split'
3879     else
3880       let do_edit = s:Mods(a:mods) . 'edit'
3881       call s:BlurStatus()
3882     endif
3883     call extend(env, {'COLUMNS': '' . get(g:, 'fugitive_columns', 80)}, 'keep')
3884   endif
3885   if s:run_jobs
3886     call extend(env, {'COLUMNS': '' . (&columns - 1)}, 'keep')
3887     let state.pty = allow_pty && fugitive#CanPty()
3888     if !state.pty
3889       let args = s:AskPassArgs(dir) + args
3890     endif
3891     let tmp = {
3892           \ 'no_more': no_pager || get(overrides, 'no_more'),
3893           \ 'line_count': 0,
3894           \ 'err': '',
3895           \ 'out': '',
3896           \ 'escape': ''}
3897     let env.FUGITIVE = state.file
3898     let editor = 'sh ' . s:TempScript(
3899           \ '[ -f "$FUGITIVE.exit" ] && cat "$FUGITIVE.exit" >&2 && exit 1',
3900           \ 'echo "$1" > "$FUGITIVE.edit"',
3901           \ 'printf "\033]51;fugitive:edit\007" >&2',
3902           \ 'while [ -f "$FUGITIVE.edit" -a ! -f "$FUGITIVE.exit" ]; do sleep 0.05 2>/dev/null || sleep 1; done',
3903           \ 'exit 0')
3904     call extend(env, {
3905           \ 'NO_COLOR': '1',
3906           \ 'GIT_EDITOR': editor,
3907           \ 'GIT_SEQUENCE_EDITOR': editor,
3908           \ 'GIT_PAGER': 'cat',
3909           \ 'PAGER': 'cat'}, 'keep')
3910     if len($GPG_TTY) && !has_key(env, 'GPG_TTY')
3911       let env.GPG_TTY = ''
3912       let did_override_gpg_tty = 1
3913     endif
3914     if stream
3915       call writefile(['fugitive: aborting edit due to background operation.'], state.file . '.exit')
3916     elseif pager
3917       call writefile(['fugitive: aborting edit due to use of pager.'], state.file . '.exit')
3918       let after = '|' . do_edit . ' ' . s:fnameescape(state.file) . after_edit . after
3919     else
3920       let env.GIT_MERGE_AUTOEDIT = '1'
3921       let tmp.echo = ''
3922     endif
3923     let args = s:disable_colors + flags + ['-c', 'advice.waitingForEditor=false'] + args
3924     let argv = s:UserCommandList({'git': options.git, 'git_dir': options.git_dir}) + args
3925     let [argv, jobopts] = s:JobOpts(argv, env)
3926     call fugitive#Autowrite()
3927     call writefile([], state.file, 'b')
3928     call s:RunSave(state)
3929     if has_key(tmp, 'echo')
3930       echo ""
3931     endif
3932     if exists('*ch_close_in')
3933       call extend(jobopts, {
3934             \ 'mode': 'raw',
3935             \ 'out_cb': function('s:RunReceive', [state, tmp, 'out']),
3936             \ 'err_cb': function('s:RunReceive', [state, tmp, 'err']),
3937             \ 'close_cb': function('s:RunClose', [state, tmp]),
3938             \ 'exit_cb': function('s:RunExit', [state, tmp]),
3939             \ })
3940       if state.pty
3941         let jobopts.pty = 1
3942       endif
3943       let job = job_start(argv, jobopts)
3944     else
3945       let job = jobstart(argv, extend(jobopts, {
3946             \ 'pty': state.pty,
3947             \ 'TERM': 'dumb',
3948             \ 'stdout_buffered': pager,
3949             \ 'stderr_buffered': pager,
3950             \ 'on_stdout': function('s:RunReceive', [state, tmp, 'out']),
3951             \ 'on_stderr': function('s:RunReceive', [state, tmp, 'err']),
3952             \ 'on_exit': function('s:RunClose', [state, tmp]),
3953             \ }))
3954     endif
3955     let state.job = job
3956     if pager
3957       let tmp.closed_in = 1
3958       call s:RunCloseIn(job)
3959     endif
3960     if stream
3961       exe 'silent' do_edit '++ff=unix' s:fnameescape(state.file)
3962       let state.capture_bufnr = bufnr(state.file)
3963       call setbufvar(state.capture_bufnr, '&modified', 1)
3964       return (after_edit . after)[1:-1]
3965     endif
3966     call add(s:resume_queue, [state, tmp, job])
3967     return 'call fugitive#Resume()|checktime' . after
3968   elseif pager
3969     let pre = s:BuildEnvPrefix(env)
3970     try
3971       if exists('+guioptions') && &guioptions =~# '!'
3972         let guioptions = &guioptions
3973         set guioptions-=!
3974       endif
3975       silent! execute '!' . escape(pre . s:shellesc(s:UserCommandList(options) + s:disable_colors + flags + ['--no-pager'] + args), '!#%') .
3976             \ (&shell =~# 'csh' ? ' >& ' . s:shellesc(state.file) : ' > ' . s:shellesc(state.file) . ' 2>&1')
3977       let state.exit_status = v:shell_error
3978     finally
3979       if exists('guioptions')
3980         let &guioptions = guioptions
3981       endif
3982     endtry
3983     redraw!
3984     call s:RunSave(state)
3985     call s:RunFinished(state)
3986     return do_edit . ' ' . s:fnameescape(state.file) . after_edit .
3987           \ '|call fugitive#DidChange(fugitive#Result(' . string(state.file) . '))' . after
3988   elseif has('win32')
3989     return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git on Windows')
3990   elseif has('gui_running')
3991     return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git in GVim')
3992   else
3993     if !explicit_pathspec_option && get(options.flags, 0, '') ==# '--no-literal-pathspecs'
3994       call remove(options.flags, 0)
3995     endif
3996     if exists('l:did_override_gpg_tty')
3997       call remove(env, 'GPG_TTY')
3998     endif
3999     let cmd = s:BuildEnvPrefix(env) . s:shellesc(s:UserCommandList(options) + args)
4000     let after = '|call fugitive#DidChange(' . string(dir) . ')' . after
4001     if !wants_terminal && (no_pager || index(['add', 'clean', 'reset', 'restore', 'stage'], get(args, 0, '')) >= 0 || s:HasOpt(args, ['checkout'], '-q', '--quiet', '--no-progress'))
4002       let output = substitute(s:SystemError(cmd)[0], "\n$", '', '')
4003       if len(output)
4004         try
4005           if &more && no_pager
4006             let more = 1
4007             set nomore
4008           endif
4009           echo substitute(output, "\n$", "", "")
4010         finally
4011           if exists('l:more')
4012             set more
4013           endif
4014         endtry
4015       endif
4016       return 'checktime' . after
4017     else
4018       return 'exe ' . string('noautocmd !' . escape(cmd, '!#%')) . after
4019     endif
4020   endif
4021 endfunction
4023 let s:exec_paths = {}
4024 function! s:ExecPath() abort
4025   let git = s:GitShellCmd()
4026   if !has_key(s:exec_paths, git)
4027     let path = get(s:JobExecute(s:GitCmd() + ['--exec-path'], {}, [], [], {}).stdout, 0, '')
4028     let s:exec_paths[git] = [path, FugitiveVimPath(path)]
4029   endif
4030   return s:exec_paths[git]
4031 endfunction
4033 function! s:VimExecPath() abort
4034   return s:ExecPath()[1]
4035 endfunction
4037 let s:subcommands_before_2_5 = [
4038       \ 'add', 'am', 'apply', 'archive', 'bisect', 'blame', 'branch', 'bundle',
4039       \ 'checkout', 'cherry', 'cherry-pick', 'citool', 'clean', 'clone', 'commit', 'config',
4040       \ 'describe', 'diff', 'difftool', 'fetch', 'format-patch', 'fsck',
4041       \ 'gc', 'grep', 'gui', 'help', 'init', 'instaweb', 'log',
4042       \ 'merge', 'mergetool', 'mv', 'notes', 'pull', 'push',
4043       \ 'rebase', 'reflog', 'remote', 'repack', 'replace', 'request-pull', 'reset', 'revert', 'rm',
4044       \ 'send-email', 'shortlog', 'show', 'show-branch', 'stash', 'stage', 'status', 'submodule',
4045       \ 'tag', 'whatchanged',
4046       \ ]
4047 let s:path_subcommands = {}
4048 function! s:CompletableSubcommands(dir) abort
4049   let c_exec_path = s:cpath(s:VimExecPath())
4050   if !has_key(s:path_subcommands, c_exec_path)
4051     if fugitive#GitVersion(2, 18)
4052       let [lines, exec_error] = s:LinesError([a:dir, '--list-cmds=list-mainporcelain,nohelpers,list-complete'])
4053       call filter(lines, 'v:val =~# "^\\S\\+$"')
4054       if !exec_error && len(lines)
4055         let s:path_subcommands[c_exec_path] = lines
4056       else
4057         let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
4058               \ ['maintenance', 'prune', 'range-diff', 'restore', 'sparse-checkout', 'switch', 'worktree']
4059       endif
4060     else
4061       let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
4062             \ (fugitive#GitVersion(2, 5) ? ['worktree'] : [])
4063     endif
4064   endif
4065   let commands = copy(s:path_subcommands[c_exec_path])
4066   for path in split($PATH, has('win32') ? ';' : ':')
4067     if path !~# '^/\|^\a:[\\/]'
4068       continue
4069     endif
4070     let cpath = s:cpath(path)
4071     if !has_key(s:path_subcommands, cpath)
4072       let s:path_subcommands[cpath] = filter(map(s:GlobComplete(path.'/git-', '*', 1),'substitute(v:val,"\\.exe$","","")'), 'v:val !~# "--\\|/"')
4073     endif
4074     call extend(commands, s:path_subcommands[cpath])
4075   endfor
4076   call extend(commands, keys(fugitive#ConfigGetRegexp('^alias\.\zs[^.]\+$', a:dir)))
4077   let configured = split(FugitiveConfigGet('completion.commands', a:dir), '\s\+')
4078   let rejected = {}
4079   for command in configured
4080     if command =~# '^-.'
4081       let rejected[strpart(command, 1)] = 1
4082     endif
4083   endfor
4084   call filter(configured, 'v:val !~# "^-"')
4085   let results = filter(sort(commands + configured), '!has_key(rejected, v:val)')
4086   if exists('*uniq')
4087     return uniq(results)
4088   else
4089     let i = 1
4090     while i < len(results)
4091       if results[i] ==# results[i-1]
4092         call remove(results, i)
4093       else
4094         let i += 1
4095       endif
4096     endwhile
4097     return results
4098   endif
4099 endfunction
4101 function! fugitive#Complete(lead, ...) abort
4102   let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? s:Dir(a:3) : s:Dir()
4103   let root = a:0 >= 4 ? a:4 : s:Tree(s:Dir())
4104   let pre = a:0 > 1 ? strpart(a:1, 0, a:2) : ''
4105   let subcmd = matchstr(pre, '\u\w*[! ] *\%(\%(++\S\+\|--\S\+-pathspecs\|-c\s\+\S\+\)\s\+\)*\zs[[:alnum:]][[:alnum:]-]*\ze ')
4106   if empty(subcmd) && a:lead =~# '^+'
4107     let results = ['++curwin']
4108   elseif empty(subcmd) && a:lead =~# '^-'
4109     let results = ['--literal-pathspecs', '--no-literal-pathspecs', '--glob-pathspecs', '--noglob-pathspecs', '--icase-pathspecs', '--no-optional-locks']
4110   elseif empty(subcmd)
4111     let results = s:CompletableSubcommands(dir)
4112   elseif a:0 ==# 2 && subcmd =~# '^\%(commit\|revert\|push\|fetch\|pull\|merge\|rebase\|bisect\)$'
4113     let cmdline = substitute(a:1, '\u\w*\([! ] *\)' . subcmd, 'G' . subcmd, '')
4114     let caps_subcmd = substitute(subcmd, '\%(^\|-\)\l', '\u&', 'g')
4115     return fugitive#{caps_subcmd}Complete(a:lead, cmdline, a:2 + len(cmdline) - len(a:1), dir, root)
4116   elseif pre =~# ' -- '
4117     return fugitive#CompletePath(a:lead, a:1, a:2, dir, root)
4118   elseif a:lead =~# '^-'
4119     let results = split(s:ChompDefault('', [dir, subcmd, '--git-completion-helper']), ' ')
4120   else
4121     return fugitive#CompleteObject(a:lead, a:1, a:2, dir, root)
4122   endif
4123   return filter(results, 'strpart(v:val, 0, strlen(a:lead)) ==# a:lead')
4124 endfunction
4126 function! fugitive#CompleteForWorkingDir(A, L, P, ...) abort
4127   let path = a:0 ? a:1 : getcwd()
4128   return fugitive#Complete(a:A, a:L, a:P, FugitiveExtractGitDir(path), path)
4129 endfunction
4131 " Section: :Gcd, :Glcd
4133 function! fugitive#CdComplete(A, L, P) abort
4134   return filter(fugitive#CompletePath(a:A), 'v:val =~# "/$"')
4135 endfunction
4137 function! fugitive#Cd(path, ...) abort
4138   exe s:VersionCheck()
4139   let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
4140   if path !~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
4141     let dir = s:Dir()
4142     exe s:DirCheck(dir)
4143     let path = (empty(s:Tree(dir)) ? dir : s:Tree(dir)) . '/' . path
4144   endif
4145   return (a:0 && a:1 ? 'lcd ' : 'cd ') . fnameescape(s:VimSlash(path))
4146 endfunction
4148 " Section: :Gstatus
4150 function! s:StatusCommand(line1, line2, range, count, bang, mods, reg, arg, args, ...) abort
4151   let dir = a:0 ? s:Dir(a:1) : s:Dir()
4152   exe s:DirCheck(dir)
4153   try
4154     let mods = s:Mods(a:mods, 'Edge')
4155     let file = fugitive#Find(':', dir)
4156     let arg = ' +setl\ foldmarker=<<<<<<<<,>>>>>>>>\|let\ w:fugitive_status=FugitiveGitDir() ' .
4157           \ s:fnameescape(file)
4158     for tabnr in [tabpagenr()] + (mods =~# '\<tab\>' ? range(1, tabpagenr('$')) : [])
4159       let bufs = tabpagebuflist(tabnr)
4160       for winnr in range(1, tabpagewinnr(tabnr, '$'))
4161         if s:cpath(file, fnamemodify(bufname(bufs[winnr-1]), ':p'))
4162           if tabnr == tabpagenr() && winnr == winnr()
4163             call s:ReloadStatus()
4164           else
4165             call s:ExpireStatus(dir)
4166             exe tabnr . 'tabnext'
4167             exe winnr . 'wincmd w'
4168           endif
4169           let w:fugitive_status = dir
4170           1
4171           return ''
4172         endif
4173       endfor
4174     endfor
4175     if a:count ==# 0
4176       return mods . 'edit' . (a:bang ? '!' : '') . arg
4177     elseif a:bang
4178       return mods . 'pedit' . arg . '|wincmd P'
4179     else
4180       return mods . 'keepalt split' . arg
4181     endif
4182   catch /^fugitive:/
4183     return 'echoerr ' . string(v:exception)
4184   endtry
4185   return ''
4186 endfunction
4188 function! s:StageJump(offset, section, ...) abort
4189   let line = search('^\%(' . a:section . '\)', 'nw')
4190   if !line && a:0
4191     let line = search('^\%(' . a:1 . '\)', 'nw')
4192   endif
4193   if line
4194     exe line
4195     if a:offset
4196       for i in range(a:offset)
4197         call search(s:file_commit_pattern . '\|^$', 'W')
4198         if empty(getline('.')) && a:0 && getline(line('.') + 1) =~# '^\%(' . a:1 . '\)'
4199           call search(s:file_commit_pattern . '\|^$', 'W')
4200         endif
4201         if empty(getline('.'))
4202           return ''
4203         endif
4204       endfor
4205       call s:StageReveal()
4206     else
4207       call s:StageReveal()
4208       +
4209     endif
4210   endif
4211   return ''
4212 endfunction
4214 function! s:StageSeek(info, fallback) abort
4215   let info = a:info
4216   if empty(info.heading)
4217     return a:fallback
4218   endif
4219   let line = search('^' . escape(info.heading, '^$.*[]~\') . ' (\d\++\=)$', 'wn')
4220   if !line
4221     for section in get({'Staged': ['Unstaged', 'Untracked'], 'Unstaged': ['Untracked', 'Staged'], 'Untracked': ['Unstaged', 'Staged']}, info.section, [])
4222       let line = search('^' . section, 'wn')
4223       if line
4224         return line + (info.index > 0 ? 1 : 0)
4225       endif
4226     endfor
4227     return 1
4228   endif
4229   let i = 0
4230   while len(getline(line))
4231     let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
4232     if len(filename) &&
4233           \ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
4234           \ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
4235           \ filename ==# info.filename)
4236       if info.offset < 0
4237         return line
4238       else
4239         if getline(line+1) !~# '^@'
4240           exe s:StageInline('show', line)
4241         endif
4242         if getline(line+1) !~# '^@'
4243           return line
4244         endif
4245         let type = info.sigil ==# '-' ? '-' : '+'
4246         let offset = -1
4247         while offset < info.offset
4248           let line += 1
4249           if getline(line) =~# '^@'
4250             let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
4251           elseif getline(line) =~# '^[ ' . type . ']'
4252             let offset += 1
4253           elseif getline(line) !~# '^[ @\+-]'
4254             return line - 1
4255           endif
4256         endwhile
4257         return line
4258       endif
4259     endif
4260     let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
4261     if len(commit) && commit ==# info.commit
4262       return line
4263     endif
4264     if i ==# info.index
4265       let backup = line
4266     endif
4267     let i += getline(line) !~# '^[ @\+-]'
4268     let line += 1
4269   endwhile
4270   return exists('backup') ? backup : line - 1
4271 endfunction
4273 function! s:DoAutocmdChanged(dir) abort
4274   let dir = a:dir is# -2 ? '' : FugitiveGitDir(a:dir)
4275   if empty(dir) || !exists('#User#FugitiveChanged') || exists('g:fugitive_event')
4276     return ''
4277   endif
4278   try
4279     let g:fugitive_event = dir
4280     if type(a:dir) == type({}) && has_key(a:dir, 'args') && has_key(a:dir, 'exit_status')
4281       let g:fugitive_result = a:dir
4282     endif
4283     exe s:DoAutocmd('User FugitiveChanged')
4284   finally
4285     unlet! g:fugitive_event g:fugitive_result
4286     " Force statusline reload with the buffer's Git dir
4287     if dir isnot# FugitiveGitDir()
4288       let &l:ro = &l:ro
4289     endif
4290   endtry
4291   return ''
4292 endfunction
4294 function! s:ReloadStatusBuffer() abort
4295   if get(b:, 'fugitive_type', '') !=# 'index'
4296     return ''
4297   endif
4298   let original_lnum = line('.')
4299   let info = s:StageInfo(original_lnum)
4300   exe fugitive#BufReadStatus(0)
4301   call setpos('.', [0, s:StageSeek(info, original_lnum), 1, 0])
4302   return ''
4303 endfunction
4305 function! s:ReloadStatus() abort
4306   call s:ExpireStatus(-1)
4307   call s:ReloadStatusBuffer()
4308   exe s:DoAutocmdChanged(-1)
4309   return ''
4310 endfunction
4312 let s:last_time = reltime()
4313 if !exists('s:last_times')
4314   let s:last_times = {}
4315 endif
4317 function! s:ExpireStatus(bufnr) abort
4318   if a:bufnr is# -2 || a:bufnr is# 0
4319     let s:head_cache = {}
4320     let s:last_time = reltime()
4321     return ''
4322   endif
4323   let head_file = fugitive#Find('.git/HEAD', a:bufnr)
4324   if !empty(head_file)
4325     let s:last_times[s:Tree(a:bufnr) . '/'] = reltime()
4326     if has_key(s:head_cache, head_file)
4327       call remove(s:head_cache, head_file)
4328     endif
4329   endif
4330   return ''
4331 endfunction
4333 function! s:ReloadWinStatus(...) abort
4334   if get(b:, 'fugitive_type', '') !=# 'index' || &modified
4335     return
4336   endif
4337   if !exists('b:fugitive_reltime')
4338     exe call('s:ReloadStatusBuffer', a:000)
4339     return
4340   endif
4341   let t = b:fugitive_reltime
4342   if reltimestr(reltime(s:last_time, t)) =~# '-\|\d\{10\}\.' ||
4343         \ reltimestr(reltime(get(s:last_times, s:Tree() . '/', t), t)) =~# '-\|\d\{10\}\.'
4344     exe call('s:ReloadStatusBuffer', a:000)
4345   endif
4346 endfunction
4348 function! s:ReloadTabStatus() abort
4349   if !exists('g:fugitive_did_change_at')
4350     return
4351   elseif exists('t:fugitive_reloaded_at')
4352     let time_ahead = reltime(g:fugitive_did_change_at, t:fugitive_reloaded_at)
4353     if reltimefloat(time_ahead) >= 0
4354       return
4355     endif
4356   endif
4357   let t:fugitive_reloaded_at = reltime()
4358   let winnr = 1
4359   while winnr <= winnr('$')
4360     if getbufvar(winbufnr(winnr), 'fugitive_type') ==# 'index'
4361       if winnr != winnr()
4362         execute 'noautocmd' winnr.'wincmd w'
4363         let restorewinnr = 1
4364       endif
4365       try
4366         call s:ReloadWinStatus()
4367       finally
4368         if exists('restorewinnr')
4369           unlet restorewinnr
4370           noautocmd wincmd p
4371         endif
4372       endtry
4373     endif
4374     let winnr += 1
4375   endwhile
4376 endfunction
4378 function! fugitive#DidChange(...) abort
4379   call s:ExpireStatus(a:0 ? a:1 : -1)
4380   if a:0 > 1 ? a:2 : (!a:0 || a:1 isnot# 0)
4381     let g:fugitive_did_change_at = reltime()
4382     call s:ReloadTabStatus()
4383   else
4384     call s:ReloadWinStatus()
4385     return ''
4386   endif
4387   exe s:DoAutocmdChanged(a:0 ? a:1 : -1)
4388   return ''
4389 endfunction
4391 function! fugitive#ReloadStatus(...) abort
4392   return call('fugitive#DidChange', a:000)
4393 endfunction
4395 function! fugitive#EfmDir(...) abort
4396   let dir = matchstr(a:0 ? a:1 : &errorformat, '\c,%\\&\%(git\|fugitive\)_\=dir=\zs\%(\\.\|[^,]\)*')
4397   let dir = substitute(dir, '%%', '%', 'g')
4398   let dir = substitute(dir, '\\\ze[\,]', '', 'g')
4399   return dir
4400 endfunction
4402 augroup fugitive_status
4403   autocmd!
4404   autocmd BufWritePost         * call fugitive#DidChange(+expand('<abuf>'), 0)
4405   autocmd User FileChmodPost,FileUnlinkPost call fugitive#DidChange(+expand('<abuf>'), 0)
4406   autocmd ShellCmdPost,ShellFilterPost * nested call fugitive#DidChange(0)
4407   autocmd BufDelete * nested
4408         \ if getbufvar(+expand('<abuf>'), 'buftype') ==# 'terminal' |
4409         \   if !empty(FugitiveGitDir(+expand('<abuf>'))) |
4410         \     call fugitive#DidChange(+expand('<abuf>')) |
4411         \   else |
4412         \     call fugitive#DidChange(0) |
4413         \  endif |
4414         \ endif
4415   autocmd QuickFixCmdPost make,lmake,[cl]file,[cl]getfile nested
4416         \ call fugitive#DidChange(fugitive#EfmDir())
4417   autocmd FocusGained        *
4418         \ if get(g:, 'fugitive_focus_gained', !has('win32')) |
4419         \   call fugitive#DidChange(0) |
4420         \ endif
4421   autocmd BufEnter index,index.lock,fugitive://*//
4422         \ call s:ReloadWinStatus()
4423   autocmd TabEnter *
4424         \ call s:ReloadTabStatus()
4425 augroup END
4427 function! s:StatusSectionFile(heading, filename) abort
4428   return get(get(get(b:, 'fugitive_files', {}), a:heading, {}), a:filename, {})
4429 endfunction
4431 function! s:StageInfo(...) abort
4432   let lnum = a:0 ? a:1 : line('.')
4433   let sigil = matchstr(getline(lnum), '^[ @\+-]')
4434   let offset = -1
4435   if len(sigil)
4436     let [lnum, old_lnum, new_lnum] = s:HunkPosition(lnum)
4437     let offset = sigil ==# '-' ? old_lnum : new_lnum
4438     while getline(lnum) =~# '^[ @\+-]'
4439       let lnum -= 1
4440     endwhile
4441   endif
4442   let slnum = lnum + 1
4443   let heading = ''
4444   let index = 0
4445   while len(getline(slnum - 1)) && empty(heading)
4446     let slnum -= 1
4447     let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
4448     if empty(heading) && getline(slnum) !~# '^[ @\+-]'
4449       let index += 1
4450     endif
4451   endwhile
4452   let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
4453   let file = s:StatusSectionFile(heading, text)
4454   let relative = get(file, 'relative', len(text) ? [text] : [])
4455   return {'section': matchstr(heading, '^\u\l\+'),
4456         \ 'heading': heading,
4457         \ 'sigil': sigil,
4458         \ 'offset': offset,
4459         \ 'filename': text,
4460         \ 'relative': copy(relative),
4461         \ 'paths': map(copy(relative), 's:Tree() . "/" . v:val'),
4462         \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
4463         \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
4464         \ 'submodule': get(file, 'submodule', ''),
4465         \ 'index': index}
4466 endfunction
4468 function! s:Selection(arg1, ...) abort
4469   if a:arg1 ==# 'n'
4470     let arg1 = line('.')
4471     let arg2 = -v:count
4472   elseif a:arg1 ==# 'v'
4473     let arg1 = line("'<")
4474     let arg2 = line("'>")
4475   else
4476     let arg1 = a:arg1
4477     let arg2 = a:0 ? a:1 : 0
4478   endif
4479   let first = arg1
4480   if arg2 < 0
4481     let last = first - arg2 - 1
4482   elseif arg2 > 0
4483     let last = arg2
4484   else
4485     let last = first
4486   endif
4487   while first <= line('$') && getline(first) =~# '^$\|^[A-Z][a-z]'
4488     let first += 1
4489   endwhile
4490   if first > last || &filetype !=# 'fugitive'
4491     return []
4492   endif
4493   let flnum = first
4494   while getline(flnum) =~# '^[ @\+-]'
4495     let flnum -= 1
4496   endwhile
4497   let slnum = flnum + 1
4498   let heading = ''
4499   let index = 0
4500   while empty(heading)
4501     let slnum -= 1
4502     let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
4503     if empty(heading) && getline(slnum) !~# '^[ @\+-]'
4504       let index += 1
4505     endif
4506   endwhile
4507   let results = []
4508   let template = {
4509         \ 'heading': heading,
4510         \ 'section': matchstr(heading, '^\u\l\+'),
4511         \ 'filename': '',
4512         \ 'relative': [],
4513         \ 'paths': [],
4514         \ 'commit': '',
4515         \ 'status': '',
4516         \ 'patch': 0,
4517         \ 'index': index}
4518   let line = getline(flnum)
4519   let lnum = first - (arg1 == flnum ? 0 : 1)
4520   let root = s:Tree() . '/'
4521   while lnum <= last
4522     let heading = matchstr(line, '^\u\l\+\ze.\{-\}\ze (\d\++\=)$')
4523     if len(heading)
4524       let template.heading = heading
4525       let template.section = matchstr(heading, '^\u\l\+')
4526       let template.index = 0
4527     elseif line =~# '^[ @\+-]'
4528       let template.index -= 1
4529       if !results[-1].patch
4530         let results[-1].patch = lnum
4531       endif
4532       let results[-1].lnum = lnum
4533     elseif line =~# '^[A-Z?] '
4534       let text = matchstr(line, '^[A-Z?] \zs.*')
4535       let file = s:StatusSectionFile(template.heading, text)
4536       let relative = get(file, 'relative', len(text) ? [text] : [])
4537       call add(results, extend(deepcopy(template), {
4538             \ 'lnum': lnum,
4539             \ 'filename': text,
4540             \ 'relative': copy(relative),
4541             \ 'paths': map(copy(relative), 'root . v:val'),
4542             \ 'status': matchstr(line, '^[A-Z?]'),
4543             \ }))
4544     elseif line =~# '^\x\x\x\+ '
4545       call add(results, extend({
4546             \ 'lnum': lnum,
4547             \ 'commit': matchstr(line, '^\x\x\x\+'),
4548             \ }, template, 'keep'))
4549     elseif line =~# '^\l\+ \x\x\x\+ '
4550       call add(results, extend({
4551             \ 'lnum': lnum,
4552             \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
4553             \ 'status': matchstr(line, '^\l\+'),
4554             \ }, template, 'keep'))
4555     endif
4556     let lnum += 1
4557     let template.index += 1
4558     let line = getline(lnum)
4559   endwhile
4560   if len(results) && results[0].patch && arg2 == 0
4561     while getline(results[0].patch) =~# '^[ \+-]'
4562       let results[0].patch -= 1
4563     endwhile
4564     while getline(results[0].lnum + 1) =~# '^[ \+-]'
4565       let results[0].lnum += 1
4566     endwhile
4567   endif
4568   return results
4569 endfunction
4571 function! s:StageArgs(visual) abort
4572   let commits = []
4573   let paths = []
4574   for record in s:Selection(a:visual ? 'v' : 'n')
4575     if len(record.commit)
4576       call add(commits, record.commit)
4577     endif
4578     call extend(paths, record.paths)
4579   endfor
4580   if s:cpath(s:Tree(), getcwd())
4581     call map(paths, 'fugitive#Path(v:val, "./")')
4582   endif
4583   return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
4584 endfunction
4586 function! s:Do(action, visual) abort
4587   let line = getline('.')
4588   let reload = 0
4589   if !a:visual && !v:count && line =~# '^[A-Z][a-z]'
4590     let header = matchstr(line, '^\S\+\ze:')
4591     if len(header) && exists('*s:Do' . a:action . header . 'Header')
4592       let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
4593     else
4594       let section = matchstr(line, '^\S\+')
4595       if exists('*s:Do' . a:action . section . 'Heading')
4596         let reload = s:Do{a:action}{section}Heading(line) > 0
4597       endif
4598     endif
4599     return reload ? s:ReloadStatus() : ''
4600   endif
4601   let selection = s:Selection(a:visual ? 'v' : 'n')
4602   if empty(selection)
4603     return ''
4604   endif
4605   call filter(selection, 'v:val.section ==# selection[0].section')
4606   let status = 0
4607   let err = ''
4608   try
4609     for record in selection
4610       if exists('*s:Do' . a:action . record.section)
4611         let status = s:Do{a:action}{record.section}(record)
4612       else
4613         continue
4614       endif
4615       if !status
4616         return ''
4617       endif
4618       let reload = reload || (status > 0)
4619     endfor
4620     if status < 0
4621       execute record.lnum + 1
4622     endif
4623     let success = 1
4624   catch /^fugitive:/
4625     return 'echoerr ' . string(v:exception)
4626   finally
4627     if reload
4628       execute s:ReloadStatus()
4629     endif
4630     if exists('success')
4631       call s:StageReveal()
4632     endif
4633   endtry
4634   return ''
4635 endfunction
4637 function! s:StageReveal() abort
4638   exe 'normal! zv'
4639   let begin = line('.')
4640   if getline(begin) =~# '^@'
4641     let end = begin + 1
4642     while getline(end) =~# '^[ \+-]'
4643       let end += 1
4644     endwhile
4645   elseif getline(begin) =~# '^commit '
4646     let end = begin
4647     while end < line('$') && getline(end + 1) !~# '^commit '
4648       let end += 1
4649     endwhile
4650   elseif getline(begin) =~# s:section_pattern
4651     let end = begin
4652     while len(getline(end + 1))
4653       let end += 1
4654     endwhile
4655   endif
4656   if exists('end')
4657     while line('.') > line('w0') + &scrolloff && end > line('w$')
4658       execute "normal! \<C-E>"
4659     endwhile
4660   endif
4661 endfunction
4663 let s:file_pattern = '^[A-Z?] .\|^diff --'
4664 let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
4665 let s:item_pattern = s:file_commit_pattern . '\|^@@'
4667 function! s:NextHunk(count) abort
4668   if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
4669     exe s:StageInline('show')
4670   endif
4671   for i in range(a:count)
4672     if &filetype ==# 'fugitive'
4673       call search(s:file_pattern . '\|^@', 'W')
4674       if getline('.') =~# s:file_pattern
4675         exe s:StageInline('show')
4676         if getline(line('.') + 1) =~# '^@'
4677           +
4678         endif
4679       endif
4680     else
4681       call search('^@@', 'W')
4682     endif
4683   endfor
4684   call s:StageReveal()
4685   return '.'
4686 endfunction
4688 function! s:PreviousHunk(count) abort
4689   normal! 0
4690   for i in range(a:count)
4691     if &filetype ==# 'fugitive'
4692       if getline('.') =~# '^@' && getline(line('.') - 1) =~# s:file_pattern
4693         -
4694       endif
4695       let lnum = search(s:file_pattern . '\|^@','Wbn')
4696       call s:StageInline('show', lnum)
4697       call search('^? .\|^@','Wb')
4698     else
4699       call search('^@@', 'Wb')
4700     endif
4701   endfor
4702   call s:StageReveal()
4703   return '.'
4704 endfunction
4706 function! s:NextFile(count) abort
4707   for i in range(a:count)
4708     exe s:StageInline('hide')
4709     if !search(s:file_pattern, 'W')
4710       break
4711     endif
4712   endfor
4713   exe s:StageInline('hide')
4714   return '.'
4715 endfunction
4717 function! s:PreviousFile(count) abort
4718   exe s:StageInline('hide')
4719   normal! 0
4720   for i in range(a:count)
4721     if !search(s:file_pattern, 'Wb')
4722       break
4723     endif
4724     exe s:StageInline('hide')
4725   endfor
4726   return '.'
4727 endfunction
4729 function! s:NextItem(count) abort
4730   for i in range(a:count)
4731     if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
4732       call search('^commit ', 'W')
4733     endif
4734   endfor
4735   call s:StageReveal()
4736   return '.'
4737 endfunction
4739 function! s:PreviousItem(count) abort
4740   normal! 0
4741   for i in range(a:count)
4742     if !search(s:item_pattern, 'Wb') && getline('.') !~# s:item_pattern
4743       call search('^commit ', 'Wb')
4744     endif
4745   endfor
4746   call s:StageReveal()
4747   return '.'
4748 endfunction
4750 let s:section_pattern = '^[A-Z][a-z][^:]*$'
4751 let s:section_commit_pattern = s:section_pattern . '\|^commit '
4753 function! s:NextSection(count) abort
4754   let orig = line('.')
4755   if getline('.') !~# '^commit '
4756     -
4757   endif
4758   for i in range(a:count)
4759     if !search(s:section_commit_pattern, 'W')
4760       break
4761     endif
4762   endfor
4763   if getline('.') =~# s:section_commit_pattern
4764     call s:StageReveal()
4765     return getline('.') =~# s:section_pattern ? '+' : ':'
4766   else
4767     return orig
4768   endif
4769 endfunction
4771 function! s:PreviousSection(count) abort
4772   let orig = line('.')
4773   if getline('.') !~# '^commit '
4774     -
4775   endif
4776   normal! 0
4777   for i in range(a:count)
4778     if !search(s:section_commit_pattern . '\|\%^', 'bW')
4779       break
4780     endif
4781   endfor
4782   if getline('.') =~# s:section_commit_pattern || line('.') == 1
4783     call s:StageReveal()
4784     return getline('.') =~# s:section_pattern ? '+' : ':'
4785   else
4786     return orig
4787   endif
4788 endfunction
4790 function! s:NextSectionEnd(count) abort
4791   +
4792   if empty(getline('.'))
4793     +
4794   endif
4795   for i in range(a:count)
4796     if !search(s:section_commit_pattern, 'W')
4797       return '$'
4798     endif
4799   endfor
4800   return search('^.', 'Wb')
4801 endfunction
4803 function! s:PreviousSectionEnd(count) abort
4804   let old = line('.')
4805   for i in range(a:count)
4806     if search(s:section_commit_pattern, 'Wb') <= 1
4807       exe old
4808       if i
4809         break
4810       else
4811         return ''
4812       endif
4813     endif
4814     let old = line('.')
4815   endfor
4816   return search('^.', 'Wb')
4817 endfunction
4819 function! s:PatchSearchExpr(reverse) abort
4820   let line = getline('.')
4821   if col('.') ==# 1 && line =~# '^[+-]'
4822     if line =~# '^[+-]\{3\} '
4823       let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
4824     else
4825       let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
4826     endif
4827     if a:reverse
4828       return '?' . escape(pattern, '/?') . "\<CR>"
4829     else
4830       return '/' . escape(pattern, '/') . "\<CR>"
4831     endif
4832   endif
4833   return a:reverse ? '#' : '*'
4834 endfunction
4836 function! s:StageInlineGetDiff(diff_section, info) abort
4837   let diff = []
4838   if a:info.status ==# 'U'
4839     let diff_header = 'diff --cc ' . s:Quote(a:info.relative[0])
4840   else
4841     let diff_header = 'diff --git ' . s:Quote(a:info.relative[-1]) . ' ' . s:Quote(a:info.relative[0])
4842   endif
4843   let stdout = fugitive#Wait(a:diff_section).stdout
4844   let start = index(stdout, diff_header)
4845   if start == -1
4846     return [[], -1]
4847   endif
4848   let index = start + 1
4849   while get(stdout, index, '@@') !~# '^@@\|^diff '
4850     let index += 1
4851   endwhile
4852   while get(stdout, index, '') =~# '^[@ \+-]'
4853     call add(diff, stdout[index])
4854     let index += 1
4855   endwhile
4856   return [diff, start]
4857 endfunction
4859 function! s:StageInline(mode, ...) abort
4860   if &filetype !=# 'fugitive'
4861     return ''
4862   endif
4863   let lnum1 = a:0 ? a:1 : line('.')
4864   let lnum = lnum1 + 1
4865   if a:0 > 1 && a:2 == 0 && lnum1 == 1
4866     let lnum = line('$') - 1
4867   elseif a:0 > 1 && a:2 == 0
4868     let info = s:StageInfo(lnum - 1)
4869     if empty(info.paths) && len(info.section)
4870       while len(getline(lnum))
4871         let lnum += 1
4872       endwhile
4873     endif
4874   elseif a:0 > 1
4875     let lnum += a:2 - 1
4876   endif
4877   while lnum > lnum1
4878     let lnum -= 1
4879     while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
4880       let lnum -= 1
4881     endwhile
4882     let info = s:StageInfo(lnum)
4883     let diff_section = get(get(b:, 'fugitive_diff', {}), info.section, {})
4884     if empty(diff_section)
4885       continue
4886     endif
4887     if getline(lnum + 1) =~# '^[ @\+-]'
4888       let lnum2 = lnum + 1
4889       while getline(lnum2 + 1) =~# '^[ @\+-]'
4890         let lnum2 += 1
4891       endwhile
4892       if a:mode !=# 'show'
4893         setlocal modifiable noreadonly
4894         exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
4895         call remove(b:fugitive_expanded[info.section], info.filename)
4896         setlocal nomodifiable readonly nomodified
4897       endif
4898       continue
4899     endif
4900     if info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
4901       continue
4902     endif
4903     let [diff, start] = s:StageInlineGetDiff(diff_section, info)
4904     if len(diff)
4905       setlocal modifiable noreadonly
4906       silent call append(lnum, diff)
4907       let b:fugitive_expanded[info.section][info.filename] = [start]
4908       setlocal nomodifiable readonly nomodified
4909       if foldclosed(lnum+1) > 0
4910         silent exe (lnum+1) . ',' . (lnum+len(diff)) . 'foldopen!'
4911       endif
4912     endif
4913   endwhile
4914   return lnum
4915 endfunction
4917 function! s:NextExpandedHunk(count) abort
4918   for i in range(a:count)
4919     call s:StageInline('show', line('.'), 1)
4920     call search(s:file_pattern . '\|^@','W')
4921   endfor
4922   return '.'
4923 endfunction
4925 function! s:StageDiff(diff) abort
4926   let lnum = line('.')
4927   let info = s:StageInfo(lnum)
4928   let prefix = info.offset > 0 ? '+' . info.offset : ''
4929   if info.submodule =~# '^S'
4930     if info.section ==# 'Staged'
4931       return 'Git --paginate diff --no-ext-diff --submodule=log --cached -- ' . info.paths[0]
4932     elseif info.submodule =~# '^SC'
4933       return 'Git --paginate diff --no-ext-diff --submodule=log -- ' . info.paths[0]
4934     else
4935       return 'Git --paginate diff --no-ext-diff --submodule=diff -- ' . info.paths[0]
4936     endif
4937   elseif empty(info.paths) && info.section ==# 'Staged'
4938     return 'Git --paginate diff --no-ext-diff --cached'
4939   elseif empty(info.paths)
4940     return 'Git --paginate diff --no-ext-diff'
4941   elseif len(info.paths) > 1
4942     execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
4943     return a:diff . '! @:'.s:fnameescape(info.paths[1])
4944   elseif info.section ==# 'Staged' && info.sigil ==# '-'
4945     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4946     return a:diff . '! :0:%'
4947   elseif info.section ==# 'Staged'
4948     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4949     return a:diff . '! @:%'
4950   elseif info.sigil ==# '-'
4951     execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4952     return a:diff . '! :(top)%'
4953   else
4954     execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
4955     return a:diff . '!'
4956   endif
4957 endfunction
4959 function! s:StageDiffEdit() abort
4960   let info = s:StageInfo(line('.'))
4961   let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
4962   if info.section ==# 'Staged'
4963     return 'Git --paginate diff --no-ext-diff --cached '.s:fnameescape(arg)
4964   elseif info.status ==# '?'
4965     call s:TreeChomp('add', '--intent-to-add', '--', arg)
4966     return s:ReloadStatus()
4967   else
4968     return 'Git --paginate diff --no-ext-diff '.s:fnameescape(arg)
4969   endif
4970 endfunction
4972 function! s:StageApply(info, reverse, extra) abort
4973   if a:info.status ==# 'R'
4974     throw 'fugitive: patching renamed file not yet supported'
4975   endif
4976   let cmd = ['apply', '-p0', '--recount'] + a:extra
4977   let info = a:info
4978   let start = info.patch
4979   let end = info.lnum
4980   let lines = getline(start, end)
4981   if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
4982     return -1
4983   endif
4984   while getline(end) =~# '^[-+\ ]'
4985     let end += 1
4986     if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . '\ ]'
4987       call add(lines, ' ' . getline(end)[1:-1])
4988     endif
4989   endwhile
4990   while start > 0 && getline(start) !~# '^@'
4991     let start -= 1
4992     if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
4993       call insert(lines, ' ' . getline(start)[1:-1])
4994     elseif getline(start) =~# '^@'
4995       call insert(lines, getline(start))
4996     endif
4997   endwhile
4998   if start == 0
4999     throw 'fugitive: could not find hunk'
5000   elseif getline(start) !~# '^@@ '
5001     throw 'fugitive: cannot apply conflict hunk'
5002   endif
5003   let i = b:fugitive_expanded[info.section][info.filename][0]
5004   let head = []
5005   let diff_lines = fugitive#Wait(b:fugitive_diff[info.section]).stdout
5006   while get(diff_lines, i, '@') !~# '^@'
5007     let line = diff_lines[i]
5008     if line ==# '--- /dev/null'
5009       call add(head, '--- ' . get(diff_lines, i + 1, '')[4:-1])
5010     elseif line !~# '^new file '
5011       call add(head, line)
5012     endif
5013     let i += 1
5014   endwhile
5015   call extend(lines, head, 'keep')
5016   let temp = tempname()
5017   call writefile(lines, temp)
5018   if a:reverse
5019     call add(cmd, '--reverse')
5020   endif
5021   call extend(cmd, ['--', temp])
5022   let output = s:ChompStderr(cmd)
5023   if empty(output)
5024     return 1
5025   endif
5026   call s:throw(output)
5027 endfunction
5029 function! s:StageDelete(lnum1, lnum2, count) abort
5030   let restore = []
5032   let err = ''
5033   let did_conflict_err = 0
5034   let reset_commit = matchstr(getline(a:lnum1), '^Un\w\+ \%(to\| from\) \zs\S\+')
5035   try
5036     for info in s:Selection(a:lnum1, a:lnum2)
5037       if empty(info.paths)
5038         if len(info.commit)
5039           let reset_commit = info.commit . '^'
5040         endif
5041         continue
5042       endif
5043       let sub = get(s:StatusSectionFile(info.section, info.filename), 'submodule', '')
5044       if sub =~# '^S' && info.status ==# 'M'
5045         let undo = 'Git checkout ' . fugitive#RevParse('HEAD', FugitiveExtractGitDir(info.paths[0]))[0:10] . ' --'
5046       elseif sub =~# '^S'
5047         let err .= '|echoerr ' . string('fugitive: will not touch submodule ' . string(info.relative[0]))
5048         break
5049       elseif info.status ==# 'D'
5050         let undo = 'GRemove'
5051       elseif info.paths[0] =~# '/$'
5052         let err .= '|echoerr ' . string('fugitive: will not delete directory ' . string(info.relative[0]))
5053         break
5054       else
5055         let undo = 'Gread ' . s:TreeChomp('hash-object', '-w', '--', info.paths[0])[0:10]
5056       endif
5057       if info.patch
5058         call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
5059       elseif sub =~# '^S'
5060         if info.section ==# 'Staged'
5061           call s:TreeChomp('reset', '--', info.paths[0])
5062         endif
5063         call s:TreeChomp('submodule', 'update', '--', info.paths[0])
5064       elseif info.status ==# '?'
5065         call s:TreeChomp('clean', '-f', '--', info.paths[0])
5066       elseif a:count == 2
5067         if get(s:StatusSectionFile('Staged', info.filename), 'status', '') ==# 'D'
5068           call delete(info.paths[0])
5069         else
5070           call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
5071         endif
5072       elseif a:count == 3
5073         if get(s:StatusSectionFile('Unstaged', info.filename), 'status', '') ==# 'D'
5074           call delete(info.paths[0])
5075         else
5076           call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
5077         endif
5078       elseif info.status =~# '[ADU]' &&
5079             \ get(s:StatusSectionFile(info.section ==# 'Staged' ? 'Unstaged' : 'Staged', info.filename), 'status', '') =~# '[AU]'
5080         if get(g:, 'fugitive_conflict_x', 0)
5081           call s:TreeChomp('checkout', info.section ==# 'Unstaged' ? '--ours' : '--theirs', '--', info.paths[0])
5082         else
5083           if !did_conflict_err
5084             let err .= '|echoerr "Use 2X for --ours or 3X for --theirs"'
5085             let did_conflict_err = 1
5086           endif
5087           continue
5088         endif
5089       elseif info.status ==# 'U'
5090         call delete(info.paths[0])
5091       elseif info.status ==# 'A'
5092         call s:TreeChomp('rm', '-f', '--', info.paths[0])
5093       elseif info.section ==# 'Unstaged'
5094         call s:TreeChomp('checkout', '--', info.paths[0])
5095       else
5096         call s:TreeChomp('checkout', '@', '--', info.paths[0])
5097       endif
5098       if len(undo)
5099         call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|' . undo)
5100       endif
5101     endfor
5102   catch /^fugitive:/
5103     let err .= '|echoerr ' . string(v:exception)
5104   endtry
5105   if empty(restore)
5106     if len(reset_commit) && empty(err)
5107       call feedkeys(':Git reset ' . reset_commit)
5108     endif
5109     return err[1:-1]
5110   endif
5111   exe s:ReloadStatus()
5112   call s:StageReveal()
5113   return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
5114 endfunction
5116 function! s:StageIgnore(lnum1, lnum2, count) abort
5117   let paths = []
5118   for info in s:Selection(a:lnum1, a:lnum2)
5119     call extend(paths, info.relative)
5120   endfor
5121   call map(paths, '"/" . v:val')
5122   if !a:0
5123     let dir = fugitive#Find('.git/info/')
5124     if !isdirectory(dir)
5125       try
5126         call mkdir(dir)
5127       catch
5128       endtry
5129     endif
5130   endif
5131   exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
5132   let last = line('$')
5133   if last == 1 && empty(getline(1))
5134     call setline(last, paths)
5135   else
5136     call append(last, paths)
5137     exe last + 1
5138   endif
5139   return ''
5140 endfunction
5142 function! s:DoToggleHeadHeader(value) abort
5143   exe 'edit' fnameescape(fugitive#Find('.git/'))
5144   call search('\C^index$', 'wc')
5145 endfunction
5147 function! s:DoToggleHelpHeader(value) abort
5148   exe 'help fugitive-map'
5149 endfunction
5151 function! s:DoStagePushHeader(value) abort
5152   let remote = matchstr(a:value, '\zs[^/]\+\ze/')
5153   if empty(remote)
5154     let remote = '.'
5155   endif
5156   let branch = matchstr(a:value, '\%([^/]\+/\)\=\zs\S\+')
5157   call feedkeys(':Git push ' . remote . ' ' . branch)
5158 endfunction
5160 function! s:DoTogglePushHeader(value) abort
5161   return s:DoStagePushHeader(a:value)
5162 endfunction
5164 function! s:DoStageUnpushedHeading(heading) abort
5165   let remote = matchstr(a:heading, 'to \zs[^/]\+\ze/')
5166   if empty(remote)
5167     let remote = '.'
5168   endif
5169   let branch = matchstr(a:heading, 'to \%([^/]\+/\)\=\zs\S\+')
5170   if branch ==# '*'
5171     return
5172   endif
5173   call feedkeys(':Git push ' . remote . ' ' . '@:' . 'refs/heads/' . branch)
5174 endfunction
5176 function! s:DoToggleUnpushedHeading(heading) abort
5177   return s:DoStageUnpushedHeading(a:heading)
5178 endfunction
5180 function! s:DoStageUnpushed(record) abort
5181   let remote = matchstr(a:record.heading, 'to \zs[^/]\+\ze/')
5182   if empty(remote)
5183     let remote = '.'
5184   endif
5185   let branch = matchstr(a:record.heading, 'to \%([^/]\+/\)\=\zs\S\+')
5186   if branch ==# '*'
5187     return
5188   endif
5189   call feedkeys(':Git push ' . remote . ' ' . a:record.commit . ':' . 'refs/heads/' . branch)
5190 endfunction
5192 function! s:DoToggleUnpushed(record) abort
5193   return s:DoStageUnpushed(a:record)
5194 endfunction
5196 function! s:DoUnstageUnpulledHeading(heading) abort
5197   call feedkeys(':Git rebase')
5198 endfunction
5200 function! s:DoToggleUnpulledHeading(heading) abort
5201   call s:DoUnstageUnpulledHeading(a:heading)
5202 endfunction
5204 function! s:DoUnstageUnpulled(record) abort
5205   call feedkeys(':Git rebase ' . a:record.commit)
5206 endfunction
5208 function! s:DoToggleUnpulled(record) abort
5209   call s:DoUnstageUnpulled(a:record)
5210 endfunction
5212 function! s:DoUnstageUnpushed(record) abort
5213   call feedkeys(':Git -c sequence.editor=true rebase --interactive --autosquash ' . a:record.commit . '^')
5214 endfunction
5216 function! s:DoToggleStagedHeading(...) abort
5217   call s:TreeChomp('reset', '-q')
5218   return 1
5219 endfunction
5221 function! s:DoUnstageStagedHeading(heading) abort
5222   return s:DoToggleStagedHeading(a:heading)
5223 endfunction
5225 function! s:DoToggleUnstagedHeading(...) abort
5226   call s:TreeChomp('add', '-u')
5227   return 1
5228 endfunction
5230 function! s:DoStageUnstagedHeading(heading) abort
5231   return s:DoToggleUnstagedHeading(a:heading)
5232 endfunction
5234 function! s:DoToggleUntrackedHeading(...) abort
5235   call s:TreeChomp('add', '.')
5236   return 1
5237 endfunction
5239 function! s:DoStageUntrackedHeading(heading) abort
5240   return s:DoToggleUntrackedHeading(a:heading)
5241 endfunction
5243 function! s:DoToggleStaged(record) abort
5244   if a:record.patch
5245     return s:StageApply(a:record, 1, ['--cached'])
5246   else
5247     call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
5248     return 1
5249   endif
5250 endfunction
5252 function! s:DoUnstageStaged(record) abort
5253   return s:DoToggleStaged(a:record)
5254 endfunction
5256 function! s:DoToggleUnstaged(record) abort
5257   if a:record.patch
5258     return s:StageApply(a:record, 0, ['--cached'])
5259   else
5260     call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
5261     return 1
5262   endif
5263 endfunction
5265 function! s:DoStageUnstaged(record) abort
5266   return s:DoToggleUnstaged(a:record)
5267 endfunction
5269 function! s:DoUnstageUnstaged(record) abort
5270   if a:record.status ==# 'A'
5271     call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
5272     return 1
5273   else
5274     return -1
5275   endif
5276 endfunction
5278 function! s:DoToggleUntracked(record) abort
5279   call s:TreeChomp(['add', '--'] + a:record.paths)
5280   return 1
5281 endfunction
5283 function! s:DoStageUntracked(record) abort
5284   return s:DoToggleUntracked(a:record)
5285 endfunction
5287 function! s:StagePatch(lnum1,lnum2) abort
5288   let add = []
5289   let reset = []
5290   let intend = []
5292   for lnum in range(a:lnum1,a:lnum2)
5293     let info = s:StageInfo(lnum)
5294     if empty(info.paths) && info.section ==# 'Staged'
5295       execute 'tab Git reset --patch'
5296       break
5297     elseif empty(info.paths) && info.section ==# 'Unstaged'
5298       execute 'tab Git add --patch'
5299       break
5300     elseif empty(info.paths) && info.section ==# 'Untracked'
5301       execute 'tab Git add --interactive'
5302       break
5303     elseif empty(info.paths)
5304       continue
5305     endif
5306     execute lnum
5307     if info.section ==# 'Staged'
5308       let reset += info.relative
5309     elseif info.section ==# 'Untracked'
5310       let intend += info.paths
5311     elseif info.status !~# '^D'
5312       let add += info.relative
5313     endif
5314   endfor
5315   try
5316     if !empty(intend)
5317       call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
5318     endif
5319     if !empty(add)
5320       execute "tab Git add --patch -- ".join(map(add,'fnameescape(v:val)'))
5321     endif
5322     if !empty(reset)
5323       execute "tab Git reset --patch -- ".join(map(reset,'fnameescape(v:val)'))
5324     endif
5325   catch /^fugitive:/
5326     return 'echoerr ' . string(v:exception)
5327   endtry
5328   return s:ReloadStatus()
5329 endfunction
5331 " Section: :Git commit, :Git revert
5333 function! s:CommitInteractive(line1, line2, range, bang, mods, options, patch) abort
5334   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)
5335   let status = len(status) ? status . '|' : ''
5336   if a:patch
5337     return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
5338   else
5339     return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
5340   endif
5341 endfunction
5343 function! s:CommitSubcommand(line1, line2, range, bang, mods, options) abort
5344   let argv = copy(a:options.subcommand_args)
5345   let i = 0
5346   while get(argv, i, '--') !=# '--'
5347     if argv[i] =~# '^-[apzsneiovq].'
5348       call insert(argv, argv[i][0:1])
5349       let argv[i+1] = '-' . argv[i+1][2:-1]
5350     else
5351       let i += 1
5352     endif
5353   endwhile
5354   if s:HasOpt(argv, '-i', '--interactive')
5355     return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 0)
5356   elseif s:HasOpt(argv, '-p', '--patch')
5357     return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 1)
5358   else
5359     return {}
5360   endif
5361 endfunction
5363 function! s:RevertSubcommand(line1, line2, range, bang, mods, options) abort
5364   return {'insert_args': ['--edit']}
5365 endfunction
5367 function! fugitive#CommitComplete(A, L, P, ...) abort
5368   let dir = a:0 ? a:1 : s:Dir()
5369   if a:A =~# '^--fixup=\|^--squash='
5370     let commits = s:LinesError([dir, 'log', '--pretty=format:%s', '@{upstream}..'])[0]
5371     let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
5372     if pre =~# "'"
5373       call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
5374       call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
5375       return commits
5376     else
5377       return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
5378     endif
5379   else
5380     return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'), a:000)
5381   endif
5382   return []
5383 endfunction
5385 function! fugitive#RevertComplete(A, L, P, ...) abort
5386   return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5387 endfunction
5389 " Section: :Git merge, :Git rebase, :Git pull
5391 function! fugitive#MergeComplete(A, L, P, ...) abort
5392   return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5393 endfunction
5395 function! fugitive#RebaseComplete(A, L, P, ...) abort
5396   return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5397 endfunction
5399 function! fugitive#PullComplete(A, L, P, ...) abort
5400   return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
5401 endfunction
5403 function! s:MergeSubcommand(line1, line2, range, bang, mods, options) abort
5404   if empty(a:options.subcommand_args) && (
5405         \ filereadable(fugitive#Find('.git/MERGE_MSG', a:options)) ||
5406         \ isdirectory(fugitive#Find('.git/rebase-apply', a:options)) ||
5407         \  !empty(s:TreeChomp([a:options.git_dir, 'diff-files', '--diff-filter=U'])))
5408     return 'echoerr ":Git merge for loading conflicts has been removed in favor of :Git mergetool"'
5409   endif
5410   return {}
5411 endfunction
5413 function! s:RebaseSubcommand(line1, line2, range, bang, mods, options) abort
5414   let args = a:options.subcommand_args
5415   if s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '-i', '--interactive')
5416     return {'env': {'GIT_SEQUENCE_EDITOR': 'true'}, 'insert_args': ['--interactive']}
5417   endif
5418   return {}
5419 endfunction
5421 " Section: :Git bisect
5423 function! s:CompleteBisect(A, L, P, ...) abort
5424   let bisect_subcmd = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
5425   if empty(bisect_subcmd)
5426     let subcmds = ['start', 'bad', 'new', 'good', 'old', 'terms', 'skip', 'next', 'reset', 'replay', 'log', 'run']
5427     return s:FilterEscape(subcmds, a:A)
5428   endif
5429   let dir = a:0 ? a:1 : s:Dir()
5430   return fugitive#CompleteObject(a:A, dir)
5431 endfunction
5433 function! fugitive#BisectComplete(A, L, P, ...) abort
5434   return s:CompleteSub('bisect', a:A, a:L, a:P, function('s:CompleteBisect'), a:000)
5435 endfunction
5437 " Section: :Git difftool, :Git mergetool
5439 function! s:ToolItems(state, from, to, offsets, text, ...) abort
5440   let items = []
5441   for i in range(len(a:state.diff))
5442     let diff = a:state.diff[i]
5443     let path = (i == len(a:state.diff) - 1) ? a:to : a:from
5444     if empty(path)
5445       return []
5446     endif
5447     let item = {
5448           \ 'valid': a:0 ? a:1 : 1,
5449           \ 'filename': diff.filename . s:VimSlash(path),
5450           \ 'lnum': matchstr(get(a:offsets, i), '\d\+'),
5451           \ 'text': a:text}
5452     if len(get(diff, 'module', ''))
5453       let item.module = diff.module . path
5454     endif
5455     call add(items, item)
5456   endfor
5457   let items[-1].context = {'diff': items[0:-2]}
5458   return [items[-1]]
5459 endfunction
5461 function! s:ToolToFrom(str) abort
5462   if a:str =~# ' => '
5463     let str = a:str =~# '{.* => .*}' ? a:str : '{' . a:str . '}'
5464     return [substitute(str, '{.* => \(.*\)}', '\1', ''),
5465           \ substitute(str, '{\(.*\) => .*}', '\1', '')]
5466   else
5467     return [a:str, a:str]
5468   endif
5469 endfunction
5471 function! s:ToolParse(state, line) abort
5472   if type(a:line) !=# type('') || a:state.mode ==# 'hunk' && a:line =~# '^[ +-]'
5473     return []
5474   elseif a:line =~# '^diff '
5475     let a:state.mode = 'diffhead'
5476     let a:state.from = ''
5477     let a:state.to = ''
5478   elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- [^/]'
5479     let a:state.from = a:line[4:-1]
5480     let a:state.to = a:state.from
5481   elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ [^/]'
5482     let a:state.to = a:line[4:-1]
5483     if empty(get(a:state, 'from', ''))
5484       let a:state.from = a:state.to
5485     endif
5486   elseif a:line[0] ==# '@'
5487     let a:state.mode = 'hunk'
5488     if has_key(a:state, 'from')
5489       let offsets = split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' ')
5490       return s:ToolItems(a:state, a:state.from, a:state.to, offsets, matchstr(a:line, ' @@\+ \zs.*'))
5491     endif
5492   elseif a:line =~# '^\* Unmerged path .'
5493     let file = a:line[16:-1]
5494     return s:ToolItems(a:state, file, file, [], '')
5495   elseif a:line =~# '^[A-Z]\d*\t.\|^:.*\t.'
5496     " --raw, --name-status
5497     let [status; files] = split(a:line, "\t")
5498     return s:ToolItems(a:state, files[0], files[-1], [], a:state.name_only ? '' : status)
5499   elseif a:line =~# '^ \S.* |'
5500     " --stat
5501     let [_, to, changes; __] = matchlist(a:line, '^ \(.\{-\}\) \+|\zs \(.*\)$')
5502     let [to, from] = s:ToolToFrom(to)
5503     return s:ToolItems(a:state, from, to, [], changes)
5504   elseif a:line =~# '^ *\([0-9.]\+%\) .'
5505     " --dirstat
5506     let [_, changes, to; __] = matchlist(a:line, '^ *\([0-9.]\+%\) \(.*\)')
5507     return s:ToolItems(a:state, to, to, [], changes)
5508   elseif a:line =~# '^\(\d\+\|-\)\t\(\d\+\|-\)\t.'
5509     " --numstat
5510     let [_, add, remove, to; __] = matchlist(a:line, '^\(\d\+\|-\)\t\(\d\+\|-\)\t\(.*\)')
5511     let [to, from] = s:ToolToFrom(to)
5512     return s:ToolItems(a:state, from, to, [], add ==# '-' ? 'Binary file' : '+' . add . ' -' . remove, add !=# '-')
5513   elseif a:state.mode !=# 'diffhead' && a:state.mode !=# 'hunk' && len(a:line) || a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
5514     return [{'text': a:line}]
5515   endif
5516   return []
5517 endfunction
5519 function! s:ToolStream(line1, line2, range, bang, mods, options, args, state) abort
5520   let i = 0
5521   let argv = copy(a:args)
5522   let prompt = 1
5523   let state = a:state
5524   while i < len(argv)
5525     let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
5526     if len(match) && len(match[2])
5527       call insert(argv, match[1])
5528       let argv[i+1] = '-' . match[2]
5529       continue
5530     endif
5531     let arg = argv[i]
5532     if arg =~# '^-t$\|^--tool=\|^--tool-help$\|^--help$'
5533       return {}
5534     elseif arg =~# '^-y$\|^--no-prompt$'
5535       let prompt = 0
5536       call remove(argv, i)
5537       continue
5538     elseif arg ==# '--prompt'
5539       let prompt = 1
5540       call remove(argv, i)
5541       continue
5542     elseif arg =~# '^--\%(no-\)\=\(symlinks\|trust-exit-code\|gui\)$'
5543       call remove(argv, i)
5544       continue
5545     elseif arg ==# '--'
5546       break
5547     endif
5548     let i += 1
5549   endwhile
5550   call fugitive#Autowrite()
5551   let a:state.mode = 'init'
5552   let a:state.from = ''
5553   let a:state.to = ''
5554   let exec = s:UserCommandList({'git': a:options.git, 'git_dir': a:options.git_dir}) + ['-c', 'diff.context=0']
5555   let exec += a:options.flags + ['--no-pager', 'diff', '--no-ext-diff', '--no-color', '--no-prefix'] + argv
5556   if prompt
5557     let title = ':Git ' . s:fnameescape(a:options.flags + [a:options.subcommand] + a:options.subcommand_args)
5558     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)
5559   else
5560     let filename = ''
5561     let cmd = []
5562     let tabnr = tabpagenr() + 1
5563     for line in s:SystemList(exec)[0]
5564       for item in s:ToolParse(a:state, line)
5565         if len(get(item, 'filename', '')) && item.filename != filename
5566           call add(cmd, 'tabedit ' . s:fnameescape(item.filename))
5567           for i in reverse(range(len(get(item.context, 'diff', []))))
5568             call add(cmd, (i ? 'rightbelow' : 'leftabove') . ' vertical Gdiffsplit! ' . s:fnameescape(item.context.diff[i].filename))
5569           endfor
5570           call add(cmd, 'wincmd =')
5571           let filename = item.filename
5572         endif
5573       endfor
5574     endfor
5575     return join(cmd, '|') . (empty(cmd) ? '' : '|' . tabnr . 'tabnext')
5576   endif
5577 endfunction
5579 function! s:MergetoolSubcommand(line1, line2, range, bang, mods, options) abort
5580   let dir = a:options.git_dir
5581   exe s:DirCheck(dir)
5582   let i = 0
5583   let prompt = 1
5584   let cmd = ['diff', '--diff-filter=U']
5585   let state = {'name_only': 0}
5586   let state.diff = [{'prefix': ':2:', 'module': ':2:'}, {'prefix': ':3:', 'module': ':3:'}, {'prefix': ':(top)'}]
5587   call map(state.diff, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
5588   return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, ['--diff-filter=U'] + a:options.subcommand_args, state)
5589 endfunction
5591 function! s:DifftoolSubcommand(line1, line2, range, bang, mods, options) abort
5592   let dir = s:Dir(a:options)
5593   exe s:DirCheck(dir)
5594   let i = 0
5595   let argv = copy(a:options.subcommand_args)
5596   let commits = []
5597   let cached = 0
5598   let reverse = 1
5599   let prompt = 1
5600   let state = {'name_only': 0}
5601   let merge_base_against = {}
5602   let dash = (index(argv, '--') > i ? ['--'] : [])
5603   while i < len(argv)
5604     let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
5605     if len(match) && len(match[2])
5606       call insert(argv, match[1])
5607       let argv[i+1] = '-' . match[2]
5608       continue
5609     endif
5610     let arg = argv[i]
5611     if arg ==# '--cached'
5612       let cached = 1
5613     elseif arg ==# '-R'
5614       let reverse = 1
5615     elseif arg ==# '--name-only'
5616       let state.name_only = 1
5617       let argv[0] = '--name-status'
5618     elseif arg ==# '--'
5619       break
5620     elseif arg !~# '^-\|^\.\.\=\%(/\|$\)'
5621       let parsed = s:LinesError(['rev-parse', '--revs-only', substitute(arg, ':.*', '', '')] + dash)[0]
5622       call map(parsed, '{"uninteresting": v:val =~# "^\\^", "prefix": substitute(v:val, "^\\^", "", "") . ":"}')
5623       let merge_base_against = {}
5624       if arg =~# '\.\.\.' && len(parsed) > 2
5625         let display = map(split(arg, '\.\.\.', 1), 'empty(v:val) ? "@" : v:val')
5626         if len(display) == 2
5627           let parsed[0].module = display[1] . ':'
5628           let parsed[1].module = display[0] . ':'
5629         endif
5630         let parsed[2].module = arg . ':'
5631         if empty(commits)
5632           let merge_base_against = parsed[0]
5633           let parsed = [parsed[2]]
5634         endif
5635       elseif arg =~# '\.\.' && len(parsed) == 2
5636         let display = map(split(arg, '\.\.', 1), 'empty(v:val) ? "@" : v:val')
5637         if len(display) == 2
5638           let parsed[0].module = display[0] . ':'
5639           let parsed[1].module = display[1] . ':'
5640         endif
5641       elseif len(parsed) == 1
5642         let parsed[0].module = arg . ':'
5643       endif
5644       call extend(commits, parsed)
5645     endif
5646     let i += 1
5647   endwhile
5648   if len(merge_base_against)
5649     call add(commits, merge_base_against)
5650   endif
5651   let commits = filter(copy(commits), 'v:val.uninteresting') + filter(commits, '!v:val.uninteresting')
5652   if cached
5653     if empty(commits)
5654       call add(commits, {'prefix': '@:', 'module': '@:'})
5655     endif
5656     call add(commits, {'prefix': ':0:', 'module': ':0:'})
5657   elseif len(commits) < 2
5658     call add(commits, {'prefix': ':(top)'})
5659     if len(commits) < 2
5660       call insert(commits, {'prefix': ':0:', 'module': ':0:'})
5661     endif
5662   endif
5663   if reverse
5664     let commits = [commits[-1]] + repeat([commits[0]], len(commits) - 1)
5665     call reverse(commits)
5666   endif
5667   if len(commits) > 2
5668     call add(commits, remove(commits, 0))
5669   endif
5670   call map(commits, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
5671   let state.diff = commits
5672   return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, argv, state)
5673 endfunction
5675 " Section: :Ggrep, :Glog
5677 if !exists('g:fugitive_summary_format')
5678   let g:fugitive_summary_format = '%s'
5679 endif
5681 function! fugitive#GrepComplete(A, L, P) abort
5682   return s:CompleteSub('grep', a:A, a:L, a:P)
5683 endfunction
5685 function! fugitive#LogComplete(A, L, P) abort
5686   return s:CompleteSub('log', a:A, a:L, a:P)
5687 endfunction
5689 function! s:GrepParseLine(options, quiet, dir, line) abort
5690   if !a:quiet
5691     echo a:line
5692   endif
5693   let entry = {'valid': 1}
5694   let match = matchlist(a:line, '^\(.\{-\}\):\([1-9]\d*\):\([1-9]\d*:\)\=\(.*\)$')
5695   if a:line =~# '^git: \|^usage: \|^error: \|^fatal: \|^BUG: '
5696     return {'text': a:line}
5697   elseif len(match)
5698     let entry.module = match[1]
5699     let entry.lnum = +match[2]
5700     let entry.col = +match[3]
5701     let entry.text = match[4]
5702   else
5703     let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
5704     if len(entry.module)
5705       let entry.text = 'Binary file'
5706       let entry.valid = 0
5707     endif
5708   endif
5709   if empty(entry.module) && !a:options.line_number
5710     let match = matchlist(a:line, '^\(.\{-\}\):\(.*\)$')
5711     if len(match)
5712       let entry.module = match[1]
5713       let entry.pattern = '\M^' . escape(match[2], '\.^$/') . '$'
5714     endif
5715   endif
5716   if empty(entry.module) && a:options.name_count && a:line =~# ':\d\+$'
5717     let entry.text = matchstr(a:line, '\d\+$')
5718     let entry.module = strpart(a:line, 0, len(a:line) - len(entry.text) - 1)
5719   endif
5720   if empty(entry.module) && a:options.name_only
5721     let entry.module = a:line
5722   endif
5723   if empty(entry.module)
5724     return {'text': a:line}
5725   endif
5726   if entry.module !~# ':'
5727     let entry.filename = s:PathJoin(a:options.prefix, entry.module)
5728   else
5729     let entry.filename = fugitive#Find(matchstr(entry.module, '^[^:]*:') .
5730           \ substitute(matchstr(entry.module, ':\zs.*'), '/\=:', '/', 'g'), a:dir)
5731   endif
5732   return entry
5733 endfunction
5735 let s:grep_combine_flags = '[aiIrhHEGPFnlLzocpWq]\{-\}'
5736 function! s:GrepOptions(args, dir) abort
5737   let options = {'name_only': 0, 'name_count': 0, 'line_number': 0}
5738   let tree = s:Tree(a:dir)
5739   let prefix = empty(tree) ? fugitive#Find(':0:', a:dir) :
5740         \ s:VimSlash(tree . '/')
5741   let options.prefix = prefix
5742   for arg in a:args
5743     if arg ==# '--'
5744       break
5745     endif
5746     if arg =~# '^\%(-' . s:grep_combine_flags . 'c\|--count\)$'
5747       let options.name_count = 1
5748     endif
5749     if arg =~# '^\%(-' . s:grep_combine_flags . 'n\|--line-number\)$'
5750       let options.line_number = 1
5751     elseif arg =~# '^\%(--no-line-number\)$'
5752       let options.line_number = 0
5753     endif
5754     if arg =~# '^\%(-' . s:grep_combine_flags . '[lL]\|--files-with-matches\|--name-only\|--files-without-match\)$'
5755       let options.name_only = 1
5756     endif
5757     if arg ==# '--cached'
5758       let options.prefix = fugitive#Find(':0:', a:dir)
5759     elseif arg ==# '--no-cached'
5760       let options.prefix = prefix
5761     endif
5762   endfor
5763   return options
5764 endfunction
5766 function! s:GrepCfile(result) abort
5767   let options = s:GrepOptions(a:result.args, a:result)
5768   let entry = s:GrepParseLine(options, 1, a:result, getline('.'))
5769   if get(entry, 'col')
5770     return [entry.filename, entry.lnum, "norm!" . entry.col . "|"]
5771   elseif has_key(entry, 'lnum')
5772     return [entry.filename, entry.lnum]
5773   elseif has_key(entry, 'pattern')
5774     return [entry.filename, '', 'silent /' . entry.pattern]
5775   elseif has_key(entry, 'filename')
5776     return [entry.filename]
5777   else
5778     return []
5779   endif
5780 endfunction
5782 function! s:GrepSubcommand(line1, line2, range, bang, mods, options) abort
5783   let args = copy(a:options.subcommand_args)
5784   let handle = -1
5785   let quiet = 0
5786   let i = 0
5787   while i < len(args) && args[i] !=# '--'
5788     let partition = matchstr(args[i], '^-' . s:grep_combine_flags . '\ze[qzO]')
5789     if len(partition) > 1
5790       call insert(args, '-' . strpart(args[i], len(partition)), i+1)
5791       let args[i] = partition
5792     elseif args[i] =~# '^\%(-' . s:grep_combine_flags . '[eABC]\|--max-depth\|--context\|--after-context\|--before-context\|--threads\)$'
5793       let i += 1
5794     elseif args[i] =~# '^\%(-O\|--open-files-in-pager\)$'
5795       let handle = 1
5796       call remove(args, i)
5797       continue
5798     elseif args[i] =~# '^\%(-O\|--open-files-in-pager=\)'
5799       let handle = 0
5800     elseif args[i] =~# '^-[qz].'
5801       let args[i] = '-' . args[i][2:-1]
5802       let quiet = 1
5803     elseif args[i] =~# '^\%(-[qz]\|--quiet\)$'
5804       let quiet = 1
5805       call remove(args, i)
5806       continue
5807     elseif args[i] =~# '^--no-quiet$'
5808       let quiet = 0
5809     elseif args[i] =~# '^\%(--heading\)$'
5810       call remove(args, i)
5811       continue
5812     endif
5813     let i += 1
5814   endwhile
5815   if handle < 0 ? !quiet : !handle
5816     return {}
5817   endif
5818   call fugitive#Autowrite()
5819   let listnr = get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2
5820   if s:HasOpt(args, '--no-line-number')
5821     let lc = []
5822   else
5823     let lc = fugitive#GitVersion(2, 19) ? ['-n', '--column'] : ['-n']
5824   endif
5825   let cmd = ['grep', '--no-color', '--full-name'] + lc
5826   let dir = s:Dir(a:options)
5827   let options = s:GrepOptions(lc + args, dir)
5828   if listnr > 0
5829     exe listnr 'wincmd w'
5830   else
5831     call s:BlurStatus()
5832   endif
5833   let title = (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)
5834   call s:QuickfixCreate(listnr, {'title': title})
5835   let tempfile = tempname()
5836   let state = {
5837         \ 'git': a:options.git,
5838         \ 'flags': a:options.flags,
5839         \ 'args': cmd + args,
5840         \ 'git_dir': s:GitDir(a:options),
5841         \ 'cwd': s:UserCommandCwd(a:options),
5842         \ 'filetype': 'git',
5843         \ 'mods': s:Mods(a:mods),
5844         \ 'file': s:Resolve(tempfile)}
5845   let event = listnr < 0 ? 'grep-fugitive' : 'lgrep-fugitive'
5846   exe s:DoAutocmd('QuickFixCmdPre ' . event)
5847   try
5848     if !quiet && &more
5849       let more = 1
5850       set nomore
5851     endif
5852     if !quiet
5853       echo title
5854     endif
5855     let list = s:SystemList(s:UserCommandList(a:options) + cmd + args)[0]
5856     call writefile(list + [''], tempfile, 'b')
5857     call s:RunSave(state)
5858     call map(list, 's:GrepParseLine(options, ' . quiet . ', dir, v:val)')
5859     call s:QuickfixSet(listnr, list, 'a')
5860     let press_enter_shortfall = &cmdheight - len(list)
5861     if press_enter_shortfall > 0 && !quiet
5862       echo repeat("\n", press_enter_shortfall - 1)
5863     endif
5864   finally
5865     if exists('l:more')
5866       let &more = more
5867     endif
5868   endtry
5869   call s:RunFinished(state)
5870   exe s:DoAutocmd('QuickFixCmdPost ' . event)
5871   if quiet
5872     let bufnr = bufnr('')
5873     exe s:QuickfixOpen(listnr, a:mods)
5874     if bufnr != bufnr('') && !a:bang
5875       wincmd p
5876     endif
5877   end
5878   if !a:bang && !empty(list)
5879     return 'silent ' . (listnr < 0 ? 'c' : 'l').'first'
5880   else
5881     return ''
5882   endif
5883 endfunction
5885 function! fugitive#GrepCommand(line1, line2, range, bang, mods, arg) abort
5886   return fugitive#Command(a:line1, a:line2, a:range, a:bang, a:mods,
5887         \ "grep -O " . a:arg)
5888 endfunction
5890 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}'
5892 function! s:LogFlushQueue(state, dir) abort
5893   let queue = remove(a:state, 'queue')
5894   if a:state.child_found && get(a:state, 'ignore_commit')
5895     call remove(queue, 0)
5896   elseif len(queue) && len(a:state.target) && len(get(a:state, 'parents', []))
5897     let from = substitute(a:state.target, '^/', ':', '')
5898     let offsets = []
5899     let queue[0].context.diff = map(copy(a:state.parents), s:log_diff_context)
5900   endif
5901   if len(queue) && queue[-1] ==# {'text': ''}
5902     call remove(queue, -1)
5903   endif
5904   return queue
5905 endfunction
5907 function! s:LogParse(state, dir, prefix, line) abort
5908   if a:state.mode ==# 'hunk' && a:line =~# '^[-+ ]'
5909     return []
5910   endif
5911   let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
5912   if len(list)
5913     let queue = s:LogFlushQueue(a:state, a:dir)
5914     let a:state.mode = 'commit'
5915     let a:state.base = a:prefix . list[2]
5916     if len(list[1])
5917       let [a:state.base_module; a:state.parents] = split(list[1], ' ')
5918     else
5919       let a:state.base_module = list[2]
5920       let a:state.parents = []
5921     endif
5922     let a:state.message = list[3]
5923     let a:state.from = ''
5924     let a:state.to = ''
5925     let context = {}
5926     let a:state.queue = [{
5927           \ 'valid': 1,
5928           \ 'context': context,
5929           \ 'filename': s:PathJoin(a:state.base, a:state.target),
5930           \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
5931           \ 'text': a:state.message}]
5932     let a:state.child_found = 0
5933     return queue
5934   elseif type(a:line) == type(0)
5935     return s:LogFlushQueue(a:state, a:dir)
5936   elseif a:line =~# '^diff'
5937     let a:state.mode = 'diffhead'
5938     let a:state.from = ''
5939     let a:state.to = ''
5940   elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- \w/'
5941     let a:state.from = a:line[6:-1]
5942     let a:state.to = a:state.from
5943   elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ \w/'
5944     let a:state.to = a:line[6:-1]
5945     if empty(get(a:state, 'from', ''))
5946       let a:state.from = a:state.to
5947     endif
5948   elseif a:line =~# '^@@[^@]*+\d' && len(get(a:state, 'to', '')) && has_key(a:state, 'base')
5949     let a:state.mode = 'hunk'
5950     if empty(a:state.target) || a:state.target ==# '/' . a:state.to
5951       if !a:state.child_found && len(a:state.queue) && a:state.queue[-1] ==# {'text': ''}
5952         call remove(a:state.queue, -1)
5953       endif
5954       let a:state.child_found = 1
5955       let offsets = map(split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' '), '+matchstr(v:val, "\\d\\+")')
5956       let context = {}
5957       if len(a:state.parents)
5958         let from = ":" . a:state.from
5959         let context.diff = map(copy(a:state.parents), s:log_diff_context)
5960       endif
5961       call add(a:state.queue, {
5962             \ 'valid': 1,
5963             \ 'context': context,
5964             \ 'filename': s:VimSlash(a:state.base . '/' . a:state.to),
5965             \ 'module': a:state.base_module . ':' . a:state.to,
5966             \ 'lnum': offsets[-1],
5967             \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
5968     endif
5969   elseif a:state.follow &&
5970         \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
5971     let rename = matchstr(a:line, '^ \%(copy\|rename\) \zs.* => .*\ze (\d\+%)$')
5972     if len(rename)
5973       let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
5974       if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
5975         let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
5976       endif
5977     endif
5978     if !get(a:state, 'ignore_summary')
5979       call add(a:state.queue, {'text': a:line})
5980     endif
5981   elseif a:state.mode ==# 'commit' || a:state.mode ==# 'init'
5982     call add(a:state.queue, {'text': a:line})
5983   endif
5984   return []
5985 endfunction
5987 function! fugitive#LogCommand(line1, count, range, bang, mods, args, type) abort
5988   exe s:VersionCheck()
5989   let dir = s:Dir()
5990   exe s:DirCheck(dir)
5991   let listnr = a:type =~# '^l' ? 0 : -1
5992   let [args, after] = s:SplitExpandChain('log ' . a:args, s:Tree(dir))
5993   call remove(args, 0)
5994   let split = index(args, '--')
5995   if split > 0
5996     let paths = args[split : -1]
5997     let args = args[0 : split - 1]
5998   elseif split == 0
5999     let paths = args
6000     let args = []
6001   else
6002     let paths = []
6003   endif
6004   if a:line1 == 0 && a:count
6005     let path = fugitive#Path(bufname(a:count), '/', dir)
6006     let titlepre = ':0,' . a:count
6007   elseif a:count >= 0
6008     let path = fugitive#Path(@%, '/', dir)
6009     let titlepre = a:count == 0 ? ':0,' . bufnr('') : ':'
6010   else
6011     let titlepre = ':'
6012     let path = ''
6013   endif
6014   let range = ''
6015   let extra_args = []
6016   let extra_paths = []
6017   let state = {'mode': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
6018   if path =~# '^/\.git\%(/\|$\)\|^$'
6019     let path = ''
6020   elseif a:line1 == 0
6021     let range = "0," . (a:count ? a:count : bufnr(''))
6022     let extra_paths = ['.' . path]
6023     if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
6024       let state.follow = 1
6025       if !s:HasOpt(args, '--follow')
6026         call insert(extra_args, '--follow')
6027       endif
6028       if !s:HasOpt(args, '--summary')
6029         call insert(extra_args, '--summary')
6030         let state.ignore_summary = 1
6031       endif
6032     endif
6033     let state.ignore_commit = 1
6034   elseif a:count > 0
6035     if !s:HasOpt(args, '--merges', '--no-merges')
6036       call insert(extra_args, '--no-merges')
6037     endif
6038     call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
6039     let state.ignore_commit = 1
6040   endif
6041   if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
6042     let owner = s:Owner(@%, dir)
6043     if len(owner)
6044       call add(args, owner . (owner =~# '^\x\{40,}' ? '' : '^{}'))
6045     endif
6046   endif
6047   if empty(extra_paths)
6048     let path = ''
6049   endif
6050   if s:HasOpt(args, '-g', '--walk-reflogs')
6051     let format = "%gd %P\t%H %gs"
6052   else
6053     let format = "%h %P\t%H " . g:fugitive_summary_format
6054   endif
6055   let cmd = ['--no-pager']
6056   call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'] +
6057         \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
6058         \ args + extra_args + paths + extra_paths)
6059   let state.target = path
6060   let title = titlepre . (listnr < 0 ? 'Gclog ' : 'Gllog ') . s:fnameescape(args + paths)
6061   return s:QuickfixStream(listnr, 'log', title, s:UserCommandList(dir) + cmd, !a:bang, a:mods, s:function('s:LogParse'), state, dir, s:DirUrlPrefix(dir)) . after
6062 endfunction
6064 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
6066 function! s:UsableWin(nr) abort
6067   return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
6068         \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
6069         \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
6070         \ index(['nofile','help','quickfix', 'terminal'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
6071 endfunction
6073 function! s:ArgSplit(string) abort
6074   let string = a:string
6075   let args = []
6076   while string =~# '\S'
6077     let arg = matchstr(string, '^\s*\%(\\.\|\S\)\+')
6078     let string = strpart(string, len(arg))
6079     let arg = substitute(arg, '^\s\+', '', '')
6080     call add(args, substitute(arg, '\\\+[|" ]', '\=submatch(0)[len(submatch(0))/2 : -1]', 'g'))
6081   endwhile
6082   return args
6083 endfunction
6085 function! s:PlusEscape(string) abort
6086   return substitute(a:string, '\\*[|" ]', '\=repeat("\\", len(submatch(0))).submatch(0)', 'g')
6087 endfunction
6089 function! s:OpenParse(string, wants_cmd, wants_multiple) abort
6090   let opts = []
6091   let cmds = []
6092   let args = s:ArgSplit(a:string)
6093   while !empty(args)
6094     if args[0] =~# '^++'
6095       call add(opts, ' ' . s:PlusEscape(remove(args, 0)))
6096     elseif a:wants_cmd && args[0] ==# '+'
6097       call remove(args, 0)
6098       call add(cmds, '$')
6099     elseif a:wants_cmd && args[0] =~# '^+'
6100       call add(cmds, remove(args, 0)[1:-1])
6101     else
6102       break
6103     endif
6104   endwhile
6105   if !a:wants_multiple && empty(args)
6106     let args = ['>:']
6107   endif
6108   let dir = s:Dir()
6109   let wants_cmd = a:wants_cmd
6110   let urls = []
6111   for arg in args
6112     let [url, lnum] = s:OpenExpand(dir, arg, wants_cmd)
6113     if lnum
6114       call insert(cmds, lnum)
6115     endif
6116     call add(urls, url)
6117     let wants_cmd = 0
6118   endfor
6120   let pre = join(opts, '')
6121   if len(cmds) > 1
6122     let pre .= ' +' . s:PlusEscape(join(map(cmds, '"exe ".string(v:val)'), '|'))
6123   elseif len(cmds)
6124     let pre .= ' +' . s:PlusEscape(cmds[0])
6125   endif
6126   return [a:wants_multiple ? urls : urls[0], pre]
6127 endfunction
6129 function! s:OpenExpand(dir, file, wants_cmd) abort
6130   if a:file ==# '-'
6131     let result = fugitive#Result()
6132     if has_key(result, 'file')
6133       let efile = result.file
6134     else
6135       throw 'fugitive: no previous command output'
6136     endif
6137   else
6138     let efile = s:Expand(a:file)
6139   endif
6140   if efile =~# '^https\=://'
6141     let [url, lnum] = s:ResolveUrl(efile, a:dir)
6142     return [url, a:wants_cmd ? lnum : 0]
6143   endif
6144   let url = s:Generate(efile, a:dir)
6145   if a:wants_cmd && a:file[0] ==# '>' && efile[0] !=# '>' && get(b:, 'fugitive_type', '') isnot# 'tree' && &filetype !=# 'netrw'
6146     let line = line('.')
6147     if s:Slash(expand('%:p')) !=# s:Slash(url)
6148       let diffcmd = 'diff'
6149       let from = s:DirRev(@%)[1]
6150       let to = s:DirRev(url)[1]
6151       if empty(from) && empty(to)
6152         let diffcmd = 'diff-files'
6153         let args = ['--', expand('%:p'), url]
6154       elseif empty(to)
6155         let args = [from, '--', url]
6156       elseif empty(from)
6157         let args = [to, '--', expand('%:p')]
6158         let reverse = 1
6159       else
6160         let args = [from, to]
6161       endif
6162       let [res, exec_error] = s:LinesError([a:dir, diffcmd, '-U0'] + args)
6163       if !exec_error
6164         call filter(res, 'v:val =~# "^@@ "')
6165         call map(res, 'substitute(v:val, ''[-+]\d\+\zs '', ",1 ", "g")')
6166         call map(res, 'matchlist(v:val, ''^@@ -\(\d\+\),\(\d\+\) +\(\d\+\),\(\d\+\) @@'')[1:4]')
6167         if exists('reverse')
6168           call map(res, 'v:val[2:3] + v:val[0:1]')
6169         endif
6170         call filter(res, 'v:val[0] < '.line('.'))
6171         let hunk = get(res, -1, [0,0,0,0])
6172         if hunk[0] + hunk[1] > line('.')
6173           let line = hunk[2] + max([1 - hunk[3], 0])
6174         else
6175           let line = hunk[2] + max([hunk[3], 1]) + line('.') - hunk[0] - max([hunk[1], 1])
6176         endif
6177       endif
6178     endif
6179     return [url, line]
6180   endif
6181   return [url, 0]
6182 endfunction
6184 function! fugitive#DiffClose() abort
6185   let mywinnr = winnr()
6186   for winnr in [winnr('#')] + range(winnr('$'),1,-1)
6187     if winnr != mywinnr && getwinvar(winnr,'&diff')
6188       execute winnr.'wincmd w'
6189       close
6190       if winnr('$') > 1
6191         wincmd p
6192       endif
6193     endif
6194   endfor
6195   diffoff!
6196 endfunction
6198 function! s:BlurStatus() abort
6199   if (&previewwindow || exists('w:fugitive_status')) && get(b:,'fugitive_type', '') ==# 'index'
6200     let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
6201     if len(winnrs)
6202       exe winnrs[0].'wincmd w'
6203     else
6204       belowright new +setl\ bufhidden=delete
6205     endif
6206     if &diff
6207       call fugitive#DiffClose()
6208     endif
6209   endif
6210 endfunction
6212 let s:bang_edits = {'split': 'Git', 'vsplit': 'vertical Git', 'tabedit': 'tab Git', 'pedit': 'Git!'}
6213 function! fugitive#Open(cmd, bang, mods, arg, ...) abort
6214   exe s:VersionCheck()
6215   if a:bang
6216     return 'echoerr ' . string(':G' . a:cmd . '! for temp buffer output has been replaced by :' . get(s:bang_edits, a:cmd, 'Git') . ' --paginate')
6217   endif
6219   try
6220     let [file, pre] = s:OpenParse(a:arg, 1, 0)
6221   catch /^fugitive:/
6222     return 'echoerr ' . string(v:exception)
6223   endtry
6224   let mods = s:Mods(a:mods)
6225   if a:cmd ==# 'edit'
6226     call s:BlurStatus()
6227   endif
6228   return mods . a:cmd . pre . ' ' . s:fnameescape(file)
6229 endfunction
6231 function! fugitive#DropCommand(line1, count, range, bang, mods, arg, ...) abort
6232   exe s:VersionCheck()
6234   let mods = s:Mods(a:mods)
6235   try
6236     let [files, pre] = s:OpenParse(a:arg, 1, 1)
6237   catch /^fugitive:/
6238     return 'echoerr ' . string(v:exception)
6239   endtry
6240   if empty(files)
6241     return 'drop'
6242   endif
6243   call s:BlurStatus()
6244   return mods . 'drop' . ' ' . s:fnameescape(files) . substitute(pre, '^ *+', '|', '')
6245 endfunction
6247 function! s:ReadPrepare(line1, count, range, mods) abort
6248   let mods = s:Mods(a:mods)
6249   let after = a:count
6250   if a:count < 0
6251     let delete = 'silent 1,' . line('$') . 'delete_|'
6252     let after = line('$')
6253   elseif a:range == 2
6254     let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
6255   else
6256     let delete = ''
6257   endif
6258   if foldlevel(after)
6259     let pre = after . 'foldopen!|'
6260   else
6261     let pre = ''
6262   endif
6263   return [pre . 'keepalt ' . mods . after . 'read', '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')]
6264 endfunction
6266 function! fugitive#ReadCommand(line1, count, range, bang, mods, arg, ...) abort
6267   exe s:VersionCheck()
6268   let [read, post] = s:ReadPrepare(a:line1, a:count, a:range, a:mods)
6269   try
6270     let [file, pre] = s:OpenParse(a:arg, 0, 0)
6271   catch /^fugitive:/
6272     return 'echoerr ' . string(v:exception)
6273   endtry
6274   if file =~# '^fugitive:' && a:count is# 0
6275     return 'exe ' .string('keepalt ' . s:Mods(a:mods) . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
6276   endif
6277   return read . ' ' . pre . ' ' . s:fnameescape(file) . post
6278 endfunction
6280 function! fugitive#EditComplete(A, L, P) abort
6281   if a:A =~# '^>'
6282     return map(s:FilterEscape(s:CompleteHeads(s:Dir()), a:A[1:-1]), "'>' . v:val")
6283   else
6284     return fugitive#CompleteObject(a:A, a:L, a:P)
6285   endif
6286 endfunction
6288 function! fugitive#ReadComplete(A, L, P) abort
6289   return fugitive#EditComplete(a:A, a:L, a:P)
6290 endfunction
6292 " Section: :Gwrite, :Gwq
6294 function! fugitive#WriteCommand(line1, line2, range, bang, mods, arg, ...) abort
6295   exe s:VersionCheck()
6296   if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG')) && empty(a:arg)
6297     return (empty($GIT_INDEX_FILE) ? 'write|bdelete' : 'wq') . (a:bang ? '!' : '')
6298   elseif get(b:, 'fugitive_type', '') ==# 'index' && empty(a:arg)
6299     return 'Git commit'
6300   elseif &buftype ==# 'nowrite' && getline(4) =~# '^[+-]\{3\} '
6301     return 'echoerr ' . string('fugitive: :Gwrite from :Git diff has been removed in favor of :Git add --edit')
6302   endif
6303   let mytab = tabpagenr()
6304   let mybufnr = bufnr('')
6305   let args = s:ArgSplit(a:arg)
6306   let after = ''
6307   if get(args, 0) =~# '^+'
6308     let after = '|' . remove(args, 0)[1:-1]
6309   endif
6310   try
6311     let file = len(args) ? s:Generate(s:Expand(join(args, ' '))) : fugitive#Real(@%)
6312   catch /^fugitive:/
6313     return 'echoerr ' . string(v:exception)
6314   endtry
6315   if empty(file)
6316     return 'echoerr '.string('fugitive: cannot determine file path')
6317   endif
6318   if file =~# '^fugitive:'
6319     return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
6320   endif
6321   exe s:DirCheck()
6322   let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
6323   if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
6324     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
6325     return 'echoerr v:errmsg'
6326   endif
6327   let treebufnr = 0
6328   for nr in range(1,bufnr('$'))
6329     if fnamemodify(bufname(nr),':p') ==# file
6330       let treebufnr = nr
6331     endif
6332   endfor
6334   if treebufnr > 0 && treebufnr != bufnr('')
6335     let temp = tempname()
6336     silent execute 'keepalt %write '.temp
6337     for tab in [mytab] + range(1,tabpagenr('$'))
6338       for winnr in range(1,tabpagewinnr(tab,'$'))
6339         if tabpagebuflist(tab)[winnr-1] == treebufnr
6340           execute 'tabnext '.tab
6341           if winnr != winnr()
6342             execute winnr.'wincmd w'
6343             let restorewinnr = 1
6344           endif
6345           try
6346             let lnum = line('.')
6347             let last = line('$')
6348             silent execute '$read '.temp
6349             silent execute '1,'.last.'delete_'
6350             silent write!
6351             silent execute lnum
6352             diffupdate
6353             let did = 1
6354           finally
6355             if exists('restorewinnr')
6356               wincmd p
6357             endif
6358             execute 'tabnext '.mytab
6359           endtry
6360           break
6361         endif
6362       endfor
6363     endfor
6364     if !exists('did')
6365       call writefile(readfile(temp,'b'),file,'b')
6366     endif
6367   else
6368     execute 'write! '.s:fnameescape(file)
6369   endif
6371   let message = s:ChompStderr(['add'] + (a:bang ? ['--force'] : []) + ['--', file])
6372   if len(message)
6373     let v:errmsg = 'fugitive: '.message
6374     return 'echoerr v:errmsg'
6375   endif
6376   if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
6377     setlocal nomodified
6378   endif
6380   let one = fugitive#Find(':1:'.file)
6381   let two = fugitive#Find(':2:'.file)
6382   let three = fugitive#Find(':3:'.file)
6383   for nr in range(1,bufnr('$'))
6384     let name = fnamemodify(bufname(nr), ':p')
6385     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
6386       execute nr.'bdelete'
6387     endif
6388   endfor
6390   unlet! restorewinnr
6391   let zero = fugitive#Find(':0:'.file)
6392   exe s:DoAutocmd('BufWritePost ' . s:fnameescape(zero))
6393   for tab in range(1,tabpagenr('$'))
6394     for winnr in range(1,tabpagewinnr(tab,'$'))
6395       let bufnr = tabpagebuflist(tab)[winnr-1]
6396       let bufname = fnamemodify(bufname(bufnr), ':p')
6397       if bufname ==# zero && bufnr != mybufnr
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 '.s:fnameescape(file)
6407           silent execute '1,'.last.'delete_'
6408           silent execute lnum
6409           setlocal nomodified
6410           diffupdate
6411         finally
6412           if exists('restorewinnr')
6413             wincmd p
6414           endif
6415           execute 'tabnext '.mytab
6416         endtry
6417         break
6418       endif
6419     endfor
6420   endfor
6421   call fugitive#DidChange()
6422   return 'checktime' . after
6423 endfunction
6425 function! fugitive#WqCommand(...) abort
6426   let bang = a:4 ? '!' : ''
6427   if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG'))
6428     return 'wq'.bang
6429   endif
6430   let result = call('fugitive#WriteCommand', a:000)
6431   if result =~# '^\%(write\|wq\|echoerr\)'
6432     return s:sub(result,'^write','wq')
6433   else
6434     return result.'|quit'.bang
6435   endif
6436 endfunction
6438 " Section: :Git push, :Git fetch
6440 function! s:CompletePush(A, L, P, ...) abort
6441   let dir = a:0 ? a:1 : s:Dir()
6442   let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
6443   if empty(remote)
6444     let matches = s:LinesError([dir, 'remote'])[0]
6445   elseif a:A =~# ':'
6446     let lead = matchstr(a:A, '^[^:]*:')
6447     let matches = s:LinesError([dir, 'ls-remote', remote])[0]
6448     call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
6449     call map(matches, 'lead . s:sub(v:val, "^.*\t", "")')
6450   else
6451     let matches = s:CompleteHeads(dir)
6452     if a:A =~# '^[\''"]\=+'
6453       call map(matches, '"+" . v:val')
6454     endif
6455   endif
6456   return s:FilterEscape(matches, a:A)
6457 endfunction
6459 function! fugitive#PushComplete(A, L, P, ...) abort
6460   return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompletePush'), a:000)
6461 endfunction
6463 function! fugitive#FetchComplete(A, L, P, ...) abort
6464   return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
6465 endfunction
6467 function! s:PushSubcommand(...) abort
6468   return {'no_more': 1}
6469 endfunction
6471 function! s:FetchSubcommand(...) abort
6472   return {'no_more': 1}
6473 endfunction
6475 " Section: :Gdiff
6477 augroup fugitive_diff
6478   autocmd!
6479   autocmd BufWinLeave * nested
6480         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
6481         \   call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
6482         \ endif
6483   autocmd BufWinEnter * nested
6484         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
6485         \   call s:diffoff() |
6486         \ endif
6487 augroup END
6489 function! s:can_diffoff(buf) abort
6490   return getwinvar(bufwinnr(a:buf), '&diff') &&
6491         \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
6492 endfunction
6494 function! fugitive#CanDiffoff(buf) abort
6495   return s:can_diffoff(bufnr(a:buf))
6496 endfunction
6498 function! s:DiffModifier(count, default) abort
6499   let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
6500   if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
6501     return ''
6502   elseif &diffopt =~# 'vertical'
6503     return 'vertical '
6504   elseif !get(g:, 'fugitive_diffsplit_directional_fit', a:default)
6505     return ''
6506   elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
6507     return ''
6508   else
6509     return 'vertical '
6510   endif
6511 endfunction
6513 function! s:diff_window_count() abort
6514   let c = 0
6515   for nr in range(1,winnr('$'))
6516     let c += getwinvar(nr,'&diff')
6517   endfor
6518   return c
6519 endfunction
6521 function! s:diffthis() abort
6522   if !&diff
6523     let w:fugitive_diff_restore = 1
6524     diffthis
6525   endif
6526 endfunction
6528 function! s:diffoff() abort
6529   unlet! w:fugitive_diff_restore
6530   diffoff
6531 endfunction
6533 function! s:diffoff_all(dir) abort
6534   let curwin = winnr()
6535   for nr in range(1,winnr('$'))
6536     if getwinvar(nr, '&diff') && !empty(getwinvar(nr, 'fugitive_diff_restore'))
6537       call setwinvar(nr, 'fugitive_diff_restore', '')
6538     endif
6539   endfor
6540   if curwin != winnr()
6541     execute curwin.'wincmd w'
6542   endif
6543   diffoff!
6544 endfunction
6546 function! s:IsConflicted() abort
6547   return len(@%) && !empty(s:ChompDefault('', ['ls-files', '--unmerged', '--', expand('%:p')]))
6548 endfunction
6550 function! fugitive#Diffsplit(autodir, keepfocus, mods, arg, ...) abort
6551   exe s:VersionCheck()
6552   let args = s:ArgSplit(a:arg)
6553   let post = ''
6554   let autodir = a:autodir
6555   while get(args, 0, '') =~# '^++'
6556     if args[0] =~? '^++novertical$'
6557       let autodir = 0
6558     else
6559       return 'echoerr ' . string('fugitive: unknown option ' . args[0])
6560     endif
6561     call remove(args, 0)
6562   endwhile
6563   if get(args, 0) =~# '^+'
6564     let post = remove(args, 0)[1:-1]
6565   endif
6566   if exists(':DiffGitCached') && empty(args)
6567     return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
6568   endif
6569   let commit = s:DirCommitFile(@%)[1]
6570   if a:mods =~# '\<\d*tab\>'
6571     let mods = substitute(a:mods, '\<\d*tab\>', '', 'g')
6572     let pre = matchstr(a:mods, '\<\d*tab\>') . 'edit'
6573   else
6574     let mods = 'keepalt ' . a:mods
6575     let pre = ''
6576   endif
6577   let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
6578   if (empty(args) || args[0] =~# '^>\=:$') && a:keepfocus
6579     exe s:DirCheck()
6580     if commit =~# '^1\=$' && s:IsConflicted()
6581       let parents = [s:Relative(':2:'), s:Relative(':3:')]
6582     elseif empty(commit)
6583       let parents = [s:Relative(':0:')]
6584     elseif commit =~# '^\d\=$'
6585       let parents = [s:Relative('@:')]
6586     elseif commit =~# '^\x\x\+$'
6587       let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
6588       call map(parents, 's:Relative(v:val . ":")')
6589     endif
6590   endif
6591   try
6592     if exists('parents') && len(parents) > 1
6593       exe pre
6594       let mods = (autodir ? s:DiffModifier(len(parents) + 1, empty(args) || args[0] =~# '^>') : '') . s:Mods(mods, 'leftabove')
6595       let nr = bufnr('')
6596       if len(parents) > 1 && !&equalalways
6597         let equalalways = 0
6598         set equalalways
6599       endif
6600       execute mods 'split' s:fnameescape(fugitive#Find(parents[0]))
6601       call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
6602       let nr2 = bufnr('')
6603       call s:diffthis()
6604       exe back
6605       call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
6606       let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
6607       for i in range(len(parents)-1, 1, -1)
6608         execute mods 'split' s:fnameescape(fugitive#Find(parents[i]))
6609         call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
6610         let nrx = bufnr('')
6611         call s:diffthis()
6612         exe back
6613         call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
6614       endfor
6615       call s:diffthis()
6616       return post
6617     elseif len(args)
6618       let arg = join(args, ' ')
6619       if arg ==# ''
6620         return post
6621       elseif arg ==# ':/'
6622         exe s:DirCheck()
6623         let file = s:Relative()
6624       elseif arg ==# ':'
6625         exe s:DirCheck()
6626         let file = len(commit) ? s:Relative() : s:Relative(s:IsConflicted() ? ':1:' : ':0:')
6627       elseif arg =~# '^:\d$'
6628         exe s:DirCheck()
6629         let file = s:Relative(arg . ':')
6630       elseif arg =~# '^[~^]\d*$'
6631         return 'echoerr ' . string('fugitive: change ' . arg . ' to !' . arg . ' to diff against ancestor')
6632       else
6633         try
6634           let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
6635         catch /^fugitive:/
6636           return 'echoerr ' . string(v:exception)
6637         endtry
6638       endif
6639       if a:keepfocus || arg =~# '^>'
6640         let mods = s:Mods(a:mods, 'leftabove')
6641       else
6642         let mods = s:Mods(a:mods)
6643       endif
6644     elseif exists('parents')
6645       let file = get(parents, -1, s:Relative(repeat('0', 40). ':'))
6646       let mods = s:Mods(a:mods, 'leftabove')
6647     elseif len(commit)
6648       let file = s:Relative()
6649       let mods = s:Mods(a:mods, 'rightbelow')
6650     elseif s:IsConflicted()
6651       let file = s:Relative(':1:')
6652       let mods = s:Mods(a:mods, 'leftabove')
6653       if get(g:, 'fugitive_legacy_commands', 1)
6654         let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
6655       endif
6656     else
6657       exe s:DirCheck()
6658       let file = s:Relative(':0:')
6659       let mods = s:Mods(a:mods, 'leftabove')
6660     endif
6661     let spec = s:Generate(file)
6662     if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
6663       let spec = s:VimSlash(spec . s:Relative('/'))
6664     endif
6665     exe pre
6666     let w:fugitive_diff_restore = 1
6667     let mods = (autodir ? s:DiffModifier(2, empty(args) || args[0] =~# '^>') : '') . mods
6668     if &diffopt =~# 'vertical'
6669       let diffopt = &diffopt
6670       set diffopt-=vertical
6671     endif
6672     execute mods 'diffsplit' s:fnameescape(spec)
6673     let w:fugitive_diff_restore = 1
6674     let winnr = winnr()
6675     if getwinvar('#', '&diff')
6676       if a:keepfocus
6677         exe back
6678       endif
6679     endif
6680     return post
6681   catch /^fugitive:/
6682     return 'echoerr ' . string(v:exception)
6683   finally
6684     if exists('l:equalalways')
6685       let &g:equalalways = equalalways
6686     endif
6687     if exists('diffopt')
6688       let &diffopt = diffopt
6689     endif
6690   endtry
6691 endfunction
6693 " Section: :GMove, :GRemove
6695 function! s:Move(force, rename, destination) abort
6696   exe s:VersionCheck()
6697   let dir = s:Dir()
6698   exe s:DirCheck(dir)
6699   if s:DirCommitFile(@%)[1] !~# '^0\=$' || empty(@%)
6700     return 'echoerr ' . string('fugitive: mv not supported for this buffer')
6701   endif
6702   if a:rename
6703     let default_root = expand('%:p:s?[\/]$??:h') . '/'
6704   else
6705     let default_root = s:Tree(dir) . '/'
6706   endif
6707   if a:destination =~# '^:/:\='
6708     let destination = s:Tree(dir) . s:Expand(substitute(a:destination, '^:/:\=', '', ''))
6709   elseif a:destination =~# '^:(top)'
6710     let destination = s:Expand(matchstr(a:destination, ')\zs.*'))
6711     if destination !~# '^/\|^\a\+:'
6712       let destination = s:Tree(dir) . '/' . destination
6713     endif
6714     let destination = s:Tree(dir) .
6715   elseif a:destination =~# '^:(\%(top,literal\|literal,top\))'
6716     let destination = s:Tree(dir) . matchstr(a:destination, ')\zs.*')
6717   elseif a:destination =~# '^:(literal)\.\.\=\%(/\|$\)'
6718     let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
6719   elseif a:destination =~# '^:(literal)'
6720     let destination = simplify(default_root . matchstr(a:destination, ')\zs.*'))
6721   else
6722     let destination = s:Expand(a:destination)
6723     if destination =~# '^\.\.\=\%(/\|$\)'
6724       let destination = simplify(getcwd() . '/' . destination)
6725     elseif destination !~# '^\a\+:\|^/'
6726       let destination = default_root . destination
6727     endif
6728   endif
6729   let destination = s:Slash(destination)
6730   if isdirectory(@%)
6731     setlocal noswapfile
6732   endif
6733   let exec = fugitive#Execute(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination], dir)
6734   if exec.exit_status && exec.stderr !=# ['']
6735     return 'echoerr ' .string('fugitive: '.s:JoinChomp(exec.stderr))
6736   endif
6737   if isdirectory(destination)
6738     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
6739   endif
6740   let reload = '|call fugitive#DidChange(' . string(exec) . ')'
6741   if empty(s:DirCommitFile(@%)[1])
6742     if isdirectory(destination)
6743       return 'keepalt edit '.s:fnameescape(destination) . reload
6744     else
6745       return 'keepalt saveas! '.s:fnameescape(destination) . reload
6746     endif
6747   else
6748     return 'file '.s:fnameescape(fugitive#Find(':0:'.destination, dir)) . reload
6749   endif
6750 endfunction
6752 function! fugitive#RenameComplete(A,L,P) abort
6753   if a:A =~# '^[.:]\=/'
6754     return fugitive#CompletePath(a:A)
6755   else
6756     let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
6757     return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
6758   endif
6759 endfunction
6761 function! fugitive#MoveCommand(line1, line2, range, bang, mods, arg, ...) abort
6762   return s:Move(a:bang, 0, a:arg)
6763 endfunction
6765 function! fugitive#RenameCommand(line1, line2, range, bang, mods, arg, ...) abort
6766   return s:Move(a:bang, 1, a:arg)
6767 endfunction
6769 function! s:Remove(after, force) abort
6770   exe s:VersionCheck()
6771   let dir = s:Dir()
6772   exe s:DirCheck(dir)
6773   if len(@%) && s:DirCommitFile(@%)[1] ==# ''
6774     let cmd = ['rm']
6775   elseif s:DirCommitFile(@%)[1] ==# '0'
6776     let cmd = ['rm','--cached']
6777   else
6778     return 'echoerr ' . string('fugitive: rm not supported for this buffer')
6779   endif
6780   if a:force
6781     let cmd += ['--force']
6782   endif
6783   let message = s:ChompStderr(cmd + ['--', expand('%:p')], dir)
6784   if len(message)
6785     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
6786     return 'echoerr '.string(v:errmsg)
6787   else
6788     return a:after . (a:force ? '!' : ''). '|call fugitive#DidChange(' . string(dir) . ')'
6789   endif
6790 endfunction
6792 function! fugitive#RemoveCommand(line1, line2, range, bang, mods, arg, ...) abort
6793   return s:Remove('edit', a:bang)
6794 endfunction
6796 function! fugitive#UnlinkCommand(line1, line2, range, bang, mods, arg, ...) abort
6797   return s:Remove('edit', a:bang)
6798 endfunction
6800 function! fugitive#DeleteCommand(line1, line2, range, bang, mods, arg, ...) abort
6801   return s:Remove('bdelete', a:bang)
6802 endfunction
6804 " Section: :Git blame
6806 function! s:Keywordprg() abort
6807   let args = ' --git-dir=' . escape(FugitiveGitPath(s:GitDir()), "\\\"' ")
6808   if has('gui_running') && !has('win32')
6809     return s:GitShellCmd() . ' --no-pager' . args . ' log -1'
6810   else
6811     return s:GitShellCmd() . args . ' show'
6812   endif
6813 endfunction
6815 function! s:linechars(pattern) abort
6816   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
6817   if &conceallevel > 1
6818     for col in range(1, chars)
6819       let chars -= synconcealed(line('.'), col)[0]
6820     endfor
6821   endif
6822   return chars
6823 endfunction
6825 function! s:BlameBufnr(...) abort
6826   let state = s:TempState(a:0 ? a:1 : bufnr(''))
6827   if get(state, 'filetype', '') ==# 'fugitiveblame'
6828     return get(state, 'origin_bufnr', -1)
6829   else
6830     return -1
6831   endif
6832 endfunction
6834 function! s:BlameCommitFileLnum(...) abort
6835   let line = a:0 ? a:1 : getline('.')
6836   let state = a:0 > 1 ? a:2 : s:TempState()
6837   if get(state, 'filetype', '') !=# 'fugitiveblame'
6838     return ['', '', 0]
6839   endif
6840   let commit = matchstr(line, '^\^\=[?*]*\zs\x\+')
6841   if commit =~# '^0\+$'
6842     let commit = ''
6843   elseif has_key(state, 'blame_reverse_end')
6844     let commit = get(s:LinesError([state.git_dir, 'rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end])[0], 0, '')
6845   endif
6846   let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
6847   let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s*\d\+ \%((\| *\d\+)\)')
6848   if empty(path) && lnum
6849     let path = get(state, 'blame_file', '')
6850   endif
6851   return [commit, path, lnum]
6852 endfunction
6854 function! s:BlameLeave() abort
6855   let state = s:TempState()
6856   let bufwinnr = exists('*win_id2win') ? win_id2win(get(state, 'origin_winid')) : 0
6857   if bufwinnr == 0
6858     let bufwinnr = bufwinnr(get(state, 'origin_bufnr', -1))
6859   endif
6860   if get(state, 'filetype', '') ==# 'fugitiveblame' && bufwinnr > 0
6861     let bufnr = bufnr('')
6862     exe bufwinnr . 'wincmd w'
6863     return bufnr . 'bdelete'
6864   endif
6865   return ''
6866 endfunction
6868 function! s:BlameQuit() abort
6869   let cmd = s:BlameLeave()
6870   if empty(cmd)
6871     return 'bdelete'
6872   elseif len(s:DirCommitFile(@%)[1])
6873     return cmd . '|Gedit'
6874   else
6875     return cmd
6876   endif
6877 endfunction
6879 function! fugitive#BlameComplete(A, L, P) abort
6880   return s:CompleteSub('blame', a:A, a:L, a:P)
6881 endfunction
6883 function! s:BlameSubcommand(line1, count, range, bang, mods, options) abort
6884   let dir = s:Dir(a:options)
6885   exe s:DirCheck(dir)
6886   let flags = copy(a:options.subcommand_args)
6887   let i = 0
6888   let raw = 0
6889   let commits = []
6890   let files = []
6891   let ranges = []
6892   if a:line1 > 0 && a:count > 0 && a:range != 1
6893     call extend(ranges, ['-L', a:line1 . ',' . a:count])
6894   endif
6895   while i < len(flags)
6896     let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
6897     if len(match) && len(match[2])
6898       call insert(flags, match[1])
6899       let flags[i+1] = '-' . match[2]
6900       continue
6901     endif
6902     let arg = flags[i]
6903     if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
6904       let raw = 1
6905     elseif arg ==# '--contents' && i + 1 < len(flags)
6906       call extend(commits, remove(flags, i, i+1))
6907       continue
6908     elseif arg ==# '-L' && i + 1 < len(flags)
6909       call extend(ranges, remove(flags, i, i+1))
6910       continue
6911     elseif arg =~# '^--contents='
6912       call add(commits, remove(flags, i))
6913       continue
6914     elseif arg =~# '^-L.'
6915       call add(ranges, remove(flags, i))
6916       continue
6917     elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
6918       let i += 1
6919       if i == len(flags)
6920         echohl ErrorMsg
6921         echo s:ChompStderr([dir, 'blame', arg])
6922         echohl NONE
6923         return ''
6924       endif
6925     elseif arg ==# '--'
6926       if i + 1 < len(flags)
6927         call extend(files, remove(flags, i + 1, -1))
6928       endif
6929       call remove(flags, i)
6930       break
6931     elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
6932       if index(flags, '--') >= 0
6933         call add(commits, remove(flags, i))
6934         continue
6935       endif
6936       if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
6937         call add(commits, remove(flags, i))
6938         continue
6939       endif
6940       try
6941         let dcf = s:DirCommitFile(fugitive#Find(arg, dir))
6942         if len(dcf[1]) && empty(dcf[2])
6943           call add(commits, remove(flags, i))
6944           continue
6945         endif
6946       catch /^fugitive:/
6947       endtry
6948       call add(files, remove(flags, i))
6949       continue
6950     endif
6951     let i += 1
6952   endwhile
6953   let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./', dir))), '^\.\%(/\|$\)', '', '')
6954   if empty(commits) && len(files) > 1
6955     call add(commits, remove(files, 1))
6956   endif
6957   exe s:BlameLeave()
6958   try
6959     let cmd = a:options.flags + ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', a:options.subcommand, '--show-number']
6960     call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
6961     if a:count > 0 && empty(ranges)
6962       let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
6963     endif
6964     call extend(cmd, ranges)
6965     let tempname = tempname()
6966     let temp = tempname . (raw ? '' : '.fugitiveblame')
6967     if len(commits)
6968       let cmd += commits
6969     elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
6970       let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
6971     elseif empty(files) && !s:HasOpt(flags, '--reverse')
6972       if &modified || !empty(s:DirCommitFile(@%)[1])
6973         let cmd += ['--contents', tempname . '.in']
6974         silent execute 'noautocmd keepalt %write ' . s:fnameescape(tempname . '.in')
6975         let delete_in = 1
6976       elseif &autoread
6977         exe 'checktime ' . bufnr('')
6978       endif
6979     else
6980       call fugitive#Autowrite()
6981     endif
6982     let basecmd = [{'git': a:options.git}, dir, '--literal-pathspecs'] + cmd + ['--'] + (len(files) ? files : [file])
6983     let [err, exec_error] = s:StdoutToFile(temp, basecmd)
6984     if exists('delete_in')
6985       call delete(tempname . '.in')
6986     endif
6987     redraw
6988     try
6989       if exec_error
6990         let lines = split(err, "\n")
6991         if empty(lines)
6992           let lines = readfile(temp)
6993         endif
6994         for i in range(len(lines))
6995           if lines[i] =~# '^error: \|^fatal: '
6996             echohl ErrorMsg
6997             echon lines[i]
6998             echohl NONE
6999             break
7000           else
7001             echon lines[i]
7002           endif
7003           if i != len(lines) - 1
7004             echon "\n"
7005           endif
7006         endfor
7007         return ''
7008       endif
7009       let temp_state = {
7010             \ 'git': a:options.git,
7011             \ 'flags': a:options.flags,
7012             \ 'args': [a:options.subcommand] + a:options.subcommand_args,
7013             \ 'git_dir': s:GitDir(a:options),
7014             \ 'cwd': s:UserCommandCwd(a:options),
7015             \ 'filetype': (raw ? 'git' : 'fugitiveblame'),
7016             \ 'blame_options': a:options,
7017             \ 'blame_flags': flags,
7018             \ 'blame_file': file}
7019       if s:HasOpt(flags, '--reverse')
7020         let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
7021       endif
7022       if a:line1 == 0 && a:count == 1
7023         if get(a:options, 'curwin')
7024           let edit = 'edit'
7025         elseif a:bang
7026           let edit = 'pedit'
7027         else
7028           let edit = 'split'
7029         endif
7030         return s:BlameCommit(s:Mods(a:mods) . edit, get(readfile(temp), 0, ''), temp_state)
7031       elseif (a:line1 == 0 || a:range == 1) && a:count > 0
7032         let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit', 'edit'], a:count - (a:line1 ? a:line1 : 1), 'split')
7033         return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
7034       else
7035         let temp = s:Resolve(temp)
7036         let temp_state.file = temp
7037         call s:RunSave(temp_state)
7038         if len(ranges + commits + files) || raw
7039           let reload = '|call fugitive#DidChange(fugitive#Result(' . string(temp_state.file) . '))'
7040           let mods = s:Mods(a:mods)
7041           if a:count != 0
7042             exe 'silent keepalt' mods get(a:options, 'curwin') ? 'edit' : 'split' s:fnameescape(temp)
7043           elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
7044             exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
7045           else
7046             return mods . 'edit ' . s:fnameescape(temp) . reload
7047           endif
7048           return reload[1 : -1]
7049         endif
7050         let tabmod = matchstr(a:mods, '\<\d*tab\>')
7051         let mods = substitute(a:mods, '\<\d*tab\>', '', 'g')
7052         if !empty(tabmod)
7053           silent execute tabmod . 'edit %'
7054         endif
7055         let temp_state.origin_bufnr = bufnr('')
7056         if exists('*win_getid')
7057           let temp_state.origin_winid = win_getid()
7058         endif
7059         let restore = []
7060         for winnr in range(winnr('$'),1,-1)
7061           if getwinvar(winnr, '&scrollbind')
7062             if !&l:scrollbind
7063               call setwinvar(winnr, '&scrollbind', 0)
7064             elseif winnr != winnr() && getwinvar(winnr, '&foldenable')
7065               call setwinvar(winnr, '&foldenable', 0)
7066               call add(restore, 'call setwinvar(bufwinnr('.winbufnr(winnr).'),"&foldenable",1)')
7067             endif
7068           endif
7069           let win_blame_bufnr = s:BlameBufnr(winbufnr(winnr))
7070           if getwinvar(winnr, '&scrollbind') ? win_blame_bufnr == temp_state.origin_bufnr : win_blame_bufnr > 0
7071             execute winbufnr(winnr).'bdelete'
7072           endif
7073         endfor
7074         let restore_winnr = get(temp_state, 'origin_winid', 'bufwinnr(' . temp_state.origin_bufnr . ')')
7075         if !&l:scrollbind
7076           call add(restore, 'call setwinvar(' . restore_winnr . ',"&scrollbind",0)')
7077         endif
7078         if &l:wrap
7079           call add(restore, 'call setwinvar(' . restore_winnr . ',"&wrap",1)')
7080         endif
7081         if &l:foldenable
7082           call add(restore, 'call setwinvar(' . restore_winnr . ',"&foldenable",1)')
7083         endif
7084         setlocal scrollbind nowrap nofoldenable
7085         let top = line('w0') + &scrolloff
7086         let current = line('.')
7087         exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
7088         let w:fugitive_leave = join(restore, '|')
7089         execute top
7090         normal! zt
7091         execute current
7092         setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
7093         if exists('+relativenumber')
7094           setlocal norelativenumber
7095         endif
7096         if exists('+signcolumn')
7097           setlocal signcolumn=no
7098         endif
7099         execute "vertical resize ".(s:linechars('.\{-\}\s\+\d\+\ze)')+1)
7100         redraw
7101         syncbind
7102         exe s:DoAutocmdChanged(temp_state)
7103       endif
7104     endtry
7105     return ''
7106   catch /^fugitive:/
7107     return 'echoerr ' . string(v:exception)
7108   endtry
7109 endfunction
7111 function! s:BlameCommit(cmd, ...) abort
7112   let line = a:0 ? a:1 : getline('.')
7113   let state = a:0 ? a:2 : s:TempState()
7114   let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
7115   let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
7116   let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
7117   if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
7118     let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
7119     return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
7120   endif
7121   if commit =~# '^0*$'
7122     return 'echoerr ' . string('fugitive: no commit')
7123   endif
7124   if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
7125     let path = commit . ':' . path
7126     return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
7127   endif
7128   let cmd = fugitive#Open(mods . a:cmd, 0, '', commit, [commit])
7129   if cmd =~# '^echoerr'
7130     return cmd
7131   endif
7132   execute cmd
7133   if a:cmd ==# 'pedit' || empty(path)
7134     return ''
7135   endif
7136   if search('^diff .* b/\M'.escape(path,'\').'$','W')
7137     call search('^+++')
7138     let head = line('.')
7139     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
7140       let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
7141       let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
7142       if lnum >= top && lnum <= top + len
7143         let offset = lnum - top
7144         if &scrolloff
7145           +
7146           normal! zt
7147         else
7148           normal! zt
7149           +
7150         endif
7151         while offset > 0 && line('.') < line('$')
7152           +
7153           if getline('.') =~# '^[ ' . sigil . ']'
7154             let offset -= 1
7155           endif
7156         endwhile
7157         return 'normal! zv'
7158       endif
7159     endwhile
7160     execute head
7161     normal! zt
7162   endif
7163   return ''
7164 endfunction
7166 function! s:BlameJump(suffix, ...) abort
7167   let suffix = a:suffix
7168   let [commit, path, lnum] = s:BlameCommitFileLnum()
7169   if empty(path)
7170     return 'echoerr ' . string('fugitive: could not determine filename for blame')
7171   endif
7172   if commit =~# '^0*$'
7173     let commit = '@'
7174     let suffix = ''
7175   endif
7176   let offset = line('.') - line('w0')
7177   let state = s:TempState()
7178   let flags = get(state, 'blame_flags', [])
7179   let blame_bufnr = s:BlameBufnr()
7180   if blame_bufnr > 0
7181     let bufnr = bufnr('')
7182     let winnr = bufwinnr(blame_bufnr)
7183     if winnr > 0
7184       exe winnr.'wincmd w'
7185       exe bufnr.'bdelete'
7186     endif
7187     execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
7188     execute lnum
7189   endif
7190   let my_bufnr = bufnr('')
7191   if blame_bufnr < 0
7192     let blame_args = flags + [commit . suffix, '--', path]
7193     let result = s:BlameSubcommand(0, 0, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
7194   else
7195     let blame_args = flags
7196     let result = s:BlameSubcommand(-1, -1, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
7197   endif
7198   if bufnr('') == my_bufnr
7199     return result
7200   endif
7201   execute result
7202   execute lnum
7203   let delta = line('.') - line('w0') - offset
7204   if delta > 0
7205     execute 'normal! '.delta."\<C-E>"
7206   elseif delta < 0
7207     execute 'normal! '.(-delta)."\<C-Y>"
7208   endif
7209   keepjumps syncbind
7210   redraw
7211   echo ':Git blame' s:fnameescape(blame_args)
7212   return ''
7213 endfunction
7215 let s:hash_colors = {}
7217 function! fugitive#BlameSyntax() abort
7218   let conceal = has('conceal') ? ' conceal' : ''
7219   let flags = get(s:TempState(), 'blame_flags', [])
7220   syn spell notoplevel
7221   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
7222   syn match FugitiveblameHash       "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7223   if s:HasOpt(flags, '-b') || FugitiveConfigGet('blame.blankBoundary') =~# '^1$\|^true$'
7224     syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7225   else
7226     syn match FugitiveblameBoundary "^\^"
7227   endif
7228   syn match FugitiveblameScoreDebug        " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
7229   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
7230   syn match FugitiveblameTime "\<[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
7231   exec 'syn match FugitiveblameLineNumber         "\s[[:digit:][:space:]]\{0,' . (len(line('$'))-1). '\}\d)\@=" contained containedin=FugitiveblameAnnotation' conceal
7232   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)
7233   exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
7234   exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
7235   syn match FugitiveblameShort              " \+\d\+)" contained contains=FugitiveblameLineNumber
7236   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
7237   hi def link FugitiveblameBoundary           Keyword
7238   hi def link FugitiveblameHash               Identifier
7239   hi def link FugitiveblameBoundaryIgnore     Ignore
7240   hi def link FugitiveblameUncommitted        Ignore
7241   hi def link FugitiveblameScoreDebug         Debug
7242   hi def link FugitiveblameTime               PreProc
7243   hi def link FugitiveblameLineNumber         Number
7244   hi def link FugitiveblameOriginalFile       String
7245   hi def link FugitiveblameOriginalLineNumber Float
7246   hi def link FugitiveblameShort              FugitiveblameDelimiter
7247   hi def link FugitiveblameDelimiter          Delimiter
7248   hi def link FugitiveblameNotCommittedYet    Comment
7249   if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
7250     return
7251   endif
7252   let seen = {}
7253   for x in split('01234567890abcdef', '\zs')
7254     exe 'syn match FugitiveblameHashGroup'.x '"\%(^\^\=[*?]*\)\@<='.x.'\x\{5,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
7255   endfor
7256   for lnum in range(1, line('$'))
7257     let orig_hash = matchstr(getline(lnum), '^\^\=[*?]*\zs\x\{6\}')
7258     let hash = orig_hash
7259     let hash = substitute(hash, '\(\x\)\x', '\=submatch(1).printf("%x", 15-str2nr(submatch(1),16))', 'g')
7260     let hash = substitute(hash, '\(\x\x\)', '\=printf("%02x", str2nr(submatch(1),16)*3/4+32)', 'g')
7261     if hash ==# '' || orig_hash ==# '000000' || has_key(seen, hash)
7262       continue
7263     endif
7264     let seen[hash] = 1
7265     if &t_Co == 256
7266       let [s, r, g, b; __] = map(matchlist(orig_hash, '\(\x\)\x\(\x\)\x\(\x\)\x'), 'str2nr(v:val,16)')
7267       let color = 16 + (r + 1) / 3 * 36 + (g + 1) / 3 * 6 + (b + 1) / 3
7268       if color == 16
7269         let color = 235
7270       elseif color == 231
7271         let color = 255
7272       endif
7273       let s:hash_colors[hash] = ' ctermfg='.color
7274     else
7275       let s:hash_colors[hash] = ''
7276     endif
7277     let pattern = substitute(orig_hash, '^\(\x\)\x\(\x\)\x\(\x\)\x$', '\1\\x\2\\x\3\\x', '') . '*'
7278     exe 'syn match FugitiveblameHash'.hash.'       "\%(^\^\=[*?]*\)\@<='.pattern.'" contained containedin=FugitiveblameHashGroup' . orig_hash[0]
7279   endfor
7280   syn match FugitiveblameUncommitted "\%(^\^\=[?*]*\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7281   call s:BlameRehighlight()
7282 endfunction
7284 function! s:BlameRehighlight() abort
7285   for [hash, cterm] in items(s:hash_colors)
7286     if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
7287       exe 'hi FugitiveblameHash'.hash.' guifg=#' . hash . cterm
7288     else
7289       exe 'hi link FugitiveblameHash'.hash.' Identifier'
7290     endif
7291   endfor
7292 endfunction
7294 function! s:BlameMaps(is_ftplugin) abort
7295   let ft = a:is_ftplugin
7296   call s:MapGitOps(ft)
7297   call s:Map('n', '<F1>', ':help :Git_blame<CR>', '<silent>', ft)
7298   call s:Map('n', 'g?',   ':help :Git_blame<CR>', '<silent>', ft)
7299   call s:Map('n', 'gq',   ':exe <SID>BlameQuit()<CR>', '<silent>', ft)
7300   call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7301   call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7302   call s:Map('n', '-',    ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7303   call s:Map('n', 's',    ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7304   call s:Map('n', 'u',    ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7305   call s:Map('n', 'P',    ':<C-U>exe <SID>BlameJump("^".v:count1)<CR>', '<silent>', ft)
7306   call s:Map('n', '~',    ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>', ft)
7307   call s:Map('n', 'i',    ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7308   call s:Map('n', 'o',    ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>', ft)
7309   call s:Map('n', 'O',    ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>', ft)
7310   call s:Map('n', 'p',    ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>', ft)
7311   exe s:Map('n', '.',    ":<C-U> <C-R>=substitute(<SID>BlameCommitFileLnum()[0],'^$','@','')<CR><Home>", '', ft)
7312   exe s:Map('n', '(',    "-", '', ft)
7313   exe s:Map('n', ')',    "+", '', ft)
7314   call s:Map('n', 'A',    ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>', ft)
7315   call s:Map('n', 'C',    ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>', ft)
7316   call s:Map('n', 'D',    ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>', ft)
7317 endfunction
7319 function! fugitive#BlameFileType() abort
7320   setlocal nomodeline
7321   setlocal foldmethod=manual
7322   if len(s:GitDir())
7323     let &l:keywordprg = s:Keywordprg()
7324   endif
7325   let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
7326   if exists('+concealcursor')
7327     setlocal concealcursor=nc conceallevel=2
7328     let b:undo_ftplugin .= ' concealcursor< conceallevel<'
7329   endif
7330   if &modifiable
7331     return ''
7332   endif
7333   call s:BlameMaps(1)
7334 endfunction
7336 function! s:BlameCursorSync(bufnr, line) abort
7337   if a:line == line('.')
7338     return
7339   endif
7340   if get(s:TempState(), 'origin_bufnr') == a:bufnr || get(s:TempState(a:bufnr), 'origin_bufnr') == bufnr('')
7341     if &startofline
7342       execute a:line
7343     else
7344       let pos = getpos('.')
7345       let pos[1] = a:line
7346       call setpos('.', pos)
7347     endif
7348   endif
7349 endfunction
7351 augroup fugitive_blame
7352   autocmd!
7353   autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
7354   autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
7355   autocmd WinLeave * let s:cursor_for_blame = [bufnr(''), line('.')]
7356   autocmd WinEnter * if exists('s:cursor_for_blame') | call call('s:BlameCursorSync', s:cursor_for_blame) | endif
7357 augroup END
7359 " Section: :GBrowse
7361 function! s:BrowserOpen(url, mods, echo_copy) abort
7362   let [_, main, query, anchor; __] = matchlist(a:url, '^\([^#?]*\)\(?[^#]*\)\=\(#.*\)\=')
7363   let url = main . tr(query, ' ', '+') . anchor
7364   let url = substitute(url, '[ <>\|"]', '\="%".printf("%02X",char2nr(submatch(0)))', 'g')
7365   let mods = s:Mods(a:mods)
7366   if a:echo_copy
7367     if has('clipboard')
7368       let @+ = url
7369     endif
7370     return 'echo '.string(url)
7371   elseif exists(':Browse') == 2
7372     return 'echo '.string(url).'|' . mods . 'Browse '.url
7373   elseif exists(':OpenBrowser') == 2
7374     return 'echo '.string(url).'|' . mods . 'OpenBrowser '.url
7375   else
7376     if !exists('g:loaded_netrw')
7377       runtime! autoload/netrw.vim
7378     endif
7379     if exists('*netrw#BrowseX')
7380       return 'echo '.string(url).'|' . mods . 'call netrw#BrowseX('.string(url).', 0)'
7381     elseif exists('*netrw#NetrwBrowseX')
7382       return 'echo '.string(url).'|' . mods . 'call netrw#NetrwBrowseX('.string(url).', 0)'
7383     else
7384       return 'echoerr ' . string('Netrw not found. Define your own :Browse to use :GBrowse')
7385     endif
7386   endif
7387 endfunction
7389 function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, ...) abort
7390   exe s:VersionCheck()
7391   let dir = s:Dir()
7392   try
7393     let arg = a:arg
7394     if arg =~# '^++\%([Gg]it\)\=[Rr]emote='
7395       let remote = matchstr(arg, '^++\%([Gg]it\)\=[Rr]emote=\zs\S\+')
7396       let arg = matchstr(arg, '\s\zs\S.*')
7397     endif
7398     let validremote = '\.\%(git\)\=\|\.\=/.*\|\a[[:alnum:]_-]*\%(://.\{-\}\)\='
7399     if arg ==# '-'
7400       let remote = ''
7401       let rev = ''
7402       let result = fugitive#Result()
7403       if filereadable(get(result, 'file', ''))
7404         let rev = s:fnameescape(result.file)
7405       else
7406         return 'echoerr ' . string('fugitive: could not find prior :Git invocation')
7407       endif
7408     elseif !exists('l:remote')
7409       let remote = matchstr(arg, '\\\@<!\%(\\\\\)*[!@]\zs\%('.validremote.'\)$')
7410       let rev = strpart(arg, 0, len(arg) - len(remote) - (empty(remote) ? 0 : 1))
7411     else
7412       let rev = arg
7413     endif
7414     let expanded = s:Expand(rev)
7415     if expanded =~? '^\a\a\+:[\/][\/]' && expanded !~? '^fugitive:'
7416       return s:BrowserOpen(s:Slash(expanded), a:mods, a:bang)
7417     endif
7418     if !exists('l:result')
7419       let result = s:TempState(empty(expanded) ? bufnr('') : expanded)
7420     endif
7421     if !get(result, 'origin_bufnr', 1) && filereadable(get(result, 'file', ''))
7422       for line in readfile(result.file, '', 4096)
7423         let rev = s:fnameescape(matchstr(line, '\<https\=://[^[:space:]<>]*[^[:space:]<>.,;:"''!?]'))
7424         if len(rev)
7425           return s:BrowserOpen(rev, a:mods, a:bang)
7426         endif
7427       endfor
7428       return 'echoerr ' . string('fugitive: no URL found in output of :Git')
7429     endif
7430     if empty(remote) && expanded =~# '^[^-./:^~][^:^~]*$' && !empty(dir)
7431       let config = fugitive#Config(dir)
7432       if !empty(FugitiveConfigGet('remote.' . expanded . '.url', config))
7433         let remote = expanded
7434         let expanded = ''
7435       endif
7436     endif
7437     if empty(expanded)
7438       let bufname = &buftype =~# '^\%(nofile\|terminal\)$' ? '' : s:BufName('%')
7439       let expanded = s:DirRev(bufname)[1]
7440       if empty(expanded)
7441         let expanded = fugitive#Path(bufname, ':(top)', dir)
7442       endif
7443       if a:count > 0 && has_key(result, 'origin_bufnr') && a:range != 2
7444         let blame = s:BlameCommitFileLnum(getline(a:count))
7445         if len(blame[0])
7446           let expanded = blame[0]
7447         endif
7448       endif
7449     endif
7450     let full = s:Generate(expanded, dir)
7451     let commit = ''
7452     let ref = ''
7453     let forbid_ref_as_commit = 0
7454     if full =~# '^fugitive:'
7455       let [dir, commit, path] = s:DirCommitFile(full)
7456       if commit =~# '^\d\=$'
7457         let commit = ''
7458         let type = path =~# '^/\=$' ? 'tree' : 'blob'
7459       else
7460         let ref_match = matchlist(expanded, '^\(@{\@!\|[^:~^@]\+\)\(:\%(//\)\@!\|[~^@]\|$\)')
7461         let ref = get(ref_match, 1, '')
7462         let forbid_ref_as_commit = ref =~# '^@\=$' || ref_match[2] !~# '^:\=$'
7463         if empty(path) && !forbid_ref_as_commit
7464           let type = 'ref'
7465         else
7466           let type = s:ChompDefault(empty(path) ? 'commit': 'blob',
7467                 \ ['cat-file', '-t', commit . substitute(path, '^/', ':', '')], dir)
7468         endif
7469       endif
7470       let path = path[1:-1]
7471     elseif !empty(s:Tree(dir))
7472       let relevant_dir = FugitiveExtractGitDir(full)
7473       if !empty(relevant_dir)
7474         let dir = relevant_dir
7475       endif
7476       let path = fugitive#Path(full, '/', dir)[1:-1]
7477       if empty(path) || isdirectory(full)
7478         let type = 'tree'
7479       else
7480         let type = 'blob'
7481       endif
7482     else
7483       let path = '.git/' . full[strlen(dir)+1:-1]
7484       let type = ''
7485     endif
7486     exe s:DirCheck(dir)
7487     if path =~# '^\.git/'
7488       let ref = matchstr(path, '^.git/\zs\%(refs/[^/]\+/[^/].*\|\w*HEAD\)$')
7489       let type = empty(ref) ? 'root': 'ref'
7490       let path = ''
7491     endif
7492     if empty(ref) || ref ==# 'HEAD' || ref ==# '@'
7493       let ref = fugitive#Head(-1, dir)
7494     endif
7495     if ref =~# '^\x\{40,\}$'
7496       let ref = ''
7497     elseif !empty(ref) && ref !~# '^refs/'
7498       let ref = FugitiveExecute(['rev-parse', '--symbolic-full-name', ref], dir).stdout[0]
7499       if ref !~# '^refs/'
7500         let ref = ''
7501       endif
7502     endif
7504     if !exists('l:config') || s:Dir(config) !=# dir
7505       let config = fugitive#Config(dir)
7506     endif
7507     let merge = ''
7508     if !empty(remote) && ref =~# '^refs/remotes/[^/]\+/[^/]\|^refs/heads/[^/]'
7509       let merge = matchstr(ref, '^refs/\%(heads/\|remotes/[^/]\+/\)\zs.\+')
7510       let ref = 'refs/heads/' . merge
7511     elseif ref =~# '^refs/remotes/[^/]\+/[^/]'
7512       let remote = matchstr(ref, '^refs/remotes/\zs[^/]\+')
7513       let merge = matchstr(ref, '^refs/remotes/[^/]\+/\zs.\+')
7514       let ref = 'refs/heads/' . merge
7515     elseif ref =~# '^refs/heads/[^/]'
7516       let merge = strpart(ref, 11)
7517       let r = FugitiveConfigGet('branch.' . merge . '.remote', config)
7518       let m = FugitiveConfigGet('branch.' . merge . '.merge', config)[11:-1]
7519       if r ==# '.' && !empty(m)
7520         let r2 = FugitiveConfigGet('branch.'.m.'.remote', config)
7521         if r2 !~# '^\.\=$'
7522           let r = r2
7523           let m = FugitiveConfigGet('branch.'.m.'.merge', config)[11:-1]
7524         endif
7525       endif
7526       if r !~# '^\.\=$'
7527         let remote = r
7528       endif
7529       if !empty(remote)
7530         let remote_ref = 'refs/remotes/' . remote . '/' . merge
7531         if FugitiveConfigGet('push.default', config) ==# 'upstream' ||
7532               \ !filereadable(FugitiveFind('.git/' . remote_ref, dir)) && empty(s:ChompDefault('', ['rev-parse', '--verify', remote_ref, '--'], dir))
7533           let merge = m
7534           let ref = 'refs/heads/' . merge
7535         endif
7536       endif
7537     endif
7539     if empty(remote) || remote ==# '.'
7540       let remote = s:RemoteDefault(config)
7541     endif
7542     if empty(merge) || empty(remote)
7543       let provider_ref = ref
7544     else
7545       let provider_ref = 'refs/remotes/' . remote . '/' . merge
7546     endif
7547     if forbid_ref_as_commit || a:count >= 0
7548       let ref = ''
7549       if type ==# 'ref'
7550         let type = 'commit'
7551       endif
7552     elseif type ==# 'ref' && ref =~# '^refs/\%(heads\|tags\)/[^/]'
7553         let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
7554     endif
7556     let line1 = a:count > 0 && type ==# 'blob' ? a:line1 : 0
7557     let line2 = a:count > 0 && type ==# 'blob' ? a:count : 0
7558     if empty(commit) && type =~# '^\%(tree\|blob\)$'
7559       if a:count < 0
7560         let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
7561       elseif len(provider_ref)
7562         let owner = s:Owner(@%, dir)
7563         let commit = s:ChompDefault('', ['merge-base', provider_ref, empty(owner) ? '@' : owner, '--'], dir)
7564         if line2 > 0 && empty(arg) && commit =~# '^\x\{40,\}$' && type ==# 'blob'
7565           let blame_list = tempname()
7566           call writefile([commit, ''], blame_list, 'b')
7567           let blame_cmd = ['-c', 'blame.coloring=none', 'blame', '-L', line1.','.line2, '-S', blame_list, '-s', '--show-number']
7568           if !&l:modified || has_key(result, 'origin_bufnr')
7569             let [blame, exec_error] = s:LinesError(blame_cmd + ['./' . path], dir)
7570           else
7571             let blame_in = tempname()
7572             silent exe 'noautocmd keepalt %write' blame_in
7573             let [blame, exec_error] = s:LinesError(blame_cmd + ['--contents', blame_in, './' . path], dir)
7574             call delete(blame_in)
7575           endif
7576           call delete(blame_list)
7577           if !exec_error
7578             let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
7579             if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
7580               let line1 = +matchstr(blame[0], blame_regex)
7581               let line2 = +matchstr(blame[-1], blame_regex)
7582             else
7583               throw "fugitive: can't browse to unpushed change"
7584             endif
7585           endif
7586         endif
7587       endif
7588       if empty(commit)
7589         let commit = fugitive#RevParse(empty(ref) ? 'HEAD' : ref, dir)
7590       endif
7591     endif
7593     if remote =~# ':'
7594       let remote_url = remote
7595     else
7596       let remote_url = fugitive#RemoteUrl(remote, config)
7597     endif
7598     let raw = empty(remote_url) ? remote : remote_url
7599     let git_dir = s:GitDir(dir)
7601     let opts = {
7602           \ 'git_dir': git_dir,
7603           \ 'repo': {'git_dir': git_dir},
7604           \ 'remote': raw,
7605           \ 'remote_name': remote,
7606           \ 'commit': s:UrlEncode(commit),
7607           \ 'path': substitute(s:UrlEncode(path), '%20', ' ', 'g'),
7608           \ 'type': type,
7609           \ 'line1': line1,
7610           \ 'line2': line2}
7612     if empty(path)
7613       if type ==# 'ref' && ref =~# '^refs/'
7614         let opts.path = '.git/' . s:UrlEncode(ref)
7615         let opts.type = ''
7616       elseif type ==# 'root'
7617         let opts.path ='.git/index'
7618         let opts.type = ''
7619       endif
7620     elseif type ==# 'tree' && !empty(path)
7621       let opts.path = s:sub(opts.path, '/\=$', '/')
7622     endif
7624     for l:.Handler in get(g:, 'fugitive_browse_handlers', [])
7625       let l:.url = call(Handler, [copy(opts)])
7626       if type(url) == type('') && url =~# '://'
7627         return s:BrowserOpen(url, a:mods, a:bang)
7628       endif
7629     endfor
7631     if !empty(remote_url)
7632       return 'echoerr ' . string("fugitive: no GBrowse handler installed for '".remote_url."'")
7633     else
7634       return 'echoerr ' . string("fugitive: could not find remote named '".remote."'")
7635     endif
7636   catch /^fugitive:/
7637     return 'echoerr ' . string(v:exception)
7638   endtry
7639 endfunction
7641 function! s:RemoteRefToLocalRef(repo, remote_url, ref_path) abort
7642   let ref_path = substitute(a:ref_path, ':', '/', '')
7643   let rev = ''
7644   if ref_path =~# '^\x\{40,\}\%(/\|$\)'
7645     let rev = substitute(ref_path, '/', ':', '')
7646   elseif ref_path =~# '^[^:/^~]\+'
7647     let first_component = matchstr(ref_path, '^[^:/^~]\+')
7648     let lines = fugitive#Execute(['ls-remote', a:remote_url, first_component, first_component . '/*'], a:repo).stdout[0:-2]
7649     for line in lines
7650       let full = matchstr(line, "\t\\zs.*")
7651       for candidate in [full, matchstr(full, '^refs/\w\+/\zs.*')]
7652         if candidate ==# first_component || strpart(ref_path . '/', 0, len(candidate) + 1) ==# candidate . '/'
7653           let rev = matchstr(line, '^\x\+') . substitute(strpart(ref_path, len(candidate)), '/', ':', '')
7654         endif
7655       endfor
7656     endfor
7657   endif
7658   if empty(rev)
7659     return ''
7660   endif
7661   let commitish = matchstr(rev, '^[^:^~]*')
7662   let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo)
7663   if rev_parse.exit_status
7664     if fugitive#Execute(['fetch', remote_url, commitish], a:repo).exit_status
7665       return ''
7666     endif
7667     let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo)
7668   endif
7669   if rev_parse.exit_status
7670     return ''
7671   endif
7672   return rev_parse.stdout[0] . matchstr(rev, ':.*')
7673 endfunction
7675 function! fugitive#ResolveUrl(target, ...) abort
7676   let repo = call('s:Dir', a:000)
7677   let origins = get(g:, 'fugitive_url_origins', {})
7678   let prefix = substitute(s:Slash(a:target), '#.*', '', '')
7679   while prefix =~# '://'
7680     let extracted = FugitiveExtractGitDir(expand(get(origins, prefix, '')))
7681     if !empty(extracted)
7682       let repo = s:Dir(extracted)
7683       break
7684     endif
7685     let prefix = matchstr(prefix, '.*\ze/')
7686   endwhile
7687   let git_dir = s:GitDir(repo)
7688   for remote_name in keys(FugitiveConfigGetRegexp('^remote\.\zs.*\ze\.url$', repo))
7689     let remote_url = fugitive#RemoteUrl(remote_name, repo)
7690     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`']]
7691       let handler_opts = {
7692             \ 'git_dir': git_dir,
7693             \ 'repo': {'git_dir': git_dir},
7694             \ 'remote': remote_url,
7695             \ 'remote_name': remote_name,
7696             \ 'commit': '1`commit`',
7697             \ 'type': get(variant, 0),
7698             \ 'path': get(variant, 1) ? '1`path`' : '',
7699             \ 'line1': get(variant, 2),
7700             \ 'line2': get(variant, 3)}
7701       let url = ''
7702       for l:.Handler in get(g:, 'fugitive_browse_handlers', [])
7703         let l:.url = call(Handler, [copy(handler_opts)])
7704         if type(url) == type('') && url =~# '://'
7705           break
7706         endif
7707       endfor
7708       if type(url) != type('') || url !~# '://'
7709         continue
7710       endif
7711       let keys = split(substitute(url, '\d`\(\w\+`\)\|.', '\1', 'g'), '`')
7712       let pattern = substitute(url, '\d`\w\+`\|[][^$.*\~]', '\=len(submatch(0)) == 1 ? "\\" . submatch(0) : "\\([^#?&;]\\{-\\}\\)"', 'g')
7713       let pattern = '^' . substitute(pattern, '^https\=:', 'https\\=:', '') . '$'
7714       let target = s:Slash(no_anchor ? substitute(a:target, '#.*', '', '') : a:target)
7715       let values = matchlist(s:Slash(a:target), pattern)[1:-1]
7716       if empty(values)
7717         continue
7718       endif
7719       let kvs = {}
7720       for i in range(len(keys))
7721         let kvs[keys[i]] = values[i]
7722       endfor
7723       if has_key(kvs, 'commit') && has_key(kvs, 'path')
7724         let ref_path = kvs.commit . '/' . kvs.path
7725       elseif has_key(kvs, 'commit') && variant[0] ==# 'tree'
7726         let ref_path = kvs.commit . '/'
7727       elseif has_key(kvs, 'commit')
7728         let ref_path = kvs.commit
7729       else
7730         continue
7731       endif
7732       let rev = s:RemoteRefToLocalRef(repo, remote_url, fugitive#UrlDecode(ref_path))
7733       return [fugitive#Find(rev, repo), empty(rev) ? 0 : +get(kvs, 'line1')]
7734     endfor
7735   endfor
7736   return ['', 0]
7737 endfunction
7739 function! s:ResolveUrl(target, ...) abort
7740   try
7741     let [url, lnum] = call('fugitive#ResolveUrl', [a:target] + a:000)
7742     if !empty(url)
7743       return [url, lnum]
7744     endif
7745   catch
7746   endtry
7747   return [substitute(a:target, '#.*', '', ''), 0]
7748 endfunction
7750 " Section: Maps
7752 let s:ref_header = '\%(Merge\|Rebase\|Upstream\|Pull\|Push\)'
7754 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
7755 function! fugitive#MapCfile(...) abort
7756   exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
7757   let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
7758   if !exists('g:fugitive_no_maps')
7759     call s:Map('n', 'gf',          '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
7760     call s:Map('n', '<C-W>f',     '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
7761     call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
7762     call s:Map('n', '<C-W>gf',  '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
7763     call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<unique>', 1)
7764   endif
7765 endfunction
7767 function! s:ContainingCommit() abort
7768   let commit = s:Owner(@%)
7769   return empty(commit) ? '@' : commit
7770 endfunction
7772 function! s:SquashArgument(...) abort
7773   if &filetype == 'fugitive'
7774     let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze \|^' . s:ref_header . ': \zs\S\+')
7775   elseif has_key(s:temp_files, s:cpath(expand('%:p')))
7776     let commit = matchstr(getline('.'), '\S\@<!\x\{4,\}\S\@!')
7777   else
7778     let commit = s:Owner(@%)
7779   endif
7780   return len(commit) && a:0 ? printf(a:1, commit) : commit
7781 endfunction
7783 function! s:RebaseArgument() abort
7784   return s:SquashArgument(' %s^')
7785 endfunction
7787 function! s:NavigateUp(count) abort
7788   let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
7789   let c = a:count
7790   while c
7791     if rev =~# ':.*/.'
7792       let rev = matchstr(rev, '.*\ze/.\+', '')
7793     elseif rev =~# '.:.'
7794       let rev = matchstr(rev, '^.[^:]*:')
7795     elseif rev =~# '^:'
7796       let rev = '@^{}'
7797     elseif rev =~# ':$'
7798       let rev = rev[0:-2]
7799     else
7800       return rev.'~'.c
7801     endif
7802     let c -= 1
7803   endwhile
7804   return rev
7805 endfunction
7807 function! s:ParseDiffHeader(str) abort
7808   let list = matchlist(a:str, '\Cdiff --git \("\=\w/.*\|/dev/null\) \("\=\w/.*\|/dev/null\)$')
7809   if empty(list)
7810     let list = matchlist(a:str, '\Cdiff --git \("\=[^/].*\|/dev/null\) \("\=[^/].*\|/dev/null\)$')
7811   endif
7812   return [fugitive#Unquote(get(list, 1, '')), fugitive#Unquote(get(list, 2, ''))]
7813 endfunction
7815 function! s:HunkPosition(lnum) abort
7816   let lnum = a:lnum + get({'@': 1, '\': -1}, getline(a:lnum)[0], 0)
7817   let offsets = {' ': -1, '+': 0, '-': 0}
7818   let sigil = getline(lnum)[0]
7819   let line_char = sigil
7820   while has_key(offsets, line_char)
7821     let offsets[line_char] += 1
7822     let lnum -= 1
7823     let line_char = getline(lnum)[0]
7824   endwhile
7825   let starts = matchlist(getline(lnum), '^@@\+[ 0-9,-]* -\(\d\+\)\%(,\d\+\)\= +\(\d\+\)[ ,]')
7826   if empty(starts)
7827     return [0, 0, 0]
7828   endif
7829   return [lnum,
7830         \ sigil ==# '+' ? 0 : starts[1] + offsets[' '] + offsets['-'],
7831         \ sigil ==# '-' ? 0 : starts[2] + offsets[' '] + offsets['+']]
7832 endfunction
7834 function! s:MapMotion(lhs, rhs) abort
7835   let maps = [
7836         \ s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
7837         \ s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
7838         \ s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")]
7839   call filter(maps, '!empty(v:val)')
7840   return join(maps, '|')
7841 endfunction
7843 function! s:MapGitOps(is_ftplugin) abort
7844   let ft = a:is_ftplugin
7845   if &modifiable
7846     return ''
7847   endif
7848   exe s:Map('n', 'c<Space>', ':Git commit<Space>', '', ft)
7849   exe s:Map('n', 'c<CR>', ':Git commit<CR>', '', ft)
7850   exe s:Map('n', 'cv<Space>', ':tab Git commit -v<Space>', '', ft)
7851   exe s:Map('n', 'cv<CR>', ':tab Git commit -v<CR>', '', ft)
7852   exe s:Map('n', 'ca', ':<C-U>Git commit --amend<CR>', '<silent>', ft)
7853   exe s:Map('n', 'cc', ':<C-U>Git commit<CR>', '<silent>', ft)
7854   exe s:Map('n', 'ce', ':<C-U>Git commit --amend --no-edit<CR>', '<silent>', ft)
7855   exe s:Map('n', 'cw', ':<C-U>Git commit --amend --only<CR>', '<silent>', ft)
7856   exe s:Map('n', 'cva', ':<C-U>tab Git commit -v --amend<CR>', '<silent>', ft)
7857   exe s:Map('n', 'cvc', ':<C-U>tab Git commit -v<CR>', '<silent>', ft)
7858   exe s:Map('n', 'cRa', ':<C-U>Git commit --reset-author --amend<CR>', '<silent>', ft)
7859   exe s:Map('n', 'cRe', ':<C-U>Git commit --reset-author --amend --no-edit<CR>', '<silent>', ft)
7860   exe s:Map('n', 'cRw', ':<C-U>Git commit --reset-author --amend --only<CR>', '<silent>', ft)
7861   exe s:Map('n', 'cf', ':<C-U>Git commit --fixup=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7862   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)
7863   exe s:Map('n', 'cs', ':<C-U>Git commit --no-edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7864   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)
7865   exe s:Map('n', 'cA', ':<C-U>Git commit --edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7866   exe s:Map('n', 'c?', ':<C-U>help fugitive_c<CR>', '<silent>', ft)
7868   exe s:Map('n', 'cr<Space>', ':Git revert<Space>', '', ft)
7869   exe s:Map('n', 'cr<CR>', ':Git revert<CR>', '', ft)
7870   exe s:Map('n', 'crc', ':<C-U>Git revert <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>', ft)
7871   exe s:Map('n', 'crn', ':<C-U>Git revert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>', ft)
7872   exe s:Map('n', 'cr?', ':<C-U>help fugitive_cr<CR>', '<silent>', ft)
7874   exe s:Map('n', 'cm<Space>', ':Git merge<Space>', '', ft)
7875   exe s:Map('n', 'cm<CR>', ':Git merge<CR>', '', ft)
7876   exe s:Map('n', 'cmt', ':Git mergetool', '', ft)
7877   exe s:Map('n', 'cm?', ':<C-U>help fugitive_cm<CR>', '<silent>', ft)
7879   exe s:Map('n', 'cz<Space>', ':Git stash<Space>', '', ft)
7880   exe s:Map('n', 'cz<CR>', ':Git stash<CR>', '', ft)
7881   exe s:Map('n', 'cza', ':<C-U>Git stash apply --quiet --index stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7882   exe s:Map('n', 'czA', ':<C-U>Git stash apply --quiet stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7883   exe s:Map('n', 'czp', ':<C-U>Git stash pop --quiet --index stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7884   exe s:Map('n', 'czP', ':<C-U>Git stash pop --quiet stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7885   exe s:Map('n', 'czs', ':<C-U>Git stash push --staged<CR>', '', ft)
7886   exe s:Map('n', 'czv', ':<C-U>exe "Gedit" fugitive#RevParse("stash@{" . v:count . "}")<CR>', '<silent>', ft)
7887   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)
7888   exe s:Map('n', 'czz', ':<C-U>Git stash push <C-R>=v:count > 1 ? " --all" : v:count ? " --include-untracked" : ""<CR><CR>', '', ft)
7889   exe s:Map('n', 'cz?', ':<C-U>help fugitive_cz<CR>', '<silent>', ft)
7891   exe s:Map('n', 'co<Space>', ':Git checkout<Space>', '', ft)
7892   exe s:Map('n', 'co<CR>', ':Git checkout<CR>', '', ft)
7893   exe s:Map('n', 'coo', ':<C-U>Git checkout <C-R>=substitute(<SID>SquashArgument(),"^$",get(<SID>TempState(),"filetype","") ==# "git" ? expand("<cfile>") : "","")<CR> --<CR>', '', ft)
7894   exe s:Map('n', 'co?', ':<C-U>help fugitive_co<CR>', '<silent>', ft)
7896   exe s:Map('n', 'cb<Space>', ':Git branch<Space>', '', ft)
7897   exe s:Map('n', 'cb<CR>', ':Git branch<CR>', '', ft)
7898   exe s:Map('n', 'cb?', ':<C-U>help fugitive_cb<CR>', '<silent>', ft)
7900   exe s:Map('n', 'r<Space>', ':Git rebase<Space>', '', ft)
7901   exe s:Map('n', 'r<CR>', ':Git rebase<CR>', '', ft)
7902   exe s:Map('n', 'ri', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>', ft)
7903   exe s:Map('n', 'rf', ':<C-U>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>', ft)
7904   exe s:Map('n', 'ru', ':<C-U>Git rebase --interactive @{upstream}<CR>', '<silent>', ft)
7905   exe s:Map('n', 'rp', ':<C-U>Git rebase --interactive @{push}<CR>', '<silent>', ft)
7906   exe s:Map('n', 'rw', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>', '<silent>', ft)
7907   exe s:Map('n', 'rm', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>', '<silent>', ft)
7908   exe s:Map('n', 'rd', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7909   exe s:Map('n', 'rk', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7910   exe s:Map('n', 'rx', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7911   exe s:Map('n', 'rr', ':<C-U>Git rebase --continue<CR>', '<silent>', ft)
7912   exe s:Map('n', 'rs', ':<C-U>Git rebase --skip<CR>', '<silent>', ft)
7913   exe s:Map('n', 're', ':<C-U>Git rebase --edit-todo<CR>', '<silent>', ft)
7914   exe s:Map('n', 'ra', ':<C-U>Git rebase --abort<CR>', '<silent>', ft)
7915   exe s:Map('n', 'r?', ':<C-U>help fugitive_r<CR>', '<silent>', ft)
7916 endfunction
7918 function! fugitive#MapJumps(...) abort
7919   if !&modifiable
7920     if get(b:, 'fugitive_type', '') ==# 'blob'
7921       let blame_tail = '<C-R>=v:count ? " --reverse" : ""<CR><CR>'
7922       exe s:Map('n', '<2-LeftMouse>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
7923       exe s:Map('n', '<CR>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
7924       exe s:Map('n', 'o',    ':<C-U>0,1Git blame' . blame_tail, '<silent>')
7925       exe s:Map('n', 'p',    ':<C-U>0,1Git! blame' . blame_tail, '<silent>')
7926       if has('patch-7.4.1898')
7927         exe s:Map('n', 'gO',   ':<C-U>vertical 0,1Git blame' . blame_tail, '<silent>')
7928         exe s:Map('n', 'O',    ':<C-U>tab 0,1Git blame' . blame_tail, '<silent>')
7929       else
7930         exe s:Map('n', 'gO',   ':<C-U>0,4Git blame' . blame_tail, '<silent>')
7931         exe s:Map('n', 'O',    ':<C-U>0,5Git blame' . blame_tail, '<silent>')
7932       endif
7934       call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
7935       call s:Map('n', 'dd', ":<C-U>call fugitive#DiffClose()<Bar>Gdiffsplit!<CR>", '<silent>')
7936       call s:Map('n', 'dh', ":<C-U>call fugitive#DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
7937       call s:Map('n', 'ds', ":<C-U>call fugitive#DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
7938       call s:Map('n', 'dv', ":<C-U>call fugitive#DiffClose()<Bar>Gvdiffsplit!<CR>", '<silent>')
7939       call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
7941     else
7942       call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
7943       call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
7944       call s:Map('n', 'o',    ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
7945       call s:Map('n', 'gO',   ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
7946       call s:Map('n', 'O',    ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
7947       call s:Map('n', 'p',    ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
7949       if !exists('g:fugitive_no_maps')
7950         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>')
7951         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>')
7952       endif
7953       call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
7954       call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
7955       call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
7956       call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
7957       call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
7958       call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
7959       call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
7960       call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
7961       call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
7962       call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
7963       call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
7964       call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
7965       call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
7966       call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
7967       call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
7968       call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
7969     endif
7970     call s:Map('n', 'S',    ':<C-U>echoerr "Use gO"<CR>', '<silent><unique>')
7971     call s:Map('n', 'dq', ":<C-U>call fugitive#DiffClose()<CR>", '<silent>')
7972     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>')
7973     call s:Map('n', 'P',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
7974     call s:Map('n', '~',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
7975     call s:Map('n', 'C',     ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
7976     call s:Map('n', 'cp',    ":<C-U>echoerr 'Use gC'<CR>", '<silent><unique>')
7977     call s:Map('n', 'gC',    ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
7978     call s:Map('n', 'gc',    ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
7979     call s:Map('n', 'gi',    ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
7980     call s:Map('x', 'gi',    ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
7982     call s:Map('n', '.',     ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
7983     call s:Map('x', '.',     ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
7984     call s:Map('n', 'g?',    ":<C-U>help fugitive-map<CR>", '<silent>')
7985     call s:Map('n', '<F1>',  ":<C-U>help fugitive-map<CR>", '<silent>')
7986   endif
7988   let old_browsex = maparg('<Plug>NetrwBrowseX', 'n')
7989   let new_browsex = substitute(old_browsex, '\Cnetrw#CheckIfRemote(\%(netrw#GX()\)\=)', '0', 'g')
7990   let new_browsex = substitute(new_browsex, 'netrw#GX()\|expand((exists("g:netrw_gx")? g:netrw_gx : ''<cfile>''))', 'fugitive#GX()', 'g')
7991   if new_browsex !=# old_browsex
7992     exe 'nnoremap <silent> <buffer> <Plug>NetrwBrowseX' new_browsex
7993   endif
7994   call s:MapGitOps(0)
7995 endfunction
7997 function! fugitive#GX() abort
7998   try
7999     let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'git' ? s:cfile() : []
8000     if len(results) && len(results[0])
8001       return FugitiveReal(s:Generate(results[0]))
8002     endif
8003   catch /^fugitive:/
8004   endtry
8005   return expand(get(g:, 'netrw_gx', expand('<cfile>')))
8006 endfunction
8008 function! s:CfilePorcelain(...) abort
8009   let tree = s:Tree()
8010   if empty(tree)
8011     return ['']
8012   endif
8013   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
8014   let info = s:StageInfo()
8015   let line = getline('.')
8016   if len(info.sigil) && len(info.section) && len(info.paths)
8017     if info.section ==# 'Unstaged' && info.sigil !=# '-'
8018       return [lead . info.relative[0], info.offset, 'normal!zv']
8019     elseif info.section ==# 'Staged' && info.sigil ==# '-'
8020       return ['@:' . info.relative[0], info.offset, 'normal!zv']
8021     else
8022       return [':0:' . info.relative[0], info.offset, 'normal!zv']
8023     endif
8024   elseif len(info.paths)
8025     return [lead . info.relative[0]]
8026   elseif len(info.commit)
8027     return [info.commit]
8028   elseif line =~# '^' . s:ref_header . ': \|^Head: '
8029     return [matchstr(line, ' \zs.*')]
8030   else
8031     return ['']
8032   endif
8033 endfunction
8035 function! fugitive#PorcelainCfile() abort
8036   let file = fugitive#Find(s:CfilePorcelain()[0])
8037   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
8038 endfunction
8040 function! s:StatusCfile(...) abort
8041   let tree = s:Tree()
8042   if empty(tree)
8043     return []
8044   endif
8045   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
8046   if getline('.') =~# '^.\=\trenamed:.* -> '
8047     return [lead . matchstr(getline('.'),' -> \zs.*')]
8048   elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
8049     return [lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')]
8050   elseif getline('.') =~# '^.\=\t.'
8051     return [lead . matchstr(getline('.'),'\t\zs.*')]
8052   elseif getline('.') =~# ': needs merge$'
8053     return [lead . matchstr(getline('.'),'.*\ze: needs merge$')]
8054   elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
8055     return ['HEAD']
8056   elseif getline('.') =~# '^\%(. \)\=On branch '
8057     return ['refs/heads/'.getline('.')[12:]]
8058   elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
8059     return [matchstr(getline('.'),"'\\zs\\S\\+\\ze'")]
8060   else
8061     return []
8062   endif
8063 endfunction
8065 function! fugitive#MessageCfile() abort
8066   let file = fugitive#Find(get(s:StatusCfile(), 0, ''))
8067   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
8068 endfunction
8070 function! s:BranchCfile(result) abort
8071   return matchstr(getline('.'), '^. \zs\S\+')
8072 endfunction
8074 let s:diff_header_pattern = '^diff --git \%("\=[abciow12]/.*\|/dev/null\) \%("\=[abciow12]/.*\|/dev/null\)$'
8075 function! s:cfile() abort
8076   let temp_state = s:TempState()
8077   let name = substitute(get(get(temp_state, 'args', []), 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
8078   if exists('*s:' . name . 'Cfile')
8079     let cfile = s:{name}Cfile(temp_state)
8080     if !empty(cfile)
8081       return type(cfile) == type('') ? [cfile] : cfile
8082     endif
8083   endif
8084   if empty(FugitiveGitDir())
8085     return []
8086   endif
8087   try
8088     let myhash = s:DirRev(@%)[1]
8089     if len(myhash)
8090       try
8091         let myhash = fugitive#RevParse(myhash)
8092       catch /^fugitive:/
8093         let myhash = ''
8094       endtry
8095     endif
8096     if empty(myhash) && get(temp_state, 'filetype', '') ==# 'git'
8097       let lnum = line('.')
8098       while lnum > 0
8099         if getline(lnum) =~# '^\%(commit\|tag\) \w'
8100           let myhash = matchstr(getline(lnum),'^\w\+ \zs\S\+')
8101           break
8102         endif
8103         let lnum -= 1
8104       endwhile
8105     endif
8107     let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
8109     let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
8110           \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
8112     if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
8113       return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
8114     elseif showtree
8115       return [treebase . s:sub(getline('.'),'/$','')]
8117     else
8119       let dcmds = []
8121       " Index
8122       if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
8123         let ref = matchstr(getline('.'),'\x\{40,\}')
8124         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
8125         return [file]
8126       endif
8128       if getline('.') =~# '^ref: '
8129         let ref = strpart(getline('.'),5)
8131       elseif getline('.') =~# '^\%([|/\\_ ]*\*[|/\\_ ]*\)\=commit \x\{40,\}\>'
8132         let ref = matchstr(getline('.'),'\x\{40,\}')
8133         return [ref]
8135       elseif getline('.') =~# '^parent \x\{40,\}\>'
8136         let ref = matchstr(getline('.'),'\x\{40,\}')
8137         let line = line('.')
8138         let parent = 0
8139         while getline(line) =~# '^parent '
8140           let parent += 1
8141           let line -= 1
8142         endwhile
8143         return [ref]
8145       elseif getline('.') =~# '^tree \x\{40,\}$'
8146         let ref = matchstr(getline('.'),'\x\{40,\}')
8147         if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
8148           let ref = myhash.':'
8149         endif
8150         return [ref]
8152       elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
8153         let ref = matchstr(getline('.'),'\x\{40,\}')
8154         let type = matchstr(getline(line('.')+1),'type \zs.*')
8156       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
8157         let ref = s:DirRev(@%)[1]
8159       elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
8160         let ref = matchstr(getline('.'),'\x\{40,\}')
8161         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
8163       elseif getline('.') =~# '^[A-Z]\d*\t\S' && len(myhash)
8164         let files = split(getline('.'), "\t")[1:-1]
8165         let ref = 'b/' . files[-1]
8166         if getline('.') =~# '^D'
8167           let ref = 'a/' . files[0]
8168         elseif getline('.') !~# '^A'
8169           let dcmds = ['', 'Gdiffsplit! >' . myhash . '^:' . fnameescape(files[0])]
8170         endif
8172       elseif getline('.') =~# '^[+-]'
8173         let [header_lnum, old_lnum, new_lnum] = s:HunkPosition(line('.'))
8174         if new_lnum > 0
8175           let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[1]
8176           let dcmds = [new_lnum, 'normal!zv']
8177         elseif old_lnum > 0
8178           let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[0]
8179           let dcmds = [old_lnum, 'normal!zv']
8180         else
8181           let ref = fugitive#Unquote(matchstr(getline('.'), '\C[+-]\{3\} \zs"\=[abciow12]/.*'))
8182         endif
8184       elseif getline('.') =~# '^rename from '
8185         let ref = 'a/'.getline('.')[12:]
8186       elseif getline('.') =~# '^rename to '
8187         let ref = 'b/'.getline('.')[10:]
8189       elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
8190         let diff = getline(search(s:diff_header_pattern, 'bcnW'))
8191         let offset = matchstr(getline('.'), '+\zs\d\+')
8193         let [dref, ref] = s:ParseDiffHeader(diff)
8194         let dcmd = 'Gdiffsplit! +'.offset
8196       elseif getline('.') =~# s:diff_header_pattern
8197         let [dref, ref] = s:ParseDiffHeader(getline('.'))
8198         let dcmd = 'Gdiffsplit!'
8200       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# s:diff_header_pattern
8201         let [dref, ref] = s:ParseDiffHeader(getline(line('.') - '.'))
8202         let dcmd = 'Gdiffsplit!'
8204       elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
8205         let ref = getline('.')
8207       elseif expand('<cword>') =~# '^\x\{7,\}\>'
8208         return [expand('<cword>')]
8210       else
8211         let ref = ''
8212       endif
8214       let prefixes = {
8215             \ '1': '',
8216             \ '2': '',
8217             \ 'b': ':0:',
8218             \ 'i': ':0:',
8219             \ 'o': '',
8220             \ 'w': ''}
8222       if len(myhash)
8223         let prefixes.a = myhash.'^:'
8224         let prefixes.b = myhash.':'
8225       endif
8226       let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
8227       if exists('dref')
8228         let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
8229       endif
8231       if ref ==# '/dev/null'
8232         " Empty blob
8233         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
8234       endif
8236       if exists('dref')
8237         return [ref, dcmd . ' >' . s:fnameescape(dref)] + dcmds
8238       elseif ref != ""
8239         return [ref] + dcmds
8240       endif
8242     endif
8243     return []
8244   endtry
8245 endfunction
8247 function! s:GF(mode) abort
8248   try
8249     let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'gitcommit' ? s:StatusCfile() : s:cfile()
8250   catch /^fugitive:/
8251     return 'echoerr ' . string(v:exception)
8252   endtry
8253   if len(results) > 1
8254     let cmd = 'G' . a:mode .
8255           \ (empty(results[1]) ? '' : ' +' . s:PlusEscape(results[1])) . ' ' .
8256           \ fnameescape(results[0])
8257     let tail = join(map(results[2:-1], '"|" . v:val'), '')
8258     if a:mode ==# 'pedit' && len(tail)
8259       return cmd . '|wincmd P|exe ' . string(tail[1:-1]) . '|wincmd p'
8260     else
8261       return cmd . tail
8262     endif
8263   elseif len(results) && len(results[0])
8264     return 'G' . a:mode . ' ' . s:fnameescape(results[0])
8265   else
8266     return ''
8267   endif
8268 endfunction
8270 function! fugitive#Cfile() abort
8271   let pre = ''
8272   let results = s:cfile()
8273   if empty(results)
8274     if !empty(s:TempState())
8275       let cfile = s:TempDotMap()
8276       if !empty(cfile)
8277         return fnameescape(s:Generate(cfile))
8278       endif
8279     endif
8280     let cfile = expand('<cfile>')
8281     if &includeexpr =~# '\<v:fname\>'
8282       sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
8283     endif
8284     return cfile
8285   elseif len(results) > 1
8286     let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
8287   endif
8288   return pre . fnameescape(s:Generate(results[0]))
8289 endfunction
8291 " Section: Statusline
8293 function! fugitive#Statusline(...) abort
8294   let dir = s:Dir(bufnr(''))
8295   if empty(dir)
8296     return ''
8297   endif
8298   let status = ''
8299   let commit = s:DirCommitFile(@%)[1]
8300   if len(commit)
8301     let status .= ':' . commit[0:6]
8302   endif
8303   let status .= '('.fugitive#Head(7, dir).')'
8304   return '[Git'.status.']'
8305 endfunction
8307 function! fugitive#statusline(...) abort
8308   return fugitive#Statusline()
8309 endfunction
8311 " Section: Folding
8313 function! fugitive#Foldtext() abort
8314   if &foldmethod !=# 'syntax'
8315     return foldtext()
8316   endif
8318   let line_foldstart = getline(v:foldstart)
8319   if line_foldstart =~# '^diff '
8320     let [add, remove] = [-1, -1]
8321     let filename = ''
8322     for lnum in range(v:foldstart, v:foldend)
8323       let line = getline(lnum)
8324       if filename ==# '' && line =~# '^[+-]\{3\} "\=[abciow12]/'
8325         let filename = fugitive#Unquote(line[4:-1])[2:-1]
8326       endif
8327       if line =~# '^+'
8328         let add += 1
8329       elseif line =~# '^-'
8330         let remove += 1
8331       elseif line =~# '^Binary '
8332         let binary = 1
8333       endif
8334     endfor
8335     if filename ==# ''
8336       let filename = fugitive#Unquote(matchstr(line_foldstart, '^diff .\{-\} \zs"\=[abciow12]/\zs.*\ze "\=[abciow12]/'))[2:-1]
8337     endif
8338     if filename ==# ''
8339       let filename = line_foldstart[5:-1]
8340     endif
8341     if exists('binary')
8342       return 'Binary: '.filename
8343     else
8344       return '+-' . v:folddashes . ' ' . (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
8345     endif
8346   elseif line_foldstart =~# '^@@\+ .* @@'
8347     return '+-' . v:folddashes . ' ' . line_foldstart
8348   elseif &filetype ==# 'fugitive' && line_foldstart =~# '^[A-Z][a-z].* (\d\+)$'
8349     let c = +matchstr(line_foldstart, '(\zs\d\+\ze)$')
8350     return '+-' . v:folddashes . printf('%3d item', c) . (c == 1 ? ':  ' : 's: ') . matchstr(line_foldstart, '.*\ze (\d\+)$')
8351   elseif &filetype ==# 'gitcommit' && line_foldstart =~# '^# .*:$'
8352     let lines = getline(v:foldstart, v:foldend)
8353     call filter(lines, 'v:val =~# "^#\t"')
8354     call map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
8355     call map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
8356     return line_foldstart.' '.join(lines, ', ')
8357   endif
8358   return foldtext()
8359 endfunction
8361 function! fugitive#foldtext() abort
8362   return fugitive#Foldtext()
8363 endfunction
8365 " Section: End