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