Show 7 not 8 SHA charcters in statusline
[vim-fugitive.git] / autoload / fugitive.vim
blobb5350d6ac8b3393a08c50000a0267f2b71d2c140
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         let error = b:fugitive_type
1444         unlet b:fugitive_type
1445         if rev =~# '^:\d:'
1446           let &readonly = !filewritable(dir . '/index')
1447           return 'silent doautocmd BufNewFile '.s:fnameescape(amatch)
1448         else
1449           setlocal readonly nomodifiable
1450           return 'echo ' . string(error)
1451         endif
1452       elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1453         return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1454       endif
1455       if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1456         let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1457       endif
1458     endif
1460     if b:fugitive_type !=# 'blob'
1461       setlocal nomodeline
1462     endif
1464     setlocal noreadonly modifiable
1465     let pos = getpos('.')
1466     silent keepjumps %delete_
1467     setlocal endofline
1469     try
1470       if b:fugitive_type ==# 'tree'
1471         let b:fugitive_display_format = b:fugitive_display_format % 2
1472         if b:fugitive_display_format
1473           call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1474         else
1475           if !exists('sha')
1476             let sha = system(s:Prepare(dir, 'rev-parse', '--verify', rev, '--'))[0:-2]
1477           endif
1478           call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1479         endif
1480       elseif b:fugitive_type ==# 'tag'
1481         let b:fugitive_display_format = b:fugitive_display_format % 2
1482         if b:fugitive_display_format
1483           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1484         else
1485           call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1486         endif
1487       elseif b:fugitive_type ==# 'commit'
1488         let b:fugitive_display_format = b:fugitive_display_format % 2
1489         if b:fugitive_display_format
1490           call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1491         else
1492           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])
1493           keepjumps call search('^parent ')
1494           if getline('.') ==# 'parent '
1495             silent keepjumps delete_
1496           else
1497             silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
1498           endif
1499           keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1500           if lnum
1501             silent keepjumps delete_
1502           end
1503           silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/g/\r$/s///'
1504           keepjumps 1
1505         endif
1506       elseif b:fugitive_type ==# 'stage'
1507         call s:ReplaceCmd([dir, 'ls-files', '--stage'])
1508       elseif b:fugitive_type ==# 'blob'
1509         call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1510         setlocal nomodeline
1511       endif
1512     finally
1513       keepjumps call setpos('.',pos)
1514       setlocal nomodified noswapfile
1515       if rev !~# '^:.:'
1516         setlocal nomodifiable
1517       else
1518         let &modifiable = b:fugitive_type !=# 'tree'
1519       endif
1520       let &readonly = !&modifiable || !filewritable(dir . '/index')
1521       if &bufhidden ==# ''
1522         setlocal bufhidden=delete
1523       endif
1524       if b:fugitive_type !=# 'blob'
1525         setlocal filetype=git foldmethod=syntax
1526         nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1527         nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1528       else
1529         call fugitive#MapJumps()
1530       endif
1531     endtry
1533     return ''
1534   catch /^fugitive:/
1535     return 'echoerr v:errmsg'
1536   endtry
1537 endfunction
1539 function! fugitive#BufWriteCmd(...) abort
1540   return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
1541 endfunction
1543 function! fugitive#SourceCmd(...) abort
1544   let amatch = a:0 ? a:1 : expand('<amatch>')
1545   let temp = s:BlobTemp(amatch)
1546   if empty(temp)
1547     return 'noautocmd source ' . s:fnameescape(amatch)
1548   endif
1549   if !exists('g:virtual_scriptnames')
1550     let g:virtual_scriptnames = {}
1551   endif
1552   let g:virtual_scriptnames[temp] = amatch
1553   return 'source ' . s:fnameescape(temp)
1554 endfunction
1556 " Section: Temp files
1558 if !exists('s:temp_files')
1559   let s:temp_files = {}
1560 endif
1562 function! s:SetupTemp(file) abort
1563   if has_key(s:temp_files, s:cpath(a:file))
1564     let dict = s:temp_files[s:cpath(a:file)]
1565     let b:git_dir = dict.dir
1566     call extend(b:, {'fugitive_type': 'temp'}, 'keep')
1567     if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
1568       let &l:filetype = dict.filetype
1569     endif
1570     setlocal foldmarker=<<<<<<<,>>>>>>>
1571     setlocal bufhidden=delete nobuflisted
1572     setlocal buftype=nowrite
1573     nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
1574     if getline(1) !~# '^diff '
1575       setlocal nomodifiable
1576     endif
1577     call FugitiveDetect(a:file)
1578   endif
1579   return ''
1580 endfunction
1582 augroup fugitive_temp
1583   autocmd!
1584   autocmd BufNewFile,BufReadPost * exe s:SetupTemp(expand('<amatch>:p'))
1585 augroup END
1587 " Section: :Git
1589 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,'<mods>',<q-args>)")
1591 function! s:Git(bang, mods, args) abort
1592   if a:bang
1593     return s:Edit('edit', 1, a:mods, a:args)
1594   endif
1595   let git = s:UserCommand()
1596   if has('gui_running') && !has('win32')
1597     let git .= ' --no-pager'
1598   endif
1599   let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
1600   let after = matchstr(a:args, '\v\C\\@<!%(\\\\)*\zs\|.*')
1601   let tree = s:Tree()
1602   if has('win32')
1603     let after = '|call fugitive#ReloadStatus()' . after
1604   endif
1605   if exists(':terminal') && has('nvim') && !get(g:, 'fugitive_force_bang_command')
1606     if len(@%)
1607       -tabedit %
1608     else
1609       -tabnew
1610     endif
1611     execute 'lcd' fnameescape(tree)
1612     let exec = escape(git . ' ' . s:ShellExpand(args), '#%')
1613     return 'exe ' . string('terminal ' . exec) . after
1614   else
1615     let cmd = "exe '!'.escape(" . string(git) . " . ' ' . s:ShellExpand(" . string(args) . "),'!#%')"
1616     if s:cpath(tree) !=# s:cpath(getcwd())
1617       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1618       let cmd = 'try|' . cd . ' ' . tree . '|' . cmd . '|finally|' . cd . ' ' . s:fnameescape(getcwd()) . '|endtry'
1619     endif
1620     return cmd . after
1621   endif
1622 endfunction
1624 let s:exec_paths = {}
1625 function! s:Subcommands() abort
1626   if !has_key(s:exec_paths, g:fugitive_git_executable)
1627     let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
1628   endif
1629   let exec_path = s:exec_paths[g:fugitive_git_executable]
1630   return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
1631 endfunction
1633 let s:aliases = {}
1634 function! s:Aliases() abort
1635   if !has_key(s:aliases, b:git_dir)
1636     let s:aliases[b:git_dir] = {}
1637     let lines = split(s:TreeChomp('config','-z','--get-regexp','^alias[.]'),"\1")
1638     for line in v:shell_error ? [] : lines
1639       let s:aliases[b:git_dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
1640     endfor
1641   endif
1642   return s:aliases[b:git_dir]
1643 endfunction
1645 function! s:GitComplete(A, L, P) abort
1646   let pre = strpart(a:L, 0, a:P)
1647   if pre !~# ' [[:alnum:]-]\+ '
1648     let cmds = s:Subcommands()
1649     return filter(sort(cmds+keys(s:Aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
1650   elseif pre =~# ' -- '
1651     return fugitive#PathComplete(a:A, b:git_dir)
1652   else
1653     return fugitive#Complete(a:A, b:git_dir)
1654   endif
1655 endfunction
1657 " Section: :Gcd, :Glcd
1659 function! s:DirComplete(A, L, P) abort
1660   return filter(fugitive#PathComplete(a:A), 'v:val =~# "/$"')
1661 endfunction
1663 function! s:DirArg(path) abort
1664   let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
1665   if path =~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
1666     return path
1667   else
1668     return (empty(s:Tree()) ? b:git_dir : s:Tree()) . '/' . path
1669   endif
1670 endfunction
1672 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd  :exe 'cd<bang>'  s:fnameescape(s:DirArg(<q-args>))")
1673 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe 'lcd<bang>' s:fnameescape(s:DirArg(<q-args>))")
1675 " Section: :Gstatus
1677 call s:command("-bar -bang -range=-1 Gstatus :execute s:Status(<bang>0, <count>, '<mods>')")
1678 augroup fugitive_status
1679   autocmd!
1680   if !has('win32')
1681     autocmd FocusGained,ShellCmdPost * call fugitive#ReloadStatus()
1682     autocmd BufDelete term://* call fugitive#ReloadStatus()
1683   endif
1684 augroup END
1686 function! s:Status(bang, count, mods) abort
1687   try
1688     exe (a:mods ==# '<mods>' ? '' : a:mods) 'Gpedit :'
1689     wincmd P
1690     setlocal foldmethod=syntax foldlevel=1 buftype=nowrite
1691     nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
1692   catch /^fugitive:/
1693     return 'echoerr v:errmsg'
1694   endtry
1695   return ''
1696 endfunction
1698 function! fugitive#ReloadStatus() abort
1699   if exists('s:reloading_status')
1700     return
1701   endif
1702   try
1703     let s:reloading_status = 1
1704     let mytab = tabpagenr()
1705     for tab in [mytab] + range(1,tabpagenr('$'))
1706       for winnr in range(1,tabpagewinnr(tab,'$'))
1707         if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
1708           execute 'tabnext '.tab
1709           if winnr != winnr()
1710             execute winnr.'wincmd w'
1711             let restorewinnr = 1
1712           endif
1713           try
1714             if !&modified
1715               call fugitive#BufReadStatus()
1716             endif
1717           finally
1718             if exists('restorewinnr')
1719               wincmd p
1720             endif
1721             execute 'tabnext '.mytab
1722           endtry
1723         endif
1724       endfor
1725     endfor
1726   finally
1727     unlet! s:reloading_status
1728   endtry
1729 endfunction
1731 function! fugitive#reload_status() abort
1732   return fugitive#ReloadStatus()
1733 endfunction
1735 function! s:stage_info(lnum) abort
1736   let filename = matchstr(getline(a:lnum),'^.\=\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
1737   let lnum = a:lnum
1738   if has('multi_byte_encoding')
1739     let colon = '\%(:\|\%uff1a\)'
1740   else
1741     let colon = ':'
1742   endif
1743   while lnum && getline(lnum) !~# colon.'$'
1744     let lnum -= 1
1745   endwhile
1746   if !lnum
1747     return ['', '']
1748   elseif (getline(lnum+1) =~# '^.\= .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) =~# '^\%(. \)\=Changes to be committed:$'
1749     return [matchstr(filename, colon.' *\zs.*'), 'staged']
1750   elseif (getline(lnum+1) =~# '^.\= .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.'  ') || getline(lnum) =~# '^\(. \)\=Untracked files:$'
1751     return [filename, 'untracked']
1752   elseif getline(lnum+2) =~# '^.\= .*\<git checkout ' || getline(lnum) =~# '\%(. \)\=Changes not staged for commit:$'
1753     return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
1754   elseif getline(lnum+2) =~# '^.\= .*\<git \%(add\|rm\)' || getline(lnum) =~# '\%(. \)\=Unmerged paths:$'
1755     return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
1756   else
1757     return ['', 'unknown']
1758   endif
1759 endfunction
1761 function! s:StageNext(count) abort
1762   for i in range(a:count)
1763     call search('^.\=\t.*','W')
1764   endfor
1765   return '.'
1766 endfunction
1768 function! s:StagePrevious(count) abort
1769   if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
1770     return 'CtrlP '.fnameescape(s:Tree())
1771   else
1772     for i in range(a:count)
1773       call search('^.\=\t.*','Wbe')
1774     endfor
1775     return '.'
1776   endif
1777 endfunction
1779 function! s:StageReloadSeek(target,lnum1,lnum2) abort
1780   let jump = a:target
1781   let f = matchstr(getline(a:lnum1-1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1782   if f !=# '' | let jump = f | endif
1783   let f = matchstr(getline(a:lnum2+1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1784   if f !=# '' | let jump = f | endif
1785   silent! edit!
1786   1
1787   redraw
1788   call search('^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1789 endfunction
1791 function! s:StageUndo() abort
1792   let [filename, section] = s:stage_info(line('.'))
1793   if empty(filename)
1794     return ''
1795   endif
1796   let hash = s:TreeChomp('hash-object', '-w', './' . filename)
1797   if !empty(hash)
1798     if section ==# 'untracked'
1799       call s:TreeChomp('clean', '-f', './' . filename)
1800     elseif section ==# 'unmerged'
1801       call s:TreeChomp('rm', './' . filename)
1802     elseif section ==# 'unstaged'
1803       call s:TreeChomp('checkout', './' . filename)
1804     else
1805       call s:TreeChomp('checkout', 'HEAD^{}', './' . filename)
1806     endif
1807     call s:StageReloadSeek(filename, line('.'), line('.'))
1808     let @" = hash
1809     return 'checktime|redraw|echomsg ' .
1810           \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
1811   endif
1812 endfunction
1814 function! s:StageDiff(diff) abort
1815   let [filename, section] = s:stage_info(line('.'))
1816   if filename ==# '' && section ==# 'staged'
1817     return 'Git! diff --no-ext-diff --cached'
1818   elseif filename ==# ''
1819     return 'Git! diff --no-ext-diff'
1820   elseif filename =~# ' -> '
1821     let [old, new] = split(filename,' -> ')
1822     execute 'Gedit '.s:fnameescape(':0:'.new)
1823     return a:diff.' HEAD:'.s:fnameescape(old)
1824   elseif section ==# 'staged'
1825     execute 'Gedit '.s:fnameescape(':0:'.filename)
1826     return a:diff.' -'
1827   else
1828     execute 'Gedit '.s:fnameescape('/'.filename)
1829     return a:diff
1830   endif
1831 endfunction
1833 function! s:StageDiffEdit() abort
1834   let [filename, section] = s:stage_info(line('.'))
1835   let arg = (filename ==# '' ? '.' : filename)
1836   if section ==# 'staged'
1837     return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
1838   elseif section ==# 'untracked'
1839     call s:TreeChomp('add', '--intent-to-add', './' . arg)
1840     if arg ==# '.'
1841       silent! edit!
1842       1
1843       if !search('^.*:\n.*\n.\= .*"git checkout \|^\%(# \)=Changes not staged for commit:$','W')
1844         call search(':$','W')
1845       endif
1846     else
1847       call s:StageReloadSeek(arg,line('.'),line('.'))
1848     endif
1849     return ''
1850   else
1851     return 'Git! diff --no-ext-diff '.s:shellesc(arg)
1852   endif
1853 endfunction
1855 function! s:StageToggle(lnum1,lnum2) abort
1856   if a:lnum1 == 1 && a:lnum2 == 1
1857     return 'Gedit .git/|call search("^index$", "wc")'
1858   endif
1859   try
1860     let output = ''
1861     for lnum in range(a:lnum1,a:lnum2)
1862       let [filename, section] = s:stage_info(lnum)
1863       if getline('.') =~# ':$'
1864         if section ==# 'staged'
1865           call s:TreeChomp('reset','-q')
1866           silent! edit!
1867           1
1868           if !search('^.*:\n.\= .*"git add .*\n#\n\|^\%(. \)\=Untracked files:$','W')
1869             call search(':$','W')
1870           endif
1871           return ''
1872         elseif section ==# 'unstaged'
1873           call s:TreeChomp('add','-u')
1874           silent! edit!
1875           1
1876           if !search('^.*:\n\.\= .*"git add .*\n#\n\|^\%( \)=Untracked files:$','W')
1877             call search(':$','W')
1878           endif
1879           return ''
1880         else
1881           call s:TreeChomp('add', '.')
1882           silent! edit!
1883           1
1884           call search(':$','W')
1885           return ''
1886         endif
1887       endif
1888       if filename ==# ''
1889         continue
1890       endif
1891       execute lnum
1892       if section ==# 'staged'
1893         let files_to_unstage = split(filename, ' -> ')
1894         let filename = files_to_unstage[-1]
1895         let cmd = ['reset', '-q'] + map(copy(files_to_unstage), '"./" . v:val')
1896       elseif getline(lnum) =~# '^.\=\tdeleted:'
1897         let cmd = ['rm', './' . filename]
1898       elseif getline(lnum) =~# '^.\=\tmodified:'
1899         let cmd = ['add', './' . filename]
1900       else
1901         let cmd = ['add','-A', './' . filename]
1902       endif
1903       if !exists('first_filename')
1904         let first_filename = filename
1905       endif
1906       let output .= call('s:TreeChomp', cmd)."\n"
1907     endfor
1908     if exists('first_filename')
1909       call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
1910     endif
1911     echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
1912   catch /^fugitive:/
1913     return 'echoerr v:errmsg'
1914   endtry
1915   return 'checktime'
1916 endfunction
1918 function! s:StagePatch(lnum1,lnum2) abort
1919   let add = []
1920   let reset = []
1922   for lnum in range(a:lnum1,a:lnum2)
1923     let [filename, section] = s:stage_info(lnum)
1924     if getline('.') =~# ':$' && section ==# 'staged'
1925       return 'Git reset --patch'
1926     elseif getline('.') =~# ':$' && section ==# 'unstaged'
1927       return 'Git add --patch'
1928     elseif getline('.') =~# ':$' && section ==# 'untracked'
1929       return 'Git add -N .'
1930     elseif filename ==# ''
1931       continue
1932     endif
1933     if !exists('first_filename')
1934       let first_filename = filename
1935     endif
1936     execute lnum
1937     if filename =~ ' -> '
1938       let reset += [split(filename,' -> ')[1]]
1939     elseif section ==# 'staged'
1940       let reset += [filename]
1941     elseif getline(lnum) !~# '^.\=\tdeleted:'
1942       let add += [filename]
1943     endif
1944   endfor
1945   try
1946     if !empty(add)
1947       execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1948     endif
1949     if !empty(reset)
1950       execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1951     endif
1952     if exists('first_filename')
1953       silent! edit!
1954       1
1955       redraw
1956       call search('^.\=\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1957     endif
1958   catch /^fugitive:/
1959     return 'echoerr v:errmsg'
1960   endtry
1961   return 'checktime'
1962 endfunction
1964 " Section: :Gcommit
1966 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit('<mods>', <q-args>)")
1968 function! s:Commit(mods, args, ...) abort
1969   let mods = s:gsub(a:mods ==# '<mods>' ? '' : a:mods, '<tab>', '-tab')
1970   let dir = a:0 ? a:1 : b:git_dir
1971   let tree = s:Tree(dir)
1972   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1973   let cwd = getcwd()
1974   let msgfile = dir . '/COMMIT_EDITMSG'
1975   let outfile = tempname()
1976   let errorfile = tempname()
1977   try
1978     let guioptions = &guioptions
1979     try
1980       if &guioptions =~# '!'
1981         setglobal guioptions-=!
1982       endif
1983       execute cd s:fnameescape(tree)
1984       if s:winshell()
1985         let command = ''
1986         let old_editor = $GIT_EDITOR
1987         let $GIT_EDITOR = 'false'
1988       else
1989         let command = 'env GIT_EDITOR=false '
1990       endif
1991       let args = s:ShellExpand(a:args)
1992       let command .= s:UserCommand() . ' commit ' . args
1993       if &shell =~# 'csh'
1994         noautocmd silent execute '!('.escape(command, '!#%').' > '.outfile.') >& '.errorfile
1995       elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1996         noautocmd execute '!'.command.' 2> '.errorfile
1997       else
1998         noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1999       endif
2000       let error = v:shell_error
2001     finally
2002       execute cd s:fnameescape(cwd)
2003       let &guioptions = guioptions
2004     endtry
2005     if !has('gui_running')
2006       redraw!
2007     endif
2008     if !error
2009       if filereadable(outfile)
2010         for line in readfile(outfile)
2011           echo line
2012         endfor
2013       endif
2014       return ''
2015     else
2016       let errors = readfile(errorfile)
2017       let error = get(errors,-2,get(errors,-1,'!'))
2018       if error =~# 'false''\=\.$'
2019         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
2020         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
2021         let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2022         let cwd = getcwd()
2023         let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
2024         let args = '-F '.s:shellesc(msgfile).' '.args
2025         if args !~# '\%(^\| \)--cleanup\>'
2026           let args = '--cleanup=strip '.args
2027         endif
2028         if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
2029           execute mods 'keepalt edit' s:fnameescape(msgfile)
2030         elseif a:args =~# '\%(^\| \)-\w*v' || mods =~# '\<tab\>'
2031           execute mods 'keepalt -tabedit' s:fnameescape(msgfile)
2032         elseif get(b:, 'fugitive_type', '') ==# 'index'
2033           execute mods 'keepalt edit' s:fnameescape(msgfile)
2034           execute (search('^#','n')+1).'wincmd+'
2035           setlocal nopreviewwindow
2036         else
2037           execute mods 'keepalt split' s:fnameescape(msgfile)
2038         endif
2039         let b:fugitive_commit_arguments = args
2040         setlocal bufhidden=wipe filetype=gitcommit
2041         return '1'
2042       elseif error ==# '!'
2043         return 'Gstatus'
2044       else
2045         call s:throw(empty(error)?join(errors, ' '):error)
2046       endif
2047     endif
2048   catch /^fugitive:/
2049     return 'echoerr v:errmsg'
2050   finally
2051     if exists('old_editor')
2052       let $GIT_EDITOR = old_editor
2053     endif
2054     call delete(outfile)
2055     call delete(errorfile)
2056     call fugitive#ReloadStatus()
2057   endtry
2058 endfunction
2060 function! s:CommitComplete(A,L,P) abort
2061   if a:A =~# '^--fixup=\|^--squash='
2062     let commits = split(s:TreeChomp('log', '--pretty=format:%s', '@{upstream}..'), "\n")
2063     if !v:shell_error
2064       let pre = matchstr(a:A, '^--\w*=') . ':/^'
2065       return map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")')
2066     endif
2067   elseif a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
2068     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']
2069     return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
2070   else
2071     return fugitive#PathComplete(a:A, b:git_dir)
2072   endif
2073   return []
2074 endfunction
2076 function! s:FinishCommit() abort
2077   let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
2078   if !empty(args)
2079     call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
2080     return s:Commit('', args, getbufvar(+expand('<abuf>'),'git_dir'))
2081   endif
2082   return ''
2083 endfunction
2085 " Section: :Gmerge, :Grebase, :Gpull
2087 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
2088       \ "execute s:Merge('merge', <bang>0, <q-args>)")
2089 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Grebase " .
2090       \ "execute s:Merge('rebase', <bang>0, <q-args>)")
2091 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
2092       \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
2094 function! s:RevisionComplete(A, L, P) abort
2095   return s:TreeChomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
2096         \ . "\nHEAD\nFETCH_HEAD\nMERGE_HEAD\nORIG_HEAD"
2097 endfunction
2099 function! s:RemoteComplete(A, L, P) abort
2100   let remote = matchstr(a:L, ' \zs\S\+\ze ')
2101   if !empty(remote)
2102     let matches = split(s:TreeChomp('ls-remote', remote), "\n")
2103     call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
2104     call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
2105   else
2106     let matches = split(s:TreeChomp('remote'), "\n")
2107   endif
2108   return join(matches, "\n")
2109 endfunction
2111 function! fugitive#Cwindow() abort
2112   if &buftype == 'quickfix'
2113     cwindow
2114   else
2115     botright cwindow
2116     if &buftype == 'quickfix'
2117       wincmd p
2118     endif
2119   endif
2120 endfunction
2122 let s:common_efm = ''
2123       \ . '%+Egit:%.%#,'
2124       \ . '%+Eusage:%.%#,'
2125       \ . '%+Eerror:%.%#,'
2126       \ . '%+Efatal:%.%#,'
2127       \ . '%-G%.%#%\e[K%.%#,'
2128       \ . '%-G%.%#%\r%.%\+'
2130 function! s:Merge(cmd, bang, args) abort
2131   if a:cmd =~# '^rebase' && ' '.a:args =~# ' -i\| --interactive\| --edit-todo'
2132     return 'echoerr "git rebase --interactive not supported"'
2133   endif
2134   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2135   let cwd = getcwd()
2136   let [mp, efm] = [&l:mp, &l:efm]
2137   let had_merge_msg = filereadable(b:git_dir . '/MERGE_MSG')
2138   try
2139     let &l:errorformat = ''
2140           \ . '%-Gerror:%.%#false''.,'
2141           \ . '%-G%.%# ''git commit'' %.%#,'
2142           \ . '%+Emerge:%.%#,'
2143           \ . s:common_efm . ','
2144           \ . '%+ECannot %.%#: You have unstaged changes.,'
2145           \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
2146           \ . '%+EThere is no tracking information for the current branch.,'
2147           \ . '%+EYou are not currently on a branch. Please specify which,'
2148           \ . 'CONFLICT (%m): %f deleted in %.%#,'
2149           \ . 'CONFLICT (%m): Merge conflict in %f,'
2150           \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
2151           \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
2152           \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
2153           \ . '%+ECONFLICT %.%#,'
2154           \ . '%+EKONFLIKT %.%#,'
2155           \ . '%+ECONFLIT %.%#,'
2156           \ . "%+EXUNG \u0110\u1ed8T %.%#,"
2157           \ . "%+E\u51b2\u7a81 %.%#,"
2158           \ . 'U%\t%f'
2159     if a:cmd =~# '^merge' && empty(a:args) &&
2160           \ (had_merge_msg || isdirectory(b:git_dir . '/rebase-apply') ||
2161           \  !empty(s:TreeChomp('diff-files', '--diff-filter=U')))
2162       let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
2163     else
2164       let &l:makeprg = s:sub(s:UserCommand() . ' ' . a:cmd .
2165             \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' || a:cmd =~# '^rebase' ? '' : ' --edit') .
2166             \ ' ' . a:args, ' *$', '')
2167     endif
2168     if !empty($GIT_EDITOR) || has('win32')
2169       let old_editor = $GIT_EDITOR
2170       let $GIT_EDITOR = 'false'
2171     else
2172       let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
2173     endif
2174     execute cd fnameescape(s:Tree())
2175     silent noautocmd make!
2176   catch /^Vim\%((\a\+)\)\=:E211/
2177     let err = v:exception
2178   finally
2179     redraw!
2180     let [&l:mp, &l:efm] = [mp, efm]
2181     if exists('old_editor')
2182       let $GIT_EDITOR = old_editor
2183     endif
2184     execute cd fnameescape(cwd)
2185   endtry
2186   call fugitive#ReloadStatus()
2187   if empty(filter(getqflist(),'v:val.valid'))
2188     if !had_merge_msg && filereadable(b:git_dir . '/MERGE_MSG')
2189       cclose
2190       return 'Gcommit --no-status -n -t '.s:shellesc(b:git_dir . '/MERGE_MSG')
2191     endif
2192   endif
2193   let qflist = getqflist()
2194   let found = 0
2195   for e in qflist
2196     if !empty(e.bufnr)
2197       let found = 1
2198       let e.pattern = '^<<<<<<<'
2199     endif
2200   endfor
2201   call fugitive#Cwindow()
2202   if found
2203     call setqflist(qflist, 'r')
2204     if !a:bang
2205       return 'cfirst'
2206     endif
2207   endif
2208   return exists('err') ? 'echoerr '.string(err) : ''
2209 endfunction
2211 " Section: :Ggrep, :Glog
2213 if !exists('g:fugitive_summary_format')
2214   let g:fugitive_summary_format = '%s'
2215 endif
2217 function! s:GrepComplete(A, L, P) abort
2218   if strpart(a:L, 0, a:P) =~# ' -- '
2219     return fugitive#PathComplete(a:A, b:git_dir)
2220   else
2221     return fugitive#Complete(a:A, b:git_dir)
2222   endif
2223 endfunction
2225 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
2226 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
2227 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Glog :call s:Log('grep',<bang>0,<line1>,<count>,<q-args>)")
2228 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Gllog :call s:Log('lgrep',<bang>0,<line1>,<count>,<q-args>)")
2230 function! s:Grep(cmd,bang,arg) abort
2231   let grepprg = &grepprg
2232   let grepformat = &grepformat
2233   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2234   let dir = getcwd()
2235   try
2236     execute cd s:fnameescape(s:Tree())
2237     let &grepprg = s:UserCommand() . ' --no-pager grep -n --no-color'
2238     let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
2239     exe a:cmd.'! '.escape(s:ShellExpand(matchstr(a:arg, '\v\C.{-}%($|[''" ]\@=\|)@=')), '|#%')
2240     let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
2241     for entry in list
2242       if bufname(entry.bufnr) =~ ':'
2243         let entry.filename = s:Generate(bufname(entry.bufnr))
2244         unlet! entry.bufnr
2245         let changed = 1
2246       elseif a:arg =~# '\%(^\| \)--cached\>'
2247         let entry.filename = s:Generate(':0:'.bufname(entry.bufnr))
2248         unlet! entry.bufnr
2249         let changed = 1
2250       endif
2251     endfor
2252     if a:cmd =~# '^l' && exists('changed')
2253       call setloclist(0, list, 'r')
2254     elseif exists('changed')
2255       call setqflist(list, 'r')
2256     endif
2257     if !a:bang && !empty(list)
2258       return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
2259     else
2260       return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
2261     endif
2262   finally
2263     let &grepprg = grepprg
2264     let &grepformat = grepformat
2265     execute cd s:fnameescape(dir)
2266   endtry
2267 endfunction
2269 function! s:Log(cmd, bang, line1, line2, ...) abort
2270   let args = ' ' . join(a:000, ' ')
2271   let before = substitute(args, ' --\S\@!.*', '', '')
2272   let after = strpart(args, len(before))
2273   let path = s:Relative('/')
2274   let relative = path[1:-1]
2275   if path =~# '^/\.git\%(/\|$\)' || len(after)
2276     let path = ''
2277   endif
2278   if before !~# '\s[^[:space:]-]'
2279     let owner = s:Owner(@%)
2280     if len(owner)
2281       let before .= ' ' . s:shellesc(owner)
2282     endif
2283   endif
2284   if relative =~# '^\.git\%(/\|$\)'
2285     let relative = ''
2286   endif
2287   if len(relative) && a:line2 > 0
2288     let before .= ' -L ' . s:shellesc(a:line1 . ',' . a:line2 . ':' . relative)
2289   elseif len(relative) && (empty(after) || a:line2 == 0)
2290     let after = (len(after) > 3 ? after : ' -- ') . relative
2291   endif
2292   let grepformat = &grepformat
2293   let grepprg = &grepprg
2294   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2295   let dir = getcwd()
2296   try
2297     execute cd s:fnameescape(s:Tree())
2298     let &grepprg = escape(s:UserCommand() . ' --no-pager log --no-color ' .
2299           \ s:shellesc('--pretty=format:fugitive://'.b:git_dir.'//%H'.path.'::'.g:fugitive_summary_format), '%#')
2300     let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
2301     exe a:cmd . (a:bang ? '! ' : ' ') . s:ShellExpand(before . after)
2302   finally
2303     let &grepformat = grepformat
2304     let &grepprg = grepprg
2305     execute cd s:fnameescape(dir)
2306   endtry
2307 endfunction
2309 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
2311 function! s:UsableWin(nr) abort
2312   return a:nr && !getwinvar(a:nr, '&previewwindow') &&
2313         \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
2314 endfunction
2316 function! s:EditParse(args) abort
2317   let pre = []
2318   let args = copy(a:args)
2319   while !empty(args) && args[0] =~# '^+'
2320     call add(pre, ' ' . escape(remove(args, 0), ' |"'))
2321   endwhile
2322   if len(args)
2323     let file = join(args)
2324   elseif empty(expand('%'))
2325     let file = ':'
2326   elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
2327     let file = ':0:%'
2328   else
2329     let file = '%'
2330   endif
2331   return [s:Expand(file), join(pre)]
2332 endfunction
2334 function! s:BlurStatus() abort
2335   if &previewwindow && get(b:,'fugitive_type', '') ==# 'index'
2336     let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
2337     if len(winnrs)
2338       exe winnrs[0].'wincmd w'
2339     elseif winnr('$') == 1
2340       let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
2341       execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
2342     else
2343       rightbelow new
2344     endif
2345     if &diff
2346       let mywinnr = winnr()
2347       for winnr in range(winnr('$'),1,-1)
2348         if winnr != mywinnr && getwinvar(winnr,'&diff')
2349           execute winnr.'wincmd w'
2350           close
2351           if winnr('$') > 1
2352             wincmd p
2353           endif
2354         endif
2355       endfor
2356       diffoff!
2357     endif
2358   endif
2359 endfunction
2361 function! s:Edit(cmd, bang, mods, args, ...) abort
2362   let mods = a:mods ==# '<mods>' ? '' : a:mods
2364   if a:bang
2365     let temp = tempname()
2366     let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2367     let cwd = getcwd()
2368     try
2369       execute cd s:fnameescape(s:Tree())
2370       let git = s:UserCommand()
2371       let args = s:ShellExpand(a:args)
2372       silent! execute '!' . escape(git . ' --no-pager ' . args, '!#%') .
2373             \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
2374     finally
2375       execute cd s:fnameescape(cwd)
2376     endtry
2377     let temp = s:Resolve(temp)
2378     let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'git' }
2379     if a:cmd ==# 'edit'
2380       call s:BlurStatus()
2381     endif
2382     silent execute mods a:cmd temp
2383     call fugitive#ReloadStatus()
2384     return 'redraw|echo ' . string(':!' . git . ' ' . args)
2385   endif
2387   let [file, pre] = s:EditParse(a:000)
2388   try
2389     let file = s:Generate(file)
2390   catch /^fugitive:/
2391     return 'echoerr v:errmsg'
2392   endtry
2393   if file !~# '^\a\a\+:'
2394     let file = s:sub(file, '/$', '')
2395   endif
2396   if a:cmd ==# 'edit'
2397     call s:BlurStatus()
2398   endif
2399   return mods . ' ' . a:cmd . pre . ' ' . s:fnameescape(file)
2400 endfunction
2402 function! s:Read(count, line1, line2, range, bang, mods, args, ...) abort
2403   let mods = a:mods ==# '<mods>' ? '' : a:mods
2404   let after = a:line2
2405   if a:count < 0
2406     let delete = 'silent 1,' . line('$') . 'delete_|'
2407     let after = line('$')
2408   elseif a:range == 2
2409     let delete = 'silent ' . a:line1 . ',' . a:line2 . 'delete_|'
2410   else
2411     let delete = ''
2412   endif
2413   if a:bang
2414     let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2415     let cwd = getcwd()
2416     try
2417       execute cd s:fnameescape(s:Tree())
2418       let git = s:UserCommand()
2419       let args = s:ShellExpand(a:args)
2420       silent execute mods after.'read!' escape(git . ' --no-pager ' . args, '!#%')
2421     finally
2422       execute cd s:fnameescape(cwd)
2423     endtry
2424     execute delete . 'diffupdate'
2425     call fugitive#ReloadStatus()
2426     return 'redraw|echo '.string(':!'.git.' '.args)
2427   endif
2428   let [file, pre] = s:EditParse(a:000)
2429   try
2430     let file = s:Generate(file)
2431   catch /^fugitive:/
2432     return 'echoerr v:errmsg'
2433   endtry
2434   if file =~# '^fugitive:' && after is# 0
2435     return 'exe ' .string(mods . ' ' . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
2436   endif
2437   return mods . ' ' . after . 'read' . pre . ' ' . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
2438 endfunction
2440 function! s:EditRunComplete(A,L,P) abort
2441   if a:L =~# '^\w\+!'
2442     return s:GitComplete(a:A, a:L, a:P)
2443   else
2444     return fugitive#Complete(a:A, a:L, a:P)
2445   endif
2446 endfunction
2448 call s:command("-bar -bang -nargs=*           -complete=customlist,fugitive#Complete Ge       execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2449 call s:command("-bar -bang -nargs=*           -complete=customlist,fugitive#Complete Gedit    execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2450 call s:command("-bar -bang -nargs=*           -complete=customlist,s:EditRunComplete Gpedit   execute s:Edit('pedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2451 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>)")
2452 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>)")
2453 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>)")
2454 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>)")
2456 " Section: :Gwrite, :Gwq
2458 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwrite :execute s:Write(<bang>0,<f-args>)")
2459 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gw :execute s:Write(<bang>0,<f-args>)")
2460 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwq :execute s:Wq(<bang>0,<f-args>)")
2462 function! s:Write(force,...) abort
2463   if exists('b:fugitive_commit_arguments')
2464     return 'write|bdelete'
2465   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
2466     return 'wq'
2467   elseif get(b:, 'fugitive_type', '') ==# 'index'
2468     return 'Gcommit'
2469   elseif &buftype ==# 'nowrite' && getline(4) =~# '^+++ '
2470     let filename = getline(4)[6:-1]
2471     setlocal buftype=
2472     silent write
2473     setlocal buftype=nowrite
2474     if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
2475       let err = s:TreeChomp('apply', '--cached', '--reverse', '--', expand('%:p'))
2476     else
2477       let err = s:TreeChomp('apply', '--cached', '--', expand('%:p'))
2478     endif
2479     if err !=# ''
2480       let v:errmsg = split(err,"\n")[0]
2481       return 'echoerr v:errmsg'
2482     elseif a:force
2483       return 'bdelete'
2484     else
2485       return 'Gedit '.fnameescape(filename)
2486     endif
2487   endif
2488   let mytab = tabpagenr()
2489   let mybufnr = bufnr('')
2490   let file = a:0 ? s:Generate(s:Expand(join(a:000, ' '))) : fugitive#Real(@%)
2491   if empty(file)
2492     return 'echoerr '.string('fugitive: cannot determine file path')
2493   endif
2494   if file =~# '^fugitive:'
2495     return 'write' . (a:force ? '! ' : ' ') . s:fnameescape(file)
2496   endif
2497   let always_permitted = s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^0\=$'
2498   if !always_permitted && !a:force && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
2499     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
2500     return 'echoerr v:errmsg'
2501   endif
2502   let treebufnr = 0
2503   for nr in range(1,bufnr('$'))
2504     if fnamemodify(bufname(nr),':p') ==# file
2505       let treebufnr = nr
2506     endif
2507   endfor
2509   if treebufnr > 0 && treebufnr != bufnr('')
2510     let temp = tempname()
2511     silent execute '%write '.temp
2512     for tab in [mytab] + range(1,tabpagenr('$'))
2513       for winnr in range(1,tabpagewinnr(tab,'$'))
2514         if tabpagebuflist(tab)[winnr-1] == treebufnr
2515           execute 'tabnext '.tab
2516           if winnr != winnr()
2517             execute winnr.'wincmd w'
2518             let restorewinnr = 1
2519           endif
2520           try
2521             let lnum = line('.')
2522             let last = line('$')
2523             silent execute '$read '.temp
2524             silent execute '1,'.last.'delete_'
2525             silent write!
2526             silent execute lnum
2527             let did = 1
2528           finally
2529             if exists('restorewinnr')
2530               wincmd p
2531             endif
2532             execute 'tabnext '.mytab
2533           endtry
2534         endif
2535       endfor
2536     endfor
2537     if !exists('did')
2538       call writefile(readfile(temp,'b'),file,'b')
2539     endif
2540   else
2541     execute 'write! '.s:fnameescape(file)
2542   endif
2544   if a:force
2545     let error = s:TreeChomp('add', '--force', '--', file)
2546   else
2547     let error = s:TreeChomp('add', '--', file)
2548   endif
2549   if v:shell_error
2550     let v:errmsg = 'fugitive: '.error
2551     return 'echoerr v:errmsg'
2552   endif
2553   if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
2554     set nomodified
2555   endif
2557   let one = s:Generate(':1:'.file)
2558   let two = s:Generate(':2:'.file)
2559   let three = s:Generate(':3:'.file)
2560   for nr in range(1,bufnr('$'))
2561     let name = fnamemodify(bufname(nr), ':p')
2562     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
2563       execute nr.'bdelete'
2564     endif
2565   endfor
2567   unlet! restorewinnr
2568   let zero = s:Generate(':0:'.file)
2569   silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
2570   for tab in range(1,tabpagenr('$'))
2571     for winnr in range(1,tabpagewinnr(tab,'$'))
2572       let bufnr = tabpagebuflist(tab)[winnr-1]
2573       let bufname = fnamemodify(bufname(bufnr), ':p')
2574       if bufname ==# zero && bufnr != mybufnr
2575         execute 'tabnext '.tab
2576         if winnr != winnr()
2577           execute winnr.'wincmd w'
2578           let restorewinnr = 1
2579         endif
2580         try
2581           let lnum = line('.')
2582           let last = line('$')
2583           silent execute '$read '.s:fnameescape(file)
2584           silent execute '1,'.last.'delete_'
2585           silent execute lnum
2586           set nomodified
2587           diffupdate
2588         finally
2589           if exists('restorewinnr')
2590             wincmd p
2591           endif
2592           execute 'tabnext '.mytab
2593         endtry
2594         break
2595       endif
2596     endfor
2597   endfor
2598   call fugitive#ReloadStatus()
2599   return 'checktime'
2600 endfunction
2602 function! s:Wq(force,...) abort
2603   let bang = a:force ? '!' : ''
2604   if exists('b:fugitive_commit_arguments')
2605     return 'wq'.bang
2606   endif
2607   let result = call(s:function('s:Write'),[a:force]+a:000)
2608   if result =~# '^\%(write\|wq\|echoerr\)'
2609     return s:sub(result,'^write','wq')
2610   else
2611     return result.'|quit'.bang
2612   endif
2613 endfunction
2615 augroup fugitive_commit
2616   autocmd!
2617   autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
2618 augroup END
2620 " Section: :Gpush, :Gfetch
2622 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush  execute s:Dispatch('<bang>', 'push '.<q-args>)")
2623 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
2625 function! s:Dispatch(bang, args)
2626   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2627   let cwd = getcwd()
2628   let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
2629   try
2630     let b:current_compiler = 'git'
2631     let &l:errorformat = s:common_efm
2632     execute cd fnameescape(s:Tree())
2633     let &l:makeprg = substitute(s:UserCommand() . ' ' . a:args, '\s\+$', '', '')
2634     if exists(':Make') == 2
2635       noautocmd Make
2636     else
2637       silent noautocmd make!
2638       redraw!
2639       return 'call fugitive#Cwindow()'
2640     endif
2641     return ''
2642   finally
2643     let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
2644     if empty(cc) | unlet! b:current_compiler | endif
2645     execute cd fnameescape(cwd)
2646   endtry
2647 endfunction
2649 " Section: :Gdiff
2651 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
2652 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
2653 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
2655 augroup fugitive_diff
2656   autocmd!
2657   autocmd BufWinLeave *
2658         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
2659         \   call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
2660         \ endif
2661   autocmd BufWinEnter *
2662         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
2663         \   call s:diffoff() |
2664         \ endif
2665 augroup END
2667 function! s:can_diffoff(buf) abort
2668   return getwinvar(bufwinnr(a:buf), '&diff') &&
2669         \ !empty(getbufvar(a:buf, 'git_dir')) &&
2670         \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
2671 endfunction
2673 function! fugitive#CanDiffoff(buf) abort
2674   return s:can_diffoff(a:buf)
2675 endfunction
2677 function! s:diff_modifier(count) abort
2678   let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
2679   if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
2680     return 'keepalt '
2681   elseif &diffopt =~# 'vertical'
2682     return 'keepalt vert '
2683   elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
2684     return 'keepalt '
2685   else
2686     return 'keepalt vert '
2687   endif
2688 endfunction
2690 function! s:diff_window_count() abort
2691   let c = 0
2692   for nr in range(1,winnr('$'))
2693     let c += getwinvar(nr,'&diff')
2694   endfor
2695   return c
2696 endfunction
2698 function! s:diff_restore() abort
2699   let restore = 'setlocal nodiff noscrollbind'
2700         \ . ' scrollopt=' . &l:scrollopt
2701         \ . (&l:wrap ? ' wrap' : ' nowrap')
2702         \ . ' foldlevel=999'
2703         \ . ' foldmethod=' . &l:foldmethod
2704         \ . ' foldcolumn=' . &l:foldcolumn
2705         \ . ' foldlevel=' . &l:foldlevel
2706         \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
2707   if has('cursorbind')
2708     let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
2709   endif
2710   return restore
2711 endfunction
2713 function! s:diffthis() abort
2714   if !&diff
2715     let w:fugitive_diff_restore = s:diff_restore()
2716     diffthis
2717   endif
2718 endfunction
2720 function! s:diffoff() abort
2721   if exists('w:fugitive_diff_restore')
2722     execute w:fugitive_diff_restore
2723     unlet w:fugitive_diff_restore
2724   else
2725     diffoff
2726   endif
2727 endfunction
2729 function! s:diffoff_all(dir) abort
2730   let curwin = winnr()
2731   for nr in range(1,winnr('$'))
2732     if getwinvar(nr,'&diff')
2733       if nr != winnr()
2734         execute nr.'wincmd w'
2735         let restorewinnr = 1
2736       endif
2737       if exists('b:git_dir') && b:git_dir ==# a:dir
2738         call s:diffoff()
2739       endif
2740     endif
2741   endfor
2742   execute curwin.'wincmd w'
2743 endfunction
2745 function! s:CompareAge(mine, theirs) abort
2746   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
2747   let mine = substitute(a:mine, '^:', '', '')
2748   let theirs = substitute(a:theirs, '^:', '', '')
2749   let my_score    = get(scores, ':'.mine, 0)
2750   let their_score = get(scores, ':'.theirs, 0)
2751   if my_score || their_score
2752     return my_score < their_score ? -1 : my_score != their_score
2753   elseif mine ==# theirs
2754     return 0
2755   endif
2756   let base = s:TreeChomp('merge-base', mine, theirs)
2757   if base ==# mine
2758     return -1
2759   elseif base ==# theirs
2760     return 1
2761   endif
2762   let my_time    = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:mine, '--')
2763   let their_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:theirs, '--')
2764   return my_time < their_time ? -1 : my_time != their_time
2765 endfunction
2767 function! s:Diff(vert,keepfocus,...) abort
2768   let args = copy(a:000)
2769   let post = ''
2770   if get(args, 0) =~# '^+'
2771     let post = remove(args, 0)[1:-1]
2772   endif
2773   let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
2774   let commit = s:DirCommitFile(@%)[1]
2775   let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
2776   if exists(':DiffGitCached')
2777     return 'DiffGitCached'
2778   elseif (empty(args) || args[0] ==# ':') && commit =~# '^[0-1]\=$' && !empty(s:TreeChomp('ls-files', '--unmerged', '--', expand('%:p')))
2779     if v:shell_error
2780       return 'echoerr ' . string("fugitive: error determining merge status of the current buffer")
2781     endif
2782     let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
2783     let nr = bufnr('')
2784     execute 'leftabove '.vert.'split' s:fnameescape(s:Generate(s:Relative(':2:')))
2785     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2786     let nr2 = bufnr('')
2787     call s:diffthis()
2788     exe back
2789     execute 'rightbelow '.vert.'split' s:fnameescape(s:Generate(s:Relative(':3:')))
2790     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2791     let nr3 = bufnr('')
2792     call s:diffthis()
2793     exe back
2794     call s:diffthis()
2795     execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
2796     execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
2797     return post
2798   elseif len(args)
2799     let arg = join(args, ' ')
2800     if arg ==# ''
2801       return post
2802     elseif arg ==# '/'
2803       let file = s:Relative()
2804     elseif arg ==# ':'
2805       let file = s:Relative(':0:')
2806     elseif arg =~# '^:/.'
2807       try
2808         let file = fugitive#RevParse(arg).s:Relative(':')
2809       catch /^fugitive:/
2810         return 'echoerr v:errmsg'
2811       endtry
2812     else
2813       let file = s:Expand(arg)
2814     endif
2815     if file !~# ':' && file !~# '^/' && s:TreeChomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
2816       let file = file.s:Relative(':')
2817     endif
2818   else
2819     let file = empty(commit) ? s:Relative(':0:') : s:Relative()
2820   endif
2821   try
2822     let spec = s:Generate(file)
2823     let restore = s:diff_restore()
2824     if exists('+cursorbind')
2825       setlocal cursorbind
2826     endif
2827     let w:fugitive_diff_restore = restore
2828     if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
2829       execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
2830     else
2831       execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
2832     endif
2833     let &l:readonly = &l:readonly
2834     redraw
2835     let w:fugitive_diff_restore = restore
2836     let winnr = winnr()
2837     if getwinvar('#', '&diff')
2838       exe back
2839       if !a:keepfocus
2840         call feedkeys(winnr."\<C-W>w", 'n')
2841       endif
2842     endif
2843     return post
2844   catch /^fugitive:/
2845     return 'echoerr v:errmsg'
2846   endtry
2847 endfunction
2849 " Section: :Gmove, :Gremove
2851 function! s:Move(force, rename, destination) abort
2852   if a:destination =~# '^\.\.\=\%(/\|$\)'
2853     let destination = simplify(getcwd() . '/' . a:destination)
2854   elseif a:destination =~# '^\a\+:\|^/'
2855     let destination = a:destination
2856   elseif a:destination =~# '^:/:\='
2857     let destination = s:Tree() . substitute(a:destination, '^:/:\=', '', '')
2858   elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\))'
2859     let destination = s:Tree() . matchstr(a:destination, ')\zs.*')
2860   elseif a:destination =~# '^:(literal)'
2861     let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
2862   elseif a:rename
2863     let destination = expand('%:p:s?[\/]$??:h') . '/' . a:destination
2864   else
2865     let destination = s:Tree() . '/' . a:destination
2866   endif
2867   let destination = s:Slash(destination)
2868   if isdirectory(@%)
2869     setlocal noswapfile
2870   endif
2871   let message = call('s:TreeChomp', ['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination])
2872   if v:shell_error
2873     let v:errmsg = 'fugitive: '.message
2874     return 'echoerr v:errmsg'
2875   endif
2876   if isdirectory(destination)
2877     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
2878   endif
2879   call fugitive#ReloadStatus()
2880   if empty(s:DirCommitFile(@%)[1])
2881     if isdirectory(destination)
2882       return 'keepalt edit '.s:fnameescape(destination)
2883     else
2884       return 'keepalt saveas! '.s:fnameescape(destination)
2885     endif
2886   else
2887     return 'file '.s:fnameescape(s:Generate(':0:'.destination))
2888   endif
2889 endfunction
2891 function! s:RenameComplete(A,L,P) abort
2892   if a:A =~# '^[.:]\=/'
2893     return fugitive#PathComplete(a:A)
2894   else
2895     let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
2896     return map(fugitive#PathComplete(pre.a:A), 'strpart(v:val, len(pre))')
2897   endif
2898 endfunction
2900 function! s:Remove(after, force) abort
2901   if s:DirCommitFile(@%)[1] ==# ''
2902     let cmd = ['rm']
2903   elseif s:DirCommitFile(@%)[1] ==# '0'
2904     let cmd = ['rm','--cached']
2905   else
2906     let v:errmsg = 'fugitive: rm not supported here'
2907     return 'echoerr v:errmsg'
2908   endif
2909   if a:force
2910     let cmd += ['--force']
2911   endif
2912   let message = call('s:TreeChomp', cmd + ['--', expand('%:p')])
2913   if v:shell_error
2914     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
2915     return 'echoerr '.string(v:errmsg)
2916   else
2917     call fugitive#ReloadStatus()
2918     return a:after . (a:force ? '!' : '')
2919   endif
2920 endfunction
2922 augroup fugitive_remove
2923   autocmd!
2924   autocmd User Fugitive if s:DirCommitFile(@%)[1] =~# '^0\=$' |
2925         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,fugitive#PathComplete Gmove :execute s:Move(<bang>0,0,<q-args>)" |
2926         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
2927         \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
2928         \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
2929         \ endif
2930 augroup END
2932 " Section: :Gblame
2934 function! s:Keywordprg() abort
2935   let args = ' --git-dir='.escape(b:git_dir,"\\\"' ")
2936   if has('gui_running') && !has('win32')
2937     return s:UserCommand() . ' --no-pager' . args . ' log -1'
2938   else
2939     return s:UserCommand() . args . ' show'
2940   endif
2941 endfunction
2943 augroup fugitive_blame
2944   autocmd!
2945   autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:Keywordprg() | endif
2946   autocmd Syntax fugitiveblame call s:BlameSyntax()
2947   autocmd User Fugitive
2948         \ if get(b:, 'fugitive_type') =~# '^\%(file\|blob\|blame\)$' || filereadable(@%) |
2949         \   exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,'<mods>',[<f-args>])" |
2950         \ endif
2951   autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
2952   autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
2953 augroup END
2955 function! s:linechars(pattern) abort
2956   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
2957   if exists('*synconcealed') && &conceallevel > 1
2958     for col in range(1, chars)
2959       let chars -= synconcealed(line('.'), col)[0]
2960     endfor
2961   endif
2962   return chars
2963 endfunction
2965 function! s:Blame(bang, line1, line2, count, mods, args) abort
2966   if exists('b:fugitive_blamed_bufnr')
2967     return 'bdelete'
2968   endif
2969   try
2970     if empty(s:Relative('/'))
2971       call s:throw('file or blob required')
2972     endif
2973     if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
2974       call s:throw('unsupported option')
2975     endif
2976     call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
2977     let cmd = ['--no-pager', 'blame', '--show-number']
2978     if a:count
2979       let cmd += ['-L', a:line1 . ',' . a:line1]
2980     endif
2981     let cmd += a:args
2982     if s:DirCommitFile(@%)[1] =~# '\D\|..'
2983       let cmd += [s:DirCommitFile(@%)[1]]
2984     else
2985       let cmd += ['--contents', '-']
2986     endif
2987     let cmd += ['--', expand('%:p')]
2988     let basecmd = escape(fugitive#Prepare(cmd), '!#%')
2989     try
2990       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2991       let tree = s:Tree()
2992       if len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
2993         let cwd = getcwd()
2994         execute cd s:fnameescape(tree)
2995       endif
2996       let error = tempname()
2997       let temp = error.'.fugitiveblame'
2998       if &shell =~# 'csh'
2999         silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
3000       else
3001         silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
3002       endif
3003       if exists('l:cwd')
3004         execute cd s:fnameescape(cwd)
3005         unlet cwd
3006       endif
3007       if v:shell_error
3008         call s:throw(join(readfile(error),"\n"))
3009       endif
3010       if a:count
3011         let edit = substitute(a:mods, '^<mods>$', '', '') . get(['edit', 'split', 'pedit'], a:line2 - a:line1, ' split')
3012         return s:BlameCommit(edit, get(readfile(temp), 0, ''))
3013       else
3014         for winnr in range(winnr('$'),1,-1)
3015           call setwinvar(winnr, '&scrollbind', 0)
3016           if exists('+cursorbind')
3017             call setwinvar(winnr, '&cursorbind', 0)
3018           endif
3019           if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
3020             execute winbufnr(winnr).'bdelete'
3021           endif
3022         endfor
3023         let bufnr = bufnr('')
3024         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
3025         if exists('+cursorbind')
3026           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
3027         endif
3028         if &l:wrap
3029           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
3030         endif
3031         if &l:foldenable
3032           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
3033         endif
3034         setlocal scrollbind nowrap nofoldenable
3035         if exists('+cursorbind')
3036           setlocal cursorbind
3037         endif
3038         let top = line('w0') + &scrolloff
3039         let current = line('.')
3040         let temp = s:Resolve(temp)
3041         let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'fugitiveblame', 'args': cmd, 'bufnr': bufnr }
3042         exe 'keepalt leftabove vsplit '.temp
3043         let b:fugitive_blamed_bufnr = bufnr
3044         let b:fugitive_type = 'blame'
3045         let w:fugitive_leave = restore
3046         let b:fugitive_blame_arguments = join(a:args,' ')
3047         execute top
3048         normal! zt
3049         execute current
3050         if exists('+cursorbind')
3051           setlocal cursorbind
3052         endif
3053         setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame buftype=nowrite
3054         if exists('+concealcursor')
3055           setlocal concealcursor=nc conceallevel=2
3056         endif
3057         if exists('+relativenumber')
3058           setlocal norelativenumber
3059         endif
3060         execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
3061         nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
3062         nnoremap <buffer> <silent> g?   :help fugitive-:Gblame<CR>
3063         nnoremap <buffer> <silent> q    :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
3064         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>
3065         nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
3066         nnoremap <buffer> <silent> -    :<C-U>exe <SID>BlameJump('')<CR>
3067         nnoremap <buffer> <silent> P    :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
3068         nnoremap <buffer> <silent> ~    :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
3069         nnoremap <buffer> <silent> i    :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
3070         nnoremap <buffer> <silent> o    :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
3071         nnoremap <buffer> <silent> O    :<C-U>exe <SID>BlameCommit("tabedit")<CR>
3072         nnoremap <buffer> <silent> p    :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft").' pedit', 0, '', matchstr(getline('.'), '\x\+'), matchstr(getline('.'), '\x\+'))<CR>
3073         nnoremap <buffer> <silent> A    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
3074         nnoremap <buffer> <silent> C    :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
3075         nnoremap <buffer> <silent> D    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
3076         redraw
3077         syncbind
3078       endif
3079     finally
3080       if exists('l:cwd')
3081         execute cd s:fnameescape(cwd)
3082       endif
3083     endtry
3084     return ''
3085   catch /^fugitive:/
3086     return 'echoerr v:errmsg'
3087   endtry
3088 endfunction
3090 function! s:BlameCommit(cmd, ...) abort
3091   let line = a:0 ? a:1 : getline('.')
3092   if line =~# '^0\{4,40\} '
3093     return 'echoerr ' . string('Not Committed Yet')
3094   endif
3095   let cmd = s:Edit(a:cmd, 0, '', matchstr(line, '\x\+'), matchstr(line, '\x\+'))
3096   if cmd =~# '^echoerr'
3097     return cmd
3098   endif
3099   let lnum = matchstr(line, ' \zs\d\+\ze\s\+[([:digit:]]')
3100   let path = matchstr(line, '^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
3101   if path ==# ''
3102     let path = fugitive#Path(a:0 ? @% : bufname(b:fugitive_blamed_bufnr), '')
3103   endif
3104   execute cmd
3105   if a:cmd ==# 'pedit'
3106     return ''
3107   endif
3108   if search('^diff .* b/\M'.escape(path,'\').'$','W')
3109     call search('^+++')
3110     let head = line('.')
3111     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
3112       let top = +matchstr(getline('.'),' +\zs\d\+')
3113       let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
3114       if lnum >= top && lnum <= top + len
3115         let offset = lnum - top
3116         if &scrolloff
3117           +
3118           normal! zt
3119         else
3120           normal! zt
3121           +
3122         endif
3123         while offset > 0 && line('.') < line('$')
3124           +
3125           if getline('.') =~# '^[ +]'
3126             let offset -= 1
3127           endif
3128         endwhile
3129         return 'normal! zv'
3130       endif
3131     endwhile
3132     execute head
3133     normal! zt
3134   endif
3135   return ''
3136 endfunction
3138 function! s:BlameJump(suffix) abort
3139   let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
3140   if commit =~# '^0\+$'
3141     let commit = ':0'
3142   endif
3143   let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
3144   let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
3145   if path ==# ''
3146     let path = fugitive#Path(bufname(b:fugitive_blamed_bufnr), '')
3147   endif
3148   let args = b:fugitive_blame_arguments
3149   let offset = line('.') - line('w0')
3150   let bufnr = bufnr('%')
3151   let winnr = bufwinnr(b:fugitive_blamed_bufnr)
3152   if winnr > 0
3153     exe winnr.'wincmd w'
3154   endif
3155   execute 'Gedit' s:fnameescape(commit . a:suffix . ':' . path)
3156   execute lnum
3157   if winnr > 0
3158     exe bufnr.'bdelete'
3159   endif
3160   if exists(':Gblame')
3161     execute 'Gblame '.args
3162     execute lnum
3163     let delta = line('.') - line('w0') - offset
3164     if delta > 0
3165       execute 'normal! '.delta."\<C-E>"
3166     elseif delta < 0
3167       execute 'normal! '.(-delta)."\<C-Y>"
3168     endif
3169     syncbind
3170   endif
3171   return ''
3172 endfunction
3174 let s:hash_colors = {}
3176 function! s:BlameSyntax() abort
3177   let b:current_syntax = 'fugitiveblame'
3178   let conceal = has('conceal') ? ' conceal' : ''
3179   let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
3180   syn match FugitiveblameBoundary "^\^"
3181   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
3182   syn match FugitiveblameHash       "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3183   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3184   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
3185   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
3186   exec 'syn match FugitiveblameLineNumber         " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
3187   exec 'syn match FugitiveblameOriginalFile       " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
3188   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
3189   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
3190   syn match FugitiveblameShort              " \d\+)" contained contains=FugitiveblameLineNumber
3191   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
3192   hi def link FugitiveblameBoundary           Keyword
3193   hi def link FugitiveblameHash               Identifier
3194   hi def link FugitiveblameUncommitted        Ignore
3195   hi def link FugitiveblameTime               PreProc
3196   hi def link FugitiveblameLineNumber         Number
3197   hi def link FugitiveblameOriginalFile       String
3198   hi def link FugitiveblameOriginalLineNumber Float
3199   hi def link FugitiveblameShort              FugitiveblameDelimiter
3200   hi def link FugitiveblameDelimiter          Delimiter
3201   hi def link FugitiveblameNotCommittedYet    Comment
3202   let seen = {}
3203   for lnum in range(1, line('$'))
3204     let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
3205     if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
3206       continue
3207     endif
3208     let seen[hash] = 1
3209     if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
3210           \ && empty(get(s:hash_colors, hash))
3211       let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
3212       let color = csapprox#per_component#Approximate(r, g, b)
3213       if color == 16 && &background ==# 'dark'
3214         let color = 8
3215       endif
3216       let s:hash_colors[hash] = ' ctermfg='.color
3217     else
3218       let s:hash_colors[hash] = ''
3219     endif
3220     exe 'syn match FugitiveblameHash'.hash.'       "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
3221   endfor
3222   call s:RehighlightBlame()
3223 endfunction
3225 function! s:RehighlightBlame() abort
3226   for [hash, cterm] in items(s:hash_colors)
3227     if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
3228       exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
3229     else
3230       exe 'hi link FugitiveblameHash'.hash.' Identifier'
3231     endif
3232   endfor
3233 endfunction
3235 " Section: :Gbrowse
3237 call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,fugitive#Complete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
3239 let s:redirects = {}
3241 function! s:Browse(bang,line1,count,...) abort
3242   try
3243     let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
3244     if a:0
3245       let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
3246       let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
3247     else
3248       let remote = ''
3249       let rev = ''
3250     endif
3251     if rev ==# ''
3252       let rev = s:DirRev(@%)[1]
3253     endif
3254     if rev =~# '^:\=$'
3255       let expanded = s:Relative()
3256     else
3257       let expanded = s:Expand(rev)
3258     endif
3259     let cdir = fugitive#CommonDir(b:git_dir)
3260     for dir in ['tags/', 'heads/', 'remotes/']
3261       if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . dir . expanded)
3262         let expanded = '.git/refs/' . dir . expanded
3263       endif
3264     endfor
3265     let full = s:Generate(expanded)
3266     let commit = ''
3267     if full =~? '^fugitive:'
3268       let [dir, commit, path] = s:DirCommitFile(full)
3269       if commit =~# '^:\=\d$'
3270         let commit = ''
3271       endif
3272       if commit =~ '..'
3273         let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
3274         let branch = matchstr(expanded, '^[^:]*')
3275       else
3276         let type = 'blob'
3277       endif
3278       let path = path[1:-1]
3279     elseif empty(s:Tree())
3280       let path = '.git/' . full[strlen(b:git_dir)+1:-1]
3281       let type = ''
3282     else
3283       let path = full[strlen(s:Tree())+1:-1]
3284       if path =~# '^\.git/'
3285         let type = ''
3286       elseif isdirectory(full)
3287         let type = 'tree'
3288       else
3289         let type = 'blob'
3290       endif
3291     endif
3292     if type ==# 'tree' && !empty(path)
3293       let path = s:sub(path, '/\=$', '/')
3294     endif
3295     if path =~# '^\.git/.*HEAD$' && filereadable(b:git_dir . '/' . path[5:-1])
3296       let body = readfile(b:git_dir . '/' . path[5:-1])[0]
3297       if body =~# '^\x\{40\}$'
3298         let commit = body
3299         let type = 'commit'
3300         let path = ''
3301       elseif body =~# '^ref: refs/'
3302         let path = '.git/' . matchstr(body,'ref: \zs.*')
3303       endif
3304     endif
3306     let merge = ''
3307     if path =~# '^\.git/refs/remotes/.'
3308       if empty(remote)
3309         let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
3310         let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3311       else
3312         let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3313         let path = '.git/refs/heads/'.merge
3314       endif
3315     elseif path =~# '^\.git/refs/heads/.'
3316       let branch = path[16:-1]
3317     elseif !exists('branch')
3318       let branch = FugitiveHead()
3319     endif
3320     if !empty(branch)
3321       let r = fugitive#Config('branch.'.branch.'.remote')
3322       let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
3323       if r ==# '.' && !empty(m)
3324         let r2 = fugitive#Config('branch.'.m.'.remote')
3325         if r2 !~# '^\.\=$'
3326           let r = r2
3327           let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
3328         endif
3329       endif
3330       if empty(remote)
3331         let remote = r
3332       endif
3333       if r ==# '.' || r ==# remote
3334         let merge = m
3335         if path =~# '^\.git/refs/heads/.'
3336           let path = '.git/refs/heads/'.merge
3337         endif
3338       endif
3339     endif
3341     let line1 = a:count > 0 ? a:line1 : 0
3342     let line2 = a:count > 0 ? a:count : 0
3343     if empty(commit) && path !~# '^\.git/'
3344       if a:line1 && !a:count && !empty(merge)
3345         let commit = merge
3346       else
3347         let commit = ''
3348         if len(merge)
3349           let owner = s:Owner(@%)
3350           let commit = s:TreeChomp('merge-base', 'refs/remotes/' . remote . '/' . merge, empty(owner) ? 'HEAD' : owner, '--')
3351           if v:shell_error
3352             let commit = ''
3353           endif
3354           if a:count && !a:0 && commit =~# '^\x\{40\}$'
3355             let blame_list = tempname()
3356             call writefile([commit, ''], blame_list, 'b')
3357             let blame_in = tempname()
3358             silent exe '%write' blame_in
3359             let blame = split(s:TreeChomp('blame', '--contents', blame_in, '-L', a:line1.','.a:count, '-S', blame_list, '-s', '--show-number', './' . path), "\n")
3360             if !v:shell_error
3361               let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
3362               if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
3363                 let line1 = +matchstr(blame[0], blame_regex)
3364                 let line2 = +matchstr(blame[-1], blame_regex)
3365               else
3366                 call s:throw("Can't browse to uncommitted change")
3367               endif
3368             endif
3369           endif
3370         endif
3371       endif
3372       if empty(commit)
3373         let commit = readfile(b:git_dir . '/HEAD', '', 1)[0]
3374       endif
3375       let i = 0
3376       while commit =~# '^ref: ' && i < 10
3377         let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
3378         let i -= 1
3379       endwhile
3380     endif
3382     if empty(remote)
3383       let remote = '.'
3384     endif
3385     let raw = fugitive#RemoteUrl(remote)
3386     if empty(raw)
3387       let raw = remote
3388     endif
3390     if raw =~# '^https\=://' && s:executable('curl')
3391       if !has_key(s:redirects, raw)
3392         let s:redirects[raw] = matchstr(system('curl -I ' .
3393               \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
3394               \ 'Location: \zs\S\+\ze/info/refs?')
3395       endif
3396       if len(s:redirects[raw])
3397         let raw = s:redirects[raw]
3398       endif
3399     endif
3401     let opts = {
3402           \ 'dir': b:git_dir,
3403           \ 'repo': fugitive#repo(),
3404           \ 'remote': raw,
3405           \ 'revision': 'No longer provided',
3406           \ 'commit': commit,
3407           \ 'path': path,
3408           \ 'type': type,
3409           \ 'line1': line1,
3410           \ 'line2': line2}
3412     let url = ''
3413     for Handler in get(g:, 'fugitive_browse_handlers', [])
3414       let url = call(Handler, [copy(opts)])
3415       if !empty(url)
3416         break
3417       endif
3418     endfor
3420     if empty(url)
3421       call s:throw("No Gbrowse handler found for '".raw."'")
3422     endif
3424     let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
3425     if a:bang
3426       if has('clipboard')
3427         let @+ = url
3428       endif
3429       return 'echomsg '.string(url)
3430     elseif exists(':Browse') == 2
3431       return 'echomsg '.string(url).'|Browse '.url
3432     else
3433       if !exists('g:loaded_netrw')
3434         runtime! autoload/netrw.vim
3435       endif
3436       if exists('*netrw#BrowseX')
3437         return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
3438       else
3439         return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
3440       endif
3441     endif
3442   catch /^fugitive:/
3443     return 'echoerr v:errmsg'
3444   endtry
3445 endfunction
3447 " Section: Go to file
3449 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
3450 function! fugitive#MapCfile(...) abort
3451   exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
3452   let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
3453   if !exists('g:fugitive_no_maps')
3454     call s:map('n', 'gf',          '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
3455     call s:map('n', '<C-W>f',     '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3456     call s:map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3457     call s:map('n', '<C-W>gf',  '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
3458     call s:map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
3459   endif
3460 endfunction
3462 function! s:ContainingCommit() abort
3463   let commit = s:Owner(@%)
3464   return empty(commit) ? 'HEAD' : commit
3465 endfunction
3467 function! s:NavigateUp(count) abort
3468   let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
3469   let c = a:count
3470   while c
3471     if rev =~# ':.*/.'
3472       let rev = matchstr(rev, '.*\ze/.\+', '')
3473     elseif rev =~# '.:.'
3474       let rev = matchstr(rev, '^.[^:]*:')
3475     elseif rev =~# '^:'
3476       let rev = 'HEAD^{}'
3477     elseif rev =~# ':$'
3478       let rev = rev[0:-2]
3479     else
3480       return rev.'~'.c
3481     endif
3482     let c -= 1
3483   endwhile
3484   return rev
3485 endfunction
3487 function! fugitive#MapJumps(...) abort
3488   if get(b:, 'fugitive_type', '') ==# 'blob'
3489     nnoremap <buffer> <silent> <CR>    :<C-U>.Gblame<CR>
3490   else
3491     nnoremap <buffer> <silent> <CR>    :<C-U>exe <SID>GF("edit")<CR>
3492   endif
3493   if !&modifiable
3494     let nowait = v:version >= 704 ? '<nowait>' : ''
3495     if get(b:, 'fugitive_type', '') ==# 'blob'
3496       nnoremap <buffer> <silent> o     :<C-U>.,.+1Gblame<CR>
3497       nnoremap <buffer> <silent> S     :<C-U>vertical .,.+1Gblame<CR>
3498       nnoremap <buffer> <silent> O     :<C-U>tab .,.+1Gblame<CR>
3499       nnoremap <buffer> <silent> p     :<C-U>.,.+2Gblame<CR>
3500     else
3501       nnoremap <buffer> <silent> o     :<C-U>exe <SID>GF("split")<CR>
3502       nnoremap <buffer> <silent> S     :<C-U>exe <SID>GF("vsplit")<CR>
3503       nnoremap <buffer> <silent> O     :<C-U>exe <SID>GF("tabedit")<CR>
3504       nnoremap <buffer> <silent> p     :<C-U>exe <SID>GF("pedit")<CR>
3505     endif
3506     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>"
3507     nnoremap <buffer> <silent> P     :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>
3508     nnoremap <buffer> <silent> ~     :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>
3509     nnoremap <buffer> <silent> C     :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3510     nnoremap <buffer> <silent> cc    :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3511     nnoremap <buffer> <silent> co    :<C-U>exe 'Gsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3512     nnoremap <buffer> <silent> cS    :<C-U>exe 'Gvsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3513     nnoremap <buffer> <silent> cO    :<C-U>exe 'Gtabedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3514     nnoremap <buffer> <silent> cp    :<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3515     nmap     <buffer>          .     <SID>: <Plug><cfile><Home>
3516   endif
3517 endfunction
3519 function! s:StatusCfile(...) abort
3520   let pre = ''
3521   let tree = FugitiveTreeForGitDir(b:git_dir)
3522   let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
3523   if getline('.') =~# '^.\=\trenamed:.* -> '
3524     return lead . matchstr(getline('.'),' -> \zs.*')
3525   elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
3526     return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
3527   elseif getline('.') =~# '^.\=\t.'
3528     return lead . matchstr(getline('.'),'\t\zs.*')
3529   elseif getline('.') =~# ': needs merge$'
3530     return lead . matchstr(getline('.'),'.*\ze: needs merge$')
3531   elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
3532     return 'HEAD'
3533   elseif getline('.') =~# '^\%(. \)\=On branch '
3534     return 'refs/heads/'.getline('.')[12:]
3535   elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
3536     return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
3537   else
3538     return ''
3539   endif
3540 endfunction
3542 function! fugitive#StatusCfile() abort
3543   let file = s:Generate(s:StatusCfile())
3544   return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
3545 endfunction
3547 function! s:cfile() abort
3548   try
3549     let myhash = s:DirRev(@%)[1]
3550     if len(myhash)
3551       try
3552         let myhash = fugitive#RevParse(myhash)
3553       catch /^fugitive:/
3554         let myhash = ''
3555       endtry
3556     endif
3557     if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
3558       let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
3559     endif
3561     let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
3563     let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
3564           \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
3566     if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
3567       return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
3568     elseif showtree
3569       return [treebase . s:sub(getline('.'),'/$','')]
3571     else
3573       let dcmds = []
3575       " Index
3576       if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
3577         let ref = matchstr(getline('.'),'\x\{40\}')
3578         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
3579         return [file]
3580       endif
3582       if getline('.') =~# '^ref: '
3583         let ref = strpart(getline('.'),5)
3585       elseif getline('.') =~# '^commit \x\{40\}\>'
3586         let ref = matchstr(getline('.'),'\x\{40\}')
3587         return [ref]
3589       elseif getline('.') =~# '^parent \x\{40\}\>'
3590         let ref = matchstr(getline('.'),'\x\{40\}')
3591         let line = line('.')
3592         let parent = 0
3593         while getline(line) =~# '^parent '
3594           let parent += 1
3595           let line -= 1
3596         endwhile
3597         return [ref]
3599       elseif getline('.') =~# '^tree \x\{40\}$'
3600         let ref = matchstr(getline('.'),'\x\{40\}')
3601         if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
3602           let ref = myhash.':'
3603         endif
3604         return [ref]
3606       elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
3607         let ref = matchstr(getline('.'),'\x\{40\}')
3608         let type = matchstr(getline(line('.')+1),'type \zs.*')
3610       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
3611         let ref = s:DirRev(@%)[1]
3613       elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
3614         let ref = matchstr(getline('.'),'\x\{40\}')
3615         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
3617       elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
3618         let ref = getline('.')[4:]
3620       elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
3621         let type = getline('.')[0]
3622         let lnum = line('.') - 1
3623         let offset = 0
3624         while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3625           if getline(lnum) =~# '^[ '.type.']'
3626             let offset += 1
3627           endif
3628           let lnum -= 1
3629         endwhile
3630         let offset += matchstr(getline(lnum), type.'\zs\d\+')
3631         let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
3632         let dcmds = [offset, 'normal!zv']
3634       elseif getline('.') =~# '^rename from '
3635         let ref = 'a/'.getline('.')[12:]
3636       elseif getline('.') =~# '^rename to '
3637         let ref = 'b/'.getline('.')[10:]
3639       elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3640         let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
3641         let offset = matchstr(getline('.'), '+\zs\d\+')
3643         let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3644         let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3645         let dcmd = 'Gdiff! +'.offset
3647       elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3648         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3649         let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3650         let dcmd = 'Gdiff!'
3652       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3653         let line = getline(line('.')-1)
3654         let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3655         let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3656         let dcmd = 'Gdiff!'
3658       elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
3659         let ref = getline('.')
3661       elseif expand('<cword>') =~# '^\x\{7,40\}\>'
3662         return [expand('<cword>')]
3664       else
3665         let ref = ''
3666       endif
3668       let prefixes = {
3669             \ '1': '',
3670             \ '2': '',
3671             \ 'b': ':0:',
3672             \ 'i': ':0:',
3673             \ 'o': '',
3674             \ 'w': ''}
3676       if len(myhash)
3677         let prefixes.a = myhash.'^:'
3678         let prefixes.b = myhash.':'
3679       endif
3680       let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3681       if exists('dref')
3682         let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3683       endif
3685       if ref ==# '/dev/null'
3686         " Empty blob
3687         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
3688       endif
3690       if exists('dref')
3691         return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
3692       elseif ref != ""
3693         return [ref] + dcmds
3694       endif
3696     endif
3697     return []
3698   endtry
3699 endfunction
3701 function! s:GF(mode) abort
3702   try
3703     let results = &filetype ==# 'gitcommit' ? [s:StatusCfile()] : s:cfile()
3704   catch /^fugitive:/
3705     return 'echoerr v:errmsg'
3706   endtry
3707   if len(results) > 1
3708     return 'G' . a:mode .
3709           \ ' +' . escape(join(results[1:-1], '|'), '| ') . ' ' .
3710           \ s:fnameescape(results[0])
3711   elseif len(results)
3712     return 'G' . a:mode . ' ' . s:fnameescape(results[0])
3713   else
3714     return ''
3715   endif
3716 endfunction
3718 function! fugitive#Cfile() abort
3719   let pre = ''
3720   let results = s:cfile()
3721   if empty(results)
3722     let cfile = expand('<cfile>')
3723     if &includeexpr =~# '\<v:fname\>'
3724       sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
3725     endif
3726     return cfile
3727   elseif len(results) > 1
3728     let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
3729   endif
3730   return pre . s:fnameescape(s:Generate(results[0]))
3731 endfunction
3733 " Section: Statusline
3735 function! fugitive#Statusline(...) abort
3736   if !exists('b:git_dir')
3737     return ''
3738   endif
3739   let status = ''
3740   let commit = s:DirCommitFile(@%)[1]
3741   if len(commit)
3742     let status .= ':' . commit[0:6]
3743   endif
3744   let status .= '('.FugitiveHead(7).')'
3745   return '[Git'.status.']'
3746 endfunction
3748 function! fugitive#statusline(...) abort
3749   return fugitive#Statusline()
3750 endfunction
3752 function! fugitive#head(...) abort
3753   if !exists('b:git_dir')
3754     return ''
3755   endif
3757   return fugitive#Head(a:0 ? a:1 : 0)
3758 endfunction
3760 " Section: Folding
3762 function! fugitive#Foldtext() abort
3763   if &foldmethod !=# 'syntax'
3764     return foldtext()
3765   endif
3767   let line_foldstart = getline(v:foldstart)
3768   if line_foldstart =~# '^diff '
3769     let [add, remove] = [-1, -1]
3770     let filename = ''
3771     for lnum in range(v:foldstart, v:foldend)
3772       let line = getline(lnum)
3773       if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
3774         let filename = line[6:-1]
3775       endif
3776       if line =~# '^+'
3777         let add += 1
3778       elseif line =~# '^-'
3779         let remove += 1
3780       elseif line =~# '^Binary '
3781         let binary = 1
3782       endif
3783     endfor
3784     if filename ==# ''
3785       let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
3786     endif
3787     if filename ==# ''
3788       let filename = line_foldstart[5:-1]
3789     endif
3790     if exists('binary')
3791       return 'Binary: '.filename
3792     else
3793       return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3794     endif
3795   elseif line_foldstart =~# '^# .*:$'
3796     let lines = getline(v:foldstart, v:foldend)
3797     call filter(lines, 'v:val =~# "^#\t"')
3798     cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
3799     cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
3800     return line_foldstart.' '.join(lines, ', ')
3801   endif
3802   return foldtext()
3803 endfunction
3805 function! fugitive#foldtext() abort
3806   return fugitive#Foldtext()
3807 endfunction
3809 augroup fugitive_folding
3810   autocmd!
3811   autocmd User Fugitive
3812         \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3813         \    set foldtext=fugitive#Foldtext() |
3814         \ endif
3815 augroup END
3817 " Section: Initialization
3819 function! fugitive#Init() abort
3820   if exists('#User#FugitiveBoot')
3821     try
3822       let [save_mls, &modelines] = [&mls, 0]
3823       doautocmd User FugitiveBoot
3824     finally
3825       let &mls = save_mls
3826     endtry
3827   endif
3828   if !exists('g:fugitive_no_maps')
3829     call s:map('c', '<C-R><C-G>', '<SID>fnameescape(fugitive#Object(@%))', '<expr>')
3830     call s:map('n', 'y<C-G>', ':<C-U>call setreg(v:register, fugitive#Object(@%))<CR>', '<silent>')
3831   endif
3832   if expand('%:p') =~# ':[\/][\/]'
3833     let &l:path = s:sub(&path, '^\.%(,|$)', '')
3834   endif
3835   if stridx(&tags, escape(b:git_dir, ', ')) == -1
3836     if filereadable(b:git_dir.'/tags')
3837       let &l:tags = escape(b:git_dir.'/tags', ', ').','.&tags
3838     endif
3839     if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
3840       let &l:tags = escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.&tags
3841     endif
3842   endif
3843   try
3844     let [save_mls, &modelines] = [&mls, 0]
3845     call s:define_commands()
3846     doautocmd User Fugitive
3847   finally
3848     let &mls = save_mls
3849   endtry
3850 endfunction
3852 function! fugitive#is_git_dir(path) abort
3853   return FugitiveIsGitDir(a:path)
3854 endfunction
3856 function! fugitive#extract_git_dir(path) abort
3857   return FugitiveExtractGitDir(a:path)
3858 endfunction
3860 function! fugitive#detect(path) abort
3861   return FugitiveDetect(a:path)
3862 endfunction
3864 " Section: End