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