Fix backport of @ to old Git
[vim-fugitive.git] / autoload / fugitive.vim
blob86505e5726ca8111e65b37649179aea08be709ba
1 " Location:     autoload/fugitive.vim
2 " Maintainer:   Tim Pope <http://tpo.pe/>
4 if exists('g:autoloaded_fugitive')
5   finish
6 endif
7 let g:autoloaded_fugitive = 1
9 if !exists('g:fugitive_git_executable')
10   let g:fugitive_git_executable = 'git'
11 endif
13 " Section: Utility
15 function! s:function(name) abort
16   return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
17 endfunction
19 function! s:sub(str,pat,rep) abort
20   return substitute(a:str,'\v\C'.a:pat,a:rep,'')
21 endfunction
23 function! s:gsub(str,pat,rep) abort
24   return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
25 endfunction
27 function! s:Uniq(list) abort
28   let i = 0
29   let seen = {}
30   while i < len(a:list)
31     let str = string(a:list[i])
32     if has_key(seen, str)
33       call remove(a:list, i)
34     else
35       let seen[str] = 1
36       let i += 1
37     endif
38   endwhile
39   return a:list
40 endfunction
42 function! s:winshell() abort
43   return has('win32') && &shellcmdflag !~# '^-'
44 endfunction
46 function! s:shellesc(arg) abort
47   if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
48     return a:arg
49   elseif s:winshell()
50     return '"'.s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"').'"'
51   else
52     return shellescape(a:arg)
53   endif
54 endfunction
56 let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
57 function! s:fnameescape(file) abort
58   if exists('*fnameescape')
59     return fnameescape(a:file)
60   else
61     return escape(a:file, s:fnameescape)
62   endif
63 endfunction
65 function! s:throw(string) abort
66   let v:errmsg = 'fugitive: '.a:string
67   throw v:errmsg
68 endfunction
70 function! s:warn(str) abort
71   echohl WarningMsg
72   echomsg a:str
73   echohl None
74   let v:warningmsg = a:str
75 endfunction
77 function! s:Slash(path) abort
78   if exists('+shellslash')
79     return tr(a:path, '\', '/')
80   else
81     return a:path
82   endif
83 endfunction
85 function! s:PlatformSlash(path) abort
86   if exists('+shellslash') && !&shellslash
87     return tr(a:path, '/', '\')
88   else
89     return a:path
90   endif
91 endfunction
93 function! s:Resolve(path) abort
94   let path = resolve(a:path)
95   if has('win32')
96     let path = s:PlatformSlash(fnamemodify(fnamemodify(path, ':h'), ':p') . fnamemodify(path, ':t'))
97   endif
98   return path
99 endfunction
101 function! s:cpath(path, ...) abort
102   if exists('+fileignorecase') && &fileignorecase
103     let path = s:PlatformSlash(tolower(a:path))
104   else
105     let path = s:PlatformSlash(a:path)
106   endif
107   return a:0 ? path ==# s:cpath(a:1) : path
108 endfunction
110 let s:executables = {}
112 function! s:executable(binary) abort
113   if !has_key(s:executables, a:binary)
114     let s:executables[a:binary] = executable(a:binary)
115   endif
116   return s:executables[a:binary]
117 endfunction
119 function! s:map(mode, lhs, rhs, ...) abort
120   let flags = (a:0 ? a:1 : '') . (a:rhs =~# '<Plug>' ? '' : '<script>')
121   let head = a:lhs
122   let tail = ''
123   let keys = get(g:, a:mode.'remap', {})
124   if type(keys) == type([])
125     return
126   endif
127   while !empty(head)
128     if has_key(keys, head)
129       let head = keys[head]
130       if empty(head)
131         return
132       endif
133       break
134     endif
135     let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
136     let head = substitute(head, '<[^<>]*>$\|.$', '', '')
137   endwhile
138   if flags !~# '<unique>' || empty(mapcheck(head.tail, a:mode))
139     exe a:mode.'map <buffer>' flags head.tail a:rhs
140     if a:0 > 1
141       let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
142             \ '|sil! exe "' . a:mode . 'unmap <buffer> ' . head.tail . '"'
143     endif
144   endif
145 endfunction
147 function! s:System(cmd) abort
148   try
149     return system(a:cmd)
150   catch /^Vim\%((\a\+)\)\=:E484:/
151     let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
152     call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
153     call map(opts, 'v:val."=".eval("&".v:val)')
154     call s:throw('failed to run `' . a:cmd . '` with ' . join(opts, ' '))
155   endtry
156 endfunction
158 " Section: Git
160 function! s:UserCommand() abort
161   return get(g:, 'fugitive_git_command', g:fugitive_git_executable)
162 endfunction
164 function! s:Prepare(...) abort
165   return call('fugitive#Prepare', a:000)
166 endfunction
168 let s:git_versions = {}
169 function! fugitive#GitVersion(...) abort
170   if !has_key(s:git_versions, g:fugitive_git_executable)
171     let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), "\\S\\+\\ze\n")
172   endif
173   return s:git_versions[g:fugitive_git_executable]
174 endfunction
176 let s:commondirs = {}
177 function! fugitive#CommonDir(dir) abort
178   if empty(a:dir)
179     return ''
180   endif
181   if !has_key(s:commondirs, a:dir)
182     if getfsize(a:dir . '/HEAD') < 10
183       let s:commondirs[a:dir] = ''
184     elseif filereadable(a:dir . '/commondir')
185       let dir = get(readfile(a:dir . '/commondir', 1), 0, '')
186       if dir =~# '^/\|^\a:/'
187         let s:commondirs[a:dir] = dir
188       else
189         let s:commondirs[a:dir] = simplify(a:dir . '/' . dir)
190       endif
191     else
192       let s:commondirs[a:dir] = a:dir
193     endif
194   endif
195   return s:commondirs[a:dir]
196 endfunction
198 function! s:Tree(...) abort
199   return FugitiveTreeForGitDir(a:0 ? a:1 : get(b:, 'git_dir', ''))
200 endfunction
202 function! s:PreparePathArgs(cmd, dir, literal) abort
203   let literal_supported = fugitive#GitVersion() !~# '^0\|^1\.[1-8]\.'
204   if a:literal && literal_supported
205     call insert(a:cmd, '--literal-pathspecs')
206   endif
207   let split = index(a:cmd, '--')
208   if split < 0
209     return a:cmd
210   endif
211   for i in range(split + 1, len(a:cmd) - 1)
212     if type(a:cmd[i]) == type(0)
213       let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
214     elseif a:literal
215       let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
216     elseif !literal_supported
217       let a:cmd[i] = substitute(a:cmd[i], '^:\%(/\|([^)]*)\)\=:\=', './', '')
218     endif
219   endfor
220   return a:cmd
221 endfunction
223 let s:prepare_env = {
224       \ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
225       \ 'core.editor': 'GIT_EDITOR',
226       \ 'core.askpass': 'GIT_ASKPASS',
227       \ }
228 function! fugitive#Prepare(...) abort
229   if !a:0
230     return g:fugitive_git_executable
231   endif
232   if type(a:1) ==# type([])
233     let cmd = a:000[1:-1] + a:1
234   else
235     let cmd = copy(a:000)
236   endif
237   let pre = ''
238   let i = 0
239   while i < len(cmd)
240     if cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
241       let dir = remove(cmd, 0)
242     elseif type(cmd[i]) ==# type(0)
243       let dir = getbufvar(remove(cmd, i), 'git_dir')
244     elseif cmd[i] ==# '-c' && len(cmd) > i + 1
245       let key = matchstr(cmd[i+1], '^[^=]*')
246       if has_key(s:prepare_env, tolower(key)) || key !~# '\.'
247         let var = get(s:prepare_env, tolower(key), key)
248         let val = matchstr(cmd[i+1], '=\zs.*')
249         if s:winshell()
250           let pre .= 'set ' . var . '=' . s:shellesc(val) . ' & '
251         else
252           let pre = (len(pre) ? pre : 'env ') . var . '=' . s:shellesc(val) . ' '
253         endif
254       endif
255       if fugitive#GitVersion() =~# '^0\|^1\.[1-7]\.' || cmd[i+1] !~# '\.'
256         call remove(cmd, i, i + 1)
257       else
258         let i += 2
259       endif
260     elseif cmd[i] =~# '^--.*pathspecs$'
261       let explicit_pathspec_option = 1
262       if fugitive#GitVersion() =~# '^0\|^1\.[1-8]\.'
263         call remove(cmd, i)
264       else
265         let i += 1
266       endif
267     elseif cmd[i] !~# '^-'
268       break
269     else
270       let i += 1
271     endif
272   endwhile
273   if !exists('dir')
274     let dir = get(b:, 'git_dir', '')
275   endif
276   let tree = s:Tree(dir)
277   call s:PreparePathArgs(cmd, dir, !exists('explicit_pathspec_option'))
278   let args = join(map(copy(cmd), 's:shellesc(v:val)'))
279   if empty(tree) || index(cmd, '--') == len(cmd) - 1
280     let args = s:shellesc('--git-dir=' . dir) . ' ' . args
281   elseif fugitive#GitVersion() =~# '^0\|^1\.[1-8]\.'
282     let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? ' & ' : '; ') . pre
283   else
284     let args = '-C ' . s:shellesc(tree) . ' ' . args
285   endif
286   return pre . g:fugitive_git_executable . ' ' . args
287 endfunction
289 function! s:TreeChomp(...) abort
290   return s:sub(s:System(call('fugitive#Prepare', a:000)), '\n$', '')
291 endfunction
293 function! fugitive#Head(...) abort
294   let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
295   if empty(dir) || !filereadable(dir . '/HEAD')
296     return ''
297   endif
298   let head = readfile(dir . '/HEAD')[0]
299   if head =~# '^ref: '
300     return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
301   elseif head =~# '^\x\{40\}$'
302     let len = a:0 ? a:1 : 0
303     return len < 0 ? head : len ? head[0:len-1] : ''
304   else
305     return ''
306   endif
307 endfunction
309 function! fugitive#RevParse(rev, ...) abort
310   let hash = system(s:Prepare(a:0 ? a:1 : b:git_dir, 'rev-parse', '--verify', a:rev, '--'))[0:-2]
311   if !v:shell_error && hash =~# '^\x\{40\}$'
312     return hash
313   endif
314   call s:throw('rev-parse '.a:rev.': '.hash)
315 endfunction
317 function! fugitive#Config(name, ...) abort
318   let cmd = fugitive#Prepare(a:0 ? a:1 : get(b:, 'git_dir', ''), '--no-literal-pathspecs', 'config', '--get', '--', a:name)
319   let out = matchstr(system(cmd), "[^\n]*")
320   return v:shell_error ? '' : out
321 endfunction
323 function! s:Remote(dir) abort
324   let head = FugitiveHead(0, a:dir)
325   let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
326   let i = 10
327   while remote ==# '.' && i > 0
328     let head = matchstr(fugitive#Config('branch.' . head . '.merge'), 'refs/heads/\zs.*')
329     let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
330     let i -= 1
331   endwhile
332   return remote =~# '^\.\=$' ? 'origin' : remote
333 endfunction
335 function! fugitive#RemoteUrl(...) abort
336   let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
337   let remote = !a:0 || a:1 =~# '^\.\=$' ? s:Remote(dir) : a:1
338   if fugitive#GitVersion() =~# '^[01]\.\|^2\.[0-6]\.'
339     return fugitive#Config('remote.' . remote . '.url')
340   endif
341   let cmd = s:Prepare(dir, 'remote', 'get-url', remote, '--')
342   let out = substitute(system(cmd), "\n$", '', '')
343   return v:shell_error ? '' : out
344 endfunction
346 " Section: Repository Object
348 function! s:add_methods(namespace, method_names) abort
349   for name in a:method_names
350     let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
351   endfor
352 endfunction
354 let s:commands = []
355 function! s:command(definition) abort
356   let s:commands += [a:definition]
357 endfunction
359 function! s:define_commands() abort
360   for command in s:commands
361     exe 'command! -buffer '.command
362   endfor
363 endfunction
365 let s:repo_prototype = {}
366 let s:repos = {}
368 function! fugitive#repo(...) abort
369   let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : FugitiveExtractGitDir(expand('%:p')))
370   if dir !=# ''
371     if has_key(s:repos, dir)
372       let repo = get(s:repos, dir)
373     else
374       let repo = {'git_dir': dir}
375       let s:repos[dir] = repo
376     endif
377     return extend(repo, s:repo_prototype, 'keep')
378   endif
379   call s:throw('not a git repository: '.expand('%:p'))
380 endfunction
382 function! s:repo_dir(...) dict abort
383   return join([self.git_dir]+a:000,'/')
384 endfunction
386 function! s:repo_tree(...) dict abort
387   let dir = s:Tree(self.git_dir)
388   if dir ==# ''
389     call s:throw('no work tree')
390   else
391     return join([dir]+a:000,'/')
392   endif
393 endfunction
395 function! s:repo_bare() dict abort
396   if self.dir() =~# '/\.git$'
397     return 0
398   else
399     return s:Tree(self.git_dir) ==# ''
400   endif
401 endfunction
403 function! s:repo_route(object) dict abort
404   return fugitive#Route(a:object, self.git_dir)
405 endfunction
407 function! s:repo_translate(rev) dict abort
408   return s:Slash(fugitive#Route(substitute(a:rev, '^/', ':(top)', ''), self.git_dir))
409 endfunction
411 function! s:repo_head(...) dict abort
412   return fugitive#Head(a:0 ? a:1 : 0, self.git_dir)
413 endfunction
415 call s:add_methods('repo',['dir','tree','bare','route','translate','head'])
417 function! s:repo_prepare(...) dict abort
418   return call('fugitive#Prepare', [self.git_dir] + a:000)
419 endfunction
421 function! s:repo_git_command(...) dict abort
422   let git = s:UserCommand() . ' --git-dir='.s:shellesc(self.git_dir)
423   return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
424 endfunction
426 function! s:repo_git_chomp(...) dict abort
427   let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
428   let output = git . join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
429   return s:sub(s:System(output),'\n$','')
430 endfunction
432 function! s:repo_git_chomp_in_tree(...) dict abort
433   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
434   let dir = getcwd()
435   try
436     execute cd s:fnameescape(self.tree())
437     return call(self.git_chomp, a:000, self)
438   finally
439     execute cd s:fnameescape(dir)
440   endtry
441 endfunction
443 function! s:repo_rev_parse(rev) dict abort
444   return fugitive#RevParse(a:rev, self.git_dir)
445 endfunction
447 call s:add_methods('repo',['prepare','git_command','git_chomp','git_chomp_in_tree','rev_parse'])
449 function! s:repo_superglob(base) dict abort
450   return map(fugitive#Complete(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
451 endfunction
453 call s:add_methods('repo',['superglob'])
455 function! s:repo_config(name) dict abort
456   return fugitive#Config(a:name, self.git_dir)
457 endfunction
459 function! s:repo_user() dict abort
460   let username = self.config('user.name')
461   let useremail = self.config('user.email')
462   return username.' <'.useremail.'>'
463 endfunction
465 call s:add_methods('repo',['config', 'user'])
467 " Section: File API
469 function! s:DirCommitFile(path) abort
470   let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40\}\|[0-3]\)\(/.*\)\=$')
471   if empty(vals)
472     return ['', '', '']
473   endif
474   return vals[1:3]
475 endfunction
477 function! s:DirRev(url) abort
478   let [dir, commit, file] = s:DirCommitFile(a:url)
479   return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
480 endfunction
482 function! s:Owner(path, ...) abort
483   let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
484   if empty(dir)
485     return ''
486   endif
487   let [pdir, commit, file] = s:DirCommitFile(a:path)
488   if s:cpath(dir, pdir) && commit =~# '^\x\{40\}$'
489     return commit
490   endif
491   let path = fnamemodify(a:path, ':p')
492   if s:cpath(dir . '/', path[0 : len(dir)]) && a:path =~# 'HEAD$'
493     return strpart(path, len(dir) + 1)
494   endif
495   let refs = fugitive#CommonDir(dir) . '/refs'
496   if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
497     return strpart(path, len(refs) - 4)
498   endif
499   return ''
500 endfunction
502 function! fugitive#Real(url) abort
503   if empty(a:url)
504     return ''
505   endif
506   let [dir, commit, file] = s:DirCommitFile(a:url)
507   if len(dir)
508     let tree = s:Tree(dir)
509     return s:PlatformSlash((len(tree) ? tree : dir) . file)
510   endif
511   let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
512   if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
513     let url = {pre}Real(a:url)
514   else
515     let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
516   endif
517   return s:PlatformSlash(empty(url) ? a:url : url)
518 endfunction
520 function! fugitive#Path(url, ...) abort
521   if empty(a:url)
522     return ''
523   endif
524   let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
525   let tree = s:Tree(dir)
526   if !a:0
527     return fugitive#Real(a:url)
528   elseif a:1 =~# '\.$'
529     let path = s:Slash(fugitive#Real(a:url))
530     let cwd = getcwd()
531     let lead = ''
532     while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
533       if s:cpath(cwd . '/', path[0 : len(cwd)])
534         if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
535           break
536         endif
537         return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
538       endif
539       let cwd = fnamemodify(cwd, ':h')
540       let lead .= '../'
541     endwhile
542     return a:1[0:-2] . path
543   endif
544   let url = s:Slash(fnamemodify(a:url, ':p'))
545   if url =~# '/$' && s:Slash(a:url) !~# '/$'
546     let url = url[0:-2]
547   endif
548   let [argdir, commit, file] = s:DirCommitFile(a:url)
549   if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
550     let file = ''
551   elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
552     let file = '/.git'.url[strlen(dir) : -1]
553   elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
554     let file = url[len(tree) : -1]
555   elseif s:cpath(url) ==# s:cpath(tree) || len(argdir) && empty(file)
556     let file = '/'
557   endif
558   if empty(file) && a:1 =~# '^$\|^[.:]/$'
559     return s:Slash(fugitive#Real(a:url))
560   endif
561   return substitute(file, '^/', a:1, '')
562 endfunction
564 function! s:Relative(...) abort
565   return fugitive#Path(@%, a:0 ? a:1 : ':(top)')
566 endfunction
568 function! fugitive#Route(object, ...) abort
569   if type(a:object) == type(0)
570     let name = bufname(a:object)
571     return s:PlatformSlash(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
572   elseif a:object =~# '^[~$]'
573     let prefix = matchstr(a:object, '^[~$]\i*')
574     let owner = expand(prefix)
575     return s:PlatformSlash((len(owner) ? owner : prefix) . strpart(a:object, len(prefix)))
576   elseif s:Slash(a:object) =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
577     return s:PlatformSlash(a:object)
578   elseif s:Slash(a:object) =~# '^\.\.\=\%(/\|$\)'
579     return s:PlatformSlash(simplify(getcwd() . '/' . a:object))
580   endif
581   let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
582   if empty(dir)
583     let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs.*', '', '')
584     let dir = FugitiveExtractGitDir(file)
585     if empty(dir)
586       return fnamemodify(len(file) ? file : a:object, ':p')
587     endif
588   endif
589   let rev = s:Slash(a:object)
590   let tree = s:Tree(dir)
591   let base = len(tree) ? tree : 'fugitive://' . dir . '//0'
592   if rev ==# '.git'
593     let f = len(tree) ? tree . '/.git' : dir
594   elseif rev =~# '^\.git/'
595     let f = substitute(rev, '^\.git', '', '')
596     let cdir = fugitive#CommonDir(dir)
597     if f =~# '^/\.\./\.\.\%(/\|$\)'
598       let f = simplify(len(tree) ? tree . f[3:-1] : dir . f)
599     elseif f =~# '^/\.\.\%(/\|$\)'
600       let f = base . f[3:-1]
601     elseif cdir !=# dir && (
602           \ f =~# '^/\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
603           \ f !~# '^/logs$\|/\w*HEAD$' && getftime(dir . f) < 0 && getftime(cdir . f) >= 0)
604       let f = simplify(cdir . f)
605     else
606       let f = simplify(dir . f)
607     endif
608   elseif rev ==# ':/'
609     let f = base
610   elseif rev =~# '^\.\%(/\|$\)'
611     let f = base . rev[1:-1]
612   elseif rev =~# '^::\%(/\|\a\+\:\)'
613     let f = rev[2:-1]
614   elseif rev =~# '^::\.\.\=\%(/\|$\)'
615     let f = simplify(getcwd() . '/' . rev[2:-1])
616   elseif rev =~# '^::'
617     let f = base . '/' . rev[2:-1]
618   elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
619     let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
620     if s:cpath(base . '/', (f . '/')[0 : len(base)])
621       let f = 'fugitive://' . dir . '//' . +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1)
622     else
623       let altdir = FugitiveExtractGitDir(f)
624       if len(altdir) && !s:cpath(dir, altdir)
625         return fugitive#Route(a:object, altdir)
626       endif
627     endif
628   elseif rev =~# '^:[0-3]:'
629     let f = 'fugitive://' . dir . '//' . rev[1] . '/' . rev[3:-1]
630   elseif rev ==# ':'
631     if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && s:cpath(fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(dir)]) ==# s:cpath(dir . '/') && filereadable($GIT_INDEX_FILE)
632       let f = fnamemodify($GIT_INDEX_FILE, ':p')
633     else
634       let f = dir . '/index'
635     endif
636   elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
637     let f = base . '/' . matchstr(rev, ')\zs.*')
638   elseif rev =~# '^:/\@!'
639     let f = 'fugitive://' . dir . '//0/' . rev[1:-1]
640   else
641     if rev =~# 'HEAD$\|^refs/' && rev !~# ':'
642       let cdir = rev =~# '^refs/' ? fugitive#CommonDir(dir) : dir
643       if filereadable(cdir . '/' . rev)
644         let f = simplify(cdir . '/' . rev)
645       endif
646     endif
647     if !exists('f')
648       let commit = substitute(matchstr(rev, '^[^:]\+\|^:.*'), '^@\%($\|[~^]\|@{\)\@=', 'HEAD', '')
649       let file = substitute(matchstr(rev, '^[^:]\+\zs:.*'), '^:', '/', '')
650       if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
651         let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
652         if s:cpath(base . '/', (file . '/')[0 : len(base)])
653           let file = '/' . strpart(file, len(base) + 1)
654         else
655           let altdir = FugitiveExtractGitDir(file)
656           if len(altdir) && !s:cpath(dir, altdir)
657             return fugitive#Route(a:object, altdir)
658           endif
659           return file
660         endif
661       endif
662       if commit !~# '^[0-9a-f]\{40\}$'
663         let commit = system(s:Prepare(dir, 'rev-parse', '--verify', commit, '--'))[0:-2]
664         let commit = v:shell_error ? '' : commit
665       endif
666       if len(commit)
667         let f = 'fugitive://' . dir . '//' . commit . file
668       else
669         let f = base . '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', '')
670       endif
671     endif
672   endif
673   return s:PlatformSlash(f)
674 endfunction
676 function! s:Generate(rev, ...) abort
677   let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
678   let tree = s:Tree(dir)
679   let object = a:rev
680   if a:rev =~# '^/\.git\%(/\|$\)'
681     let object = a:rev[1:-1]
682   elseif a:rev =~# '^/' && len(tree) && getftime(tree . a:rev) >= 0 && getftime(a:rev) < 0
683     let object = ':(top)' . a:rev[1:-1]
684   endif
685   return fugitive#Route(object, dir)
686 endfunction
688 function! s:DotRelative(path) abort
689   let cwd = getcwd()
690   let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
691   if s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
692     return '.' . strpart(path, len(cwd))
693   endif
694   return a:path
695 endfunction
697 function! fugitive#Object(...) abort
698   let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
699   let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
700   if s:cpath(dir) !=# s:cpath(fdir)
701     let rev = ''
702   endif
703   let tree = s:Tree(dir)
704   if empty(rev) && empty(tree)
705   elseif empty(rev)
706     let rev = fugitive#Path(a:0 ? a:1 : @%, './', dir)
707     let cdir = fugitive#CommonDir(dir)
708     if rev =~# '^\./\.git/refs/\%(tags\|heads\|remotes\)/.\|^\./\.git/\w*HEAD$'
709       let rev = rev[7:-1]
710     elseif s:cpath(cdir . '/refs/', rev[0 : len(cdir)])
711       let rev = strpart(rev, len(cdir)+1)
712     elseif rev =~# '^\./.git\%(/\|$\)'
713       return fnamemodify(a:0 ? a:1 : @%, ':p')
714     endif
715   endif
716   if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
717     return rev
718   else
719     return tree . rev[1:-1]
720   endif
721 endfunction
723 let s:var = '\%(%\|#<\=\d\+\|##\=\)'
724 let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
725 let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
727 function! s:BufName(var) abort
728   if a:var ==# '%'
729     return bufname(get(b:, 'fugitive_blamed_bufnr', ''))
730   elseif a:var =~# '^#\d*$'
731     let nr = getbufvar(+a:var[1:-1], 'fugitive_blamed_bufnr', '')
732     return bufname(nr ? nr : +a:var[1:-1])
733   else
734     return expand(a:var)
735   endif
736 endfunction
738 function! s:ExpandVar(other, var, flags, esc) abort
739   if a:other =~# '^\'
740     return a:other[1:-1]
741   elseif a:other =~# '^!'
742     let buffer = s:BufName(len(a:other) > 1 ? '#'. a:other[1:-1] : '%')
743     let owner = s:Owner(buffer)
744     return len(owner) ? owner : '@'
745   endif
746   let flags = a:flags
747   let file = s:DotRelative(fugitive#Real(s:BufName(a:var)))
748   while len(flags)
749     let flag = matchstr(flags, s:flag)
750     let flags = strpart(flags, len(flag))
751     if flag ==# ':.'
752       let file = s:DotRelative(file)
753     else
754       let file = fnamemodify(file, flag)
755     endif
756   endwhile
757   let file = s:Slash(file)
758   return (len(a:esc) ? shellescape(file) : file)
759 endfunction
761 function! s:Expand(rev) abort
762   if a:rev =~# '^:[0-3]$'
763     let file = a:rev . s:Relative(':')
764   elseif a:rev =~# '^-'
765     let file = 'HEAD^{}' . a:rev[1:-1] . s:Relative(':')
766   elseif a:rev =~# '^@{'
767     let file = 'HEAD' . a:rev. s:Relative(':')
768   elseif a:rev =~# '^\^[0-9~^{]\|^\~[0-9~^]'
769     let commit = substitute(s:DirCommitFile(@%)[1], '^\d\=$', 'HEAD', '')
770     let file = commit . a:rev . s:Relative(':')
771   else
772     let file = a:rev
773   endif
774   return substitute(file,
775         \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
776         \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),"")', 'g')
777 endfunction
779 function! fugitive#Expand(object) abort
780   return substitute(a:object,
781         \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
782         \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
783 endfunction
785 function! s:ShellExpand(cmd) abort
786   return substitute(a:cmd, '\(\\[!#%]\|!\d*\)\|' . s:expand,
787         \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
788 endfunction
790 let s:trees = {}
791 let s:indexes = {}
792 function! s:TreeInfo(dir, commit) abort
793   if a:commit =~# '^:\=[0-3]$'
794     let index = get(s:indexes, a:dir, [])
795     let newftime = getftime(a:dir . '/index')
796     if get(index, 0, -1) < newftime
797       let out = system(fugitive#Prepare(a:dir, 'ls-files', '--stage', '--'))
798       let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
799       if v:shell_error
800         return [{}, -1]
801       endif
802       for line in split(out, "\n")
803         let [info, filename] = split(line, "\t")
804         let [mode, sha, stage] = split(info, '\s\+')
805         let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
806         while filename =~# '/'
807           let filename = substitute(filename, '/[^/]*$', '', '')
808           let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
809         endwhile
810       endfor
811     endif
812     return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
813   elseif a:commit =~# '^\x\{40\}$'
814     if !has_key(s:trees, a:dir)
815       let s:trees[a:dir] = {}
816     endif
817     if !has_key(s:trees[a:dir], a:commit)
818       let ftime = +system(fugitive#Prepare(a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'))
819       if v:shell_error
820         let s:trees[a:dir][a:commit] = [{}, -1]
821         return s:trees[a:dir][a:commit]
822       endif
823       let s:trees[a:dir][a:commit] = [{}, +ftime]
824       let out = system(fugitive#Prepare(a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'))
825       if v:shell_error
826         return s:trees[a:dir][a:commit]
827       endif
828       for line in split(out, "\n")
829         let [info, filename] = split(line, "\t")
830         let [mode, type, sha, size] = split(info, '\s\+')
831         let s:trees[a:dir][a:commit][0][filename] = [ftime, mode, type, sha, +size, filename]
832       endfor
833     endif
834     return s:trees[a:dir][a:commit]
835   endif
836   return [{}, -1]
837 endfunction
839 function! s:PathInfo(url) abort
840   let [dir, commit, file] = s:DirCommitFile(a:url)
841   if empty(dir) || !get(g:, 'fugitive_file_api', 1)
842     return [-1, '000000', '', '', -1]
843   endif
844   let path = substitute(file[1:-1], '/*$', '', '')
845   let [tree, ftime] = s:TreeInfo(dir, commit)
846   let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
847   if empty(entry) || file =~# '/$' && entry[1] !=# 'tree'
848     return [-1, '000000', '', '', -1]
849   else
850     return entry
851   endif
852 endfunction
854 function! fugitive#simplify(url) abort
855   let [dir, commit, file] = s:DirCommitFile(a:url)
856   if empty(dir)
857     return ''
858   endif
859   if file =~# '/\.\.\%(/\|$\)'
860     let tree = s:Tree(dir)
861     if len(tree)
862       let path = simplify(tree . file)
863       if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
864         return s:PlatformSlash(path)
865       endif
866     endif
867   endif
868   return s:PlatformSlash('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
869 endfunction
871 function! fugitive#resolve(url) abort
872   let url = fugitive#simplify(a:url)
873   if url =~? '^fugitive:'
874     return url
875   else
876     return resolve(url)
877   endif
878 endfunction
880 function! fugitive#getftime(url) abort
881   return s:PathInfo(a:url)[0]
882 endfunction
884 function! fugitive#getfsize(url) abort
885   let entry = s:PathInfo(a:url)
886   if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
887     let dir = s:DirCommitFile(a:url)[0]
888     let size = +system(s:Prepare(dir, 'cat-file', '-s', entry[3]))
889     let entry[4] = v:shell_error ? -1 : size
890   endif
891   return entry[4]
892 endfunction
894 function! fugitive#getftype(url) abort
895   return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
896 endfunction
898 function! fugitive#filereadable(url) abort
899   return s:PathInfo(a:url)[2] ==# 'blob'
900 endfunction
902 function! fugitive#filewritable(url) abort
903   let [dir, commit, file] = s:DirCommitFile(a:url)
904   if commit !~# '^\d$' || !filewritable(dir . '/index')
905     return 0
906   endif
907   return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
908 endfunction
910 function! fugitive#isdirectory(url) abort
911   return s:PathInfo(a:url)[2] ==# 'tree'
912 endfunction
914 function! fugitive#getfperm(url) abort
915   let [dir, commit, file] = s:DirCommitFile(a:url)
916   let perm = getfperm(dir)
917   let fperm = s:PathInfo(a:url)[1]
918   if fperm ==# '040000'
919     let fperm = '000755'
920   endif
921   if fperm !~# '[15]'
922     let perm = tr(perm, 'x', '-')
923   endif
924   if fperm !~# '[45]$'
925     let perm = tr(perm, 'rw', '--')
926   endif
927   if commit !~# '^\d$'
928     let perm = tr(perm, 'w', '-')
929   endif
930   return perm ==# '---------' ? '' : perm
931 endfunction
933 function! fugitive#setfperm(url, perm) abort
934   let [dir, commit, file] = s:DirCommitFile(a:url)
935   let entry = s:PathInfo(a:url)
936   let perm = fugitive#getfperm(a:url)
937   if commit !~# '^\d$' || entry[2] !=# 'blob' ||
938       \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
939     return -2
940   endif
941   call system(s:Prepare(dir, 'update-index', '--index-info'),
942         \ (a:perm =~# 'x' ? '000755 ' : '000644 ') . entry[3] . ' ' . commit . "\t" . file[1:-1])
943   return v:shell_error ? -1 : 0
944 endfunction
946 function! s:TempCmd(out, cmd) abort
947   let prefix = ''
948   try
949     let cmd = (type(a:cmd) == type([]) ? call('s:Prepare', a:cmd) : a:cmd)
950     let redir = ' > ' . a:out
951     if s:winshell()
952       let cmd_escape_char = &shellxquote == '(' ?  '^' : '^^^'
953       return s:System('cmd /c "' . prefix . s:gsub(cmd, '[<>]', cmd_escape_char . '&') . redir . '"')
954     elseif &shell =~# 'fish'
955       return s:System(' begin;' . prefix . cmd . redir . ';end ')
956     else
957       return s:System(' (' . prefix . cmd . redir . ') ')
958     endif
959   endtry
960 endfunction
962 if !exists('s:blobdirs')
963   let s:blobdirs = {}
964 endif
965 function! s:BlobTemp(url) abort
966   let [dir, commit, file] = s:DirCommitFile(a:url)
967   if empty(file)
968     return ''
969   endif
970   if !has_key(s:blobdirs, dir)
971     let s:blobdirs[dir] = tempname()
972   endif
973   let tempfile = s:blobdirs[dir] . '/' . commit . file
974   let tempparent = fnamemodify(tempfile, ':h')
975   if !isdirectory(tempparent)
976     call mkdir(tempparent, 'p')
977   endif
978   if commit =~# '^\d$' || !filereadable(tempfile)
979     let rev = s:DirRev(a:url)[1]
980     let command = s:Prepare(dir, 'cat-file', 'blob', rev)
981     call s:TempCmd(tempfile, command)
982     if v:shell_error
983       call delete(tempfile)
984       return ''
985     endif
986   endif
987   return s:Resolve(tempfile)
988 endfunction
990 function! fugitive#readfile(url, ...) abort
991   let entry = s:PathInfo(a:url)
992   if entry[2] !=# 'blob'
993     return []
994   endif
995   let temp = s:BlobTemp(a:url)
996   if empty(temp)
997     return []
998   endif
999   return call('readfile', [temp] + a:000)
1000 endfunction
1002 function! fugitive#writefile(lines, url, ...) abort
1003   let url = type(a:url) ==# type('') ? a:url : ''
1004   let [dir, commit, file] = s:DirCommitFile(url)
1005   let entry = s:PathInfo(url)
1006   if commit =~# '^\d$' && entry[2] !=# 'tree'
1007     let temp = tempname()
1008     if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
1009       call writefile(fugitive#readfile(url, 'b'), temp, 'b')
1010     endif
1011     call call('writefile', [a:lines, temp] + a:000)
1012     let hash = system(s:Prepare(dir, 'hash-object', '-w', temp))[0:-2]
1013     let mode = len(entry[1]) ? entry[1] : '100644'
1014     if !v:shell_error && hash =~# '^\x\{40\}$'
1015       call system(s:Prepare(dir, 'update-index', '--index-info'),
1016             \ mode . ' ' . hash . ' ' . commit . "\t" . file[1:-1])
1017       if !v:shell_error
1018         return 0
1019       endif
1020     endif
1021   endif
1022   return call('writefile', [a:lines, a:url] + a:000)
1023 endfunction
1025 let s:globsubs = {
1026       \ '/**/': '/\%([^./][^/]*/\)*',
1027       \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
1028       \ '**/': '[^/]*\%(/[^./][^/]*\)*',
1029       \ '**': '.*',
1030       \ '/*': '/[^/.][^/]*',
1031       \ '*': '[^/]*',
1032       \ '?': '[^/]'}
1033 function! fugitive#glob(url, ...) abort
1034   let [dirglob, commit, glob] = s:DirCommitFile(a:url)
1035   let append = matchstr(glob, '/*$')
1036   let glob = substitute(glob, '/*$', '', '')
1037   let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\^$]', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
1038   let results = []
1039   for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
1040     if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(dir . '/HEAD')
1041       continue
1042     endif
1043     let files = items(s:TreeInfo(dir, commit)[0])
1044     if len(append)
1045       call filter(files, 'v:val[1][2] ==# "tree"')
1046     endif
1047     call map(files, 'v:val[0]')
1048     call filter(files, 'v:val =~# pattern')
1049     let prepend = 'fugitive://' . dir . '//' . substitute(commit, '^:', '', '') . '/'
1050     call sort(files)
1051     call map(files, 's:PlatformSlash(prepend . v:val . append)')
1052     call extend(results, files)
1053   endfor
1054   if a:0 > 1 && a:2
1055     return results
1056   else
1057     return join(results, "\n")
1058   endif
1059 endfunction
1061 function! fugitive#delete(url, ...) abort
1062   let [dir, commit, file] = s:DirCommitFile(a:url)
1063   if a:0 && len(a:1) || commit !~# '^\d$'
1064     return -1
1065   endif
1066   let entry = s:PathInfo(a:url)
1067   if entry[2] !=# 'blob'
1068     return -1
1069   endif
1070   call system(s:Prepare(dir, 'update-index', '--index-info'),
1071         \ '000000 0000000000000000000000000000000000000000 ' . commit . "\t" . file[1:-1])
1072   return v:shell_error ? -1 : 0
1073 endfunction
1075 " Section: Buffer Object
1077 let s:buffer_prototype = {}
1079 function! fugitive#buffer(...) abort
1080   let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
1081   call extend(buffer, s:buffer_prototype, 'keep')
1082   if buffer.getvar('git_dir') !=# ''
1083     return buffer
1084   endif
1085   call s:throw('not a git repository: '.bufname(buffer['#']))
1086 endfunction
1088 function! s:buffer_getvar(var) dict abort
1089   return getbufvar(self['#'],a:var)
1090 endfunction
1092 function! s:buffer_getline(lnum) dict abort
1093   return get(getbufline(self['#'], a:lnum), 0, '')
1094 endfunction
1096 function! s:buffer_repo() dict abort
1097   return fugitive#repo(self.getvar('git_dir'))
1098 endfunction
1100 function! s:buffer_type(...) dict abort
1101   if !empty(self.getvar('fugitive_type'))
1102     let type = self.getvar('fugitive_type')
1103   elseif fnamemodify(self.spec(),':p') =~# '\.git/refs/\|\.git/\w*HEAD$'
1104     let type = 'head'
1105   elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
1106     let type = 'tree'
1107   elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
1108     let type = 'tree'
1109   elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
1110     let type = 'index'
1111   elseif isdirectory(self.spec())
1112     let type = 'directory'
1113   elseif self.spec() == ''
1114     let type = 'null'
1115   else
1116     let type = 'file'
1117   endif
1118   if a:0
1119     return !empty(filter(copy(a:000),'v:val ==# type'))
1120   else
1121     return type
1122   endif
1123 endfunction
1125 if has('win32')
1127   function! s:buffer_spec() dict abort
1128     let bufname = bufname(self['#'])
1129     let retval = ''
1130     for i in split(bufname,'[^:]\zs\\')
1131       let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
1132     endfor
1133     return s:Slash(fnamemodify(retval,':p'))
1134   endfunction
1136 else
1138   function! s:buffer_spec() dict abort
1139     let bufname = bufname(self['#'])
1140     return s:Slash(bufname == '' ? '' : fnamemodify(bufname,':p'))
1141   endfunction
1143 endif
1145 function! s:buffer_name() dict abort
1146   return self.spec()
1147 endfunction
1149 function! s:buffer_commit() dict abort
1150   return matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs\w*')
1151 endfunction
1153 function! s:buffer_relative(...) dict abort
1154   let rev = matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs.*')
1155   if rev != ''
1156     let rev = s:sub(rev,'\w*','')
1157   elseif s:cpath(self.spec()[0 : len(self.repo().dir())]) ==#
1158         \ s:cpath(self.repo().dir() . '/')
1159     let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
1160   elseif !self.repo().bare() &&
1161         \ s:cpath(self.spec()[0 : len(self.repo().tree())]) ==#
1162         \ s:cpath(self.repo().tree() . '/')
1163     let rev = self.spec()[strlen(self.repo().tree()) : -1]
1164   endif
1165   return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
1166 endfunction
1168 function! s:buffer_path(...) dict abort
1169   if a:0
1170     return self.relative(a:1)
1171   endif
1172   return self.relative()
1173 endfunction
1175 call s:add_methods('buffer',['getvar','getline','repo','type','spec','name','commit','path','relative'])
1177 " Section: Completion
1179 function! s:GlobComplete(lead, pattern) abort
1180   if v:version >= 704
1181     let results = glob(a:lead . a:pattern, 0, 1)
1182   else
1183     let results = split(glob(a:lead . a:pattern), "\n")
1184   endif
1185   call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1186   call map(results, 'v:val[ strlen(a:lead) : -1 ]')
1187   return results
1188 endfunction
1190 function! fugitive#PathComplete(base, ...) abort
1191   let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
1192   let tree = s:Tree(dir) . '/'
1193   let strip = '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\|:(literal)\)'
1194   let base = substitute(a:base, strip, '', '')
1195   if base =~# '^\.git/'
1196     let pattern = s:gsub(base[5:-1], '/', '*&').'*'
1197     let matches = s:GlobComplete(dir . '/', pattern)
1198     let cdir = fugitive#CommonDir(dir)
1199     if len(cdir) && s:cpath(dir) !=# s:cpath(cdir)
1200       call extend(matches, s:GlobComplete(cdir . '/', pattern))
1201     endif
1202     call s:Uniq(matches)
1203     call map(matches, "'.git/' . v:val")
1204   elseif base =~# '^\~/'
1205     let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
1206   elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/\|^:(literal)'
1207     let matches = s:GlobComplete('', base . '*')
1208   elseif len(tree) > 1
1209     let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*')
1210   else
1211     let matches = []
1212   endif
1213   call map(matches, 's:fnameescape(s:Slash(matchstr(a:base, strip) . v:val))')
1214   return matches
1215 endfunction
1217 function! fugitive#Complete(base, ...) abort
1218   let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
1219   let cwd = a:0 == 1 ? s:Tree(dir) : getcwd()
1220   let tree = s:Tree(dir) . '/'
1221   let subdir = ''
1222   if len(tree) > 1 && s:cpath(tree, cwd[0 : len(tree) - 1])
1223     let subdir = strpart(cwd, len(tree)) . '/'
1224   endif
1226   if a:base =~# '^\.\=/\|^:(' || a:base !~# ':'
1227     let results = []
1228     if a:base =~# '^refs/'
1229       let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:Slash(v:val)')
1230     elseif a:base !~# '^\.\=/\|^:('
1231       let heads = ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'MERGE_HEAD', 'refs/']
1232       let heads += sort(split(s:TreeChomp(["rev-parse","--symbolic","--branches","--tags","--remotes"], dir),"\n"))
1233       if filereadable(fugitive#CommonDir(dir) . '/refs/stash')
1234         let heads += ["stash"]
1235         let heads += sort(split(s:TreeChomp(["stash","list","--pretty=format:%gd"], dir),"\n"))
1236       endif
1237       call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1238       let results += heads
1239     endif
1240     call map(results, 's:fnameescape(v:val)')
1241     if !empty(tree)
1242       let results += a:0 == 1 ? fugitive#PathComplete(a:base, dir) : fugitive#PathComplete(a:base)
1243     endif
1244     return results
1246   elseif a:base =~# '^:'
1247     let entries = split(s:TreeChomp(['ls-files','--stage'], dir),"\n")
1248     if a:base =~# ':\./'
1249       call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
1250     endif
1251     call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
1252     if a:base !~# '^:[0-3]\%(:\|$\)'
1253       call filter(entries,'v:val[1] == "0"')
1254       call map(entries,'v:val[2:-1]')
1255     endif
1257   else
1258     let tree = matchstr(a:base, '.*[:/]')
1259     let entries = split(s:TreeChomp(['ls-tree', substitute(tree,  ':\zs\./', '\=subdir', '')], dir),"\n")
1260     call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
1261     call map(entries,'tree.s:sub(v:val,".*\t","")')
1263   endif
1264   call filter(entries, 'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1265   return map(entries, 's:fnameescape(v:val)')
1266 endfunction
1268 " Section: Buffer auto-commands
1270 function! s:ReplaceCmd(cmd, ...) abort
1271   let temp = tempname()
1272   let err = s:TempCmd(temp, a:cmd)
1273   if v:shell_error
1274     call s:throw((len(err) ? err : filereadable(temp) ? join(readfile(temp), ' ') : 'unknown error running ' . a:cmd))
1275   endif
1276   let temp = s:Resolve(temp)
1277   let fn = expand('%:p')
1278   silent exe 'doau BufReadPre '.s:fnameescape(fn)
1279   silent exe 'keepalt file '.temp
1280   try
1281     if a:0
1282       silent noautocmd edit!
1283     else
1284       silent edit!
1285     endif
1286   finally
1287     try
1288       silent exe 'keepalt file '.s:fnameescape(fn)
1289     catch /^Vim\%((\a\+)\)\=:E302:/
1290     endtry
1291     call delete(temp)
1292     if s:cpath(fnamemodify(bufname('$'), ':p'), temp)
1293       silent execute 'bwipeout '.bufnr('$')
1294     endif
1295     silent exe 'doau BufReadPost '.s:fnameescape(fn)
1296   endtry
1297 endfunction
1299 function! fugitive#BufReadStatus() abort
1300   let amatch = s:Slash(expand('%:p'))
1301   if !exists('b:fugitive_display_format')
1302     let b:fugitive_display_format = filereadable(expand('%').'.lock')
1303   endif
1304   let b:fugitive_display_format = b:fugitive_display_format % 2
1305   let b:fugitive_type = 'index'
1306   try
1307     let cmd = [fnamemodify(amatch, ':h')]
1308     setlocal noro ma nomodeline
1309     if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p')) !=# s:cpath(amatch)
1310       let cmd += ['-c', 'GIT_INDEX_FILE=' . amatch]
1311     endif
1312     if b:fugitive_display_format
1313       let cmd += ['ls-files', '--stage']
1314     else
1315       let cmd += [
1316             \ '-c', 'status.displayCommentPrefix=true',
1317             \ '-c', 'color.status=false',
1318             \ '-c', 'status.short=false',
1319             \ 'status']
1320     endif
1321     call s:ReplaceCmd(call('fugitive#Prepare', cmd), 1)
1322     if b:fugitive_display_format
1323       if &filetype !=# 'git'
1324         set filetype=git
1325       endif
1326       set nospell
1327     else
1328       if &filetype !=# 'gitcommit'
1329         set filetype=gitcommit
1330       endif
1331       set foldtext=fugitive#Foldtext()
1332     endif
1333     setlocal readonly nomodifiable nomodified noswapfile
1334     if &bufhidden ==# ''
1335       setlocal bufhidden=delete
1336     endif
1337     call fugitive#MapJumps()
1338     let nowait = v:version >= 704 ? '<nowait>' : ''
1339     nunmap   <buffer>          P
1340     nunmap   <buffer>          ~
1341     nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
1342     nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
1343     exe "nnoremap <buffer> <silent>" nowait "- :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>"
1344     exe "xnoremap <buffer> <silent>" nowait "- :<C-U>silent execute <SID>StageToggle(line(\"'<\"),line(\"'>\"))<CR>"
1345     nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe fugitive#BufReadStatus()<CR>
1346     nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe fugitive#BufReadStatus()<CR>
1347     nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>
1348     nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>:echohl WarningMsg<Bar>echo ':Gstatus cA is deprecated in favor of ce'<Bar>echohl NONE<CR>
1349     nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
1350     nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
1351     nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
1352     nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
1353     nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
1354     nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
1355     nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1356     nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1357     nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1358     nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1359     nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
1360     nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
1361     nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1362     xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1363     nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1364     xnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1365     nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
1366     nnoremap <buffer> <silent> r :<C-U>edit<CR>
1367     nnoremap <buffer> <silent> R :<C-U>edit<CR>
1368     nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
1369     nnoremap <buffer>          . : <C-R>=<SID>fnameescape(<SID>StatusCfile())<CR><Home>
1370     nnoremap <buffer> <silent> g?   :help fugitive-:Gstatus<CR>
1371     nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
1372   catch /^fugitive:/
1373     return 'echoerr v:errmsg'
1374   endtry
1375 endfunction
1377 function! fugitive#FileReadCmd(...) abort
1378   let amatch = a:0 ? a:1 : expand('<amatch>')
1379   let [dir, rev] = s:DirRev(amatch)
1380   let line = a:0 > 1 ? a:2 : line("'[")
1381   if empty(dir)
1382     return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1383   endif
1384   if rev !~# ':'
1385     let cmd = s:Prepare(dir, 'log', '--pretty=format:%B', '-1', rev, '--')
1386   else
1387     let cmd = s:Prepare(dir, 'cat-file', '-p', rev)
1388   endif
1389   return line . 'read !' . escape(cmd, '!#%')
1390 endfunction
1392 function! fugitive#FileWriteCmd(...) abort
1393   let tmp = tempname()
1394   let amatch = a:0 ? a:1 : expand('<amatch>')
1395   let autype = a:0 > 1 ? 'Buf' : 'File'
1396   if exists('#' . autype . 'WritePre')
1397     execute 'doautocmd ' . autype . 'WritePre ' . s:fnameescape(amatch)
1398   endif
1399   try
1400     let [dir, commit, file] = s:DirCommitFile(amatch)
1401     if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1402       return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1403     endif
1404     silent execute "'[,']write !".s:Prepare(dir, 'hash-object', '-w', '--stdin', '--').' > '.tmp
1405     let sha1 = readfile(tmp)[0]
1406     let old_mode = matchstr(system(s:Prepare(dir, 'ls-files', '--stage', '.' . file)), '^\d\+')
1407     if empty(old_mode)
1408       let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1409     endif
1410     let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1411     let error = system(s:Prepare(dir, 'update-index', '--index-info'), info . "\n")
1412     if v:shell_error == 0
1413       setlocal nomodified
1414       if exists('#' . autype . 'WritePost')
1415         execute 'doautocmd ' . autype . 'WritePost ' . s:fnameescape(amatch)
1416       endif
1417       call fugitive#ReloadStatus()
1418       return ''
1419     else
1420       return 'echoerr '.string('fugitive: '.error)
1421     endif
1422   finally
1423     call delete(tmp)
1424   endtry
1425 endfunction
1427 function! fugitive#BufReadCmd(...) abort
1428   let amatch = a:0 ? a:1 : expand('<amatch>')
1429   try
1430     let [dir, rev] = s:DirRev(amatch)
1431     if empty(dir)
1432       return 'echo "Invalid Fugitive URL"'
1433     endif
1434     if rev =~# '^:\d$'
1435       let b:fugitive_type = 'stage'
1436     else
1437       let b:fugitive_type = system(s:Prepare(dir, 'cat-file', '-t', rev))[0:-2]
1438       if v:shell_error && rev =~# '^:0'
1439         let sha = system(s:Prepare(dir, 'write-tree', '--prefix=' . rev[3:-1]))[0:-2]
1440         let b:fugitive_type = 'tree'
1441       endif
1442       if v:shell_error
1443         unlet b:fugitive_type
1444         if rev =~# '^:\d:'
1445           let &readonly = !filewritable(dir . '/index')
1446           return 'silent doautocmd BufNewFile '.s:fnameescape(amatch)
1447         else
1448           setlocal readonly nomodifiable
1449           return ''
1450         endif
1451       elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1452         return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1453       endif
1454       if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1455         let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1456       endif
1457     endif
1459     if b:fugitive_type !=# 'blob'
1460       setlocal nomodeline
1461     endif
1463     setlocal noreadonly modifiable
1464     let pos = getpos('.')
1465     silent keepjumps %delete_
1466     setlocal endofline
1468     try
1469       if b:fugitive_type ==# 'tree'
1470         let b:fugitive_display_format = b:fugitive_display_format % 2
1471         if b:fugitive_display_format
1472           call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1473         else
1474           if !exists('sha')
1475             let sha = system(s:Prepare(dir, 'rev-parse', '--verify', rev, '--'))[0:-2]
1476           endif
1477           call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1478         endif
1479       elseif b:fugitive_type ==# 'tag'
1480         let b:fugitive_display_format = b:fugitive_display_format % 2
1481         if b:fugitive_display_format
1482           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1483         else
1484           call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1485         endif
1486       elseif b:fugitive_type ==# 'commit'
1487         let b:fugitive_display_format = b:fugitive_display_format % 2
1488         if b:fugitive_display_format
1489           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1490         else
1491           call s:ReplaceCmd([dir, 'show', '--no-color', '--pretty=format:tree%x20%T%nparent%x20%P%nauthor%x20%an%x20<%ae>%x20%ad%ncommitter%x20%cn%x20<%ce>%x20%cd%nencoding%x20%e%n%n%s%n%n%b', rev])
1492           keepjumps call search('^parent ')
1493           if getline('.') ==# 'parent '
1494             silent keepjumps delete_
1495           else
1496             silent exe 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
1497           endif
1498           keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1499           if lnum
1500             silent keepjumps delete_
1501           end
1502           silent keepjumps 1,/^diff --git\|\%$/g/\r$/s///
1503           keepjumps 1
1504         endif
1505       elseif b:fugitive_type ==# 'stage'
1506         call s:ReplaceCmd([dir, 'ls-files', '--stage'])
1507       elseif b:fugitive_type ==# 'blob'
1508         call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1509         setlocal nomodeline
1510       endif
1511     finally
1512       keepjumps call setpos('.',pos)
1513       setlocal nomodified noswapfile
1514       if rev !~# '^:.:'
1515         setlocal nomodifiable
1516       else
1517         let &modifiable = b:fugitive_type !=# 'tree'
1518       endif
1519       let &readonly = !&modifiable || !filewritable(dir . '/index')
1520       if &bufhidden ==# ''
1521         setlocal bufhidden=delete
1522       endif
1523       if b:fugitive_type !=# 'blob'
1524         setlocal filetype=git foldmethod=syntax
1525         nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1526         nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1527       else
1528         call fugitive#MapJumps()
1529       endif
1530     endtry
1532     return ''
1533   catch /^fugitive:/
1534     return 'echoerr v:errmsg'
1535   endtry
1536 endfunction
1538 function! fugitive#BufWriteCmd(...) abort
1539   return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
1540 endfunction
1542 function! fugitive#SourceCmd(...) abort
1543   let amatch = a:0 ? a:1 : expand('<amatch>')
1544   let temp = s:BlobTemp(amatch)
1545   if empty(temp)
1546     return 'noautocmd source ' . s:fnameescape(amatch)
1547   endif
1548   if !exists('g:virtual_scriptnames')
1549     let g:virtual_scriptnames = {}
1550   endif
1551   let g:virtual_scriptnames[temp] = amatch
1552   return 'source ' . s:fnameescape(temp)
1553 endfunction
1555 " Section: Temp files
1557 if !exists('s:temp_files')
1558   let s:temp_files = {}
1559 endif
1561 function! s:SetupTemp(file) abort
1562   if has_key(s:temp_files, s:cpath(a:file))
1563     let dict = s:temp_files[s:cpath(a:file)]
1564     let b:git_dir = dict.dir
1565     call extend(b:, {'fugitive_type': 'temp'}, 'keep')
1566     if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
1567       let &l:filetype = dict.filetype
1568     endif
1569     setlocal foldmarker=<<<<<<<,>>>>>>>
1570     setlocal bufhidden=delete nobuflisted
1571     setlocal buftype=nowrite
1572     nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
1573     if getline(1) !~# '^diff '
1574       setlocal nomodifiable
1575     endif
1576     call FugitiveDetect(a:file)
1577   endif
1578   return ''
1579 endfunction
1581 augroup fugitive_temp
1582   autocmd!
1583   autocmd BufNewFile,BufReadPost * exe s:SetupTemp(expand('<amatch>:p'))
1584 augroup END
1586 " Section: :Git
1588 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,'<mods>',<q-args>)")
1590 function! s:Git(bang, mods, args) abort
1591   if a:bang
1592     return s:Edit('edit', 1, a:mods, a:args)
1593   endif
1594   let git = s:UserCommand()
1595   if has('gui_running') && !has('win32')
1596     let git .= ' --no-pager'
1597   endif
1598   let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
1599   let after = matchstr(a:args, '\v\C\\@<!%(\\\\)*\zs\|.*')
1600   let tree = s:Tree()
1601   if has('win32')
1602     let after = '|call fugitive#ReloadStatus()' . after
1603   endif
1604   if exists(':terminal') && has('nvim') && !get(g:, 'fugitive_force_bang_command')
1605     if len(@%)
1606       -tabedit %
1607     else
1608       -tabnew
1609     endif
1610     execute 'lcd' fnameescape(tree)
1611     let exec = escape(git . ' ' . s:ShellExpand(args), '#%')
1612     return 'exe ' . string('terminal ' . exec) . after
1613   else
1614     let cmd = "exe '!'.escape(" . string(git) . " . ' ' . s:ShellExpand(" . string(args) . "),'!#%')"
1615     if s:cpath(tree) !=# s:cpath(getcwd())
1616       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1617       let cmd = 'try|' . cd . ' ' . tree . '|' . cmd . '|finally|' . cd . ' ' . s:fnameescape(getcwd()) . '|endtry'
1618     endif
1619     return cmd . after
1620   endif
1621 endfunction
1623 let s:exec_paths = {}
1624 function! s:Subcommands() abort
1625   if !has_key(s:exec_paths, g:fugitive_git_executable)
1626     let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
1627   endif
1628   let exec_path = s:exec_paths[g:fugitive_git_executable]
1629   return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
1630 endfunction
1632 let s:aliases = {}
1633 function! s:Aliases() abort
1634   if !has_key(s:aliases, b:git_dir)
1635     let s:aliases[b:git_dir] = {}
1636     let lines = split(s:TreeChomp('config','-z','--get-regexp','^alias[.]'),"\1")
1637     for line in v:shell_error ? [] : lines
1638       let s:aliases[b:git_dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
1639     endfor
1640   endif
1641   return s:aliases[b:git_dir]
1642 endfunction
1644 function! s:GitComplete(A, L, P) abort
1645   let pre = strpart(a:L, 0, a:P)
1646   if pre !~# ' [[:alnum:]-]\+ '
1647     let cmds = s:Subcommands()
1648     return filter(sort(cmds+keys(s:Aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
1649   elseif pre =~# ' -- '
1650     return fugitive#PathComplete(a:A, b:git_dir)
1651   else
1652     return fugitive#Complete(a:A, b:git_dir)
1653   endif
1654 endfunction
1656 " Section: :Gcd, :Glcd
1658 function! s:DirComplete(A, L, P) abort
1659   return filter(fugitive#PathComplete(a:A), 'v:val =~# "/$"')
1660 endfunction
1662 function! s:DirArg(path) abort
1663   let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
1664   if path =~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
1665     return path
1666   else
1667     return (empty(s:Tree()) ? b:git_dir : s:Tree()) . '/' . path
1668   endif
1669 endfunction
1671 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd  :exe 'cd<bang>'  s:fnameescape(s:DirArg(<q-args>))")
1672 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe 'lcd<bang>' s:fnameescape(s:DirArg(<q-args>))")
1674 " Section: :Gstatus
1676 call s:command("-bar -bang -range=-1 Gstatus :execute s:Status(<bang>0, <count>, '<mods>')")
1677 augroup fugitive_status
1678   autocmd!
1679   if !has('win32')
1680     autocmd FocusGained,ShellCmdPost * call fugitive#ReloadStatus()
1681     autocmd BufDelete term://* call fugitive#ReloadStatus()
1682   endif
1683 augroup END
1685 function! s:Status(bang, count, mods) abort
1686   try
1687     exe (a:mods ==# '<mods>' ? '' : a:mods) 'Gpedit :'
1688     wincmd P
1689     setlocal foldmethod=syntax foldlevel=1 buftype=nowrite
1690     nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
1691   catch /^fugitive:/
1692     return 'echoerr v:errmsg'
1693   endtry
1694   return ''
1695 endfunction
1697 function! fugitive#ReloadStatus() abort
1698   if exists('s:reloading_status')
1699     return
1700   endif
1701   try
1702     let s:reloading_status = 1
1703     let mytab = tabpagenr()
1704     for tab in [mytab] + range(1,tabpagenr('$'))
1705       for winnr in range(1,tabpagewinnr(tab,'$'))
1706         if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
1707           execute 'tabnext '.tab
1708           if winnr != winnr()
1709             execute winnr.'wincmd w'
1710             let restorewinnr = 1
1711           endif
1712           try
1713             if !&modified
1714               call fugitive#BufReadStatus()
1715             endif
1716           finally
1717             if exists('restorewinnr')
1718               wincmd p
1719             endif
1720             execute 'tabnext '.mytab
1721           endtry
1722         endif
1723       endfor
1724     endfor
1725   finally
1726     unlet! s:reloading_status
1727   endtry
1728 endfunction
1730 function! fugitive#reload_status() abort
1731   return fugitive#ReloadStatus()
1732 endfunction
1734 function! s:stage_info(lnum) abort
1735   let filename = matchstr(getline(a:lnum),'^.\=\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
1736   let lnum = a:lnum
1737   if has('multi_byte_encoding')
1738     let colon = '\%(:\|\%uff1a\)'
1739   else
1740     let colon = ':'
1741   endif
1742   while lnum && getline(lnum) !~# colon.'$'
1743     let lnum -= 1
1744   endwhile
1745   if !lnum
1746     return ['', '']
1747   elseif (getline(lnum+1) =~# '^.\= .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) =~# '^\%(. \)\=Changes to be committed:$'
1748     return [matchstr(filename, colon.' *\zs.*'), 'staged']
1749   elseif (getline(lnum+1) =~# '^.\= .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.'  ') || getline(lnum) =~# '^\(. \)\=Untracked files:$'
1750     return [filename, 'untracked']
1751   elseif getline(lnum+2) =~# '^.\= .*\<git checkout ' || getline(lnum) =~# '\%(. \)\=Changes not staged for commit:$'
1752     return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
1753   elseif getline(lnum+2) =~# '^.\= .*\<git \%(add\|rm\)' || getline(lnum) =~# '\%(. \)\=Unmerged paths:$'
1754     return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
1755   else
1756     return ['', 'unknown']
1757   endif
1758 endfunction
1760 function! s:StageNext(count) abort
1761   for i in range(a:count)
1762     call search('^.\=\t.*','W')
1763   endfor
1764   return '.'
1765 endfunction
1767 function! s:StagePrevious(count) abort
1768   if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
1769     return 'CtrlP '.fnameescape(s:Tree())
1770   else
1771     for i in range(a:count)
1772       call search('^.\=\t.*','Wbe')
1773     endfor
1774     return '.'
1775   endif
1776 endfunction
1778 function! s:StageReloadSeek(target,lnum1,lnum2) abort
1779   let jump = a:target
1780   let f = matchstr(getline(a:lnum1-1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1781   if f !=# '' | let jump = f | endif
1782   let f = matchstr(getline(a:lnum2+1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1783   if f !=# '' | let jump = f | endif
1784   silent! edit!
1785   1
1786   redraw
1787   call search('^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1788 endfunction
1790 function! s:StageUndo() abort
1791   let [filename, section] = s:stage_info(line('.'))
1792   if empty(filename)
1793     return ''
1794   endif
1795   let hash = s:TreeChomp('hash-object', '-w', './' . filename)
1796   if !empty(hash)
1797     if section ==# 'untracked'
1798       call s:TreeChomp('clean', '-f', './' . filename)
1799     elseif section ==# 'unmerged'
1800       call s:TreeChomp('rm', './' . filename)
1801     elseif section ==# 'unstaged'
1802       call s:TreeChomp('checkout', './' . filename)
1803     else
1804       call s:TreeChomp('checkout', 'HEAD^{}', './' . filename)
1805     endif
1806     call s:StageReloadSeek(filename, line('.'), line('.'))
1807     let @" = hash
1808     return 'checktime|redraw|echomsg ' .
1809           \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
1810   endif
1811 endfunction
1813 function! s:StageDiff(diff) abort
1814   let [filename, section] = s:stage_info(line('.'))
1815   if filename ==# '' && section ==# 'staged'
1816     return 'Git! diff --no-ext-diff --cached'
1817   elseif filename ==# ''
1818     return 'Git! diff --no-ext-diff'
1819   elseif filename =~# ' -> '
1820     let [old, new] = split(filename,' -> ')
1821     execute 'Gedit '.s:fnameescape(':0:'.new)
1822     return a:diff.' HEAD:'.s:fnameescape(old)
1823   elseif section ==# 'staged'
1824     execute 'Gedit '.s:fnameescape(':0:'.filename)
1825     return a:diff.' -'
1826   else
1827     execute 'Gedit '.s:fnameescape('/'.filename)
1828     return a:diff
1829   endif
1830 endfunction
1832 function! s:StageDiffEdit() abort
1833   let [filename, section] = s:stage_info(line('.'))
1834   let arg = (filename ==# '' ? '.' : filename)
1835   if section ==# 'staged'
1836     return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
1837   elseif section ==# 'untracked'
1838     call s:TreeChomp('add', '--intent-to-add', './' . arg)
1839     if arg ==# '.'
1840       silent! edit!
1841       1
1842       if !search('^.*:\n.*\n.\= .*"git checkout \|^\%(# \)=Changes not staged for commit:$','W')
1843         call search(':$','W')
1844       endif
1845     else
1846       call s:StageReloadSeek(arg,line('.'),line('.'))
1847     endif
1848     return ''
1849   else
1850     return 'Git! diff --no-ext-diff '.s:shellesc(arg)
1851   endif
1852 endfunction
1854 function! s:StageToggle(lnum1,lnum2) abort
1855   if a:lnum1 == 1 && a:lnum2 == 1
1856     return 'Gedit .git/|call search("^index$", "wc")'
1857   endif
1858   try
1859     let output = ''
1860     for lnum in range(a:lnum1,a:lnum2)
1861       let [filename, section] = s:stage_info(lnum)
1862       if getline('.') =~# ':$'
1863         if section ==# 'staged'
1864           call s:TreeChomp('reset','-q')
1865           silent! edit!
1866           1
1867           if !search('^.*:\n.\= .*"git add .*\n#\n\|^\%(. \)\=Untracked files:$','W')
1868             call search(':$','W')
1869           endif
1870           return ''
1871         elseif section ==# 'unstaged'
1872           call s:TreeChomp('add','-u')
1873           silent! edit!
1874           1
1875           if !search('^.*:\n\.\= .*"git add .*\n#\n\|^\%( \)=Untracked files:$','W')
1876             call search(':$','W')
1877           endif
1878           return ''
1879         else
1880           call s:TreeChomp('add', '.')
1881           silent! edit!
1882           1
1883           call search(':$','W')
1884           return ''
1885         endif
1886       endif
1887       if filename ==# ''
1888         continue
1889       endif
1890       execute lnum
1891       if section ==# 'staged'
1892         let files_to_unstage = split(filename, ' -> ')
1893         let filename = files_to_unstage[-1]
1894         let cmd = ['reset', '-q'] + map(copy(files_to_unstage), '"./" . v:val')
1895       elseif getline(lnum) =~# '^.\=\tdeleted:'
1896         let cmd = ['rm', './' . filename]
1897       elseif getline(lnum) =~# '^.\=\tmodified:'
1898         let cmd = ['add', './' . filename]
1899       else
1900         let cmd = ['add','-A', './' . filename]
1901       endif
1902       if !exists('first_filename')
1903         let first_filename = filename
1904       endif
1905       let output .= call('s:TreeChomp', cmd)."\n"
1906     endfor
1907     if exists('first_filename')
1908       call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
1909     endif
1910     echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
1911   catch /^fugitive:/
1912     return 'echoerr v:errmsg'
1913   endtry
1914   return 'checktime'
1915 endfunction
1917 function! s:StagePatch(lnum1,lnum2) abort
1918   let add = []
1919   let reset = []
1921   for lnum in range(a:lnum1,a:lnum2)
1922     let [filename, section] = s:stage_info(lnum)
1923     if getline('.') =~# ':$' && section ==# 'staged'
1924       return 'Git reset --patch'
1925     elseif getline('.') =~# ':$' && section ==# 'unstaged'
1926       return 'Git add --patch'
1927     elseif getline('.') =~# ':$' && section ==# 'untracked'
1928       return 'Git add -N .'
1929     elseif filename ==# ''
1930       continue
1931     endif
1932     if !exists('first_filename')
1933       let first_filename = filename
1934     endif
1935     execute lnum
1936     if filename =~ ' -> '
1937       let reset += [split(filename,' -> ')[1]]
1938     elseif section ==# 'staged'
1939       let reset += [filename]
1940     elseif getline(lnum) !~# '^.\=\tdeleted:'
1941       let add += [filename]
1942     endif
1943   endfor
1944   try
1945     if !empty(add)
1946       execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1947     endif
1948     if !empty(reset)
1949       execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1950     endif
1951     if exists('first_filename')
1952       silent! edit!
1953       1
1954       redraw
1955       call search('^.\=\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1956     endif
1957   catch /^fugitive:/
1958     return 'echoerr v:errmsg'
1959   endtry
1960   return 'checktime'
1961 endfunction
1963 " Section: :Gcommit
1965 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit('<mods>', <q-args>)")
1967 function! s:Commit(mods, args, ...) abort
1968   let mods = s:gsub(a:mods ==# '<mods>' ? '' : a:mods, '<tab>', '-tab')
1969   let dir = a:0 ? a:1 : b:git_dir
1970   let tree = s:Tree(dir)
1971   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1972   let cwd = getcwd()
1973   let msgfile = dir . '/COMMIT_EDITMSG'
1974   let outfile = tempname()
1975   let errorfile = tempname()
1976   try
1977     let guioptions = &guioptions
1978     try
1979       if &guioptions =~# '!'
1980         setglobal guioptions-=!
1981       endif
1982       execute cd s:fnameescape(tree)
1983       if s:winshell()
1984         let command = ''
1985         let old_editor = $GIT_EDITOR
1986         let $GIT_EDITOR = 'false'
1987       else
1988         let command = 'env GIT_EDITOR=false '
1989       endif
1990       let args = s:ShellExpand(a:args)
1991       let command .= s:UserCommand() . ' commit ' . args
1992       if &shell =~# 'csh'
1993         noautocmd silent execute '!('.escape(command, '!#%').' > '.outfile.') >& '.errorfile
1994       elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1995         noautocmd execute '!'.command.' 2> '.errorfile
1996       else
1997         noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1998       endif
1999       let error = v:shell_error
2000     finally
2001       execute cd s:fnameescape(cwd)
2002       let &guioptions = guioptions
2003     endtry
2004     if !has('gui_running')
2005       redraw!
2006     endif
2007     if !error
2008       if filereadable(outfile)
2009         for line in readfile(outfile)
2010           echo line
2011         endfor
2012       endif
2013       return ''
2014     else
2015       let errors = readfile(errorfile)
2016       let error = get(errors,-2,get(errors,-1,'!'))
2017       if error =~# 'false''\=\.$'
2018         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
2019         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
2020         let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2021         let cwd = getcwd()
2022         let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
2023         let args = '-F '.s:shellesc(msgfile).' '.args
2024         if args !~# '\%(^\| \)--cleanup\>'
2025           let args = '--cleanup=strip '.args
2026         endif
2027         if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
2028           execute mods 'keepalt edit' s:fnameescape(msgfile)
2029         elseif a:args =~# '\%(^\| \)-\w*v' || mods =~# '\<tab\>'
2030           execute mods 'keepalt -tabedit' s:fnameescape(msgfile)
2031         elseif get(b:, 'fugitive_type', '') ==# 'index'
2032           execute mods 'keepalt edit' s:fnameescape(msgfile)
2033           execute (search('^#','n')+1).'wincmd+'
2034           setlocal nopreviewwindow
2035         else
2036           execute mods 'keepalt split' s:fnameescape(msgfile)
2037         endif
2038         let b:fugitive_commit_arguments = args
2039         setlocal bufhidden=wipe filetype=gitcommit
2040         return '1'
2041       elseif error ==# '!'
2042         return 'Gstatus'
2043       else
2044         call s:throw(empty(error)?join(errors, ' '):error)
2045       endif
2046     endif
2047   catch /^fugitive:/
2048     return 'echoerr v:errmsg'
2049   finally
2050     if exists('old_editor')
2051       let $GIT_EDITOR = old_editor
2052     endif
2053     call delete(outfile)
2054     call delete(errorfile)
2055     call fugitive#ReloadStatus()
2056   endtry
2057 endfunction
2059 function! s:CommitComplete(A,L,P) abort
2060   if a:A =~# '^--fixup=\|^--squash='
2061     let commits = split(s:TreeChomp('log', '--pretty=format:%s', '@{upstream}..'), "\n")
2062     if !v:shell_error
2063       let pre = matchstr(a:A, '^--\w*=') . ':/^'
2064       return map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")')
2065     endif
2066   elseif a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
2067     let args = ['-C', '-F', '-a', '-c', '-e', '-i', '-m', '-n', '-o', '-q', '-s', '-t', '-u', '-v', '--all', '--allow-empty', '--amend', '--author=', '--cleanup=', '--dry-run', '--edit', '--file=', '--fixup=', '--include', '--interactive', '--message=', '--no-verify', '--only', '--quiet', '--reedit-message=', '--reuse-message=', '--signoff', '--squash=', '--template=', '--untracked-files', '--verbose']
2068     return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
2069   else
2070     return fugitive#PathComplete(a:A, b:git_dir)
2071   endif
2072   return []
2073 endfunction
2075 function! s:FinishCommit() abort
2076   let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
2077   if !empty(args)
2078     call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
2079     return s:Commit('', args, getbufvar(+expand('<abuf>'),'git_dir'))
2080   endif
2081   return ''
2082 endfunction
2084 " Section: :Gmerge, :Grebase, :Gpull
2086 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
2087       \ "execute s:Merge('merge', <bang>0, <q-args>)")
2088 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Grebase " .
2089       \ "execute s:Merge('rebase', <bang>0, <q-args>)")
2090 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
2091       \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
2093 function! s:RevisionComplete(A, L, P) abort
2094   return s:TreeChomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
2095         \ . "\nHEAD\nFETCH_HEAD\nMERGE_HEAD\nORIG_HEAD"
2096 endfunction
2098 function! s:RemoteComplete(A, L, P) abort
2099   let remote = matchstr(a:L, ' \zs\S\+\ze ')
2100   if !empty(remote)
2101     let matches = split(s:TreeChomp('ls-remote', remote), "\n")
2102     call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
2103     call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
2104   else
2105     let matches = split(s:TreeChomp('remote'), "\n")
2106   endif
2107   return join(matches, "\n")
2108 endfunction
2110 function! fugitive#Cwindow() abort
2111   if &buftype == 'quickfix'
2112     cwindow
2113   else
2114     botright cwindow
2115     if &buftype == 'quickfix'
2116       wincmd p
2117     endif
2118   endif
2119 endfunction
2121 let s:common_efm = ''
2122       \ . '%+Egit:%.%#,'
2123       \ . '%+Eusage:%.%#,'
2124       \ . '%+Eerror:%.%#,'
2125       \ . '%+Efatal:%.%#,'
2126       \ . '%-G%.%#%\e[K%.%#,'
2127       \ . '%-G%.%#%\r%.%\+'
2129 function! s:Merge(cmd, bang, args) abort
2130   if a:cmd =~# '^rebase' && ' '.a:args =~# ' -i\| --interactive\| --edit-todo'
2131     return 'echoerr "git rebase --interactive not supported"'
2132   endif
2133   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2134   let cwd = getcwd()
2135   let [mp, efm] = [&l:mp, &l:efm]
2136   let had_merge_msg = filereadable(b:git_dir . '/MERGE_MSG')
2137   try
2138     let &l:errorformat = ''
2139           \ . '%-Gerror:%.%#false''.,'
2140           \ . '%-G%.%# ''git commit'' %.%#,'
2141           \ . '%+Emerge:%.%#,'
2142           \ . s:common_efm . ','
2143           \ . '%+ECannot %.%#: You have unstaged changes.,'
2144           \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
2145           \ . '%+EThere is no tracking information for the current branch.,'
2146           \ . '%+EYou are not currently on a branch. Please specify which,'
2147           \ . 'CONFLICT (%m): %f deleted in %.%#,'
2148           \ . 'CONFLICT (%m): Merge conflict in %f,'
2149           \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
2150           \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
2151           \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
2152           \ . '%+ECONFLICT %.%#,'
2153           \ . '%+EKONFLIKT %.%#,'
2154           \ . '%+ECONFLIT %.%#,'
2155           \ . "%+EXUNG \u0110\u1ed8T %.%#,"
2156           \ . "%+E\u51b2\u7a81 %.%#,"
2157           \ . 'U%\t%f'
2158     if a:cmd =~# '^merge' && empty(a:args) &&
2159           \ (had_merge_msg || isdirectory(b:git_dir . '/rebase-apply') ||
2160           \  !empty(s:TreeChomp('diff-files', '--diff-filter=U')))
2161       let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
2162     else
2163       let &l:makeprg = s:sub(s:UserCommand() . ' ' . a:cmd .
2164             \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' || a:cmd =~# '^rebase' ? '' : ' --edit') .
2165             \ ' ' . a:args, ' *$', '')
2166     endif
2167     if !empty($GIT_EDITOR) || has('win32')
2168       let old_editor = $GIT_EDITOR
2169       let $GIT_EDITOR = 'false'
2170     else
2171       let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
2172     endif
2173     execute cd fnameescape(s:Tree())
2174     silent noautocmd make!
2175   catch /^Vim\%((\a\+)\)\=:E211/
2176     let err = v:exception
2177   finally
2178     redraw!
2179     let [&l:mp, &l:efm] = [mp, efm]
2180     if exists('old_editor')
2181       let $GIT_EDITOR = old_editor
2182     endif
2183     execute cd fnameescape(cwd)
2184   endtry
2185   call fugitive#ReloadStatus()
2186   if empty(filter(getqflist(),'v:val.valid'))
2187     if !had_merge_msg && filereadable(b:git_dir . '/MERGE_MSG')
2188       cclose
2189       return 'Gcommit --no-status -n -t '.s:shellesc(b:git_dir . '/MERGE_MSG')
2190     endif
2191   endif
2192   let qflist = getqflist()
2193   let found = 0
2194   for e in qflist
2195     if !empty(e.bufnr)
2196       let found = 1
2197       let e.pattern = '^<<<<<<<'
2198     endif
2199   endfor
2200   call fugitive#Cwindow()
2201   if found
2202     call setqflist(qflist, 'r')
2203     if !a:bang
2204       return 'cfirst'
2205     endif
2206   endif
2207   return exists('err') ? 'echoerr '.string(err) : ''
2208 endfunction
2210 " Section: :Ggrep, :Glog
2212 if !exists('g:fugitive_summary_format')
2213   let g:fugitive_summary_format = '%s'
2214 endif
2216 function! s:GrepComplete(A, L, P) abort
2217   if strpart(a:L, 0, a:P) =~# ' -- '
2218     return fugitive#PathComplete(a:A, b:git_dir)
2219   else
2220     return fugitive#Complete(a:A, b:git_dir)
2221   endif
2222 endfunction
2224 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
2225 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
2226 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Glog :call s:Log('grep',<bang>0,<line1>,<count>,<q-args>)")
2227 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Gllog :call s:Log('lgrep',<bang>0,<line1>,<count>,<q-args>)")
2229 function! s:Grep(cmd,bang,arg) abort
2230   let grepprg = &grepprg
2231   let grepformat = &grepformat
2232   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2233   let dir = getcwd()
2234   try
2235     execute cd s:fnameescape(s:Tree())
2236     let &grepprg = s:UserCommand() . ' --no-pager grep -n --no-color'
2237     let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
2238     exe a:cmd.'! '.escape(s:ShellExpand(matchstr(a:arg, '\v\C.{-}%($|[''" ]\@=\|)@=')), '|#%')
2239     let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
2240     for entry in list
2241       if bufname(entry.bufnr) =~ ':'
2242         let entry.filename = s:Generate(bufname(entry.bufnr))
2243         unlet! entry.bufnr
2244         let changed = 1
2245       elseif a:arg =~# '\%(^\| \)--cached\>'
2246         let entry.filename = s:Generate(':0:'.bufname(entry.bufnr))
2247         unlet! entry.bufnr
2248         let changed = 1
2249       endif
2250     endfor
2251     if a:cmd =~# '^l' && exists('changed')
2252       call setloclist(0, list, 'r')
2253     elseif exists('changed')
2254       call setqflist(list, 'r')
2255     endif
2256     if !a:bang && !empty(list)
2257       return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
2258     else
2259       return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
2260     endif
2261   finally
2262     let &grepprg = grepprg
2263     let &grepformat = grepformat
2264     execute cd s:fnameescape(dir)
2265   endtry
2266 endfunction
2268 function! s:Log(cmd, bang, line1, line2, ...) abort
2269   let args = ' ' . join(a:000, ' ')
2270   let before = substitute(args, ' --\S\@!.*', '', '')
2271   let after = strpart(args, len(before))
2272   let path = s:Relative('/')
2273   let relative = path[1:-1]
2274   if path =~# '^/\.git\%(/\|$\)' || len(after)
2275     let path = ''
2276   endif
2277   if before !~# '\s[^[:space:]-]'
2278     let owner = s:Owner(@%)
2279     if len(owner)
2280       let before .= ' ' . s:shellesc(owner)
2281     endif
2282   endif
2283   if relative =~# '^\.git\%(/\|$\)'
2284     let relative = ''
2285   endif
2286   if len(relative) && a:line2 > 0
2287     let before .= ' -L ' . s:shellesc(a:line1 . ',' . a:line2 . ':' . relative)
2288   elseif len(relative) && (empty(after) || a:line2 == 0)
2289     let after = (len(after) > 3 ? after : ' -- ') . relative
2290   endif
2291   let grepformat = &grepformat
2292   let grepprg = &grepprg
2293   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2294   let dir = getcwd()
2295   try
2296     execute cd s:fnameescape(s:Tree())
2297     let &grepprg = escape(s:UserCommand() . ' --no-pager log --no-color ' .
2298           \ s:shellesc('--pretty=format:fugitive://'.b:git_dir.'//%H'.path.'::'.g:fugitive_summary_format), '%#')
2299     let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
2300     exe a:cmd . (a:bang ? '! ' : ' ') . s:ShellExpand(before . after)
2301   finally
2302     let &grepformat = grepformat
2303     let &grepprg = grepprg
2304     execute cd s:fnameescape(dir)
2305   endtry
2306 endfunction
2308 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
2310 function! s:UsableWin(nr) abort
2311   return a:nr && !getwinvar(a:nr, '&previewwindow') &&
2312         \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
2313 endfunction
2315 function! s:EditParse(args) abort
2316   let pre = []
2317   let args = copy(a:args)
2318   while !empty(args) && args[0] =~# '^+'
2319     call add(pre, ' ' . escape(remove(args, 0), ' |"'))
2320   endwhile
2321   if len(args)
2322     let file = join(args)
2323   elseif empty(expand('%'))
2324     let file = ':'
2325   elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
2326     let file = ':0:%'
2327   else
2328     let file = '%'
2329   endif
2330   return [s:Expand(file), join(pre)]
2331 endfunction
2333 function! s:BlurStatus() abort
2334   if &previewwindow && get(b:,'fugitive_type', '') ==# 'index'
2335     let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
2336     if len(winnrs)
2337       exe winnrs[0].'wincmd w'
2338     elseif winnr('$') == 1
2339       let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
2340       execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
2341     else
2342       rightbelow new
2343     endif
2344     if &diff
2345       let mywinnr = winnr()
2346       for winnr in range(winnr('$'),1,-1)
2347         if winnr != mywinnr && getwinvar(winnr,'&diff')
2348           execute winnr.'wincmd w'
2349           close
2350           if winnr('$') > 1
2351             wincmd p
2352           endif
2353         endif
2354       endfor
2355       diffoff!
2356     endif
2357   endif
2358 endfunction
2360 function! s:Edit(cmd, bang, mods, args, ...) abort
2361   let mods = a:mods ==# '<mods>' ? '' : a:mods
2363   if a:bang
2364     let temp = tempname()
2365     let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2366     let cwd = getcwd()
2367     try
2368       execute cd s:fnameescape(s:Tree())
2369       let git = s:UserCommand()
2370       let args = s:ShellExpand(a:args)
2371       silent! execute '!' . escape(git . ' --no-pager ' . args, '!#%') .
2372             \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
2373     finally
2374       execute cd s:fnameescape(cwd)
2375     endtry
2376     let temp = s:Resolve(temp)
2377     let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'git' }
2378     if a:cmd ==# 'edit'
2379       call s:BlurStatus()
2380     endif
2381     silent execute mods a:cmd temp
2382     call fugitive#ReloadStatus()
2383     return 'redraw|echo ' . string(':!' . git . ' ' . args)
2384   endif
2386   let [file, pre] = s:EditParse(a:000)
2387   try
2388     let file = s:Generate(file)
2389   catch /^fugitive:/
2390     return 'echoerr v:errmsg'
2391   endtry
2392   if file !~# '^\a\a\+:'
2393     let file = s:sub(file, '/$', '')
2394   endif
2395   if a:cmd ==# 'edit'
2396     call s:BlurStatus()
2397   endif
2398   return mods . ' ' . a:cmd . pre . ' ' . s:fnameescape(file)
2399 endfunction
2401 function! s:Read(count, line1, line2, range, bang, mods, args, ...) abort
2402   let mods = a:mods ==# '<mods>' ? '' : a:mods
2403   let after = a:line2
2404   if a:count < 0
2405     let delete = 'silent 1,' . line('$') . 'delete_|'
2406     let after = line('$')
2407   elseif a:range == 2
2408     let delete = 'silent ' . a:line1 . ',' . a:line2 . 'delete_|'
2409   else
2410     let delete = ''
2411   endif
2412   if a:bang
2413     let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2414     let cwd = getcwd()
2415     try
2416       execute cd s:fnameescape(s:Tree())
2417       let git = s:UserCommand()
2418       let args = s:ShellExpand(a:args)
2419       silent execute mods after.'read!' escape(git . ' --no-pager ' . args, '!#%')
2420     finally
2421       execute cd s:fnameescape(cwd)
2422     endtry
2423     execute delete . 'diffupdate'
2424     call fugitive#ReloadStatus()
2425     return 'redraw|echo '.string(':!'.git.' '.args)
2426   endif
2427   let [file, pre] = s:EditParse(a:000)
2428   try
2429     let file = s:Generate(file)
2430   catch /^fugitive:/
2431     return 'echoerr v:errmsg'
2432   endtry
2433   if file =~# '^fugitive:' && after is# 0
2434     return 'exe ' .string(mods . ' ' . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
2435   endif
2436   return mods . ' ' . after . 'read' . pre . ' ' . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
2437 endfunction
2439 function! s:EditRunComplete(A,L,P) abort
2440   if a:L =~# '^\w\+!'
2441     return s:GitComplete(a:A, a:L, a:P)
2442   else
2443     return fugitive#Complete(a:A, a:L, a:P)
2444   endif
2445 endfunction
2447 call s:command("-bar -bang -nargs=*           -complete=customlist,fugitive#Complete Ge       execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2448 call s:command("-bar -bang -nargs=*           -complete=customlist,fugitive#Complete Gedit    execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2449 call s:command("-bar -bang -nargs=*           -complete=customlist,s:EditRunComplete Gpedit   execute s:Edit('pedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2450 call s:command("-bar -bang -nargs=* -range=0  -complete=customlist,s:EditRunComplete Gsplit   execute s:Edit((<count> ? <count> : '').'split', <bang>0, '<mods>', <q-args>, <f-args>)")
2451 call s:command("-bar -bang -nargs=* -range=0  -complete=customlist,s:EditRunComplete Gvsplit  execute s:Edit((<count> ? <count> : '').'vsplit', <bang>0, '<mods>', <q-args>, <f-args>)")
2452 call s:command("-bar -bang -nargs=* -range=0  -complete=customlist,s:EditRunComplete" . (has('patch-7.4.542') ? ' -addr=tabs' : '') . " Gtabedit execute s:Edit((<count> ? <count> : '').'tabedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2453 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:EditRunComplete Gread execute s:Read(<count>, <line1>, <line2>, +'<range>', <bang>0, '<mods>', <q-args>, <f-args>)")
2455 " Section: :Gwrite, :Gwq
2457 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwrite :execute s:Write(<bang>0,<f-args>)")
2458 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gw :execute s:Write(<bang>0,<f-args>)")
2459 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwq :execute s:Wq(<bang>0,<f-args>)")
2461 function! s:Write(force,...) abort
2462   if exists('b:fugitive_commit_arguments')
2463     return 'write|bdelete'
2464   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
2465     return 'wq'
2466   elseif get(b:, 'fugitive_type', '') ==# 'index'
2467     return 'Gcommit'
2468   elseif &buftype ==# 'nowrite' && getline(4) =~# '^+++ '
2469     let filename = getline(4)[6:-1]
2470     setlocal buftype=
2471     silent write
2472     setlocal buftype=nowrite
2473     if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
2474       let err = s:TreeChomp('apply', '--cached', '--reverse', '--', expand('%:p'))
2475     else
2476       let err = s:TreeChomp('apply', '--cached', '--', expand('%:p'))
2477     endif
2478     if err !=# ''
2479       let v:errmsg = split(err,"\n")[0]
2480       return 'echoerr v:errmsg'
2481     elseif a:force
2482       return 'bdelete'
2483     else
2484       return 'Gedit '.fnameescape(filename)
2485     endif
2486   endif
2487   let mytab = tabpagenr()
2488   let mybufnr = bufnr('')
2489   let file = a:0 ? s:Generate(s:Expand(join(a:000, ' '))) : fugitive#Real(@%)
2490   if empty(file)
2491     return 'echoerr '.string('fugitive: cannot determine file path')
2492   endif
2493   if file =~# '^fugitive:'
2494     return 'write' . (a:force ? '! ' : ' ') . s:fnameescape(file)
2495   endif
2496   let always_permitted = s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^0\=$'
2497   if !always_permitted && !a:force && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
2498     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
2499     return 'echoerr v:errmsg'
2500   endif
2501   let treebufnr = 0
2502   for nr in range(1,bufnr('$'))
2503     if fnamemodify(bufname(nr),':p') ==# file
2504       let treebufnr = nr
2505     endif
2506   endfor
2508   if treebufnr > 0 && treebufnr != bufnr('')
2509     let temp = tempname()
2510     silent execute '%write '.temp
2511     for tab in [mytab] + range(1,tabpagenr('$'))
2512       for winnr in range(1,tabpagewinnr(tab,'$'))
2513         if tabpagebuflist(tab)[winnr-1] == treebufnr
2514           execute 'tabnext '.tab
2515           if winnr != winnr()
2516             execute winnr.'wincmd w'
2517             let restorewinnr = 1
2518           endif
2519           try
2520             let lnum = line('.')
2521             let last = line('$')
2522             silent execute '$read '.temp
2523             silent execute '1,'.last.'delete_'
2524             silent write!
2525             silent execute lnum
2526             let did = 1
2527           finally
2528             if exists('restorewinnr')
2529               wincmd p
2530             endif
2531             execute 'tabnext '.mytab
2532           endtry
2533         endif
2534       endfor
2535     endfor
2536     if !exists('did')
2537       call writefile(readfile(temp,'b'),file,'b')
2538     endif
2539   else
2540     execute 'write! '.s:fnameescape(file)
2541   endif
2543   if a:force
2544     let error = s:TreeChomp('add', '--force', '--', file)
2545   else
2546     let error = s:TreeChomp('add', '--', file)
2547   endif
2548   if v:shell_error
2549     let v:errmsg = 'fugitive: '.error
2550     return 'echoerr v:errmsg'
2551   endif
2552   if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
2553     set nomodified
2554   endif
2556   let one = s:Generate(':1:'.file)
2557   let two = s:Generate(':2:'.file)
2558   let three = s:Generate(':3:'.file)
2559   for nr in range(1,bufnr('$'))
2560     let name = fnamemodify(bufname(nr), ':p')
2561     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
2562       execute nr.'bdelete'
2563     endif
2564   endfor
2566   unlet! restorewinnr
2567   let zero = s:Generate(':0:'.file)
2568   silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
2569   for tab in range(1,tabpagenr('$'))
2570     for winnr in range(1,tabpagewinnr(tab,'$'))
2571       let bufnr = tabpagebuflist(tab)[winnr-1]
2572       let bufname = fnamemodify(bufname(bufnr), ':p')
2573       if bufname ==# zero && bufnr != mybufnr
2574         execute 'tabnext '.tab
2575         if winnr != winnr()
2576           execute winnr.'wincmd w'
2577           let restorewinnr = 1
2578         endif
2579         try
2580           let lnum = line('.')
2581           let last = line('$')
2582           silent execute '$read '.s:fnameescape(file)
2583           silent execute '1,'.last.'delete_'
2584           silent execute lnum
2585           set nomodified
2586           diffupdate
2587         finally
2588           if exists('restorewinnr')
2589             wincmd p
2590           endif
2591           execute 'tabnext '.mytab
2592         endtry
2593         break
2594       endif
2595     endfor
2596   endfor
2597   call fugitive#ReloadStatus()
2598   return 'checktime'
2599 endfunction
2601 function! s:Wq(force,...) abort
2602   let bang = a:force ? '!' : ''
2603   if exists('b:fugitive_commit_arguments')
2604     return 'wq'.bang
2605   endif
2606   let result = call(s:function('s:Write'),[a:force]+a:000)
2607   if result =~# '^\%(write\|wq\|echoerr\)'
2608     return s:sub(result,'^write','wq')
2609   else
2610     return result.'|quit'.bang
2611   endif
2612 endfunction
2614 augroup fugitive_commit
2615   autocmd!
2616   autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
2617 augroup END
2619 " Section: :Gpush, :Gfetch
2621 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush  execute s:Dispatch('<bang>', 'push '.<q-args>)")
2622 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
2624 function! s:Dispatch(bang, args)
2625   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2626   let cwd = getcwd()
2627   let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
2628   try
2629     let b:current_compiler = 'git'
2630     let &l:errorformat = s:common_efm
2631     execute cd fnameescape(s:Tree())
2632     let &l:makeprg = substitute(s:UserCommand() . ' ' . a:args, '\s\+$', '', '')
2633     if exists(':Make') == 2
2634       noautocmd Make
2635     else
2636       silent noautocmd make!
2637       redraw!
2638       return 'call fugitive#Cwindow()'
2639     endif
2640     return ''
2641   finally
2642     let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
2643     if empty(cc) | unlet! b:current_compiler | endif
2644     execute cd fnameescape(cwd)
2645   endtry
2646 endfunction
2648 " Section: :Gdiff
2650 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
2651 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
2652 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
2654 augroup fugitive_diff
2655   autocmd!
2656   autocmd BufWinLeave *
2657         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
2658         \   call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
2659         \ endif
2660   autocmd BufWinEnter *
2661         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
2662         \   call s:diffoff() |
2663         \ endif
2664 augroup END
2666 function! s:can_diffoff(buf) abort
2667   return getwinvar(bufwinnr(a:buf), '&diff') &&
2668         \ !empty(getbufvar(a:buf, 'git_dir')) &&
2669         \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
2670 endfunction
2672 function! fugitive#CanDiffoff(buf) abort
2673   return s:can_diffoff(a:buf)
2674 endfunction
2676 function! s:diff_modifier(count) abort
2677   let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
2678   if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
2679     return 'keepalt '
2680   elseif &diffopt =~# 'vertical'
2681     return 'keepalt vert '
2682   elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
2683     return 'keepalt '
2684   else
2685     return 'keepalt vert '
2686   endif
2687 endfunction
2689 function! s:diff_window_count() abort
2690   let c = 0
2691   for nr in range(1,winnr('$'))
2692     let c += getwinvar(nr,'&diff')
2693   endfor
2694   return c
2695 endfunction
2697 function! s:diff_restore() abort
2698   let restore = 'setlocal nodiff noscrollbind'
2699         \ . ' scrollopt=' . &l:scrollopt
2700         \ . (&l:wrap ? ' wrap' : ' nowrap')
2701         \ . ' foldlevel=999'
2702         \ . ' foldmethod=' . &l:foldmethod
2703         \ . ' foldcolumn=' . &l:foldcolumn
2704         \ . ' foldlevel=' . &l:foldlevel
2705         \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
2706   if has('cursorbind')
2707     let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
2708   endif
2709   return restore
2710 endfunction
2712 function! s:diffthis() abort
2713   if !&diff
2714     let w:fugitive_diff_restore = s:diff_restore()
2715     diffthis
2716   endif
2717 endfunction
2719 function! s:diffoff() abort
2720   if exists('w:fugitive_diff_restore')
2721     execute w:fugitive_diff_restore
2722     unlet w:fugitive_diff_restore
2723   else
2724     diffoff
2725   endif
2726 endfunction
2728 function! s:diffoff_all(dir) abort
2729   let curwin = winnr()
2730   for nr in range(1,winnr('$'))
2731     if getwinvar(nr,'&diff')
2732       if nr != winnr()
2733         execute nr.'wincmd w'
2734         let restorewinnr = 1
2735       endif
2736       if exists('b:git_dir') && b:git_dir ==# a:dir
2737         call s:diffoff()
2738       endif
2739     endif
2740   endfor
2741   execute curwin.'wincmd w'
2742 endfunction
2744 function! s:CompareAge(mine, theirs) abort
2745   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
2746   let mine = substitute(a:mine, '^:', '', '')
2747   let theirs = substitute(a:theirs, '^:', '', '')
2748   let my_score    = get(scores, ':'.mine, 0)
2749   let their_score = get(scores, ':'.theirs, 0)
2750   if my_score || their_score
2751     return my_score < their_score ? -1 : my_score != their_score
2752   elseif mine ==# theirs
2753     return 0
2754   endif
2755   let base = s:TreeChomp('merge-base', mine, theirs)
2756   if base ==# mine
2757     return -1
2758   elseif base ==# theirs
2759     return 1
2760   endif
2761   let my_time    = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:mine, '--')
2762   let their_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:theirs, '--')
2763   return my_time < their_time ? -1 : my_time != their_time
2764 endfunction
2766 function! s:Diff(vert,keepfocus,...) abort
2767   let args = copy(a:000)
2768   let post = ''
2769   if get(args, 0) =~# '^+'
2770     let post = remove(args, 0)[1:-1]
2771   endif
2772   let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
2773   let commit = s:DirCommitFile(@%)[1]
2774   let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
2775   if exists(':DiffGitCached')
2776     return 'DiffGitCached'
2777   elseif (empty(args) || args[0] ==# ':') && commit =~# '^[0-1]\=$' && !empty(s:TreeChomp('ls-files', '--unmerged', '--', expand('%:p')))
2778     if v:shell_error
2779       return 'echoerr ' . string("fugitive: error determining merge status of the current buffer")
2780     endif
2781     let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
2782     let nr = bufnr('')
2783     execute 'leftabove '.vert.'split' s:fnameescape(s:Generate(s:Relative(':2:')))
2784     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2785     let nr2 = bufnr('')
2786     call s:diffthis()
2787     exe back
2788     execute 'rightbelow '.vert.'split' s:fnameescape(s:Generate(s:Relative(':3:')))
2789     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2790     let nr3 = bufnr('')
2791     call s:diffthis()
2792     exe back
2793     call s:diffthis()
2794     execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
2795     execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
2796     return post
2797   elseif len(args)
2798     let arg = join(args, ' ')
2799     if arg ==# ''
2800       return post
2801     elseif arg ==# '/'
2802       let file = s:Relative()
2803     elseif arg ==# ':'
2804       let file = s:Relative(':0:')
2805     elseif arg =~# '^:/.'
2806       try
2807         let file = fugitive#RevParse(arg).s:Relative(':')
2808       catch /^fugitive:/
2809         return 'echoerr v:errmsg'
2810       endtry
2811     else
2812       let file = s:Expand(arg)
2813     endif
2814     if file !~# ':' && file !~# '^/' && s:TreeChomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
2815       let file = file.s:Relative(':')
2816     endif
2817   else
2818     let file = empty(commit) ? s:Relative(':0:') : s:Relative()
2819   endif
2820   try
2821     let spec = s:Generate(file)
2822     let restore = s:diff_restore()
2823     if exists('+cursorbind')
2824       setlocal cursorbind
2825     endif
2826     let w:fugitive_diff_restore = restore
2827     if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
2828       execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
2829     else
2830       execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
2831     endif
2832     let &l:readonly = &l:readonly
2833     redraw
2834     let w:fugitive_diff_restore = restore
2835     let winnr = winnr()
2836     if getwinvar('#', '&diff')
2837       exe back
2838       if !a:keepfocus
2839         call feedkeys(winnr."\<C-W>w", 'n')
2840       endif
2841     endif
2842     return post
2843   catch /^fugitive:/
2844     return 'echoerr v:errmsg'
2845   endtry
2846 endfunction
2848 " Section: :Gmove, :Gremove
2850 function! s:Move(force, rename, destination) abort
2851   if a:destination =~# '^\.\.\=\%(/\|$\)'
2852     let destination = simplify(getcwd() . '/' . a:destination)
2853   elseif a:destination =~# '^\a\+:\|^/'
2854     let destination = a:destination
2855   elseif a:destination =~# '^:/:\='
2856     let destination = s:Tree() . substitute(a:destination, '^:/:\=', '', '')
2857   elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\))'
2858     let destination = s:Tree() . matchstr(a:destination, ')\zs.*')
2859   elseif a:destination =~# '^:(literal)'
2860     let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
2861   elseif a:rename
2862     let destination = expand('%:p:s?[\/]$??:h') . '/' . a:destination
2863   else
2864     let destination = s:Tree() . '/' . a:destination
2865   endif
2866   let destination = s:Slash(destination)
2867   if isdirectory(@%)
2868     setlocal noswapfile
2869   endif
2870   let message = call('s:TreeChomp', ['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination])
2871   if v:shell_error
2872     let v:errmsg = 'fugitive: '.message
2873     return 'echoerr v:errmsg'
2874   endif
2875   if isdirectory(destination)
2876     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
2877   endif
2878   call fugitive#ReloadStatus()
2879   if empty(s:DirCommitFile(@%)[1])
2880     if isdirectory(destination)
2881       return 'keepalt edit '.s:fnameescape(destination)
2882     else
2883       return 'keepalt saveas! '.s:fnameescape(destination)
2884     endif
2885   else
2886     return 'file '.s:fnameescape(s:Generate(':0:'.destination))
2887   endif
2888 endfunction
2890 function! s:RenameComplete(A,L,P) abort
2891   if a:A =~# '^[.:]\=/'
2892     return fugitive#PathComplete(a:A)
2893   else
2894     let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
2895     return map(fugitive#PathComplete(pre.a:A), 'strpart(v:val, len(pre))')
2896   endif
2897 endfunction
2899 function! s:Remove(after, force) abort
2900   if s:DirCommitFile(@%)[1] ==# ''
2901     let cmd = ['rm']
2902   elseif s:DirCommitFile(@%)[1] ==# '0'
2903     let cmd = ['rm','--cached']
2904   else
2905     let v:errmsg = 'fugitive: rm not supported here'
2906     return 'echoerr v:errmsg'
2907   endif
2908   if a:force
2909     let cmd += ['--force']
2910   endif
2911   let message = call('s:TreeChomp', cmd + ['--', expand('%:p')])
2912   if v:shell_error
2913     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
2914     return 'echoerr '.string(v:errmsg)
2915   else
2916     call fugitive#ReloadStatus()
2917     return a:after . (a:force ? '!' : '')
2918   endif
2919 endfunction
2921 augroup fugitive_remove
2922   autocmd!
2923   autocmd User Fugitive if s:DirCommitFile(@%)[1] =~# '^0\=$' |
2924         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,fugitive#PathComplete Gmove :execute s:Move(<bang>0,0,<q-args>)" |
2925         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
2926         \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
2927         \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
2928         \ endif
2929 augroup END
2931 " Section: :Gblame
2933 function! s:Keywordprg() abort
2934   let args = ' --git-dir='.escape(b:git_dir,"\\\"' ")
2935   if has('gui_running') && !has('win32')
2936     return s:UserCommand() . ' --no-pager' . args . ' log -1'
2937   else
2938     return s:UserCommand() . args . ' show'
2939   endif
2940 endfunction
2942 augroup fugitive_blame
2943   autocmd!
2944   autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:Keywordprg() | endif
2945   autocmd Syntax fugitiveblame call s:BlameSyntax()
2946   autocmd User Fugitive
2947         \ if get(b:, 'fugitive_type') =~# '^\%(file\|blob\|blame\)$' || filereadable(@%) |
2948         \   exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,'<mods>',[<f-args>])" |
2949         \ endif
2950   autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
2951   autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
2952 augroup END
2954 function! s:linechars(pattern) abort
2955   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
2956   if exists('*synconcealed') && &conceallevel > 1
2957     for col in range(1, chars)
2958       let chars -= synconcealed(line('.'), col)[0]
2959     endfor
2960   endif
2961   return chars
2962 endfunction
2964 function! s:Blame(bang, line1, line2, count, mods, args) abort
2965   if exists('b:fugitive_blamed_bufnr')
2966     return 'bdelete'
2967   endif
2968   try
2969     if empty(s:Relative('/'))
2970       call s:throw('file or blob required')
2971     endif
2972     if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
2973       call s:throw('unsupported option')
2974     endif
2975     call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
2976     let cmd = ['--no-pager', 'blame', '--show-number']
2977     if a:count
2978       let cmd += ['-L', a:line1 . ',' . a:line1]
2979     endif
2980     let cmd += a:args
2981     if s:DirCommitFile(@%)[1] =~# '\D\|..'
2982       let cmd += [s:DirCommitFile(@%)[1]]
2983     else
2984       let cmd += ['--contents', '-']
2985     endif
2986     let cmd += ['--', expand('%:p')]
2987     let basecmd = escape(fugitive#Prepare(cmd), '!#%')
2988     try
2989       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2990       let tree = s:Tree()
2991       if len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
2992         let cwd = getcwd()
2993         execute cd s:fnameescape(tree)
2994       endif
2995       let error = tempname()
2996       let temp = error.'.fugitiveblame'
2997       if &shell =~# 'csh'
2998         silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
2999       else
3000         silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
3001       endif
3002       if exists('l:cwd')
3003         execute cd s:fnameescape(cwd)
3004         unlet cwd
3005       endif
3006       if v:shell_error
3007         call s:throw(join(readfile(error),"\n"))
3008       endif
3009       if a:count
3010         let edit = substitute(a:mods, '^<mods>$', '', '') . get(['edit', 'split', 'pedit'], a:line2 - a:line1, ' split')
3011         return s:BlameCommit(edit, get(readfile(temp), 0, ''))
3012       else
3013         for winnr in range(winnr('$'),1,-1)
3014           call setwinvar(winnr, '&scrollbind', 0)
3015           if exists('+cursorbind')
3016             call setwinvar(winnr, '&cursorbind', 0)
3017           endif
3018           if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
3019             execute winbufnr(winnr).'bdelete'
3020           endif
3021         endfor
3022         let bufnr = bufnr('')
3023         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
3024         if exists('+cursorbind')
3025           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
3026         endif
3027         if &l:wrap
3028           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
3029         endif
3030         if &l:foldenable
3031           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
3032         endif
3033         setlocal scrollbind nowrap nofoldenable
3034         if exists('+cursorbind')
3035           setlocal cursorbind
3036         endif
3037         let top = line('w0') + &scrolloff
3038         let current = line('.')
3039         let temp = s:Resolve(temp)
3040         let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'fugitiveblame', 'args': cmd, 'bufnr': bufnr }
3041         exe 'keepalt leftabove vsplit '.temp
3042         let b:fugitive_blamed_bufnr = bufnr
3043         let b:fugitive_type = 'blame'
3044         let w:fugitive_leave = restore
3045         let b:fugitive_blame_arguments = join(a:args,' ')
3046         execute top
3047         normal! zt
3048         execute current
3049         if exists('+cursorbind')
3050           setlocal cursorbind
3051         endif
3052         setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame buftype=nowrite
3053         if exists('+concealcursor')
3054           setlocal concealcursor=nc conceallevel=2
3055         endif
3056         if exists('+relativenumber')
3057           setlocal norelativenumber
3058         endif
3059         execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
3060         nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
3061         nnoremap <buffer> <silent> g?   :help fugitive-:Gblame<CR>
3062         nnoremap <buffer> <silent> q    :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
3063         nnoremap <buffer> <silent> gq   :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete<Bar>if expand("%:p") =~# "^fugitive:[\\/][\\/]"<Bar>Gedit<Bar>endif','^-1','','')<CR>
3064         nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
3065         nnoremap <buffer> <silent> -    :<C-U>exe <SID>BlameJump('')<CR>
3066         nnoremap <buffer> <silent> P    :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
3067         nnoremap <buffer> <silent> ~    :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
3068         nnoremap <buffer> <silent> i    :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
3069         nnoremap <buffer> <silent> o    :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
3070         nnoremap <buffer> <silent> O    :<C-U>exe <SID>BlameCommit("tabedit")<CR>
3071         nnoremap <buffer> <silent> p    :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft").' pedit', 0, '', matchstr(getline('.'), '\x\+'), matchstr(getline('.'), '\x\+'))<CR>
3072         nnoremap <buffer> <silent> A    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
3073         nnoremap <buffer> <silent> C    :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
3074         nnoremap <buffer> <silent> D    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
3075         redraw
3076         syncbind
3077       endif
3078     finally
3079       if exists('l:cwd')
3080         execute cd s:fnameescape(cwd)
3081       endif
3082     endtry
3083     return ''
3084   catch /^fugitive:/
3085     return 'echoerr v:errmsg'
3086   endtry
3087 endfunction
3089 function! s:BlameCommit(cmd, ...) abort
3090   let line = a:0 ? a:1 : getline('.')
3091   if line =~# '^0\{4,40\} '
3092     return 'echoerr ' . string('Not Committed Yet')
3093   endif
3094   let cmd = s:Edit(a:cmd, 0, '', matchstr(line, '\x\+'), matchstr(line, '\x\+'))
3095   if cmd =~# '^echoerr'
3096     return cmd
3097   endif
3098   let lnum = matchstr(line, ' \zs\d\+\ze\s\+[([:digit:]]')
3099   let path = matchstr(line, '^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
3100   if path ==# ''
3101     let path = fugitive#Path(a:0 ? @% : bufname(b:fugitive_blamed_bufnr), '')
3102   endif
3103   execute cmd
3104   if a:cmd ==# 'pedit'
3105     return ''
3106   endif
3107   if search('^diff .* b/\M'.escape(path,'\').'$','W')
3108     call search('^+++')
3109     let head = line('.')
3110     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
3111       let top = +matchstr(getline('.'),' +\zs\d\+')
3112       let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
3113       if lnum >= top && lnum <= top + len
3114         let offset = lnum - top
3115         if &scrolloff
3116           +
3117           normal! zt
3118         else
3119           normal! zt
3120           +
3121         endif
3122         while offset > 0 && line('.') < line('$')
3123           +
3124           if getline('.') =~# '^[ +]'
3125             let offset -= 1
3126           endif
3127         endwhile
3128         return 'normal! zv'
3129       endif
3130     endwhile
3131     execute head
3132     normal! zt
3133   endif
3134   return ''
3135 endfunction
3137 function! s:BlameJump(suffix) abort
3138   let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
3139   if commit =~# '^0\+$'
3140     let commit = ':0'
3141   endif
3142   let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
3143   let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
3144   if path ==# ''
3145     let path = fugitive#Path(bufname(b:fugitive_blamed_bufnr), '')
3146   endif
3147   let args = b:fugitive_blame_arguments
3148   let offset = line('.') - line('w0')
3149   let bufnr = bufnr('%')
3150   let winnr = bufwinnr(b:fugitive_blamed_bufnr)
3151   if winnr > 0
3152     exe winnr.'wincmd w'
3153   endif
3154   execute 'Gedit' s:fnameescape(commit . a:suffix . ':' . path)
3155   execute lnum
3156   if winnr > 0
3157     exe bufnr.'bdelete'
3158   endif
3159   if exists(':Gblame')
3160     execute 'Gblame '.args
3161     execute lnum
3162     let delta = line('.') - line('w0') - offset
3163     if delta > 0
3164       execute 'normal! '.delta."\<C-E>"
3165     elseif delta < 0
3166       execute 'normal! '.(-delta)."\<C-Y>"
3167     endif
3168     syncbind
3169   endif
3170   return ''
3171 endfunction
3173 let s:hash_colors = {}
3175 function! s:BlameSyntax() abort
3176   let b:current_syntax = 'fugitiveblame'
3177   let conceal = has('conceal') ? ' conceal' : ''
3178   let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
3179   syn match FugitiveblameBoundary "^\^"
3180   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
3181   syn match FugitiveblameHash       "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3182   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3183   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
3184   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
3185   exec 'syn match FugitiveblameLineNumber         " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
3186   exec 'syn match FugitiveblameOriginalFile       " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
3187   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
3188   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
3189   syn match FugitiveblameShort              " \d\+)" contained contains=FugitiveblameLineNumber
3190   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
3191   hi def link FugitiveblameBoundary           Keyword
3192   hi def link FugitiveblameHash               Identifier
3193   hi def link FugitiveblameUncommitted        Ignore
3194   hi def link FugitiveblameTime               PreProc
3195   hi def link FugitiveblameLineNumber         Number
3196   hi def link FugitiveblameOriginalFile       String
3197   hi def link FugitiveblameOriginalLineNumber Float
3198   hi def link FugitiveblameShort              FugitiveblameDelimiter
3199   hi def link FugitiveblameDelimiter          Delimiter
3200   hi def link FugitiveblameNotCommittedYet    Comment
3201   let seen = {}
3202   for lnum in range(1, line('$'))
3203     let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
3204     if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
3205       continue
3206     endif
3207     let seen[hash] = 1
3208     if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
3209           \ && empty(get(s:hash_colors, hash))
3210       let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
3211       let color = csapprox#per_component#Approximate(r, g, b)
3212       if color == 16 && &background ==# 'dark'
3213         let color = 8
3214       endif
3215       let s:hash_colors[hash] = ' ctermfg='.color
3216     else
3217       let s:hash_colors[hash] = ''
3218     endif
3219     exe 'syn match FugitiveblameHash'.hash.'       "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
3220   endfor
3221   call s:RehighlightBlame()
3222 endfunction
3224 function! s:RehighlightBlame() abort
3225   for [hash, cterm] in items(s:hash_colors)
3226     if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
3227       exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
3228     else
3229       exe 'hi link FugitiveblameHash'.hash.' Identifier'
3230     endif
3231   endfor
3232 endfunction
3234 " Section: :Gbrowse
3236 call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,fugitive#Complete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
3238 let s:redirects = {}
3240 function! s:Browse(bang,line1,count,...) abort
3241   try
3242     let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
3243     if a:0
3244       let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
3245       let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
3246     else
3247       let remote = ''
3248       let rev = ''
3249     endif
3250     if rev ==# ''
3251       let rev = s:DirRev(@%)[1]
3252     endif
3253     if rev =~# '^:\=$'
3254       let expanded = s:Relative()
3255     else
3256       let expanded = s:Expand(rev)
3257     endif
3258     let cdir = fugitive#CommonDir(b:git_dir)
3259     for dir in ['tags/', 'heads/', 'remotes/']
3260       if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . dir . expanded)
3261         let expanded = '.git/refs/' . dir . expanded
3262       endif
3263     endfor
3264     let full = s:Generate(expanded)
3265     let commit = ''
3266     if full =~? '^fugitive:'
3267       let [dir, commit, path] = s:DirCommitFile(full)
3268       if commit =~# '^:\=\d$'
3269         let commit = ''
3270       endif
3271       if commit =~ '..'
3272         let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
3273         let branch = matchstr(expanded, '^[^:]*')
3274       else
3275         let type = 'blob'
3276       endif
3277       let path = path[1:-1]
3278     elseif empty(s:Tree())
3279       let path = '.git/' . full[strlen(b:git_dir)+1:-1]
3280       let type = ''
3281     else
3282       let path = full[strlen(s:Tree())+1:-1]
3283       if path =~# '^\.git/'
3284         let type = ''
3285       elseif isdirectory(full)
3286         let type = 'tree'
3287       else
3288         let type = 'blob'
3289       endif
3290     endif
3291     if type ==# 'tree' && !empty(path)
3292       let path = s:sub(path, '/\=$', '/')
3293     endif
3294     if path =~# '^\.git/.*HEAD$' && filereadable(b:git_dir . '/' . path[5:-1])
3295       let body = readfile(b:git_dir . '/' . path[5:-1])[0]
3296       if body =~# '^\x\{40\}$'
3297         let commit = body
3298         let type = 'commit'
3299         let path = ''
3300       elseif body =~# '^ref: refs/'
3301         let path = '.git/' . matchstr(body,'ref: \zs.*')
3302       endif
3303     endif
3305     let merge = ''
3306     if path =~# '^\.git/refs/remotes/.'
3307       if empty(remote)
3308         let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
3309         let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3310       else
3311         let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3312         let path = '.git/refs/heads/'.merge
3313       endif
3314     elseif path =~# '^\.git/refs/heads/.'
3315       let branch = path[16:-1]
3316     elseif !exists('branch')
3317       let branch = FugitiveHead()
3318     endif
3319     if !empty(branch)
3320       let r = fugitive#Config('branch.'.branch.'.remote')
3321       let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
3322       if r ==# '.' && !empty(m)
3323         let r2 = fugitive#Config('branch.'.m.'.remote')
3324         if r2 !~# '^\.\=$'
3325           let r = r2
3326           let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
3327         endif
3328       endif
3329       if empty(remote)
3330         let remote = r
3331       endif
3332       if r ==# '.' || r ==# remote
3333         let merge = m
3334         if path =~# '^\.git/refs/heads/.'
3335           let path = '.git/refs/heads/'.merge
3336         endif
3337       endif
3338     endif
3340     let line1 = a:count > 0 ? a:line1 : 0
3341     let line2 = a:count > 0 ? a:count : 0
3342     if empty(commit) && path !~# '^\.git/'
3343       if a:line1 && !a:count && !empty(merge)
3344         let commit = merge
3345       else
3346         let commit = ''
3347         if len(merge)
3348           let remotehead = cdir . '/refs/remotes/' . remote . '/' . merge
3349           let commit = filereadable(remotehead) ? get(readfile(remotehead), 0, '') : ''
3350           if a:count && !a:0 && commit =~# '^\x\{40\}$'
3351             let blame_list = tempname()
3352             call writefile([commit, ''], blame_list, 'b')
3353             let blame_in = tempname()
3354             silent exe '%write' blame_in
3355             let blame = split(s:TreeChomp('blame', '--contents', blame_in, '-L', a:line1.','.a:count, '-S', blame_list, '-s', '--show-number', './' . path), "\n")
3356             if !v:shell_error
3357               let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
3358               if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
3359                 let line1 = +matchstr(blame[0], blame_regex)
3360                 let line2 = +matchstr(blame[-1], blame_regex)
3361               else
3362                 call s:throw("Can't browse to uncommitted change")
3363               endif
3364             endif
3365           endif
3366         endif
3367       endif
3368       if empty(commit)
3369         let commit = readfile(b:git_dir . '/HEAD', '', 1)[0]
3370       endif
3371       let i = 0
3372       while commit =~# '^ref: ' && i < 10
3373         let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
3374         let i -= 1
3375       endwhile
3376     endif
3378     if empty(remote)
3379       let remote = '.'
3380     endif
3381     let raw = fugitive#RemoteUrl(remote)
3382     if empty(raw)
3383       let raw = remote
3384     endif
3386     if raw =~# '^https\=://' && s:executable('curl')
3387       if !has_key(s:redirects, raw)
3388         let s:redirects[raw] = matchstr(system('curl -I ' .
3389               \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
3390               \ 'Location: \zs\S\+\ze/info/refs?')
3391       endif
3392       if len(s:redirects[raw])
3393         let raw = s:redirects[raw]
3394       endif
3395     endif
3397     let opts = {
3398           \ 'dir': b:git_dir,
3399           \ 'repo': fugitive#repo(),
3400           \ 'remote': raw,
3401           \ 'revision': 'No longer provided',
3402           \ 'commit': commit,
3403           \ 'path': path,
3404           \ 'type': type,
3405           \ 'line1': line1,
3406           \ 'line2': line2}
3408     for Handler in get(g:, 'fugitive_browse_handlers', [])
3409       let url = call(Handler, [copy(opts)])
3410       if !empty(url)
3411         break
3412       endif
3413     endfor
3415     if empty(url)
3416       call s:throw("No Gbrowse handler found for '".raw."'")
3417     endif
3419     let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
3420     if a:bang
3421       if has('clipboard')
3422         let @+ = url
3423       endif
3424       return 'echomsg '.string(url)
3425     elseif exists(':Browse') == 2
3426       return 'echomsg '.string(url).'|Browse '.url
3427     else
3428       if !exists('g:loaded_netrw')
3429         runtime! autoload/netrw.vim
3430       endif
3431       if exists('*netrw#BrowseX')
3432         return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
3433       else
3434         return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
3435       endif
3436     endif
3437   catch /^fugitive:/
3438     return 'echoerr v:errmsg'
3439   endtry
3440 endfunction
3442 " Section: Go to file
3444 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
3445 function! fugitive#MapCfile(...) abort
3446   exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
3447   let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
3448   if !exists('g:fugitive_no_maps')
3449     call s:map('n', 'gf',          '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
3450     call s:map('n', '<C-W>f',     '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3451     call s:map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3452     call s:map('n', '<C-W>gf',  '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
3453     call s:map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
3454   endif
3455 endfunction
3457 function! s:ContainingCommit() abort
3458   let commit = s:Owner(@%)
3459   return empty(commit) ? 'HEAD' : commit
3460 endfunction
3462 function! s:NavigateUp(count) abort
3463   let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
3464   let c = a:count
3465   while c
3466     if rev =~# ':.*/.'
3467       let rev = matchstr(rev, '.*\ze/.\+', '')
3468     elseif rev =~# '.:.'
3469       let rev = matchstr(rev, '^.[^:]*:')
3470     elseif rev =~# '^:'
3471       let rev = 'HEAD^{}'
3472     elseif rev =~# ':$'
3473       let rev = rev[0:-2]
3474     else
3475       return rev.'~'.c
3476     endif
3477     let c -= 1
3478   endwhile
3479   return rev
3480 endfunction
3482 function! fugitive#MapJumps(...) abort
3483   if get(b:, 'fugitive_type', '') ==# 'blob'
3484     nnoremap <buffer> <silent> <CR>    :<C-U>.Gblame<CR>
3485   else
3486     nnoremap <buffer> <silent> <CR>    :<C-U>exe <SID>GF("edit")<CR>
3487   endif
3488   if !&modifiable
3489     let nowait = v:version >= 704 ? '<nowait>' : ''
3490     if get(b:, 'fugitive_type', '') ==# 'blob'
3491       nnoremap <buffer> <silent> o     :<C-U>.,.+1Gblame<CR>
3492       nnoremap <buffer> <silent> S     :<C-U>vertical .,.+1Gblame<CR>
3493       nnoremap <buffer> <silent> O     :<C-U>tab .,.+1Gblame<CR>
3494       nnoremap <buffer> <silent> p     :<C-U>.,.+2Gblame<CR>
3495     else
3496       nnoremap <buffer> <silent> o     :<C-U>exe <SID>GF("split")<CR>
3497       nnoremap <buffer> <silent> S     :<C-U>exe <SID>GF("vsplit")<CR>
3498       nnoremap <buffer> <silent> O     :<C-U>exe <SID>GF("tabedit")<CR>
3499       nnoremap <buffer> <silent> p     :<C-U>exe <SID>GF("pedit")<CR>
3500     endif
3501     exe "nnoremap <buffer> <silent>" nowait  "-     :<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>"
3502     nnoremap <buffer> <silent> P     :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>
3503     nnoremap <buffer> <silent> ~     :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>
3504     nnoremap <buffer> <silent> C     :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3505     nnoremap <buffer> <silent> cc    :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3506     nnoremap <buffer> <silent> co    :<C-U>exe 'Gsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3507     nnoremap <buffer> <silent> cS    :<C-U>exe 'Gvsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3508     nnoremap <buffer> <silent> cO    :<C-U>exe 'Gtabedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3509     nnoremap <buffer> <silent> cp    :<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3510     nmap     <buffer>          .     <SID>: <Plug><cfile><Home>
3511   endif
3512 endfunction
3514 function! s:StatusCfile(...) abort
3515   let pre = ''
3516   let tree = FugitiveTreeForGitDir(b:git_dir)
3517   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
3518   if getline('.') =~# '^.\=\trenamed:.* -> '
3519     return lead . matchstr(getline('.'),' -> \zs.*')
3520   elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
3521     return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
3522   elseif getline('.') =~# '^.\=\t.'
3523     return lead . matchstr(getline('.'),'\t\zs.*')
3524   elseif getline('.') =~# ': needs merge$'
3525     return lead . matchstr(getline('.'),'.*\ze: needs merge$')
3526   elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
3527     return 'HEAD'
3528   elseif getline('.') =~# '^\%(. \)\=On branch '
3529     return 'refs/heads/'.getline('.')[12:]
3530   elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
3531     return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
3532   else
3533     return ''
3534   endif
3535 endfunction
3537 function! fugitive#StatusCfile() abort
3538   let file = s:Generate(s:StatusCfile())
3539   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
3540 endfunction
3542 function! s:cfile() abort
3543   try
3544     let myhash = s:DirRev(@%)[1]
3545     if len(myhash)
3546       try
3547         let myhash = fugitive#RevParse(myhash)
3548       catch /^fugitive:/
3549         let myhash = ''
3550       endtry
3551     endif
3552     if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
3553       let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
3554     endif
3556     let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
3558     let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
3559           \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
3561     if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
3562       return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
3563     elseif showtree
3564       return [treebase . s:sub(getline('.'),'/$','')]
3566     else
3568       let dcmds = []
3570       " Index
3571       if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
3572         let ref = matchstr(getline('.'),'\x\{40\}')
3573         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
3574         return [file]
3575       endif
3577       if getline('.') =~# '^ref: '
3578         let ref = strpart(getline('.'),5)
3580       elseif getline('.') =~# '^commit \x\{40\}\>'
3581         let ref = matchstr(getline('.'),'\x\{40\}')
3582         return [ref]
3584       elseif getline('.') =~# '^parent \x\{40\}\>'
3585         let ref = matchstr(getline('.'),'\x\{40\}')
3586         let line = line('.')
3587         let parent = 0
3588         while getline(line) =~# '^parent '
3589           let parent += 1
3590           let line -= 1
3591         endwhile
3592         return [ref]
3594       elseif getline('.') =~# '^tree \x\{40\}$'
3595         let ref = matchstr(getline('.'),'\x\{40\}')
3596         if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
3597           let ref = myhash.':'
3598         endif
3599         return [ref]
3601       elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
3602         let ref = matchstr(getline('.'),'\x\{40\}')
3603         let type = matchstr(getline(line('.')+1),'type \zs.*')
3605       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
3606         let ref = s:DirRev(@%)[1]
3608       elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
3609         let ref = matchstr(getline('.'),'\x\{40\}')
3610         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
3612       elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
3613         let ref = getline('.')[4:]
3615       elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
3616         let type = getline('.')[0]
3617         let lnum = line('.') - 1
3618         let offset = 0
3619         while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3620           if getline(lnum) =~# '^[ '.type.']'
3621             let offset += 1
3622           endif
3623           let lnum -= 1
3624         endwhile
3625         let offset += matchstr(getline(lnum), type.'\zs\d\+')
3626         let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
3627         let dcmds = [offset, 'normal!zv']
3629       elseif getline('.') =~# '^rename from '
3630         let ref = 'a/'.getline('.')[12:]
3631       elseif getline('.') =~# '^rename to '
3632         let ref = 'b/'.getline('.')[10:]
3634       elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3635         let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
3636         let offset = matchstr(getline('.'), '+\zs\d\+')
3638         let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3639         let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3640         let dcmd = 'Gdiff! +'.offset
3642       elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3643         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3644         let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3645         let dcmd = 'Gdiff!'
3647       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3648         let line = getline(line('.')-1)
3649         let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3650         let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3651         let dcmd = 'Gdiff!'
3653       elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
3654         let ref = getline('.')
3656       elseif expand('<cword>') =~# '^\x\{7,40\}\>'
3657         return [expand('<cword>')]
3659       else
3660         let ref = ''
3661       endif
3663       let prefixes = {
3664             \ '1': '',
3665             \ '2': '',
3666             \ 'b': ':0:',
3667             \ 'i': ':0:',
3668             \ 'o': '',
3669             \ 'w': ''}
3671       if len(myhash)
3672         let prefixes.a = myhash.'^:'
3673         let prefixes.b = myhash.':'
3674       endif
3675       let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3676       if exists('dref')
3677         let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3678       endif
3680       if ref ==# '/dev/null'
3681         " Empty blob
3682         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
3683       endif
3685       if exists('dref')
3686         return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
3687       elseif ref != ""
3688         return [ref] + dcmds
3689       endif
3691     endif
3692     return []
3693   endtry
3694 endfunction
3696 function! s:GF(mode) abort
3697   try
3698     let results = &filetype ==# 'gitcommit' ? [s:StatusCfile()] : s:cfile()
3699   catch /^fugitive:/
3700     return 'echoerr v:errmsg'
3701   endtry
3702   if len(results) > 1
3703     return 'G' . a:mode .
3704           \ ' +' . escape(join(results[1:-1], '|'), '| ') . ' ' .
3705           \ s:fnameescape(results[0])
3706   elseif len(results)
3707     return 'G' . a:mode . ' ' . s:fnameescape(results[0])
3708   else
3709     return ''
3710   endif
3711 endfunction
3713 function! fugitive#Cfile() abort
3714   let pre = ''
3715   let results = s:cfile()
3716   if empty(results)
3717     let cfile = expand('<cfile>')
3718     if &includeexpr =~# '\<v:fname\>'
3719       sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
3720     endif
3721     return cfile
3722   elseif len(results) > 1
3723     let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
3724   endif
3725   return pre . s:fnameescape(s:Generate(results[0]))
3726 endfunction
3728 " Section: Statusline
3730 function! fugitive#Statusline(...) abort
3731   if !exists('b:git_dir')
3732     return ''
3733   endif
3734   let status = ''
3735   let commit = s:DirCommitFile(@%)[1]
3736   if len(commit)
3737     let status .= ':' . commit[0:7]
3738   endif
3739   let status .= '('.FugitiveHead(7).')'
3740   return '[Git'.status.']'
3741 endfunction
3743 function! fugitive#statusline(...) abort
3744   return fugitive#Statusline()
3745 endfunction
3747 function! fugitive#head(...) abort
3748   if !exists('b:git_dir')
3749     return ''
3750   endif
3752   return fugitive#Head(a:0 ? a:1 : 0)
3753 endfunction
3755 " Section: Folding
3757 function! fugitive#Foldtext() abort
3758   if &foldmethod !=# 'syntax'
3759     return foldtext()
3760   endif
3762   let line_foldstart = getline(v:foldstart)
3763   if line_foldstart =~# '^diff '
3764     let [add, remove] = [-1, -1]
3765     let filename = ''
3766     for lnum in range(v:foldstart, v:foldend)
3767       let line = getline(lnum)
3768       if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
3769         let filename = line[6:-1]
3770       endif
3771       if line =~# '^+'
3772         let add += 1
3773       elseif line =~# '^-'
3774         let remove += 1
3775       elseif line =~# '^Binary '
3776         let binary = 1
3777       endif
3778     endfor
3779     if filename ==# ''
3780       let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
3781     endif
3782     if filename ==# ''
3783       let filename = line_foldstart[5:-1]
3784     endif
3785     if exists('binary')
3786       return 'Binary: '.filename
3787     else
3788       return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3789     endif
3790   elseif line_foldstart =~# '^# .*:$'
3791     let lines = getline(v:foldstart, v:foldend)
3792     call filter(lines, 'v:val =~# "^#\t"')
3793     cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
3794     cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
3795     return line_foldstart.' '.join(lines, ', ')
3796   endif
3797   return foldtext()
3798 endfunction
3800 function! fugitive#foldtext() abort
3801   return fugitive#Foldtext()
3802 endfunction
3804 augroup fugitive_folding
3805   autocmd!
3806   autocmd User Fugitive
3807         \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3808         \    set foldtext=fugitive#Foldtext() |
3809         \ endif
3810 augroup END
3812 " Section: Initialization
3814 function! fugitive#Init() abort
3815   if exists('#User#FugitiveBoot')
3816     try
3817       let [save_mls, &modelines] = [&mls, 0]
3818       doautocmd User FugitiveBoot
3819     finally
3820       let &mls = save_mls
3821     endtry
3822   endif
3823   if !exists('g:fugitive_no_maps')
3824     call s:map('c', '<C-R><C-G>', '<SID>fnameescape(fugitive#Object(@%))', '<expr>')
3825     call s:map('n', 'y<C-G>', ':<C-U>call setreg(v:register, fugitive#Object(@%))<CR>', '<silent>')
3826   endif
3827   if expand('%:p') =~# ':[\/][\/]'
3828     let &l:path = s:sub(&path, '^\.%(,|$)', '')
3829   endif
3830   if stridx(&tags, escape(b:git_dir, ', ')) == -1
3831     if filereadable(b:git_dir.'/tags')
3832       let &l:tags = escape(b:git_dir.'/tags', ', ').','.&tags
3833     endif
3834     if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
3835       let &l:tags = escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.&tags
3836     endif
3837   endif
3838   try
3839     let [save_mls, &modelines] = [&mls, 0]
3840     call s:define_commands()
3841     doautocmd User Fugitive
3842   finally
3843     let &mls = save_mls
3844   endtry
3845 endfunction
3847 function! fugitive#is_git_dir(path) abort
3848   return FugitiveIsGitDir(a:path)
3849 endfunction
3851 function! fugitive#extract_git_dir(path) abort
3852   return FugitiveExtractGitDir(a:path)
3853 endfunction
3855 function! fugitive#detect(path) abort
3856   return FugitiveDetect(a:path)
3857 endfunction
3859 " Section: End