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