fugitive.vim 2.3
[vim-fugitive.git] / plugin / fugitive.vim
blob28b38737badf30c3f46ecd02b3de967e03f8f2c0
1 " fugitive.vim - A Git wrapper so awesome, it should be illegal
2 " Maintainer:   Tim Pope <http://tpo.pe/>
3 " Version:      2.3
4 " GetLatestVimScripts: 2975 1 :AutoInstall: fugitive.vim
6 if exists('g:loaded_fugitive') || &cp
7   finish
8 endif
9 let g:loaded_fugitive = 1
11 if !exists('g:fugitive_git_executable')
12   let g:fugitive_git_executable = 'git'
13 endif
15 " Section: Utility
17 function! s:function(name) abort
18   return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
19 endfunction
21 function! s:sub(str,pat,rep) abort
22   return substitute(a:str,'\v\C'.a:pat,a:rep,'')
23 endfunction
25 function! s:gsub(str,pat,rep) abort
26   return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
27 endfunction
29 function! s:winshell() abort
30   return &shell =~? 'cmd' || exists('+shellslash') && !&shellslash
31 endfunction
33 function! s:shellesc(arg) abort
34   if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
35     return a:arg
36   elseif s:winshell()
37     return '"'.s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"').'"'
38   else
39     return shellescape(a:arg)
40   endif
41 endfunction
43 function! s:fnameescape(file) abort
44   if exists('*fnameescape')
45     return fnameescape(a:file)
46   else
47     return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
48   endif
49 endfunction
51 function! s:throw(string) abort
52   let v:errmsg = 'fugitive: '.a:string
53   throw v:errmsg
54 endfunction
56 function! s:warn(str) abort
57   echohl WarningMsg
58   echomsg a:str
59   echohl None
60   let v:warningmsg = a:str
61 endfunction
63 function! s:shellslash(path) abort
64   if s:winshell()
65     return s:gsub(a:path,'\\','/')
66   else
67     return a:path
68   endif
69 endfunction
71 let s:executables = {}
73 function! s:executable(binary) abort
74   if !has_key(s:executables, a:binary)
75     let s:executables[a:binary] = executable(a:binary)
76   endif
77   return s:executables[a:binary]
78 endfunction
80 let s:git_versions = {}
82 function! s:git_command() abort
83   return get(g:, 'fugitive_git_command', g:fugitive_git_executable)
84 endfunction
86 function! fugitive#git_version(...) abort
87   if !has_key(s:git_versions, g:fugitive_git_executable)
88     let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), "\\S\\+\n")
89   endif
90   return s:git_versions[g:fugitive_git_executable]
91 endfunction
93 function! s:recall() abort
94   let rev = s:sub(s:buffer().rev(), '^/', '')
95   if rev ==# ':'
96     return matchstr(getline('.'),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$\|^\d\{6} \x\{40\} \d\t\zs.*')
97   elseif s:buffer().type('tree')
98     let file = matchstr(getline('.'), '\t\zs.*')
99     if empty(file) && line('.') > 2
100       let file = s:sub(getline('.'), '/$', '')
101     endif
102     if !empty(file) && rev !~# ':$'
103       return rev . '/' . file
104     else
105       return rev . file
106     endif
107   endif
108   return rev
109 endfunction
111 function! s:map(mode, lhs, rhs, ...) abort
112   let flags = (a:0 ? a:1 : '') . (a:rhs =~# '^<Plug>' ? '' : '<script>')
113   let head = a:lhs
114   let tail = ''
115   let keys = get(g:, a:mode.'remap', {})
116   if type(keys) == type([])
117     return
118   endif
119   while !empty(head)
120     if has_key(keys, head)
121       let head = keys[head]
122       if empty(head)
123         return
124       endif
125       break
126     endif
127     let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
128     let head = substitute(head, '<[^<>]*>$\|.$', '', '')
129   endwhile
130   if flags !~# '<unique>' || empty(mapcheck(head.tail, a:mode))
131     exe a:mode.'map <buffer>' flags head.tail a:rhs
132   endif
133 endfunction
135 function! s:add_methods(namespace, method_names) abort
136   for name in a:method_names
137     let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
138   endfor
139 endfunction
141 let s:commands = []
142 function! s:command(definition) abort
143   let s:commands += [a:definition]
144 endfunction
146 function! s:define_commands() abort
147   for command in s:commands
148     exe 'command! -buffer '.command
149   endfor
150 endfunction
152 let s:abstract_prototype = {}
154 " Section: Initialization
156 function! fugitive#is_git_dir(path) abort
157   let path = s:sub(a:path, '[\/]$', '') . '/'
158   return getfsize(path.'HEAD') > 10 && (
159         \ isdirectory(path.'objects') && isdirectory(path.'refs') ||
160         \ getftype(path.'commondir') ==# 'file')
161 endfunction
163 function! FugitiveIsGitDir(path) abort
164   return fugitive#is_git_dir(a:path)
165 endfunction
167 function! fugitive#extract_git_dir(path) abort
168   if s:shellslash(a:path) =~# '^fugitive://.*//'
169     return matchstr(s:shellslash(a:path), '\C^fugitive://\zs.\{-\}\ze//')
170   endif
171   if isdirectory(a:path)
172     let path = fnamemodify(a:path, ':p:s?[\/]$??')
173   else
174     let path = fnamemodify(a:path, ':p:h:s?[\/]$??')
175   endif
176   let root = s:shellslash(resolve(path))
177   let previous = ""
178   while root !=# previous
179     if root =~# '\v^//%([^/]+/?)?$'
180       " This is for accessing network shares from Cygwin Vim. There won't be
181       " any git directory called //.git or //serverName/.git so let's avoid
182       " checking for them since such checks are extremely slow.
183       break
184     endif
185     if index(split($GIT_CEILING_DIRECTORIES, ':'), root) >= 0
186       break
187     endif
188     if root ==# $GIT_WORK_TREE && fugitive#is_git_dir($GIT_DIR)
189       return simplify(fnamemodify(expand($GIT_DIR), ':p:s?[\/]$??'))
190     endif
191     if fugitive#is_git_dir($GIT_DIR)
192       " Ensure that we've cached the worktree
193       call s:configured_tree(simplify(fnamemodify(expand($GIT_DIR), ':p:s?[\/]$??')))
194       if has_key(s:dir_for_worktree, root)
195         return s:dir_for_worktree[root]
196       endif
197     endif
198     let dir = s:sub(root, '[\/]$', '') . '/.git'
199     let type = getftype(dir)
200     if type ==# 'dir' && fugitive#is_git_dir(dir)
201       return dir
202     elseif type ==# 'link' && fugitive#is_git_dir(dir)
203       return resolve(dir)
204     elseif type !=# '' && filereadable(dir)
205       let line = get(readfile(dir, '', 1), 0, '')
206       if line =~# '^gitdir: \.' && fugitive#is_git_dir(root.'/'.line[8:-1])
207         return simplify(root.'/'.line[8:-1])
208       elseif line =~# '^gitdir: ' && fugitive#is_git_dir(line[8:-1])
209         return line[8:-1]
210       endif
211     elseif fugitive#is_git_dir(root)
212       return root
213     endif
214     let previous = root
215     let root = fnamemodify(root, ':h')
216   endwhile
217   return ''
218 endfunction
220 function! FugitiveExtractGitDir(path) abort
221   return fugitive#extract_git_dir(a:path)
222 endfunction
224 function! fugitive#detect(path) abort
225   if exists('b:git_dir') && (b:git_dir ==# '' || b:git_dir =~# '/$')
226     unlet b:git_dir
227   endif
228   if !exists('b:git_dir')
229     let dir = fugitive#extract_git_dir(a:path)
230     if dir !=# ''
231       let b:git_dir = dir
232       if empty(fugitive#buffer().path())
233         silent! exe haslocaldir() ? 'lcd .' : 'cd .'
234       endif
235     endif
236   endif
237   if exists('b:git_dir')
238     if exists('#User#FugitiveBoot')
239       try
240         let [save_mls, &modelines] = [&mls, 0]
241         doautocmd User FugitiveBoot
242       finally
243         let &mls = save_mls
244       endtry
245     endif
246     if !exists('g:fugitive_no_maps')
247       call s:map('c', '<C-R><C-G>', 'fnameescape(<SID>recall())', '<expr>')
248       call s:map('n', 'y<C-G>', ':call setreg(v:register, <SID>recall())<CR>', '<silent>')
249     endif
250     let buffer = fugitive#buffer()
251     if expand('%:p') =~# '://'
252       call buffer.setvar('&path', s:sub(buffer.getvar('&path'), '^\.%(,|$)', ''))
253     endif
254     if stridx(buffer.getvar('&tags'), escape(b:git_dir, ', ')) == -1
255       if filereadable(b:git_dir.'/tags')
256         call buffer.setvar('&tags', escape(b:git_dir.'/tags', ', ').','.buffer.getvar('&tags'))
257       endif
258       if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
259         call buffer.setvar('&tags', escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.buffer.getvar('&tags'))
260       endif
261     endif
262     try
263       let [save_mls, &modelines] = [&mls, 0]
264       call s:define_commands()
265       doautocmd User Fugitive
266     finally
267       let &mls = save_mls
268     endtry
269   endif
270 endfunction
272 function! FugitiveDetect(path) abort
273   return fugitive#detect(a:path)
274 endfunction
276 augroup fugitive
277   autocmd!
278   autocmd BufNewFile,BufReadPost * call fugitive#detect(expand('%:p'))
279   autocmd FileType           netrw call fugitive#detect(expand('%:p'))
280   autocmd User NERDTreeInit,NERDTreeNewRoot call fugitive#detect(b:NERDTree.root.path.str())
281   autocmd VimEnter * if expand('<amatch>')==''|call fugitive#detect(getcwd())|endif
282   autocmd CmdWinEnter * call fugitive#detect(expand('#:p'))
283   autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
284 augroup END
286 " Section: Repository
288 let s:repo_prototype = {}
289 let s:repos = {}
290 let s:worktree_for_dir = {}
291 let s:dir_for_worktree = {}
293 function! s:repo(...) abort
294   let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : fugitive#extract_git_dir(expand('%:p')))
295   if dir !=# ''
296     if has_key(s:repos, dir)
297       let repo = get(s:repos, dir)
298     else
299       let repo = {'git_dir': dir}
300       let s:repos[dir] = repo
301     endif
302     return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
303   endif
304   call s:throw('not a git repository: '.expand('%:p'))
305 endfunction
307 function! fugitive#repo(...) abort
308   return call('s:repo', a:000)
309 endfunction
311 function! s:repo_dir(...) dict abort
312   return join([self.git_dir]+a:000,'/')
313 endfunction
315 function! s:configured_tree(git_dir) abort
316   if !has_key(s:worktree_for_dir, a:git_dir)
317     let s:worktree_for_dir[a:git_dir] = ''
318     let config_file = a:git_dir . '/config'
319     if filereadable(config_file)
320       let config = readfile(config_file,'',10)
321       call filter(config,'v:val =~# "^\\s*worktree *="')
322       if len(config) == 1
323         let worktree = matchstr(config[0], '= *\zs.*')
324       endif
325     elseif filereadable(a:git_dir . '/gitdir')
326       let worktree = fnamemodify(readfile(a:git_dir . '/gitdir')[0], ':h')
327       if worktree ==# '.'
328         unlet! worktree
329       endif
330     endif
331     if exists('worktree')
332       let s:worktree_for_dir[a:git_dir] = worktree
333       let s:dir_for_worktree[s:worktree_for_dir[a:git_dir]] = a:git_dir
334     endif
335   endif
336   if s:worktree_for_dir[a:git_dir] =~# '^\.'
337     return simplify(a:git_dir . '/' . s:worktree_for_dir[a:git_dir])
338   else
339     return s:worktree_for_dir[a:git_dir]
340   endif
341 endfunction
343 function! s:repo_tree(...) dict abort
344   if self.dir() =~# '/\.git$'
345     let dir = self.dir()[0:-6]
346     if dir !~# '/'
347       let dir .= '/'
348     endif
349   else
350     let dir = s:configured_tree(self.git_dir)
351   endif
352   if dir ==# ''
353     call s:throw('no work tree')
354   else
355     return join([dir]+a:000,'/')
356   endif
357 endfunction
359 function! s:repo_bare() dict abort
360   if self.dir() =~# '/\.git$'
361     return 0
362   else
363     return s:configured_tree(self.git_dir) ==# ''
364   endif
365 endfunction
367 function! s:repo_translate(spec) dict abort
368   let refs = self.dir('refs/')
369   if filereadable(self.dir('commondir'))
370     let refs = simplify(self.dir(get(readfile(self.dir('commondir'), 1), 0, ''))) . '/refs/'
371   endif
372   if a:spec ==# '.' || a:spec ==# '/.'
373     return self.bare() ? self.dir() : self.tree()
374   elseif a:spec =~# '^/\=\.git$' && self.bare()
375     return self.dir()
376   elseif a:spec =~# '^/\=\.git/'
377     return self.dir(s:sub(a:spec, '^/=\.git/', ''))
378   elseif a:spec =~# '^/'
379     return self.tree().a:spec
380   elseif a:spec =~# '^:[0-3]:'
381     return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
382   elseif a:spec ==# ':'
383     if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(self.dir())] ==# self.dir('') && filereadable($GIT_INDEX_FILE)
384       return fnamemodify($GIT_INDEX_FILE,':p')
385     else
386       return self.dir('index')
387     endif
388   elseif a:spec =~# '^:/'
389     let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
390     return 'fugitive://'.self.dir().'//'.ref
391   elseif a:spec =~# '^:'
392     return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
393   elseif a:spec ==# '@'
394     return self.dir('HEAD')
395   elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(refs . '../' . a:spec)
396     return simplify(refs . '../' . a:spec)
397   elseif filereadable(refs.a:spec)
398     return refs.a:spec
399   elseif filereadable(refs.'tags/'.a:spec)
400     return refs.'tags/'.a:spec
401   elseif filereadable(refs.'heads/'.a:spec)
402     return refs.'heads/'.a:spec
403   elseif filereadable(refs.'remotes/'.a:spec)
404     return refs.'remotes/'.a:spec
405   elseif filereadable(refs.'remotes/'.a:spec.'/HEAD')
406     return refs.'remotes/'.a:spec.'/HEAD'
407   else
408     try
409       let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
410       let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
411       return 'fugitive://'.self.dir().'//'.ref.path
412     catch /^fugitive:/
413       return self.tree(a:spec)
414     endtry
415   endif
416 endfunction
418 function! s:repo_head(...) dict abort
419     let head = s:repo().head_ref()
421     if head =~# '^ref: '
422       let branch = s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','')
423     elseif head =~# '^\x\{40\}$'
424       " truncate hash to a:1 characters if we're in detached head mode
425       let len = a:0 ? a:1 : 0
426       let branch = len ? head[0:len-1] : ''
427     else
428       return ''
429     endif
431     return branch
432 endfunction
434 call s:add_methods('repo',['dir','tree','bare','translate','head'])
436 function! s:repo_git_command(...) dict abort
437   let git = s:git_command() . ' --git-dir='.s:shellesc(self.git_dir)
438   return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
439 endfunction
441 function! s:repo_git_chomp(...) dict abort
442   let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
443   let output = git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
444   return s:sub(system(output),'\n$','')
445 endfunction
447 function! s:repo_git_chomp_in_tree(...) dict abort
448   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
449   let dir = getcwd()
450   try
451     execute cd s:fnameescape(s:repo().tree())
452     return call(s:repo().git_chomp, a:000, s:repo())
453   finally
454     execute cd s:fnameescape(dir)
455   endtry
456 endfunction
458 function! s:repo_rev_parse(rev) dict abort
459   let hash = self.git_chomp('rev-parse','--verify',a:rev)
460   if hash =~ '\<\x\{40\}$'
461     return matchstr(hash,'\<\x\{40\}$')
462   endif
463   call s:throw('rev-parse '.a:rev.': '.hash)
464 endfunction
466 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
468 function! s:repo_dirglob(base) dict abort
469   let base = s:sub(a:base,'^/','')
470   let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
471   call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
472   return matches
473 endfunction
475 function! s:repo_superglob(base) dict abort
476   if a:base =~# '^/' || a:base !~# ':'
477     let results = []
478     if a:base !~# '^/'
479       let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
480       let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
481       " Add any stashes.
482       if filereadable(s:repo().dir('refs/stash'))
483         let heads += ["stash"]
484         let heads += sort(split(s:repo().git_chomp("stash","list","--pretty=format:%gd"),"\n"))
485       endif
486       call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
487       let results += heads
488     endif
489     if !self.bare()
490       let base = s:sub(a:base,'^/','')
491       let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
492       call map(matches,'s:shellslash(v:val)')
493       call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
494       call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
495       let results += matches
496     endif
497     return results
499   elseif a:base =~# '^:'
500     let entries = split(self.git_chomp('ls-files','--stage'),"\n")
501     call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
502     if a:base !~# '^:[0-3]\%(:\|$\)'
503       call filter(entries,'v:val[1] == "0"')
504       call map(entries,'v:val[2:-1]')
505     endif
506     call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
507     return entries
509   else
510     let tree = matchstr(a:base,'.*[:/]')
511     let entries = split(self.git_chomp('ls-tree',tree),"\n")
512     call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
513     call map(entries,'tree.s:sub(v:val,".*\t","")')
514     return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
515   endif
516 endfunction
518 call s:add_methods('repo',['dirglob','superglob'])
520 function! s:repo_config(conf) dict abort
521   return matchstr(s:repo().git_chomp('config',a:conf),"[^\r\n]*")
522 endfun
524 function! s:repo_user() dict abort
525   let username = s:repo().config('user.name')
526   let useremail = s:repo().config('user.email')
527   return username.' <'.useremail.'>'
528 endfun
530 function! s:repo_aliases() dict abort
531   if !has_key(self,'_aliases')
532     let self._aliases = {}
533     for line in split(self.git_chomp('config','-z','--get-regexp','^alias[.]'),"\1")
534       let self._aliases[matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
535     endfor
536   endif
537   return self._aliases
538 endfunction
540 call s:add_methods('repo',['config', 'user', 'aliases'])
542 function! s:repo_keywordprg() dict abort
543   let args = ' --git-dir='.escape(self.dir(),"\\\"' ")
544   if has('gui_running') && !has('win32')
545     return s:git_command() . ' --no-pager' . args . ' log -1'
546   else
547     return s:git_command() . args . ' show'
548   endif
549 endfunction
551 call s:add_methods('repo',['keywordprg'])
553 " Section: Buffer
555 let s:buffer_prototype = {}
557 function! s:buffer(...) abort
558   let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
559   call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
560   if buffer.getvar('git_dir') !=# ''
561     return buffer
562   endif
563   call s:throw('not a git repository: '.expand('%:p'))
564 endfunction
566 function! fugitive#buffer(...) abort
567   return s:buffer(a:0 ? a:1 : '%')
568 endfunction
570 function! s:buffer_getvar(var) dict abort
571   return getbufvar(self['#'],a:var)
572 endfunction
574 function! s:buffer_setvar(var,value) dict abort
575   return setbufvar(self['#'],a:var,a:value)
576 endfunction
578 function! s:buffer_getline(lnum) dict abort
579   return get(getbufline(self['#'], a:lnum), 0, '')
580 endfunction
582 function! s:buffer_repo() dict abort
583   return s:repo(self.getvar('git_dir'))
584 endfunction
586 function! s:buffer_type(...) dict abort
587   if self.getvar('fugitive_type') != ''
588     let type = self.getvar('fugitive_type')
589   elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
590     let type = 'head'
591   elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
592     let type = 'tree'
593   elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
594     let type = 'tree'
595   elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
596     let type = 'index'
597   elseif isdirectory(self.spec())
598     let type = 'directory'
599   elseif self.spec() == ''
600     let type = 'null'
601   else
602     let type = 'file'
603   endif
604   if a:0
605     return !empty(filter(copy(a:000),'v:val ==# type'))
606   else
607     return type
608   endif
609 endfunction
611 if has('win32')
613   function! s:buffer_spec() dict abort
614     let bufname = bufname(self['#'])
615     let retval = ''
616     for i in split(bufname,'[^:]\zs\\')
617       let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
618     endfor
619     return s:shellslash(fnamemodify(retval,':p'))
620   endfunction
622 else
624   function! s:buffer_spec() dict abort
625     let bufname = bufname(self['#'])
626     return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
627   endfunction
629 endif
631 function! s:buffer_name() dict abort
632   return self.spec()
633 endfunction
635 function! s:buffer_commit() dict abort
636   return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
637 endfunction
639 function! s:cpath(path) abort
640   if exists('+fileignorecase') && &fileignorecase
641     return tolower(a:path)
642   else
643     return a:path
644   endif
645 endfunction
647 function! s:buffer_path(...) dict abort
648   let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
649   if rev != ''
650     let rev = s:sub(rev,'\w*','')
651   elseif s:cpath(self.spec()[0 : len(self.repo().dir())]) ==#
652         \ s:cpath(self.repo().dir() . '/')
653     let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
654   elseif !self.repo().bare() &&
655         \ s:cpath(self.spec()[0 : len(self.repo().tree())]) ==#
656         \ s:cpath(self.repo().tree() . '/')
657     let rev = self.spec()[strlen(self.repo().tree()) : -1]
658   endif
659   return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
660 endfunction
662 function! s:buffer_rev() dict abort
663   let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
664   if rev =~ '^\x/'
665     return ':'.rev[0].':'.rev[2:-1]
666   elseif rev =~ '.'
667     return s:sub(rev,'/',':')
668   elseif self.spec() =~ '\.git/index$'
669     return ':'
670   elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
671     return self.spec()[strlen(self.repo().dir())+1 : -1]
672   else
673     return self.path('/')
674   endif
675 endfunction
677 function! s:buffer_sha1() dict abort
678   if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
679     return self.repo().rev_parse(self.rev())
680   else
681     return ''
682   endif
683 endfunction
685 function! s:buffer_expand(rev) dict abort
686   if a:rev =~# '^:[0-3]$'
687     let file = a:rev.self.path(':')
688   elseif a:rev =~# '^[-:]/$'
689     let file = '/'.self.path()
690   elseif a:rev =~# '^-'
691     let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
692   elseif a:rev =~# '^@{'
693     let file = 'HEAD'.a:rev.self.path(':')
694   elseif a:rev =~# '^[~^]'
695     let commit = s:sub(self.commit(),'^\d=$','HEAD')
696     let file = commit.a:rev.self.path(':')
697   else
698     let file = a:rev
699   endif
700   return s:sub(substitute(file,
701         \ '%$\|\\\([[:punct:]]\)','\=len(submatch(1)) ? submatch(1) : self.path()','g'),
702         \ '\.\@<=/$','')
703 endfunction
705 function! s:buffer_containing_commit() dict abort
706   if self.commit() =~# '^\d$'
707     return ':'
708   elseif self.commit() =~# '.'
709     return self.commit()
710   else
711     return 'HEAD'
712   endif
713 endfunction
715 function! s:buffer_up(...) dict abort
716   let rev = self.rev()
717   let c = a:0 ? a:1 : 1
718   while c
719     if rev =~# '^[/:]$'
720       let rev = 'HEAD'
721     elseif rev =~# '^:'
722       let rev = ':'
723     elseif rev =~# '^refs/[^^~:]*$\|^[^^~:]*HEAD$'
724       let rev .= '^{}'
725     elseif rev =~# '^/\|:.*/'
726       let rev = s:sub(rev, '.*\zs/.*', '')
727     elseif rev =~# ':.'
728       let rev = matchstr(rev, '^[^:]*:')
729     elseif rev =~# ':$'
730       let rev = rev[0:-2]
731     else
732       return rev.'~'.c
733     endif
734     let c -= 1
735   endwhile
736   return rev
737 endfunction
739 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit','up'])
741 " Section: Git
743 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
745 function! s:ExecuteInTree(cmd) abort
746   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
747   let dir = getcwd()
748   try
749     execute cd s:fnameescape(s:repo().tree())
750     execute a:cmd
751   finally
752     execute cd s:fnameescape(dir)
753   endtry
754 endfunction
756 function! s:Git(bang, args) abort
757   if a:bang
758     return s:Edit('edit', 1, a:args)
759   endif
760   let git = s:git_command()
761   if has('gui_running') && !has('win32')
762     let git .= ' --no-pager'
763   endif
764   let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
765   if exists(':terminal') && has('nvim')
766     let dir = s:repo().tree()
767     if expand('%') != ''
768       -tabedit %
769     else
770       -tabnew
771     endif
772     execute 'lcd' fnameescape(dir)
773     execute 'terminal' git args
774   else
775     call s:ExecuteInTree('!'.git.' '.args)
776     if has('win32')
777       call fugitive#reload_status()
778     endif
779   endif
780   return matchstr(a:args, '\v\C\\@<!%(\\\\)*\|\zs.*')
781 endfunction
783 function! fugitive#git_commands() abort
784   if !exists('s:exec_path')
785     let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
786   endif
787   return map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
788 endfunction
790 function! s:GitComplete(A, L, P) abort
791   if strpart(a:L, 0, a:P) !~# ' [[:alnum:]-]\+ '
792     let cmds = fugitive#git_commands()
793     return filter(sort(cmds+keys(s:repo().aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
794   else
795     return s:repo().superglob(a:A)
796   endif
797 endfunction
799 " Section: Gcd, Glcd
801 function! s:DirComplete(A,L,P) abort
802   let matches = s:repo().dirglob(a:A)
803   return matches
804 endfunction
806 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd  :exe 'cd<bang>'  s:fnameescape(s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>))")
807 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe 'lcd<bang>' s:fnameescape(s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>))")
809 " Section: Gstatus
811 call s:command("-bar Gstatus :execute s:Status()")
812 augroup fugitive_status
813   autocmd!
814   if !has('win32')
815     autocmd FocusGained,ShellCmdPost * call fugitive#reload_status()
816     autocmd BufDelete term://* call fugitive#reload_status()
817   endif
818 augroup END
820 function! s:Status() abort
821   try
822     Gpedit :
823     wincmd P
824     setlocal foldmethod=syntax foldlevel=1
825     nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
826   catch /^fugitive:/
827     return 'echoerr v:errmsg'
828   endtry
829   return ''
830 endfunction
832 function! fugitive#reload_status() abort
833   if exists('s:reloading_status')
834     return
835   endif
836   try
837     let s:reloading_status = 1
838     let mytab = tabpagenr()
839     for tab in [mytab] + range(1,tabpagenr('$'))
840       for winnr in range(1,tabpagewinnr(tab,'$'))
841         if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
842           execute 'tabnext '.tab
843           if winnr != winnr()
844             execute winnr.'wincmd w'
845             let restorewinnr = 1
846           endif
847           try
848             if !&modified
849               call s:BufReadIndex()
850             endif
851           finally
852             if exists('restorewinnr')
853               wincmd p
854             endif
855             execute 'tabnext '.mytab
856           endtry
857         endif
858       endfor
859     endfor
860   finally
861     unlet! s:reloading_status
862   endtry
863 endfunction
865 function! fugitive#ReloadStatus() abort
866   return fugitive#reload_status()
867 endfunction
869 function! s:stage_info(lnum) abort
870   let filename = matchstr(getline(a:lnum),'^#\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
871   let lnum = a:lnum
872   if has('multi_byte_encoding')
873     let colon = '\%(:\|\%uff1a\)'
874   else
875     let colon = ':'
876   endif
877   while lnum && getline(lnum) !~# colon.'$'
878     let lnum -= 1
879   endwhile
880   if !lnum
881     return ['', '']
882   elseif (getline(lnum+1) =~# '^# .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) ==# '# Changes to be committed:'
883     return [matchstr(filename, colon.' *\zs.*'), 'staged']
884   elseif (getline(lnum+1) =~# '^# .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.'  ') || getline(lnum) ==# '# Untracked files:'
885     return [filename, 'untracked']
886   elseif getline(lnum+2) =~# '^# .*\<git checkout ' || getline(lnum) ==# '# Changes not staged for commit:'
887     return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
888   elseif getline(lnum+2) =~# '^# .*\<git \%(add\|rm\)' || getline(lnum) ==# '# Unmerged paths:'
889     return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
890   else
891     return ['', 'unknown']
892   endif
893 endfunction
895 function! s:StageNext(count) abort
896   for i in range(a:count)
897     call search('^#\t.*','W')
898   endfor
899   return '.'
900 endfunction
902 function! s:StagePrevious(count) abort
903   if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
904     return 'CtrlP '.fnameescape(s:repo().tree())
905   else
906     for i in range(a:count)
907       call search('^#\t.*','Wbe')
908     endfor
909     return '.'
910   endif
911 endfunction
913 function! s:StageReloadSeek(target,lnum1,lnum2) abort
914   let jump = a:target
915   let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
916   if f !=# '' | let jump = f | endif
917   let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
918   if f !=# '' | let jump = f | endif
919   silent! edit!
920   1
921   redraw
922   call search('^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
923 endfunction
925 function! s:StageUndo() abort
926   let [filename, section] = s:stage_info(line('.'))
927   if empty(filename)
928     return ''
929   endif
930   let repo = s:repo()
931   let hash = repo.git_chomp('hash-object', '-w', filename)
932   if !empty(hash)
933     if section ==# 'untracked'
934       call repo.git_chomp_in_tree('clean', '-f', '--', filename)
935     elseif section ==# 'unmerged'
936       call repo.git_chomp_in_tree('rm', '--', filename)
937     elseif section ==# 'unstaged'
938       call repo.git_chomp_in_tree('checkout', '--', filename)
939     else
940       call repo.git_chomp_in_tree('checkout', 'HEAD', '--', filename)
941     endif
942     call s:StageReloadSeek(filename, line('.'), line('.'))
943     let @" = hash
944     return 'checktime|redraw|echomsg ' .
945           \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
946   endif
947 endfunction
949 function! s:StageDiff(diff) abort
950   let [filename, section] = s:stage_info(line('.'))
951   if filename ==# '' && section ==# 'staged'
952     return 'Git! diff --no-ext-diff --cached'
953   elseif filename ==# ''
954     return 'Git! diff --no-ext-diff'
955   elseif filename =~# ' -> '
956     let [old, new] = split(filename,' -> ')
957     execute 'Gedit '.s:fnameescape(':0:'.new)
958     return a:diff.' HEAD:'.s:fnameescape(old)
959   elseif section ==# 'staged'
960     execute 'Gedit '.s:fnameescape(':0:'.filename)
961     return a:diff.' -'
962   else
963     execute 'Gedit '.s:fnameescape('/'.filename)
964     return a:diff
965   endif
966 endfunction
968 function! s:StageDiffEdit() abort
969   let [filename, section] = s:stage_info(line('.'))
970   let arg = (filename ==# '' ? '.' : filename)
971   if section ==# 'staged'
972     return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
973   elseif section ==# 'untracked'
974     let repo = s:repo()
975     call repo.git_chomp_in_tree('add','--intent-to-add',arg)
976     if arg ==# '.'
977       silent! edit!
978       1
979       if !search('^# .*:\n#.*\n# .*"git checkout \|^# Changes not staged for commit:$','W')
980         call search('^# .*:$','W')
981       endif
982     else
983       call s:StageReloadSeek(arg,line('.'),line('.'))
984     endif
985     return ''
986   else
987     return 'Git! diff --no-ext-diff '.s:shellesc(arg)
988   endif
989 endfunction
991 function! s:StageToggle(lnum1,lnum2) abort
992   if a:lnum1 == 1 && a:lnum2 == 1
993     return 'Gedit /.git|call search("^index$", "wc")'
994   endif
995   try
996     let output = ''
997     for lnum in range(a:lnum1,a:lnum2)
998       let [filename, section] = s:stage_info(lnum)
999       let repo = s:repo()
1000       if getline('.') =~# '^# .*:$'
1001         if section ==# 'staged'
1002           call repo.git_chomp_in_tree('reset','-q')
1003           silent! edit!
1004           1
1005           if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
1006             call search('^# .*:$','W')
1007           endif
1008           return ''
1009         elseif section ==# 'unstaged'
1010           call repo.git_chomp_in_tree('add','-u')
1011           silent! edit!
1012           1
1013           if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
1014             call search('^# .*:$','W')
1015           endif
1016           return ''
1017         else
1018           call repo.git_chomp_in_tree('add','.')
1019           silent! edit!
1020           1
1021           call search('^# .*:$','W')
1022           return ''
1023         endif
1024       endif
1025       if filename ==# ''
1026         continue
1027       endif
1028       execute lnum
1029       if section ==# 'staged'
1030         if filename =~ ' -> '
1031           let files_to_unstage = split(filename,' -> ')
1032         else
1033           let files_to_unstage = [filename]
1034         endif
1035         let filename = files_to_unstage[-1]
1036         let cmd = ['reset','-q','--'] + files_to_unstage
1037       elseif getline(lnum) =~# '^#\tdeleted:'
1038         let cmd = ['rm','--',filename]
1039       elseif getline(lnum) =~# '^#\tmodified:'
1040         let cmd = ['add','--',filename]
1041       else
1042         let cmd = ['add','-A','--',filename]
1043       endif
1044       if !exists('first_filename')
1045         let first_filename = filename
1046       endif
1047       let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
1048     endfor
1049     if exists('first_filename')
1050       call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
1051     endif
1052     echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
1053   catch /^fugitive:/
1054     return 'echoerr v:errmsg'
1055   endtry
1056   return 'checktime'
1057 endfunction
1059 function! s:StagePatch(lnum1,lnum2) abort
1060   let add = []
1061   let reset = []
1063   for lnum in range(a:lnum1,a:lnum2)
1064     let [filename, section] = s:stage_info(lnum)
1065     if getline('.') =~# '^# .*:$' && section ==# 'staged'
1066       return 'Git reset --patch'
1067     elseif getline('.') =~# '^# .*:$' && section ==# 'unstaged'
1068       return 'Git add --patch'
1069     elseif getline('.') =~# '^# .*:$' && section ==# 'untracked'
1070       return 'Git add -N .'
1071     elseif filename ==# ''
1072       continue
1073     endif
1074     if !exists('first_filename')
1075       let first_filename = filename
1076     endif
1077     execute lnum
1078     if filename =~ ' -> '
1079       let reset += [split(filename,' -> ')[1]]
1080     elseif section ==# 'staged'
1081       let reset += [filename]
1082     elseif getline(lnum) !~# '^#\tdeleted:'
1083       let add += [filename]
1084     endif
1085   endfor
1086   try
1087     if !empty(add)
1088       execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1089     endif
1090     if !empty(reset)
1091       execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1092     endif
1093     if exists('first_filename')
1094       silent! edit!
1095       1
1096       redraw
1097       call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1098     endif
1099   catch /^fugitive:/
1100     return 'echoerr v:errmsg'
1101   endtry
1102   return 'checktime'
1103 endfunction
1105 " Section: Gcommit
1107 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit('<mods>', <q-args>)")
1109 function! s:Commit(mods, args, ...) abort
1110   let mods = s:gsub(a:mods ==# '<mods>' ? '' : a:mods, '<tab>', '-tab')
1111   let repo = a:0 ? a:1 : s:repo()
1112   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1113   let dir = getcwd()
1114   let msgfile = repo.dir('COMMIT_EDITMSG')
1115   let outfile = tempname()
1116   let errorfile = tempname()
1117   try
1118     try
1119       execute cd s:fnameescape(repo.tree())
1120       if s:winshell()
1121         let command = ''
1122         let old_editor = $GIT_EDITOR
1123         let $GIT_EDITOR = 'false'
1124       else
1125         let command = 'env GIT_EDITOR=false '
1126       endif
1127       let command .= repo.git_command('commit').' '.a:args
1128       if &shell =~# 'csh'
1129         noautocmd silent execute '!('.command.' > '.outfile.') >& '.errorfile
1130       elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1131         noautocmd execute '!'.command.' 2> '.errorfile
1132       else
1133         noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1134       endif
1135       let error = v:shell_error
1136     finally
1137       execute cd s:fnameescape(dir)
1138     endtry
1139     if !has('gui_running')
1140       redraw!
1141     endif
1142     if !error
1143       if filereadable(outfile)
1144         for line in readfile(outfile)
1145           echo line
1146         endfor
1147       endif
1148       return ''
1149     else
1150       let errors = readfile(errorfile)
1151       let error = get(errors,-2,get(errors,-1,'!'))
1152       if error =~# 'false''\=\.$'
1153         let args = a:args
1154         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
1155         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1156         let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
1157         let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
1158         let args = '-F '.s:shellesc(msgfile).' '.args
1159         if args !~# '\%(^\| \)--cleanup\>'
1160           let args = '--cleanup=strip '.args
1161         endif
1162         if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
1163           execute mods 'keepalt edit' s:fnameescape(msgfile)
1164         elseif a:args =~# '\%(^\| \)-\w*v' || mods =~# '\<tab\>'
1165           execute mods 'keepalt -tabedit' s:fnameescape(msgfile)
1166         elseif s:buffer().type() ==# 'index'
1167           execute mods 'keepalt edit' s:fnameescape(msgfile)
1168           execute (search('^#','n')+1).'wincmd+'
1169           setlocal nopreviewwindow
1170         else
1171           execute mods 'keepalt split' s:fnameescape(msgfile)
1172         endif
1173         let b:fugitive_commit_arguments = args
1174         setlocal bufhidden=wipe filetype=gitcommit
1175         return '1'
1176       elseif error ==# '!'
1177         return s:Status()
1178       else
1179         call s:throw(empty(error)?join(errors, ' '):error)
1180       endif
1181     endif
1182   catch /^fugitive:/
1183     return 'echoerr v:errmsg'
1184   finally
1185     if exists('old_editor')
1186       let $GIT_EDITOR = old_editor
1187     endif
1188     call delete(outfile)
1189     call delete(errorfile)
1190     call fugitive#reload_status()
1191   endtry
1192 endfunction
1194 function! s:CommitComplete(A,L,P) abort
1195   if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
1196     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']
1197     return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
1198   else
1199     return s:repo().superglob(a:A)
1200   endif
1201 endfunction
1203 function! s:FinishCommit() abort
1204   let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
1205   if !empty(args)
1206     call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
1207     return s:Commit('', args, s:repo(getbufvar(+expand('<abuf>'),'git_dir')))
1208   endif
1209   return ''
1210 endfunction
1212 " Section: Gmerge, Gpull
1214 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
1215       \ "execute s:Merge('merge', <bang>0, <q-args>)")
1216 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
1217       \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
1219 function! s:RevisionComplete(A, L, P) abort
1220   return s:repo().git_chomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
1221         \ . "\nHEAD\nFETCH_HEAD\nORIG_HEAD"
1222 endfunction
1224 function! s:RemoteComplete(A, L, P) abort
1225   let remote = matchstr(a:L, ' \zs\S\+\ze ')
1226   if !empty(remote)
1227     let matches = split(s:repo().git_chomp('ls-remote', remote), "\n")
1228     call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1229     call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1230   else
1231     let matches = split(s:repo().git_chomp('remote'), "\n")
1232   endif
1233   return join(matches, "\n")
1234 endfunction
1236 function! fugitive#cwindow() abort
1237   if &buftype == 'quickfix'
1238     cwindow
1239   else
1240     botright cwindow
1241     if &buftype == 'quickfix'
1242       wincmd p
1243     endif
1244   endif
1245 endfunction
1247 let s:common_efm = ''
1248       \ . '%+Egit:%.%#,'
1249       \ . '%+Eusage:%.%#,'
1250       \ . '%+Eerror:%.%#,'
1251       \ . '%+Efatal:%.%#,'
1252       \ . '%-G%.%#%\e[K%.%#,'
1253       \ . '%-G%.%#%\r%.%\+'
1255 function! s:Merge(cmd, bang, args) abort
1256   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1257   let cwd = getcwd()
1258   let [mp, efm] = [&l:mp, &l:efm]
1259   let had_merge_msg = filereadable(s:repo().dir('MERGE_MSG'))
1260   try
1261     let &l:errorformat = ''
1262           \ . '%-Gerror:%.%#false''.,'
1263           \ . '%-G%.%# ''git commit'' %.%#,'
1264           \ . '%+Emerge:%.%#,'
1265           \ . s:common_efm . ','
1266           \ . '%+ECannot %.%#: You have unstaged changes.,'
1267           \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
1268           \ . '%+EThere is no tracking information for the current branch.,'
1269           \ . '%+EYou are not currently on a branch. Please specify which,'
1270           \ . 'CONFLICT (%m): %f deleted in %.%#,'
1271           \ . 'CONFLICT (%m): Merge conflict in %f,'
1272           \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
1273           \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
1274           \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
1275           \ . '%+ECONFLICT %.%#,'
1276           \ . '%+EKONFLIKT %.%#,'
1277           \ . '%+ECONFLIT %.%#,'
1278           \ . "%+EXUNG \u0110\u1ed8T %.%#,"
1279           \ . "%+E\u51b2\u7a81 %.%#,"
1280           \ . 'U%\t%f'
1281     if a:cmd =~# '^merge' && empty(a:args) &&
1282           \ (had_merge_msg || isdirectory(s:repo().dir('rebase-apply')) ||
1283           \  !empty(s:repo().git_chomp('diff-files', '--diff-filter=U')))
1284       let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
1285     else
1286       let &l:makeprg = s:sub(s:git_command() . ' ' . a:cmd .
1287             \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' ? '' : ' --edit') .
1288             \ ' ' . a:args, ' *$', '')
1289     endif
1290     if !empty($GIT_EDITOR) || has('win32')
1291       let old_editor = $GIT_EDITOR
1292       let $GIT_EDITOR = 'false'
1293     else
1294       let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
1295     endif
1296     execute cd fnameescape(s:repo().tree())
1297     silent noautocmd make!
1298   catch /^Vim\%((\a\+)\)\=:E211/
1299     let err = v:exception
1300   finally
1301     redraw!
1302     let [&l:mp, &l:efm] = [mp, efm]
1303     if exists('old_editor')
1304       let $GIT_EDITOR = old_editor
1305     endif
1306     execute cd fnameescape(cwd)
1307   endtry
1308   call fugitive#reload_status()
1309   if empty(filter(getqflist(),'v:val.valid'))
1310     if !had_merge_msg && filereadable(s:repo().dir('MERGE_MSG'))
1311       cclose
1312       return 'Gcommit --no-status -n -t '.s:shellesc(s:repo().dir('MERGE_MSG'))
1313     endif
1314   endif
1315   let qflist = getqflist()
1316   let found = 0
1317   for e in qflist
1318     if !empty(e.bufnr)
1319       let found = 1
1320       let e.pattern = '^<<<<<<<'
1321     endif
1322   endfor
1323   call fugitive#cwindow()
1324   if found
1325     call setqflist(qflist, 'r')
1326     if !a:bang
1327       return 'cfirst'
1328     endif
1329   endif
1330   return exists('err') ? 'echoerr '.string(err) : ''
1331 endfunction
1333 " Section: Ggrep, Glog
1335 if !exists('g:fugitive_summary_format')
1336   let g:fugitive_summary_format = '%s'
1337 endif
1339 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1340 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1341 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Glog :call s:Log('grep<bang>',<line1>,<count>,<f-args>)")
1342 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Gllog :call s:Log('lgrep<bang>',<line1>,<count>,<f-args>)")
1344 function! s:Grep(cmd,bang,arg) abort
1345   let grepprg = &grepprg
1346   let grepformat = &grepformat
1347   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1348   let dir = getcwd()
1349   try
1350     execute cd s:fnameescape(s:repo().tree())
1351     let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n', '--no-color')
1352     let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
1353     exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
1354     let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
1355     for entry in list
1356       if bufname(entry.bufnr) =~ ':'
1357         let entry.filename = s:repo().translate(bufname(entry.bufnr))
1358         unlet! entry.bufnr
1359         let changed = 1
1360       elseif a:arg =~# '\%(^\| \)--cached\>'
1361         let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
1362         unlet! entry.bufnr
1363         let changed = 1
1364       endif
1365     endfor
1366     if a:cmd =~# '^l' && exists('changed')
1367       call setloclist(0, list, 'r')
1368     elseif exists('changed')
1369       call setqflist(list, 'r')
1370     endif
1371     if !a:bang && !empty(list)
1372       return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
1373     else
1374       return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
1375     endif
1376   finally
1377     let &grepprg = grepprg
1378     let &grepformat = grepformat
1379     execute cd s:fnameescape(dir)
1380   endtry
1381 endfunction
1383 function! s:Log(cmd, line1, line2, ...) abort
1384   let path = s:buffer().path('/')
1385   if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
1386     let path = ''
1387   endif
1388   let cmd = ['--no-pager', 'log', '--no-color']
1389   let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format]
1390   if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
1391     if s:buffer().commit() =~# '\x\{40\}'
1392       let cmd += [s:buffer().commit()]
1393     elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
1394       let cmd += [s:buffer().path()[5:-1]]
1395     endif
1396   end
1397   let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
1398   if path =~# '/.'
1399     if a:line2
1400       let cmd += ['-L', a:line1 . ',' . a:line2 . ':' . path[1:-1]]
1401     else
1402       let cmd += ['--', path[1:-1]]
1403     endif
1404   endif
1405   let grepformat = &grepformat
1406   let grepprg = &grepprg
1407   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1408   let dir = getcwd()
1409   try
1410     execute cd s:fnameescape(s:repo().tree())
1411     let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
1412     let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
1413     exe a:cmd
1414   finally
1415     let &grepformat = grepformat
1416     let &grepprg = grepprg
1417     execute cd s:fnameescape(dir)
1418   endtry
1419 endfunction
1421 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
1423 function! s:UsableWin(nr) abort
1424   return a:nr && !getwinvar(a:nr, '&previewwindow') &&
1425         \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
1426 endfunction
1428 function! s:Edit(cmd,bang,...) abort
1429   let buffer = s:buffer()
1430   if a:cmd !~# 'read'
1431     if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
1432       let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
1433       if len(winnrs)
1434         exe winnrs[0].'wincmd w'
1435       elseif winnr('$') == 1
1436         let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
1437         execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
1438       else
1439         rightbelow new
1440       endif
1441       if &diff
1442         let mywinnr = winnr()
1443         for winnr in range(winnr('$'),1,-1)
1444           if winnr != mywinnr && getwinvar(winnr,'&diff')
1445             execute winnr.'wincmd w'
1446             close
1447             if winnr('$') > 1
1448               wincmd p
1449             endif
1450           endif
1451         endfor
1452         diffoff!
1453       endif
1454     endif
1455   endif
1457   if a:bang
1458     let arglist = map(copy(a:000), 's:gsub(v:val, ''\\@<!%(\\\\)*\zs[%#]'', ''\=s:buffer().expand(submatch(0))'')')
1459     let args = join(arglist, ' ')
1460     if a:cmd =~# 'read'
1461       let git = buffer.repo().git_command()
1462       let last = line('$')
1463       silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
1464       if a:cmd ==# 'read'
1465         silent execute '1,'.last.'delete_'
1466       endif
1467       call fugitive#reload_status()
1468       diffupdate
1469       return 'redraw|echo '.string(':!'.git.' '.args)
1470     else
1471       let temp = resolve(tempname())
1472       if has('win32')
1473         let temp = fnamemodify(fnamemodify(temp, ':h'), ':p').fnamemodify(temp, ':t')
1474       endif
1475       let s:temp_files[s:cpath(temp)] = { 'dir': buffer.repo().dir(), 'args': arglist }
1476       silent execute a:cmd.' '.temp
1477       if a:cmd =~# 'pedit'
1478         wincmd P
1479       endif
1480       let echo = s:Edit('read',1,args)
1481       silent write!
1482       setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
1483       if getline(1) !~# '^diff '
1484         setlocal readonly nomodifiable
1485       endif
1486       if a:cmd =~# 'pedit'
1487         wincmd p
1488       endif
1489       return echo
1490     endif
1491     return ''
1492   endif
1494   if a:0 && a:1 == ''
1495     return ''
1496   elseif a:0
1497     let file = buffer.expand(join(a:000, ' '))
1498   elseif expand('%') ==# ''
1499     let file = ':'
1500   elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
1501     let file = buffer.path(':')
1502   else
1503     let file = buffer.path('/')
1504   endif
1505   try
1506     let file = buffer.repo().translate(file)
1507   catch /^fugitive:/
1508     return 'echoerr v:errmsg'
1509   endtry
1510   if file !~# '^fugitive:'
1511     let file = s:sub(file, '/$', '')
1512   endif
1513   if a:cmd ==# 'read'
1514     return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
1515   else
1516     return a:cmd.' '.s:fnameescape(file)
1517   endif
1518 endfunction
1520 function! s:EditComplete(A,L,P) abort
1521   return map(s:repo().superglob(a:A), 'fnameescape(v:val)')
1522 endfunction
1524 function! s:EditRunComplete(A,L,P) abort
1525   if a:L =~# '^\w\+!'
1526     return s:GitComplete(a:A,a:L,a:P)
1527   else
1528     return s:repo().superglob(a:A)
1529   endif
1530 endfunction
1532 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Ge       :execute s:Edit('edit<bang>',0,<f-args>)")
1533 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gedit    :execute s:Edit('edit<bang>',0,<f-args>)")
1534 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit   :execute s:Edit('pedit',<bang>0,<f-args>)")
1535 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gsplit   :execute s:Edit('split',<bang>0,<f-args>)")
1536 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gvsplit  :execute s:Edit('vsplit',<bang>0,<f-args>)")
1537 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
1538 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:EditRunComplete Gread :execute s:Edit((<count> == -1 ? '' : <count>).'read',<bang>0,<f-args>)")
1540 " Section: Gwrite, Gwq
1542 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
1543 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
1544 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
1546 function! s:Write(force,...) abort
1547   if exists('b:fugitive_commit_arguments')
1548     return 'write|bdelete'
1549   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
1550     return 'wq'
1551   elseif s:buffer().type() == 'index'
1552     return 'Gcommit'
1553   elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
1554     let filename = getline(4)[6:-1]
1555     setlocal buftype=
1556     silent write
1557     setlocal buftype=nowrite
1558     if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
1559       let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
1560     else
1561       let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
1562     endif
1563     if err !=# ''
1564       let v:errmsg = split(err,"\n")[0]
1565       return 'echoerr v:errmsg'
1566     elseif a:force
1567       return 'bdelete'
1568     else
1569       return 'Gedit '.fnameescape(filename)
1570     endif
1571   endif
1572   let mytab = tabpagenr()
1573   let mybufnr = bufnr('')
1574   let path = a:0 ? join(a:000, ' ') : s:buffer().path()
1575   if empty(path)
1576     return 'echoerr '.string('fugitive: cannot determine file path')
1577   endif
1578   if path =~# '^:\d\>'
1579     return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
1580   endif
1581   let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
1582   if !always_permitted && !a:force && s:repo().git_chomp_in_tree('diff','--name-status','HEAD','--',path) . s:repo().git_chomp_in_tree('ls-files','--others','--',path) !=# ''
1583     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
1584     return 'echoerr v:errmsg'
1585   endif
1586   let file = s:repo().translate(path)
1587   let treebufnr = 0
1588   for nr in range(1,bufnr('$'))
1589     if fnamemodify(bufname(nr),':p') ==# file
1590       let treebufnr = nr
1591     endif
1592   endfor
1594   if treebufnr > 0 && treebufnr != bufnr('')
1595     let temp = tempname()
1596     silent execute '%write '.temp
1597     for tab in [mytab] + range(1,tabpagenr('$'))
1598       for winnr in range(1,tabpagewinnr(tab,'$'))
1599         if tabpagebuflist(tab)[winnr-1] == treebufnr
1600           execute 'tabnext '.tab
1601           if winnr != winnr()
1602             execute winnr.'wincmd w'
1603             let restorewinnr = 1
1604           endif
1605           try
1606             let lnum = line('.')
1607             let last = line('$')
1608             silent execute '$read '.temp
1609             silent execute '1,'.last.'delete_'
1610             silent write!
1611             silent execute lnum
1612             let did = 1
1613           finally
1614             if exists('restorewinnr')
1615               wincmd p
1616             endif
1617             execute 'tabnext '.mytab
1618           endtry
1619         endif
1620       endfor
1621     endfor
1622     if !exists('did')
1623       call writefile(readfile(temp,'b'),file,'b')
1624     endif
1625   else
1626     execute 'write! '.s:fnameescape(s:repo().translate(path))
1627   endif
1629   if a:force
1630     let error = s:repo().git_chomp_in_tree('add', '--force', '--', path)
1631   else
1632     let error = s:repo().git_chomp_in_tree('add', '--', path)
1633   endif
1634   if v:shell_error
1635     let v:errmsg = 'fugitive: '.error
1636     return 'echoerr v:errmsg'
1637   endif
1638   if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1639     set nomodified
1640   endif
1642   let one = s:repo().translate(':1:'.path)
1643   let two = s:repo().translate(':2:'.path)
1644   let three = s:repo().translate(':3:'.path)
1645   for nr in range(1,bufnr('$'))
1646     let name = fnamemodify(bufname(nr), ':p')
1647     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
1648       execute nr.'bdelete'
1649     endif
1650   endfor
1652   unlet! restorewinnr
1653   let zero = s:repo().translate(':0:'.path)
1654   silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
1655   for tab in range(1,tabpagenr('$'))
1656     for winnr in range(1,tabpagewinnr(tab,'$'))
1657       let bufnr = tabpagebuflist(tab)[winnr-1]
1658       let bufname = fnamemodify(bufname(bufnr), ':p')
1659       if bufname ==# zero && bufnr != mybufnr
1660         execute 'tabnext '.tab
1661         if winnr != winnr()
1662           execute winnr.'wincmd w'
1663           let restorewinnr = 1
1664         endif
1665         try
1666           let lnum = line('.')
1667           let last = line('$')
1668           silent execute '$read '.s:fnameescape(file)
1669           silent execute '1,'.last.'delete_'
1670           silent execute lnum
1671           set nomodified
1672           diffupdate
1673         finally
1674           if exists('restorewinnr')
1675             wincmd p
1676           endif
1677           execute 'tabnext '.mytab
1678         endtry
1679         break
1680       endif
1681     endfor
1682   endfor
1683   call fugitive#reload_status()
1684   return 'checktime'
1685 endfunction
1687 function! s:Wq(force,...) abort
1688   let bang = a:force ? '!' : ''
1689   if exists('b:fugitive_commit_arguments')
1690     return 'wq'.bang
1691   endif
1692   let result = call(s:function('s:Write'),[a:force]+a:000)
1693   if result =~# '^\%(write\|wq\|echoerr\)'
1694     return s:sub(result,'^write','wq')
1695   else
1696     return result.'|quit'.bang
1697   endif
1698 endfunction
1700 augroup fugitive_commit
1701   autocmd!
1702   autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
1703 augroup END
1705 " Section: Gpush, Gfetch
1707 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush  execute s:Dispatch('<bang>', 'push '.<q-args>)")
1708 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
1710 function! s:Dispatch(bang, args)
1711   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1712   let cwd = getcwd()
1713   let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
1714   try
1715     let b:current_compiler = 'git'
1716     let &l:errorformat = s:common_efm
1717     let &l:makeprg = substitute(s:git_command() . ' ' . a:args, '\s\+$', '', '')
1718     execute cd fnameescape(s:repo().tree())
1719     if exists(':Make') == 2
1720       noautocmd Make
1721     else
1722       silent noautocmd make!
1723       redraw!
1724       return 'call fugitive#cwindow()'
1725     endif
1726     return ''
1727   finally
1728     let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
1729     if empty(cc) | unlet! b:current_compiler | endif
1730     execute cd fnameescape(cwd)
1731   endtry
1732 endfunction
1734 " Section: Gdiff
1736 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
1737 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
1738 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
1740 augroup fugitive_diff
1741   autocmd!
1742   autocmd BufWinLeave *
1743         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
1744         \   call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
1745         \ endif
1746   autocmd BufWinEnter *
1747         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
1748         \   call s:diffoff() |
1749         \ endif
1750 augroup END
1752 function! s:can_diffoff(buf) abort
1753   return getwinvar(bufwinnr(a:buf), '&diff') &&
1754         \ !empty(getbufvar(a:buf, 'git_dir')) &&
1755         \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
1756 endfunction
1758 function! fugitive#can_diffoff(buf) abort
1759   return s:can_diffoff(a:buf)
1760 endfunction
1762 function! s:diff_modifier(count) abort
1763   let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
1764   if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
1765     return 'keepalt '
1766   elseif &diffopt =~# 'vertical'
1767     return 'keepalt vert '
1768   elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
1769     return 'keepalt '
1770   else
1771     return 'keepalt vert '
1772   endif
1773 endfunction
1775 function! s:diff_window_count() abort
1776   let c = 0
1777   for nr in range(1,winnr('$'))
1778     let c += getwinvar(nr,'&diff')
1779   endfor
1780   return c
1781 endfunction
1783 function! s:diff_restore() abort
1784   let restore = 'setlocal nodiff noscrollbind'
1785         \ . ' scrollopt=' . &l:scrollopt
1786         \ . (&l:wrap ? ' wrap' : ' nowrap')
1787         \ . ' foldlevel=999'
1788         \ . ' foldmethod=' . &l:foldmethod
1789         \ . ' foldcolumn=' . &l:foldcolumn
1790         \ . ' foldlevel=' . &l:foldlevel
1791         \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
1792   if has('cursorbind')
1793     let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
1794   endif
1795   return restore
1796 endfunction
1798 function! s:diffthis() abort
1799   if !&diff
1800     let w:fugitive_diff_restore = s:diff_restore()
1801     diffthis
1802   endif
1803 endfunction
1805 function! s:diffoff() abort
1806   if exists('w:fugitive_diff_restore')
1807     execute w:fugitive_diff_restore
1808     unlet w:fugitive_diff_restore
1809   else
1810     diffoff
1811   endif
1812 endfunction
1814 function! s:diffoff_all(dir) abort
1815   let curwin = winnr()
1816   for nr in range(1,winnr('$'))
1817     if getwinvar(nr,'&diff')
1818       if nr != winnr()
1819         execute nr.'wincmd w'
1820         let restorewinnr = 1
1821       endif
1822       if exists('b:git_dir') && b:git_dir ==# a:dir
1823         call s:diffoff()
1824       endif
1825     endif
1826   endfor
1827   execute curwin.'wincmd w'
1828 endfunction
1830 function! s:buffer_compare_age(commit) dict abort
1831   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1832   let my_score    = get(scores,':'.self.commit(),0)
1833   let their_score = get(scores,':'.a:commit,0)
1834   if my_score || their_score
1835     return my_score < their_score ? -1 : my_score != their_score
1836   elseif self.commit() ==# a:commit
1837     return 0
1838   endif
1839   let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1840   if base ==# self.commit()
1841     return -1
1842   elseif base ==# a:commit
1843     return 1
1844   endif
1845   let my_time    = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1846   let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1847   return my_time < their_time ? -1 : my_time != their_time
1848 endfunction
1850 call s:add_methods('buffer',['compare_age'])
1852 function! s:Diff(vert,keepfocus,...) abort
1853   let args = copy(a:000)
1854   let post = ''
1855   if get(args, 0) =~# '^+'
1856     let post = remove(args, 0)[1:-1]
1857   endif
1858   let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
1859   if exists(':DiffGitCached')
1860     return 'DiffGitCached'
1861   elseif (empty(args) || args[0] == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1862     let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
1863     let nr = bufnr('')
1864     execute 'leftabove '.vert.'split' s:fnameescape(fugitive#repo().translate(s:buffer().expand(':2')))
1865     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1866     let nr2 = bufnr('')
1867     call s:diffthis()
1868     wincmd p
1869     execute 'rightbelow '.vert.'split' s:fnameescape(fugitive#repo().translate(s:buffer().expand(':3')))
1870     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1871     let nr3 = bufnr('')
1872     call s:diffthis()
1873     wincmd p
1874     call s:diffthis()
1875     execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
1876     execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
1877     return post
1878   elseif len(args)
1879     let arg = join(args, ' ')
1880     if arg ==# ''
1881       return post
1882     elseif arg ==# '/'
1883       let file = s:buffer().path('/')
1884     elseif arg ==# ':'
1885       let file = s:buffer().path(':0:')
1886     elseif arg =~# '^:/.'
1887       try
1888         let file = s:repo().rev_parse(arg).s:buffer().path(':')
1889       catch /^fugitive:/
1890         return 'echoerr v:errmsg'
1891       endtry
1892     else
1893       let file = s:buffer().expand(arg)
1894     endif
1895     if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1896       let file = file.s:buffer().path(':')
1897     endif
1898   else
1899     let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1900   endif
1901   try
1902     let spec = s:repo().translate(file)
1903     let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1904     let restore = s:diff_restore()
1905     if exists('+cursorbind')
1906       setlocal cursorbind
1907     endif
1908     let w:fugitive_diff_restore = restore
1909     if s:buffer().compare_age(commit) < 0
1910       execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
1911     else
1912       execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
1913     endif
1914     let &l:readonly = &l:readonly
1915     redraw
1916     let w:fugitive_diff_restore = restore
1917     let winnr = winnr()
1918     if getwinvar('#', '&diff')
1919       wincmd p
1920       if !a:keepfocus
1921         call feedkeys(winnr."\<C-W>w", 'n')
1922       endif
1923     endif
1924     return post
1925   catch /^fugitive:/
1926     return 'echoerr v:errmsg'
1927   endtry
1928 endfunction
1930 " Section: Gmove, Gremove
1932 function! s:Move(force, rename, destination) abort
1933   if a:destination =~# '^/'
1934     let destination = a:destination[1:-1]
1935   elseif a:rename
1936     let destination = fnamemodify(s:buffer().path(), ':h') . '/' . a:destination
1937   else
1938     let destination = s:shellslash(fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p'))
1939     if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1940       let destination = destination[strlen(s:repo().tree('')):-1]
1941     endif
1942   endif
1943   if isdirectory(s:buffer().spec())
1944     " Work around Vim parser idiosyncrasy
1945     let discarded = s:buffer().setvar('&swapfile',0)
1946   endif
1947   let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1948   if v:shell_error
1949     let v:errmsg = 'fugitive: '.message
1950     return 'echoerr v:errmsg'
1951   endif
1952   let destination = s:repo().tree(destination)
1953   if isdirectory(destination)
1954     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1955   endif
1956   call fugitive#reload_status()
1957   if empty(s:buffer().commit())
1958     if isdirectory(destination)
1959       return 'keepalt edit '.s:fnameescape(destination)
1960     else
1961       return 'keepalt saveas! '.s:fnameescape(destination)
1962     endif
1963   else
1964     return 'file '.s:fnameescape(s:repo().translate(':0:'.destination))
1965   endif
1966 endfunction
1968 function! s:MoveComplete(A,L,P) abort
1969   if a:A =~# '^/'
1970     return s:repo().superglob(a:A)
1971   else
1972     let matches = split(glob(a:A.'*'),"\n")
1973     call map(matches,'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1974     return matches
1975   endif
1976 endfunction
1978 function! s:RenameComplete(A,L,P) abort
1979   if a:A =~# '^/'
1980     return s:repo().superglob(a:A)
1981   else
1982     let pre = '/'. fnamemodify(s:buffer().path(), ':h') . '/'
1983     return map(s:repo().superglob(pre.a:A), 'strpart(v:val, len(pre))')
1984   endif
1985 endfunction
1987 function! s:Remove(after, force) abort
1988   if s:buffer().commit() ==# ''
1989     let cmd = ['rm']
1990   elseif s:buffer().commit() ==# '0'
1991     let cmd = ['rm','--cached']
1992   else
1993     let v:errmsg = 'fugitive: rm not supported here'
1994     return 'echoerr v:errmsg'
1995   endif
1996   if a:force
1997     let cmd += ['--force']
1998   endif
1999   let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
2000   if v:shell_error
2001     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
2002     return 'echoerr '.string(v:errmsg)
2003   else
2004     call fugitive#reload_status()
2005     return a:after . (a:force ? '!' : '')
2006   endif
2007 endfunction
2009 augroup fugitive_remove
2010   autocmd!
2011   autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
2012         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,0,<q-args>)" |
2013         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
2014         \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
2015         \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
2016         \ endif
2017 augroup END
2019 " Section: Gblame
2021 augroup fugitive_blame
2022   autocmd!
2023   autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
2024   autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
2025   autocmd Syntax fugitiveblame call s:BlameSyntax()
2026   autocmd User Fugitive if s:buffer().type('file', 'blob') | exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,[<f-args>])" | endif
2027   autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
2028 augroup END
2030 function! s:linechars(pattern) abort
2031   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
2032   if exists('*synconcealed') && &conceallevel > 1
2033     for col in range(1, chars)
2034       let chars -= synconcealed(line('.'), col)[0]
2035     endfor
2036   endif
2037   return chars
2038 endfunction
2040 function! s:Blame(bang,line1,line2,count,args) abort
2041   if exists('b:fugitive_blamed_bufnr')
2042     return 'bdelete'
2043   endif
2044   try
2045     if s:buffer().path() == ''
2046       call s:throw('file or blob required')
2047     endif
2048     if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
2049       call s:throw('unsupported option')
2050     endif
2051     call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
2052     let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
2053     if s:buffer().commit() =~# '\D\|..'
2054       let cmd += [s:buffer().commit()]
2055     else
2056       let cmd += ['--contents', '-']
2057     endif
2058     let cmd += ['--', s:buffer().path()]
2059     let basecmd = escape(call(s:repo().git_command,cmd,s:repo()),'!%#')
2060     try
2061       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2062       if !s:repo().bare()
2063         let dir = getcwd()
2064         execute cd s:fnameescape(s:repo().tree())
2065       endif
2066       if a:count
2067         execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
2068       else
2069         let error = resolve(tempname())
2070         let temp = error.'.fugitiveblame'
2071         if &shell =~# 'csh'
2072           silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
2073         else
2074           silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
2075         endif
2076         if exists('l:dir')
2077           execute cd s:fnameescape(dir)
2078           unlet dir
2079         endif
2080         if v:shell_error
2081           call s:throw(join(readfile(error),"\n"))
2082         endif
2083         for winnr in range(winnr('$'),1,-1)
2084           call setwinvar(winnr, '&scrollbind', 0)
2085           if exists('+cursorbind')
2086             call setwinvar(winnr, '&cursorbind', 0)
2087           endif
2088           if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
2089             execute winbufnr(winnr).'bdelete'
2090           endif
2091         endfor
2092         let bufnr = bufnr('')
2093         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
2094         if exists('+cursorbind')
2095           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
2096         endif
2097         if &l:wrap
2098           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
2099         endif
2100         if &l:foldenable
2101           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
2102         endif
2103         setlocal scrollbind nowrap nofoldenable
2104         if exists('+cursorbind')
2105           setlocal cursorbind
2106         endif
2107         let top = line('w0') + &scrolloff
2108         let current = line('.')
2109         if has('win32')
2110           let temp = fnamemodify(fnamemodify(temp, ':h'), ':p').fnamemodify(temp, ':t')
2111         endif
2112         let s:temp_files[s:cpath(temp)] = { 'dir': s:repo().dir(), 'args': cmd }
2113         exe 'keepalt leftabove vsplit '.temp
2114         let b:fugitive_blamed_bufnr = bufnr
2115         let w:fugitive_leave = restore
2116         let b:fugitive_blame_arguments = join(a:args,' ')
2117         execute top
2118         normal! zt
2119         execute current
2120         if exists('+cursorbind')
2121           setlocal cursorbind
2122         endif
2123         setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame
2124         if exists('+concealcursor')
2125           setlocal concealcursor=nc conceallevel=2
2126         endif
2127         if exists('+relativenumber')
2128           setlocal norelativenumber
2129         endif
2130         execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
2131         nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
2132         nnoremap <buffer> <silent> g?   :help fugitive-:Gblame<CR>
2133         nnoremap <buffer> <silent> q    :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
2134         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>
2135         nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2136         nnoremap <buffer> <silent> -    :<C-U>exe <SID>BlameJump('')<CR>
2137         nnoremap <buffer> <silent> P    :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
2138         nnoremap <buffer> <silent> ~    :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
2139         nnoremap <buffer> <silent> i    :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2140         nnoremap <buffer> <silent> o    :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
2141         nnoremap <buffer> <silent> O    :<C-U>exe <SID>BlameCommit("tabedit")<CR>
2142         nnoremap <buffer> <silent> A    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
2143         nnoremap <buffer> <silent> C    :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
2144         nnoremap <buffer> <silent> D    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
2145         redraw
2146         syncbind
2147       endif
2148     finally
2149       if exists('l:dir')
2150         execute cd s:fnameescape(dir)
2151       endif
2152     endtry
2153     return ''
2154   catch /^fugitive:/
2155     return 'echoerr v:errmsg'
2156   endtry
2157 endfunction
2159 function! s:BlameCommit(cmd) abort
2160   let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
2161   if cmd =~# '^echoerr'
2162     return cmd
2163   endif
2164   let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2165   let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2166   if path ==# ''
2167     let path = s:buffer(b:fugitive_blamed_bufnr).path()
2168   endif
2169   execute cmd
2170   if search('^diff .* b/\M'.escape(path,'\').'$','W')
2171     call search('^+++')
2172     let head = line('.')
2173     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
2174       let top = +matchstr(getline('.'),' +\zs\d\+')
2175       let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
2176       if lnum >= top && lnum <= top + len
2177         let offset = lnum - top
2178         if &scrolloff
2179           +
2180           normal! zt
2181         else
2182           normal! zt
2183           +
2184         endif
2185         while offset > 0 && line('.') < line('$')
2186           +
2187           if getline('.') =~# '^[ +]'
2188             let offset -= 1
2189           endif
2190         endwhile
2191         return 'normal! zv'
2192       endif
2193     endwhile
2194     execute head
2195     normal! zt
2196   endif
2197   return ''
2198 endfunction
2200 function! s:BlameJump(suffix) abort
2201   let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
2202   if commit =~# '^0\+$'
2203     let commit = ':0'
2204   endif
2205   let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2206   let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2207   if path ==# ''
2208     let path = s:buffer(b:fugitive_blamed_bufnr).path()
2209   endif
2210   let args = b:fugitive_blame_arguments
2211   let offset = line('.') - line('w0')
2212   let bufnr = bufnr('%')
2213   let winnr = bufwinnr(b:fugitive_blamed_bufnr)
2214   if winnr > 0
2215     exe winnr.'wincmd w'
2216   endif
2217   execute s:Edit('edit', 0, commit.a:suffix.':'.path)
2218   execute lnum
2219   if winnr > 0
2220     exe bufnr.'bdelete'
2221   endif
2222   if exists(':Gblame')
2223     execute 'Gblame '.args
2224     execute lnum
2225     let delta = line('.') - line('w0') - offset
2226     if delta > 0
2227       execute 'normal! '.delta."\<C-E>"
2228     elseif delta < 0
2229       execute 'normal! '.(-delta)."\<C-Y>"
2230     endif
2231     syncbind
2232   endif
2233   return ''
2234 endfunction
2236 let s:hash_colors = {}
2238 function! s:BlameSyntax() abort
2239   let b:current_syntax = 'fugitiveblame'
2240   let conceal = has('conceal') ? ' conceal' : ''
2241   let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
2242   syn match FugitiveblameBoundary "^\^"
2243   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
2244   syn match FugitiveblameHash       "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2245   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2246   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
2247   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
2248   exec 'syn match FugitiveblameLineNumber         " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
2249   exec 'syn match FugitiveblameOriginalFile       " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
2250   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
2251   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
2252   syn match FugitiveblameShort              " \d\+)" contained contains=FugitiveblameLineNumber
2253   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
2254   hi def link FugitiveblameBoundary           Keyword
2255   hi def link FugitiveblameHash               Identifier
2256   hi def link FugitiveblameUncommitted        Ignore
2257   hi def link FugitiveblameTime               PreProc
2258   hi def link FugitiveblameLineNumber         Number
2259   hi def link FugitiveblameOriginalFile       String
2260   hi def link FugitiveblameOriginalLineNumber Float
2261   hi def link FugitiveblameShort              FugitiveblameDelimiter
2262   hi def link FugitiveblameDelimiter          Delimiter
2263   hi def link FugitiveblameNotCommittedYet    Comment
2264   let seen = {}
2265   for lnum in range(1, line('$'))
2266     let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
2267     if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
2268       continue
2269     endif
2270     let seen[hash] = 1
2271     if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
2272           \ && empty(get(s:hash_colors, hash))
2273       let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
2274       let color = csapprox#per_component#Approximate(r, g, b)
2275       if color == 16 && &background ==# 'dark'
2276         let color = 8
2277       endif
2278       let s:hash_colors[hash] = ' ctermfg='.color
2279     else
2280       let s:hash_colors[hash] = ''
2281     endif
2282     exe 'syn match FugitiveblameHash'.hash.'       "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
2283   endfor
2284   call s:RehighlightBlame()
2285 endfunction
2287 function! s:RehighlightBlame() abort
2288   for [hash, cterm] in items(s:hash_colors)
2289     if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
2290       exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
2291     else
2292       exe 'hi link FugitiveblameHash'.hash.' Identifier'
2293     endif
2294   endfor
2295 endfunction
2297 " Section: Gbrowse
2299 call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
2301 let s:redirects = {}
2303 function! s:Browse(bang,line1,count,...) abort
2304   try
2305     let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
2306     if a:0
2307       let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
2308       let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
2309     else
2310       let remote = ''
2311       let rev = ''
2312     endif
2313     if rev ==# ''
2314       let expanded = s:buffer().rev()
2315     elseif rev ==# ':'
2316       let expanded = s:buffer().path('/')
2317     else
2318       let expanded = s:buffer().expand(rev)
2319     endif
2320     let full = s:repo().translate(expanded)
2321     let commit = ''
2322     if full =~# '^fugitive://'
2323       let commit = matchstr(full,'://.*//\zs\w\w\+')
2324       let path = matchstr(full,'://.*//\w\+\zs/.*')
2325       if commit =~ '..'
2326         let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
2327         let branch = matchstr(expanded, '^[^:]*')
2328       else
2329         let type = 'blob'
2330       endif
2331       let path = path[1:-1]
2332     elseif s:repo().bare()
2333       let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
2334       let type = ''
2335     else
2336       let path = full[strlen(s:repo().tree())+1:-1]
2337       if path =~# '^\.git/'
2338         let type = ''
2339       elseif isdirectory(full)
2340         let type = 'tree'
2341       else
2342         let type = 'blob'
2343       endif
2344     endif
2345     if type ==# 'tree' && !empty(path)
2346       let path = s:sub(path, '/\=$', '/')
2347     endif
2348     if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
2349       let body = readfile(s:repo().dir(path[5:-1]))[0]
2350       if body =~# '^\x\{40\}$'
2351         let commit = body
2352         let type = 'commit'
2353         let path = ''
2354       elseif body =~# '^ref: refs/'
2355         let path = '.git/' . matchstr(body,'ref: \zs.*')
2356       endif
2357     endif
2359     let merge = ''
2360     if path =~# '^\.git/refs/remotes/.'
2361       if empty(remote)
2362         let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
2363         let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
2364       else
2365         let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
2366         let path = '.git/refs/heads/'.merge
2367       endif
2368     elseif path =~# '^\.git/refs/heads/.'
2369       let branch = path[16:-1]
2370     elseif !exists('branch')
2371       let branch = s:repo().head()
2372     endif
2373     if !empty(branch)
2374       let r = s:repo().git_chomp('config','branch.'.branch.'.remote')
2375       let m = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1]
2376       if r ==# '.' && !empty(m)
2377         let r2 = s:repo().git_chomp('config','branch.'.m.'.remote')
2378         if r2 !~# '^\.\=$'
2379           let r = r2
2380           let m = s:repo().git_chomp('config','branch.'.m.'.merge')[11:-1]
2381         endif
2382       endif
2383       if empty(remote)
2384         let remote = r
2385       endif
2386       if r ==# '.' || r ==# remote
2387         let merge = m
2388         if path =~# '^\.git/refs/heads/.'
2389           let path = '.git/refs/heads/'.merge
2390         endif
2391       endif
2392     endif
2394     if empty(commit) && path !~# '^\.git/'
2395       if a:line1 && !a:count && !empty(merge)
2396         let commit = merge
2397       else
2398         let commit = s:repo().rev_parse('HEAD')
2399       endif
2400     endif
2402     if empty(remote)
2403       let remote = '.'
2404       let remote_for_url = 'origin'
2405     else
2406       let remote_for_url = remote
2407     endif
2408     if fugitive#git_version() =~# '^[01]\.\|^2\.[0-6]\.'
2409       let raw = s:repo().git_chomp('config','remote.'.remote_for_url.'.url')
2410     else
2411       let raw = s:repo().git_chomp('remote','get-url',remote_for_url)
2412     endif
2413     if raw ==# ''
2414       let raw = remote
2415     endif
2417     if raw =~# '^https\=://' && s:executable('curl')
2418       if !has_key(s:redirects, raw)
2419         let s:redirects[raw] = matchstr(system('curl -I ' .
2420               \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
2421               \ 'Location: \zs\S\+\ze/info/refs?')
2422       endif
2423       if len(s:redirects[raw])
2424         let raw = s:redirects[raw]
2425       endif
2426     endif
2428     for Handler in g:fugitive_browse_handlers
2429       let url = call(Handler, [{
2430             \ 'repo': s:repo(),
2431             \ 'remote': raw,
2432             \ 'revision': 'No longer provided',
2433             \ 'commit': commit,
2434             \ 'path': path,
2435             \ 'type': type,
2436             \ 'line1': a:count > 0 ? a:line1 : 0,
2437             \ 'line2': a:count > 0 ? a:count : 0}])
2438       if !empty(url)
2439         break
2440       endif
2441     endfor
2443     if empty(url) && raw ==# '.'
2444       call s:throw("Instaweb failed to start")
2445     elseif empty(url)
2446       call s:throw("'".remote."' is not a supported remote")
2447     endif
2449     let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
2450     if a:bang
2451       if has('clipboard')
2452         let @+ = url
2453       endif
2454       return 'echomsg '.string(url)
2455     elseif exists(':Browse') == 2
2456       return 'echomsg '.string(url).'|Browse '.url
2457     else
2458       if !exists('g:loaded_netrw')
2459         runtime! autoload/netrw.vim
2460       endif
2461       if exists('*netrw#BrowseX')
2462         return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
2463       else
2464         return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
2465       endif
2466     endif
2467   catch /^fugitive:/
2468     return 'echoerr v:errmsg'
2469   endtry
2470 endfunction
2472 function! s:github_url(opts, ...) abort
2473   if a:0 || type(a:opts) != type({})
2474     return ''
2475   endif
2476   let domain_pattern = 'github\.com'
2477   let domains = exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
2478   for domain in domains
2479     let domain_pattern .= '\|' . escape(split(domain, '://')[-1], '.')
2480   endfor
2481   let repo = matchstr(get(a:opts, 'remote'), '^\%(https\=://\|git://\|git@\)\=\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
2482   if repo ==# ''
2483     return ''
2484   endif
2485   call s:warn('Install rhubarb.vim for GitHub support')
2486   return 'https://github.com/tpope/vim-rhubarb'
2487 endfunction
2489 function! s:instaweb_url(opts) abort
2490   if a:opts.remote !=# '.'
2491     return ''
2492   endif
2493   let output = a:opts.repo.git_chomp('instaweb','-b','unknown')
2494   if output =~# 'http://'
2495     let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:opts.repo.dir(),':t')
2496   else
2497     return ''
2498   endif
2499   if a:opts.path =~# '^\.git/refs/.'
2500     return root . ';a=shortlog;h=' . matchstr(a:opts.path,'^\.git/\zs.*')
2501   elseif a:opts.path =~# '^\.git\>'
2502     return root
2503   endif
2504   let url = root
2505   if a:opts.commit =~# '^\x\{40\}$'
2506     if a:opts.type ==# 'commit'
2507       let url .= ';a=commit'
2508     endif
2509     let url .= ';h=' . a:opts.repo.rev_parse(a:opts.commit . (a:opts.path == '' ? '' : ':' . a:opts.path))
2510   else
2511     if a:opts.type ==# 'blob' && empty(a:opts.commit)
2512       let url .= ';h='.a:opts.repo.git_chomp('hash-object', '-w', a:opts.path)
2513     else
2514       try
2515         let url .= ';h=' . a:opts.repo.rev_parse((a:opts.commit == '' ? 'HEAD' : ':' . a:opts.commit) . ':' . a:opts.path)
2516       catch /^fugitive:/
2517         call s:throw('fugitive: cannot browse uncommitted file')
2518       endtry
2519     endif
2520     let root .= ';hb=' . matchstr(a:opts.repo.head_ref(),'[^ ]\+$')
2521   endif
2522   if a:opts.path !=# ''
2523     let url .= ';f=' . a:opts.path
2524   endif
2525   if get(a:opts, 'line1')
2526     let url .= '#l' . a:opts.line1
2527   endif
2528   return url
2529 endfunction
2531 if !exists('g:fugitive_browse_handlers')
2532   let g:fugitive_browse_handlers = []
2533 endif
2535 call extend(g:fugitive_browse_handlers,
2536       \ [s:function('s:github_url'), s:function('s:instaweb_url')])
2538 " Section: File access
2540 function! s:ReplaceCmd(cmd,...) abort
2541   let fn = expand('%:p')
2542   let tmp = tempname()
2543   let prefix = ''
2544   try
2545     if a:0 && a:1 != ''
2546       if s:winshell()
2547         let old_index = $GIT_INDEX_FILE
2548         let $GIT_INDEX_FILE = a:1
2549       else
2550         let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
2551       endif
2552     endif
2553     let redir = ' > '.tmp
2554     if &shellpipe =~ '2>&1'
2555       let redir .= ' 2>&1'
2556     endif
2557     if s:winshell()
2558       let cmd_escape_char = &shellxquote == '(' ?  '^' : '^^^'
2559       call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').redir.'"')
2560     elseif &shell =~# 'fish'
2561       call system(' begin;'.prefix.a:cmd.redir.';end ')
2562     else
2563       call system(' ('.prefix.a:cmd.redir.') ')
2564     endif
2565   finally
2566     if exists('old_index')
2567       let $GIT_INDEX_FILE = old_index
2568     endif
2569   endtry
2570   silent exe 'doau BufReadPre '.s:fnameescape(fn)
2571   silent exe 'keepalt file '.tmp
2572   try
2573     silent noautocmd edit!
2574   finally
2575     try
2576       silent exe 'keepalt file '.s:fnameescape(fn)
2577     catch /^Vim\%((\a\+)\)\=:E302/
2578     endtry
2579     call delete(tmp)
2580     if fnamemodify(bufname('$'), ':p') ==# tmp
2581       silent execute 'bwipeout '.bufnr('$')
2582     endif
2583     silent exe 'doau BufReadPost '.s:fnameescape(fn)
2584   endtry
2585 endfunction
2587 function! s:BufReadIndex() abort
2588   if !exists('b:fugitive_display_format')
2589     let b:fugitive_display_format = filereadable(expand('%').'.lock')
2590   endif
2591   let b:fugitive_display_format = b:fugitive_display_format % 2
2592   let b:fugitive_type = 'index'
2593   try
2594     let b:git_dir = s:repo().dir()
2595     setlocal noro ma nomodeline
2596     if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
2597       let index = ''
2598     else
2599       let index = expand('%:p')
2600     endif
2601     if b:fugitive_display_format
2602       call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
2603       set ft=git nospell
2604     else
2605       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2606       let dir = getcwd()
2607       if fugitive#git_version() =~# '^0\|^1\.[1-7]\.'
2608         let cmd = s:repo().git_command('status')
2609       else
2610         let cmd = s:repo().git_command(
2611               \ '-c', 'status.displayCommentPrefix=true',
2612               \ '-c', 'color.status=false',
2613               \ '-c', 'status.short=false',
2614               \ 'status')
2615       endif
2616       try
2617         execute cd s:fnameescape(s:repo().tree())
2618         call s:ReplaceCmd(cmd, index)
2619       finally
2620         execute cd s:fnameescape(dir)
2621       endtry
2622       set ft=gitcommit
2623       set foldtext=fugitive#foldtext()
2624     endif
2625     setlocal ro noma nomod noswapfile
2626     if &bufhidden ==# ''
2627       setlocal bufhidden=delete
2628     endif
2629     call s:JumpInit()
2630     nunmap   <buffer>          P
2631     nunmap   <buffer>          ~
2632     nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
2633     nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
2634     nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
2635     xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
2636     nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
2637     nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
2638     nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>
2639     nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>:echohl WarningMsg<Bar>echo ':Gstatus cA is deprecated in favor of ce'<CR>
2640     nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
2641     nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
2642     nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
2643     nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
2644     nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
2645     nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
2646     nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2647     nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2648     nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2649     nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2650     nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
2651     nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2652     nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2653     xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2654     nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2655     xnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2656     nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
2657     nnoremap <buffer> <silent> r :<C-U>edit<CR>
2658     nnoremap <buffer> <silent> R :<C-U>edit<CR>
2659     nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
2660     nnoremap <buffer> <silent> g?   :help fugitive-:Gstatus<CR>
2661     nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
2662   catch /^fugitive:/
2663     return 'echoerr v:errmsg'
2664   endtry
2665 endfunction
2667 function! s:FileRead() abort
2668   try
2669     let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
2670     let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
2671     let hash = repo.rev_parse(path)
2672     if path =~ '^:'
2673       let type = 'blob'
2674     else
2675       let type = repo.git_chomp('cat-file','-t',hash)
2676     endif
2677     " TODO: use count, if possible
2678     return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
2679   catch /^fugitive:/
2680     return 'echoerr v:errmsg'
2681   endtry
2682 endfunction
2684 function! s:BufReadIndexFile() abort
2685   try
2686     let b:fugitive_type = 'blob'
2687     let b:git_dir = s:repo().dir()
2688     try
2689       call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
2690     finally
2691       if &bufhidden ==# ''
2692         setlocal bufhidden=delete
2693       endif
2694       setlocal noswapfile
2695     endtry
2696     return ''
2697   catch /^fugitive: rev-parse/
2698     silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
2699     return ''
2700   catch /^fugitive:/
2701     return 'echoerr v:errmsg'
2702   endtry
2703 endfunction
2705 function! s:BufWriteIndexFile() abort
2706   let tmp = tempname()
2707   try
2708     let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
2709     let stage = matchstr(expand('<amatch>'),'//\zs\d')
2710     silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
2711     let sha1 = readfile(tmp)[0]
2712     let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
2713     if old_mode == ''
2714       let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
2715     endif
2716     let info = old_mode.' '.sha1.' '.stage."\t".path
2717     call writefile([info],tmp)
2718     if s:winshell()
2719       let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
2720     else
2721       let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
2722     endif
2723     if v:shell_error == 0
2724       setlocal nomodified
2725       if exists('#BufWritePost')
2726         execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
2727       endif
2728       call fugitive#reload_status()
2729       return ''
2730     else
2731       return 'echoerr '.string('fugitive: '.error)
2732     endif
2733   finally
2734     call delete(tmp)
2735   endtry
2736 endfunction
2738 function! s:BufReadObject() abort
2739   try
2740     setlocal noro ma
2741     let b:git_dir = s:repo().dir()
2742     let hash = s:buffer().sha1()
2743     if !exists("b:fugitive_type")
2744       let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
2745     endif
2746     if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
2747       return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
2748     endif
2749     if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
2750       let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
2751     endif
2753     if b:fugitive_type !=# 'blob'
2754       setlocal nomodeline
2755     endif
2757     let pos = getpos('.')
2758     silent keepjumps %delete_
2759     setlocal endofline
2761     try
2762       if b:fugitive_type ==# 'tree'
2763         let b:fugitive_display_format = b:fugitive_display_format % 2
2764         if b:fugitive_display_format
2765           call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
2766         else
2767           call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
2768         endif
2769       elseif b:fugitive_type ==# 'tag'
2770         let b:fugitive_display_format = b:fugitive_display_format % 2
2771         if b:fugitive_display_format
2772           call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2773         else
2774           call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
2775         endif
2776       elseif b:fugitive_type ==# 'commit'
2777         let b:fugitive_display_format = b:fugitive_display_format % 2
2778         if b:fugitive_display_format
2779           call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2780         else
2781           call s:ReplaceCmd(s:repo().git_command('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',hash))
2782           keepjumps call search('^parent ')
2783           if getline('.') ==# 'parent '
2784             silent keepjumps delete_
2785           else
2786             silent exe 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
2787           endif
2788           keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2789           if lnum
2790             silent keepjumps delete_
2791           end
2792           silent keepjumps 1,/^diff --git\|\%$/g/\r$/s///
2793           keepjumps 1
2794         endif
2795       elseif b:fugitive_type ==# 'blob'
2796         call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2797         setlocal nomodeline
2798       endif
2799     finally
2800       keepjumps call setpos('.',pos)
2801       setlocal ro noma nomod noswapfile
2802       if &bufhidden ==# ''
2803         setlocal bufhidden=delete
2804       endif
2805       if b:fugitive_type !=# 'blob'
2806         setlocal filetype=git foldmethod=syntax
2807         nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
2808         nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
2809       else
2810         call s:JumpInit()
2811       endif
2812     endtry
2814     return ''
2815   catch /^fugitive: rev-parse/
2816     return ''
2817   catch /^fugitive:/
2818     return 'echoerr v:errmsg'
2819   endtry
2820 endfunction
2822 augroup fugitive_files
2823   autocmd!
2824   autocmd BufReadCmd  index{,.lock}
2825         \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
2826         \   exe s:BufReadIndex() |
2827         \ elseif filereadable(expand('<amatch>')) |
2828         \   read <amatch> |
2829         \   1delete |
2830         \ endif
2831   autocmd FileReadCmd fugitive://**//[0-3]/**          exe s:FileRead()
2832   autocmd BufReadCmd  fugitive://**//[0-3]/**          exe s:BufReadIndexFile()
2833   autocmd BufWriteCmd fugitive://**//[0-3]/**          exe s:BufWriteIndexFile()
2834   autocmd BufReadCmd  fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
2835   autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
2836   autocmd FileType git
2837         \ if exists('b:git_dir') |
2838         \  call s:JumpInit() |
2839         \ endif
2840   autocmd FileType git,gitcommit,gitrebase
2841         \ if exists('b:git_dir') |
2842         \   call s:GFInit() |
2843         \ endif
2844 augroup END
2846 " Section: Temp files
2848 if !exists('s:temp_files')
2849   let s:temp_files = {}
2850 endif
2852 augroup fugitive_temp
2853   autocmd!
2854   autocmd BufNewFile,BufReadPost *
2855         \ if has_key(s:temp_files,s:cpath(expand('<afile>:p'))) |
2856         \   let b:git_dir = s:temp_files[s:cpath(expand('<afile>:p'))].dir |
2857         \   let b:git_type = 'temp' |
2858         \   let b:git_args = s:temp_files[s:cpath(expand('<afile>:p'))].args |
2859         \   call fugitive#detect(expand('<afile>:p')) |
2860         \   setlocal bufhidden=delete nobuflisted |
2861         \   nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>|
2862         \ endif
2863 augroup END
2865 " Section: Go to file
2867 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
2868 function! s:GFInit(...) abort
2869   cnoremap <buffer> <expr> <Plug><cfile> fugitive#cfile()
2870   if !exists('g:fugitive_no_maps')
2871     call s:map('n', 'gf',          '<SID>:find <Plug><cfile><CR>', '<silent><unique>')
2872     call s:map('n', '<C-W>f',     '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>')
2873     call s:map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>')
2874     call s:map('n', '<C-W>gf',  '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>')
2875     call s:map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>')
2876   endif
2877 endfunction
2879 function! s:JumpInit(...) abort
2880   nnoremap <buffer> <silent> <CR>    :<C-U>exe <SID>GF("edit")<CR>
2881   if !&modifiable
2882     nnoremap <buffer> <silent> o     :<C-U>exe <SID>GF("split")<CR>
2883     nnoremap <buffer> <silent> S     :<C-U>exe <SID>GF("vsplit")<CR>
2884     nnoremap <buffer> <silent> O     :<C-U>exe <SID>GF("tabedit")<CR>
2885     nnoremap <buffer> <silent> -     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().up(v:count1))<Bar> if fugitive#buffer().type('tree')<Bar>call search('^'.escape(expand('#:t'),'.*[]~\').'/\=$','wc')<Bar>endif<CR>
2886     nnoremap <buffer> <silent> P     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
2887     nnoremap <buffer> <silent> ~     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
2888     nnoremap <buffer> <silent> C     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2889     nnoremap <buffer> <silent> cc    :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2890     nnoremap <buffer> <silent> co    :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
2891     nnoremap <buffer> <silent> cS    :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
2892     nnoremap <buffer> <silent> cO    :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
2893     nnoremap <buffer> <silent> cP    :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
2894     nnoremap <buffer>          .     : <C-R>=fnameescape(<SID>recall())<CR><Home>
2895   endif
2896 endfunction
2898 function! s:cfile() abort
2899   try
2900     let buffer = s:buffer()
2901     let myhash = buffer.sha1()
2902     if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
2903       let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
2904     endif
2906     if buffer.type('tree')
2907       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2908       if showtree && line('.') > 2
2909         return [buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$','')]
2910       elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
2911         return [buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
2912       endif
2914     elseif buffer.type('blob')
2915       let ref = expand("<cfile>")
2916       try
2917         let sha1 = buffer.repo().rev_parse(ref)
2918       catch /^fugitive:/
2919       endtry
2920       if exists('sha1')
2921         return [ref]
2922       endif
2924     else
2926       let dcmds = []
2928       " Index
2929       if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
2930         let ref = matchstr(getline('.'),'\x\{40\}')
2931         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
2932         return [file]
2934       elseif getline('.') =~# '^#\trenamed:.* -> '
2935         let file = '/'.matchstr(getline('.'),' -> \zs.*')
2936         return [file]
2937       elseif getline('.') =~# '^#\t\(\k\| \)\+\p\?: *.'
2938         let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
2939         return [file]
2940       elseif getline('.') =~# '^#\t.'
2941         let file = '/'.matchstr(getline('.'),'#\t\zs.*')
2942         return [file]
2943       elseif getline('.') =~# ': needs merge$'
2944         let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
2945         return [file, 'Gdiff!']
2947       elseif getline('.') ==# '# Not currently on any branch.'
2948         return ['HEAD']
2949       elseif getline('.') =~# '^# On branch '
2950         let file = 'refs/heads/'.getline('.')[12:]
2951         return [file]
2952       elseif getline('.') =~# "^# Your branch .*'"
2953         let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
2954         return [file]
2955       endif
2957       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2959       if getline('.') =~# '^ref: '
2960         let ref = strpart(getline('.'),5)
2962       elseif getline('.') =~# '^commit \x\{40\}\>'
2963         let ref = matchstr(getline('.'),'\x\{40\}')
2964         return [ref]
2966       elseif getline('.') =~# '^parent \x\{40\}\>'
2967         let ref = matchstr(getline('.'),'\x\{40\}')
2968         let line = line('.')
2969         let parent = 0
2970         while getline(line) =~# '^parent '
2971           let parent += 1
2972           let line -= 1
2973         endwhile
2974         return [ref]
2976       elseif getline('.') =~ '^tree \x\{40\}$'
2977         let ref = matchstr(getline('.'),'\x\{40\}')
2978         if s:repo().rev_parse(myhash.':') == ref
2979           let ref = myhash.':'
2980         endif
2981         return [ref]
2983       elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
2984         let ref = matchstr(getline('.'),'\x\{40\}')
2985         let type = matchstr(getline(line('.')+1),'type \zs.*')
2987       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
2988         let ref = buffer.rev()
2990       elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
2991         let ref = matchstr(getline('.'),'\x\{40\}')
2992         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
2994       elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
2995         let ref = getline('.')[4:]
2997       elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
2998         let type = getline('.')[0]
2999         let lnum = line('.') - 1
3000         let offset = 0
3001         while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
3002           if getline(lnum) =~# '^[ '.type.']'
3003             let offset += 1
3004           endif
3005           let lnum -= 1
3006         endwhile
3007         let offset += matchstr(getline(lnum), type.'\zs\d\+')
3008         let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
3009         let dcmds = [offset, 'normal!zv']
3011       elseif getline('.') =~# '^rename from '
3012         let ref = 'a/'.getline('.')[12:]
3013       elseif getline('.') =~# '^rename to '
3014         let ref = 'b/'.getline('.')[10:]
3016       elseif getline('.') =~# '^@@ -\d\+,\d\+ +\d\+,'
3017         let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
3018         let offset = matchstr(getline('.'), '+\zs\d\+')
3020         let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3021         let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3022         let dcmd = 'Gdiff! +'.offset
3024       elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3025         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3026         let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3027         let dcmd = 'Gdiff!'
3029       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3030         let line = getline(line('.')-1)
3031         let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3032         let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3033         let dcmd = 'Gdiff!'
3035       elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
3036         let ref = getline('.')
3038       elseif expand('<cword>') =~# '^\x\{7,40\}\>'
3039         return [expand('<cword>')]
3041       else
3042         let ref = ''
3043       endif
3045       let prefixes = {
3046             \ '1': '',
3047             \ '2': '',
3048             \ 'b': ':0:',
3049             \ 'i': ':0:',
3050             \ 'o': '',
3051             \ 'w': ''}
3053       if len(myhash)
3054         let prefixes.a = myhash.'^:'
3055         let prefixes.b = myhash.':'
3056       endif
3057       let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3058       if exists('dref')
3059         let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3060       endif
3062       if ref ==# '/dev/null'
3063         " Empty blob
3064         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
3065       endif
3067       if exists('dref')
3068         return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
3069       elseif ref != ""
3070         return [ref] + dcmds
3071       endif
3073     endif
3074     return []
3075   endtry
3076 endfunction
3078 function! s:GF(mode) abort
3079   try
3080     let results = s:cfile()
3081   catch /^fugitive:/
3082     return 'echoerr v:errmsg'
3083   endtry
3084   if len(results)
3085     return s:Edit(a:mode, 0, results[0]).join(map(results[1:-1], '"|".v:val'), '')
3086   else
3087     return ''
3088   endif
3089 endfunction
3091 function! fugitive#cfile() abort
3092   let pre = ''
3093   let results = s:cfile()
3094   if empty(results)
3095     let cfile = expand('<cfile>')
3096     if &includeexpr =~# '\<v:fname\>'
3097       sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
3098     endif
3099     return cfile
3100   elseif len(results) > 1
3101     let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
3102   endif
3103   return pre . s:fnameescape(fugitive#repo().translate(results[0]))
3104 endfunction
3106 function! fugitive#Cfile() abort
3107   return fugitive#cfile()
3108 endfunction
3110 " Section: Statusline
3112 function! s:repo_head_ref() dict abort
3113   if !filereadable(self.dir('HEAD'))
3114     return ''
3115   endif
3116   return readfile(self.dir('HEAD'))[0]
3117 endfunction
3119 call s:add_methods('repo',['head_ref'])
3121 function! fugitive#statusline(...) abort
3122   if !exists('b:git_dir')
3123     return ''
3124   endif
3125   let status = ''
3126   if s:buffer().commit() != ''
3127     let status .= ':' . s:buffer().commit()[0:7]
3128   endif
3129   let status .= '('.fugitive#head(7).')'
3130   if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
3131     return ',GIT'.status
3132   else
3133     return '[Git'.status.']'
3134   endif
3135 endfunction
3137 function! fugitive#Statusline(...) abort
3138   return fugitive#statusline()
3139 endfunction
3141 function! FugitiveStatusline(...) abort
3142   return fugitive#statusline()
3143 endfunction
3145 function! fugitive#head(...) abort
3146   if !exists('b:git_dir')
3147     return ''
3148   endif
3150   return s:repo().head(a:0 ? a:1 : 0)
3151 endfunction
3153 function! FugitiveHead(...) abort
3154   return fugitive#head(a:0 ? a:1 : 0)
3155 endfunction
3157 augroup fugitive_statusline
3158   autocmd!
3159   autocmd User Flags call Hoist('buffer', function('fugitive#statusline'))
3160 augroup END
3162 " Section: Folding
3164 function! fugitive#foldtext() abort
3165   if &foldmethod !=# 'syntax'
3166     return foldtext()
3167   endif
3169   let line_foldstart = getline(v:foldstart)
3170   if line_foldstart =~# '^diff '
3171     let [add, remove] = [-1, -1]
3172     let filename = ''
3173     for lnum in range(v:foldstart, v:foldend)
3174       let line = getline(lnum)
3175       if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
3176         let filename = line[6:-1]
3177       endif
3178       if line =~# '^+'
3179         let add += 1
3180       elseif line =~# '^-'
3181         let remove += 1
3182       elseif line =~# '^Binary '
3183         let binary = 1
3184       endif
3185     endfor
3186     if filename ==# ''
3187       let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
3188     endif
3189     if filename ==# ''
3190       let filename = line_foldstart[5:-1]
3191     endif
3192     if exists('binary')
3193       return 'Binary: '.filename
3194     else
3195       return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3196     endif
3197   elseif line_foldstart =~# '^# .*:$'
3198     let lines = getline(v:foldstart, v:foldend)
3199     call filter(lines, 'v:val =~# "^#\t"')
3200     cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
3201     cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
3202     return line_foldstart.' '.join(lines, ', ')
3203   endif
3204   return foldtext()
3205 endfunction
3207 function! fugitive#Foldtext() abort
3208   return fugitive#foldtext()
3209 endfunction
3211 augroup fugitive_foldtext
3212   autocmd!
3213   autocmd User Fugitive
3214         \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3215         \    set foldtext=fugitive#Foldtext() |
3216         \ endif
3217 augroup END